LiveKitBridge.swift

  1import Foundation
  2import LiveKit
  3import WebRTC
  4import ScreenCaptureKit
  5
  6class LKRoomDelegate: RoomDelegate {
  7    var data: UnsafeRawPointer
  8    var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void
  9    var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
 10    var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
 11    
 12    init(
 13        data: UnsafeRawPointer,
 14        onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
 15        onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
 16        onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void)
 17    {
 18        self.data = data
 19        self.onDidDisconnect = onDidDisconnect
 20        self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
 21        self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack
 22    }
 23
 24    func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) {
 25        if connectionState.isDisconnected {
 26            self.onDidDisconnect(self.data)
 27        }
 28    }
 29
 30    func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
 31        if track.kind == .video {
 32            self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque())
 33        }
 34    }
 35    
 36    func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
 37        if track.kind == .video {
 38            self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString)
 39        }
 40    }
 41}
 42
 43class LKVideoRenderer: NSObject, VideoRenderer {
 44    var data: UnsafeRawPointer
 45    var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool
 46    var onDrop: @convention(c) (UnsafeRawPointer) -> Void
 47    var adaptiveStreamIsEnabled: Bool = false
 48    var adaptiveStreamSize: CGSize = .zero
 49    weak var track: VideoTrack?
 50
 51    init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) {
 52        self.data = data
 53        self.onFrame = onFrame
 54        self.onDrop = onDrop
 55    }
 56
 57    deinit {
 58        self.onDrop(self.data)
 59    }
 60
 61    func setSize(_ size: CGSize) {
 62    }
 63
 64    func renderFrame(_ frame: RTCVideoFrame?) {
 65        let buffer = frame?.buffer as? RTCCVPixelBuffer
 66        if let pixelBuffer = buffer?.pixelBuffer {
 67            if !self.onFrame(self.data, pixelBuffer) {
 68                DispatchQueue.main.async {
 69                    self.track?.remove(videoRenderer: self)
 70                }
 71            }
 72        }
 73    }
 74}
 75
 76@_cdecl("LKRoomDelegateCreate")
 77public func LKRoomDelegateCreate(
 78    data: UnsafeRawPointer,
 79    onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
 80    onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
 81    onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
 82) -> UnsafeMutableRawPointer {
 83    let delegate = LKRoomDelegate(
 84        data: data,
 85        onDidDisconnect: onDidDisconnect,
 86        onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack,
 87        onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack
 88    )
 89    return Unmanaged.passRetained(delegate).toOpaque()
 90}
 91
 92@_cdecl("LKRoomCreate")
 93public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer  {
 94    let delegate = Unmanaged<LKRoomDelegate>.fromOpaque(delegate).takeUnretainedValue()
 95    return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque()
 96}
 97
 98@_cdecl("LKRoomConnect")
 99public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
100    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
101
102    room.connect(url as String, token as String).then { _ in
103        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
104    }.catch { error in
105        callback(callback_data, error.localizedDescription as CFString)
106    }
107}
108
109@_cdecl("LKRoomDisconnect")
110public func LKRoomDisconnect(room: UnsafeRawPointer) {
111    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
112    room.disconnect()
113}
114
115@_cdecl("LKRoomPublishVideoTrack")
116public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) {
117    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
118    let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue()
119    room.localParticipant?.publishVideoTrack(track: track).then { publication in
120        callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil)
121    }.catch { error in
122        callback(callback_data, nil, error.localizedDescription as CFString)
123    }
124}
125
126@_cdecl("LKRoomUnpublishTrack")
127public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) {
128    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
129    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
130    let _ = room.localParticipant?.unpublish(publication: publication)
131}
132
133@_cdecl("LKRoomVideoTracksForRemoteParticipant")
134public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
135    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
136    
137    for (_, participant) in room.remoteParticipants {
138        if participant.identity == participantId as String {
139            return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray?
140        }
141    }
142    
143    return nil;
144}
145
146@_cdecl("LKCreateScreenShareTrackForDisplay")
147public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
148    let display = Unmanaged<MacOSDisplay>.fromOpaque(display).takeUnretainedValue()
149    let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy)
150    return Unmanaged.passRetained(track).toOpaque()
151}
152
153@_cdecl("LKVideoRendererCreate")
154public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
155    Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
156}
157
158@_cdecl("LKVideoTrackAddRenderer")
159public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) {
160    let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! VideoTrack
161    let renderer = Unmanaged<LKVideoRenderer>.fromOpaque(renderer).takeRetainedValue()
162    renderer.track = track
163    track.add(videoRenderer: renderer)
164}
165
166@_cdecl("LKRemoteVideoTrackGetSid")
167public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString {
168    let track = Unmanaged<RemoteVideoTrack>.fromOpaque(track).takeUnretainedValue()
169    return track.sid! as CFString
170}
171
172@_cdecl("LKDisplaySources")
173public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) {
174    MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in
175        callback(data, displaySources as CFArray, nil)
176    }.catch { error in
177        callback(data, nil, error.localizedDescription as CFString)
178    }
179}