HaishinKit.swift/Sources/Util/DisplayLinkedQueue.swift

125 lines
4.1 KiB
Swift

import Foundation
import AVFoundation
#if os(macOS)
import CoreVideo
final class DisplayLink: NSObject {
var frameInterval: Int = 0
private(set) var timestamp: CFTimeInterval = 0
private var displayLink: CVDisplayLink?
private weak var delegate: NSObject?
private var selector: Selector?
private var status: CVReturn = 0
private var callback: CVDisplayLinkOutputCallback = { (displayLink: CVDisplayLink, inNow: UnsafePointer<CVTimeStamp>, inOutputTIme: UnsafePointer<CVTimeStamp>, flagsIn: CVOptionFlags, flgasOut: UnsafeMutablePointer<CVOptionFlags>, displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in
let displayLink: DisplayLink = Unmanaged<DisplayLink>.fromOpaque(displayLinkContext!).takeUnretainedValue()
displayLink.timestamp = Double(inNow.pointee.videoTime) / Double(inNow.pointee.videoTimeScale)
_ = displayLink.delegate?.perform(displayLink.selector, with: displayLink)
return 0
}
init(target: NSObject, selector sel: Selector) {
super.init()
status = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
guard let displayLink: CVDisplayLink = displayLink else {
return
}
self.delegate = target
self.selector = sel
CVDisplayLinkSetOutputCallback(displayLink, callback, Unmanaged.passUnretained(self).toOpaque())
}
func add(to runloop: RunLoop, forMode mode: RunLoopMode) {
guard let displayLink: CVDisplayLink = displayLink else {
return
}
status = CVDisplayLinkStart(displayLink)
}
func invalidate() {
guard let displayLink: CVDisplayLink = displayLink else {
return
}
status = CVDisplayLinkStop(displayLink)
}
}
#else
typealias DisplayLink = CADisplayLink
#endif
protocol DisplayLinkedQueueDelegate: class {
func queue(_ buffer: CMSampleBuffer)
}
final class DisplayLinkedQueue: NSObject {
var running: Bool = false
var bufferTime: TimeInterval = 0.1 // sec
weak var delegate: DisplayLinkedQueueDelegate?
private(set) var duration: TimeInterval = 0
private var isReady: Bool = false
private var buffers: [CMSampleBuffer] = []
private var mediaTime: CFTimeInterval = 0
private var displayLink: DisplayLink? {
didSet {
oldValue?.invalidate()
guard let displayLink: DisplayLink = displayLink else {
return
}
displayLink.frameInterval = 1
displayLink.add(to: .main, forMode: .commonModes)
}
}
private let lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.DisplayLinkedQueue.lock")
func enqueue(_ buffer: CMSampleBuffer) {
lockQueue.async {
self.duration += buffer.duration.seconds
self.buffers.append(buffer)
if !self.isReady {
self.isReady = self.duration <= self.bufferTime
}
}
}
@objc func update(displayLink: DisplayLink) {
guard let first: CMSampleBuffer = buffers.first, isReady else {
return
}
if mediaTime == 0 {
mediaTime = displayLink.timestamp
}
if first.presentationTimeStamp.seconds <= displayLink.timestamp - mediaTime {
lockQueue.async {
self.buffers.removeFirst()
}
delegate?.queue(first)
}
}
}
extension DisplayLinkedQueue: Running {
// MARK: Running
func startRunning() {
lockQueue.async {
guard !self.running else {
return
}
self.displayLink = DisplayLink(target: self, selector: #selector(self.update(displayLink:)))
self.running = false
}
}
func stopRunning() {
lockQueue.async {
guard self.running else {
return
}
self.displayLink = nil
self.buffers.removeAll()
self.running = false
}
}
}