pluto-rtc 0.0.2

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/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # PlutoRTC
2
+
3
+ A bare bones package for developers to create RTC apps quickly and painlessly.
4
+ Leverages Pluto/Iroh for connectivity and Firebase for signaling/auth.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install pluto-rtc
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Initialization
15
+
16
+ ```typescript
17
+ import { ConnectionManager } from 'pluto-rtc';
18
+
19
+ const rtc = new ConnectionManager({
20
+ firebaseConfig: { ... }, // Your Firebase Config
21
+ tag: 'my-awesome-game'
22
+ });
23
+
24
+ // Initialize and Listen
25
+ await rtc.startListening();
26
+ ```
27
+
28
+ ### Authentication
29
+
30
+ Redirects to the configured auth provider (e.g. Plutonium Identity).
31
+
32
+ ```typescript
33
+ rtc.signIn(); // Triggers redirect
34
+ ```
35
+
36
+ ### Discovery & Connection
37
+
38
+ Connect to other devices belonging to the same user:
39
+
40
+ ```typescript
41
+ // Search for my devices
42
+ const devices = await rtc.searchDevices();
43
+
44
+ // Connect to a device
45
+ const conn = await rtc.connect(devices[0].ticket);
46
+
47
+ conn.send("Hello World!");
48
+
49
+ conn.onMessage((msg) => {
50
+ console.log("Received:", msg);
51
+ });
52
+ ```
53
+
54
+ ### Rooms (Multi-User / Custom Signaling)
55
+
56
+ Create a room for others to join (using short IDs):
57
+
58
+ ```typescript
59
+ // Host a room
60
+ const roomId = await rtc.createRoom('my-game-rooms');
61
+ console.log(`Room created: ${roomId}`);
62
+
63
+ // Handle join requests
64
+ rtc.onRoomJoinRequest(async (req) => {
65
+ console.log(`User ${req.userId} wants to join`);
66
+ await rtc.acceptJoinRequest(roomId, req.id, 'my-game-rooms');
67
+ });
68
+
69
+ rtc.onConnection((conn) => {
70
+ console.log("New peer connected:", conn.id);
71
+ });
72
+ ```
73
+
74
+ Join a room:
75
+
76
+ ```typescript
77
+ // Join a room
78
+ try {
79
+ const conn = await rtc.joinRoom('ROOM_ID', 'my-game-rooms');
80
+ console.log("Joined room!");
81
+ } catch (e) {
82
+ console.error("Join rejected or failed", e);
83
+ }
84
+ ```
@@ -0,0 +1,35 @@
1
+ export interface ConnectionOptions {
2
+ reliable?: boolean;
3
+ }
4
+ export declare class Connection {
5
+ private listeners;
6
+ private closeListeners;
7
+ private writer;
8
+ private reader;
9
+ private isClosed;
10
+ private receiveBuffer;
11
+ readonly id: string;
12
+ readonly deviceId: string;
13
+ constructor(id: string, deviceId: string, writer: WritableStreamDefaultWriter<Uint8Array>, reader: ReadableStreamDefaultReader<Uint8Array>);
14
+ /**
15
+ * Send a message to the other peer.
16
+ * Messages can be strings or objects (JSON stringified).
17
+ */
18
+ send(message: string | object | ArrayBuffer | Uint8Array): Promise<void>;
19
+ /**
20
+ * Disconnect the connection
21
+ */
22
+ disconnect(): Promise<void>;
23
+ /**
24
+ * Listen for incoming messages
25
+ */
26
+ onMessage(callback: (message: any) => void): void;
27
+ /**
28
+ * Listen for disconnection
29
+ */
30
+ onDisconnect(callback: () => void): void;
31
+ private close;
32
+ private readLoop;
33
+ private handleData;
34
+ private emitMessage;
35
+ }
@@ -0,0 +1,146 @@
1
+ export class Connection {
2
+ constructor(id, deviceId, writer, reader) {
3
+ this.listeners = [];
4
+ this.closeListeners = [];
5
+ // We'll keep track of the internal stream
6
+ this.writer = null;
7
+ this.reader = null;
8
+ this.isClosed = false;
9
+ // Buffer for incoming data
10
+ this.receiveBuffer = new Uint8Array(0);
11
+ this.id = id;
12
+ this.deviceId = deviceId;
13
+ this.writer = writer;
14
+ this.reader = reader;
15
+ this.readLoop();
16
+ }
17
+ /**
18
+ * Send a message to the other peer.
19
+ * Messages can be strings or objects (JSON stringified).
20
+ */
21
+ async send(message) {
22
+ if (this.isClosed || !this.writer) {
23
+ throw new Error('Connection is closed');
24
+ }
25
+ let payload;
26
+ if (typeof message === 'string') {
27
+ payload = new TextEncoder().encode(message);
28
+ }
29
+ else if (message instanceof Uint8Array) {
30
+ payload = message;
31
+ }
32
+ else if (message instanceof ArrayBuffer) {
33
+ payload = new Uint8Array(message);
34
+ }
35
+ else {
36
+ payload = new TextEncoder().encode(JSON.stringify(message));
37
+ }
38
+ // Framing: 4 bytes length (Big Endian) + payload
39
+ const frame = new Uint8Array(4 + payload.length);
40
+ const view = new DataView(frame.buffer);
41
+ view.setUint32(0, payload.length, false);
42
+ frame.set(payload, 4);
43
+ try {
44
+ await this.writer.write(frame);
45
+ }
46
+ catch (err) {
47
+ console.error('[Connection] Send error:', err);
48
+ this.close();
49
+ throw err;
50
+ }
51
+ }
52
+ /**
53
+ * Disconnect the connection
54
+ */
55
+ async disconnect() {
56
+ this.close();
57
+ }
58
+ /**
59
+ * Listen for incoming messages
60
+ */
61
+ onMessage(callback) {
62
+ this.listeners.push(callback);
63
+ }
64
+ /**
65
+ * Listen for disconnection
66
+ */
67
+ onDisconnect(callback) {
68
+ this.closeListeners.push(callback);
69
+ }
70
+ close() {
71
+ if (this.isClosed)
72
+ return;
73
+ this.isClosed = true;
74
+ // Close writer/reader
75
+ this.writer?.close().catch(() => { });
76
+ this.reader?.cancel().catch(() => { });
77
+ this.writer = null;
78
+ this.reader = null;
79
+ // Notify listeners
80
+ this.closeListeners.forEach(cb => cb());
81
+ }
82
+ async readLoop() {
83
+ if (!this.reader)
84
+ return;
85
+ try {
86
+ while (!this.isClosed) {
87
+ const { done, value } = await this.reader.read();
88
+ if (done) {
89
+ this.close();
90
+ break;
91
+ }
92
+ if (value) {
93
+ this.handleData(value);
94
+ }
95
+ }
96
+ }
97
+ catch (err) {
98
+ console.error('[Connection] Read loop error:', err);
99
+ this.close();
100
+ }
101
+ }
102
+ handleData(chunk) {
103
+ // Append to buffer
104
+ const newBuffer = new Uint8Array(this.receiveBuffer.length + chunk.length);
105
+ newBuffer.set(this.receiveBuffer);
106
+ newBuffer.set(chunk, this.receiveBuffer.length);
107
+ this.receiveBuffer = newBuffer;
108
+ // Process messages
109
+ while (true) {
110
+ if (this.receiveBuffer.length < 4)
111
+ break; // Need at least length header
112
+ const view = new DataView(this.receiveBuffer.buffer, this.receiveBuffer.byteOffset, this.receiveBuffer.byteLength);
113
+ const length = view.getUint32(0, false);
114
+ if (this.receiveBuffer.length < 4 + length)
115
+ break; // Wait for full message
116
+ // Extract message
117
+ const messageData = this.receiveBuffer.slice(4, 4 + length);
118
+ this.receiveBuffer = this.receiveBuffer.slice(4 + length);
119
+ // Check if it's text/JSON
120
+ try {
121
+ const text = new TextDecoder().decode(messageData);
122
+ // Try to parse JSON, if it looks like object/array, return parsed
123
+ // Otherwise return string
124
+ try {
125
+ const json = JSON.parse(text);
126
+ if (typeof json === 'object' && json !== null) {
127
+ this.emitMessage(json);
128
+ }
129
+ else {
130
+ this.emitMessage(text);
131
+ }
132
+ }
133
+ catch {
134
+ this.emitMessage(text);
135
+ }
136
+ }
137
+ catch {
138
+ // Validation failed, send raw? For now assume it wraps text/json
139
+ this.emitMessage(messageData);
140
+ }
141
+ }
142
+ }
143
+ emitMessage(msg) {
144
+ this.listeners.forEach(cb => cb(msg));
145
+ }
146
+ }
@@ -0,0 +1,38 @@
1
+ import { ClientOptions } from './core/Client';
2
+ import { Connection } from './core/Connection';
3
+ import { JoinRequest } from './core/Room';
4
+ /**
5
+ * @deprecated Use Client instead. This class is a backward-compatible wrapper.
6
+ */
7
+ export declare class ConnectionManager {
8
+ private client;
9
+ constructor(options: ClientOptions);
10
+ get currentUser(): import("@firebase/auth").User | null;
11
+ onAuthChange(callback: (user: any) => void): void;
12
+ init(): Promise<void>;
13
+ connect(ticket: string): Promise<Connection>;
14
+ getConnections(): Connection[];
15
+ getTicket(): Promise<string>;
16
+ getNodeId(): Promise<string>;
17
+ startListening(): Promise<void>;
18
+ stopListening(): void;
19
+ searchDevices(): Promise<{
20
+ deviceId: string;
21
+ deviceName: string;
22
+ online: boolean;
23
+ ticket: string;
24
+ }[]>;
25
+ onDevicesChange(callback: any): () => void;
26
+ createRoom(): Promise<string>;
27
+ joinRoom(roomId: string): Promise<Connection[]>;
28
+ leaveRoom(roomId: string): Promise<void>;
29
+ onConnection(callback: (conn: Connection) => void): void;
30
+ onDisconnection(callback: (conn: Connection) => void): void;
31
+ onMessage(callback: (conn: Connection, msg: any) => void): void;
32
+ onRoomJoinRequest(callback: (request: JoinRequest) => void): void;
33
+ signInWithPluto(): void;
34
+ signOut(): Promise<void>;
35
+ signInAnonymously(): Promise<void>;
36
+ }
37
+ export * from './core/Connection';
38
+ export * from './core/Client';
@@ -0,0 +1,78 @@
1
+ import { Client } from './core/Client';
2
+ /**
3
+ * @deprecated Use Client instead. This class is a backward-compatible wrapper.
4
+ */
5
+ export class ConnectionManager {
6
+ constructor(options) {
7
+ this.client = new Client(options);
8
+ }
9
+ get currentUser() {
10
+ return this.client.signaling.currentUser;
11
+ }
12
+ onAuthChange(callback) {
13
+ return this.client.signaling.onAuthChange(callback);
14
+ }
15
+ async init() {
16
+ return this.client.init();
17
+ }
18
+ async connect(ticket) {
19
+ return this.client.connect(ticket);
20
+ }
21
+ getConnections() {
22
+ return this.client.getConnections();
23
+ }
24
+ async getTicket() {
25
+ return this.client.getTicket();
26
+ }
27
+ async getNodeId() {
28
+ return this.client.getNodeId();
29
+ }
30
+ async startListening() {
31
+ return this.client.startListening();
32
+ }
33
+ stopListening() {
34
+ return this.client.stopListening();
35
+ }
36
+ // Discovery (delegated to Signaling)
37
+ async searchDevices() {
38
+ return this.client.signaling.searchDevices();
39
+ }
40
+ onDevicesChange(callback) {
41
+ return this.client.signaling.onDevicesChange(callback);
42
+ }
43
+ // Rooms (delegated to RoomManager)
44
+ async createRoom() {
45
+ return this.client.rooms.createRoom();
46
+ }
47
+ async joinRoom(roomId) {
48
+ return this.client.rooms.joinRoom(roomId);
49
+ }
50
+ async leaveRoom(roomId) {
51
+ return this.client.rooms.leaveRoom(roomId);
52
+ }
53
+ // Listeners
54
+ onConnection(callback) {
55
+ this.client.onConnection(callback);
56
+ }
57
+ onDisconnection(callback) {
58
+ this.client.onDisconnection(callback);
59
+ }
60
+ onMessage(callback) {
61
+ this.client.onMessage(callback);
62
+ }
63
+ onRoomJoinRequest(callback) {
64
+ this.client.onRoomJoinRequest(callback);
65
+ }
66
+ // Auth helpers
67
+ signInWithPluto() {
68
+ this.client.signaling.signInWithPluto();
69
+ }
70
+ async signOut() {
71
+ await this.client.signaling.signOut();
72
+ }
73
+ async signInAnonymously() {
74
+ await this.client.signaling.signInAnonymously();
75
+ }
76
+ }
77
+ export * from './core/Connection';
78
+ export * from './core/Client'; // Export Client so users can switch
@@ -0,0 +1,7 @@
1
+ export declare class MediaTransport {
2
+ static sendTrack(track: MediaStreamTrack, sendFn: (data: Uint8Array) => Promise<void>): Promise<{
3
+ stop: () => void;
4
+ requestKeyFrame: () => void;
5
+ } | void>;
6
+ static receiveTrack(kind: 'audio' | 'video', onKeyFrameRequest?: () => void): MediaStreamTrack | null;
7
+ }
@@ -0,0 +1,262 @@
1
+ // Protocol-ish logic for transforming MediaStreamTracks into Chunks and back.
2
+ // Since we don't have VideoEncoder/VideoDecoder in standard ts environment (it's web only),
3
+ // we will just define the interfaces or classes here.
4
+ // We use type 'any' for WebCodecs interfaces to avoid TS errors in environemnts without @types/dom-webcodecs
5
+ export class MediaTransport {
6
+ // Encodes a track into the connection
7
+ static async sendTrack(track, sendFn) {
8
+ console.log(`[MediaTransport] sendTrack called for ${track.kind}`);
9
+ if ((typeof VideoEncoder === 'undefined') && (typeof AudioEncoder === 'undefined')) {
10
+ console.error("WebCodecs API not supported.");
11
+ return;
12
+ }
13
+ const processor = new window.MediaStreamTrackProcessor({ track });
14
+ const reader = processor.readable.getReader();
15
+ // Very basic encoder setup.
16
+ // In reality we need to negotiate codecs. We assume VP8 for video and Opus for audio.
17
+ let encoder = null;
18
+ if (track.kind === 'video') {
19
+ let seqNum = 0;
20
+ encoder = new window.VideoEncoder({
21
+ output: (chunk, metadata) => {
22
+ if (chunk.type === 'key') {
23
+ console.log(`[MediaTransport] Generated KEY frame for video. Size: ${chunk.byteLength}`);
24
+ }
25
+ // console.log(`[MediaTransport] Encoded video chunk. Key: ${chunk.type}, Size: ${chunk.byteLength}`);
26
+ // Send chunk
27
+ // We need to serialize the chunk.
28
+ // [MAGIC 1b][timestamp 8 bytes][isKey 1 byte][seq 2 bytes][data...]
29
+ const buffer = new ArrayBuffer(12 + chunk.byteLength);
30
+ const view = new DataView(buffer);
31
+ view.setUint8(0, 0x77); // Magic byte
32
+ view.setBigInt64(1, BigInt(chunk.timestamp), false); // Big Endian
33
+ view.setUint8(9, chunk.type === 'key' ? 1 : 0);
34
+ const seq = (seqNum++) % 65536;
35
+ view.setUint16(10, seq, false);
36
+ const data = new Uint8Array(buffer);
37
+ chunk.copyTo(new Uint8Array(buffer, 12));
38
+ sendFn(data).catch(e => {
39
+ const msg = (e.message || String(e)).toLowerCase();
40
+ if (msg.includes("connection is closed") || msg.includes("connection closed") || msg.includes("sending stopped by peer")) {
41
+ console.log(`[MediaTransport] Connection closed/stopped, stopping video encoder.`);
42
+ if (encoder) {
43
+ encoder.close();
44
+ encoder = null;
45
+ }
46
+ }
47
+ else {
48
+ console.error("Send media failed", e);
49
+ }
50
+ });
51
+ },
52
+ error: (e) => console.error("VideoEncoder error", e)
53
+ });
54
+ encoder.configure({
55
+ codec: 'vp8',
56
+ width: 640,
57
+ height: 480,
58
+ bitrate: 500000, // 500 Kbps - Reduce congestion
59
+ framerate: 24, // slightly lower framerate
60
+ latencyMode: 'realtime', // Critical for low latency streaming
61
+ });
62
+ }
63
+ else if (track.kind === 'audio') {
64
+ encoder = new window.AudioEncoder({
65
+ output: (chunk, metadata) => {
66
+ const buffer = new ArrayBuffer(9 + chunk.byteLength);
67
+ const view = new DataView(buffer);
68
+ view.setUint8(0, 0x77); // Magic
69
+ view.setBigInt64(1, BigInt(chunk.timestamp), false);
70
+ const data = new Uint8Array(buffer);
71
+ chunk.copyTo(new Uint8Array(buffer, 9));
72
+ sendFn(data).catch(e => {
73
+ const msg = (e.message || String(e)).toLowerCase();
74
+ if (msg.includes("connection is closed") || msg.includes("connection closed")) {
75
+ console.log(`[MediaTransport] Connection closed, stopping audio encoder.`);
76
+ if (encoder) {
77
+ encoder.close();
78
+ encoder = null;
79
+ }
80
+ }
81
+ else {
82
+ console.error("Send audio failed", e);
83
+ }
84
+ });
85
+ },
86
+ error: (e) => console.error("AudioEncoder error", e)
87
+ });
88
+ encoder.configure({
89
+ codec: 'opus',
90
+ sampleRate: 48000,
91
+ numberOfChannels: 1
92
+ });
93
+ }
94
+ // Read loop - Run asynchronously so we can return the controller immediately
95
+ (async () => {
96
+ try {
97
+ while (true) {
98
+ const { done, value } = await reader.read();
99
+ if (done) {
100
+ console.log(`[MediaTransport] Reader done for ${track.kind}`);
101
+ break;
102
+ }
103
+ if (value) {
104
+ try {
105
+ if (encoder && encoder.state !== 'closed') {
106
+ const forceKey = encoder.forceKeyFrame;
107
+ if (forceKey) {
108
+ console.log(`[MediaTransport] Forcing Key Frame now.`);
109
+ encoder.encode(value, { keyFrame: true });
110
+ encoder.forceKeyFrame = false;
111
+ }
112
+ else {
113
+ encoder.encode(value);
114
+ }
115
+ }
116
+ }
117
+ finally {
118
+ value.close();
119
+ }
120
+ }
121
+ }
122
+ }
123
+ catch (e) {
124
+ console.error(`[MediaTransport] Read loop error for ${track.kind}:`, e);
125
+ }
126
+ })();
127
+ // Return controller
128
+ return {
129
+ stop: () => {
130
+ // TODO: Close processor and reader
131
+ },
132
+ requestKeyFrame: () => {
133
+ if (encoder && encoder.state === 'configured') {
134
+ console.log(`[MediaTransport] Key Frame Requested for ${track.kind}`);
135
+ // We can't force it here directly because we are in a read loop.
136
+ // But we can set a flag that the loop checks.
137
+ encoder.forceKeyFrame = true;
138
+ }
139
+ else {
140
+ console.warn(`[MediaTransport] Impossible to request KeyFrame: Encoder state is ${encoder?.state}`);
141
+ }
142
+ }
143
+ };
144
+ }
145
+ // Receives chunks and outputs a MediaStreamTrack
146
+ static receiveTrack(kind, onKeyFrameRequest) {
147
+ if ((typeof VideoDecoder === 'undefined') && (typeof AudioDecoder === 'undefined')) {
148
+ console.warn("WebCodecs API not supported.");
149
+ return null;
150
+ }
151
+ const generator = new window.MediaStreamTrackGenerator({ kind });
152
+ const writer = generator.writable.getWriter();
153
+ let decoder = null;
154
+ // Throttling for PLI
155
+ let lastPli = 0;
156
+ let expectedSeq = null;
157
+ if (kind === 'video') {
158
+ decoder = new window.VideoDecoder({
159
+ output: (frame) => {
160
+ try {
161
+ writer.write(frame);
162
+ }
163
+ finally {
164
+ frame.close();
165
+ }
166
+ },
167
+ error: (e) => console.error("VideoDecoder error", e)
168
+ });
169
+ decoder.configure({
170
+ codec: 'vp8'
171
+ });
172
+ // Force wait for key frame immediately
173
+ decoder.waitingForKeyFrame = true;
174
+ }
175
+ else {
176
+ decoder = new window.AudioDecoder({
177
+ output: (data) => {
178
+ writer.write(data);
179
+ data.close();
180
+ },
181
+ error: (e) => console.error("AudioDecoder error", e)
182
+ });
183
+ decoder.configure({
184
+ codec: 'opus',
185
+ sampleRate: 48000,
186
+ numberOfChannels: 1
187
+ });
188
+ }
189
+ // Attach a method to the track object or return a controller?
190
+ // We will just return the track, but we need a way to feed data into the decoder.
191
+ // We can monkey-patch the track or store it in a map in PeerConnection.
192
+ generator.feed = (data) => {
193
+ if (decoder.state === 'closed')
194
+ return;
195
+ // Verify Magic
196
+ if (data.byteLength < 1 || data[0] !== 0x77) {
197
+ console.error("[MediaTransport] Invalid magic byte in media stream! Dropping packet.");
198
+ return;
199
+ }
200
+ if (kind === 'video') {
201
+ // Parse
202
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
203
+ const timestamp = Number(view.getBigInt64(1, false));
204
+ const isKey = view.getUint8(9) === 1;
205
+ const seq = view.getUint16(10, false);
206
+ const chunkData = data.slice(12);
207
+ // Sequence Check
208
+ if (expectedSeq !== null) {
209
+ if (seq !== expectedSeq) {
210
+ console.warn(`[MediaTransport] Sequence Mismatch! Expected ${expectedSeq}, got ${seq}. Dropping and requesting PLI.`);
211
+ decoder.waitingForKeyFrame = true;
212
+ if (onKeyFrameRequest)
213
+ onKeyFrameRequest();
214
+ // We also reset expectedSeq to this new one + 1?
215
+ // No, we better wait for KeyFrame.
216
+ // But we must update expectedSeq to avoid infinite loops if the stream just continues.
217
+ expectedSeq = (seq + 1) % 65536;
218
+ return;
219
+ }
220
+ }
221
+ expectedSeq = (seq + 1) % 65536;
222
+ const chunk = new window.EncodedVideoChunk({
223
+ type: isKey ? 'key' : 'delta',
224
+ timestamp: timestamp,
225
+ data: chunkData
226
+ });
227
+ if (decoder.state === 'configured' && !isKey && decoder.waitingForKeyFrame !== false) {
228
+ // Drop delta frames until we get a keyframe
229
+ const now = Date.now();
230
+ if (now - lastPli > 1000) {
231
+ console.log("[MediaTransport] Dropping delta frame, waiting for key frame. Requesting PLI.");
232
+ if (onKeyFrameRequest)
233
+ onKeyFrameRequest();
234
+ lastPli = now;
235
+ }
236
+ return;
237
+ }
238
+ if (isKey) {
239
+ decoder.waitingForKeyFrame = false;
240
+ }
241
+ try {
242
+ decoder.decode(chunk);
243
+ }
244
+ catch (e) {
245
+ console.error("[MediaTransport] Decode error:", e);
246
+ }
247
+ }
248
+ else {
249
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
250
+ const timestamp = Number(view.getBigInt64(1, false));
251
+ const chunkData = data.slice(9);
252
+ const chunk = new window.EncodedAudioChunk({
253
+ type: 'key', // Audio usually all key frames or dependent? Opus is complicated.
254
+ timestamp: timestamp,
255
+ data: chunkData
256
+ });
257
+ decoder.decode(chunk);
258
+ }
259
+ };
260
+ return generator;
261
+ }
262
+ }
@@ -0,0 +1,38 @@
1
+ import { Client } from '../core/Client';
2
+ import { Connection } from '../core/Connection';
3
+ export declare class PlutoPeerConnection extends EventTarget {
4
+ static defaultClient: Client | null;
5
+ static setDefaultClient(client: Client): void;
6
+ onicecandidate: ((ev: RTCPeerConnectionIceEvent) => any) | null;
7
+ oniceconnectionstatechange: ((ev: Event) => any) | null;
8
+ ontrack: ((ev: RTCTrackEvent) => any) | null;
9
+ onsignalingstatechange: ((ev: Event) => any) | null;
10
+ connectionState: RTCPeerConnectionState;
11
+ iceConnectionState: RTCIceConnectionState;
12
+ signalingState: RTCSignalingState;
13
+ localDescription: RTCSessionDescription | null;
14
+ remoteDescription: RTCSessionDescription | null;
15
+ private client;
16
+ private connection;
17
+ constructor(configuration?: RTCConfiguration, clientInstance?: Client);
18
+ createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit>;
19
+ createAnswer(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit>;
20
+ setLocalDescription(desc: RTCSessionDescriptionInit): Promise<void>;
21
+ setRemoteDescription(desc: RTCSessionDescriptionInit): Promise<void>;
22
+ addIceCandidate(candidate: RTCIceCandidateInit | RTCIceCandidate): Promise<void>;
23
+ /**
24
+ * Manually provide an existing connection (e.g. from an incoming connection listener)
25
+ */
26
+ private bufferTracks;
27
+ handleIncomingConnection(connection: Connection): Promise<void>;
28
+ private receivers;
29
+ private setupHooks;
30
+ addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender;
31
+ private mediaSenders;
32
+ private startIrohStream;
33
+ createDataChannel(label: string): RTCDataChannel;
34
+ sendData(data: any): void;
35
+ private connect;
36
+ close(): void;
37
+ private updateState;
38
+ }