Home telegram - chatMessageInteractiveInstantVideoNode
Post
Cancel

telegram - chatMessageInteractiveInstantVideoNode

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>
This post is licensed under CC BY 4.0 by the author.