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}