diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index c8457946e..8d265ecc2 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -264,6 +264,10 @@ final class MPVBackend: PlayerBackend { self.startClientUpdates() + // Captions should only be displayed when selected by the user, + // not when the video starts. So, we remove them. + self.client?.removeSubs() + if !preservingTime, !upgrading, let segment = self.model.sponsorBlock.segments.first, diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 6f3aff7a6..28913757c 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -176,7 +176,7 @@ final class PlayerModel: ObservableObject { @Default(.resetWatchedStatusOnPlaying) var resetWatchedStatusOnPlaying @Default(.playerRate) var playerRate @Default(.systemControlsSeekDuration) var systemControlsSeekDuration - + #if os(macOS) @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration @@ -192,7 +192,7 @@ final class PlayerModel: ObservableObject { var onPlayStream = [(Stream) -> Void]() var rateToRestore: Float? private var remoteCommandCenterConfigured = false - + #if os(macOS) var keyPressMonitor: Any? #endif @@ -771,10 +771,12 @@ final class PlayerModel: ObservableObject { func handleCurrentItemChange() { if currentItem == nil { - captions = nil FeedModel.shared.calculateUnwatchedFeed() } + // Captions need to be set to nil on item change, to clear the previus values. + captions = nil + #if os(macOS) Windows.player.window?.title = windowTitle #endif @@ -1158,7 +1160,7 @@ final class PlayerModel: ObservableObject { return nil } - + #if os(macOS) private func assignKeyPressMonitor() { keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in @@ -1193,7 +1195,7 @@ final class PlayerModel: ObservableObject { return nil } } - + private func destroyKeyPressMonitor() { if let keyPressMonitor = keyPressMonitor { NSEvent.removeMonitor(keyPressMonitor) diff --git a/Shared/Player/Controls/ControlsOverlay.swift b/Shared/Player/Controls/ControlsOverlay.swift index d2b6bafb2..596318dbf 100644 --- a/Shared/Player/Controls/ControlsOverlay.swift +++ b/Shared/Player/Controls/ControlsOverlay.swift @@ -11,16 +11,16 @@ struct ControlsOverlay: View { @Default(.qualityProfiles) private var qualityProfiles #if os(tvOS) - enum Field: Hashable { - case qualityProfile - case stream - case increaseRate - case decreaseRate - case captions - } + enum Field: Hashable { + case qualityProfile + case stream + case increaseRate + case decreaseRate + case captions + } - @FocusState private var focusedField: Field? - @State private var presentingButtonHintAlert = false + @FocusState private var focusedField: Field? + @State private var presentingButtonHintAlert = false #endif var body: some View { @@ -94,10 +94,10 @@ struct ControlsOverlay: View { #endif #if os(tvOS) - Text("Press and hold remote button to open captions and quality menus") - .frame(maxWidth: 400) - .font(.caption) - .foregroundColor(.secondary) + Text("Press and hold remote button to open captions and quality menus") + .frame(maxWidth: 400) + .font(.caption) + .foregroundColor(.secondary) #endif } .frame(maxHeight: overlayHeight) @@ -117,9 +117,9 @@ struct ControlsOverlay: View { private var overlayHeight: Double { #if os(tvOS) - contentSize.height + 80.0 + contentSize.height + 80.0 #else - contentSize.height + contentSize.height #endif } @@ -160,26 +160,26 @@ struct ControlsOverlay: View { @ViewBuilder private var rateButton: some View { #if os(macOS) - ratePicker - .labelsHidden() - .frame(maxWidth: 100) + ratePicker + .labelsHidden() + .frame(maxWidth: 100) #elseif os(iOS) - Menu { - ratePicker - } label: { - Text(player.rateLabel(player.currentRate)) - .foregroundColor(.primary) - .frame(width: 123) - } - .transaction { t in t.animation = .none } - .buttonStyle(.plain) - .foregroundColor(.primary) - .frame(width: 123, height: 40) - .modifier(ControlBackgroundModifier()) - .mask(RoundedRectangle(cornerRadius: 3)) - #else + Menu { + ratePicker + } label: { Text(player.rateLabel(player.currentRate)) - .frame(minWidth: 120) + .foregroundColor(.primary) + .frame(width: 123) + } + .transaction { t in t.animation = .none } + .buttonStyle(.plain) + .foregroundColor(.primary) + .frame(width: 123, height: 40) + .modifier(ControlBackgroundModifier()) + .mask(RoundedRectangle(cornerRadius: 3)) + #else + Text(player.rateLabel(player.currentRate)) + .frame(minWidth: 120) #endif } @@ -241,50 +241,50 @@ struct ControlsOverlay: View { private var rateButtonsSpacing: Double { #if os(tvOS) - 10 + 10 #else - 8 + 8 #endif } @ViewBuilder private var qualityProfileButton: some View { #if os(macOS) - qualityProfilePicker - .labelsHidden() - .frame(maxWidth: 300) + qualityProfilePicker + .labelsHidden() + .frame(maxWidth: 300) #elseif os(iOS) - Menu { - qualityProfilePicker - } label: { - Text(player.qualityProfileSelection?.description ?? "Automatic".localized()) - .frame(maxWidth: 240) - } - .transaction { t in t.animation = .none } - .buttonStyle(.plain) - .foregroundColor(.primary) - .frame(maxWidth: 240) - .frame(height: 40) - .modifier(ControlBackgroundModifier()) - .mask(RoundedRectangle(cornerRadius: 3)) + Menu { + qualityProfilePicker + } label: { + Text(player.qualityProfileSelection?.description ?? "Automatic".localized()) + .frame(maxWidth: 240) + } + .transaction { t in t.animation = .none } + .buttonStyle(.plain) + .foregroundColor(.primary) + .frame(maxWidth: 240) + .frame(height: 40) + .modifier(ControlBackgroundModifier()) + .mask(RoundedRectangle(cornerRadius: 3)) #else - ControlsOverlayButton(focusedField: $focusedField, field: .qualityProfile) { - Text(player.qualityProfileSelection?.description ?? "Automatic".localized()) - .lineLimit(1) - .frame(maxWidth: 320) - } - .contextMenu { - Button("Automatic") { player.qualityProfileSelection = nil } - - ForEach(qualityProfiles) { qualityProfile in - Button { - player.qualityProfileSelection = qualityProfile - } label: { - Text(qualityProfile.description) - } + ControlsOverlayButton(focusedField: $focusedField, field: .qualityProfile) { + Text(player.qualityProfileSelection?.description ?? "Automatic".localized()) + .lineLimit(1) + .frame(maxWidth: 320) + } + .contextMenu { + Button("Automatic") { player.qualityProfileSelection = nil } - Button("Cancel", role: .cancel) {} + ForEach(qualityProfiles) { qualityProfile in + Button { + player.qualityProfileSelection = qualityProfile + } label: { + Text(qualityProfile.description) } + + Button("Cancel", role: .cancel) {} } + } #endif } @@ -300,72 +300,72 @@ struct ControlsOverlay: View { @ViewBuilder private var qualityButton: some View { #if os(macOS) - StreamControl() - .labelsHidden() - .frame(maxWidth: 300) + StreamControl() + .labelsHidden() + .frame(maxWidth: 300) #elseif os(iOS) - Menu { - StreamControl() - } label: { - Text(player.streamSelection?.resolutionAndFormat ?? "loading") - .frame(width: 140, height: 40) - .foregroundColor(.primary) - } - .transaction { t in t.animation = .none } + Menu { + StreamControl() + } label: { + Text(player.streamSelection?.resolutionAndFormat ?? "loading") + .frame(width: 140, height: 40) + .foregroundColor(.primary) + } + .transaction { t in t.animation = .none } - .buttonStyle(.plain) - .foregroundColor(.primary) - .frame(width: 240, height: 40) - .modifier(ControlBackgroundModifier()) - .mask(RoundedRectangle(cornerRadius: 3)) + .buttonStyle(.plain) + .foregroundColor(.primary) + .frame(width: 240, height: 40) + .modifier(ControlBackgroundModifier()) + .mask(RoundedRectangle(cornerRadius: 3)) #else - StreamControl(focusedField: $focusedField) + StreamControl(focusedField: $focusedField) #endif } @ViewBuilder private var captionsButton: some View { #if os(macOS) - captionsPicker - .labelsHidden() - .frame(maxWidth: 300) + captionsPicker + .labelsHidden() + .frame(maxWidth: 300) #elseif os(iOS) - Menu { - captionsPicker - } label: { - HStack(spacing: 4) { - Image(systemName: "text.bubble") - if let captions = captionsBinding.wrappedValue { - Text(captions.code) - .foregroundColor(.primary) - } + Menu { + captionsPicker + } label: { + HStack(spacing: 4) { + Image(systemName: "text.bubble") + if let captions = captionsBinding.wrappedValue { + Text(captions.code) + .foregroundColor(.primary) } - .frame(width: 240) - .frame(height: 40) } - .transaction { t in t.animation = .none } - .buttonStyle(.plain) - .foregroundColor(.primary) .frame(width: 240) - .modifier(ControlBackgroundModifier()) - .mask(RoundedRectangle(cornerRadius: 3)) + .frame(height: 40) + } + .transaction { t in t.animation = .none } + .buttonStyle(.plain) + .foregroundColor(.primary) + .frame(width: 240) + .modifier(ControlBackgroundModifier()) + .mask(RoundedRectangle(cornerRadius: 3)) #else - ControlsOverlayButton(focusedField: $focusedField, field: .captions) { - HStack(spacing: 8) { - Image(systemName: "text.bubble") - if let captions = captionsBinding.wrappedValue { - Text(captions.code) - } + ControlsOverlayButton(focusedField: $focusedField, field: .captions) { + HStack(spacing: 8) { + Image(systemName: "text.bubble") + if let captions = captionsBinding.wrappedValue { + Text(captions.code) } - .frame(maxWidth: 320) } - .contextMenu { - Button("Disabled") { captionsBinding.wrappedValue = nil } + .frame(maxWidth: 320) + } + .contextMenu { + Button("Disabled") { captionsBinding.wrappedValue = nil } - ForEach(player.currentVideo?.captions ?? []) { caption in - Button(caption.description) { captionsBinding.wrappedValue = caption } - } - Button("Cancel", role: .cancel) {} + ForEach(player.currentVideo?.captions ?? []) { caption in + Button(caption.description) { captionsBinding.wrappedValue = caption } } + Button("Cancel", role: .cancel) {} + } #endif } @@ -374,12 +374,12 @@ struct ControlsOverlay: View { let captions = player.currentVideo?.captions ?? [] Picker("Captions", selection: captionsBinding) { if captions.isEmpty { - Text("Not available") + Text("Not available").tag(Captions?.none) } else { Text("Disabled").tag(Captions?.none) - } - ForEach(captions) { caption in - Text(caption.description).tag(Optional(caption)) + ForEach(captions) { caption in + Text(caption.description).tag(Optional(caption)) + } } } .disabled(captions.isEmpty) diff --git a/Shared/Player/PlaybackSettings.swift b/Shared/Player/PlaybackSettings.swift index d8ad95461..5ae0da17d 100644 --- a/Shared/Player/PlaybackSettings.swift +++ b/Shared/Player/PlaybackSettings.swift @@ -433,12 +433,12 @@ struct PlaybackSettings: View { let captions = player.currentVideo?.captions ?? [] Picker("Captions".localized(), selection: $player.captions) { if captions.isEmpty { - Text("Not available") + Text("Not available").tag(Captions?.none) } else { Text("Disabled").tag(Captions?.none) - } - ForEach(captions) { caption in - Text(caption.description).tag(Optional(caption)) + ForEach(captions) { caption in + Text(caption.description).tag(Optional(caption)) + } } } .disabled(captions.isEmpty)