Add up next view

This commit is contained in:
Stephen Byatt 2021-06-26 18:19:01 +10:00
parent e37d944d97
commit 175526cc05
4 changed files with 157 additions and 36 deletions

View File

@ -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 */; };
@ -203,6 +204,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>"; };
@ -536,6 +538,7 @@
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */,
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
532E68CE267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift */,
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
@ -1002,6 +1005,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 */,

View File

@ -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>

View File

@ -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!
@ -79,7 +80,11 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
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 {
@ -142,6 +147,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
@IBAction func controlViewTapped(_ sender: Any) {
if playerDestination == .local {
videoControlsView.isHidden = true
if manifest.type == "Episode" {
smallNextUpView()
}
}
}
@ -149,6 +157,9 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
if playerDestination == .local {
videoControlsView.isHidden = false
controlsAppearTime = CACurrentMediaTime()
if manifest.type == "Episode" {
largeNextUpView()
}
}
}
@ -381,38 +392,8 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
titleLabel.text = manifest.name ?? ""
} else {
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0))\(manifest.name ?? "")"
print("ep count \(manifest.episodeCount) current \(manifest.indexNumber) end \(manifest.indexNumberEnd)")
// TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, season: manifest.parentIndexNumber ?? 0, startIndex: manifest.indexNumber, limit: 1)
// .sink(receiveCompletion: { completion in
// print(completion)
// }, receiveValue: { response in
// if let item = response.items?.first {
// print(item.name, item.indexNumber)
// }
// })
// .store(in: &cancellables)
// TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, season: manifest.parentIndexNumber ?? 0, startIndex: manifest.indexNumber, limit: 1)
// .sink(receiveCompletion: { completion in
// print(completion)
// }, receiveValue: { response in
// if let item = response.items?.first {
// print(item.name, item.indexNumber)
// }
// })
// .store(in: &cancellables)
//
// TvShowsAPI.getNextUp(userId: SessionManager.current.user.user_id!, startIndex: manifest.indexNumber, limit: 1, seriesId: manifest.seriesId)
// .sink(receiveCompletion: { completion in
// print(completion)
// }, receiveValue: { response in
// print(response.items)
// })
// .store(in: &cancellables)
//
setupNextUpView()
}
if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat {
@ -697,6 +678,43 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
selectedPlaybackSpeedIndex = index
mediaPlayer.rate = playbackSpeeds[index]
}
func smallNextUpView() {
upNextViewModel.largeView = false
upNextView.frame = smallView
}
func largeNextUpView() {
upNextViewModel.largeView = true
upNextView.frame = largeView
}
func setupNextUpView() {
// Get next episode item
TvShowsAPI.getEpisodes(seriesId: manifest.seriesId!, userId: SessionManager.current.user.user_id!, season: manifest.parentIndexNumber ?? 0, startIndex: manifest.indexNumber, limit: 1)
.sink(receiveCompletion: { completion in
print(completion)
}, receiveValue: { [self] response in
if let item = response.items?.first {
self.upNextViewModel.item = item
}
})
.store(in: &cancellables)
// 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: 500, y: 90, width: 400, height: 270)
}
}
// MARK: - GCKGenericChannelDelegate
@ -892,12 +910,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.95 {
upNextView.isHidden = false
} else {
upNextView.isHidden = true
}
}
timeText.text = String(mediaPlayer.remainingTime.stringValue.dropFirst())
if CACurrentMediaTime() - controlsAppearTime > 5 {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
self.videoControlsView.alpha = 0.0
self.smallNextUpView()
}, completion: { (_: Bool) in
self.videoControlsView.isHidden = true
self.videoControlsView.alpha = 1

View File

@ -0,0 +1,85 @@
//
/*
* 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
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 ""
}
}
struct VideoUpNextView: View {
@ObservedObject var viewModel: UpNextViewModel
var body: some View {
Button(action: {
print("Next episode")
}, label: {
VStack(alignment: viewModel.largeView ? .leading : .center) {
Text("Up Next")
.foregroundColor(.white)
.font(viewModel.largeView ? .title : .body)
image
if viewModel.largeView {
Text(viewModel.episodeName())
.padding(.trailing, 50)
.foregroundColor(.white)
.font(.title)
.lineLimit(1)
}
}
})
}
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))
}
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)
}
}