Improve history, resume videos, mark watched videos (fixes #42)

This commit is contained in:
Arkadiusz Fal
2021-12-26 22:14:46 +01:00
parent adcebb77a5
commit ac1c6685a1
31 changed files with 775 additions and 344 deletions

View File

@@ -1,4 +1,5 @@
import AVKit
import CoreData
import Defaults
import Foundation
import Logging
@@ -27,8 +28,8 @@ final class PlayerModel: ObservableObject {
@Published var streamSelection: Stream? { didSet { rebuildTVMenu() } }
@Published var queue = [PlayerQueueItem]() { didSet { Defaults[.queue] = queue } }
@Published var currentItem: PlayerQueueItem! { didSet { Defaults[.lastPlayed] = currentItem } }
@Published var history = [PlayerQueueItem]() { didSet { Defaults[.history] = history } }
@Published var currentItem: PlayerQueueItem!
@Published var historyVideos = [Video]()
@Published var preservedTime: CMTime?
@@ -47,6 +48,8 @@ final class PlayerModel: ObservableObject {
var composition = AVMutableComposition()
var loadedCompositionAssets = [AVMediaType]()
var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext
private var currentArtwork: MPMediaItemArtwork?
private var frequentTimeObserver: Any?
private var infrequentTimeObserver: Any?
@@ -131,7 +134,7 @@ final class PlayerModel: ObservableObject {
}
var live: Bool {
currentItem?.video?.live ?? false
currentVideo?.live ?? false
}
var playerItemDuration: CMTime? {
@@ -162,9 +165,17 @@ final class PlayerModel: ObservableObject {
player.pause()
}
func upgradeToStream(_ stream: Stream) {
if !self.stream.isNil, self.stream != stream {
playStream(stream, of: currentVideo!, preservingTime: true, upgrading: true)
func play(_ video: Video, at time: TimeInterval? = nil, inNavigationView: Bool = false) {
playNow(video, at: time)
guard !playingInPictureInPicture else {
return
}
if inNavigationView {
playerNavigationLinkActive = true
} else {
show()
}
}
@@ -205,6 +216,12 @@ final class PlayerModel: ObservableObject {
}
}
func upgradeToStream(_ stream: Stream) {
if !self.stream.isNil, self.stream != stream {
playStream(stream, of: currentVideo!, preservingTime: true, upgrading: true)
}
}
private func handleAvailableStreamsChange() {
rebuildTVMenu()
@@ -469,18 +486,20 @@ final class PlayerModel: ObservableObject {
}
@objc func itemDidPlayToEndTime() {
currentItem.playbackTime = playerItemDuration
prepareCurrentItemForHistory(finished: true)
if queue.isEmpty {
#if !os(macOS)
try? AVAudioSession.sharedInstance().setActive(false)
#endif
addCurrentItemToHistory()
resetQueue()
#if os(tvOS)
controller?.dismiss(animated: true)
controller?.playerView.dismiss(animated: false) { [weak self] in
self?.controller?.dismiss(animated: true)
}
#else
hide()
#endif
hide()
} else {
advanceToNextItem()
}
@@ -551,7 +570,7 @@ final class PlayerModel: ObservableObject {
}
self.timeObserverThrottle.execute {
self.updateCurrentItemIntervals()
self.updateWatch()
}
}
}
@@ -581,26 +600,15 @@ final class PlayerModel: ObservableObject {
#endif
self.timeObserverThrottle.execute {
self.updateCurrentItemIntervals()
self.updateWatch()
}
}
}
private func updateCurrentItemIntervals() {
currentItem?.playbackTime = player.currentTime()
currentItem?.videoDuration = player.currentItem?.asset.duration.seconds
}
fileprivate func updateNowPlayingInfo() {
var duration: Int?
if !currentItem.video.live {
let itemDuration = currentItem.videoDuration ?? 0
duration = itemDuration.isFinite ? Int(itemDuration) : nil
}
var nowPlayingInfo: [String: AnyObject] = [
MPMediaItemPropertyTitle: currentItem.video.title as AnyObject,
MPMediaItemPropertyArtist: currentItem.video.author as AnyObject,
MPMediaItemPropertyPlaybackDuration: duration as AnyObject,
MPNowPlayingInfoPropertyIsLiveStream: currentItem.video.live as AnyObject,
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds as AnyObject,
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
@@ -611,6 +619,15 @@ final class PlayerModel: ObservableObject {
nowPlayingInfo[MPMediaItemPropertyArtwork] = currentArtwork as AnyObject
}
if !currentItem.video.live {
let itemDuration = currentItem.videoDuration ?? 0
let duration = itemDuration.isFinite ? Int(itemDuration) : nil
if !duration.isNil {
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration as AnyObject
}
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
@@ -647,7 +664,7 @@ final class PlayerModel: ObservableObject {
if let channel: Channel = response.typedContent() {
self?.channelWithDetails = channel
withAnimation {
self?.currentItem.video.channel = channel
self?.currentItem?.video.channel = channel
}
}
}
@@ -671,7 +688,7 @@ final class PlayerModel: ObservableObject {
}
func closeCurrentItem() {
addCurrentItemToHistory()
prepareCurrentItemForHistory()
currentItem = nil
player.replaceCurrentItem(with: nil)
}

View File

@@ -29,11 +29,11 @@ extension PlayerModel {
}
func playNow(_ video: Video, at time: TimeInterval? = nil) {
if !playingInPictureInPicture || closePiPOnNavigation {
if playingInPictureInPicture, closePiPOnNavigation {
closePiP()
}
addCurrentItemToHistory()
prepareCurrentItemForHistory()
enqueueVideo(video, prepending: true) { _, item in
self.advanceToItem(item, at: time)
@@ -83,7 +83,7 @@ extension PlayerModel {
}
func advanceToNextItem() {
addCurrentItemToHistory()
prepareCurrentItemForHistory()
if let nextItem = queue.first {
advanceToItem(nextItem)
@@ -91,7 +91,7 @@ extension PlayerModel {
}
func advanceToItem(_ newItem: PlayerQueueItem, at time: TimeInterval? = nil) {
addCurrentItemToHistory()
prepareCurrentItemForHistory()
remove(newItem)
@@ -148,20 +148,15 @@ extension PlayerModel {
return item
}
func addCurrentItemToHistory() {
if let item = currentItem, Defaults[.saveHistory] {
addItemToHistory(item)
func prepareCurrentItemForHistory(finished: Bool = false) {
if !currentItem.isNil, Defaults[.saveHistory] {
if let video = currentVideo, !historyVideos.contains(where: { $0 == video }) {
historyVideos.append(video)
}
updateWatch(finished: finished)
}
}
func addItemToHistory(_ item: PlayerQueueItem) {
if let index = history.firstIndex(where: { $0.video?.videoID == item.video?.videoID }) {
history.remove(at: index)
}
history.insert(currentItem, at: 0)
}
func playHistory(_ item: PlayerQueueItem) {
var time = item.playbackTime
@@ -172,67 +167,9 @@ extension PlayerModel {
let newItem = enqueueVideo(item.video, atTime: time, prepending: true)
advanceToItem(newItem!)
if let historyItemIndex = history.firstIndex(of: item) {
history.remove(at: historyItemIndex)
}
}
@discardableResult func removeHistory(_ item: PlayerQueueItem) -> PlayerQueueItem? {
if let index = history.firstIndex(where: { $0 == item }) {
return history.remove(at: index)
}
return nil
}
func removeQueueItems() {
queue.removeAll()
}
func removeHistoryItems() {
history.removeAll()
}
func loadHistoryDetails() {
guard !accounts.current.isNil else {
return
}
queue = Defaults[.queue]
queue.forEach { item in
accounts.api.loadDetails(item) { newItem in
if let index = self.queue.firstIndex(where: { $0.id == item.id }) {
self.queue[index] = newItem
}
}
}
var savedHistory = Defaults[.history]
if let lastPlayed = Defaults[.lastPlayed] {
if let index = savedHistory.firstIndex(where: { $0.videoID == lastPlayed.videoID }) {
var updatedLastPlayed = savedHistory[index]
updatedLastPlayed.playbackTime = lastPlayed.playbackTime
updatedLastPlayed.videoDuration = lastPlayed.videoDuration
savedHistory.remove(at: index)
savedHistory.insert(updatedLastPlayed, at: 0)
} else {
savedHistory.insert(lastPlayed, at: 0)
}
Defaults[.lastPlayed] = nil
}
history = savedHistory
history.forEach { item in
accounts.api.loadDetails(item) { newItem in
if let index = self.history.firstIndex(where: { $0.id == item.id }) {
self.history[index] = newItem
}
}
}
}
}

View File

@@ -11,6 +11,15 @@ struct PlayerQueueItem: Hashable, Identifiable, Defaults.Serializable {
var playbackTime: CMTime?
var videoDuration: TimeInterval?
static func from(_ watch: Watch, video: Video? = nil) -> Self {
.init(
video,
videoID: watch.videoID,
playbackTime: CMTime.secondsInDefaultTimescale(watch.stoppedAt),
videoDuration: watch.videoDuration
)
}
init(_ video: Video? = nil, videoID: Video.ID? = nil, playbackTime: CMTime? = nil, videoDuration: TimeInterval? = nil) {
self.video = video
self.videoID = videoID ?? video!.videoID