forked from zelo72/mastodon-ios
141 lines
4.7 KiB
Swift
141 lines
4.7 KiB
Swift
//
|
|
// MediaPreviewVideoViewModel.swift
|
|
// Mastodon
|
|
//
|
|
// Created by MainasuK on 2022-2-9.
|
|
//
|
|
|
|
import os.log
|
|
import UIKit
|
|
import AVKit
|
|
import Combine
|
|
import AlamofireImage
|
|
|
|
final class MediaPreviewVideoViewModel {
|
|
|
|
let logger = Logger(subsystem: "MediaPreviewVideoViewModel", category: "ViewModel")
|
|
|
|
var disposeBag = Set<AnyCancellable>()
|
|
|
|
// input
|
|
let context: AppContext
|
|
let item: Item
|
|
|
|
// output
|
|
public private(set) var player: AVPlayer?
|
|
private var playerLooper: AVPlayerLooper?
|
|
@Published var playbackState = PlaybackState.unknown
|
|
|
|
init(context: AppContext, item: Item) {
|
|
self.context = context
|
|
self.item = item
|
|
// end init
|
|
|
|
switch item {
|
|
case .video(let mediaContext):
|
|
guard let assertURL = mediaContext.assetURL else { return }
|
|
let playerItem = AVPlayerItem(url: assertURL)
|
|
let _player = AVPlayer(playerItem: playerItem)
|
|
self.player = _player
|
|
|
|
case .gif(let mediaContext):
|
|
guard let assertURL = mediaContext.assetURL else { return }
|
|
let playerItem = AVPlayerItem(url: assertURL)
|
|
let _player = AVQueuePlayer(playerItem: playerItem)
|
|
_player.isMuted = true
|
|
self.player = _player
|
|
if let templateItem = _player.items().first {
|
|
let _playerLooper = AVPlayerLooper(player: _player, templateItem: templateItem)
|
|
self.playerLooper = _playerLooper
|
|
}
|
|
}
|
|
|
|
guard let player = player else {
|
|
assertionFailure()
|
|
return
|
|
}
|
|
|
|
// setup player state observer
|
|
$playbackState
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] status in
|
|
guard let self = self else { return }
|
|
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): player state: \(status.description)")
|
|
|
|
switch status {
|
|
case .unknown, .buffering, .readyToPlay:
|
|
break
|
|
case .playing:
|
|
try? AVAudioSession.sharedInstance().setCategory(.playback)
|
|
try? AVAudioSession.sharedInstance().setActive(true)
|
|
case .paused, .stopped, .failed:
|
|
try? AVAudioSession.sharedInstance().setCategory(.ambient) // set to ambient to allow mixed (needed for GIFV)
|
|
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
}
|
|
}
|
|
.store(in: &disposeBag)
|
|
|
|
player.publisher(for: \.status, options: [.initial, .new])
|
|
.sink(receiveValue: { [weak self] status in
|
|
guard let self = self else { return }
|
|
switch status {
|
|
case .failed:
|
|
self.playbackState = .failed
|
|
case .readyToPlay:
|
|
self.playbackState = .readyToPlay
|
|
case .unknown:
|
|
self.playbackState = .unknown
|
|
@unknown default:
|
|
assertionFailure()
|
|
}
|
|
})
|
|
.store(in: &disposeBag)
|
|
NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: nil)
|
|
.sink { [weak self] notification in
|
|
guard let self = self else { return }
|
|
guard let playerItem = notification.object as? AVPlayerItem,
|
|
let urlAsset = playerItem.asset as? AVURLAsset
|
|
else { return }
|
|
print(urlAsset.url)
|
|
guard urlAsset.url == item.assetURL else { return }
|
|
self.playbackState = .stopped
|
|
}
|
|
.store(in: &disposeBag)
|
|
}
|
|
|
|
}
|
|
|
|
extension MediaPreviewVideoViewModel {
|
|
|
|
enum Item {
|
|
case video(RemoteVideoContext)
|
|
case gif(RemoteGIFContext)
|
|
|
|
var previewURL: URL? {
|
|
switch self {
|
|
case .video(let mediaContext): return mediaContext.previewURL
|
|
case .gif(let mediaContext): return mediaContext.previewURL
|
|
}
|
|
}
|
|
|
|
var assetURL: URL? {
|
|
switch self {
|
|
case .video(let mediaContext): return mediaContext.assetURL
|
|
case .gif(let mediaContext): return mediaContext.assetURL
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RemoteVideoContext {
|
|
let assetURL: URL?
|
|
let previewURL: URL?
|
|
// let thumbnail: UIImage?
|
|
}
|
|
|
|
struct RemoteGIFContext {
|
|
let assetURL: URL?
|
|
let previewURL: URL?
|
|
}
|
|
|
|
}
|