Skip to content

Commit

Permalink
fix handling and displaying captions
Browse files Browse the repository at this point in the history
fixes #490

It also fixes the caption picker being empty when a caption was selected in the previous watched video.
  • Loading branch information
stonerl committed Apr 20, 2024
1 parent d1cf45c commit b0cfdea
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 128 deletions.
4 changes: 4 additions & 0 deletions Model/Player/Backends/MPVBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions Model/Player/PlayerModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1158,7 +1160,7 @@ final class PlayerModel: ObservableObject {

return nil
}

#if os(macOS)
private func assignKeyPressMonitor() {
keyPressMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { keyEvent -> NSEvent? in
Expand Down Expand Up @@ -1193,7 +1195,7 @@ final class PlayerModel: ObservableObject {
return nil
}
}

private func destroyKeyPressMonitor() {
if let keyPressMonitor = keyPressMonitor {
NSEvent.removeMonitor(keyPressMonitor)
Expand Down
238 changes: 119 additions & 119 deletions Shared/Player/Controls/ControlsOverlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions Shared/Player/PlaybackSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit b0cfdea

Please sign in to comment.