werift 0.24.3 → 0.25.0-alpha.0

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.
Files changed (168) hide show
  1. package/README.md +116 -113
  2. package/lib/common/src/event.js +2 -1
  3. package/lib/common/src/event.js.map +1 -1
  4. package/lib/common/src/transport.d.ts +22 -2
  5. package/lib/common/src/transport.js +84 -26
  6. package/lib/common/src/transport.js.map +1 -1
  7. package/lib/ice/src/exceptions.d.ts +9 -0
  8. package/lib/ice/src/exceptions.js +23 -1
  9. package/lib/ice/src/exceptions.js.map +1 -1
  10. package/lib/ice/src/ice.d.ts +40 -0
  11. package/lib/ice/src/ice.js +194 -28
  12. package/lib/ice/src/ice.js.map +1 -1
  13. package/lib/ice/src/iceBase.d.ts +26 -0
  14. package/lib/ice/src/iceBase.js.map +1 -1
  15. package/lib/ice/src/stun/const.d.ts +7 -0
  16. package/lib/ice/src/stun/const.js +8 -0
  17. package/lib/ice/src/stun/const.js.map +1 -1
  18. package/lib/ice/src/stun/protocol.js +1 -4
  19. package/lib/ice/src/stun/protocol.js.map +1 -1
  20. package/lib/ice/src/stun/tcpProtocol.d.ts +7 -1
  21. package/lib/ice/src/stun/tcpProtocol.js +65 -14
  22. package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
  23. package/lib/ice/src/stun/transaction.d.ts +12 -1
  24. package/lib/ice/src/stun/transaction.js +37 -4
  25. package/lib/ice/src/stun/transaction.js.map +1 -1
  26. package/lib/ice/src/turn/protocol.d.ts +79 -22
  27. package/lib/ice/src/turn/protocol.js +282 -78
  28. package/lib/ice/src/turn/protocol.js.map +1 -1
  29. package/lib/ice/src/types/model.d.ts +8 -0
  30. package/lib/ice/src/types/model.js.map +1 -1
  31. package/lib/index.mjs +5646 -3451
  32. package/lib/nonstandard/index.mjs +287 -1004
  33. package/lib/polyfill/dom.mjs +23794 -0
  34. package/lib/polyfill/index.mjs +23794 -0
  35. package/lib/rtp/src/extra/container/mp4/container.js +12 -4
  36. package/lib/rtp/src/extra/container/mp4/container.js.map +1 -1
  37. package/lib/rtp/src/extra/processor/mp4.js +8 -3
  38. package/lib/rtp/src/extra/processor/mp4.js.map +1 -1
  39. package/lib/rtp/src/extra/processor/mp4Callback.js +3 -1
  40. package/lib/rtp/src/extra/processor/mp4Callback.js.map +1 -1
  41. package/lib/sctp/src/chunk.d.ts +5 -0
  42. package/lib/sctp/src/chunk.js +16 -11
  43. package/lib/sctp/src/chunk.js.map +1 -1
  44. package/lib/sctp/src/index.d.ts +3 -1
  45. package/lib/sctp/src/index.js +10 -1
  46. package/lib/sctp/src/index.js.map +1 -1
  47. package/lib/sctp/src/sctp.d.ts +12 -3
  48. package/lib/sctp/src/sctp.js +51 -18
  49. package/lib/sctp/src/sctp.js.map +1 -1
  50. package/lib/webrtc/src/const.js +2 -0
  51. package/lib/webrtc/src/const.js.map +1 -1
  52. package/lib/webrtc/src/errors.d.ts +9 -1
  53. package/lib/webrtc/src/errors.js +19 -0
  54. package/lib/webrtc/src/errors.js.map +1 -1
  55. package/lib/webrtc/src/media/codec.d.ts +5 -0
  56. package/lib/webrtc/src/media/codec.js +21 -0
  57. package/lib/webrtc/src/media/codec.js.map +1 -1
  58. package/lib/webrtc/src/media/codecCompatibility.d.ts +23 -0
  59. package/lib/webrtc/src/media/codecCompatibility.js +138 -0
  60. package/lib/webrtc/src/media/codecCompatibility.js.map +1 -0
  61. package/lib/webrtc/src/media/index.d.ts +1 -0
  62. package/lib/webrtc/src/media/index.js +1 -0
  63. package/lib/webrtc/src/media/index.js.map +1 -1
  64. package/lib/webrtc/src/media/router.d.ts +2 -0
  65. package/lib/webrtc/src/media/router.js +23 -3
  66. package/lib/webrtc/src/media/router.js.map +1 -1
  67. package/lib/webrtc/src/media/rtpReceiver.d.ts +12 -0
  68. package/lib/webrtc/src/media/rtpReceiver.js +35 -0
  69. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  70. package/lib/webrtc/src/media/rtpSender.d.ts +65 -4
  71. package/lib/webrtc/src/media/rtpSender.js +264 -35
  72. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  73. package/lib/webrtc/src/media/rtpTransceiver.d.ts +42 -0
  74. package/lib/webrtc/src/media/rtpTransceiver.js +157 -4
  75. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  76. package/lib/webrtc/src/media/track.d.ts +29 -1
  77. package/lib/webrtc/src/media/track.js +164 -5
  78. package/lib/webrtc/src/media/track.js.map +1 -1
  79. package/lib/webrtc/src/nonstandard/index.d.ts +0 -1
  80. package/lib/webrtc/src/nonstandard/index.js +0 -1
  81. package/lib/webrtc/src/nonstandard/index.js.map +1 -1
  82. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js +2 -1
  83. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js.map +1 -1
  84. package/lib/webrtc/src/nonstandard/userMedia.d.ts +1 -2
  85. package/lib/webrtc/src/nonstandard/userMedia.js +30 -14
  86. package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
  87. package/lib/webrtc/src/peerConnection.d.ts +127 -5
  88. package/lib/webrtc/src/peerConnection.js +629 -143
  89. package/lib/webrtc/src/peerConnection.js.map +1 -1
  90. package/lib/webrtc/src/polyfill/api.d.ts +14 -0
  91. package/lib/webrtc/src/polyfill/api.js +23 -0
  92. package/lib/webrtc/src/polyfill/api.js.map +1 -0
  93. package/lib/webrtc/src/polyfill/browserIdentity.d.ts +11 -0
  94. package/lib/webrtc/src/polyfill/browserIdentity.js +59 -0
  95. package/lib/webrtc/src/polyfill/browserIdentity.js.map +1 -0
  96. package/lib/webrtc/src/polyfill/dom.d.ts +11 -0
  97. package/lib/webrtc/src/polyfill/dom.js +18 -0
  98. package/lib/webrtc/src/polyfill/dom.js.map +1 -0
  99. package/lib/webrtc/src/polyfill/existingMediaDevices.d.ts +2 -0
  100. package/lib/webrtc/src/polyfill/existingMediaDevices.js +22 -0
  101. package/lib/webrtc/src/polyfill/existingMediaDevices.js.map +1 -0
  102. package/lib/webrtc/src/polyfill/globals.d.ts +28 -0
  103. package/lib/webrtc/src/polyfill/globals.js +3 -0
  104. package/lib/webrtc/src/polyfill/globals.js.map +1 -0
  105. package/lib/webrtc/src/polyfill/index.d.ts +2 -0
  106. package/lib/webrtc/src/polyfill/index.js +19 -0
  107. package/lib/webrtc/src/polyfill/index.js.map +1 -0
  108. package/lib/webrtc/src/polyfill/install.d.ts +13 -0
  109. package/lib/webrtc/src/polyfill/install.js +266 -0
  110. package/lib/webrtc/src/polyfill/install.js.map +1 -0
  111. package/lib/webrtc/src/polyfill/mediaDevices.d.ts +38 -0
  112. package/lib/webrtc/src/polyfill/mediaDevices.js +228 -0
  113. package/lib/webrtc/src/polyfill/mediaDevices.js.map +1 -0
  114. package/lib/webrtc/src/polyfill/mediaRegister.d.ts +51 -0
  115. package/lib/webrtc/src/polyfill/mediaRegister.js +14 -0
  116. package/lib/webrtc/src/polyfill/mediaRegister.js.map +1 -0
  117. package/lib/webrtc/src/polyfill/peerConnectionConfig.d.ts +3 -0
  118. package/lib/webrtc/src/polyfill/peerConnectionConfig.js +52 -0
  119. package/lib/webrtc/src/polyfill/peerConnectionConfig.js.map +1 -0
  120. package/lib/webrtc/src/polyfill/registers/callback.d.ts +16 -0
  121. package/lib/webrtc/src/polyfill/registers/callback.js +57 -0
  122. package/lib/webrtc/src/polyfill/registers/callback.js.map +1 -0
  123. package/lib/webrtc/src/polyfill/registers/empty.d.ts +2 -0
  124. package/lib/webrtc/src/polyfill/registers/empty.js +28 -0
  125. package/lib/webrtc/src/polyfill/registers/empty.js.map +1 -0
  126. package/lib/webrtc/src/polyfill/registers/mp4Webm.d.ts +36 -0
  127. package/lib/webrtc/src/polyfill/registers/mp4Webm.js +242 -0
  128. package/lib/webrtc/src/polyfill/registers/mp4Webm.js.map +1 -0
  129. package/lib/webrtc/src/polyfill/registers/rtpRtcp.d.ts +17 -0
  130. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js +143 -0
  131. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js.map +1 -0
  132. package/lib/webrtc/src/polyfill/rtcSessionDescription.d.ts +18 -0
  133. package/lib/webrtc/src/polyfill/rtcSessionDescription.js +36 -0
  134. package/lib/webrtc/src/polyfill/rtcSessionDescription.js.map +1 -0
  135. package/lib/webrtc/src/polyfill/rtpCodec.d.ts +17 -0
  136. package/lib/webrtc/src/polyfill/rtpCodec.js +93 -0
  137. package/lib/webrtc/src/polyfill/rtpCodec.js.map +1 -0
  138. package/lib/webrtc/src/polyfill/selectSettings.d.ts +31 -0
  139. package/lib/webrtc/src/polyfill/selectSettings.js +156 -0
  140. package/lib/webrtc/src/polyfill/selectSettings.js.map +1 -0
  141. package/lib/webrtc/src/polyfill/sourceIo.d.ts +21 -0
  142. package/lib/webrtc/src/polyfill/sourceIo.js +298 -0
  143. package/lib/webrtc/src/polyfill/sourceIo.js.map +1 -0
  144. package/lib/webrtc/src/polyfill/trackStop.d.ts +4 -0
  145. package/lib/webrtc/src/polyfill/trackStop.js +16 -0
  146. package/lib/webrtc/src/polyfill/trackStop.js.map +1 -0
  147. package/lib/webrtc/src/sctpManager.d.ts +2 -1
  148. package/lib/webrtc/src/sctpManager.js +5 -2
  149. package/lib/webrtc/src/sctpManager.js.map +1 -1
  150. package/lib/webrtc/src/sdp.d.ts +1 -1
  151. package/lib/webrtc/src/sdp.js +5 -6
  152. package/lib/webrtc/src/sdp.js.map +1 -1
  153. package/lib/webrtc/src/sdpManager.d.ts +38 -1
  154. package/lib/webrtc/src/sdpManager.js +236 -30
  155. package/lib/webrtc/src/sdpManager.js.map +1 -1
  156. package/lib/webrtc/src/secureTransportManager.d.ts +43 -12
  157. package/lib/webrtc/src/secureTransportManager.js +123 -47
  158. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  159. package/lib/webrtc/src/transceiverManager.d.ts +91 -6
  160. package/lib/webrtc/src/transceiverManager.js +477 -62
  161. package/lib/webrtc/src/transceiverManager.js.map +1 -1
  162. package/lib/webrtc/src/transport/ice.d.ts +8 -0
  163. package/lib/webrtc/src/transport/ice.js +17 -1
  164. package/lib/webrtc/src/transport/ice.js.map +1 -1
  165. package/lib/webrtc/src/transport/sctp.d.ts +3 -2
  166. package/lib/webrtc/src/transport/sctp.js +8 -2
  167. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  168. package/package.json +14 -3
@@ -5,10 +5,23 @@ const const_1 = require("./const");
5
5
  const errors_1 = require("./errors");
6
6
  const common_1 = require("./imports/common");
7
7
  const media_1 = require("./media");
8
- const peerConnection_1 = require("./peerConnection");
8
+ const codecCompatibility_1 = require("./media/codecCompatibility");
9
+ const track_1 = require("./media/track");
9
10
  const sdp_1 = require("./sdp");
10
11
  const utils_1 = require("./utils");
11
12
  const log = (0, common_1.debug)("werift:packages/webrtc/src/media/rtpTransceiverManager.ts");
13
+ function simulcastFromSendEncodings(encodings) {
14
+ if (!encodings || encodings.length < 2) {
15
+ return undefined;
16
+ }
17
+ const rids = encodings
18
+ .map((encoding) => encoding.rid)
19
+ .filter((rid) => typeof rid === "string" && rid.length > 0);
20
+ if (rids.length < 2) {
21
+ return undefined;
22
+ }
23
+ return rids.map((rid) => ({ rid, direction: "send" }));
24
+ }
12
25
  class TransceiverManager {
13
26
  constructor(cname, config, router) {
14
27
  Object.defineProperty(this, "cname", {
@@ -35,6 +48,18 @@ class TransceiverManager {
35
48
  writable: true,
36
49
  value: []
37
50
  });
51
+ Object.defineProperty(this, "watched", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: new WeakSet()
56
+ });
57
+ Object.defineProperty(this, "remoteOfferSnapshot", {
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ value: void 0
62
+ });
38
63
  Object.defineProperty(this, "onTransceiverAdded", {
39
64
  enumerable: true,
40
65
  configurable: true,
@@ -73,33 +98,121 @@ class TransceiverManager {
73
98
  return this.transceivers.find((transceiver) => transceiver.mLineIndex === index);
74
99
  }
75
100
  pushTransceiver(t) {
101
+ this.watchTransceiver(t);
76
102
  this.transceivers.push(t);
77
103
  }
78
104
  replaceTransceiver(t, index) {
105
+ this.watchTransceiver(t);
79
106
  this.transceivers[index] = t;
80
107
  }
81
- addTransceiver(trackOrKind, dtlsTransport, options = {}) {
108
+ /**m-line から外れた transceiver の購読を解除する */
109
+ unwatchTransceiver(t) {
110
+ this.watched.delete(t);
111
+ t.onRelease.allUnsubscribe();
112
+ t.onStopRequested.allUnsubscribe();
113
+ }
114
+ watchTransceiver(t) {
115
+ if (this.watched.has(t)) {
116
+ return;
117
+ }
118
+ this.watched.add(t);
119
+ t.onRelease.subscribe(() => {
120
+ this.router.unregisterTransceiver(t);
121
+ });
122
+ t.onStopRequested.subscribe(() => {
123
+ // 未関連付けの stop は m-line を作らないので交渉不要
124
+ if (!t.stopped) {
125
+ this.onNegotiationNeeded.execute();
126
+ }
127
+ });
128
+ }
129
+ /**
130
+ * 拒否 / 停止の交渉が確定した port 0 の位置のうち、同じ kind で
131
+ * まだ他の transceiver が予約していないものを返す。
132
+ * stopping (未交渉) の位置は先取りしない。
133
+ */
134
+ findReusableTransceiver(kind) {
135
+ return this.transceivers.find((t) => t.stopped &&
136
+ t.kind === kind &&
137
+ t.mLineIndex != undefined &&
138
+ !this.transceivers.some((other) => other !== t && other.mLineIndex === t.mLineIndex));
139
+ }
140
+ /**旧 transceiver を m-line から外し、同じ位置に新しい transceiver を置く */
141
+ takeOverMLine(oldTransceiver, newTransceiver) {
142
+ const index = this.transceivers.indexOf(oldTransceiver);
143
+ newTransceiver.mLineIndex = oldTransceiver.mLineIndex;
144
+ oldTransceiver.mid = null;
145
+ oldTransceiver.mLineIndex = undefined;
146
+ this.unwatchTransceiver(oldTransceiver);
147
+ this.replaceTransceiver(newTransceiver, index);
148
+ }
149
+ /**
150
+ * 既存の未関連付け transceiver を remote m-line の位置に関連付ける。
151
+ * 同じ位置に停止済みの旧 transceiver があれば m-line から外し、配列上でも置き換える。
152
+ * (旧 transceiver が位置を持ったままだと MID の割り当て先が重複する)
153
+ */
154
+ associateMLine(transceiver, mLineIndex) {
155
+ const previous = this.transceivers.find((t) => t !== transceiver && t.stopped && t.mLineIndex === mLineIndex);
156
+ if (!previous) {
157
+ return;
158
+ }
159
+ this.transceivers.splice(this.transceivers.indexOf(transceiver), 1);
160
+ this.takeOverMLine(previous, transceiver);
161
+ }
162
+ /**
163
+ * remote offer が定義した位置のうち、関連付けられなかった未交渉 transceiver の予約を解除する。
164
+ * (remote が予約位置を別 kind や別 transceiver で再利用した場合、次の offer で末尾に追加させる)
165
+ */
166
+ releaseUnassociatedReservations(associated, mLineCount) {
167
+ for (const t of this.transceivers) {
168
+ if (!associated.has(t) &&
169
+ t.mid == null &&
170
+ t.mLineIndex != undefined &&
171
+ t.mLineIndex < mLineCount) {
172
+ t.mLineIndex = undefined;
173
+ }
174
+ }
175
+ }
176
+ addTransceiver(trackOrKind, dtlsTransport, options = {}, { remoteMLineIndex, } = {}) {
82
177
  const kind = typeof trackOrKind === "string" ? trackOrKind : trackOrKind.kind;
83
178
  const direction = options.direction || "sendrecv";
84
- const sender = new media_1.RTCRtpSender(trackOrKind);
179
+ if (typeof trackOrKind !== "string") {
180
+ (0, track_1.captureTrackSourceCodecs)(trackOrKind);
181
+ (0, codecCompatibility_1.assertCodecsSupported)({
182
+ kind,
183
+ configured: this.filterCodecsByDirection(kind, direction),
184
+ source: (0, track_1.getTrackSourceCodecs)(trackOrKind),
185
+ preferences: undefined,
186
+ });
187
+ }
188
+ const sender = new media_1.RTCRtpSender(trackOrKind, {
189
+ pendingRtp: this.config.pendingRtp,
190
+ });
85
191
  const receiver = new media_1.RTCRtpReceiver(this.config, kind, sender.ssrc);
86
192
  const newTransceiver = new media_1.RTCRtpTransceiver(kind, dtlsTransport, receiver, sender, direction);
87
- newTransceiver.options = options;
193
+ newTransceiver.onCodecPreferencesChanged.subscribe(() => {
194
+ this.onNegotiationNeeded.execute();
195
+ });
196
+ newTransceiver.options = {
197
+ ...options,
198
+ simulcast: options.simulcast ?? simulcastFromSendEncodings(options.sendEncodings),
199
+ };
88
200
  newTransceiver.sender.setStreams(options.streams ?? []);
89
201
  newTransceiver.sender.setSendEncodings((options.sendEncodings ?? []).map((encoding) => ({ ...encoding })));
90
202
  this.router.registerRtpSender(newTransceiver.sender);
91
- // reuse inactive
92
- const inactiveTransceiverIndex = this.transceivers.findIndex((t) => t.currentDirection === "inactive" && !t.usedForSender);
93
- const inactiveTransceiver = this.transceivers.find((t) => t.currentDirection === "inactive" && !t.usedForSender);
94
- if (inactiveTransceiverIndex > -1 && inactiveTransceiver) {
95
- this.replaceTransceiver(newTransceiver, inactiveTransceiverIndex);
96
- newTransceiver.mLineIndex = inactiveTransceiver.mLineIndex;
97
- newTransceiver.mid = inactiveTransceiver.mid;
98
- inactiveTransceiver.setCurrentDirection(undefined);
203
+ // 旧 transceiver は復活させず、MID は次の offer で新しく割り当てる
204
+ const reusable = remoteMLineIndex == undefined
205
+ ? this.findReusableTransceiver(kind)
206
+ : this.transceivers.find((t) => t.stopped && t.mLineIndex === remoteMLineIndex);
207
+ if (reusable) {
208
+ this.takeOverMLine(reusable, newTransceiver);
99
209
  }
100
210
  else {
101
211
  this.pushTransceiver(newTransceiver);
102
212
  }
213
+ if (remoteMLineIndex != undefined) {
214
+ this.remoteOfferSnapshot?.remoteCreated.add(newTransceiver);
215
+ }
103
216
  this.onTransceiverAdded.execute(newTransceiver);
104
217
  return newTransceiver;
105
218
  }
@@ -107,10 +220,16 @@ class TransceiverManager {
107
220
  if (this.getSenders().find((sender) => sender.track?.uuid === track.uuid)) {
108
221
  throw (0, errors_1.createWebRtcDomException)("InvalidAccessError", "Track already added");
109
222
  }
110
- const emptyTrackSenderTransceiver = this.transceivers.find((t) => t.sender.track == undefined &&
223
+ const reusableForTrack = (t) => t.sender.track == undefined &&
111
224
  t.kind === track.kind &&
112
- const_1.SenderDirections.includes(t.direction) === true);
225
+ !t.usedForSender &&
226
+ !t.stopping &&
227
+ !t.stopped &&
228
+ !t.rejected &&
229
+ !t.pendingRejection;
230
+ const emptyTrackSenderTransceiver = this.transceivers.find((t) => reusableForTrack(t) && const_1.SenderDirections.includes(t.direction));
113
231
  if (emptyTrackSenderTransceiver) {
232
+ this.validateTrackForTransceiver(track, emptyTrackSenderTransceiver);
114
233
  const sender = emptyTrackSenderTransceiver.sender;
115
234
  sender.setStreams(streams);
116
235
  sender.registerTrack(track);
@@ -118,13 +237,17 @@ class TransceiverManager {
118
237
  ...emptyTrackSenderTransceiver.options,
119
238
  streams,
120
239
  };
240
+ emptyTrackSenderTransceiver.codecs = [];
121
241
  return emptyTrackSenderTransceiver;
122
242
  }
123
- const notSendTransceiver = this.transceivers.find((t) => t.sender.track == undefined &&
124
- t.kind === track.kind &&
125
- const_1.SenderDirections.includes(t.direction) === false &&
126
- !t.usedForSender);
243
+ const notSendTransceiver = this.transceivers.find((t) => reusableForTrack(t) && !const_1.SenderDirections.includes(t.direction));
127
244
  if (notSendTransceiver) {
245
+ const nextDirection = notSendTransceiver.direction === "recvonly"
246
+ ? "sendrecv"
247
+ : notSendTransceiver.direction === "inactive"
248
+ ? "sendonly"
249
+ : notSendTransceiver.direction;
250
+ this.validateTrackForTransceiver(track, notSendTransceiver, nextDirection);
128
251
  const sender = notSendTransceiver.sender;
129
252
  sender.setStreams(streams);
130
253
  sender.registerTrack(track);
@@ -140,6 +263,7 @@ class TransceiverManager {
140
263
  notSendTransceiver.setDirection("sendonly");
141
264
  break;
142
265
  }
266
+ notSendTransceiver.codecs = [];
143
267
  return notSendTransceiver;
144
268
  }
145
269
  else {
@@ -150,6 +274,10 @@ class TransceiverManager {
150
274
  return transceiver;
151
275
  }
152
276
  }
277
+ /**
278
+ * sender から track を外す。
279
+ * @returns 交渉が必要な変更をした場合 true (呼び出し側が negotiationneeded を要求する)
280
+ */
153
281
  removeTrack(sender) {
154
282
  if (!this.getSenders().find(({ ssrc }) => sender.ssrc === ssrc)) {
155
283
  throw (0, errors_1.createWebRtcDomException)("InvalidAccessError", "Sender does not exist");
@@ -158,36 +286,46 @@ class TransceiverManager {
158
286
  if (!transceiver)
159
287
  throw new Error("No matching transceiver found");
160
288
  if (transceiver.stopping || transceiver.stopped) {
161
- return;
289
+ return false;
162
290
  }
163
- sender.stop();
164
- if (["recvonly", "inactive"].includes(transceiver.currentDirection ?? "")) {
165
- this.onNegotiationNeeded.execute();
166
- return;
291
+ if (sender.track == undefined) {
292
+ return false;
167
293
  }
294
+ // sender 自体は止めず track だけ外し、同じ sender で送信を再開できるようにする
295
+ sender.detachTrack();
168
296
  if (transceiver.direction === "sendrecv") {
169
297
  transceiver.setDirection("recvonly");
170
298
  }
171
- else if (transceiver.direction === "sendonly" ||
172
- transceiver.direction === "recvonly") {
299
+ else if (transceiver.direction === "sendonly") {
173
300
  transceiver.setDirection("inactive");
174
301
  }
302
+ return true;
175
303
  }
176
304
  assignTransceiverCodecs(transceiver) {
177
- const codecs = this.config.codecs[transceiver.kind].filter((codecCandidate) => {
305
+ const configured = this.filterCodecsByDirection(transceiver.kind, transceiver.direction);
306
+ transceiver.codecs = (0, codecCompatibility_1.assertCodecsSupported)({
307
+ kind: transceiver.kind,
308
+ configured,
309
+ source: (0, track_1.getTrackSourceCodecs)(transceiver.sender.track),
310
+ preferences: transceiver.codecPreferences,
311
+ });
312
+ transceiver.codecPreferencesNeedResolution = false;
313
+ }
314
+ filterCodecsByDirection(kind, direction) {
315
+ return this.config.codecs[kind].filter((codecCandidate) => {
178
316
  switch (codecCandidate.direction) {
179
317
  case "recvonly": {
180
- if (const_1.ReceiverDirection.includes(transceiver.direction))
318
+ if (const_1.ReceiverDirection.includes(direction))
181
319
  return true;
182
320
  return false;
183
321
  }
184
322
  case "sendonly": {
185
- if (const_1.SenderDirections.includes(transceiver.direction))
323
+ if (const_1.SenderDirections.includes(direction))
186
324
  return true;
187
325
  return false;
188
326
  }
189
327
  case "sendrecv": {
190
- if ([media_1.Sendrecv, media_1.Recvonly, media_1.Sendonly].includes(transceiver.direction))
328
+ if ([media_1.Sendrecv, media_1.Recvonly, media_1.Sendonly].includes(direction))
191
329
  return true;
192
330
  return false;
193
331
  }
@@ -198,7 +336,15 @@ class TransceiverManager {
198
336
  return false;
199
337
  }
200
338
  });
201
- transceiver.codecs = codecs;
339
+ }
340
+ validateTrackForTransceiver(track, transceiver, direction = transceiver.direction) {
341
+ (0, track_1.captureTrackSourceCodecs)(track);
342
+ (0, codecCompatibility_1.assertCodecsSupported)({
343
+ kind: track.kind,
344
+ configured: this.filterCodecsByDirection(track.kind, direction),
345
+ source: (0, track_1.getTrackSourceCodecs)(track),
346
+ preferences: transceiver.codecPreferences,
347
+ });
202
348
  }
203
349
  getLocalRtpParams(transceiver) {
204
350
  if (transceiver.mid == undefined)
@@ -235,33 +381,212 @@ class TransceiverManager {
235
381
  };
236
382
  return receiveParameters;
237
383
  }
238
- setRemoteRTP(transceiver, remoteMedia, type, mLineIndex) {
239
- if (!transceiver.mid) {
240
- transceiver.mid = remoteMedia.rtp.muxId ?? null;
384
+ /**
385
+ * remote offer と local (source constraint + preferences) から
386
+ * answer 用の codec を再解決し、sender/receiver の codec 状態も
387
+ * 新しい negotiated codec に同期する。
388
+ * setCodecPreferences() による無効化後に createAnswer() から呼ばれる。
389
+ * direction / headerExtensions / onTrack は setRemoteDescription() 時の
390
+ * まま変えない (track イベントの重複発火を避ける)。
391
+ */
392
+ resyncAnswerCodecs(transceiver, remoteMedia) {
393
+ this.refreshAnswerCodecs(transceiver, remoteMedia);
394
+ const localParams = this.getLocalRtpParams(transceiver);
395
+ transceiver.sender.prepareSend(localParams);
396
+ if (["recvonly", "sendrecv"].includes(transceiver.direction)) {
397
+ const remoteParams = this.getRemoteRtpParams(remoteMedia, transceiver);
398
+ for (const param of remoteMedia.simulcastParameters) {
399
+ this.router.registerRtpReceiverByRid(transceiver, param, remoteParams);
400
+ }
401
+ transceiver.receiver.resyncCodecs(remoteParams, remoteMedia.ssrc[0]?.ssrc);
402
+ this.router.registerRtpReceiverBySsrc(transceiver, remoteParams);
241
403
  }
242
- transceiver.mLineIndex = mLineIndex;
404
+ }
405
+ /**
406
+ * remote offer と local (source constraint + preferences) から
407
+ * answer 用の codec だけを再解決する (sender/receiver の同期なし)。
408
+ * setRemoteRTP() の交渉部分として使う。
409
+ */
410
+ resolveRemoteCodecSet(localCodecs, remoteMedia, order, source) {
243
411
  // # negotiate codecs
244
- transceiver.codecs = remoteMedia.rtp.codecs.filter((remoteCodec) => {
245
- const localCodecs = this.config.codecs[remoteMedia.kind] || [];
246
- const existCodec = (0, peerConnection_1.findCodecByMimeType)(localCodecs, remoteCodec);
247
- if (!existCodec) {
248
- return false;
412
+ const codecName = (codec) => codec.name.toLowerCase();
413
+ const redPayloadTypes = (codec) => (codec.parameters ?? "")
414
+ .split("/")
415
+ .map(Number)
416
+ .filter((payloadType) => !Number.isNaN(payloadType));
417
+ const remoteByLocal = new Map();
418
+ const usedRemote = new Set();
419
+ for (const localCodec of localCodecs) {
420
+ if (["red", "rtx"].includes(codecName(localCodec)))
421
+ continue;
422
+ const remoteCodec = remoteMedia.rtp.codecs.find((codec) => !usedRemote.has(codec) &&
423
+ codec.mimeType.toLowerCase() === localCodec.mimeType.toLowerCase() &&
424
+ (source == undefined ||
425
+ source.some((sourceCodec) => (0, codecCompatibility_1.isCodecCompatible)(sourceCodec, codec))));
426
+ if (remoteCodec) {
427
+ remoteByLocal.set(localCodec, remoteCodec);
428
+ usedRemote.add(remoteCodec);
429
+ }
430
+ }
431
+ const negotiated = localCodecs.flatMap((localCodec) => {
432
+ if (codecName(localCodec) === "red") {
433
+ const remotePrimaryPayloadTypes = redPayloadTypes(localCodec).flatMap((payloadType) => {
434
+ const localPrimary = localCodecs.find((codec) => codec.payloadType === payloadType);
435
+ const remotePrimary = localPrimary
436
+ ? remoteByLocal.get(localPrimary)
437
+ : undefined;
438
+ return remotePrimary ? [remotePrimary.payloadType] : [];
439
+ });
440
+ const remoteRed = remoteMedia.rtp.codecs.find((codec) => {
441
+ if (codecName(codec) !== "red" || usedRemote.has(codec))
442
+ return false;
443
+ const referenced = redPayloadTypes(codec);
444
+ return (referenced.length === remotePrimaryPayloadTypes.length &&
445
+ referenced.every((payloadType, index) => payloadType === remotePrimaryPayloadTypes[index]));
446
+ });
447
+ if (!remoteRed)
448
+ return [];
449
+ usedRemote.add(remoteRed);
450
+ return [remoteRed];
451
+ }
452
+ if (codecName(localCodec) !== "rtx") {
453
+ const remoteCodec = remoteByLocal.get(localCodec);
454
+ return remoteCodec ? [remoteCodec] : [];
249
455
  }
250
- if (existCodec?.name.toLowerCase() === "rtx") {
251
- const params = (0, sdp_1.codecParametersFromString)(existCodec.parameters ?? "");
252
- const pt = params["apt"];
253
- const origin = remoteMedia.rtp.codecs.find((c) => c.payloadType === pt);
254
- if (!origin) {
456
+ const localApt = (0, sdp_1.codecParametersFromString)(localCodec.parameters ?? "").apt;
457
+ const localPrimary = localCodecs.find((codec) => codec.payloadType === localApt);
458
+ if (!localPrimary)
459
+ return [];
460
+ const remotePrimary = remoteByLocal.get(localPrimary);
461
+ if (!remotePrimary)
462
+ return [];
463
+ const remoteRtx = remoteMedia.rtp.codecs.find((codec) => {
464
+ if (codecName(codec) !== "rtx" || usedRemote.has(codec)) {
255
465
  return false;
256
466
  }
257
- return !!(0, peerConnection_1.findCodecByMimeType)(localCodecs, origin);
258
- }
259
- return true;
467
+ return ((0, sdp_1.codecParametersFromString)(codec.parameters ?? "").apt ===
468
+ remotePrimary.payloadType);
469
+ });
470
+ if (!remoteRtx)
471
+ return [];
472
+ usedRemote.add(remoteRtx);
473
+ return [remoteRtx];
474
+ });
475
+ if (order === "remote") {
476
+ const remoteOrder = new Map(remoteMedia.rtp.codecs.map((codec, index) => [codec, index]));
477
+ negotiated.sort((left, right) => remoteOrder.get(left) - remoteOrder.get(right));
478
+ }
479
+ if (negotiated.length === 0) {
480
+ throw (0, errors_1.createWebRtcDomException)("NotSupportedError", "No compatible codec remains after remote negotiation.");
481
+ }
482
+ return negotiated;
483
+ }
484
+ refreshAnswerCodecs(transceiver, remoteMedia) {
485
+ const configured = this.config.codecs[remoteMedia.kind] || [];
486
+ const source = (0, track_1.getTrackSourceCodecs)(transceiver.sender.track);
487
+ (0, codecCompatibility_1.assertCodecsSupported)({
488
+ kind: remoteMedia.kind,
489
+ configured,
490
+ source,
491
+ preferences: transceiver.codecPreferences,
260
492
  });
493
+ const localCodecs = (0, codecCompatibility_1.resolveCodecs)(configured, source, transceiver.codecPreferences);
494
+ transceiver.codecs = this.resolveRemoteCodecSet(localCodecs, remoteMedia, "local", source);
495
+ transceiver.codecPreferencesNeedResolution = false;
261
496
  log("negotiated codecs", transceiver.codecs);
262
- if (transceiver.codecs.length === 0) {
263
- throw new Error("negotiate codecs failed.");
497
+ }
498
+ /**
499
+ * remote SDP の全 audio/video m-line の codec を副作用なしで解決する。
500
+ * transceiver の対応付けは本適用と同じ resolver を使い、検証と適用の判定をずらさない。
501
+ * - port 0、停止済み / 停止中の m-line は codec 解決をしない (空)
502
+ * - local capability と MIME が 1 つも一致しない m-line は空とし、
503
+ * offer は setRemoteRTP() で拒否 (port 0 answer)、answer/pranswer は
504
+ * SDPManager が InvalidAccessError とする (Issue #705)
505
+ * - source constraint / preference / pending offer との不一致は NotSupportedError
506
+ */
507
+ planRemoteRtpCodecs(remoteSdp, findTransceiver) {
508
+ const plan = new Map();
509
+ const isOffer = remoteSdp.type === "offer";
510
+ for (const [index, remoteMedia] of remoteSdp.media.entries()) {
511
+ if (!["audio", "video"].includes(remoteMedia.kind))
512
+ continue;
513
+ const transceiver = findTransceiver(remoteMedia, index);
514
+ if (remoteMedia.port === 0 ||
515
+ transceiver?.stopped ||
516
+ transceiver?.stopping) {
517
+ plan.set(index, []);
518
+ continue;
519
+ }
520
+ const source = (0, track_1.getTrackSourceCodecs)(transceiver?.sender.track);
521
+ const configured = this.config.codecs[remoteMedia.kind] || [];
522
+ const localCodecs = isOffer
523
+ ? configured
524
+ : transceiver?.pendingLocalOfferCodecs || [];
525
+ if (!(0, codecCompatibility_1.hasCommonPrimaryMimeType)(localCodecs, remoteMedia.rtp.codecs)) {
526
+ plan.set(index, []);
527
+ continue;
528
+ }
529
+ if (isOffer) {
530
+ (0, codecCompatibility_1.assertCodecsSupported)({
531
+ kind: remoteMedia.kind,
532
+ configured,
533
+ source,
534
+ preferences: transceiver?.codecPreferences,
535
+ });
536
+ }
537
+ const negotiated = this.resolveRemoteCodecSet(isOffer
538
+ ? (0, codecCompatibility_1.resolveCodecs)(configured, source, transceiver?.codecPreferences)
539
+ : localCodecs, remoteMedia, isOffer ? "local" : "remote", source);
540
+ if (source != undefined) {
541
+ (0, codecCompatibility_1.assertCodecsSupported)({
542
+ kind: remoteMedia.kind,
543
+ configured: negotiated,
544
+ source,
545
+ preferences: undefined,
546
+ });
547
+ }
548
+ plan.set(index, negotiated);
549
+ }
550
+ return plan;
551
+ }
552
+ /**
553
+ * remote m-line を transceiver に適用する。
554
+ * codecs は planRemoteRtpCodecs() で検証済みの解決結果を渡す。
555
+ * codec が空、または remote port 0 の m-line は拒否として扱い、
556
+ * sender/receiver 準備・router 登録・onTrack・TWCC を行わない。
557
+ * @returns 受け入れた (RTP を流す) 場合 true
558
+ */
559
+ setRemoteRTP(transceiver, remoteMedia, type, mLineIndex, codecs) {
560
+ if (!transceiver.mid) {
561
+ transceiver.mid = remoteMedia.rtp.muxId ?? null;
562
+ }
563
+ transceiver.mLineIndex = mLineIndex;
564
+ if (type === "answer")
565
+ transceiver.pendingLocalOfferCodecs = undefined;
566
+ if (transceiver.stopped) {
567
+ // 確定済みの停止 / 拒否 m-line は復活させない
568
+ return false;
569
+ }
570
+ if (transceiver.stopping) {
571
+ // app の stop() は自分の offer で交渉する。port 0 の answer で停止を確定する
572
+ if (type === "answer" && remoteMedia.port === 0) {
573
+ transceiver.commitStopped({ rejected: false });
574
+ }
575
+ return false;
576
+ }
577
+ log("negotiated codecs", codecs);
578
+ if (remoteMedia.port === 0 || codecs.length === 0) {
579
+ if (type === "answer") {
580
+ transceiver.commitStopped({ rejected: true });
581
+ }
582
+ else {
583
+ // answer が確定するまで既存の RTP pipeline / track は維持する
584
+ transceiver.pendingRejection = true;
585
+ }
586
+ return false;
264
587
  }
588
+ transceiver.pendingRejection = false;
589
+ transceiver.codecs = codecs;
265
590
  transceiver.headerExtensions = remoteMedia.rtp.headerExtensions.filter((extension) => (this.config.headerExtensions[remoteMedia.kind] ||
266
591
  []).find((v) => v.uri === extension.uri));
267
592
  // # configure direction
@@ -281,12 +606,12 @@ class TransceiverManager {
281
606
  for (const param of remoteMedia.simulcastParameters) {
282
607
  this.router.registerRtpReceiverByRid(transceiver, param, remotePrams);
283
608
  }
284
- transceiver.receiver.prepareReceive(remotePrams);
609
+ // codec map / RTX / TWCC を negotiated codec に合わせて再構築する
610
+ transceiver.receiver.resyncCodecs(remotePrams, remoteMedia.ssrc[0]?.ssrc);
285
611
  // register ssrc receiver
286
612
  this.router.registerRtpReceiverBySsrc(transceiver, remotePrams);
287
613
  }
288
- if (remoteMedia.port !== 0 &&
289
- ["sendonly", "sendrecv"].includes(mediaDirection)) {
614
+ if (["sendonly", "sendrecv"].includes(mediaDirection)) {
290
615
  const remoteStreamIds = [
291
616
  ...new Set(remoteMedia.msids.map((msid) => msid.split(" ")[0])),
292
617
  ];
@@ -294,18 +619,107 @@ class TransceiverManager {
294
619
  transceiver.receiver.remoteStreamId = remoteStreamIds[0];
295
620
  transceiver.receiver.remoteStreamIds = remoteStreamIds;
296
621
  transceiver.receiver.remoteTrackId = remoteTrackId;
297
- this.onTrack.execute({
298
- track: transceiver.receiver.track,
299
- transceiver,
300
- streams: remoteStreamIds.map((id) => new media_1.MediaStream({
301
- id,
302
- tracks: [transceiver.receiver.track],
303
- })),
304
- });
622
+ // re-offer / re-answer で同じ受信を重複通知しない
623
+ if (!transceiver.firedReceiving) {
624
+ transceiver.firedReceiving = true;
625
+ this.onTrack.execute({
626
+ track: transceiver.receiver.track,
627
+ transceiver,
628
+ streams: remoteStreamIds.map((id) => new media_1.MediaStream({
629
+ id,
630
+ tracks: [transceiver.receiver.track],
631
+ })),
632
+ });
633
+ }
634
+ }
635
+ else {
636
+ transceiver.firedReceiving = false;
637
+ }
638
+ return true;
639
+ }
640
+ /**
641
+ * remote offer 適用前の transceiver 対応を保存する。
642
+ * 同じ offer/answer 交換中に複数回呼ばれても最初の状態を保持する。
643
+ */
644
+ beginRemoteOffer() {
645
+ if (this.remoteOfferSnapshot) {
646
+ return;
647
+ }
648
+ this.remoteOfferSnapshot = {
649
+ transceivers: [...this.transceivers],
650
+ remoteCreated: new Set(),
651
+ states: new Map(this.transceivers.map((t) => [
652
+ t,
653
+ {
654
+ mid: t.mid,
655
+ mLineIndex: t.mLineIndex,
656
+ pendingRejection: t.pendingRejection,
657
+ firedReceiving: t.firedReceiving,
658
+ },
659
+ ])),
660
+ };
661
+ }
662
+ /**
663
+ * local answer の確定で、拒否予定の m-line を停止・解放する。
664
+ * remote SDP 起因の停止なので negotiationneeded は要求しない。
665
+ */
666
+ commitRemoteOffer() {
667
+ this.remoteOfferSnapshot = undefined;
668
+ for (const transceiver of this.transceivers) {
669
+ if (transceiver.pendingRejection) {
670
+ transceiver.commitStopped({ rejected: true });
671
+ }
672
+ }
673
+ }
674
+ /**remote offer の rollback で transceiver 対応を元に戻す */
675
+ rollbackRemoteOffer() {
676
+ const snapshot = this.remoteOfferSnapshot;
677
+ this.remoteOfferSnapshot = undefined;
678
+ if (!snapshot) {
679
+ return;
680
+ }
681
+ for (const transceiver of this.transceivers) {
682
+ if (snapshot.states.has(transceiver)) {
683
+ continue;
684
+ }
685
+ // app が追加した transceiver と、remote offer が作ったが app が addTrack で使い始めた
686
+ // transceiver は残し、関連付けだけ外す (track の有無ではなく作成元で判定する)
687
+ if (!snapshot.remoteCreated.has(transceiver) ||
688
+ transceiver.sender.track) {
689
+ transceiver.mid = null;
690
+ transceiver.mLineIndex = undefined;
691
+ snapshot.transceivers.push(transceiver);
692
+ continue;
693
+ }
694
+ // remote offer が作った transceiver は破棄する
695
+ transceiver.forceStop();
696
+ this.unwatchTransceiver(transceiver);
305
697
  }
306
- if (remoteMedia.ssrc[0]?.ssrc) {
307
- transceiver.receiver.setupTWCC(remoteMedia.ssrc[0].ssrc);
698
+ this.transceivers.splice(0, this.transceivers.length, ...snapshot.transceivers);
699
+ snapshot.transceivers.forEach((t) => this.watchTransceiver(t));
700
+ for (const [transceiver, state] of snapshot.states) {
701
+ transceiver.mid = state.mid;
702
+ transceiver.mLineIndex = state.mLineIndex;
703
+ transceiver.pendingRejection = state.pendingRejection;
704
+ transceiver.firedReceiving = state.firedReceiving;
705
+ }
706
+ }
707
+ /**
708
+ * answer 確定後に、stopping のまま交渉対象になり得ない transceiver の停止を確定する。
709
+ * (MID が確定済み local description の非ゼロ m-line にない = offer から外れる)
710
+ * @param negotiatedMids 確定済み local description の非ゼロ port の MID
711
+ * @returns 次の自分の offer で port 0 を交渉すべき transceiver があるか
712
+ */
713
+ settleStoppingTransceivers(negotiatedMids) {
714
+ for (const t of this.transceivers) {
715
+ if (!t.stopping || t.stopped) {
716
+ continue;
717
+ }
718
+ if (t.mid == undefined || !negotiatedMids.has(t.mid)) {
719
+ t.commitStopped({ rejected: false });
720
+ }
308
721
  }
722
+ return this.transceivers.some((t) => t.stopping && !t.stopped);
309
723
  }
310
724
  collectStats(timestamp) {
311
725
  const stats = [];
@@ -344,6 +758,7 @@ class TransceiverManager {
344
758
  close() {
345
759
  for (const transceiver of this.transceivers) {
346
760
  transceiver.forceStop();
761
+ this.unwatchTransceiver(transceiver);
347
762
  }
348
763
  this.onTransceiverAdded.allUnsubscribe();
349
764
  this.onRemoteTransceiverAdded.allUnsubscribe();