diff --git a/Fixtures/View+Fixtures.swift b/Fixtures/View+Fixtures.swift index 8dddeb6d..fe593f5b 100644 --- a/Fixtures/View+Fixtures.swift +++ b/Fixtures/View+Fixtures.swift @@ -45,7 +45,7 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier { player.currentItem = PlayerQueueItem( Video( videoID: "", - title: "", + title: "Video Name", author: "", length: 0, published: "2 days ago", diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index fd2f788f..cf3648cb 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -102,8 +102,8 @@ extension Defaults.Keys { static let playerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small static let fullScreenPlayerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small #elseif os(tvOS) - static let playerControlsLayoutDefault = PlayerControlsLayout.veryLarge - static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.veryLarge + static let playerControlsLayoutDefault = PlayerControlsLayout.tvRegular + static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.tvRegular #else static let playerControlsLayoutDefault = PlayerControlsLayout.medium static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.medium @@ -129,7 +129,12 @@ extension Defaults.Keys { #if !os(macOS) static let pauseOnEnteringBackground = Key("pauseOnEnteringBackground", default: true) #endif - static let closeLastItemOnPlaybackEnd = Key("closeLastItemOnPlaybackEnd", default: false) + #if os(tvOS) + static let closeLastItemOnPlaybackEndDefault = true + #else + static let closeLastItemOnPlaybackEndDefault = false + #endif + static let closeLastItemOnPlaybackEnd = Key("closeLastItemOnPlaybackEnd", default: closeLastItemOnPlaybackEndDefault) #if os(tvOS) static let closePlayerOnItemCloseDefault = true diff --git a/Shared/Player/Controls/OSD/Seek.swift b/Shared/Player/Controls/OSD/Seek.swift index c40dcb4a..e1307f8b 100644 --- a/Shared/Player/Controls/OSD/Seek.swift +++ b/Shared/Player/Controls/OSD/Seek.swift @@ -19,9 +19,9 @@ struct Seek: View { var body: some View { Button(action: model.restoreTime) { - VStack(spacing: 2) { + VStack(spacing: playerControlsLayout.osdSpacing) { ProgressBar(value: progress) - .frame(maxHeight: 5) + .frame(maxHeight: playerControlsLayout.osdProgressBarHeight) timeline @@ -37,6 +37,7 @@ struct Seek: View { Text(chapter.title) .multilineTextAlignment(.center) .font(.system(size: playerControlsLayout.chapterFontSize)) + .fixedSize(horizontal: false, vertical: true) } if let segment = projectedSegment { Text(SponsorBlockAPI.categoryDescription(segment.category) ?? "Sponsor") @@ -44,14 +45,15 @@ struct Seek: View { .foregroundColor(Color("AppRedColor")) } } else { - if !model.restoreSeekTime.isNil { - Divider() - Label(model.restoreSeekPlaybackTime, systemImage: "arrow.counterclockwise") - .foregroundColor(.secondary) - .font(.system(size: playerControlsLayout.chapterFontSize).monospacedDigit()) - .frame(height: playerControlsLayout.chapterFontSize + 5) - } - + #if !os(tvOS) + if !model.restoreSeekTime.isNil { + Divider() + Label(model.restoreSeekPlaybackTime, systemImage: "arrow.counterclockwise") + .foregroundColor(.secondary) + .font(.system(size: playerControlsLayout.chapterFontSize).monospacedDigit()) + .frame(height: playerControlsLayout.chapterFontSize + 5) + } + #endif Group { switch model.lastSeekType { case let .segmentSkip(category): @@ -67,7 +69,7 @@ struct Seek: View { } #if os(tvOS) .frame(minWidth: 250, minHeight: 100) - .padding(10) + .padding(30) #endif .frame(maxWidth: playerControlsLayout.seekOSDWidth) .padding(2) diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index 7f6c3f79..08de87cb 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -49,7 +49,7 @@ struct PlayerControls: View { .transition(.opacity) .frame(maxWidth: .infinity, alignment: .topLeading) #if os(tvOS) - .offset(x: 10, y: 5) + .offset(x: 10, y: 10) .focused($focusedField, equals: .seekOSD) .onChange(of: player.playerTime.lastSeekTime) { _ in if !model.presentingControls { @@ -108,8 +108,26 @@ struct PlayerControls: View { Spacer() + if playerControlsLayout.displaysTitleLine { + VStack(alignment: .leading) { + Text(player.currentVideo?.title ?? "Not Playing") + .shadow(radius: 10) + .font(.system(size: playerControlsLayout.titleLineFontSize).bold()) + .lineLimit(1) + + Text(player.currentVideo?.channel.name ?? "") + .fontWeight(.semibold) + .shadow(radius: 10) + .foregroundColor(.secondary) + .font(.system(size: playerControlsLayout.authorLineFontSize)) + .lineLimit(1) + } + + .frame(maxWidth: .infinity, alignment: .leading) + .offset(y: -40) + } + timeline - .frame(maxWidth: 1000) .padding(.bottom, 2) } .zIndex(1) @@ -135,9 +153,6 @@ struct PlayerControls: View { musicModeButton #endif } - #if os(tvOS) - .frame(width: 1200) - #endif .zIndex(0) #if os(tvOS) .offset(y: -playerControlsLayout.timelineHeight - 30) diff --git a/Shared/Player/Controls/PlayerControlsLayout.swift b/Shared/Player/Controls/PlayerControlsLayout.swift index 29ba3a0f..95205f2d 100644 --- a/Shared/Player/Controls/PlayerControlsLayout.swift +++ b/Shared/Player/Controls/PlayerControlsLayout.swift @@ -2,6 +2,7 @@ import Defaults import Foundation enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { + case tvRegular case veryLarge case large case medium @@ -10,6 +11,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var description: String { switch self { + case .tvRegular: + return "TV" case .veryLarge: return "Very Large" default: @@ -19,6 +22,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var buttonsSpacing: Double { switch self { + case .tvRegular: + return 80 case .veryLarge: return 40 case .large: @@ -34,6 +39,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var buttonFontSize: Double { switch self { + case .tvRegular: + return 48 case .veryLarge: return 35 case .large: @@ -49,6 +56,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var bigButtonFontSize: Double { switch self { + case .tvRegular: + return 65 case .veryLarge: return 55 case .large: @@ -64,6 +73,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var buttonSize: Double { switch self { + case .tvRegular: + return 90 case .veryLarge: return 60 case .large: @@ -79,6 +90,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var bigButtonSize: Double { switch self { + case .tvRegular: + return 100 case .veryLarge: return 85 case .large: @@ -94,6 +107,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var segmentFontSize: Double { switch self { + case .tvRegular: + return 20 case .veryLarge: return 16 case .large: @@ -109,6 +124,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var chapterFontSize: Double { switch self { + case .tvRegular: + return 24 case .veryLarge: return 20 case .large: @@ -124,6 +141,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var projectedTimeFontSize: Double { switch self { + case .tvRegular: + return 30 case .veryLarge: return 25 case .large: @@ -139,6 +158,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var thumbSize: Double { switch self { + case .tvRegular: + return 45 case .veryLarge: return 35 case .large: @@ -154,6 +175,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var timeFontSize: Double { switch self { + case .tvRegular: + return 45 case .veryLarge: return 35 case .large: @@ -169,6 +192,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var bufferingStateFontSize: Double { switch self { + case .tvRegular: + return 45 case .veryLarge: return 30 case .large: @@ -184,6 +209,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var timeLeadingEdgePadding: Double { switch self { + case .tvRegular: + return 20 case .veryLarge: return 5 case .large: @@ -199,6 +226,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var timeTrailingEdgePadding: Double { switch self { + case .tvRegular: + return 20 case .veryLarge: return 16 case .large: @@ -214,6 +243,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var timelineHeight: Double { switch self { + case .tvRegular: + return 80 case .veryLarge: return 40 case .large: @@ -229,6 +260,8 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var seekOSDWidth: Double { switch self { + case .tvRegular: + return 240 case .veryLarge: return 240 case .large: @@ -245,4 +278,50 @@ enum PlayerControlsLayout: String, CaseIterable, Defaults.Serializable { var osdVerticalOffset: Double { buttonSize } + + var osdProgressBarHeight: Double { + switch self { + case .tvRegular: + return 20 + case .veryLarge: + return 10 + case .large: + return 8 + case .medium: + return 5 + case .small: + return 5 + case .smaller: + return 2 + } + } + + var osdSpacing: Double { + switch self { + case .tvRegular: + return 8 + case .veryLarge: + return 8 + case .large: + return 6 + case .medium: + return 4 + case .small: + return 2 + case .smaller: + return 2 + } + } + + var displaysTitleLine: Bool { + self == .tvRegular + } + + var titleLineFontSize: Double { + 60 + } + + var authorLineFontSize: Double { + 30 + } } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 9ce62268..1ea52c83 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -60,7 +60,9 @@ struct VideoPlayerView: View { @EnvironmentObject internal var player @EnvironmentObject internal var playerControls @EnvironmentObject internal var recents - @EnvironmentObject internal var search + #if os(macOS) + @EnvironmentObject internal var search + #endif @EnvironmentObject internal var thumbnails @Default(.horizontalPlayerGestureEnabled) var horizontalPlayerGestureEnabled