openrtc-netcode 3.0.1 → 3.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 +181 -40
- package/dist/actions.d.ts +40 -0
- package/dist/actions.js +269 -0
- package/dist/client.js +1049 -294
- package/dist/codec.d.ts +74 -0
- package/dist/codec.js +167 -0
- package/dist/framing.d.ts +9 -0
- package/dist/framing.js +39 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/latestState.d.ts +4 -1
- package/dist/latestState.js +23 -19
- package/dist/motion.d.ts +9 -0
- package/dist/motion.js +12 -0
- package/dist/objects.d.ts +34 -5
- package/dist/objects.js +276 -38
- package/dist/protocol.d.ts +15 -2
- package/dist/protocol.js +20 -5
- package/dist/replication.d.ts +31 -0
- package/dist/replication.js +135 -0
- package/dist/roomRuntime.js +18 -16
- package/dist/simulation.d.ts +80 -0
- package/dist/simulation.js +269 -0
- package/dist/snapshots.d.ts +12 -0
- package/dist/snapshots.js +71 -0
- package/dist/state.d.ts +17 -4
- package/dist/state.js +162 -51
- package/dist/types.d.ts +50 -3
- package/dist/validation.d.ts +10 -0
- package/dist/validation.js +88 -0
- package/dist/voice.d.ts +2 -0
- package/dist/voice.js +81 -39
- package/package.json +2 -2
package/dist/voice.js
CHANGED
|
@@ -11,6 +11,8 @@ export class NetcodeVoiceController {
|
|
|
11
11
|
this.localMediaStream = null;
|
|
12
12
|
this.options = {};
|
|
13
13
|
this.active = false;
|
|
14
|
+
this.generation = 0;
|
|
15
|
+
this.starting = null;
|
|
14
16
|
this.muted = false;
|
|
15
17
|
}
|
|
16
18
|
get enabled() {
|
|
@@ -30,41 +32,61 @@ export class NetcodeVoiceController {
|
|
|
30
32
|
}));
|
|
31
33
|
}
|
|
32
34
|
async start(options = {}) {
|
|
35
|
+
if (this.starting)
|
|
36
|
+
return this.starting;
|
|
33
37
|
if (this.active && this.localMediaStream)
|
|
34
38
|
return this.localMediaStream;
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const generation = ++this.generation;
|
|
40
|
+
const opening = (async () => {
|
|
41
|
+
const localStream = options.mediaStream ?? await this.captureAudio(options);
|
|
42
|
+
if (generation !== this.generation) {
|
|
43
|
+
if (!options.mediaStream || options.stopTracksOnStop !== false) {
|
|
44
|
+
for (const track of localStream.getTracks())
|
|
45
|
+
track.stop();
|
|
46
|
+
}
|
|
47
|
+
throw new Error('Voice start was superseded by stop.');
|
|
48
|
+
}
|
|
49
|
+
this.options = options;
|
|
50
|
+
this.localMediaStream = localStream;
|
|
51
|
+
this.active = true;
|
|
52
|
+
this.applyMuteState();
|
|
53
|
+
this.emitter.emit('local:stream', localStream);
|
|
43
54
|
try {
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
55
|
+
if (this.localPeerId())
|
|
56
|
+
await Promise.all(this.peers()
|
|
57
|
+
.filter((peer) => peer.connected).map((peer) => this.startPeerSession(peer.id)));
|
|
47
58
|
}
|
|
48
59
|
catch (error) {
|
|
49
|
-
|
|
60
|
+
if (generation === this.generation)
|
|
61
|
+
await this.stop();
|
|
50
62
|
throw error;
|
|
51
63
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
64
|
+
if (generation !== this.generation)
|
|
65
|
+
throw new Error('Voice start was superseded by stop.');
|
|
66
|
+
return localStream;
|
|
67
|
+
})().finally(() => { if (this.starting === opening)
|
|
68
|
+
this.starting = null; });
|
|
69
|
+
this.starting = opening;
|
|
70
|
+
return opening;
|
|
57
71
|
}
|
|
58
72
|
async stop() {
|
|
73
|
+
this.generation++;
|
|
74
|
+
this.starting = null;
|
|
59
75
|
this.active = false;
|
|
76
|
+
const localStream = this.localMediaStream;
|
|
77
|
+
const stopTracks = this.options.stopTracksOnStop !== false;
|
|
78
|
+
this.localMediaStream = null;
|
|
79
|
+
const generation = this.generation;
|
|
60
80
|
const current = [...this.sessions.keys()].map((peerId) => this.closePeerSession(peerId));
|
|
61
81
|
await Promise.allSettled([...current, ...this.closingSessions]);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
82
|
+
const retainedTracks = new Set(this.localStream?.getTracks() ?? []);
|
|
83
|
+
if (stopTracks)
|
|
84
|
+
for (const track of localStream?.getTracks() ?? []) {
|
|
85
|
+
if (!retainedTracks.has(track))
|
|
86
|
+
track.stop();
|
|
87
|
+
}
|
|
88
|
+
if (generation === this.generation)
|
|
89
|
+
this.emitter.emit('peers', []);
|
|
68
90
|
}
|
|
69
91
|
setMuted(muted) {
|
|
70
92
|
this.muted = muted;
|
|
@@ -79,18 +101,24 @@ export class NetcodeVoiceController {
|
|
|
79
101
|
await this.startPeerSession(peerId);
|
|
80
102
|
}
|
|
81
103
|
handlePeerDisconnected(peerId) {
|
|
82
|
-
this.
|
|
104
|
+
void this.closePeerSession(peerId);
|
|
83
105
|
}
|
|
84
106
|
async startPeerSession(peerId) {
|
|
85
107
|
const connection = this.connectionForPeer(peerId);
|
|
86
108
|
const localStream = this.localMediaStream;
|
|
87
|
-
if (!connection || !localStream)
|
|
109
|
+
if (!connection || !localStream || !this.active || Boolean(connection.isClosed))
|
|
88
110
|
return;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
while (true) {
|
|
112
|
+
if (!this.active || this.localMediaStream !== localStream || this.connectionForPeer(peerId) !== connection || Boolean(connection.isClosed))
|
|
113
|
+
return;
|
|
114
|
+
const existing = this.sessions.get(peerId);
|
|
115
|
+
if (existing?.connection === connection)
|
|
116
|
+
return;
|
|
117
|
+
if (!existing)
|
|
118
|
+
break;
|
|
93
119
|
await this.closePeerSession(peerId, false);
|
|
120
|
+
// Another notification may have installed its session while removal awaited.
|
|
121
|
+
}
|
|
94
122
|
const session = {
|
|
95
123
|
peerId,
|
|
96
124
|
connection,
|
|
@@ -98,20 +126,30 @@ export class NetcodeVoiceController {
|
|
|
98
126
|
remoteStream: null,
|
|
99
127
|
stopListening: () => undefined,
|
|
100
128
|
};
|
|
129
|
+
this.sessions.set(peerId, session);
|
|
101
130
|
session.stopListening = connection.media.onTrack((event) => {
|
|
102
|
-
|
|
131
|
+
if (!this.active || this.sessions.get(peerId) !== session || event.track.kind !== 'audio')
|
|
132
|
+
return;
|
|
133
|
+
session.remoteStream = event.streams[0] ?? (typeof MediaStream !== 'undefined' ? new MediaStream([event.track]) : null);
|
|
103
134
|
if (session.remoteStream) {
|
|
104
135
|
this.emitter.emit('peer:stream', { peerId, stream: session.remoteStream });
|
|
105
136
|
}
|
|
106
137
|
this.emitter.emit('peers', this.listPeers());
|
|
107
138
|
});
|
|
108
|
-
|
|
139
|
+
// Publication can precede local microphone enable. Subscribe first, then
|
|
140
|
+
// project the media owner's existing receivers rather than losing that track.
|
|
141
|
+
const tracks = [...new Set(connection.media.receivers?.().map((receiver) => receiver.track)
|
|
142
|
+
.filter((track) => track.kind === 'audio' && track.readyState === 'live') ?? [])];
|
|
143
|
+
if (!session.remoteStream && tracks.length && typeof MediaStream !== 'undefined') {
|
|
144
|
+
session.remoteStream = new MediaStream(tracks);
|
|
145
|
+
this.emitter.emit('peer:stream', { peerId, stream: session.remoteStream });
|
|
146
|
+
}
|
|
109
147
|
this.emitter.emit('peers', this.listPeers());
|
|
110
148
|
try {
|
|
111
149
|
await Promise.resolve();
|
|
112
150
|
for (const track of localStream.getAudioTracks()) {
|
|
113
151
|
const sender = await connection.media.addTrack(track, { streams: [localStream] });
|
|
114
|
-
if (this.sessions.get(peerId) !== session || !this.active || connection.isClosed
|
|
152
|
+
if (this.sessions.get(peerId) !== session || !this.active || Boolean(connection.isClosed)) {
|
|
115
153
|
await connection.media.removeTrack(sender);
|
|
116
154
|
return;
|
|
117
155
|
}
|
|
@@ -128,17 +166,21 @@ export class NetcodeVoiceController {
|
|
|
128
166
|
throw error;
|
|
129
167
|
}
|
|
130
168
|
}
|
|
131
|
-
|
|
169
|
+
closePeerSession(peerId, emit = true) {
|
|
132
170
|
const session = this.sessions.get(peerId);
|
|
133
171
|
if (!session)
|
|
134
|
-
return;
|
|
172
|
+
return Promise.resolve();
|
|
135
173
|
this.sessions.delete(peerId);
|
|
136
174
|
session.stopListening();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
175
|
+
const closing = Promise.allSettled(session.senders.map((sender) => session.connection.media.removeTrack(sender))).then(() => {
|
|
176
|
+
if (emit) {
|
|
177
|
+
if (!this.sessions.has(peerId))
|
|
178
|
+
this.emitter.emit('peer:left', { peerId });
|
|
179
|
+
this.emitter.emit('peers', this.listPeers());
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
this.trackClosing(closing);
|
|
183
|
+
return closing;
|
|
142
184
|
}
|
|
143
185
|
trackClosing(operation) {
|
|
144
186
|
this.closingSessions.add(operation);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openrtc-netcode",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test:watch": "vitest"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"openrtc": "^3.0.
|
|
45
|
+
"openrtc": "^3.0.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^24.10.1",
|