commit
c5b2a3ce0c
|
@ -14,6 +14,7 @@
|
||||||
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||||
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; };
|
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; };
|
||||||
09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; };
|
09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; };
|
||||||
|
0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */; };
|
||||||
531069572684E7EE00CFFDBA /* InfoTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */; };
|
531069572684E7EE00CFFDBA /* InfoTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */; };
|
||||||
531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069512684E7EE00CFFDBA /* MediaInfoView.swift */; };
|
531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069512684E7EE00CFFDBA /* MediaInfoView.swift */; };
|
||||||
531069592684E7EE00CFFDBA /* SubtitlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069522684E7EE00CFFDBA /* SubtitlesView.swift */; };
|
531069592684E7EE00CFFDBA /* SubtitlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069522684E7EE00CFFDBA /* SubtitlesView.swift */; };
|
||||||
|
@ -204,6 +205,7 @@
|
||||||
091B5A872683142E00D78B61 /* ServerDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDiscovery.swift; sourceTree = "<group>"; };
|
091B5A872683142E00D78B61 /* ServerDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDiscovery.swift; sourceTree = "<group>"; };
|
||||||
091B5A882683142E00D78B61 /* UDPBroadCastConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UDPBroadCastConnection.swift; sourceTree = "<group>"; };
|
091B5A882683142E00D78B61 /* UDPBroadCastConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UDPBroadCastConnection.swift; sourceTree = "<group>"; };
|
||||||
09389CC626819B4500AE350E /* VideoPlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerModel.swift; sourceTree = "<group>"; };
|
09389CC626819B4500AE350E /* VideoPlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerModel.swift; sourceTree = "<group>"; };
|
||||||
|
0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoUpNextView.swift; sourceTree = "<group>"; };
|
||||||
3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.debug.xcconfig"; sourceTree = "<group>"; };
|
3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoTabBarViewController.swift; sourceTree = "<group>"; };
|
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -392,11 +394,11 @@
|
||||||
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
|
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
|
|
||||||
531069512684E7EE00CFFDBA /* MediaInfoView.swift */,
|
531069512684E7EE00CFFDBA /* MediaInfoView.swift */,
|
||||||
531069522684E7EE00CFFDBA /* SubtitlesView.swift */,
|
531069522684E7EE00CFFDBA /* SubtitlesView.swift */,
|
||||||
531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
|
|
||||||
531069542684E7EE00CFFDBA /* AudioView.swift */,
|
531069542684E7EE00CFFDBA /* AudioView.swift */,
|
||||||
|
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
|
||||||
|
531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
|
||||||
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */,
|
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */,
|
||||||
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */,
|
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */,
|
||||||
);
|
);
|
||||||
|
@ -539,6 +541,7 @@
|
||||||
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
|
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
|
||||||
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
||||||
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
|
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
|
||||||
|
0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */,
|
||||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
|
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
|
||||||
532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */,
|
532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */,
|
||||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
||||||
|
@ -1014,6 +1017,7 @@
|
||||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
||||||
|
0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */,
|
||||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
||||||
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
|
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
|
||||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19115.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_5" orientation="landscape" appearance="light"/>
|
<device id="retina6_5" orientation="landscape" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19107.4"/>
|
|
||||||
<capability name="Image references" minToolsVersion="12.0"/>
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
|
@ -170,6 +169,11 @@
|
||||||
<outletCollection property="gestureRecognizers" destination="iQW-fW-KWT" appends="YES" id="H09-88-nzQ"/>
|
<outletCollection property="gestureRecognizers" destination="iQW-fW-KWT" appends="YES" id="H09-88-nzQ"/>
|
||||||
</connections>
|
</connections>
|
||||||
</view>
|
</view>
|
||||||
|
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CY9-gw-dv8" userLabel="UpNextView">
|
||||||
|
<rect key="frame" x="675" y="254" width="224" height="161"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.34509803921568627" green="0.33725490196078434" blue="0.83921568627450982" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
<viewLayoutGuide key="safeArea" id="zud-b9-RyD"/>
|
<viewLayoutGuide key="safeArea" id="zud-b9-RyD"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
@ -194,6 +198,7 @@
|
||||||
<outlet property="seekSlider" destination="e9f-8l-RdN" id="b3H-tn-TPG"/>
|
<outlet property="seekSlider" destination="e9f-8l-RdN" id="b3H-tn-TPG"/>
|
||||||
<outlet property="timeText" destination="qft-iu-f1z" id="pAX-J3-I53"/>
|
<outlet property="timeText" destination="qft-iu-f1z" id="pAX-J3-I53"/>
|
||||||
<outlet property="titleLabel" destination="o8N-R1-DhT" id="E7D-iU-bMi"/>
|
<outlet property="titleLabel" destination="o8N-R1-DhT" id="E7D-iU-bMi"/>
|
||||||
|
<outlet property="upNextView" destination="CY9-gw-dv8" id="BP6-bc-6Vk"/>
|
||||||
<outlet property="videoContentView" destination="Tsh-rC-BwO" id="5uR-No-wLy"/>
|
<outlet property="videoContentView" destination="Tsh-rC-BwO" id="5uR-No-wLy"/>
|
||||||
<outlet property="videoControlsView" destination="Qcb-Fb-qZl" id="Z1U-Qr-8ND"/>
|
<outlet property="videoControlsView" destination="Qcb-Fb-qZl" id="Z1U-Qr-8ND"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
var mediaPlayer = VLCMediaPlayer()
|
var mediaPlayer = VLCMediaPlayer()
|
||||||
|
|
||||||
|
@IBOutlet weak var upNextView: UIView!
|
||||||
@IBOutlet weak var timeText: UILabel!
|
@IBOutlet weak var timeText: UILabel!
|
||||||
@IBOutlet weak var videoContentView: UIView!
|
@IBOutlet weak var videoContentView: UIView!
|
||||||
@IBOutlet weak var videoControlsView: UIView!
|
@IBOutlet weak var videoControlsView: UIView!
|
||||||
|
@ -67,17 +68,23 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
}
|
}
|
||||||
var hasSentRemoteSeek: Bool = false
|
var hasSentRemoteSeek: Bool = false
|
||||||
|
|
||||||
|
var selectedPlaybackSpeedIndex : Int = 3
|
||||||
var selectedAudioTrack: Int32 = -1
|
var selectedAudioTrack: Int32 = -1
|
||||||
var selectedCaptionTrack: Int32 = -1
|
var selectedCaptionTrack: Int32 = -1
|
||||||
var playSessionId: String = ""
|
var playSessionId: String = ""
|
||||||
var lastProgressReportTime: Double = 0
|
var lastProgressReportTime: Double = 0
|
||||||
var subtitleTrackArray: [Subtitle] = []
|
var subtitleTrackArray: [Subtitle] = []
|
||||||
var audioTrackArray: [AudioTrack] = []
|
var audioTrackArray: [AudioTrack] = []
|
||||||
|
let playbackSpeeds : [Float] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
|
||||||
|
|
||||||
var manifest: BaseItemDto = BaseItemDto()
|
var manifest: BaseItemDto = BaseItemDto()
|
||||||
var playbackItem = PlaybackItem()
|
var playbackItem = PlaybackItem()
|
||||||
var remoteTimeUpdateTimer: Timer?
|
var remoteTimeUpdateTimer: Timer?
|
||||||
|
|
||||||
|
var smallView : CGRect = .zero
|
||||||
|
var largeView : CGRect = .zero
|
||||||
|
var upNextViewModel: UpNextViewModel = UpNextViewModel()
|
||||||
|
|
||||||
// MARK: IBActions
|
// MARK: IBActions
|
||||||
@IBAction func seekSliderStart(_ sender: Any) {
|
@IBAction func seekSliderStart(_ sender: Any) {
|
||||||
if playerDestination == .local {
|
if playerDestination == .local {
|
||||||
|
@ -140,6 +147,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
@IBAction func controlViewTapped(_ sender: Any) {
|
@IBAction func controlViewTapped(_ sender: Any) {
|
||||||
if playerDestination == .local {
|
if playerDestination == .local {
|
||||||
videoControlsView.isHidden = true
|
videoControlsView.isHidden = true
|
||||||
|
if manifest.type == "Episode" {
|
||||||
|
smallNextUpView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +157,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
if playerDestination == .local {
|
if playerDestination == .local {
|
||||||
videoControlsView.isHidden = false
|
videoControlsView.isHidden = false
|
||||||
controlsAppearTime = CACurrentMediaTime()
|
controlsAppearTime = CACurrentMediaTime()
|
||||||
|
if manifest.type == "Episode" {
|
||||||
|
largeNextUpView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,9 +353,34 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var nowPlayingInfo = [String: Any]()
|
var nowPlayingInfo = [String : Any]()
|
||||||
nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? ""
|
|
||||||
|
var runTicks = 0
|
||||||
|
var playbackTicks = 0
|
||||||
|
|
||||||
|
if let ticks = manifest.runTimeTicks {
|
||||||
|
runTicks = Int(ticks / 10_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ticks = manifest.userData?.playbackPositionTicks {
|
||||||
|
playbackTicks = Int(ticks / 10_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? "Jellyfin Video"
|
||||||
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
|
||||||
|
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = AVMediaType.video
|
||||||
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = runTicks
|
||||||
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playbackTicks
|
||||||
|
|
||||||
|
if let imageData = NSData(contentsOf: manifest.getPrimaryImage(maxWidth: 200)) {
|
||||||
|
if let artworkImage = UIImage(data: imageData as Data) {
|
||||||
|
let artwork = MPMediaItemArtwork.init(boundsSize: artworkImage.size, requestHandler: { (size) -> UIImage in
|
||||||
|
return artworkImage
|
||||||
|
})
|
||||||
|
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
||||||
|
|
||||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||||
|
@ -354,6 +392,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
titleLabel.text = manifest.name ?? ""
|
titleLabel.text = manifest.name ?? ""
|
||||||
} else {
|
} else {
|
||||||
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||||
|
|
||||||
|
setupNextUpView()
|
||||||
|
upNextViewModel.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat {
|
if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat {
|
||||||
|
@ -414,7 +455,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
|
|
||||||
mediaPlayer.delegate = self
|
mediaPlayer.delegate = self
|
||||||
mediaPlayer.drawable = videoContentView
|
mediaPlayer.drawable = videoContentView
|
||||||
|
|
||||||
|
setupMediaPlayer()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupMediaPlayer() {
|
||||||
|
|
||||||
// Fetch max bitrate from UserDefaults depending on current connection mode
|
// Fetch max bitrate from UserDefaults depending on current connection mode
|
||||||
let maxBitrate = Defaults[.inNetworkBandwidth]
|
let maxBitrate = Defaults[.inNetworkBandwidth]
|
||||||
|
|
||||||
|
@ -534,6 +581,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
|
|
||||||
self.sendPlayReport()
|
self.sendPlayReport()
|
||||||
playbackItem = item
|
playbackItem = item
|
||||||
|
|
||||||
|
self.setupNowPlayingCC()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startLocalPlaybackEngine(true)
|
startLocalPlaybackEngine(true)
|
||||||
|
@ -630,6 +680,97 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
||||||
selectedAudioTrack = newTrackID
|
selectedAudioTrack = newTrackID
|
||||||
mediaPlayer.currentAudioTrackIndex = newTrackID
|
mediaPlayer.currentAudioTrackIndex = newTrackID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func playbackSpeedChanged(index: Int) {
|
||||||
|
selectedPlaybackSpeedIndex = index
|
||||||
|
mediaPlayer.rate = playbackSpeeds[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func smallNextUpView() {
|
||||||
|
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseIn) { [self] in
|
||||||
|
upNextViewModel.largeView = false
|
||||||
|
upNextView.frame = smallView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func largeNextUpView() {
|
||||||
|
UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseOut) { [self] in
|
||||||
|
upNextViewModel.largeView = true
|
||||||
|
upNextView.frame = largeView
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupNextUpView() {
|
||||||
|
getNextEpisode()
|
||||||
|
|
||||||
|
// Create the swiftUI view
|
||||||
|
let contentView = UIHostingController(rootView: VideoUpNextView(viewModel: upNextViewModel))
|
||||||
|
self.upNextView.addSubview(contentView.view)
|
||||||
|
contentView.view.backgroundColor = .clear
|
||||||
|
contentView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.view.topAnchor.constraint(equalTo: upNextView.topAnchor).isActive = true
|
||||||
|
contentView.view.bottomAnchor.constraint(equalTo: upNextView.bottomAnchor).isActive = true
|
||||||
|
contentView.view.leftAnchor.constraint(equalTo: upNextView.leftAnchor).isActive = true
|
||||||
|
contentView.view.rightAnchor.constraint(equalTo: upNextView.rightAnchor).isActive = true
|
||||||
|
|
||||||
|
// Frame sizes depend on if controls are hidden or shown
|
||||||
|
smallView = upNextView.frame
|
||||||
|
largeView = CGRect(x: 460, y: 90, width: 400, height: 270)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextEpisode() {
|
||||||
|
TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, startItemId: manifest.id, limit: 2)
|
||||||
|
.sink(receiveCompletion: { completion in
|
||||||
|
print(completion)
|
||||||
|
}, receiveValue: { [self] response in
|
||||||
|
// Returns 2 items, the first is the current episode
|
||||||
|
// The second is the next episode
|
||||||
|
if let item = response.items?.last {
|
||||||
|
self.upNextViewModel.item = item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPlayerToNextUp() {
|
||||||
|
mediaPlayer.stop()
|
||||||
|
|
||||||
|
ssTargetValueOffset = 0
|
||||||
|
ssStartValue = 0
|
||||||
|
|
||||||
|
paused = true
|
||||||
|
lastTime = 0.0
|
||||||
|
startTime = 0
|
||||||
|
controlsAppearTime = 0
|
||||||
|
isSeeking = false
|
||||||
|
|
||||||
|
|
||||||
|
remotePositionTicks = 0
|
||||||
|
|
||||||
|
selectedPlaybackSpeedIndex = 3
|
||||||
|
selectedAudioTrack = -1
|
||||||
|
selectedCaptionTrack = -1
|
||||||
|
playSessionId = ""
|
||||||
|
lastProgressReportTime = 0
|
||||||
|
subtitleTrackArray = []
|
||||||
|
audioTrackArray = []
|
||||||
|
|
||||||
|
manifest = upNextViewModel.item!
|
||||||
|
playbackItem = PlaybackItem()
|
||||||
|
|
||||||
|
upNextViewModel.item = nil
|
||||||
|
|
||||||
|
upNextView.isHidden = true
|
||||||
|
shouldShowLoadingScreen = true
|
||||||
|
videoControlsView.isHidden = true
|
||||||
|
|
||||||
|
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||||
|
|
||||||
|
setupMediaPlayer()
|
||||||
|
getNextEpisode()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - GCKGenericChannelDelegate
|
// MARK: - GCKGenericChannelDelegate
|
||||||
|
@ -794,7 +935,6 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
||||||
break
|
break
|
||||||
case .playing :
|
case .playing :
|
||||||
print("Video is playing")
|
print("Video is playing")
|
||||||
self.setupNowPlayingCC()
|
|
||||||
sendProgressReport(eventName: "unpause")
|
sendProgressReport(eventName: "unpause")
|
||||||
delegate?.hideLoadingView(self)
|
delegate?.hideLoadingView(self)
|
||||||
paused = false
|
paused = false
|
||||||
|
@ -826,10 +966,21 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
||||||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||||
seekSlider.setValue(mediaPlayer.position, animated: true)
|
seekSlider.setValue(mediaPlayer.position, animated: true)
|
||||||
delegate?.hideLoadingView(self)
|
delegate?.hideLoadingView(self)
|
||||||
|
|
||||||
|
if manifest.type == "Episode" && upNextViewModel.item != nil{
|
||||||
|
if time > 0.96 {
|
||||||
|
upNextView.isHidden = false
|
||||||
|
self.jumpForwardButton.isHidden = true
|
||||||
|
} else {
|
||||||
|
upNextView.isHidden = true
|
||||||
|
self.jumpForwardButton.isHidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
timeText.text = String(mediaPlayer.remainingTime.stringValue.dropFirst())
|
timeText.text = String(mediaPlayer.remainingTime.stringValue.dropFirst())
|
||||||
|
|
||||||
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
||||||
|
self.smallNextUpView()
|
||||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
||||||
self.videoControlsView.alpha = 0.0
|
self.videoControlsView.alpha = 0.0
|
||||||
}, completion: { (_: Bool) in
|
}, completion: { (_: Bool) in
|
||||||
|
|
|
@ -37,7 +37,8 @@ struct VideoPlayerSettings: View {
|
||||||
weak var delegate: PlayerViewController!
|
weak var delegate: PlayerViewController!
|
||||||
@State var captionTrack: Int32 = -99
|
@State var captionTrack: Int32 = -99
|
||||||
@State var audioTrack: Int32 = -99
|
@State var audioTrack: Int32 = -99
|
||||||
|
@State var playbackSpeedSelection : Int = 3
|
||||||
|
|
||||||
init(delegate: PlayerViewController) {
|
init(delegate: PlayerViewController) {
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
|
@ -60,6 +61,20 @@ struct VideoPlayerSettings: View {
|
||||||
}.onChange(of: audioTrack) { track in
|
}.onChange(of: audioTrack) { track in
|
||||||
self.delegate.audioTrackChanged(newTrackID: track)
|
self.delegate.audioTrackChanged(newTrackID: track)
|
||||||
}
|
}
|
||||||
|
Picker("Playback Speed", selection: $playbackSpeedSelection) {
|
||||||
|
ForEach(delegate.playbackSpeeds.indices, id: \.self) { speedIndex in
|
||||||
|
let speed = delegate.playbackSpeeds[speedIndex]
|
||||||
|
if floor(speed) == speed {
|
||||||
|
Text(String(format: "%.0fx", speed)).tag(speedIndex)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Text(String(format: "%.2fx", speed)).tag(speedIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: playbackSpeedSelection, perform: { index in
|
||||||
|
self.delegate.playbackSpeedChanged(index: index)
|
||||||
|
})
|
||||||
}.navigationBarTitleDisplayMode(.inline)
|
}.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationTitle("Audio & Captions")
|
.navigationTitle("Audio & Captions")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -78,8 +93,9 @@ struct VideoPlayerSettings: View {
|
||||||
}
|
}
|
||||||
}.offset(y: UIDevice.current.userInterfaceIdiom == .pad ? 14 : 0)
|
}.offset(y: UIDevice.current.userInterfaceIdiom == .pad ? 14 : 0)
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
_captionTrack.wrappedValue = self.delegate.selectedCaptionTrack
|
captionTrack = self.delegate.selectedCaptionTrack
|
||||||
_audioTrack.wrappedValue = self.delegate.selectedAudioTrack
|
audioTrack = self.delegate.selectedAudioTrack
|
||||||
|
playbackSpeedSelection = self.delegate.selectedPlaybackSpeedIndex
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
//
|
||||||
|
/*
|
||||||
|
* 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 SwiftUI
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
class UpNextViewModel: ObservableObject {
|
||||||
|
@Published var largeView: Bool = false
|
||||||
|
@Published var item: BaseItemDto? = nil
|
||||||
|
var delegate: PlayerViewController?
|
||||||
|
|
||||||
|
func episodeAndSeasonNumber() -> String {
|
||||||
|
if let pID = item?.parentIndexNumber, let id = item?.indexNumber {
|
||||||
|
return "S\(pID):E\(id)"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func episodeName() -> String {
|
||||||
|
if let name = item?.name {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextUp() {
|
||||||
|
if delegate != nil {
|
||||||
|
delegate?.setPlayerToNextUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VideoUpNextView: View {
|
||||||
|
|
||||||
|
@ObservedObject var viewModel: UpNextViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: viewModel.largeView ? .leading : .center) {
|
||||||
|
Text("Up Next")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(viewModel.largeView ? .title : .body)
|
||||||
|
|
||||||
|
Button(action: viewModel.nextUp, label: {image})
|
||||||
|
|
||||||
|
if viewModel.largeView {
|
||||||
|
Text(viewModel.episodeName())
|
||||||
|
.padding(.trailing, 50)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.minimumScaleFactor(0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shadow(color: .black, radius: 20)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var image : some View {
|
||||||
|
if let url = viewModel.item?.getPrimaryImage(maxWidth: 100) {
|
||||||
|
return AnyView(
|
||||||
|
ImageView(src: url)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.aspectRatio(CGSize(width: 16, height: 9), contentMode: .fit)
|
||||||
|
.overlay(overlayIndicator, alignment: .topTrailing)
|
||||||
|
.cornerRadius(5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return AnyView(EmptyView())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlayIndicator : some View {
|
||||||
|
Text(viewModel.episodeAndSeasonNumber())
|
||||||
|
.font(viewModel.largeView ? .title3 : .body)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.horizontal, 5)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
.cornerRadius(5)
|
||||||
|
.padding(5)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue