ChatMessageInteractiveInstantVideoNode
class
UniversalVideoNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private let postbox: Postbox
private let audioSession: ManagedAudioSession
private let manager: UniversalVideoManager
private let content: UniversalVideoContent
private let priority: UniversalVideoPriority
public let decoration: UniversalVideoDecoration
private let autoplay: Bool
private let snapshotContentWhenGone: Bool
private var contentNode: (UniversalVideoContentNode & ASDisplayNode)?
private var contentNodeId: Int32?
private var playbackCompletedIndex: Int?
private var contentRequestIndex: (AnyHashable, Int32)?
public var playbackCompleted: (() -> Void)?
public private(set) var ownsContentNode: Bool = false
public var ownsContentNodeUpdated: ((Bool) -> Void)?
private let _status = Promise<MediaPlayerStatus?>()
public var status: Signal<MediaPlayerStatus?, NoError> {
return self._status.get()
}
private let _bufferingStatus = Promise<(IndexSet, Int)?>()
public var bufferingStatus: Signal<(IndexSet, Int)?, NoError> {
return self._bufferingStatus.get()
}
private let _ready = Promise<Void>()
public var ready: Signal<Void, NoError> {
return self._ready.get()
}
enum
UniversalVideoPriority
1
2
3
4
case secondaryOverlay = 0
case embedded = 1
case gallery = 2
case overlay = 3
protocol
UniversalVideoDecoration
1
2
3
4
5
6
7
8
var backgroundNode: ASDisplayNode? { get }
var contentContainerNode: ASDisplayNode { get }
var foregroundNode: ASDisplayNode? { get }
func setStatus(_ status: Signal<MediaPlayerStatus?, NoError>)
func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?)
func updateContentNodeSnapshot(_ snapshot: UIView?)
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
func tap()
protocol
UniversalVideoContent
1
2
3
4
5
var id: AnyHashable { get }
var dimensions: CGSize { get }
var duration: Int32 { get }
func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode
func isEqual(to other: UniversalVideoContent) -> Bool
protocol
UniversalVideoContentNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var ready: Signal<Void, NoError> { get }
var status: Signal<MediaPlayerStatus, NoError> { get }
var bufferingStatus: Signal<(IndexSet, Int)?, NoError> { get }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
func play()
func pause()
func togglePlayPause()
func setSoundEnabled(_ value: Bool)
func seek(_ timestamp: Double)
func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd)
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool)
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd)
func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool)
func setBaseRate(_ baseRate: Double)
func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int
func removePlaybackCompleted(_ index: Int)
func fetchControl(_ control: UniversalVideoNodeFetchControl)
class
NativeVideoContentNode: ASDisplayNode, UniversalVideoContentNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private let postbox: Postbox
private let fileReference: FileMediaReference
private let player: MediaPlayer
private let imageNode: TransformImageNode
private let playerNode: MediaPlayerNode
private let playbackCompletedListeners = Bag<() -> Void>()
private let placeholderColor: UIColor
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) {
self.postbox = postbox
self.fileReference = fileReference
self.placeholderColor = placeholderColor
self.imageNode = TransformImageNode()
self.player = MediaPlayer(audioSessionManager: audioSessionManager, postbox: postbox, resourceReference: fileReference.resourceReference(fileReference.media.resource), tempFilePath: tempFilePath, streamable: streamVideo, video: true, preferSoftwareDecoding: false, playAutomatically: false, enableSound: enableSound, baseRate: baseRate, fetchAutomatically: fetchAutomatically, continuePlayingWithoutSoundOnLostAudioSession: continuePlayingWithoutSoundOnLostAudioSession)
var actionAtEndImpl: (() -> Void)?
if enableSound && !loopVideo {
self.player.actionAtEnd = .action({
actionAtEndImpl?()
})
} else {
self.player.actionAtEnd = .loop({
actionAtEndImpl?()
})
}
self.playerNode = MediaPlayerNode(backgroundThread: false)
// 这里会把`playerNode` attaching to `player`
// 参考另一篇Telegram-MediaPlayer
self.player.attachPlayerNode(self.playerNode)
self.dimensions = fileReference.media.dimensions
if let dimensions = self.dimensions {
self.dimensionsPromise.set(dimensions)
}
super.init()
actionAtEndImpl = { [weak self] in
self?.performActionAtEnd()
}
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, videoReference: fileReference, imageReference: imageReference, onlyFullSize: onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: fileReference.media.isInstantVideo) |> map { [weak self] getSize, getData in
Queue.mainQueue().async {
if let strongSelf = self, strongSelf.dimensions == nil {
if let dimensions = getSize() {
strongSelf.dimensions = dimensions
strongSelf.dimensionsPromise.set(dimensions)
if let size = strongSelf.validLayout {
strongSelf.updateLayout(size: size, transition: .immediate)
}
}
}
}
return getData
})
self.addSubnode(self.imageNode)
self.addSubnode(self.playerNode)
self._status.set(combineLatest(self.dimensionsPromise.get(), self.player.status)
|> map { dimensions, status in
return MediaPlayerStatus(generationTimestamp: status.generationTimestamp, duration: status.duration, dimensions: dimensions, timestamp: status.timestamp, baseRate: status.baseRate, seekId: status.seekId, status: status.status, soundEnabled: status.soundEnabled)
})
if let size = fileReference.media.size {
self._bufferingStatus.set(postbox.mediaBox.resourceRangesStatus(fileReference.media.resource) |> map { ranges in
return (ranges, size)
})
} else {
self._bufferingStatus.set(.single(nil))
}
self.imageNode.imageUpdated = { [weak self] _ in
self?._ready.set(.single(Void()))
}
}
class
TransformImageNode
class
MediaPlayerNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var videoNode: MediaPlayerNodeDisplayNode
var videoLayer: AVSampleBufferDisplayLayer?
var state: (timebase: CMTimebase, requestFrames: Bool, rotationAngle: Double, aspect: Double)?
public init(backgroundThread: Bool = false) {
self.videoNode = MediaPlayerNodeDisplayNode()
if false && backgroundThread {
self.videoQueue = Queue()
} else {
self.videoQueue = Queue.mainQueue()
}
super.init()
self.videoNode.updateInHierarchy = { [weak self] value in
if let strongSelf = self {
if strongSelf.videoInHierarchy != value {
strongSelf.videoInHierarchy = value
if value {
strongSelf.updateState()
}
}
strongSelf.updateVideoInHierarchy?(value)
}
}
self.addSubnode(self.videoNode)
self.videoQueue.async { [weak self] in
let videoLayer = MediaPlayerNodeLayer()
videoLayer.videoGravity = .resize
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.videoLayer = videoLayer
strongSelf.updateLayout()
strongSelf.layer.addSublayer(videoLayer)
/*let testLayer = RuntimeUtils.makeLayerHostCopy(videoLayer.sublayers![0].sublayers![0])*/
//testLayer.frame = CGRect(origin: CGPoint(x: -500.0, y: -300.0), size: CGSize(width: 60.0, height: 60.0))
//strongSelf.layer.addSublayer(testLayer)
strongSelf.updateState()
}
}
}
}
class
MediaPlayerNodeDisplayNode
class
MediaTrackFrame
1
2
3
4
5
public let type: MediaTrackFrameType
public let sampleBuffer: CMSampleBuffer
public let resetDecoder: Bool
public let decoded: Bool
public let rotationAngle: Double
enum MediaTrackFrameType
1
2
case video
case audio
class MediaTrackDecodableFrame
1
2
3
4
5
public let type: MediaTrackFrameType
public let packet: FFMpegPacket
public let pts: CMTime
public let dts: CMTime
public let duration: CMTime
protocol
UniversalVideoManager
1
2
3
4
5
6
7
8
func attachUniversalVideoContent(content: UniversalVideoContent, priority: UniversalVideoPriority, create: () -> UniversalVideoContentNode & ASDisplayNode, update: @escaping (((UniversalVideoContentNode &
ASDisplayNode), Bool)?) -> Void) -> (AnyHashable, Int32)
func detachUniversalVideoContent(id: AnyHashable, index: Int32)
func withUniversalVideoContent(id: AnyHashable, _ f: ((UniversalVideoContentNode & ASDisplayNode)?) -> Void)
func addPlaybackCompleted(id: AnyHashable, _ f: @escaping () -> Void) -> Int
func removePlaybackCompleted(id: AnyHashable, index: Int)
func statusSignal(content: UniversalVideoContent) -> Signal<MediaPlayerStatus?, NoError>
func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(IndexSet, Int)?, NoError>