import CoreMedia import Foundation final actor MediaLink { static let capacity = 90 static let duration: TimeInterval = 0.0 var dequeue: AsyncStream { AsyncStream { continutation in self.continutation = continutation } } private(set) var isRunning = false private var storage: TypedBlockQueue? private var continutation: AsyncStream.Continuation? { didSet { oldValue?.finish() } } private var duration: TimeInterval = MediaLink.duration private var presentationTimeStampOrigin: CMTime = .invalid private lazy var displayLink = DisplayLinkChoreographer() private weak var audioPlayer: AudioPlayerNode? init() { do { storage = try .init(capacity: Self.capacity, handlers: .outputPTSSortedSampleBuffers) } catch { logger.error(error) } } func enqueue(_ sampleBuffer: CMSampleBuffer) { guard isRunning else { return } if presentationTimeStampOrigin == .invalid { presentationTimeStampOrigin = sampleBuffer.presentationTimeStamp } do { try storage?.enqueue(sampleBuffer) } catch { logger.error(error) } } func setAudioPlayer(_ audioPlayer: AudioPlayerNode?) { self.audioPlayer = audioPlayer } private func getCurrentTime(_ timestamp: TimeInterval) async -> TimeInterval { defer { duration += timestamp } return await audioPlayer?.currentTime ?? duration } } extension MediaLink: AsyncRunner { // MARK: AsyncRunner func startRunning() { guard !isRunning else { return } isRunning = true duration = 0.0 displayLink.startRunning() Task { for await currentTime in displayLink.updateFrames { guard let storage else { continue } let currentTime = await getCurrentTime(currentTime.targetTimestamp - currentTime.timestamp) var frameCount = 0 while !storage.isEmpty { guard let first = storage.head else { break } if first.presentationTimeStamp.seconds - presentationTimeStampOrigin.seconds <= currentTime { continutation?.yield(first) frameCount += 1 _ = storage.dequeue() } else { if 2 < frameCount { logger.info("droppedFrame: \(frameCount)") } break } } } } } func stopRunning() { guard isRunning else { return } continutation = nil displayLink.stopRunning() presentationTimeStampOrigin = .invalid try? storage?.reset() isRunning = false } }