Right progress ticks for VLCPlayer

This commit is contained in:
Ethan Pippin 2021-12-28 16:45:45 -07:00
parent 6712ef279d
commit 99445e387c
3 changed files with 88 additions and 33 deletions

View File

@ -14,9 +14,9 @@ class NativePlayerViewController: AVPlayerViewController {
let viewModel: VideoPlayerViewModel let viewModel: VideoPlayerViewModel
var timeObserverToken: Any? private var timeObserverToken: Any?
var lastProgressTicks: Int64 = 0 private var lastProgressTicks: Int64 = 0
init(viewModel: VideoPlayerViewModel) { init(viewModel: VideoPlayerViewModel) {
@ -99,15 +99,14 @@ class NativePlayerViewController: AVPlayerViewController {
private func play() { private func play() {
player?.play() player?.play()
viewModel.sendPlayReport()
viewModel.sendPlayReport(startTimeTicks: viewModel.item.userData?.playbackPositionTicks ?? 0)
} }
private func sendProgressReport(seconds: Double) { private func sendProgressReport(seconds: Double) {
viewModel.sendProgressReport(ticks: Int64(seconds) * 10_000_000) viewModel.sendProgressReport()
} }
private func stop() { private func stop() {
viewModel.sendStopReport(ticks: 10_000_000) viewModel.sendStopReport()
} }
} }

View File

@ -22,6 +22,7 @@ class VLCPlayerViewController: UIViewController {
private let viewModel: VideoPlayerViewModel private let viewModel: VideoPlayerViewModel
private var vlcMediaPlayer = VLCMediaPlayer() private var vlcMediaPlayer = VLCMediaPlayer()
private var lastPlayerTicks: Int64 private var lastPlayerTicks: Int64
private var lastProgressReportTicks: Int64
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
private var overlayDismissTimer: Timer? private var overlayDismissTimer: Timer?
@ -52,6 +53,7 @@ class VLCPlayerViewController: UIViewController {
self.viewModel = viewModel self.viewModel = viewModel
self.lastPlayerTicks = viewModel.item.userData?.playbackPositionTicks ?? 0 self.lastPlayerTicks = viewModel.item.userData?.playbackPositionTicks ?? 0
self.lastProgressReportTicks = viewModel.item.userData?.playbackPositionTicks ?? 0
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
@ -243,7 +245,7 @@ extension VLCPlayerViewController {
func startPlayback() { func startPlayback() {
vlcMediaPlayer.play() vlcMediaPlayer.play()
viewModel.sendPlayReport(startTimeTicks: viewModel.item.userData?.playbackPositionTicks ?? 0) viewModel.sendPlayReport()
// 1 second = 10,000,000 ticks // 1 second = 10,000,000 ticks
let startTicks: Int64 = viewModel.item.userData?.playbackPositionTicks ?? 0 let startTicks: Int64 = viewModel.item.userData?.playbackPositionTicks ?? 0
@ -327,18 +329,18 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate {
viewModel.sliderPercentage = Double(vlcMediaPlayer.position) viewModel.sliderPercentage = Double(vlcMediaPlayer.position)
if abs(currentPlayerTicks - lastPlayerTicks) >= 10000 { if abs(currentPlayerTicks - lastPlayerTicks) >= 10_000 {
viewModel.playerState = VLCMediaPlayerState.playing viewModel.playerState = VLCMediaPlayerState.playing
} }
lastPlayerTicks = currentPlayerTicks lastPlayerTicks = currentPlayerTicks
// if CACurrentMediaTime() - lastProgressReportTime > 5 { if abs(lastProgressReportTicks - currentPlayerTicks) >= 500_000_000 {
// mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack viewModel.sendProgressReport()
// sendProgressReport(eventName: "timeupdate")
// lastProgressReportTime = CACurrentMediaTime() lastProgressReportTicks = currentPlayerTicks
// } }
} }
} }
@ -361,7 +363,7 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
func didSelectClose() { func didSelectClose() {
vlcMediaPlayer.stop() vlcMediaPlayer.stop()
viewModel.sendStopReport(ticks: currentPlayerTicks) viewModel.sendStopReport()
dismiss(animated: true, completion: nil) dismiss(animated: true, completion: nil)
} }
@ -399,12 +401,20 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
vlcMediaPlayer.jumpBackward(jumpBackwardLength.rawValue) vlcMediaPlayer.jumpBackward(jumpBackwardLength.rawValue)
restartOverlayDismissTimer() restartOverlayDismissTimer()
viewModel.sendProgressReport()
self.lastProgressReportTicks = currentPlayerTicks
} }
func didSelectForward() { func didSelectForward() {
vlcMediaPlayer.jumpForward(jumpForwardLength.rawValue) vlcMediaPlayer.jumpForward(jumpForwardLength.rawValue)
restartOverlayDismissTimer() restartOverlayDismissTimer()
viewModel.sendProgressReport()
self.lastProgressReportTicks = currentPlayerTicks
} }
func didSelectMain() { func didSelectMain() {
@ -414,9 +424,11 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
vlcMediaPlayer.play() vlcMediaPlayer.play()
restartOverlayDismissTimer() restartOverlayDismissTimer()
case .playing: case .playing:
viewModel.sendPauseReport(paused: true)
vlcMediaPlayer.pause() vlcMediaPlayer.pause()
restartOverlayDismissTimer(interval: 5) restartOverlayDismissTimer(interval: 5)
case .paused: case .paused:
viewModel.sendPauseReport(paused: false)
vlcMediaPlayer.play() vlcMediaPlayer.play()
default: () default: ()
} }
@ -433,6 +445,8 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
} }
func didEndScrubbing(position: Double) { func didEndScrubbing(position: Double) {
// Necessary math as VLCMediaPlayer doesn't work well
// by just setting the position
let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000) let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000)
let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000) let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000)
let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration) let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration)
@ -445,5 +459,9 @@ extension VLCPlayerViewController: PlayerOverlayDelegate {
} }
restartOverlayDismissTimer() restartOverlayDismissTimer()
viewModel.sendProgressReport()
self.lastProgressReportTicks = currentPlayerTicks
} }
} }

View File

@ -8,15 +8,15 @@
import Combine import Combine
import Foundation import Foundation
import JellyfinAPI import JellyfinAPI
import UIKit
#if os(tvOS) #if os(tvOS)
import TVVLCKit import TVVLCKit
#else #else
import MobileVLCKit import MobileVLCKit
#endif #endif
import Stinsen
import UIKit
final class VideoPlayerViewModel: ObservableObject { final class VideoPlayerViewModel: ViewModel {
// Manually kept state because VLCKit doesn't properly set "played" // Manually kept state because VLCKit doesn't properly set "played"
// on the VLCMediaPlayer object // on the VLCMediaPlayer object
@ -55,11 +55,18 @@ final class VideoPlayerViewModel: ObservableObject {
// Ticks of the time the media has begun // Ticks of the time the media has begun
var startTimeTicks: Int64? var startTimeTicks: Int64?
var currentSeconds: Double {
let videoDuration = Double(item.runTimeTicks! / 10_000_000)
return round(sliderPercentage * videoDuration)
}
var currentSecondTicks: Int64 {
return Int64(currentSeconds) * 10_000_000
}
// Necessary PassthroughSubject to capture manual scrubbing from sliders // Necessary PassthroughSubject to capture manual scrubbing from sliders
let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>() let sliderScrubbingSubject = PassthroughSubject<VideoPlayerViewModel, Never>()
private var cancellables = Set<AnyCancellable>()
init(item: BaseItemDto, init(item: BaseItemDto,
title: String, title: String,
subtitle: String?, subtitle: String?,
@ -95,15 +102,16 @@ final class VideoPlayerViewModel: ObservableObject {
self.selectedAudioStreamIndex = selectedAudioStreamIndex self.selectedAudioStreamIndex = selectedAudioStreamIndex
self.selectedSubtitleStreamIndex = selectedSubtitleStreamIndex self.selectedSubtitleStreamIndex = selectedSubtitleStreamIndex
super.init()
self.sliderPercentageChanged(newValue: (item.userData?.playedPercentage ?? 0) / 100) self.sliderPercentageChanged(newValue: (item.userData?.playedPercentage ?? 0) / 100)
} }
private func sliderPercentageChanged(newValue: Double) { private func sliderPercentageChanged(newValue: Double) {
let videoDuration = Double(item.runTimeTicks! / 10_000_000) let videoDuration = Double(item.runTimeTicks! / 10_000_000)
let secondsScrubbedTo = round(sliderPercentage * videoDuration) let secondsScrubbedRemaining = videoDuration - currentSeconds
let secondsScrubbedRemaining = videoDuration - secondsScrubbedTo
leftLabelText = calculateTimeText(from: secondsScrubbedTo) leftLabelText = calculateTimeText(from: currentSeconds)
rightLabelText = calculateTimeText(from: secondsScrubbedRemaining) rightLabelText = calculateTimeText(from: secondsScrubbedRemaining)
} }
@ -125,9 +133,9 @@ final class VideoPlayerViewModel: ObservableObject {
return timeText return timeText
} }
func sendPlayReport(startTimeTicks: Int64) { func sendPlayReport() {
self.startTimeTicks = startTimeTicks self.startTimeTicks = Int64(Date().timeIntervalSince1970) * 10_000_000
let startInfo = PlaybackStartInfo(canSeek: true, let startInfo = PlaybackStartInfo(canSeek: true,
item: item, item: item,
@ -153,16 +161,46 @@ final class VideoPlayerViewModel: ObservableObject {
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo) PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
.sink { completion in .sink { completion in
print(completion) self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in } receiveValue: { _ in
print("Playback start report sent!") print("Playback start report sent!")
} }
.store(in: &cancellables) .store(in: &cancellables)
} }
func sendProgressReport(ticks: Int64) { func sendPauseReport(paused: Bool) {
let startInfo = PlaybackStartInfo(canSeek: true,
item: item,
itemId: item.id,
sessionId: response.playSessionId,
mediaSourceId: item.id,
audioStreamIndex: audioStreams.first(where: { $0.index! == response.mediaSources?.first?.defaultAudioStreamIndex! })?.index,
subtitleStreamIndex: subtitleStreams.first(where: { $0.index! == response.mediaSources?.first?.defaultSubtitleStreamIndex ?? -1 })?.index,
isPaused: paused,
isMuted: false,
positionTicks: currentSecondTicks,
playbackStartTimeTicks: startTimeTicks,
volumeLevel: 100,
brightness: 100,
aspectRatio: nil,
playMethod: .directPlay,
liveStreamId: nil,
playSessionId: response.playSessionId,
repeatMode: .repeatNone,
nowPlayingQueue: nil,
playlistItemId: "playlistItem0"
)
print("Progress ticks: \(ticks)") PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
.sink { completion in
self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in
print("Pause report sent!")
}
.store(in: &cancellables)
}
func sendProgressReport() {
let progressInfo = PlaybackProgressInfo(canSeek: true, let progressInfo = PlaybackProgressInfo(canSeek: true,
item: item, item: item,
@ -173,7 +211,7 @@ final class VideoPlayerViewModel: ObservableObject {
subtitleStreamIndex: subtitleStreams.first(where: { $0.index! == response.mediaSources?.first?.defaultSubtitleStreamIndex ?? -1 })?.index, subtitleStreamIndex: subtitleStreams.first(where: { $0.index! == response.mediaSources?.first?.defaultSubtitleStreamIndex ?? -1 })?.index,
isPaused: false, isPaused: false,
isMuted: false, isMuted: false,
positionTicks: ticks, positionTicks: currentSecondTicks,
playbackStartTimeTicks: startTimeTicks, playbackStartTimeTicks: startTimeTicks,
volumeLevel: nil, volumeLevel: nil,
brightness: nil, brightness: nil,
@ -187,20 +225,20 @@ final class VideoPlayerViewModel: ObservableObject {
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo) PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: progressInfo)
.sink { completion in .sink { completion in
print(completion) self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in } receiveValue: { _ in
print("Playback progress sent!") print("Playback progress sent!")
} }
.store(in: &cancellables) .store(in: &cancellables)
} }
func sendStopReport(ticks: Int64) { func sendStopReport() {
let stopInfo = PlaybackStopInfo(item: item, let stopInfo = PlaybackStopInfo(item: item,
itemId: item.id, itemId: item.id,
sessionId: response.playSessionId, sessionId: response.playSessionId,
mediaSourceId: item.id, mediaSourceId: item.id,
positionTicks: ticks, positionTicks: currentSecondTicks,
liveStreamId: nil, liveStreamId: nil,
playSessionId: response.playSessionId, playSessionId: response.playSessionId,
failed: nil, failed: nil,
@ -210,7 +248,7 @@ final class VideoPlayerViewModel: ObservableObject {
PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo) PlaystateAPI.reportPlaybackStopped(playbackStopInfo: stopInfo)
.sink { completion in .sink { completion in
print(completion) self.handleAPIRequestError(completion: completion)
} receiveValue: { _ in } receiveValue: { _ in
print("Playback stop report sent!") print("Playback stop report sent!")
} }