initial previous and next item feature
This commit is contained in:
parent
99445e387c
commit
fe0c8ee03b
|
@ -61,6 +61,24 @@ struct VLCPlayerCompactOverlayView: View, VideoPlayerOverlay {
|
|||
|
||||
HStack(spacing: 20) {
|
||||
|
||||
if viewModel.showAdjacentItems {
|
||||
Button {
|
||||
viewModel.playerOverlayDelegate?.didSelectPreviousItem()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left.circle")
|
||||
}
|
||||
.disabled(viewModel.previousItemVideoPlayerViewModel == nil)
|
||||
.foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white)
|
||||
|
||||
Button {
|
||||
viewModel.playerOverlayDelegate?.didSelectNextItem()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right.circle")
|
||||
}
|
||||
.disabled(viewModel.nextItemVideoPlayerViewModel == nil)
|
||||
.foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white)
|
||||
}
|
||||
|
||||
if viewModel.shouldShowGoogleCast {
|
||||
Button {
|
||||
viewModel.playerOverlayDelegate?.didSelectGoogleCast()
|
||||
|
@ -205,8 +223,8 @@ struct VLCPlayerCompactOverlayView: View, VideoPlayerOverlay {
|
|||
viewModel.playerOverlayDelegate?.didSelectMain()
|
||||
} label: {
|
||||
mainButtonView
|
||||
.padding(.horizontal, 5)
|
||||
.frame(minWidth: 30, maxWidth: 30)
|
||||
.padding(.horizontal, 10)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
@ -291,7 +309,8 @@ struct VLCPlayerCompactOverlayView_Previews: PreviewProvider {
|
|||
subtitlesEnabled: true,
|
||||
sliderPercentage: 0.432,
|
||||
selectedAudioStreamIndex: -1,
|
||||
selectedSubtitleStreamIndex: -1))
|
||||
selectedSubtitleStreamIndex: -1,
|
||||
showAdjacentItems: true))
|
||||
}
|
||||
.previewInterfaceOrientation(.landscapeLeft)
|
||||
}
|
||||
|
|
|
@ -239,20 +239,11 @@ struct VLCPlayerOverlayView_Previews: PreviewProvider {
|
|||
subtitlesEnabled: true,
|
||||
sliderPercentage: 0.0,
|
||||
selectedAudioStreamIndex: -1,
|
||||
selectedSubtitleStreamIndex: -1))
|
||||
selectedSubtitleStreamIndex: -1,
|
||||
showAdjacentItems: true))
|
||||
}
|
||||
.previewInterfaceOrientation(.landscapeLeft)
|
||||
}
|
||||
}
|
||||
|
||||
extension HorizontalAlignment {
|
||||
|
||||
private struct EpisodeSeriesTitleAlignment: AlignmentID {
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[HorizontalAlignment.leading]
|
||||
}
|
||||
}
|
||||
|
||||
static let EpisodeSeriesAlignmentGuide = HorizontalAlignment(EpisodeSeriesTitleAlignment.self)
|
||||
|
||||
}
|
||||
|
|
|
@ -12,3 +12,15 @@ import SwiftUI
|
|||
protocol VideoPlayerOverlay: View {
|
||||
var viewModel: VideoPlayerViewModel { get set }
|
||||
}
|
||||
|
||||
extension HorizontalAlignment {
|
||||
|
||||
private struct EpisodeSeriesTitleAlignment: AlignmentID {
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
context[HorizontalAlignment.leading]
|
||||
}
|
||||
}
|
||||
|
||||
static let EpisodeSeriesAlignmentGuide = HorizontalAlignment(EpisodeSeriesTitleAlignment.self)
|
||||
|
||||
}
|
||||
|
|
|
@ -27,4 +27,7 @@ protocol PlayerOverlayDelegate {
|
|||
|
||||
func didSelectAudioStream(index: Int)
|
||||
func didSelectSubtitleStream(index: Int)
|
||||
|
||||
func didSelectPreviousItem()
|
||||
func didSelectNextItem()
|
||||
}
|
||||
|
|
|
@ -15,14 +15,18 @@ import MobileVLCKit
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// TODO: Make the VLC player layer a view
|
||||
// This will allow changing media and putting the view somewhere else
|
||||
// in a compact state, like a small viewer while navigating the app
|
||||
|
||||
class VLCPlayerViewController: UIViewController {
|
||||
|
||||
// MARK: variables
|
||||
|
||||
private let viewModel: VideoPlayerViewModel
|
||||
private var viewModel: VideoPlayerViewModel
|
||||
private var vlcMediaPlayer = VLCMediaPlayer()
|
||||
private var lastPlayerTicks: Int64
|
||||
private var lastProgressReportTicks: Int64
|
||||
private var lastPlayerTicks: Int64 = 0
|
||||
private var lastProgressReportTicks: Int64 = 0
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var overlayDismissTimer: Timer?
|
||||
|
||||
|
@ -31,7 +35,7 @@ class VLCPlayerViewController: UIViewController {
|
|||
}
|
||||
|
||||
private var displayingOverlay: Bool {
|
||||
return overlayHostingController.view.alpha > 0
|
||||
return currentOverlayHostingController?.view.alpha ?? 0 > 0
|
||||
}
|
||||
|
||||
private var jumpForwardLength: VideoPlayerJumpLength {
|
||||
|
@ -44,7 +48,7 @@ class VLCPlayerViewController: UIViewController {
|
|||
|
||||
private lazy var videoContentView = makeVideoContentView()
|
||||
private lazy var tapGestureView = makeTapGestureView()
|
||||
private lazy var overlayHostingController = makeOverlayHostingController()
|
||||
private var currentOverlayHostingController: UIHostingController<VLCPlayerCompactOverlayView>?
|
||||
|
||||
// MARK: init
|
||||
|
||||
|
@ -52,9 +56,6 @@ class VLCPlayerViewController: UIViewController {
|
|||
|
||||
self.viewModel = viewModel
|
||||
|
||||
self.lastPlayerTicks = viewModel.item.userData?.playbackPositionTicks ?? 0
|
||||
self.lastProgressReportTicks = viewModel.item.userData?.playbackPositionTicks ?? 0
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
viewModel.playerOverlayDelegate = self
|
||||
|
@ -67,12 +68,6 @@ class VLCPlayerViewController: UIViewController {
|
|||
private func setupSubviews() {
|
||||
view.addSubview(videoContentView)
|
||||
view.addSubview(tapGestureView)
|
||||
|
||||
addChild(overlayHostingController)
|
||||
overlayHostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
overlayHostingController.view.backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
view.addSubview(overlayHostingController.view)
|
||||
overlayHostingController.didMove(toParent: self)
|
||||
}
|
||||
|
||||
private func setupConstraints() {
|
||||
|
@ -88,12 +83,6 @@ class VLCPlayerViewController: UIViewController {
|
|||
tapGestureView.leftAnchor.constraint(equalTo: videoContentView.leftAnchor),
|
||||
tapGestureView.rightAnchor.constraint(equalTo: videoContentView.rightAnchor)
|
||||
])
|
||||
NSLayoutConstraint.activate([
|
||||
overlayHostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
overlayHostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
overlayHostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
|
||||
overlayHostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: viewWillAppear
|
||||
|
@ -120,39 +109,15 @@ class VLCPlayerViewController: UIViewController {
|
|||
setupSubviews()
|
||||
setupConstraints()
|
||||
|
||||
setupViewModelListeners()
|
||||
|
||||
view.backgroundColor = .black
|
||||
|
||||
setupMediaPlayer()
|
||||
}
|
||||
// These are kept outside of 'setupMediaPlayer' such that
|
||||
// they aren't unnecessarily set more than once
|
||||
vlcMediaPlayer.delegate = self
|
||||
vlcMediaPlayer.drawable = videoContentView
|
||||
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
|
||||
|
||||
// MARK: setupViewModelListeners
|
||||
|
||||
private func setupViewModelListeners() {
|
||||
viewModel.$playbackSpeed.sink { newSpeed in
|
||||
self.vlcMediaPlayer.rate = Float(newSpeed.rawValue)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$screenFilled.sink { shouldFill in
|
||||
self.changeFill(to: shouldFill)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in
|
||||
if sliderIsScrubbing {
|
||||
self.didBeginScrubbing()
|
||||
} else {
|
||||
self.didEndScrubbing(position: self.viewModel.sliderPercentage)
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in
|
||||
self.didSelectAudioStream(index: newAudioStreamIndex)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in
|
||||
self.didSelectSubtitleStream(index: newSubtitleStreamIndex)
|
||||
}.store(in: &cancellables)
|
||||
setupMediaPlayer(newViewModel: viewModel)
|
||||
}
|
||||
|
||||
private func changeFill(to shouldFill: Bool) {
|
||||
|
@ -177,7 +142,6 @@ class VLCPlayerViewController: UIViewController {
|
|||
super.viewDidAppear(animated)
|
||||
|
||||
startPlayback()
|
||||
restartOverlayDismissTimer()
|
||||
}
|
||||
|
||||
// MARK: subviews
|
||||
|
@ -193,7 +157,6 @@ class VLCPlayerViewController: UIViewController {
|
|||
private func makeTapGestureView() -> UIView {
|
||||
let view = UIView()
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.backgroundColor = .clear
|
||||
|
||||
let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
|
||||
let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(didRightSwipe))
|
||||
|
@ -220,33 +183,93 @@ class VLCPlayerViewController: UIViewController {
|
|||
self.didSelectBackward()
|
||||
}
|
||||
|
||||
private func makeOverlayHostingController() -> UIHostingController<VLCPlayerCompactOverlayView> {
|
||||
let overlayView = VLCPlayerCompactOverlayView(viewModel: viewModel)
|
||||
return UIHostingController(rootView: overlayView)
|
||||
// MARK: setupOverlayHostingController
|
||||
private func setupOverlayHostingController(viewModel: VideoPlayerViewModel) {
|
||||
|
||||
if let currentOverlayHostingController = currentOverlayHostingController {
|
||||
currentOverlayHostingController.view.isHidden = true
|
||||
|
||||
currentOverlayHostingController.view.removeFromSuperview()
|
||||
currentOverlayHostingController.removeFromParent()
|
||||
self.currentOverlayHostingController = nil
|
||||
}
|
||||
|
||||
let newOverlayView = VLCPlayerCompactOverlayView(viewModel: viewModel)
|
||||
let newOverlayHostingController = UIHostingController(rootView: newOverlayView)
|
||||
|
||||
newOverlayHostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
newOverlayHostingController.view.backgroundColor = UIColor.clear
|
||||
addChild(newOverlayHostingController)
|
||||
view.addSubview(newOverlayHostingController.view)
|
||||
newOverlayHostingController.didMove(toParent: self)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
newOverlayHostingController.view.topAnchor.constraint(equalTo: videoContentView.topAnchor),
|
||||
newOverlayHostingController.view.bottomAnchor.constraint(equalTo: videoContentView.bottomAnchor),
|
||||
newOverlayHostingController.view.leftAnchor.constraint(equalTo: videoContentView.leftAnchor),
|
||||
newOverlayHostingController.view.rightAnchor.constraint(equalTo: videoContentView.rightAnchor)
|
||||
])
|
||||
|
||||
self.currentOverlayHostingController = newOverlayHostingController
|
||||
|
||||
// There is a behavior when setting this that the navigation bar
|
||||
// on the current navigation controller pops up, re-hide it
|
||||
self.navigationController?.isNavigationBarHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: setupMediaPlayer
|
||||
extension VLCPlayerViewController {
|
||||
|
||||
func setupMediaPlayer() {
|
||||
/// Main function that handles setting up the media player with the current VideoPlayerViewModel
|
||||
/// and also takes the role of setting the 'viewModel' property with the given viewModel
|
||||
///
|
||||
/// Use case for this is setting new media within the same VLCPlayerViewController
|
||||
func setupMediaPlayer(newViewModel: VideoPlayerViewModel) {
|
||||
|
||||
vlcMediaPlayer.delegate = self
|
||||
vlcMediaPlayer.drawable = videoContentView
|
||||
vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
|
||||
stopOverlayDismissTimer()
|
||||
|
||||
let media = VLCMedia(url: viewModel.streamURL)
|
||||
// UX improvement
|
||||
(vlcMediaPlayer.drawable as! UIView).isHidden = true
|
||||
|
||||
// Stop current media if there is one
|
||||
if vlcMediaPlayer.media != nil {
|
||||
cancellables.forEach({ $0.cancel() })
|
||||
|
||||
vlcMediaPlayer.stop()
|
||||
viewModel.sendStopReport()
|
||||
viewModel.playerOverlayDelegate = nil
|
||||
vlcMediaPlayer.media = nil
|
||||
}
|
||||
|
||||
lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
|
||||
lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0
|
||||
|
||||
let media = VLCMedia(url: newViewModel.streamURL)
|
||||
media.addOption("--prefetch-buffer-size=1048576")
|
||||
media.addOption("--network-caching=5000")
|
||||
|
||||
vlcMediaPlayer.media = media
|
||||
|
||||
setupOverlayHostingController(viewModel: newViewModel)
|
||||
setupViewModelListeners(viewModel: newViewModel)
|
||||
|
||||
newViewModel.getAdjacentEpisodes()
|
||||
newViewModel.playerOverlayDelegate = self
|
||||
|
||||
viewModel = newViewModel
|
||||
}
|
||||
|
||||
func startPlayback() {
|
||||
// UX improvement
|
||||
(vlcMediaPlayer.drawable as! UIView).isHidden = false
|
||||
|
||||
vlcMediaPlayer.play()
|
||||
|
||||
viewModel.sendPlayReport()
|
||||
|
||||
restartOverlayDismissTimer()
|
||||
|
||||
// 1 second = 10,000,000 ticks
|
||||
let startTicks: Int64 = viewModel.item.userData?.playbackPositionTicks ?? 0
|
||||
|
||||
|
@ -261,28 +284,62 @@ extension VLCPlayerViewController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: setupViewModelListeners
|
||||
|
||||
private func setupViewModelListeners(viewModel: VideoPlayerViewModel) {
|
||||
viewModel.$playbackSpeed.sink { newSpeed in
|
||||
self.vlcMediaPlayer.rate = Float(newSpeed.rawValue)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$screenFilled.sink { shouldFill in
|
||||
self.changeFill(to: shouldFill)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in
|
||||
if sliderIsScrubbing {
|
||||
self.didBeginScrubbing()
|
||||
} else {
|
||||
self.didEndScrubbing(position: self.viewModel.sliderPercentage)
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in
|
||||
self.didSelectAudioStream(index: newAudioStreamIndex)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in
|
||||
self.didSelectSubtitleStream(index: newSubtitleStreamIndex)
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Show/Hide Overlay
|
||||
extension VLCPlayerViewController {
|
||||
|
||||
private func showOverlay() {
|
||||
guard let overlayHostingController = currentOverlayHostingController else { return }
|
||||
|
||||
guard overlayHostingController.view.alpha != 1 else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.overlayHostingController.view.alpha = 1
|
||||
overlayHostingController.view.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
private func hideOverlay() {
|
||||
guard let overlayHostingController = currentOverlayHostingController else { return }
|
||||
|
||||
guard overlayHostingController.view.alpha != 0 else { return }
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.overlayHostingController.view.alpha = 0
|
||||
overlayHostingController.view.alpha = 0
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleOverlay() {
|
||||
guard let overlayHostingController = currentOverlayHostingController else { return }
|
||||
|
||||
if overlayHostingController.view.alpha < 1 {
|
||||
showOverlay()
|
||||
} else {
|
||||
|
@ -464,4 +521,14 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
|
|||
|
||||
self.lastProgressReportTicks = currentPlayerTicks
|
||||
}
|
||||
|
||||
func didSelectPreviousItem() {
|
||||
setupMediaPlayer(newViewModel: viewModel.previousItemVideoPlayerViewModel!)
|
||||
startPlayback()
|
||||
}
|
||||
|
||||
func didSelectNextItem() {
|
||||
setupMediaPlayer(newViewModel: viewModel.nextItemVideoPlayerViewModel!)
|
||||
startPlayback()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,8 @@ extension BaseItemDto {
|
|||
subtitlesEnabled: defaultAudioStream?.index != nil,
|
||||
sliderPercentage: (self.userData?.playedPercentage ?? 0) / 100,
|
||||
selectedAudioStreamIndex: defaultAudioStream?.index ?? -1,
|
||||
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1)
|
||||
selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1,
|
||||
showAdjacentItems: true)
|
||||
|
||||
return videoPlayerViewModel
|
||||
})
|
||||
|
|
|
@ -37,6 +37,9 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
@Published var sliderIsScrubbing: Bool = false
|
||||
@Published var selectedAudioStreamIndex: Int
|
||||
@Published var selectedSubtitleStreamIndex: Int
|
||||
@Published var showAdjacentItems: Bool
|
||||
@Published var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
@Published var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||
|
||||
let item: BaseItemDto
|
||||
let title: String
|
||||
|
@ -67,6 +70,8 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
||||
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()
|
||||
|
||||
// MARK: init
|
||||
|
||||
init(item: BaseItemDto,
|
||||
title: String,
|
||||
subtitle: String?,
|
||||
|
@ -83,7 +88,8 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
subtitlesEnabled: Bool,
|
||||
sliderPercentage: Double,
|
||||
selectedAudioStreamIndex: Int,
|
||||
selectedSubtitleStreamIndex: Int) {
|
||||
selectedSubtitleStreamIndex: Int,
|
||||
showAdjacentItems: Bool) {
|
||||
self.item = item
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
|
@ -101,6 +107,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
self.sliderPercentage = sliderPercentage
|
||||
self.selectedAudioStreamIndex = selectedAudioStreamIndex
|
||||
self.selectedSubtitleStreamIndex = selectedSubtitleStreamIndex
|
||||
self.showAdjacentItems = showAdjacentItems
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -132,7 +139,87 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
|
||||
return timeText
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Adjacent Items
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
func getAdjacentEpisodes() {
|
||||
guard let seriesID = item.seriesId, item.itemType == .episode else { return }
|
||||
|
||||
TvShowsAPI.getEpisodes(seriesId: seriesID,
|
||||
userId: SessionManager.main.currentLogin.user.id,
|
||||
adjacentTo: item.id,
|
||||
limit: 3)
|
||||
.sink(receiveCompletion: { completion in
|
||||
print(completion)
|
||||
}, receiveValue: { response in
|
||||
|
||||
// 4 possible states:
|
||||
// 1 - only current episode
|
||||
// 2 - two episodes with next episode
|
||||
// 3 - two episodes with previous episode
|
||||
// 4 - three episodes with current in middle
|
||||
|
||||
// State 1
|
||||
guard let items = response.items, items.count > 1 else { return }
|
||||
|
||||
if items.count == 2 {
|
||||
if items[0].id == self.item.id {
|
||||
// State 2
|
||||
let nextItem = items[1]
|
||||
|
||||
nextItem.createVideoPlayerViewModel()
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { videoPlayerViewModel in
|
||||
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
||||
}
|
||||
.store(in: &self.cancellables)
|
||||
} else {
|
||||
// State 3
|
||||
let previousItem = items[0]
|
||||
|
||||
previousItem.createVideoPlayerViewModel()
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { videoPlayerViewModel in
|
||||
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
||||
}
|
||||
.store(in: &self.cancellables)
|
||||
}
|
||||
} else {
|
||||
// State 4
|
||||
|
||||
let previousItem = items[0]
|
||||
let nextItem = items[2]
|
||||
|
||||
previousItem.createVideoPlayerViewModel()
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { videoPlayerViewModel in
|
||||
self.previousItemVideoPlayerViewModel = videoPlayerViewModel
|
||||
}
|
||||
.store(in: &self.cancellables)
|
||||
|
||||
nextItem.createVideoPlayerViewModel()
|
||||
.sink { completion in
|
||||
self.handleAPIRequestError(completion: completion)
|
||||
} receiveValue: { videoPlayerViewModel in
|
||||
self.nextItemVideoPlayerViewModel = videoPlayerViewModel
|
||||
}
|
||||
.store(in: &self.cancellables)
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Reports
|
||||
extension VideoPlayerViewModel {
|
||||
|
||||
|
||||
// MARK: sendPlayReport
|
||||
func sendPlayReport() {
|
||||
|
||||
self.startTimeTicks = Int64(Date().timeIntervalSince1970) * 10_000_000
|
||||
|
@ -168,6 +255,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: sendPauseReport
|
||||
func sendPauseReport(paused: Bool) {
|
||||
let startInfo = PlaybackStartInfo(canSeek: true,
|
||||
item: item,
|
||||
|
@ -200,6 +288,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: sendProgressReport
|
||||
func sendProgressReport() {
|
||||
|
||||
let progressInfo = PlaybackProgressInfo(canSeek: true,
|
||||
|
@ -232,6 +321,7 @@ final class VideoPlayerViewModel: ViewModel {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: sendStopReport
|
||||
func sendStopReport() {
|
||||
|
||||
let stopInfo = PlaybackStopInfo(item: item,
|
||||
|
|
Loading…
Reference in New Issue