2022-02-09 13:35:19 +01:00
//
// M e d i a P r e v i e w V i d e o V i e w M o d e l . s w i f t
// M a s t o d o n
//
// C r e a t e d b y M a i n a s u K o n 2 0 2 2 - 2 - 9 .
//
import os . log
import UIKit
import AVKit
import Combine
import AlamofireImage
2022-10-08 07:43:06 +02:00
import MastodonCore
2022-02-09 13:35:19 +01:00
final class MediaPreviewVideoViewModel {
let logger = Logger ( subsystem : " MediaPreviewVideoViewModel " , category : " ViewModel " )
var disposeBag = Set < AnyCancellable > ( )
// i n p u t
let context : AppContext
let item : Item
// o u t p u t
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
// e n d i n i t
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
}
// s e t u p p l a y e r s t a t e o b s e r v e r
$ 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 ) // s e t t o a m b i e n t t o a l l o w m i x e d ( n e e d e d f o r G I F V )
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 ?
// l e t t h u m b n a i l : U I I m a g e ?
}
struct RemoteGIFContext {
let assetURL : URL ?
let previewURL : URL ?
}
}