Merge pull request #8 from dirtydishes/audio-track-selection

add native audio track selection
This commit is contained in:
dirtydishes 2026-05-25 13:26:19 -04:00 committed by GitHub
commit 25fe0d278f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 567 additions and 7 deletions

View file

@ -23,6 +23,7 @@
{"id":"int-4e095d3f","kind":"field_change","created_at":"2026-05-25T14:38:21.968713Z","actor":"dirtydishes","issue_id":"dreamio-djc","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Auto-select the first discovered VLC subtitle track when playback is still disabled, while preserving manual caption choices."}} {"id":"int-4e095d3f","kind":"field_change","created_at":"2026-05-25T14:38:21.968713Z","actor":"dirtydishes","issue_id":"dreamio-djc","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Auto-select the first discovered VLC subtitle track when playback is still disabled, while preserving manual caption choices."}}
{"id":"int-96629c65","kind":"field_change","created_at":"2026-05-25T14:45:38.521113Z","actor":"dirtydishes","issue_id":"dreamio-ppj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Re-applied the auto-selected VLC subtitle track after stream discovery and playback state changes to harden rendering timing."}} {"id":"int-96629c65","kind":"field_change","created_at":"2026-05-25T14:45:38.521113Z","actor":"dirtydishes","issue_id":"dreamio-ppj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Re-applied the auto-selected VLC subtitle track after stream discovery and playback state changes to harden rendering timing."}}
{"id":"int-027cec57","kind":"field_change","created_at":"2026-05-25T14:51:44.599319Z","actor":"dirtydishes","issue_id":"dreamio-3xi","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Captured OpenSubtitles V3 subtitle URLs from browser track elements and textTracks so they can be forwarded to native playback."}} {"id":"int-027cec57","kind":"field_change","created_at":"2026-05-25T14:51:44.599319Z","actor":"dirtydishes","issue_id":"dreamio-3xi","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Captured OpenSubtitles V3 subtitle URLs from browser track elements and textTracks so they can be forwarded to native playback."}}
{"id":"int-8f943c34","kind":"field_change","created_at":"2026-05-25T15:01:35.610049Z","actor":"dirtydishes","issue_id":"dreamio-bao","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Implemented native audio track discovery and selection with a far-left audio menu in the VLC-backed player."}}
{"id":"int-3acaadff","kind":"field_change","created_at":"2026-05-25T15:09:02.023077Z","actor":"dirtydishes","issue_id":"dreamio-h5n","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Limited VLC auto-subtitle reapply to real selection recovery while keeping bounded delayed startup confirmations."}} {"id":"int-3acaadff","kind":"field_change","created_at":"2026-05-25T15:09:02.023077Z","actor":"dirtydishes","issue_id":"dreamio-h5n","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Limited VLC auto-subtitle reapply to real selection recovery while keeping bounded delayed startup confirmations."}}
{"id":"int-c526b5ae","kind":"field_change","created_at":"2026-05-25T15:32:37.748454Z","actor":"dirtydishes","issue_id":"dreamio-dow","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented stream-keyed subtitle buffering, OpenSubtitles parser/resolver hardening, VLC refresh behavior, and focused validation."}} {"id":"int-c526b5ae","kind":"field_change","created_at":"2026-05-25T15:32:37.748454Z","actor":"dirtydishes","issue_id":"dreamio-dow","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented stream-keyed subtitle buffering, OpenSubtitles parser/resolver hardening, VLC refresh behavior, and focused validation."}}
{"id":"int-320e7321","kind":"field_change","created_at":"2026-05-25T15:53:52.866657Z","actor":"dirtydishes","issue_id":"dreamio-hzj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Hardened OpenSubtitles candidate discovery, nested payload resolution, VLC external subtitle visibility selection, diagnostics, tests, and turn documentation."}} {"id":"int-320e7321","kind":"field_change","created_at":"2026-05-25T15:53:52.866657Z","actor":"dirtydishes","issue_id":"dreamio-hzj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Hardened OpenSubtitles candidate discovery, nested payload resolution, VLC external subtitle visibility selection, diagnostics, tests, and turn documentation."}}

View file

@ -6,12 +6,15 @@ protocol NativePlaybackBackend: AnyObject {
var onFailure: ((Error) -> Void)? { get set } var onFailure: ((Error) -> Void)? { get set }
var onStateChange: (() -> Void)? { get set } var onStateChange: (() -> Void)? { get set }
var onSubtitleTracksChange: (() -> Void)? { get set } var onSubtitleTracksChange: (() -> Void)? { get set }
var onAudioTracksChange: (() -> Void)? { get set }
var isPlaying: Bool { get } var isPlaying: Bool { get }
var isSeekable: Bool { get } var isSeekable: Bool { get }
var duration: TimeInterval { get } var duration: TimeInterval { get }
var currentTime: TimeInterval { get } var currentTime: TimeInterval { get }
var remainingTime: TimeInterval { get } var remainingTime: TimeInterval { get }
var position: Float { get } var position: Float { get }
var audioTracks: [AudioTrack] { get }
var selectedAudioTrackID: Int32 { get }
var subtitleTracks: [SubtitleTrack] { get } var subtitleTracks: [SubtitleTrack] { get }
var selectedSubtitleTrackID: Int32 { get } var selectedSubtitleTrackID: Int32 { get }
var subtitleDelay: TimeInterval { get } var subtitleDelay: TimeInterval { get }
@ -23,6 +26,7 @@ protocol NativePlaybackBackend: AnyObject {
func togglePlayPause() func togglePlayPause()
func seek(to position: Float) func seek(to position: Float)
func jump(by seconds: TimeInterval) func jump(by seconds: TimeInterval)
func selectAudioTrack(id: Int32)
func selectSubtitleTrack(id: Int32) func selectSubtitleTrack(id: Int32)
func adjustSubtitleDelay(by seconds: TimeInterval) func adjustSubtitleDelay(by seconds: TimeInterval)
@discardableResult @discardableResult

View file

@ -9,6 +9,7 @@ final class NativePlayerViewController: UIViewController {
private var progressTimer: Timer? private var progressTimer: Timer?
private var isScrubbing = false private var isScrubbing = false
private var attachedSubtitleURLs: Set<URL> private var attachedSubtitleURLs: Set<URL>
private var audioMenuSignature: String?
private var captionsMenuSignature: String? private var captionsMenuSignature: String?
var onDismiss: (() -> Void)? var onDismiss: (() -> Void)?
@ -34,8 +35,11 @@ final class NativePlayerViewController: UIViewController {
private let controlsContainer: UIVisualEffectView = { private let controlsContainer: UIVisualEffectView = {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark)) let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16 view.layer.cornerRadius = 22
view.clipsToBounds = true view.clipsToBounds = true
view.backgroundColor = UIColor.white.withAlphaComponent(0.08)
view.layer.borderColor = UIColor.white.withAlphaComponent(0.18).cgColor
view.layer.borderWidth = 1
return view return view
}() }()
@ -49,6 +53,7 @@ final class NativePlayerViewController: UIViewController {
private let playPauseButton = NativePlayerViewController.iconButton(systemName: "pause.fill", label: "Play or Pause") private let playPauseButton = NativePlayerViewController.iconButton(systemName: "pause.fill", label: "Play or Pause")
private let jumpBackButton = NativePlayerViewController.iconButton(systemName: "gobackward.15", label: "Jump Back 15 Seconds") private let jumpBackButton = NativePlayerViewController.iconButton(systemName: "gobackward.15", label: "Jump Back 15 Seconds")
private let jumpForwardButton = NativePlayerViewController.iconButton(systemName: "goforward.15", label: "Jump Forward 15 Seconds") private let jumpForwardButton = NativePlayerViewController.iconButton(systemName: "goforward.15", label: "Jump Forward 15 Seconds")
private let audioButton = NativePlayerViewController.iconButton(systemName: "waveform.circle", label: "Audio Tracks")
private let captionsButton = NativePlayerViewController.iconButton(systemName: "captions.bubble", label: "Captions") private let captionsButton = NativePlayerViewController.iconButton(systemName: "captions.bubble", label: "Captions")
private let elapsedLabel: UILabel = { private let elapsedLabel: UILabel = {
@ -229,6 +234,11 @@ final class NativePlayerViewController: UIViewController {
self?.refreshControls() self?.refreshControls()
} }
} }
backend.onAudioTracksChange = { [weak self] in
DispatchQueue.main.async {
self?.refreshControls()
}
}
} }
private func startStartupTimer() { private func startStartupTimer() {
@ -250,8 +260,9 @@ final class NativePlayerViewController: UIViewController {
playPauseButton.addTarget(self, action: #selector(togglePlayPause), for: .touchUpInside) playPauseButton.addTarget(self, action: #selector(togglePlayPause), for: .touchUpInside)
jumpBackButton.addTarget(self, action: #selector(jumpBack), for: .touchUpInside) jumpBackButton.addTarget(self, action: #selector(jumpBack), for: .touchUpInside)
jumpForwardButton.addTarget(self, action: #selector(jumpForward), for: .touchUpInside) jumpForwardButton.addTarget(self, action: #selector(jumpForward), for: .touchUpInside)
audioButton.showsMenuAsPrimaryAction = true
captionsButton.showsMenuAsPrimaryAction = true captionsButton.showsMenuAsPrimaryAction = true
playPauseButton.layer.cornerRadius = 21 playPauseButton.layer.cornerRadius = 24
scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown) scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown)
scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged) scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged)
scrubber.addTarget(self, action: #selector(scrubbingEnded), for: [.touchUpInside, .touchUpOutside, .touchCancel]) scrubber.addTarget(self, action: #selector(scrubbingEnded), for: [.touchUpInside, .touchUpOutside, .touchCancel])
@ -266,12 +277,19 @@ final class NativePlayerViewController: UIViewController {
timeAndScrubRow.alignment = .center timeAndScrubRow.alignment = .center
timeAndScrubRow.spacing = 8 timeAndScrubRow.spacing = 8
let controlRow = UIStackView(arrangedSubviews: [jumpBackButton, playPauseButton, jumpForwardButton, captionsButton]) let playbackCluster = UIStackView(arrangedSubviews: [jumpBackButton, playPauseButton, jumpForwardButton])
playbackCluster.translatesAutoresizingMaskIntoConstraints = false
playbackCluster.axis = .horizontal
playbackCluster.alignment = .center
playbackCluster.distribution = .equalCentering
playbackCluster.spacing = 14
let controlRow = UIStackView(arrangedSubviews: [audioButton, playbackCluster, captionsButton])
controlRow.translatesAutoresizingMaskIntoConstraints = false controlRow.translatesAutoresizingMaskIntoConstraints = false
controlRow.axis = .horizontal controlRow.axis = .horizontal
controlRow.alignment = .center controlRow.alignment = .center
controlRow.distribution = .equalSpacing controlRow.distribution = .equalCentering
controlRow.spacing = 14 controlRow.spacing = 18
let stack = UIStackView(arrangedSubviews: [timeAndScrubRow, controlRow]) let stack = UIStackView(arrangedSubviews: [timeAndScrubRow, controlRow])
stack.translatesAutoresizingMaskIntoConstraints = false stack.translatesAutoresizingMaskIntoConstraints = false
@ -322,6 +340,9 @@ final class NativePlayerViewController: UIViewController {
playPauseButton.heightAnchor.constraint(equalToConstant: 42), playPauseButton.heightAnchor.constraint(equalToConstant: 42),
jumpForwardButton.widthAnchor.constraint(equalToConstant: 36), jumpForwardButton.widthAnchor.constraint(equalToConstant: 36),
jumpForwardButton.heightAnchor.constraint(equalToConstant: 36), jumpForwardButton.heightAnchor.constraint(equalToConstant: 36),
audioButton.widthAnchor.constraint(equalToConstant: 36),
audioButton.heightAnchor.constraint(equalToConstant: 36),
playbackCluster.centerXAnchor.constraint(equalTo: controlRow.centerXAnchor),
captionsButton.widthAnchor.constraint(equalToConstant: 36), captionsButton.widthAnchor.constraint(equalToConstant: 36),
captionsButton.heightAnchor.constraint(equalToConstant: 36) captionsButton.heightAnchor.constraint(equalToConstant: 36)
]) ])
@ -430,6 +451,36 @@ final class NativePlayerViewController: UIViewController {
return UIMenu(title: "Captions", children: trackActions + [delayActions]) return UIMenu(title: "Captions", children: trackActions + [delayActions])
} }
private func audioMenu() -> UIMenu {
let selectedTrackID = backend.selectedAudioTrackID
let tracks = backend.audioTracks
let options = AudioOptionMapper.options(from: tracks)
#if DEBUG
print("[DreamioAudio] build-menu tracks=\(SubtitleDebugFormatter.trackSummary(tracks)) selected=\(selectedTrackID)")
#endif
let trackActions = options.map { track in
UIAction(
title: track.name,
state: track.id == selectedTrackID ? .on : .off
) { [weak self] _ in
guard let self else {
return
}
#if DEBUG
print("[DreamioAudio] select-request id=\(track.id) name=\(track.name) before=\(self.backend.selectedAudioTrackID)")
#endif
self.backend.selectAudioTrack(id: track.id)
#if DEBUG
print("[DreamioAudio] select-result id=\(track.id) after=\(self.backend.selectedAudioTrackID) tracks=\(SubtitleDebugFormatter.trackSummary(self.backend.audioTracks))")
#endif
self.audioMenuSignature = nil
self.refreshControls()
}
}
return UIMenu(title: "Audio", children: trackActions)
}
private func startProgressUpdates() { private func startProgressUpdates() {
progressTimer?.invalidate() progressTimer?.invalidate()
progressTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in progressTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
@ -438,11 +489,13 @@ final class NativePlayerViewController: UIViewController {
} }
private func refreshControls() { private func refreshControls() {
let audioTracks = backend.audioTracks
let subtitleTracks = backend.subtitleTracks let subtitleTracks = backend.subtitleTracks
playPauseButton.setImage(UIImage(systemName: backend.isPlaying ? "pause.fill" : "play.fill"), for: .normal) playPauseButton.setImage(UIImage(systemName: backend.isPlaying ? "pause.fill" : "play.fill"), for: .normal)
scrubber.isEnabled = backend.isSeekable scrubber.isEnabled = backend.isSeekable
jumpBackButton.isEnabled = backend.isSeekable jumpBackButton.isEnabled = backend.isSeekable
jumpForwardButton.isEnabled = backend.isSeekable jumpForwardButton.isEnabled = backend.isSeekable
updateAudioMenuIfNeeded(audioTracks: audioTracks)
updateCaptionsMenuIfNeeded(subtitleTracks: subtitleTracks) updateCaptionsMenuIfNeeded(subtitleTracks: subtitleTracks)
elapsedLabel.text = PlaybackTimeFormatter.label(for: backend.currentTime) elapsedLabel.text = PlaybackTimeFormatter.label(for: backend.currentTime)
remainingLabel.text = "-\(PlaybackTimeFormatter.label(for: backend.remainingTime))" remainingLabel.text = "-\(PlaybackTimeFormatter.label(for: backend.remainingTime))"
@ -452,6 +505,26 @@ final class NativePlayerViewController: UIViewController {
[scrubber, jumpBackButton, jumpForwardButton].forEach { $0.alpha = backend.isSeekable ? 1 : 0.45 } [scrubber, jumpBackButton, jumpForwardButton].forEach { $0.alpha = backend.isSeekable ? 1 : 0.45 }
} }
private func updateAudioMenuIfNeeded(audioTracks: [AudioTrack]) {
let selectedTrackID = backend.selectedAudioTrackID
let signature = trackMenuSignatureValue(
tracks: audioTracks,
selectedTrackID: selectedTrackID
)
let hasSelectableTrack = AudioOptionMapper.options(from: audioTracks).count > 1
audioButton.isEnabled = hasSelectableTrack
audioButton.alpha = hasSelectableTrack ? 1 : 0.45
guard signature != audioMenuSignature else {
return
}
audioMenuSignature = signature
audioButton.menu = audioMenu()
#if DEBUG
print("[DreamioAudio] refresh-menu enabled=\(audioButton.isEnabled) tracks=\(SubtitleDebugFormatter.trackSummary(audioTracks)) selected=\(selectedTrackID)")
#endif
}
private func updateCaptionsMenuIfNeeded(subtitleTracks: [SubtitleTrack]) { private func updateCaptionsMenuIfNeeded(subtitleTracks: [SubtitleTrack]) {
let selectedTrackID = backend.selectedSubtitleTrackID let selectedTrackID = backend.selectedSubtitleTrackID
let signature = captionsMenuSignatureValue( let signature = captionsMenuSignatureValue(
@ -476,11 +549,19 @@ final class NativePlayerViewController: UIViewController {
tracks: [SubtitleTrack], tracks: [SubtitleTrack],
selectedTrackID: Int32, selectedTrackID: Int32,
delay: TimeInterval delay: TimeInterval
) -> String {
let trackSignature = trackMenuSignatureValue(tracks: tracks, selectedTrackID: selectedTrackID)
return "\(trackSignature)#delay=\(String(format: "%.1f", delay))"
}
private func trackMenuSignatureValue(
tracks: [SubtitleTrack],
selectedTrackID: Int32
) -> String { ) -> String {
let trackSignature = tracks let trackSignature = tracks
.map { "\($0.id):\($0.name)" } .map { "\($0.id):\($0.name)" }
.joined(separator: "|") .joined(separator: "|")
return "\(trackSignature)#selected=\(selectedTrackID)#delay=\(String(format: "%.1f", delay))" return "\(trackSignature)#selected=\(selectedTrackID)"
} }
private func revealControls() { private func revealControls() {
@ -517,8 +598,10 @@ final class NativePlayerViewController: UIViewController {
button.translatesAutoresizingMaskIntoConstraints = false button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(systemName: systemName), for: .normal) button.setImage(UIImage(systemName: systemName), for: .normal)
button.tintColor = .white button.tintColor = .white
button.backgroundColor = UIColor.black.withAlphaComponent(0.35) button.backgroundColor = UIColor.white.withAlphaComponent(0.12)
button.layer.cornerRadius = 18 button.layer.cornerRadius = 18
button.layer.borderColor = UIColor.white.withAlphaComponent(0.16).cgColor
button.layer.borderWidth = 1
button.accessibilityLabel = label button.accessibilityLabel = label
return button return button
} }

View file

@ -40,6 +40,8 @@ struct SubtitleTrack: Equatable {
let name: String let name: String
} }
typealias AudioTrack = SubtitleTrack
#if DEBUG #if DEBUG
enum SubtitleDebugFormatter { enum SubtitleDebugFormatter {
static func candidateSummary(_ candidates: [SubtitleCandidate]) -> String { static func candidateSummary(_ candidates: [SubtitleCandidate]) -> String {
@ -93,6 +95,12 @@ enum SubtitleOptionMapper {
} }
} }
enum AudioOptionMapper {
static func options(from tracks: [AudioTrack]) -> [AudioTrack] {
tracks.filter { $0.id >= 0 }
}
}
struct StreamClassification { struct StreamClassification {
let sourceKind: StreamSourceKind let sourceKind: StreamSourceKind
let containerGuess: StreamContainerGuess let containerGuess: StreamContainerGuess

View file

@ -18,6 +18,7 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
var onFailure: ((Error) -> Void)? var onFailure: ((Error) -> Void)?
var onStateChange: (() -> Void)? var onStateChange: (() -> Void)?
var onSubtitleTracksChange: (() -> Void)? var onSubtitleTracksChange: (() -> Void)?
var onAudioTracksChange: (() -> Void)?
#if canImport(MobileVLCKit) #if canImport(MobileVLCKit)
private let mediaPlayer = VLCMediaPlayer() private let mediaPlayer = VLCMediaPlayer()
@ -108,6 +109,19 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
#endif #endif
} }
func selectAudioTrack(id: Int32) {
#if canImport(MobileVLCKit)
#if DEBUG
logAudioTracks(reason: "before-select-\(id)")
#endif
mediaPlayer.currentAudioTrackIndex = id
#if DEBUG
logAudioTracks(reason: "after-select-\(id)")
#endif
onAudioTracksChange?()
#endif
}
func selectSubtitleTrack(id: Int32) { func selectSubtitleTrack(id: Int32) {
#if canImport(MobileVLCKit) #if canImport(MobileVLCKit)
didUserSelectSubtitleTrack = true didUserSelectSubtitleTrack = true
@ -197,6 +211,26 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
#endif #endif
} }
var audioTracks: [AudioTrack] {
#if canImport(MobileVLCKit)
let names = mediaPlayer.audioTrackNames as? [String] ?? []
let indexes = mediaPlayer.audioTrackIndexes as? [NSNumber] ?? []
return zip(indexes, names).map { index, name in
AudioTrack(id: index.int32Value, name: name)
}
#else
[]
#endif
}
var selectedAudioTrackID: Int32 {
#if canImport(MobileVLCKit)
mediaPlayer.currentAudioTrackIndex
#else
-1
#endif
}
var subtitleTracks: [SubtitleTrack] { var subtitleTracks: [SubtitleTrack] {
#if canImport(MobileVLCKit) #if canImport(MobileVLCKit)
let names = mediaPlayer.videoSubTitlesNames as? [String] ?? [] let names = mediaPlayer.videoSubTitlesNames as? [String] ?? []
@ -269,6 +303,12 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
} }
#if DEBUG #if DEBUG
private func logAudioTracks(reason: String) {
let names = mediaPlayer.audioTrackNames as? [String] ?? []
let indexes = mediaPlayer.audioTrackIndexes as? [NSNumber] ?? []
print("[DreamioVLC] audio tracks reason=\(reason) names=\(names) indexes=\(indexes.map { $0.int32Value }) selected=\(mediaPlayer.currentAudioTrackIndex)")
}
private func logSubtitleTracks(reason: String) { private func logSubtitleTracks(reason: String) {
let names = mediaPlayer.videoSubTitlesNames as? [String] ?? [] let names = mediaPlayer.videoSubTitlesNames as? [String] ?? []
let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber] ?? [] let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber] ?? []
@ -360,6 +400,7 @@ extension VLCNativePlaybackBackend: VLCMediaPlayerDelegate {
reapplyAutoSelectedSubtitleTrackIfNeeded(reason: stateName(mediaPlayer.state)) reapplyAutoSelectedSubtitleTrackIfNeeded(reason: stateName(mediaPlayer.state))
onReady?() onReady?()
onStateChange?() onStateChange?()
onAudioTracksChange?()
case .error: case .error:
onFailure?(NativePlaybackError.playbackFailed) onFailure?(NativePlaybackError.playbackFailed)
case .paused, .stopped, .ended: case .paused, .stopped, .ended:
@ -367,8 +408,10 @@ extension VLCNativePlaybackBackend: VLCMediaPlayerDelegate {
case .esAdded: case .esAdded:
selectPreferredSubtitleTrackIfNeeded(reason: "esAdded") selectPreferredSubtitleTrackIfNeeded(reason: "esAdded")
#if DEBUG #if DEBUG
logAudioTracks(reason: "esAdded")
logSubtitleTracks(reason: "esAdded") logSubtitleTracks(reason: "esAdded")
#endif #endif
onAudioTracksChange?()
onSubtitleTracksChange?() onSubtitleTracksChange?()
default: default:
break break

File diff suppressed because one or more lines are too long