diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 9b36cfed..2d6e6625 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -664,6 +664,46 @@ final class PlayerModel: ObservableObject { backend.closePiP() } + var pipImage: String { + transitioningToPiP ? "pip.fill" : pipController?.isPictureInPictureActive ?? false ? "pip.exit" : "pip.enter" + } + + var fullscreenImage: String { + playingFullScreen ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right" + } + + func toggleFullScreenAction() { + toggleFullscreen(playingFullScreen, showControls: false) + } + + func togglePiPAction() { + (pipController?.isPictureInPictureActive ?? false) ? closePiP() : startPiP() + } + + #if os(iOS) + var lockOrientationImage: String { + lockedOrientation.isNil ? "lock.rotation.open" : "lock.rotation" + } + + func lockOrientationAction() { + if lockedOrientation.isNil { + let orientationMask = OrientationTracker.shared.currentInterfaceOrientationMask + lockedOrientation = orientationMask + let orientation = OrientationTracker.shared.currentInterfaceOrientation + Orientation.lockOrientation(orientationMask, andRotateTo: .landscapeLeft) + // iOS 16 workaround + Orientation.lockOrientation(orientationMask, andRotateTo: orientation) + } else { + lockedOrientation = nil + Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation) + } + } + #endif + + func replayAction() { + backend.seek(to: 0.0, seekType: .userInteracted) + } + func handleQueueChange() { Defaults[.queue] = queue diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index b497f08b..43c44665 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -201,6 +201,12 @@ extension Defaults.Keys { static let actionButtonSettingsEnabled = Key("actionButtonSettingsEnabled", default: true) static let actionButtonHideEnabled = Key("actionButtonHideEnabled", default: false) static let actionButtonCloseEnabled = Key("actionButtonCloseEnabled", default: true) + static let actionButtonFullScreenEnabled = Key("actionButtonFullScreenEnabled", default: false) + static let actionButtonPipEnabled = Key("actionButtonPipEnabled", default: false) + static let actionButtonLockOrientationEnabled = Key("actionButtonLockOrientationEnabled", default: false) + static let actionButtonRestartEnabled = Key("actionButtonRestartEnabled", default: false) + static let actionButtonAdvanceToNextItemEnabled = Key("actionButtonAdvanceToNextItemEnabled", default: false) + static let actionButtonMusicModeEnabled = Key("actionButtonMusicModeEnabled", default: true) #if os(iOS) static let playerControlsLockOrientationEnabled = Key("playerControlsLockOrientationEnabled", default: true) @@ -215,7 +221,7 @@ extension Defaults.Keys { static let playerControlsRestartEnabled = Key("playerControlsRestartEnabled", default: false) static let playerControlsAdvanceToNextEnabled = Key("playerControlsAdvanceToNextEnabled", default: false) static let playerControlsPlaybackModeEnabled = Key("playerControlsPlaybackModeEnabled", default: false) - static let playerControlsMusicModeEnabled = Key("playerControlsMusicModeEnabled", default: true) + static let playerControlsMusicModeEnabled = Key("playerControlsMusicModeEnabled", default: false) static let mpvCacheSecs = Key("mpvCacheSecs", default: "120") static let mpvCachePauseWait = Key("mpvCachePauseWait", default: "3") diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index e1dcc096..74171140 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -329,7 +329,7 @@ struct PlayerControls: View { var fullscreenButton: some View { button( "Fullscreen", - systemImage: player.playingFullScreen ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right" + systemImage: player.fullscreenImage ) { player.toggleFullscreen(player.playingFullScreen, showControls: false) } @@ -367,28 +367,13 @@ struct PlayerControls: View { } private var pipButton: some View { - let image = player.transitioningToPiP ? "pip.fill" : player.pipController?.isPictureInPictureActive ?? false ? "pip.exit" : "pip.enter" - return button("PiP", systemImage: image) { - (player.pipController?.isPictureInPictureActive ?? false) ? player.closePiP() : player.startPiP() - } - .disabled(!player.pipPossible) + button("PiP", systemImage: player.pipImage, action: player.togglePiPAction) + .disabled(!player.pipPossible) } #if os(iOS) private var lockOrientationButton: some View { - button("Lock Rotation", systemImage: player.lockedOrientation.isNil ? "lock.rotation.open" : "lock.rotation", active: !player.lockedOrientation.isNil) { - if player.lockedOrientation.isNil { - let orientationMask = OrientationTracker.shared.currentInterfaceOrientationMask - player.lockedOrientation = orientationMask - let orientation = OrientationTracker.shared.currentInterfaceOrientation - Orientation.lockOrientation(orientationMask, andRotateTo: .landscapeLeft) - // iOS 16 workaround - Orientation.lockOrientation(orientationMask, andRotateTo: orientation) - } else { - player.lockedOrientation = nil - Orientation.lockOrientation(.allButUpsideDown, andRotateTo: OrientationTracker.shared.currentInterfaceOrientation) - } - } + button("Lock Rotation", systemImage: player.lockOrientationImage, active: !player.lockedOrientation.isNil, action: player.lockOrientationAction) } #endif @@ -456,9 +441,7 @@ struct PlayerControls: View { } private var restartVideoButton: some View { - button("Restart video", systemImage: "backward.end.fill", cornerRadius: 5) { - player.backend.seek(to: 0.0, seekType: .userInteracted) - } + button("Restart video", systemImage: "backward.end.fill", cornerRadius: 5, action: player.replayAction) } private var togglePlayButton: some View { diff --git a/Shared/Player/Video Details/VideoActions.swift b/Shared/Player/Video Details/VideoActions.swift index 53770681..70963429 100644 --- a/Shared/Player/Video Details/VideoActions.swift +++ b/Shared/Player/Video Details/VideoActions.swift @@ -6,6 +6,14 @@ struct VideoActions: View { case share case addToPlaylist case subscribe + case fullScreen + case pip + #if os(iOS) + case lockOrientation + #endif + case restart + case advanceToNextItem + case musicMode case settings case hide case close @@ -24,6 +32,12 @@ struct VideoActions: View { @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled @Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled + @Default(.actionButtonFullScreenEnabled) private var actionButtonFullScreenEnabled + @Default(.actionButtonPipEnabled) private var actionButtonPipEnabled + @Default(.actionButtonLockOrientationEnabled) private var actionButtonLockOrientationEnabled + @Default(.actionButtonRestartEnabled) private var actionButtonRestartEnabled + @Default(.actionButtonAdvanceToNextItemEnabled) private var actionButtonAdvanceToNextItemEnabled + @Default(.actionButtonMusicModeEnabled) private var actionButtonMusicModeEnabled @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled @@ -51,6 +65,20 @@ struct VideoActions: View { return actionButtonSubscribeEnabled case .settings: return actionButtonSettingsEnabled + case .fullScreen: + return actionButtonFullScreenEnabled + case .pip: + return actionButtonPipEnabled + #if os(iOS) + case .lockOrientation: + return actionButtonLockOrientationEnabled + #endif + case .restart: + return actionButtonRestartEnabled + case .advanceToNextItem: + return actionButtonAdvanceToNextItemEnabled + case .musicMode: + return actionButtonMusicModeEnabled case .hide: return actionButtonHideEnabled case .close: @@ -68,6 +96,8 @@ struct VideoActions: View { return !(video?.isLocal ?? true) && accounts.signedIn && accounts.app.supportsSubscriptions case .settings: return video != nil + case .advanceToNextItem: + return player.isAdvanceToNextItemAvailable default: return true } @@ -110,6 +140,23 @@ struct VideoActions: View { } } } + case .fullScreen: + actionButton("Fullscreen", systemImage: player.fullscreenImage, action: player.toggleFullScreenAction) + case .pip: + actionButton("PiP", systemImage: player.pipImage, action: player.togglePiPAction) + #if os(iOS) + case .lockOrientation: + actionButton("Lock", systemImage: player.lockOrientationImage, active: player.lockedOrientation != nil, action: player.lockOrientationAction) + #endif + case .restart: + actionButton("Replay", systemImage: "backward.end.fill", action: player.replayAction) + case .advanceToNextItem: + actionButton("Next", systemImage: "forward.fill") { + player.advanceToNextItem() + } + case .musicMode: + actionButton("Music", systemImage: "music.note", active: player.musicMode, action: player.toggleMusicMode) + case .settings: actionButton("Settings", systemImage: "gear") { withAnimation(ControlOverlaysModel.animation) { @@ -138,15 +185,17 @@ struct VideoActions: View { func actionButton( _ name: String, systemImage: String, + active: Bool = false, action: @escaping () -> Void = {} ) -> some View { Button(action: action) { VStack(spacing: 3) { Image(systemName: systemImage) .frame(width: 20, height: 20) + .foregroundColor(active ? Color("AppRedColor") : .accentColor) if playerActionsButtonLabelStyle.text { Text(name.localized()) - .foregroundColor(.secondary) + .foregroundColor(active ? Color("AppRedColor") : .secondary) .font(.caption2) .allowsTightening(true) } diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index 8b482d4c..5393a1a5 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -257,7 +257,7 @@ struct VideoDetails: View { switch page { case .queue: - return !player.queue.isEmpty + return !sidebarQueue && player.isAdvanceToNextItemAvailable default: return !video.isLocal } diff --git a/Shared/Settings/PlayerControlsSettings.swift b/Shared/Settings/PlayerControlsSettings.swift index 4f5a8a06..e67667bf 100644 --- a/Shared/Settings/PlayerControlsSettings.swift +++ b/Shared/Settings/PlayerControlsSettings.swift @@ -18,6 +18,12 @@ struct PlayerControlsSettings: View { @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled + @Default(.actionButtonFullScreenEnabled) private var actionButtonFullScreenEnabled + @Default(.actionButtonPipEnabled) private var actionButtonPipEnabled + @Default(.actionButtonLockOrientationEnabled) private var actionButtonLockOrientationEnabled + @Default(.actionButtonRestartEnabled) private var actionButtonRestartEnabled + @Default(.actionButtonAdvanceToNextItemEnabled) private var actionButtonAdvanceToNextItemEnabled + @Default(.actionButtonMusicModeEnabled) private var actionButtonMusicModeEnabled @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled #if os(iOS) @@ -211,6 +217,7 @@ struct PlayerControlsSettings: View { .labelStyle(.iconOnly) .padding(7) .foregroundColor(.accentColor) + .accessibilityAddTraits(.isButton) #if os(iOS) .background(RoundedRectangle(cornerRadius: 4).strokeBorder(lineWidth: 1).foregroundColor(.accentColor)) #endif @@ -246,6 +253,7 @@ struct PlayerControlsSettings: View { .labelStyle(.iconOnly) .padding(7) .foregroundColor(.accentColor) + .accessibilityAddTraits(.isButton) #if os(iOS) .frame(minHeight: 35) .background(RoundedRectangle(cornerRadius: 4).strokeBorder(lineWidth: 1).foregroundColor(.accentColor)) @@ -265,12 +273,24 @@ struct PlayerControlsSettings: View { } @ViewBuilder private var actionButtonToggles: some View { - Toggle("Share", isOn: $actionButtonShareEnabled) - Toggle("Add to Playlist", isOn: $actionButtonAddToPlaylistEnabled) - Toggle("Subscribe/Unsubscribe", isOn: $actionButtonSubscribeEnabled) - Toggle("Settings", isOn: $actionButtonSettingsEnabled) - Toggle("Hide player", isOn: $actionButtonHideEnabled) - Toggle("Close video", isOn: $actionButtonCloseEnabled) + Group { + Toggle("Share", isOn: $actionButtonShareEnabled) + Toggle("Add to Playlist", isOn: $actionButtonAddToPlaylistEnabled) + Toggle("Subscribe/Unsubscribe", isOn: $actionButtonSubscribeEnabled) + Toggle("Settings", isOn: $actionButtonSettingsEnabled) + Toggle("Fullscreen", isOn: $actionButtonFullScreenEnabled) + Toggle("Picture in Picture", isOn: $actionButtonPipEnabled) + } + Group { + #if os(iOS) + Toggle("Lock orientation", isOn: $actionButtonLockOrientationEnabled) + #endif + Toggle("Restart", isOn: $actionButtonRestartEnabled) + Toggle("Play next item", isOn: $actionButtonAdvanceToNextItemEnabled) + Toggle("Music Mode", isOn: $actionButtonMusicModeEnabled) + Toggle("Hide player", isOn: $actionButtonHideEnabled) + Toggle("Close video", isOn: $actionButtonCloseEnabled) + } } @ViewBuilder private var controlButtonToggles: some View { @@ -283,9 +303,9 @@ struct PlayerControlsSettings: View { #endif Toggle("Restart", isOn: $playerControlsRestartEnabled) Toggle("Play next item", isOn: $playerControlsAdvanceToNextEnabled) - Toggle("Playback mode", isOn: $playerControlsPlaybackModeEnabled) + Toggle("Playback Mode", isOn: $playerControlsPlaybackModeEnabled) #if !os(tvOS) - Toggle("Music mode", isOn: $playerControlsMusicModeEnabled) + Toggle("Music Mode", isOn: $playerControlsMusicModeEnabled) #endif } } diff --git a/Shared/Settings/SettingsView.swift b/Shared/Settings/SettingsView.swift index fedc0f29..bbda8791 100644 --- a/Shared/Settings/SettingsView.swift +++ b/Shared/Settings/SettingsView.swift @@ -247,7 +247,7 @@ struct SettingsView: View { case .player: return 450 case .controls: - return 850 + return 900 case .quality: return 420 case .history: