@livekit/rtc-node 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,52 @@
1
+ export { Room, RoomEvent, ConnectError, RoomOptions, RtcConfiguration } from './room';
2
+ export { Participant, RemoteParticipant, LocalParticipant } from './participant';
3
+ export {
4
+ Track,
5
+ LocalTrack,
6
+ RemoteTrack,
7
+ VideoTrack,
8
+ LocalAudioTrack,
9
+ LocalVideoTrack,
10
+ RemoteAudioTrack,
11
+ RemoteVideoTrack,
12
+ AudioTrack,
13
+ } from './track';
14
+ export {
15
+ VideoFrame,
16
+ VideoFrameBuffer,
17
+ I420Buffer,
18
+ I422Buffer,
19
+ I444Buffer,
20
+ I420ABuffer,
21
+ NV12Buffer,
22
+ NativeBuffer,
23
+ I010Buffer,
24
+ PlanarYuvBuffer,
25
+ PlanarYuv8Buffer,
26
+ PlanarYuv16Buffer,
27
+ ArgbFrame,
28
+ BiplanarYuv8Buffer,
29
+ } from './video_frame';
30
+ export { AudioFrame } from './audio_frame';
31
+ export { AudioStream } from './audio_stream';
32
+ export { VideoStream } from './video_stream';
33
+ export { AudioSource } from './audio_source';
34
+ export { VideoSource } from './video_source';
35
+ export {
36
+ TrackPublication,
37
+ RemoteTrackPublication,
38
+ LocalTrackPublication,
39
+ } from './track_publication';
40
+ export { E2EEManager, E2EEOptions, KeyProviderOptions, KeyProvider, FrameCryptor } from './e2ee';
41
+ export {
42
+ ConnectionQuality,
43
+ IceServer,
44
+ IceTransportType,
45
+ DataPacketKind,
46
+ ContinualGatheringPolicy,
47
+ TrackPublishOptions,
48
+ ConnectionState,
49
+ } from './proto/room_pb';
50
+ export { EncryptionType, EncryptionState } from './proto/e2ee_pb';
51
+ export { StreamState, TrackKind, TrackSource } from './proto/track_pb';
52
+ export { VideoFormatType, VideoFrameBufferType, VideoRotation } from './proto/video_frame_pb';
package/src/lib.rs ADDED
@@ -0,0 +1,5 @@
1
+ #![deny(clippy::all)]
2
+
3
+ extern crate napi_derive;
4
+
5
+ pub mod nodejs;
@@ -0,0 +1,14 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ export function livekitInitialize(callback: (data: Uint8Array) => void, captureLogs: boolean): void;
7
+ export function livekitFfiRequest(data: Uint8Array): Uint8Array;
8
+ export function livekitRetrievePtr(handle: Uint8Array): bigint;
9
+ export function livekitCopyBuffer(ptr: bigint, len: number): Uint8Array;
10
+ export class FfiHandle {
11
+ constructor(handle: bigint);
12
+ dispose(): void;
13
+ get handle(): bigint;
14
+ }
package/src/native.js ADDED
@@ -0,0 +1,244 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path');
9
+
10
+ const { platform, arch } = process;
11
+
12
+ let nativeBinding = null;
13
+ let localFileExisted = false;
14
+ let loadError = null;
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim();
21
+ return readFileSync(lddPath, 'utf8').includes('musl');
22
+ } catch (e) {
23
+ return true;
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header;
27
+ return !glibcVersionRuntime;
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.android-arm64.node'));
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./rtc-node.android-arm64.node');
39
+ } else {
40
+ nativeBinding = require('@livekit/rtc-node-android-arm64');
41
+ }
42
+ } catch (e) {
43
+ loadError = e;
44
+ }
45
+ break;
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.android-arm-eabi.node'));
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./rtc-node.android-arm-eabi.node');
51
+ } else {
52
+ nativeBinding = require('@livekit/rtc-node-android-arm-eabi');
53
+ }
54
+ } catch (e) {
55
+ loadError = e;
56
+ }
57
+ break;
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`);
60
+ }
61
+ break;
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.win32-x64-msvc.node'));
66
+ try {
67
+ if (localFileExisted) {
68
+ nativeBinding = require('./rtc-node.win32-x64-msvc.node');
69
+ } else {
70
+ nativeBinding = require('@livekit/rtc-node-win32-x64-msvc');
71
+ }
72
+ } catch (e) {
73
+ loadError = e;
74
+ }
75
+ break;
76
+ case 'ia32':
77
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.win32-ia32-msvc.node'));
78
+ try {
79
+ if (localFileExisted) {
80
+ nativeBinding = require('./rtc-node.win32-ia32-msvc.node');
81
+ } else {
82
+ nativeBinding = require('@livekit/rtc-node-win32-ia32-msvc');
83
+ }
84
+ } catch (e) {
85
+ loadError = e;
86
+ }
87
+ break;
88
+ case 'arm64':
89
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.win32-arm64-msvc.node'));
90
+ try {
91
+ if (localFileExisted) {
92
+ nativeBinding = require('./rtc-node.win32-arm64-msvc.node');
93
+ } else {
94
+ nativeBinding = require('@livekit/rtc-node-win32-arm64-msvc');
95
+ }
96
+ } catch (e) {
97
+ loadError = e;
98
+ }
99
+ break;
100
+ default:
101
+ throw new Error(`Unsupported architecture on Windows: ${arch}`);
102
+ }
103
+ break;
104
+ case 'darwin':
105
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.darwin-universal.node'));
106
+ try {
107
+ if (localFileExisted) {
108
+ nativeBinding = require('./rtc-node.darwin-universal.node');
109
+ } else {
110
+ nativeBinding = require('@livekit/rtc-node-darwin-universal');
111
+ }
112
+ break;
113
+ } catch {}
114
+ switch (arch) {
115
+ case 'x64':
116
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.darwin-x64.node'));
117
+ try {
118
+ if (localFileExisted) {
119
+ nativeBinding = require('./rtc-node.darwin-x64.node');
120
+ } else {
121
+ nativeBinding = require('@livekit/rtc-node-darwin-x64');
122
+ }
123
+ } catch (e) {
124
+ loadError = e;
125
+ }
126
+ break;
127
+ case 'arm64':
128
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.darwin-arm64.node'));
129
+ try {
130
+ if (localFileExisted) {
131
+ nativeBinding = require('./rtc-node.darwin-arm64.node');
132
+ } else {
133
+ nativeBinding = require('@livekit/rtc-node-darwin-arm64');
134
+ }
135
+ } catch (e) {
136
+ loadError = e;
137
+ }
138
+ break;
139
+ default:
140
+ throw new Error(`Unsupported architecture on macOS: ${arch}`);
141
+ }
142
+ break;
143
+ case 'freebsd':
144
+ if (arch !== 'x64') {
145
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`);
146
+ }
147
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.freebsd-x64.node'));
148
+ try {
149
+ if (localFileExisted) {
150
+ nativeBinding = require('./rtc-node.freebsd-x64.node');
151
+ } else {
152
+ nativeBinding = require('@livekit/rtc-node-freebsd-x64');
153
+ }
154
+ } catch (e) {
155
+ loadError = e;
156
+ }
157
+ break;
158
+ case 'linux':
159
+ switch (arch) {
160
+ case 'x64':
161
+ if (isMusl()) {
162
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.linux-x64-musl.node'));
163
+ try {
164
+ if (localFileExisted) {
165
+ nativeBinding = require('./rtc-node.linux-x64-musl.node');
166
+ } else {
167
+ nativeBinding = require('@livekit/rtc-node-linux-x64-musl');
168
+ }
169
+ } catch (e) {
170
+ loadError = e;
171
+ }
172
+ } else {
173
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.linux-x64-gnu.node'));
174
+ try {
175
+ if (localFileExisted) {
176
+ nativeBinding = require('./rtc-node.linux-x64-gnu.node');
177
+ } else {
178
+ nativeBinding = require('@livekit/rtc-node-linux-x64-gnu');
179
+ }
180
+ } catch (e) {
181
+ loadError = e;
182
+ }
183
+ }
184
+ break;
185
+ case 'arm64':
186
+ if (isMusl()) {
187
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.linux-arm64-musl.node'));
188
+ try {
189
+ if (localFileExisted) {
190
+ nativeBinding = require('./rtc-node.linux-arm64-musl.node');
191
+ } else {
192
+ nativeBinding = require('@livekit/rtc-node-linux-arm64-musl');
193
+ }
194
+ } catch (e) {
195
+ loadError = e;
196
+ }
197
+ } else {
198
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.linux-arm64-gnu.node'));
199
+ try {
200
+ if (localFileExisted) {
201
+ nativeBinding = require('./rtc-node.linux-arm64-gnu.node');
202
+ } else {
203
+ nativeBinding = require('@livekit/rtc-node-linux-arm64-gnu');
204
+ }
205
+ } catch (e) {
206
+ loadError = e;
207
+ }
208
+ }
209
+ break;
210
+ case 'arm':
211
+ localFileExisted = existsSync(join(__dirname, 'rtc-node.linux-arm-gnueabihf.node'));
212
+ try {
213
+ if (localFileExisted) {
214
+ nativeBinding = require('./rtc-node.linux-arm-gnueabihf.node');
215
+ } else {
216
+ nativeBinding = require('@livekit/rtc-node-linux-arm-gnueabihf');
217
+ }
218
+ } catch (e) {
219
+ loadError = e;
220
+ }
221
+ break;
222
+ default:
223
+ throw new Error(`Unsupported architecture on Linux: ${arch}`);
224
+ }
225
+ break;
226
+ default:
227
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
228
+ }
229
+
230
+ if (!nativeBinding) {
231
+ if (loadError) {
232
+ throw loadError;
233
+ }
234
+ throw new Error(`Failed to load native binding`);
235
+ }
236
+
237
+ const { livekitInitialize, livekitFfiRequest, livekitRetrievePtr, livekitCopyBuffer, FfiHandle } =
238
+ nativeBinding;
239
+
240
+ module.exports.livekitInitialize = livekitInitialize;
241
+ module.exports.livekitFfiRequest = livekitFfiRequest;
242
+ module.exports.livekitRetrievePtr = livekitRetrievePtr;
243
+ module.exports.livekitCopyBuffer = livekitCopyBuffer;
244
+ module.exports.FfiHandle = FfiHandle;
package/src/nodejs.rs ADDED
@@ -0,0 +1,115 @@
1
+ use livekit_ffi::FfiHandleId;
2
+ use livekit_ffi::{proto, server, FFI_SERVER};
3
+ use napi::{
4
+ bindgen_prelude::*,
5
+ threadsafe_function::{
6
+ ErrorStrategy, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
7
+ },
8
+ JsFunction, Status,
9
+ };
10
+ use napi_derive::napi;
11
+ use prost::Message;
12
+ use std::sync::Arc;
13
+
14
+ #[napi(ts_args_type = "callback: (data: Uint8Array) => void, captureLogs: boolean")]
15
+ fn livekit_initialize(cb: JsFunction, capture_logs: bool) {
16
+ let tsfn: ThreadsafeFunction<proto::FfiEvent, ErrorStrategy::Fatal> = cb
17
+ .create_threadsafe_function(0, |ctx: ThreadSafeCallContext<proto::FfiEvent>| {
18
+ let data = ctx.value.encode_to_vec();
19
+ let buf = Uint8Array::new(data);
20
+ Ok(vec![buf])
21
+ })
22
+ .unwrap();
23
+
24
+ FFI_SERVER.setup(server::FfiConfig {
25
+ callback_fn: Arc::new(move |event| {
26
+ let status = tsfn.call(event, ThreadsafeFunctionCallMode::NonBlocking);
27
+ if status != Status::Ok {
28
+ eprintln!("error calling callback status: {}", status);
29
+ }
30
+ }),
31
+ capture_logs,
32
+ });
33
+ }
34
+
35
+ #[napi]
36
+ fn livekit_ffi_request(data: Uint8Array) -> Uint8Array {
37
+ let data = data.to_vec();
38
+ let res = match proto::FfiRequest::decode(data.as_slice()) {
39
+ Ok(res) => res,
40
+ Err(err) => {
41
+ panic!("failed to decode request: {}", err);
42
+ }
43
+ };
44
+
45
+ let res = match server::requests::handle_request(&FFI_SERVER, res) {
46
+ Ok(res) => res,
47
+ Err(err) => {
48
+ panic!("failed to handle request: {}", err);
49
+ }
50
+ }
51
+ .encode_to_vec();
52
+ Uint8Array::new(res)
53
+ }
54
+
55
+ // FfiHandle must be used instead
56
+ //#[napi]
57
+ //fn livekit_drop_handle(handle: BigInt) -> bool {
58
+ // let (_, handle, _) = handle.get_u64();
59
+ // FFI_SERVER.drop_handle(handle)
60
+ //}
61
+
62
+ #[napi]
63
+ fn livekit_retrieve_ptr(handle: Uint8Array) -> BigInt {
64
+ BigInt::from(handle.as_ptr() as u64)
65
+ }
66
+
67
+ #[napi]
68
+ fn livekit_copy_buffer(ptr: BigInt, len: u32) -> Uint8Array {
69
+ let (_, ptr, _) = ptr.get_u64();
70
+ let data = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) };
71
+ Uint8Array::with_data_copied(data)
72
+ }
73
+
74
+ #[napi(custom_finalize)]
75
+ pub struct FfiHandle {
76
+ handle: BigInt,
77
+ disposed: bool,
78
+ // TODO(theomonnom): add gc pressure memory
79
+ }
80
+
81
+ #[napi]
82
+ impl FfiHandle {
83
+ #[napi(constructor)]
84
+ pub fn new(handle: BigInt) -> Self {
85
+ Self {
86
+ handle,
87
+ disposed: false,
88
+ }
89
+ }
90
+
91
+ #[napi]
92
+ pub fn dispose(&mut self) -> Result<()> {
93
+ if self.disposed {
94
+ return Ok(());
95
+ }
96
+ self.disposed = true;
97
+ let (_, handle, _) = self.handle.get_u64();
98
+ if !FFI_SERVER.drop_handle(handle) {
99
+ return Err(Error::from_reason("trying to drop an invalid handle"));
100
+ }
101
+
102
+ Ok(())
103
+ }
104
+
105
+ #[napi(getter)]
106
+ pub fn handle(&self) -> BigInt {
107
+ self.handle.clone()
108
+ }
109
+ }
110
+
111
+ impl ObjectFinalize for FfiHandle {
112
+ fn finalize(mut self, env: Env) -> Result<()> {
113
+ self.dispose()
114
+ }
115
+ }
@@ -0,0 +1,171 @@
1
+ import { FfiClient, FfiClientEvent, FfiHandle, FfiRequest } from './ffi_client';
2
+ import { ParticipantInfo, OwnedParticipant } from './proto/participant_pb';
3
+ import {
4
+ DataPacketKind,
5
+ PublishDataCallback,
6
+ PublishDataRequest,
7
+ PublishDataResponse,
8
+ PublishTrackCallback,
9
+ PublishTrackRequest,
10
+ PublishTrackResponse,
11
+ TrackPublishOptions,
12
+ UnpublishTrackCallback,
13
+ UnpublishTrackRequest,
14
+ UnpublishTrackResponse,
15
+ UpdateLocalMetadataCallback,
16
+ UpdateLocalMetadataRequest,
17
+ UpdateLocalMetadataResponse,
18
+ UpdateLocalNameCallback,
19
+ UpdateLocalNameRequest,
20
+ UpdateLocalNameResponse,
21
+ } from './proto/room_pb';
22
+ import { LocalTrackPublication, RemoteTrackPublication, TrackPublication } from './track_publication';
23
+ import { LocalTrack } from './track';
24
+
25
+ export abstract class Participant {
26
+ /** @internal */
27
+ info: ParticipantInfo;
28
+
29
+ /** @internal */
30
+ ffi_handle: FfiHandle;
31
+
32
+ tracks = new Map<string, TrackPublication>();
33
+
34
+ constructor(owned_info: OwnedParticipant) {
35
+ this.info = owned_info.info;
36
+ this.ffi_handle = new FfiHandle(owned_info.handle.id);
37
+ }
38
+
39
+ get sid(): string {
40
+ return this.info.sid;
41
+ }
42
+
43
+ get name(): string {
44
+ return this.info.name;
45
+ }
46
+
47
+ get identity(): string {
48
+ return this.info.identity;
49
+ }
50
+
51
+ get metadata(): string {
52
+ return this.info.metadata;
53
+ }
54
+ }
55
+
56
+ export class LocalParticipant extends Participant {
57
+ tracks: Map<string, LocalTrackPublication> = new Map();
58
+
59
+ async publishData(
60
+ data: Uint8Array,
61
+ kind: DataPacketKind,
62
+ destination_sids: Array<string | RemoteParticipant> = [], // empty = broadcast
63
+ ) {
64
+ let req = new PublishDataRequest({
65
+ localParticipantHandle: this.ffi_handle.handle,
66
+ dataPtr: FfiClient.instance.retrievePtr(data),
67
+ dataLen: BigInt(data.byteLength),
68
+ kind: kind,
69
+ });
70
+
71
+ let sids = destination_sids.map((sid) => {
72
+ if (typeof sid == 'string') return sid;
73
+ return sid.sid;
74
+ });
75
+ req.destinationSids = sids;
76
+
77
+ let res = FfiClient.instance.request<PublishDataResponse>({
78
+ message: { case: 'publishData', value: req },
79
+ });
80
+
81
+ let cb = await FfiClient.instance.waitFor<PublishDataCallback>((ev) => {
82
+ return ev.message.case == 'publishData' && ev.message.value.asyncId == res.asyncId;
83
+ });
84
+
85
+ if (cb.error) {
86
+ throw new Error(cb.error);
87
+ }
88
+ }
89
+
90
+ async updateMetadata(metadata: string) {
91
+ let req = new UpdateLocalMetadataRequest({
92
+ localParticipantHandle: this.ffi_handle.handle,
93
+ metadata: metadata,
94
+ });
95
+
96
+ let res = FfiClient.instance.request<UpdateLocalMetadataResponse>({
97
+ message: { case: 'updateLocalMetadata', value: req },
98
+ });
99
+
100
+ await FfiClient.instance.waitFor<UpdateLocalMetadataCallback>((ev) => {
101
+ return ev.message.case == 'updateLocalMetadata' && ev.message.value.asyncId == res.asyncId;
102
+ });
103
+ }
104
+
105
+ async updateName(name: string) {
106
+ let req = new UpdateLocalNameRequest({
107
+ localParticipantHandle: this.ffi_handle.handle,
108
+ name: name,
109
+ });
110
+
111
+ let res = FfiClient.instance.request<UpdateLocalNameResponse>({
112
+ message: { case: 'updateLocalName', value: req },
113
+ });
114
+
115
+ await FfiClient.instance.waitFor<UpdateLocalNameCallback>((ev) => {
116
+ return ev.message.case == 'updateLocalName' && ev.message.value.asyncId == res.asyncId;
117
+ });
118
+ }
119
+
120
+ async publishTrack(
121
+ track: LocalTrack,
122
+ options: TrackPublishOptions,
123
+ ): Promise<LocalTrackPublication> {
124
+ let req = new PublishTrackRequest({
125
+ localParticipantHandle: this.ffi_handle.handle,
126
+ trackHandle: track.ffi_handle.handle,
127
+ options: options,
128
+ });
129
+
130
+ let res = FfiClient.instance.request<PublishTrackResponse>({
131
+ message: { case: 'publishTrack', value: req },
132
+ });
133
+
134
+ let cb = await FfiClient.instance.waitFor<PublishTrackCallback>((ev) => {
135
+ return ev.message.case == 'publishTrack' && ev.message.value.asyncId == res.asyncId;
136
+ });
137
+
138
+ let track_publication = new LocalTrackPublication(cb.publication);
139
+ track_publication.track = track;
140
+ this.tracks.set(track_publication.sid, track_publication);
141
+
142
+ return track_publication;
143
+ }
144
+
145
+ async unpublishTrack(trackSid: string) {
146
+ let req = new UnpublishTrackRequest({
147
+ localParticipantHandle: this.ffi_handle.handle,
148
+ trackSid: trackSid,
149
+ });
150
+
151
+ let res = FfiClient.instance.request<UnpublishTrackResponse>({
152
+ message: { case: 'unpublishTrack', value: req },
153
+ });
154
+
155
+ await FfiClient.instance.waitFor<UnpublishTrackCallback>((ev) => {
156
+ return ev.message.case == 'unpublishTrack' && ev.message.value.asyncId == res.asyncId;
157
+ });
158
+
159
+ let pub = this.tracks.get(trackSid);
160
+ pub.track = undefined;
161
+ this.tracks.delete(trackSid);
162
+ }
163
+ }
164
+
165
+ export class RemoteParticipant extends Participant {
166
+ tracks: Map<string, RemoteTrackPublication> = new Map();
167
+
168
+ constructor(owned_info: OwnedParticipant) {
169
+ super(owned_info);
170
+ }
171
+ }