mirror of
https://github.com/yattee/yattee.git
synced 2026-08-05 23:01:28 +00:00
Add embedded audio/subtitle track selection for multi-track files
Files with multiple embedded tracks (e.g. MKV from media sources or downloads) previously exposed none of them: mpv's track-list was never read, aid was never set, and sid was only ever no/auto. The quality selector showed no Audio or Subtitles tab for such files. MPVClient now observes track-list/aid/sid and delivers a coalesced, leniently decoded [MPVTrack] snapshot; selection state is derived from the reported "selected" flags. Embedded tracks are switched live via aid/sid (no reload), surfaced in the existing Audio/Subtitles sections alongside external captions on iOS, tvOS and macOS. External sub-add/ audio-add tracks are filtered out to avoid double-listing. User picks are sticky across same-video reloads (quality switch, audio mode, retries) and preferred audio/subtitle languages auto-select a matching embedded track once per load, with external captions taking precedence.
This commit is contained in:
@@ -922,7 +922,8 @@ struct ControlsSectionRenderer: View {
|
||||
@ViewBuilder
|
||||
private var captionsButton: some View {
|
||||
if actions.hasCaptions, actions.onCaptionSelected != nil {
|
||||
controlButton(systemImage: actions.currentCaption != nil ? "captions.bubble.fill" : "captions.bubble") {
|
||||
let subtitlesActive = actions.currentCaption != nil || actions.currentEmbeddedSubtitleTrackID != nil
|
||||
controlButton(systemImage: subtitlesActive ? "captions.bubble.fill" : "captions.bubble") {
|
||||
actions.onShowCaptionsSelector?()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,6 +862,16 @@ extension ExpandedPlayerSheet {
|
||||
onStreamSelected: { [self] stream, audioStream in
|
||||
switchToStream(stream, audioStream: audioStream)
|
||||
},
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
},
|
||||
panscanValue: navigationCoordinator?.pinchPanscan ?? 0.0,
|
||||
isPanscanAllowed: !isPortraitPanelVisible,
|
||||
onTogglePanscan: { [weak navigationCoordinator] in
|
||||
@@ -1132,6 +1142,16 @@ extension ExpandedPlayerSheet {
|
||||
onStreamSelected: { [self] stream, audioStream in
|
||||
switchToStream(stream, audioStream: audioStream)
|
||||
},
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
},
|
||||
panscanValue: navigationCoordinator?.pinchPanscan ?? 0.0,
|
||||
isPanscanAllowed: !isPortraitPanelVisible,
|
||||
onTogglePanscan: { [weak navigationCoordinator] in
|
||||
@@ -1509,6 +1529,16 @@ extension ExpandedPlayerSheet {
|
||||
onStreamSelected: { [self] stream, audioStream in
|
||||
switchToStream(stream, audioStream: audioStream)
|
||||
},
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
},
|
||||
panscanValue: navigationCoordinator?.pinchPanscan ?? 0.0,
|
||||
isPanscanAllowed: !(isPanelPinned && isPanelVisible),
|
||||
onTogglePanscan: { [weak navigationCoordinator] in
|
||||
|
||||
@@ -697,6 +697,10 @@ private struct PlayerSheetsModifier: ViewModifier {
|
||||
currentRate: playerState?.rate ?? .x1,
|
||||
isControlsLocked: playerState?.isControlsLocked ?? false,
|
||||
isAudioMode: appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false,
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onStreamSelected: { stream, audioStream in
|
||||
onStreamSelected(stream, audioStream)
|
||||
},
|
||||
@@ -713,6 +717,12 @@ private struct PlayerSheetsModifier: ViewModifier {
|
||||
await playerService.switchToOnlineStream(stream, audioStream: audioStream)
|
||||
}
|
||||
},
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
},
|
||||
onRateChanged: { rate in
|
||||
playerState?.rate = rate
|
||||
playerService.currentBackend?.rate = Float(rate.rawValue)
|
||||
|
||||
@@ -261,6 +261,16 @@ struct MPVVideoView: View {
|
||||
Task {
|
||||
await playerService.selectStreamManually(stream, audioStream: audioStream)
|
||||
}
|
||||
},
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
}
|
||||
)
|
||||
#elseif os(macOS)
|
||||
|
||||
@@ -76,6 +76,12 @@ struct PlayerControlsActions {
|
||||
/// Current audio stream
|
||||
let currentAudioStream: Stream?
|
||||
|
||||
/// Embedded (in-container) tracks reported by mpv for the loaded file
|
||||
var embeddedAudioTracks: [MPVTrack] = []
|
||||
var embeddedSubtitleTracks: [MPVTrack] = []
|
||||
var currentEmbeddedAudioTrackID: Int? = nil
|
||||
var currentEmbeddedSubtitleTrackID: Int? = nil
|
||||
|
||||
#if os(iOS)
|
||||
/// Current panscan value (0.0 = fit, 1.0 = fill)
|
||||
let panscanValue: Double
|
||||
@@ -217,9 +223,9 @@ struct PlayerControlsActions {
|
||||
return !video.isFromLocalFolder
|
||||
}
|
||||
|
||||
/// Whether captions are available
|
||||
/// Whether captions are available (external files or embedded tracks)
|
||||
var hasCaptions: Bool {
|
||||
!availableCaptions.isEmpty
|
||||
!availableCaptions.isEmpty || !embeddedSubtitleTracks.isEmpty
|
||||
}
|
||||
|
||||
/// Whether chapters are available
|
||||
|
||||
@@ -91,6 +91,15 @@ struct PlayerControlsView: View {
|
||||
var onCaptionSelected: ((Caption?) -> Void)? = nil
|
||||
/// Callback when stream is selected (for quality selector integration)
|
||||
var onStreamSelected: ((Stream, Stream?) -> Void)? = nil
|
||||
/// Embedded (in-container) tracks reported by mpv for the loaded file
|
||||
var embeddedAudioTracks: [MPVTrack] = []
|
||||
var embeddedSubtitleTracks: [MPVTrack] = []
|
||||
var currentEmbeddedAudioTrackID: Int? = nil
|
||||
var currentEmbeddedSubtitleTrackID: Int? = nil
|
||||
/// Callback when an embedded audio track is selected
|
||||
var onEmbeddedAudioTrackSelected: ((Int) -> Void)? = nil
|
||||
/// Callback when an embedded subtitle track is selected (nil = off)
|
||||
var onEmbeddedSubtitleTrackSelected: ((Int?) -> Void)? = nil
|
||||
|
||||
/// Current panscan value (0.0 = fit, 1.0 = fill)
|
||||
var panscanValue: Double = 0.0
|
||||
@@ -285,9 +294,14 @@ struct PlayerControlsView: View {
|
||||
currentCaption: currentCaption,
|
||||
initialTab: .subtitles,
|
||||
showTabPicker: false,
|
||||
embeddedSubtitleTracks: embeddedSubtitleTracks,
|
||||
currentEmbeddedSubtitleTrackID: currentEmbeddedSubtitleTrackID,
|
||||
onStreamSelected: { _, _ in },
|
||||
onCaptionSelected: { caption in
|
||||
onCaptionSelected?(caption)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
onEmbeddedSubtitleTrackSelected?(trackID)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -317,10 +331,15 @@ struct PlayerControlsView: View {
|
||||
currentAudioStream: currentAudioStream,
|
||||
initialTab: .audio,
|
||||
showTabPicker: false,
|
||||
embeddedAudioTracks: embeddedAudioTracks,
|
||||
currentEmbeddedAudioTrackID: currentEmbeddedAudioTrackID,
|
||||
onStreamSelected: { stream, audioStream in
|
||||
onStreamSelected?(stream, audioStream)
|
||||
},
|
||||
onCaptionSelected: { _ in }
|
||||
onCaptionSelected: { _ in },
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
onEmbeddedAudioTrackSelected?(trackID)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -379,6 +398,10 @@ struct PlayerControlsView: View {
|
||||
availableStreams: availableStreams,
|
||||
currentStream: currentStream,
|
||||
currentAudioStream: currentAudioStream,
|
||||
embeddedAudioTracks: embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: currentEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: currentEmbeddedSubtitleTrackID,
|
||||
panscanValue: panscanValue,
|
||||
isPanscanAllowed: isPanscanAllowed,
|
||||
isAutoPlayNextEnabled: appEnvironment?.settingsManager.queueAutoPlayNext ?? true,
|
||||
|
||||
@@ -414,6 +414,83 @@ struct CaptionRowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Embedded Track Row
|
||||
|
||||
/// Row view for embedded (in-container) audio/subtitle tracks reported by mpv.
|
||||
struct EmbeddedTrackRowView: View {
|
||||
let track: MPVTrack
|
||||
let isSelected: Bool
|
||||
let isPreferred: Bool
|
||||
let showAdvancedDetails: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
titleRow
|
||||
if showAdvancedDetails, let detail = track.detailText {
|
||||
Text(detail)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.tint)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: showAdvancedDetails ? nil : 36)
|
||||
#if os(tvOS)
|
||||
.padding(.vertical, tvRowVerticalPadding)
|
||||
.padding(.horizontal, tvRowHorizontalPadding)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
#endif
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
#if os(tvOS)
|
||||
.buttonStyle(TVSettingsRowButtonStyle())
|
||||
#else
|
||||
.buttonStyle(.plain)
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var titleRow: some View {
|
||||
HStack(spacing: 6) {
|
||||
if isPreferred {
|
||||
Image(systemName: "star.fill")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
|
||||
Text(track.displayName)
|
||||
.font(.headline)
|
||||
|
||||
if track.isDefault {
|
||||
badge(String(localized: "player.track.default"))
|
||||
}
|
||||
|
||||
if track.isForced {
|
||||
badge(String(localized: "player.track.forced"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func badge(_ text: String) -> some View {
|
||||
Text(text)
|
||||
.font(.caption2)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.gray.opacity(0.2))
|
||||
.foregroundStyle(.secondary)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview("Adaptive Stream Row") {
|
||||
|
||||
@@ -188,6 +188,11 @@ extension QualitySelectorView {
|
||||
}
|
||||
|
||||
private var currentAudioDisplayValue: String {
|
||||
if embeddedAudioTracks.count > 1,
|
||||
let trackID = currentEmbeddedAudioTrackID,
|
||||
let track = embeddedAudioTracks.first(where: { $0.trackID == trackID }) {
|
||||
return track.displayName
|
||||
}
|
||||
if isCurrentStreamMuxed {
|
||||
return String(localized: "player.quality.audioFromVideo.short")
|
||||
}
|
||||
@@ -198,7 +203,11 @@ extension QualitySelectorView {
|
||||
}
|
||||
|
||||
private var currentSubtitlesDisplayValue: String {
|
||||
currentCaption?.displayName ?? String(localized: "stream.subtitles.off")
|
||||
if let trackID = currentEmbeddedSubtitleTrackID,
|
||||
let track = embeddedSubtitleTracks.first(where: { $0.trackID == trackID }) {
|
||||
return track.displayName
|
||||
}
|
||||
return currentCaption?.displayName ?? String(localized: "stream.subtitles.off")
|
||||
}
|
||||
|
||||
// MARK: - Detail Content Views
|
||||
@@ -741,7 +750,9 @@ extension QualitySelectorView {
|
||||
|
||||
@ViewBuilder
|
||||
var audioSectionContent: some View {
|
||||
if isCurrentStreamMuxed {
|
||||
if embeddedAudioTracks.count > 1 {
|
||||
embeddedAudioTracksContent
|
||||
} else if isCurrentStreamMuxed {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Image(systemName: "info.circle")
|
||||
@@ -778,6 +789,44 @@ extension QualitySelectorView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Embedded (in-container) audio tracks, switched live via mpv `aid`.
|
||||
@ViewBuilder
|
||||
private var embeddedAudioTracksContent: some View {
|
||||
#if os(tvOS)
|
||||
VStack(spacing: 8) {
|
||||
ForEach(embeddedAudioTracks) { track in
|
||||
embeddedAudioTrackRow(track)
|
||||
}
|
||||
}
|
||||
#else
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(embeddedAudioTracks.enumerated()), id: \.element.id) { index, track in
|
||||
if index > 0 {
|
||||
Divider()
|
||||
}
|
||||
embeddedAudioTrackRow(track)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
}
|
||||
.cardBackground()
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func embeddedAudioTrackRow(_ track: MPVTrack) -> some View {
|
||||
EmbeddedTrackRowView(
|
||||
track: track,
|
||||
isSelected: track.trackID == currentEmbeddedAudioTrackID,
|
||||
isPreferred: track.matchesLanguage(preferredAudioLanguage),
|
||||
showAdvancedDetails: showAdvancedStreamDetails,
|
||||
onTap: {
|
||||
onEmbeddedAudioTrackSelected(track.trackID)
|
||||
performDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func audioStreamRow(_ stream: Stream) -> some View {
|
||||
let isSelected: Bool = stream.url == selectedAudioStream?.url
|
||||
@@ -816,13 +865,17 @@ extension QualitySelectorView {
|
||||
VStack(spacing: 8) {
|
||||
CaptionRowView(
|
||||
caption: nil,
|
||||
isSelected: currentCaption == nil,
|
||||
isSelected: currentCaption == nil && currentEmbeddedSubtitleTrackID == nil,
|
||||
isPreferred: false,
|
||||
onTap: {
|
||||
handleCaptionTap(nil)
|
||||
}
|
||||
)
|
||||
|
||||
ForEach(embeddedSubtitleTracks) { track in
|
||||
embeddedSubtitleTrackRow(track)
|
||||
}
|
||||
|
||||
ForEach(sortedCaptions) { caption in
|
||||
CaptionRowView(
|
||||
caption: caption,
|
||||
@@ -838,7 +891,7 @@ extension QualitySelectorView {
|
||||
VStack(spacing: 0) {
|
||||
CaptionRowView(
|
||||
caption: nil,
|
||||
isSelected: currentCaption == nil,
|
||||
isSelected: currentCaption == nil && currentEmbeddedSubtitleTrackID == nil,
|
||||
isPreferred: false,
|
||||
onTap: {
|
||||
handleCaptionTap(nil)
|
||||
@@ -847,6 +900,14 @@ extension QualitySelectorView {
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
ForEach(embeddedSubtitleTracks) { track in
|
||||
Divider()
|
||||
|
||||
embeddedSubtitleTrackRow(track)
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
|
||||
ForEach(sortedCaptions) { caption in
|
||||
Divider()
|
||||
|
||||
@@ -866,12 +927,34 @@ extension QualitySelectorView {
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func embeddedSubtitleTrackRow(_ track: MPVTrack) -> some View {
|
||||
EmbeddedTrackRowView(
|
||||
track: track,
|
||||
isSelected: track.trackID == currentEmbeddedSubtitleTrackID,
|
||||
isPreferred: track.matchesLanguage(preferredSubtitlesLanguage),
|
||||
showAdvancedDetails: showAdvancedStreamDetails,
|
||||
onTap: {
|
||||
// Re-tapping the selected track turns subtitles off (parity
|
||||
// with external caption rows)
|
||||
if track.trackID == currentEmbeddedSubtitleTrackID {
|
||||
onEmbeddedSubtitleTrackSelected(nil)
|
||||
} else {
|
||||
onEmbeddedSubtitleTrackSelected(track.trackID)
|
||||
}
|
||||
performDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func isCaptionPreferred(_ caption: Caption) -> Bool {
|
||||
guard let preferred = preferredSubtitlesLanguage else { return false }
|
||||
return caption.baseLanguageCode == preferred || caption.languageCode.hasPrefix(preferred)
|
||||
}
|
||||
|
||||
private func handleCaptionTap(_ caption: Caption?) {
|
||||
// Note: the Off row needs no embedded-track handling — caption "off"
|
||||
// sets sid=no and clears the embedded intent in PlayerService.
|
||||
if caption?.id == currentCaption?.id && caption != nil {
|
||||
onCaptionSelected(nil)
|
||||
} else {
|
||||
|
||||
@@ -29,6 +29,15 @@ struct QualitySelectorView: View {
|
||||
let onLoadOnlineStreams: () -> Void
|
||||
let onSwitchToOnlineStream: (Stream, Stream?) -> Void
|
||||
|
||||
/// Embedded (in-container) tracks reported by mpv for the loaded file.
|
||||
let embeddedAudioTracks: [MPVTrack]
|
||||
let embeddedSubtitleTracks: [MPVTrack]
|
||||
let currentEmbeddedAudioTrackID: Int?
|
||||
let currentEmbeddedSubtitleTrackID: Int?
|
||||
let onEmbeddedAudioTrackSelected: (Int) -> Void
|
||||
/// nil = subtitles off
|
||||
let onEmbeddedSubtitleTrackSelected: (Int?) -> Void
|
||||
|
||||
/// Current playback rate
|
||||
var currentRate: PlaybackRate = .x1
|
||||
/// Callback when playback rate changes
|
||||
@@ -100,10 +109,10 @@ struct QualitySelectorView: View {
|
||||
/// Available tabs based on streams
|
||||
var availableTabs: [QualitySelectorTab] {
|
||||
var tabs: [QualitySelectorTab] = [.video]
|
||||
if hasVideoOnlyStreams && !audioStreams.isEmpty {
|
||||
if (hasVideoOnlyStreams && !audioStreams.isEmpty) || embeddedAudioTracks.count > 1 {
|
||||
tabs.append(.audio)
|
||||
}
|
||||
if !captions.isEmpty {
|
||||
if !captions.isEmpty || !embeddedSubtitleTracks.isEmpty {
|
||||
tabs.append(.subtitles)
|
||||
}
|
||||
return tabs
|
||||
@@ -133,7 +142,7 @@ struct QualitySelectorView: View {
|
||||
/// Whether streams are empty (not loading, but no streams available)
|
||||
var hasNoStreams: Bool {
|
||||
if !showTabPicker && initialTab == .subtitles {
|
||||
return !isLoading && captions.isEmpty && !isPlayingDownloadedContent
|
||||
return !isLoading && captions.isEmpty && embeddedSubtitleTracks.isEmpty && !isPlayingDownloadedContent
|
||||
}
|
||||
return !isLoading && streams.isEmpty && !isPlayingDownloadedContent
|
||||
}
|
||||
@@ -168,10 +177,16 @@ struct QualitySelectorView: View {
|
||||
isAudioMode: Bool = false,
|
||||
initialTab: QualitySelectorTab = .video,
|
||||
showTabPicker: Bool = true,
|
||||
embeddedAudioTracks: [MPVTrack] = [],
|
||||
embeddedSubtitleTracks: [MPVTrack] = [],
|
||||
currentEmbeddedAudioTrackID: Int? = nil,
|
||||
currentEmbeddedSubtitleTrackID: Int? = nil,
|
||||
onStreamSelected: @escaping (Stream, Stream?) -> Void,
|
||||
onCaptionSelected: @escaping (Caption?) -> Void = { _ in },
|
||||
onLoadOnlineStreams: @escaping () -> Void = {},
|
||||
onSwitchToOnlineStream: @escaping (Stream, Stream?) -> Void = { _, _ in },
|
||||
onEmbeddedAudioTrackSelected: @escaping (Int) -> Void = { _ in },
|
||||
onEmbeddedSubtitleTrackSelected: @escaping (Int?) -> Void = { _ in },
|
||||
onRateChanged: ((PlaybackRate) -> Void)? = nil,
|
||||
onLockToggled: ((Bool) -> Void)? = nil,
|
||||
onAudioModeToggled: ((Bool) -> Void)? = nil,
|
||||
@@ -191,10 +206,16 @@ struct QualitySelectorView: View {
|
||||
self.currentRate = currentRate
|
||||
self.isControlsLocked = isControlsLocked
|
||||
self.isAudioMode = isAudioMode
|
||||
self.embeddedAudioTracks = embeddedAudioTracks
|
||||
self.embeddedSubtitleTracks = embeddedSubtitleTracks
|
||||
self.currentEmbeddedAudioTrackID = currentEmbeddedAudioTrackID
|
||||
self.currentEmbeddedSubtitleTrackID = currentEmbeddedSubtitleTrackID
|
||||
self.onStreamSelected = onStreamSelected
|
||||
self.onCaptionSelected = onCaptionSelected
|
||||
self.onLoadOnlineStreams = onLoadOnlineStreams
|
||||
self.onSwitchToOnlineStream = onSwitchToOnlineStream
|
||||
self.onEmbeddedAudioTrackSelected = onEmbeddedAudioTrackSelected
|
||||
self.onEmbeddedSubtitleTrackSelected = onEmbeddedSubtitleTrackSelected
|
||||
self.onRateChanged = onRateChanged
|
||||
self.onLockToggled = onLockToggled
|
||||
self.onAudioModeToggled = onAudioModeToggled
|
||||
|
||||
@@ -167,6 +167,10 @@ struct TVPlayerView: View {
|
||||
},
|
||||
currentRate: playerState?.rate ?? .x1,
|
||||
isAudioMode: appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false,
|
||||
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
||||
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
||||
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
||||
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
||||
onStreamSelected: { stream, audioStream in
|
||||
switchToStream(stream, audioStream: audioStream)
|
||||
},
|
||||
@@ -183,6 +187,12 @@ struct TVPlayerView: View {
|
||||
await playerService.switchToOnlineStream(stream, audioStream: audioStream)
|
||||
}
|
||||
},
|
||||
onEmbeddedAudioTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedAudioTrack(trackID)
|
||||
},
|
||||
onEmbeddedSubtitleTrackSelected: { trackID in
|
||||
playerService.selectEmbeddedSubtitleTrack(trackID)
|
||||
},
|
||||
onRateChanged: { rate in
|
||||
playerState?.rate = rate
|
||||
playerService.currentBackend?.rate = Float(rate.rawValue)
|
||||
|
||||
Reference in New Issue
Block a user