You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
60 lines
2.0 KiB
60 lines
2.0 KiB
import CNDI
|
|
import Foundation
|
|
import MusubiCore
|
|
|
|
/// Discovers NDI sources on a dedicated thread and publishes the current list.
|
|
public final class NDIFinder: @unchecked Sendable {
|
|
// Only touched on `thread`, except for creation and `stop()`.
|
|
private let instance: NDIlib_find_instance_t
|
|
private let lock = NSLock()
|
|
private var running = true
|
|
private var thread: Thread?
|
|
|
|
public let sources: AsyncStream<[Source]>
|
|
private let continuation: AsyncStream<[Source]>.Continuation
|
|
|
|
public init?(showLocalSources: Bool = true) {
|
|
var settings = NDIlib_find_create_t(show_local_sources: showLocalSources, p_groups: nil, p_extra_ips: nil)
|
|
guard let instance = NDIlib_find_create_v2(&settings) else { return nil }
|
|
self.instance = instance
|
|
(sources, continuation) = AsyncStream.makeStream(bufferingPolicy: .bufferingNewest(1))
|
|
|
|
let thread = Thread { [weak self] in self?.run() }
|
|
thread.name = "studio.zielke.musubi.ndi-finder"
|
|
thread.qualityOfService = .utility
|
|
self.thread = thread
|
|
thread.start()
|
|
}
|
|
|
|
deinit {
|
|
stop()
|
|
NDIlib_find_destroy(instance)
|
|
}
|
|
|
|
public func stop() {
|
|
lock.withLock { running = false }
|
|
continuation.finish()
|
|
}
|
|
|
|
private var isRunning: Bool { lock.withLock { running } }
|
|
|
|
private func run() {
|
|
continuation.yield(currentSources())
|
|
while isRunning {
|
|
// Returns early when the list changed, otherwise after the timeout.
|
|
if NDIlib_find_wait_for_sources(instance, 1000) {
|
|
continuation.yield(currentSources())
|
|
}
|
|
}
|
|
}
|
|
|
|
private func currentSources() -> [Source] {
|
|
var count: UInt32 = 0
|
|
guard let list = NDIlib_find_get_current_sources(instance, &count) else { return [] }
|
|
return (0..<Int(count)).compactMap { index in
|
|
guard let name = list[index].p_ndi_name else { return nil }
|
|
return Source(ndiName: String(cString: name))
|
|
}
|
|
}
|
|
}
|