mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
105 lines
3.0 KiB
Swift
105 lines
3.0 KiB
Swift
import CoreMedia
|
|
import Foundation
|
|
|
|
final actor MediaLink {
|
|
static let capacity = 90
|
|
static let duration: TimeInterval = 0.0
|
|
|
|
var dequeue: AsyncStream<CMSampleBuffer> {
|
|
AsyncStream { continutation in
|
|
self.continutation = continutation
|
|
}
|
|
}
|
|
private(set) var isRunning = false
|
|
private var storage: TypedBlockQueue<CMSampleBuffer>?
|
|
private var continutation: AsyncStream<CMSampleBuffer>.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
|
|
}
|
|
}
|