mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 18:54:11 +00:00
Improve history, resume videos, mark watched videos (fixes #42)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user