begin matching subtitle and audio streams among adjacent items
This commit is contained in:
parent
fe0c8ee03b
commit
bc542dad8d
|
@ -23,7 +23,7 @@ protocol PlayerOverlayDelegate {
|
||||||
func didGenerallyTap()
|
func didGenerallyTap()
|
||||||
|
|
||||||
func didBeginScrubbing()
|
func didBeginScrubbing()
|
||||||
func didEndScrubbing(position: Double)
|
func didEndScrubbing()
|
||||||
|
|
||||||
func didSelectAudioStream(index: Int)
|
func didSelectAudioStream(index: Int)
|
||||||
func didSelectSubtitleStream(index: Int)
|
func didSelectSubtitleStream(index: Int)
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct ItemLandscapeMainView: View {
|
||||||
// MARK: Sidebar Image
|
// MARK: Sidebar Image
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(src: viewModel.item.getPrimaryImage(maxWidth: 130),
|
ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130),
|
||||||
bh: viewModel.item.getPrimaryImageBlurHash())
|
bh: viewModel.item.getPrimaryImageBlurHash())
|
||||||
.frame(width: 130, height: 195)
|
.frame(width: 130, height: 195)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
@ -44,7 +44,8 @@ struct ItemLandscapeMainView: View {
|
||||||
.frame(width: 130, height: 40)
|
.frame(width: 130, height: 40)
|
||||||
.background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple)
|
.background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}.disabled(viewModel.playButtonItem == nil)
|
}
|
||||||
|
.disabled(viewModel.playButtonItem == nil || viewModel.itemVideoPlayerViewModel == nil)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,8 @@ struct VLCPlayerCompactOverlayView: View, VideoPlayerOverlay {
|
||||||
Image(systemName: "captions.bubble")
|
Image(systemName: "captions.bubble")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.disabled(viewModel.selectedSubtitleStreamIndex == -1)
|
||||||
|
.foregroundColor(viewModel.selectedSubtitleStreamIndex == -1 ? .gray : .white)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Settings Menu
|
// MARK: Settings Menu
|
||||||
|
|
|
@ -23,7 +23,7 @@ protocol PlayerOverlayDelegate {
|
||||||
func didGenerallyTap()
|
func didGenerallyTap()
|
||||||
|
|
||||||
func didBeginScrubbing()
|
func didBeginScrubbing()
|
||||||
func didEndScrubbing(position: Double)
|
func didEndScrubbing()
|
||||||
|
|
||||||
func didSelectAudioStream(index: Int)
|
func didSelectAudioStream(index: Int)
|
||||||
func didSelectSubtitleStream(index: Int)
|
func didSelectSubtitleStream(index: Int)
|
||||||
|
|
|
@ -27,7 +27,7 @@ class VLCPlayerViewController: UIViewController {
|
||||||
private var vlcMediaPlayer = VLCMediaPlayer()
|
private var vlcMediaPlayer = VLCMediaPlayer()
|
||||||
private var lastPlayerTicks: Int64 = 0
|
private var lastPlayerTicks: Int64 = 0
|
||||||
private var lastProgressReportTicks: Int64 = 0
|
private var lastProgressReportTicks: Int64 = 0
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var viewModelReactCancellables = Set<AnyCancellable>()
|
||||||
private var overlayDismissTimer: Timer?
|
private var overlayDismissTimer: Timer?
|
||||||
|
|
||||||
private var currentPlayerTicks: Int64 {
|
private var currentPlayerTicks: Int64 {
|
||||||
|
@ -229,17 +229,13 @@ extension VLCPlayerViewController {
|
||||||
|
|
||||||
stopOverlayDismissTimer()
|
stopOverlayDismissTimer()
|
||||||
|
|
||||||
// UX improvement
|
|
||||||
(vlcMediaPlayer.drawable as! UIView).isHidden = true
|
|
||||||
|
|
||||||
// Stop current media if there is one
|
// Stop current media if there is one
|
||||||
if vlcMediaPlayer.media != nil {
|
if vlcMediaPlayer.media != nil {
|
||||||
cancellables.forEach({ $0.cancel() })
|
viewModelReactCancellables.forEach({ $0.cancel() })
|
||||||
|
|
||||||
vlcMediaPlayer.stop()
|
vlcMediaPlayer.stop()
|
||||||
viewModel.sendStopReport()
|
viewModel.sendStopReport()
|
||||||
viewModel.playerOverlayDelegate = nil
|
viewModel.playerOverlayDelegate = nil
|
||||||
vlcMediaPlayer.media = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
|
lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
|
||||||
|
@ -257,32 +253,27 @@ extension VLCPlayerViewController {
|
||||||
newViewModel.getAdjacentEpisodes()
|
newViewModel.getAdjacentEpisodes()
|
||||||
newViewModel.playerOverlayDelegate = self
|
newViewModel.playerOverlayDelegate = self
|
||||||
|
|
||||||
|
let startPercentage = viewModel.item.userData?.playedPercentage ?? 0
|
||||||
|
|
||||||
|
if startPercentage > 0 {
|
||||||
|
newViewModel.sliderPercentage = startPercentage / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
didSelectSubtitleStream(index: newViewModel.selectedSubtitleStreamIndex)
|
||||||
|
didSelectAudioStream(index: newViewModel.selectedAudioStreamIndex)
|
||||||
|
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: startPlayback
|
||||||
func startPlayback() {
|
func startPlayback() {
|
||||||
// UX improvement
|
|
||||||
(vlcMediaPlayer.drawable as! UIView).isHidden = false
|
|
||||||
|
|
||||||
vlcMediaPlayer.play()
|
vlcMediaPlayer.play()
|
||||||
|
|
||||||
|
setMediaPlayerTimeAtCurrentSlider()
|
||||||
|
|
||||||
viewModel.sendPlayReport()
|
viewModel.sendPlayReport()
|
||||||
|
|
||||||
restartOverlayDismissTimer()
|
restartOverlayDismissTimer()
|
||||||
|
|
||||||
// 1 second = 10,000,000 ticks
|
|
||||||
let startTicks: Int64 = viewModel.item.userData?.playbackPositionTicks ?? 0
|
|
||||||
|
|
||||||
if startTicks != 0 {
|
|
||||||
let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000)
|
|
||||||
let secondsScrubbedTo = startTicks / 10_000_000
|
|
||||||
let offset = secondsScrubbedTo - Int64(videoPosition)
|
|
||||||
if offset > 0 {
|
|
||||||
vlcMediaPlayer.jumpForward(Int32(offset))
|
|
||||||
} else {
|
|
||||||
vlcMediaPlayer.jumpBackward(Int32(abs(offset)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: setupViewModelListeners
|
// MARK: setupViewModelListeners
|
||||||
|
@ -290,27 +281,42 @@ extension VLCPlayerViewController {
|
||||||
private func setupViewModelListeners(viewModel: VideoPlayerViewModel) {
|
private func setupViewModelListeners(viewModel: VideoPlayerViewModel) {
|
||||||
viewModel.$playbackSpeed.sink { newSpeed in
|
viewModel.$playbackSpeed.sink { newSpeed in
|
||||||
self.vlcMediaPlayer.rate = Float(newSpeed.rawValue)
|
self.vlcMediaPlayer.rate = Float(newSpeed.rawValue)
|
||||||
}.store(in: &cancellables)
|
}.store(in: &viewModelReactCancellables)
|
||||||
|
|
||||||
viewModel.$screenFilled.sink { shouldFill in
|
viewModel.$screenFilled.sink { shouldFill in
|
||||||
self.changeFill(to: shouldFill)
|
self.changeFill(to: shouldFill)
|
||||||
}.store(in: &cancellables)
|
}.store(in: &viewModelReactCancellables)
|
||||||
|
|
||||||
viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in
|
viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in
|
||||||
if sliderIsScrubbing {
|
if sliderIsScrubbing {
|
||||||
self.didBeginScrubbing()
|
self.didBeginScrubbing()
|
||||||
} else {
|
} else {
|
||||||
self.didEndScrubbing(position: self.viewModel.sliderPercentage)
|
self.didEndScrubbing()
|
||||||
}
|
}
|
||||||
}.store(in: &cancellables)
|
}.store(in: &viewModelReactCancellables)
|
||||||
|
|
||||||
viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in
|
viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in
|
||||||
self.didSelectAudioStream(index: newAudioStreamIndex)
|
self.didSelectAudioStream(index: newAudioStreamIndex)
|
||||||
}.store(in: &cancellables)
|
}.store(in: &viewModelReactCancellables)
|
||||||
|
|
||||||
viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in
|
viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in
|
||||||
self.didSelectSubtitleStream(index: newSubtitleStreamIndex)
|
self.didSelectSubtitleStream(index: newSubtitleStreamIndex)
|
||||||
}.store(in: &cancellables)
|
}.store(in: &viewModelReactCancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMediaPlayerTimeAtCurrentSlider() {
|
||||||
|
// Necessary math as VLCMediaPlayer doesn't work well
|
||||||
|
// by just setting the position
|
||||||
|
let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000)
|
||||||
|
let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000)
|
||||||
|
let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration)
|
||||||
|
let newPositionOffset = secondsScrubbedTo - videoPosition
|
||||||
|
|
||||||
|
if newPositionOffset > 0 {
|
||||||
|
vlcMediaPlayer.jumpForward(Int32(newPositionOffset))
|
||||||
|
} else {
|
||||||
|
vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,13 +392,15 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
|
||||||
|
|
||||||
viewModel.sliderPercentage = Double(vlcMediaPlayer.position)
|
viewModel.sliderPercentage = Double(vlcMediaPlayer.position)
|
||||||
|
|
||||||
|
// Have to manually set playing because VLCMediaPlayer doesn't
|
||||||
|
// properly set it itself
|
||||||
if abs(currentPlayerTicks - lastPlayerTicks) >= 10_000 {
|
if abs(currentPlayerTicks - lastPlayerTicks) >= 10_000 {
|
||||||
|
|
||||||
viewModel.playerState = VLCMediaPlayerState.playing
|
viewModel.playerState = VLCMediaPlayerState.playing
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPlayerTicks = currentPlayerTicks
|
lastPlayerTicks = currentPlayerTicks
|
||||||
|
|
||||||
|
// Send progress report every 5 seconds
|
||||||
if abs(lastProgressReportTicks - currentPlayerTicks) >= 500_000_000 {
|
if abs(lastProgressReportTicks - currentPlayerTicks) >= 500_000_000 {
|
||||||
viewModel.sendProgressReport()
|
viewModel.sendProgressReport()
|
||||||
|
|
||||||
|
@ -438,7 +446,7 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
|
||||||
viewModel.subtitlesEnabled = !viewModel.subtitlesEnabled
|
viewModel.subtitlesEnabled = !viewModel.subtitlesEnabled
|
||||||
|
|
||||||
if viewModel.subtitlesEnabled {
|
if viewModel.subtitlesEnabled {
|
||||||
vlcMediaPlayer.currentVideoSubTitleIndex = vlcMediaPlayer.videoSubTitlesIndexes[1] as! Int32
|
vlcMediaPlayer.currentVideoSubTitleIndex = Int32(viewModel.selectedSubtitleStreamIndex)
|
||||||
} else {
|
} else {
|
||||||
vlcMediaPlayer.currentVideoSubTitleIndex = -1
|
vlcMediaPlayer.currentVideoSubTitleIndex = -1
|
||||||
}
|
}
|
||||||
|
@ -501,19 +509,8 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
|
||||||
stopOverlayDismissTimer()
|
stopOverlayDismissTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func didEndScrubbing(position: Double) {
|
func didEndScrubbing() {
|
||||||
// Necessary math as VLCMediaPlayer doesn't work well
|
setMediaPlayerTimeAtCurrentSlider()
|
||||||
// by just setting the position
|
|
||||||
let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000)
|
|
||||||
let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000)
|
|
||||||
let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration)
|
|
||||||
let newPositionOffset = secondsScrubbedTo - videoPosition
|
|
||||||
|
|
||||||
if newPositionOffset > 0 {
|
|
||||||
vlcMediaPlayer.jumpForward(Int32(newPositionOffset))
|
|
||||||
} else {
|
|
||||||
vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset)))
|
|
||||||
}
|
|
||||||
|
|
||||||
restartOverlayDismissTimer()
|
restartOverlayDismissTimer()
|
||||||
|
|
||||||
|
|
|
@ -79,9 +79,18 @@ extension BaseItemDto {
|
||||||
hlsURL.addQueryItem(name: "SubtitleStreamIndex", value: "\(defaultSubtitleStream!.index!)")
|
hlsURL.addQueryItem(name: "SubtitleStreamIndex", value: "\(defaultSubtitleStream!.index!)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subtitle: String? = nil
|
||||||
|
|
||||||
|
// TODO: other forms of media subtitle
|
||||||
|
if self.itemType == .episode {
|
||||||
|
if let seriesName = self.seriesName, let episodeLocator = self.getEpisodeLocator() {
|
||||||
|
subtitle = "\(seriesName) - \(episodeLocator)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let videoPlayerViewModel = VideoPlayerViewModel(item: self,
|
let videoPlayerViewModel = VideoPlayerViewModel(item: self,
|
||||||
title: self.name!,
|
title: self.name!,
|
||||||
subtitle: self.seriesName,
|
subtitle: subtitle,
|
||||||
streamURL: streamURL.url!,
|
streamURL: streamURL.url!,
|
||||||
hlsURL: hlsURL.url!,
|
hlsURL: hlsURL.url!,
|
||||||
response: response,
|
response: response,
|
||||||
|
@ -92,7 +101,7 @@ extension BaseItemDto {
|
||||||
playerState: .playing,
|
playerState: .playing,
|
||||||
shouldShowGoogleCast: false,
|
shouldShowGoogleCast: false,
|
||||||
shouldShowAirplay: false,
|
shouldShowAirplay: false,
|
||||||
subtitlesEnabled: defaultAudioStream?.index != nil,
|
subtitlesEnabled: defaultSubtitleStream?.index != nil,
|
||||||
sliderPercentage: (self.userData?.playedPercentage ?? 0) / 100,
|
sliderPercentage: (self.userData?.playedPercentage ?? 0) / 100,
|
||||||
selectedAudioStreamIndex: defaultAudioStream?.index ?? -1,
|
selectedAudioStreamIndex: defaultAudioStream?.index ?? -1,
|
||||||
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1,
|
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1,
|
||||||
|
|
|
@ -30,7 +30,7 @@ extension Defaults.Keys {
|
||||||
static let autoSelectSubtitlesLangCode = Key<String>("AutoSelectSubtitlesLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite)
|
static let autoSelectSubtitlesLangCode = Key<String>("AutoSelectSubtitlesLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite)
|
||||||
static let autoSelectAudioLangCode = Key<String>("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite)
|
static let autoSelectAudioLangCode = Key<String>("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite)
|
||||||
static let appAppearance = Key<AppAppearance>("appAppearance", default: .system, suite: SwiftfinStore.Defaults.suite)
|
static let appAppearance = Key<AppAppearance>("appAppearance", default: .system, suite: SwiftfinStore.Defaults.suite)
|
||||||
static let videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .thirty, suite: SwiftfinStore.Defaults.suite)
|
static let videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.suite)
|
||||||
static let videoPlayerJumpBackward = Key<VideoPlayerJumpLength>("videoPlayerJumpBackward", default: .thirty, suite: SwiftfinStore.Defaults.suite)
|
static let videoPlayerJumpBackward = Key<VideoPlayerJumpLength>("videoPlayerJumpBackward", default: .fifteen, suite: SwiftfinStore.Defaults.suite)
|
||||||
static let nativeVideoPlayer = Key<Bool>("nativeVideoPlayer", default: false, suite: SwiftfinStore.Defaults.suite)
|
static let nativeVideoPlayer = Key<Bool>("nativeVideoPlayer", default: false, suite: SwiftfinStore.Defaults.suite)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,18 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var sliderIsScrubbing: Bool = false
|
@Published var sliderIsScrubbing: Bool = false
|
||||||
@Published var selectedAudioStreamIndex: Int
|
@Published var selectedAudioStreamIndex: Int {
|
||||||
@Published var selectedSubtitleStreamIndex: Int
|
didSet {
|
||||||
|
previousItemVideoPlayerViewModel?.matchAudioStream(with: self)
|
||||||
|
nextItemVideoPlayerViewModel?.matchAudioStream(with: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published var selectedSubtitleStreamIndex: Int {
|
||||||
|
didSet {
|
||||||
|
previousItemVideoPlayerViewModel?.matchSubtitleStream(with: self)
|
||||||
|
nextItemVideoPlayerViewModel?.matchSubtitleStream(with: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
@Published var showAdjacentItems: Bool
|
@Published var showAdjacentItems: Bool
|
||||||
@Published var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
@Published var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||||
@Published var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
@Published var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||||
|
@ -173,6 +183,9 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { videoPlayerViewModel in
|
} receiveValue: { videoPlayerViewModel in
|
||||||
|
videoPlayerViewModel.matchSubtitleStream(with: self)
|
||||||
|
videoPlayerViewModel.matchAudioStream(with: self)
|
||||||
|
|
||||||
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
||||||
}
|
}
|
||||||
.store(in: &self.cancellables)
|
.store(in: &self.cancellables)
|
||||||
|
@ -184,6 +197,9 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { videoPlayerViewModel in
|
} receiveValue: { videoPlayerViewModel in
|
||||||
|
videoPlayerViewModel.matchSubtitleStream(with: self)
|
||||||
|
videoPlayerViewModel.matchAudioStream(with: self)
|
||||||
|
|
||||||
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
||||||
}
|
}
|
||||||
.store(in: &self.cancellables)
|
.store(in: &self.cancellables)
|
||||||
|
@ -198,6 +214,9 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { videoPlayerViewModel in
|
} receiveValue: { videoPlayerViewModel in
|
||||||
|
videoPlayerViewModel.matchSubtitleStream(with: self)
|
||||||
|
videoPlayerViewModel.matchAudioStream(with: self)
|
||||||
|
|
||||||
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
||||||
}
|
}
|
||||||
.store(in: &self.cancellables)
|
.store(in: &self.cancellables)
|
||||||
|
@ -206,6 +225,9 @@ extension VideoPlayerViewModel {
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
} receiveValue: { videoPlayerViewModel in
|
} receiveValue: { videoPlayerViewModel in
|
||||||
|
videoPlayerViewModel.matchSubtitleStream(with: self)
|
||||||
|
videoPlayerViewModel.matchAudioStream(with: self)
|
||||||
|
|
||||||
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
||||||
}
|
}
|
||||||
.store(in: &self.cancellables)
|
.store(in: &self.cancellables)
|
||||||
|
@ -213,6 +235,25 @@ extension VideoPlayerViewModel {
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func matchSubtitleStream(with masterViewModel: VideoPlayerViewModel) {
|
||||||
|
guard let currentSubtitleStream = masterViewModel.subtitleStreams.first(where: { $0.index == masterViewModel.selectedSubtitleStreamIndex }) else { return }
|
||||||
|
guard let matchingSubtitleStream = self.subtitleStreams.first(where: { mediaStreamAboutEqual($0, currentSubtitleStream) }) else { return }
|
||||||
|
|
||||||
|
self.subtitlesEnabled = masterViewModel.subtitlesEnabled
|
||||||
|
self.selectedSubtitleStreamIndex = matchingSubtitleStream.index ?? -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private func matchAudioStream(with masterViewModel: VideoPlayerViewModel) {
|
||||||
|
guard let currentAudioStream = masterViewModel.audioStreams.first(where: { $0.index == masterViewModel.selectedAudioStreamIndex }),
|
||||||
|
let matchingAudioStream = self.audioStreams.first(where: { mediaStreamAboutEqual($0, currentAudioStream) }) else { return }
|
||||||
|
|
||||||
|
self.selectedAudioStreamIndex = matchingAudioStream.index ?? -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private func mediaStreamAboutEqual(_ lhs: MediaStream, _ rhs: MediaStream) -> Bool {
|
||||||
|
return lhs.displayTitle == rhs.displayTitle && lhs.language == rhs.language
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Reports
|
// MARK: Reports
|
||||||
|
|
Loading…
Reference in New Issue