support external subtitles

This commit is contained in:
Ethan Pippin 2022-01-07 15:37:19 -07:00
parent 352907640f
commit 158edb3b5f
5 changed files with 109 additions and 32 deletions

View File

@ -0,0 +1,22 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
extension MediaStream {
func externalURL(base: String) -> URL? {
guard let deliveryURL = deliveryUrl else { return nil }
var baseComponents = URLComponents(string: base)
baseComponents?.path += deliveryURL
return baseComponents?.url
}
}

View File

@ -67,6 +67,8 @@ final class VideoPlayerViewModel: ViewModel {
}
@Published var autoplayEnabled: Bool {
willSet {
previousItemVideoPlayerViewModel?.autoplayEnabled = newValue
nextItemVideoPlayerViewModel?.autoplayEnabled = newValue
Defaults[.autoplayEnabled] = newValue
}
}
@ -115,6 +117,16 @@ final class VideoPlayerViewModel: ViewModel {
return Int64(currentSeconds) * 10_000_000
}
// MARK: Helpers
var currentAudioStream: MediaStream? {
return audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
}
var currentSubtitleStream: MediaStream? {
return subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
}
// Necessary PassthroughSubject to capture manual scrubbing from sliders
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()

View File

@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController {
// MARK: variables
private var viewModel: VideoPlayerViewModel
private var vlcMediaPlayer = VLCMediaPlayer()
private var vlcMediaPlayer: VLCMediaPlayer
private var lastPlayerTicks: Int64 = 0
private var lastProgressReportTicks: Int64 = 0
private var viewModelListeners = Set<AnyCancellable>()
@ -59,6 +59,7 @@ class VLCPlayerViewController: UIViewController {
init(viewModel: VideoPlayerViewModel) {
self.viewModel = viewModel
self.vlcMediaPlayer = VLCMediaPlayer()
super.init(nibName: nil, bundle: nil)
@ -118,14 +119,6 @@ class VLCPlayerViewController: UIViewController {
view.backgroundColor = .black
// Outside of 'setupMediaPlayer' such that they
// aren't unnecessarily set more than once
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: custom font sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16)
setupMediaPlayer(newViewModel: viewModel)
setupPanGestureRecognizer()
@ -387,6 +380,28 @@ extension VLCPlayerViewController {
/// Use case for this is setting new media within the same VLCPlayerViewController
func setupMediaPlayer(newViewModel: VideoPlayerViewModel) {
// remove old player
if vlcMediaPlayer.media != nil {
viewModelListeners.forEach({ $0.cancel() })
vlcMediaPlayer.stop()
viewModel.sendStopReport()
viewModel.playerOverlayDelegate = nil
}
vlcMediaPlayer = VLCMediaPlayer()
// setup with new player and view model
vlcMediaPlayer = VLCMediaPlayer()
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16)
stopOverlayDismissTimer()
// Stop current media if there is one
@ -436,6 +451,13 @@ extension VLCPlayerViewController {
func startPlayback() {
vlcMediaPlayer.play()
// Setup external subtitles
for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) {
if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) {
vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false)
}
}
setMediaPlayerTimeAtCurrentSlider()
viewModel.sendPlayReport()
@ -672,7 +694,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
}
// If needing to fix subtitle streams during playback
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled {
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex &&
viewModel.subtitlesEnabled {
didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex)
}

View File

@ -274,6 +274,8 @@
E1218C9C271A26C400EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9B271A26C400EA0737 /* Nuke */; };
E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; };
E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9F271A2CF200EA0737 /* Nuke */; };
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; };
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; };
E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; };
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
@ -645,6 +647,7 @@
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; };
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; };
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = "<group>"; };
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = "<group>"; };
@ -1502,10 +1505,11 @@
isa = PBXGroup;
children = (
E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
);
path = JellyfinAPIExtensions;
@ -1759,7 +1763,7 @@
536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */,
53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */,
62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */,
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */,
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */,
E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */,
E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */,
E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
@ -2051,6 +2055,7 @@
E1C812CC277AE40A00918266 /* VideoPlayerView.swift in Sources */,
53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */,
53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */,
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */,
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
@ -2191,6 +2196,7 @@
E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */,
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */,
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
@ -2701,7 +2707,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = "";
@ -2738,7 +2744,7 @@
CURRENT_PROJECT_VERSION = 66;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = TY84JMYEFE;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
EXCLUDED_ARCHS = "";
@ -2769,7 +2775,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = TY84JMYEFE;
INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -2796,7 +2802,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 66;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = TY84JMYEFE;
INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -2936,7 +2942,7 @@
minimumVersion = 1.0.0;
};
};
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */ = {
E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/JohnEstropia/CoreStore.git";
requirement = {
@ -3060,17 +3066,17 @@
};
E13DD3C52716499E009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore;
};
E13DD3CC27164CA7009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore;
};
E13DD3CE27164E1F009D4DAF /* CoreStore */ = {
isa = XCSwiftPackageProductDependency;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */;
package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */;
productName = CoreStore;
};
E13DD3D227168E65009D4DAF /* Defaults */ = {

View File

@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController {
// MARK: variables
private var viewModel: VideoPlayerViewModel
private var vlcMediaPlayer = VLCMediaPlayer()
private var vlcMediaPlayer: VLCMediaPlayer
private var lastPlayerTicks: Int64 = 0
private var lastProgressReportTicks: Int64 = 0
private var viewModelListeners = Set<AnyCancellable>()
@ -49,6 +49,7 @@ class VLCPlayerViewController: UIViewController {
init(viewModel: VideoPlayerViewModel) {
self.viewModel = viewModel
self.vlcMediaPlayer = VLCMediaPlayer()
super.init(nibName: nil, bundle: nil)
@ -97,14 +98,6 @@ class VLCPlayerViewController: UIViewController {
view.backgroundColor = .black
// These are kept outside of 'setupMediaPlayer' such that
// they aren't unnecessarily set more than once
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
setupMediaPlayer(newViewModel: viewModel)
refreshJumpBackwardOverlayView(with: viewModel.jumpBackwardLength)
@ -287,9 +280,8 @@ extension VLCPlayerViewController {
/// Use case for this is setting new media within the same VLCPlayerViewController
func setupMediaPlayer(newViewModel: VideoPlayerViewModel) {
stopOverlayDismissTimer()
// remove old player
// Stop current media if there is one
if vlcMediaPlayer.media != nil {
viewModelListeners.forEach({ $0.cancel() })
@ -298,6 +290,20 @@ extension VLCPlayerViewController {
viewModel.playerOverlayDelegate = nil
}
vlcMediaPlayer = VLCMediaPlayer()
// setup with new player and view model
vlcMediaPlayer = VLCMediaPlayer()
vlcMediaPlayer.delegate = self
vlcMediaPlayer.drawable = videoContentView
// TODO: Custom subtitle sizes
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
stopOverlayDismissTimer()
lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
@ -334,6 +340,13 @@ extension VLCPlayerViewController {
func startPlayback() {
vlcMediaPlayer.play()
// Setup external subtitles
for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) {
if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) {
vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false)
}
}
setMediaPlayerTimeAtCurrentSlider()
viewModel.sendPlayReport()
@ -526,7 +539,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
}
// If needing to fix subtitle streams during playback
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled {
if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex &&
viewModel.subtitlesEnabled {
didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex)
}