HaishinKit.swift/Sources/Util/DisplayLinkedQueue.swift

111 lines
3.2 KiB
Swift

import AVFoundation
#if os(macOS)
#else
typealias DisplayLink = CADisplayLink
#endif
protocol DisplayLinkedQueueDelegate: AnyObject {
func queue(_ buffer: CMSampleBuffer)
func empty()
}
protocol DisplayLinkedQueueClockReference: AnyObject {
var duration: TimeInterval { get }
}
final class DisplayLinkedQueue: NSObject {
static let defaultPreferredFramesPerSecond = 0
var isPaused: Bool {
get { displayLink?.isPaused ?? false }
set { displayLink?.isPaused = newValue }
}
var duration: TimeInterval {
(displayLink?.timestamp ?? 0.0) - timestamp
}
weak var delegate: DisplayLinkedQueueDelegate?
weak var clockReference: DisplayLinkedQueueClockReference?
private var timestamp: TimeInterval = 0.0
private var buffer: CircularBuffer<CMSampleBuffer> = .init(256)
private var displayLink: DisplayLink? {
didSet {
oldValue?.invalidate()
guard let displayLink = displayLink else {
return
}
displayLink.isPaused = true
if #available(iOS 10.0, tvOS 10.0, *) {
displayLink.preferredFramesPerSecond = DisplayLinkedQueue.defaultPreferredFramesPerSecond
} else {
displayLink.frameInterval = 1
}
displayLink.add(to: .main, forMode: .common)
}
}
private let lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.DisplayLinkedQueue.lock")
private(set) var isRunning: Atomic<Bool> = .init(false)
func enqueue(_ buffer: CMSampleBuffer) {
guard buffer.presentationTimeStamp != .invalid else {
return
}
if self.buffer.isEmpty {
delegate?.queue(buffer)
}
_ = self.buffer.append(buffer)
}
@objc
private func update(displayLink: DisplayLink) {
if timestamp == 0.0 {
timestamp = displayLink.timestamp
}
guard let first = buffer.first else {
return
}
defer {
if buffer.isEmpty {
delegate?.empty()
}
}
let current = clockReference?.duration ?? duration
let targetTimestamp = first.presentationTimeStamp.seconds + first.duration.seconds
if targetTimestamp < current {
buffer.removeFirst()
update(displayLink: displayLink)
return
}
if first.presentationTimeStamp.seconds <= current && current <= targetTimestamp {
buffer.removeFirst()
delegate?.queue(first)
}
}
}
extension DisplayLinkedQueue: Running {
// MARK: Running
func startRunning() {
lockQueue.async {
guard !self.isRunning.value else {
return
}
self.timestamp = 0.0
self.displayLink = DisplayLink(target: self, selector: #selector(self.update(displayLink:)))
self.isRunning.mutate { $0 = true }
}
}
func stopRunning() {
lockQueue.async {
guard self.isRunning.value else {
return
}
self.displayLink = nil
self.clockReference = nil
self.buffer.removeAll()
self.isRunning.mutate { $0 = false }
}
}
}