commit
c5b2a3ce0c
|
@ -14,6 +14,7 @@
|
|||
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||
09389CC726819B4600AE350E /* 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 */; };
|
||||
531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069512684E7EE00CFFDBA /* MediaInfoView.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -392,11 +394,11 @@
|
|||
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
|
||||
531069512684E7EE00CFFDBA /* MediaInfoView.swift */,
|
||||
531069522684E7EE00CFFDBA /* SubtitlesView.swift */,
|
||||
531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
|
||||
531069542684E7EE00CFFDBA /* AudioView.swift */,
|
||||
531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */,
|
||||
531069532684E7EE00CFFDBA /* VideoPlayer.swift */,
|
||||
531069552684E7EE00CFFDBA /* VideoPlayerViewController.swift */,
|
||||
531069562684E7EE00CFFDBA /* VideoPlayerStoryboard.storyboard */,
|
||||
);
|
||||
|
@ -539,6 +541,7 @@
|
|||
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
|
||||
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
||||
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
|
||||
0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */,
|
||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
|
||||
532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */,
|
||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
||||
|
@ -1014,6 +1017,7 @@
|
|||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
||||
0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */,
|
||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
||||
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
|
||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||
|
|
|
@ -1,9 +1,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"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19107.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="Image references" minToolsVersion="12.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.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"/>
|
||||
</connections>
|
||||
</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>
|
||||
<viewLayoutGuide key="safeArea" id="zud-b9-RyD"/>
|
||||
<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="timeText" destination="qft-iu-f1z" id="pAX-J3-I53"/>
|
||||
<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="videoControlsView" destination="Qcb-Fb-qZl" id="Z1U-Qr-8ND"/>
|
||||
</connections>
|
||||
|
|
|
@ -32,6 +32,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
var cancellables = Set<AnyCancellable>()
|
||||
var mediaPlayer = VLCMediaPlayer()
|
||||
|
||||
@IBOutlet weak var upNextView: UIView!
|
||||
@IBOutlet weak var timeText: UILabel!
|
||||
@IBOutlet weak var videoContentView: UIView!
|
||||
@IBOutlet weak var videoControlsView: UIView!
|
||||
|
@ -67,17 +68,23 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
var hasSentRemoteSeek: Bool = false
|
||||
|
||||
var selectedPlaybackSpeedIndex : Int = 3
|
||||
var selectedAudioTrack: Int32 = -1
|
||||
var selectedCaptionTrack: Int32 = -1
|
||||
var playSessionId: String = ""
|
||||
var lastProgressReportTime: Double = 0
|
||||
var subtitleTrackArray: [Subtitle] = []
|
||||
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 playbackItem = PlaybackItem()
|
||||
var remoteTimeUpdateTimer: Timer?
|
||||
|
||||
|
||||
var smallView : CGRect = .zero
|
||||
var largeView : CGRect = .zero
|
||||
var upNextViewModel: UpNextViewModel = UpNextViewModel()
|
||||
|
||||
// MARK: IBActions
|
||||
@IBAction func seekSliderStart(_ sender: Any) {
|
||||
if playerDestination == .local {
|
||||
|
@ -140,6 +147,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
@IBAction func controlViewTapped(_ sender: Any) {
|
||||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = true
|
||||
if manifest.type == "Episode" {
|
||||
smallNextUpView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,6 +157,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
if playerDestination == .local {
|
||||
videoControlsView.isHidden = false
|
||||
controlsAppearTime = CACurrentMediaTime()
|
||||
if manifest.type == "Episode" {
|
||||
largeNextUpView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,9 +353,34 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
}
|
||||
}
|
||||
|
||||
var nowPlayingInfo = [String: Any]()
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = manifest.name ?? ""
|
||||
|
||||
var nowPlayingInfo = [String : Any]()
|
||||
|
||||
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
|
||||
|
||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||
|
@ -354,6 +392,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
titleLabel.text = manifest.name ?? ""
|
||||
} else {
|
||||
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 {
|
||||
|
@ -414,7 +455,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
mediaPlayer.delegate = self
|
||||
mediaPlayer.drawable = videoContentView
|
||||
|
||||
setupMediaPlayer()
|
||||
|
||||
}
|
||||
|
||||
func setupMediaPlayer() {
|
||||
|
||||
// Fetch max bitrate from UserDefaults depending on current connection mode
|
||||
let maxBitrate = Defaults[.inNetworkBandwidth]
|
||||
|
||||
|
@ -534,6 +581,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
|
||||
self.sendPlayReport()
|
||||
playbackItem = item
|
||||
|
||||
self.setupNowPlayingCC()
|
||||
|
||||
}
|
||||
|
||||
startLocalPlaybackEngine(true)
|
||||
|
@ -630,6 +680,97 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
|
|||
selectedAudioTrack = 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
|
||||
|
@ -794,7 +935,6 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
break
|
||||
case .playing :
|
||||
print("Video is playing")
|
||||
self.setupNowPlayingCC()
|
||||
sendProgressReport(eventName: "unpause")
|
||||
delegate?.hideLoadingView(self)
|
||||
paused = false
|
||||
|
@ -826,10 +966,21 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
|
|||
mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
|
||||
seekSlider.setValue(mediaPlayer.position, animated: true)
|
||||
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())
|
||||
|
||||
if CACurrentMediaTime() - controlsAppearTime > 5 {
|
||||
self.smallNextUpView()
|
||||
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
|
||||
self.videoControlsView.alpha = 0.0
|
||||
}, completion: { (_: Bool) in
|
||||
|
|
|
@ -37,7 +37,8 @@ struct VideoPlayerSettings: View {
|
|||
weak var delegate: PlayerViewController!
|
||||
@State var captionTrack: Int32 = -99
|
||||
@State var audioTrack: Int32 = -99
|
||||
|
||||
@State var playbackSpeedSelection : Int = 3
|
||||
|
||||
init(delegate: PlayerViewController) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
@ -60,6 +61,20 @@ struct VideoPlayerSettings: View {
|
|||
}.onChange(of: audioTrack) { track in
|
||||
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)
|
||||
.navigationTitle("Audio & Captions")
|
||||
.toolbar {
|
||||
|
@ -78,8 +93,9 @@ struct VideoPlayerSettings: View {
|
|||
}
|
||||
}.offset(y: UIDevice.current.userInterfaceIdiom == .pad ? 14 : 0)
|
||||
.onAppear(perform: {
|
||||
_captionTrack.wrappedValue = self.delegate.selectedCaptionTrack
|
||||
_audioTrack.wrappedValue = self.delegate.selectedAudioTrack
|
||||
captionTrack = self.delegate.selectedCaptionTrack
|
||||
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