werift 0.17.0 → 0.17.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.
Files changed (108) hide show
  1. package/lib/ice/src/ice.d.ts +10 -1
  2. package/lib/ice/src/ice.js +15 -3
  3. package/lib/ice/src/ice.js.map +1 -1
  4. package/lib/rtp/src/container/ebml/ebml.d.ts +32 -0
  5. package/lib/rtp/src/container/ebml/ebml.js +115 -0
  6. package/lib/rtp/src/container/ebml/ebml.js.map +1 -0
  7. package/lib/rtp/src/container/ebml/id.d.ts +221 -0
  8. package/lib/rtp/src/container/ebml/id.js +225 -0
  9. package/lib/rtp/src/container/ebml/id.js.map +1 -0
  10. package/lib/rtp/src/container/ebml/index.d.ts +3 -0
  11. package/lib/rtp/src/container/ebml/index.js +20 -0
  12. package/lib/rtp/src/container/ebml/index.js.map +1 -0
  13. package/lib/rtp/src/container/ebml/typedArrayUtils.d.ts +7 -0
  14. package/lib/rtp/src/container/ebml/typedArrayUtils.js +109 -0
  15. package/lib/rtp/src/container/ebml/typedArrayUtils.js.map +1 -0
  16. package/lib/rtp/src/container/webm.d.ts +10 -4
  17. package/lib/rtp/src/container/webm.js +56 -7
  18. package/lib/rtp/src/container/webm.js.map +1 -1
  19. package/lib/rtp/src/index.d.ts +1 -0
  20. package/lib/rtp/src/index.js +1 -0
  21. package/lib/rtp/src/index.js.map +1 -1
  22. package/lib/rtp/src/processor/avBuffer.d.ts +28 -0
  23. package/lib/rtp/src/processor/avBuffer.js +91 -0
  24. package/lib/rtp/src/processor/avBuffer.js.map +1 -0
  25. package/lib/rtp/src/processor/avBufferCallback.d.ts +10 -0
  26. package/lib/rtp/src/processor/avBufferCallback.js +27 -0
  27. package/lib/rtp/src/processor/avBufferCallback.js.map +1 -0
  28. package/lib/rtp/src/processor/depacketizer.d.ts +1 -1
  29. package/lib/rtp/src/processor/depacketizer.js.map +1 -1
  30. package/lib/rtp/src/processor/depacketizerCallback.d.ts +1 -3
  31. package/lib/rtp/src/processor/depacketizerCallback.js +2 -2
  32. package/lib/rtp/src/processor/depacketizerCallback.js.map +1 -1
  33. package/lib/rtp/src/processor/depacketizerTransformer.d.ts +1 -1
  34. package/lib/rtp/src/processor/index.d.ts +3 -0
  35. package/lib/rtp/src/processor/index.js +3 -0
  36. package/lib/rtp/src/processor/index.js.map +1 -1
  37. package/lib/rtp/src/processor/jitterBuffer.d.ts +1 -1
  38. package/lib/rtp/src/processor/jitterBuffer.js.map +1 -1
  39. package/lib/rtp/src/processor/jitterBufferCallback.d.ts +1 -3
  40. package/lib/rtp/src/processor/jitterBufferCallback.js +2 -2
  41. package/lib/rtp/src/processor/jitterBufferCallback.js.map +1 -1
  42. package/lib/rtp/src/processor/jitterBufferTransformer.d.ts +1 -1
  43. package/lib/rtp/src/processor/source/index.d.ts +2 -2
  44. package/lib/rtp/src/processor/source/index.js +16 -3
  45. package/lib/rtp/src/processor/source/index.js.map +1 -1
  46. package/lib/rtp/src/processor/source/rtpCallback.d.ts +17 -0
  47. package/lib/rtp/src/processor/source/rtpCallback.js +33 -0
  48. package/lib/rtp/src/processor/source/rtpCallback.js.map +1 -0
  49. package/lib/rtp/src/processor/source/{rtp.d.ts → rtpStream.d.ts} +2 -5
  50. package/lib/rtp/src/processor/source/{rtp.js → rtpStream.js} +12 -12
  51. package/lib/rtp/src/processor/source/rtpStream.js.map +1 -0
  52. package/lib/rtp/src/processor/webm.d.ts +17 -2
  53. package/lib/rtp/src/processor/webm.js +83 -33
  54. package/lib/rtp/src/processor/webm.js.map +1 -1
  55. package/lib/rtp/src/processor/webmCallback.d.ts +16 -0
  56. package/lib/rtp/src/processor/webmCallback.js +21 -0
  57. package/lib/rtp/src/processor/webmCallback.js.map +1 -0
  58. package/lib/rtp/src/processor/webmStream.d.ts +5 -16
  59. package/lib/rtp/src/processor/webmStream.js.map +1 -1
  60. package/lib/{webrtc/src/media/receiver/red.d.ts → rtp/src/rtp/red/handler.d.ts} +2 -2
  61. package/lib/{webrtc/src/media/receiver/red.js → rtp/src/rtp/red/handler.js} +10 -10
  62. package/lib/rtp/src/rtp/red/handler.js.map +1 -0
  63. package/lib/webrtc/src/const.d.ts +1 -1
  64. package/lib/webrtc/src/const.js +2 -2
  65. package/lib/webrtc/src/const.js.map +1 -1
  66. package/lib/webrtc/src/media/receiver/nack.d.ts +1 -0
  67. package/lib/webrtc/src/media/receiver/nack.js +29 -16
  68. package/lib/webrtc/src/media/receiver/nack.js.map +1 -1
  69. package/lib/webrtc/src/media/router.js +17 -13
  70. package/lib/webrtc/src/media/router.js.map +1 -1
  71. package/lib/webrtc/src/media/rtpReceiver.d.ts +1 -1
  72. package/lib/webrtc/src/media/rtpReceiver.js +14 -9
  73. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  74. package/lib/webrtc/src/media/rtpTransceiver.d.ts +10 -6
  75. package/lib/webrtc/src/media/rtpTransceiver.js +23 -14
  76. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  77. package/lib/webrtc/src/media/track.js +3 -2
  78. package/lib/webrtc/src/media/track.js.map +1 -1
  79. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js.map +1 -1
  80. package/lib/webrtc/src/nonstandard/userMedia.d.ts +20 -3
  81. package/lib/webrtc/src/nonstandard/userMedia.js +79 -8
  82. package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
  83. package/lib/webrtc/src/peerConnection.d.ts +5 -2
  84. package/lib/webrtc/src/peerConnection.js +72 -46
  85. package/lib/webrtc/src/peerConnection.js.map +1 -1
  86. package/lib/webrtc/src/sdp.js +2 -2
  87. package/lib/webrtc/src/sdp.js.map +1 -1
  88. package/lib/webrtc/src/transport/ice.js +10 -3
  89. package/lib/webrtc/src/transport/ice.js.map +1 -1
  90. package/lib/webrtc/src/utils.d.ts +0 -1
  91. package/lib/webrtc/src/utils.js +1 -3
  92. package/lib/webrtc/src/utils.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/const.ts +1 -1
  95. package/src/media/receiver/nack.ts +33 -17
  96. package/src/media/router.ts +16 -13
  97. package/src/media/rtpReceiver.ts +21 -9
  98. package/src/media/rtpTransceiver.ts +28 -15
  99. package/src/media/track.ts +3 -2
  100. package/src/nonstandard/recorder/writer/webm.ts +2 -2
  101. package/src/nonstandard/userMedia.ts +89 -7
  102. package/src/peerConnection.ts +88 -51
  103. package/src/sdp.ts +2 -2
  104. package/src/transport/ice.ts +13 -3
  105. package/src/utils.ts +0 -3
  106. package/lib/rtp/src/processor/source/rtp.js.map +0 -1
  107. package/lib/webrtc/src/media/receiver/red.js.map +0 -1
  108. package/src/media/receiver/red.ts +0 -70
@@ -13,23 +13,13 @@ import { RTCRtpSender } from "./rtpSender";
13
13
  import { MediaStreamTrack } from "./track";
14
14
 
15
15
  export class RTCRtpTransceiver {
16
- readonly uuid = uuid.v4();
16
+ readonly id = uuid.v4();
17
17
  readonly onTrack = new Event<[MediaStreamTrack, RTCRtpTransceiver]>();
18
18
  mid?: string;
19
19
  mLineIndex?: number;
20
+ /**should not be reused because it has been used for sending before. */
20
21
  usedForSender = false;
21
- private _currentDirection?: Direction | "stopped";
22
- set currentDirection(direction: Direction | "stopped" | undefined) {
23
- this._currentDirection = direction;
24
- if (SenderDirections.includes(this._currentDirection || "")) {
25
- this.usedForSender = true;
26
- }
27
- }
28
- /**RFC 8829 4.2.5. last negotiated direction */
29
- get currentDirection(): Direction | "stopped" | undefined {
30
- return this._currentDirection;
31
- }
32
-
22
+ private _currentDirection?: Direction;
33
23
  offerDirection!: Direction;
34
24
  _codecs: RTCRtpCodecParameters[] = [];
35
25
  set codecs(codecs: RTCRtpCodecParameters[]) {
@@ -49,7 +39,7 @@ export class RTCRtpTransceiver {
49
39
  public receiver: RTCRtpReceiver,
50
40
  public sender: RTCRtpSender,
51
41
  /**RFC 8829 4.2.4. direction the transceiver was initialized with */
52
- public direction: Direction
42
+ private _direction: Direction
53
43
  ) {
54
44
  this.setDtlsTransport(dtlsTransport);
55
45
  }
@@ -58,6 +48,27 @@ export class RTCRtpTransceiver {
58
48
  return this.receiver.dtlsTransport;
59
49
  }
60
50
 
51
+ /**RFC 8829 4.2.4. setDirectionに渡された最後の値を示します */
52
+ get direction() {
53
+ return this._direction;
54
+ }
55
+
56
+ setDirection(direction: Direction) {
57
+ this._direction = direction;
58
+ if (SenderDirections.includes(this._currentDirection ?? "")) {
59
+ this.usedForSender = true;
60
+ }
61
+ }
62
+
63
+ /**RFC 8829 4.2.5. last negotiated direction */
64
+ get currentDirection(): Direction | undefined {
65
+ return this._currentDirection;
66
+ }
67
+
68
+ setCurrentDirection(direction: Direction | undefined) {
69
+ this._currentDirection = direction;
70
+ }
71
+
61
72
  setDtlsTransport(dtls: RTCDtlsTransport) {
62
73
  this.receiver.setDtlsTransport(dtls);
63
74
  this.sender.setDtlsTransport(dtls);
@@ -77,7 +88,9 @@ export class RTCRtpTransceiver {
77
88
  // todo impl
78
89
  // https://www.w3.org/TR/webrtc/#methods-8
79
90
  stop() {
80
- if (this.stopping) return;
91
+ if (this.stopping) {
92
+ return;
93
+ }
81
94
 
82
95
  // todo Stop sending and receiving with transceiver.
83
96
 
@@ -55,12 +55,13 @@ export class MediaStreamTrack extends EventTarget {
55
55
  if (this.remote) {
56
56
  throw new Error("this is remoteTrack");
57
57
  }
58
- if (!this.codec || this.stopped) {
58
+ if (this.stopped) {
59
59
  return;
60
60
  }
61
61
 
62
62
  const packet = Buffer.isBuffer(rtp) ? RtpPacket.deSerialize(rtp) : rtp;
63
- packet.header.payloadType = this.codec.payloadType;
63
+ packet.header.payloadType =
64
+ this.codec?.payloadType ?? packet.header.payloadType;
64
65
  this.onReceiveRtp.execute(packet);
65
66
  };
66
67
  }
@@ -7,8 +7,8 @@ import {
7
7
  jitterBufferTransformer,
8
8
  MediaStreamTrack,
9
9
  RtpSourceStream,
10
- WebmLiveOutput,
11
10
  WebmStream,
11
+ WebmStreamOutput,
12
12
  WeriftError,
13
13
  } from "../../..";
14
14
  import { MediaWriter } from ".";
@@ -102,7 +102,7 @@ export class WebmFactory extends MediaWriter {
102
102
  const readChunk = async ({
103
103
  value,
104
104
  done,
105
- }: ReadableStreamDefaultReadResult<WebmLiveOutput>) => {
105
+ }: ReadableStreamDefaultReadResult<WebmStreamOutput>) => {
106
106
  if (done) return;
107
107
 
108
108
  if (value.saveToFile) {
@@ -1,4 +1,4 @@
1
- import { exec } from "child_process";
1
+ import { ChildProcess, exec } from "child_process";
2
2
  import { createSocket } from "dgram";
3
3
  import { setImmediate } from "timers/promises";
4
4
  import { v4 } from "uuid";
@@ -7,17 +7,23 @@ import { randomPort } from "../../../common/src";
7
7
  import { RtpPacket } from "../../../rtp/src";
8
8
  import { MediaStreamTrack } from "../media/track";
9
9
 
10
- export const getUserMp4 = async (path: string, loop?: boolean) => {
10
+ export const getUserMedia = async (path: string, loop?: boolean) => {
11
11
  const audioPort = await randomPort();
12
12
  const videoPort = await randomPort();
13
13
 
14
- return new MediaMp4(audioPort, videoPort, path, loop);
14
+ if (path.endsWith(".mp4")) {
15
+ return new MediaPlayerMp4(audioPort, videoPort, path, loop);
16
+ } else {
17
+ return new MediaPlayerWebm(audioPort, videoPort, path, loop);
18
+ }
15
19
  };
16
20
 
17
- class MediaMp4 {
21
+ export class MediaPlayerMp4 {
18
22
  private streamId = v4();
19
23
  audio = new MediaStreamTrack({ kind: "audio", streamId: this.streamId });
20
24
  video = new MediaStreamTrack({ kind: "video", streamId: this.streamId });
25
+ private process!: ChildProcess;
26
+ stopped = false;
21
27
 
22
28
  constructor(
23
29
  private videoPort: number,
@@ -61,14 +67,90 @@ queue ! h264parse ! rtph264pay config-interval=10 pt=${payloadType++} ! \
61
67
  udpsink host=127.0.0.1 port=${this.videoPort} d. ! \
62
68
  queue ! aacparse ! avdec_aac ! audioresample ! audioconvert ! opusenc ! rtpopuspay pt=${payloadType++} ! \
63
69
  udpsink host=127.0.0.1 port=${this.audioPort}`;
64
- const process = exec(cmd);
70
+ this.process = exec(cmd);
71
+
72
+ if (this.loop) {
73
+ await new Promise((r) => this.process.on("close", r));
74
+ if (!this.stopped) {
75
+ run();
76
+ }
77
+ }
78
+ };
79
+ await setImmediate();
80
+ run();
81
+ }
82
+
83
+ stop() {
84
+ this.stopped = true;
85
+ this.process.kill("SIGINT");
86
+ }
87
+ }
88
+
89
+ export class MediaPlayerWebm {
90
+ private streamId = v4();
91
+ audio = new MediaStreamTrack({ kind: "audio", streamId: this.streamId });
92
+ video = new MediaStreamTrack({ kind: "video", streamId: this.streamId });
93
+ private process!: ChildProcess;
94
+ stopped = false;
95
+
96
+ constructor(
97
+ private videoPort: number,
98
+ private audioPort: number,
99
+ private path: string,
100
+ private loop?: boolean
101
+ ) {
102
+ this.setupTrack(audioPort, this.audio);
103
+ this.setupTrack(videoPort, this.video);
104
+ }
105
+
106
+ private setupTrack = (port: number, track: MediaStreamTrack) => {
107
+ let payloadType = 0;
108
+
109
+ const socket = createSocket("udp4");
110
+ socket.bind(port);
111
+ socket.on("message", async (buf) => {
112
+ const rtp = RtpPacket.deSerialize(buf);
113
+ if (!payloadType) {
114
+ payloadType = rtp.header.payloadType;
115
+ }
116
+
117
+ // detect gStreamer restarted
118
+ if (payloadType !== rtp.header.payloadType) {
119
+ payloadType = rtp.header.payloadType;
120
+ track.onSourceChanged.execute(rtp.header);
121
+ }
122
+ track.writeRtp(buf);
123
+ });
124
+ };
125
+
126
+ async start() {
127
+ let payloadType = 96;
128
+ const run = async () => {
129
+ if (payloadType > 100) payloadType = 96;
130
+
131
+ const cmd = `gst-launch-1.0 filesrc location=${
132
+ this.path
133
+ } ! matroskademux name=d \
134
+ d.video_0 ! queue ! rtpvp8pay pt=${payloadType++} ! \
135
+ udpsink host=127.0.0.1 port=${this.videoPort} \
136
+ d.audio_0 ! queue ! rtpopuspay pt=${payloadType++} ! \
137
+ udpsink host=127.0.0.1 port=${this.audioPort}`;
138
+ this.process = exec(cmd);
139
+ console.log(cmd);
65
140
 
66
141
  if (this.loop) {
67
- await new Promise((r) => process.on("close", r));
68
- run();
142
+ await new Promise((r) => this.process.on("close", r));
143
+ if (!this.stopped) {
144
+ run();
145
+ }
69
146
  }
70
147
  };
71
148
  await setImmediate();
72
149
  run();
73
150
  }
151
+
152
+ stop() {
153
+ this.stopped = true;
154
+ this.process.kill("SIGINT");
155
+ }
74
156
  }
@@ -25,6 +25,7 @@ import {
25
25
  import {
26
26
  DISCARD_HOST,
27
27
  DISCARD_PORT,
28
+ ReceiverDirection,
28
29
  SenderDirections,
29
30
  SRTP_PROFILE,
30
31
  } from "./const";
@@ -79,14 +80,21 @@ const log = debug("werift:packages/webrtc/src/peerConnection.ts");
79
80
  export class RTCPeerConnection extends EventTarget {
80
81
  readonly cname = uuid.v4();
81
82
  sctpTransport?: RTCSctpTransport;
82
- masterTransportEstablished = false;
83
+ transportEstablished = false;
83
84
  config: Required<PeerConfig> = cloneDeep<PeerConfig>(defaultPeerConfig);
84
85
  connectionState: ConnectionState = "new";
85
86
  iceConnectionState: RTCIceConnectionState = "new";
86
87
  iceGatheringState: IceGathererState = "new";
87
88
  signalingState: RTCSignalingState = "stable";
88
89
  negotiationneeded = false;
89
- readonly transceivers: RTCRtpTransceiver[] = [];
90
+ private readonly transceivers: RTCRtpTransceiver[] = [];
91
+ private pushTransceiver(t: RTCRtpTransceiver) {
92
+ this.transceivers.push(t);
93
+ }
94
+ private replaceTransceiver(t: RTCRtpTransceiver, index: number) {
95
+ this.transceivers[index] = t;
96
+ }
97
+
90
98
  candidatesSent = new Set<string>();
91
99
 
92
100
  readonly iceGatheringStateChange = new Event<[IceGathererState]>();
@@ -240,11 +248,11 @@ export class RTCPeerConnection extends EventTarget {
240
248
  ).filter((codecCandidate) => {
241
249
  switch (codecCandidate.direction) {
242
250
  case "recvonly": {
243
- if ([Recvonly, Sendrecv].includes(transceiver.direction)) return true;
251
+ if (ReceiverDirection.includes(transceiver.direction)) return true;
244
252
  return false;
245
253
  }
246
254
  case "sendonly": {
247
- if ([Sendonly, Sendrecv].includes(transceiver.direction)) return true;
255
+ if (SenderDirections.includes(transceiver.direction)) return true;
248
256
  return false;
249
257
  }
250
258
  case "sendrecv": {
@@ -403,8 +411,9 @@ export class RTCPeerConnection extends EventTarget {
403
411
 
404
412
  removeTrack(sender: RTCRtpSender) {
405
413
  if (this.isClosed) throw new Error("peer closed");
406
- if (!this.getSenders().find(({ ssrc }) => sender.ssrc === ssrc))
414
+ if (!this.getSenders().find(({ ssrc }) => sender.ssrc === ssrc)) {
407
415
  throw new Error("unExist");
416
+ }
408
417
 
409
418
  const transceiver = this.transceivers.find(
410
419
  ({ sender: { ssrc } }) => sender.ssrc === ssrc
@@ -419,15 +428,15 @@ export class RTCPeerConnection extends EventTarget {
419
428
  }
420
429
 
421
430
  if (transceiver.stopping || transceiver.stopped) {
422
- transceiver.direction = "inactive";
431
+ transceiver.setDirection("inactive");
423
432
  } else {
424
433
  if (transceiver.direction === "sendrecv") {
425
- transceiver.direction = "recvonly";
434
+ transceiver.setDirection("recvonly");
426
435
  } else if (
427
436
  transceiver.direction === "sendonly" ||
428
437
  transceiver.direction === "recvonly"
429
438
  ) {
430
- transceiver.direction = "inactive";
439
+ transceiver.setDirection("inactive");
431
440
  }
432
441
  }
433
442
  this.needNegotiation();
@@ -614,19 +623,18 @@ export class RTCPeerConnection extends EventTarget {
614
623
  this.dtlsTransports.forEach((d) => setupRole(d));
615
624
 
616
625
  // # configure direction
617
- this.transceivers.forEach((t) => {
618
- if (["answer", "pranswer"].includes(description.type)) {
626
+ if (["answer", "pranswer"].includes(description.type)) {
627
+ this.transceivers.forEach((t) => {
619
628
  const direction = andDirection(t.direction, t.offerDirection);
620
- t.currentDirection = direction;
621
- }
622
- });
629
+ t.setCurrentDirection(direction);
630
+ });
631
+ }
623
632
 
624
633
  // for trickle ice
625
634
  this.setLocal(description);
626
635
 
627
636
  // connect transports
628
637
  if (description.type === "answer") {
629
- log("callee start connect");
630
638
  this.connect().catch((err) => {
631
639
  log("connect failed", err);
632
640
  this.setConnectionState("failed");
@@ -634,9 +642,19 @@ export class RTCPeerConnection extends EventTarget {
634
642
  }
635
643
 
636
644
  // # gather candidates
637
- await Promise.all(
638
- this.iceTransports.map((iceTransport) => iceTransport.iceGather.gather())
645
+ const connected = this.iceTransports.find(
646
+ (transport) => transport.state === "connected"
639
647
  );
648
+ if (this.remoteIsBundled && connected) {
649
+ // no need to gather ice candidates on an existing bundled connection
650
+ await connected.iceGather.gather();
651
+ } else {
652
+ await Promise.all(
653
+ this.iceTransports.map((iceTransport) =>
654
+ iceTransport.iceGather.gather()
655
+ )
656
+ );
657
+ }
640
658
 
641
659
  description.media
642
660
  .filter((m) => ["audio", "video"].includes(m.kind))
@@ -718,7 +736,10 @@ export class RTCPeerConnection extends EventTarget {
718
736
  }
719
737
 
720
738
  private async connect() {
721
- if (this.masterTransportEstablished) return;
739
+ if (this.transportEstablished) {
740
+ return;
741
+ }
742
+ log("start connect");
722
743
 
723
744
  this.setConnectionState("connecting");
724
745
 
@@ -745,7 +766,7 @@ export class RTCPeerConnection extends EventTarget {
745
766
  })
746
767
  );
747
768
 
748
- this.masterTransportEstablished = true;
769
+ this.transportEstablished = true;
749
770
  this.setConnectionState("connected");
750
771
  }
751
772
 
@@ -831,8 +852,8 @@ export class RTCPeerConnection extends EventTarget {
831
852
  transceiver.kind === media.kind &&
832
853
  [undefined, media.rtp.muxId].includes(transceiver.mid);
833
854
 
834
- let transports = enumerate(remoteSdp.media).map(([i, remoteMedia]) => {
835
- let dtlsTransport: RTCDtlsTransport | undefined;
855
+ let transports = remoteSdp.media.map((remoteMedia, i) => {
856
+ let dtlsTransport: RTCDtlsTransport;
836
857
 
837
858
  if (["audio", "video"].includes(remoteMedia.kind)) {
838
859
  let transceiver = this.transceivers.find((t) =>
@@ -840,7 +861,7 @@ export class RTCPeerConnection extends EventTarget {
840
861
  );
841
862
  if (!transceiver) {
842
863
  // create remote transceiver
843
- transceiver = this.addTransceiver(remoteMedia.kind, {
864
+ transceiver = this._addTransceiver(remoteMedia.kind, {
844
865
  direction: "recvonly",
845
866
  });
846
867
  transceiver.mid = remoteMedia.rtp.muxId;
@@ -848,7 +869,10 @@ export class RTCPeerConnection extends EventTarget {
848
869
  } else {
849
870
  if (transceiver.direction === "inactive" && transceiver.stopping) {
850
871
  transceiver.stopped = true;
851
- transceiver.currentDirection = "inactive";
872
+
873
+ if (sessionDescription.type === "answer") {
874
+ transceiver.setCurrentDirection("inactive");
875
+ }
852
876
  return;
853
877
  }
854
878
  }
@@ -946,11 +970,17 @@ export class RTCPeerConnection extends EventTarget {
946
970
  });
947
971
  }
948
972
 
949
- await Promise.all(
950
- transports.map(async (iceTransport) => {
951
- await iceTransport.iceGather.gather();
952
- })
973
+ const connected = this.iceTransports.find(
974
+ (transport) => transport.state === "connected"
953
975
  );
976
+ if (this.remoteIsBundled && connected) {
977
+ // no need to gather ice candidates on an existing bundled connection
978
+ await connected.iceGather.gather();
979
+ } else {
980
+ await Promise.all(
981
+ transports.map((iceTransport) => iceTransport.iceGather.gather())
982
+ );
983
+ }
954
984
 
955
985
  this.negotiationneeded = false;
956
986
  if (this.shouldNegotiationneeded) {
@@ -966,8 +996,8 @@ export class RTCPeerConnection extends EventTarget {
966
996
  ) {
967
997
  if (!transceiver.mid) {
968
998
  transceiver.mid = remoteMedia.rtp.muxId;
969
- transceiver.mLineIndex = mLineIndex;
970
999
  }
1000
+ transceiver.mLineIndex = mLineIndex;
971
1001
 
972
1002
  // # negotiate codecs
973
1003
  transceiver.codecs = remoteMedia.rtp.codecs.filter((remoteCodec) => {
@@ -999,14 +1029,13 @@ export class RTCPeerConnection extends EventTarget {
999
1029
  );
1000
1030
 
1001
1031
  // # configure direction
1002
- const mediaDirection = remoteMedia.direction || "inactive";
1032
+ const mediaDirection = remoteMedia.direction ?? "inactive";
1003
1033
  const direction = reverseDirection(mediaDirection);
1004
1034
  if (["answer", "pranswer"].includes(type)) {
1005
- transceiver.currentDirection = direction;
1035
+ transceiver.setCurrentDirection(direction);
1006
1036
  } else {
1007
1037
  transceiver.offerDirection = direction;
1008
1038
  }
1009
-
1010
1039
  const localParams = this.getLocalRtpParams(transceiver);
1011
1040
  transceiver.sender.prepareSend(localParams);
1012
1041
 
@@ -1137,6 +1166,13 @@ export class RTCPeerConnection extends EventTarget {
1137
1166
  addTransceiver(
1138
1167
  trackOrKind: Kind | MediaStreamTrack,
1139
1168
  options: Partial<TransceiverOptions> = {}
1169
+ ) {
1170
+ return this._addTransceiver(trackOrKind, options);
1171
+ }
1172
+
1173
+ private _addTransceiver(
1174
+ trackOrKind: Kind | MediaStreamTrack,
1175
+ options: Partial<TransceiverOptions> = {}
1140
1176
  ) {
1141
1177
  const kind =
1142
1178
  typeof trackOrKind === "string" ? trackOrKind : trackOrKind.kind;
@@ -1150,15 +1186,15 @@ export class RTCPeerConnection extends EventTarget {
1150
1186
 
1151
1187
  const sender = new RTCRtpSender(trackOrKind);
1152
1188
  const receiver = new RTCRtpReceiver(this.config, kind, sender.ssrc);
1153
- const transceiver = new RTCRtpTransceiver(
1189
+ const newTransceiver = new RTCRtpTransceiver(
1154
1190
  kind,
1155
1191
  dtlsTransport,
1156
1192
  receiver,
1157
1193
  sender,
1158
1194
  direction
1159
1195
  );
1160
- transceiver.options = options;
1161
- this.router.registerRtpSender(transceiver.sender);
1196
+ newTransceiver.options = options;
1197
+ this.router.registerRtpSender(newTransceiver.sender);
1162
1198
 
1163
1199
  // reuse inactive
1164
1200
  const inactiveTransceiverIndex = this.transceivers.findIndex(
@@ -1166,20 +1202,20 @@ export class RTCPeerConnection extends EventTarget {
1166
1202
  );
1167
1203
  const inactiveTransceiver = this.transceivers.find(
1168
1204
  (t) => t.currentDirection === "inactive"
1169
- )!;
1170
- if (inactiveTransceiverIndex > -1) {
1171
- this.transceivers[inactiveTransceiverIndex] = transceiver;
1172
- transceiver.mLineIndex = inactiveTransceiver.mLineIndex;
1173
- inactiveTransceiver.currentDirection = "stopped";
1205
+ );
1206
+ if (inactiveTransceiverIndex > -1 && inactiveTransceiver) {
1207
+ this.replaceTransceiver(newTransceiver, inactiveTransceiverIndex);
1208
+ newTransceiver.mLineIndex = inactiveTransceiver.mLineIndex;
1209
+ inactiveTransceiver.setCurrentDirection(undefined);
1174
1210
  } else {
1175
- this.transceivers.push(transceiver);
1211
+ this.pushTransceiver(newTransceiver);
1176
1212
  }
1177
- this.onTransceiverAdded.execute(transceiver);
1213
+ this.onTransceiverAdded.execute(newTransceiver);
1178
1214
 
1179
1215
  this.updateIceConnectionState();
1180
1216
  this.needNegotiation();
1181
1217
 
1182
- return transceiver;
1218
+ return newTransceiver;
1183
1219
  }
1184
1220
 
1185
1221
  getTransceivers() {
@@ -1230,16 +1266,18 @@ export class RTCPeerConnection extends EventTarget {
1230
1266
  sender.registerTrack(track);
1231
1267
  switch (notSendTransceiver.direction) {
1232
1268
  case "recvonly":
1233
- notSendTransceiver.direction = "sendrecv";
1269
+ notSendTransceiver.setDirection("sendrecv");
1234
1270
  break;
1235
1271
  case "inactive":
1236
- notSendTransceiver.direction = "sendonly";
1272
+ notSendTransceiver.setDirection("sendonly");
1237
1273
  break;
1238
1274
  }
1239
1275
  this.needNegotiation();
1240
1276
  return sender;
1241
1277
  } else {
1242
- const transceiver = this.addTransceiver(track, { direction: "sendrecv" });
1278
+ const transceiver = this._addTransceiver(track, {
1279
+ direction: "sendrecv",
1280
+ });
1243
1281
  this.needNegotiation();
1244
1282
  return transceiver.sender;
1245
1283
  }
@@ -1306,13 +1344,12 @@ export class RTCPeerConnection extends EventTarget {
1306
1344
  }
1307
1345
 
1308
1346
  // # determine DTLS role, or preserve the currently configured role
1309
- if (!media.dtlsParams) {
1310
- throw new Error("dtlsParams missing");
1311
- }
1312
- if (dtlsTransport.role === "auto") {
1313
- media.dtlsParams.role = "client";
1314
- } else {
1315
- media.dtlsParams.role = dtlsTransport.role;
1347
+ if (media.dtlsParams) {
1348
+ if (dtlsTransport.role === "auto") {
1349
+ media.dtlsParams.role = "client";
1350
+ } else {
1351
+ media.dtlsParams.role = dtlsTransport.role;
1352
+ }
1316
1353
  }
1317
1354
 
1318
1355
  media.simulcastParameters = remoteMedia.simulcastParameters.map((v) => ({
package/src/sdp.ts CHANGED
@@ -279,7 +279,7 @@ export class SessionDescription {
279
279
  if (!bundle.items.includes(i.toString())) continue;
280
280
  const check = session.media[i];
281
281
  if (
282
- check.iceParams &&
282
+ check?.iceParams &&
283
283
  check.iceParams.usernameFragment &&
284
284
  check.iceParams.password
285
285
  ) {
@@ -344,7 +344,7 @@ export class SessionDescription {
344
344
  get string() {
345
345
  const lines = [`v=${this.version}`, `o=${this.origin}`, `s=${this.name}`];
346
346
  if (this.host) {
347
- lines.push(`c=${ipAddressFromSdp(this.host)}`);
347
+ lines.push(`c=${ipAddressToSdp(this.host)}`);
348
348
  }
349
349
  lines.push(`t=${this.time}`);
350
350
  this.group.forEach((group) => lines.push(`a=group:${group.str}`));
@@ -1,9 +1,12 @@
1
+ import debug from "debug";
1
2
  import Event from "rx.mini";
2
3
  import { v4 } from "uuid";
3
4
 
4
5
  import { Candidate, Connection, IceOptions } from "../../../ice/src";
5
6
  import { candidateFromSdp, candidateToSdp } from "../sdp";
6
7
 
8
+ const log = debug("werift:packages/webrtc/src/transport/ice.ts");
9
+
7
10
  export class RTCIceTransport {
8
11
  readonly id = v4();
9
12
  connection = this.gather.connection;
@@ -54,9 +57,16 @@ export class RTCIceTransport {
54
57
  };
55
58
 
56
59
  setRemoteParams(remoteParameters: RTCIceParameters) {
57
- this.connection.remoteIsLite = remoteParameters.iceLite;
58
- this.connection.remoteUsername = remoteParameters.usernameFragment;
59
- this.connection.remotePassword = remoteParameters.password;
60
+ if (
61
+ this.connection.remoteUsername &&
62
+ this.connection.remotePassword &&
63
+ (this.connection.remoteUsername !== remoteParameters.usernameFragment ||
64
+ this.connection.remotePassword !== remoteParameters.password)
65
+ ) {
66
+ log("restartIce", remoteParameters);
67
+ this.connection.resetNominatedPair();
68
+ }
69
+ this.connection.setRemoteParams(remoteParameters);
60
70
  }
61
71
 
62
72
  async start() {
package/src/utils.ts CHANGED
@@ -51,9 +51,6 @@ export function reverseSimulcastDirection(dir: "recv" | "send") {
51
51
  export const andDirection = (a: Direction, b: Direction) =>
52
52
  Directions[Directions.indexOf(a) & Directions.indexOf(b)];
53
53
 
54
- export const orDirection = (a: Direction, b: Direction) =>
55
- Directions[Directions.indexOf(a) & Directions.indexOf(b)];
56
-
57
54
  export function reverseDirection(dir: Direction): Direction {
58
55
  if (dir === "sendonly") return "recvonly";
59
56
  if (dir === "recvonly") return "sendonly";
@@ -1 +0,0 @@
1
- {"version":3,"file":"rtp.js","sourceRoot":"","sources":["../../../../../../rtp/src/processor/source/rtp.ts"],"names":[],"mappings":";;;AAAA,oCAAsE;AAEtE,uCAA0C;AAO1C,MAAa,eAAe;IAK1B,YACU,UAGJ,EAAE;QAHE,YAAO,GAAP,OAAO,CAGT;QAEN,OAAO,CAAC,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,IAAI,IAAI,CAAC;QAEpE,IAAI,CAAC,QAAQ,GAAG,IAAI,oBAAc,CAAC;YACjC,KAAK,EAAE,CAAC,UAAU,EAAE,EAAE;gBACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;gBAC7B,IAAI,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACpD,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAA0B;QAC7B,MAAM,GAAG,GACP,MAAM,YAAY,eAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,eAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAEvE,IACE,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,SAAS;YACrC,IAAI,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG,CAAC,MAAM,CAAC,WAAW,EACnD;YACA,IAAI,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE;gBACrC,GAAG,CAAC,KAAK,EAAE,CAAC;aACb;YACD,OAAO;SACR;QAED,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACtB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;CACF;AAzCD,0CAyCC","sourcesContent":["import { ReadableStream, ReadableStreamController } from \"stream/web\";\n\nimport { RtpPacket } from \"../../rtp/rtp\";\n\nexport interface RtpOutput {\n rtp?: RtpPacket;\n eol?: boolean;\n}\n\nexport class RtpSourceStream {\n readable: ReadableStream<RtpOutput>;\n write!: (chunk: RtpOutput) => void;\n protected controller!: ReadableStreamController<RtpOutput>;\n\n constructor(\n private options: {\n payloadType?: number;\n clearInvalidPTPacket?: boolean;\n } = {}\n ) {\n options.clearInvalidPTPacket = options.clearInvalidPTPacket ?? true;\n\n this.readable = new ReadableStream({\n start: (controller) => {\n this.controller = controller;\n this.write = (chunk) => controller.enqueue(chunk);\n },\n });\n }\n\n push(packet: Buffer | RtpPacket) {\n const rtp =\n packet instanceof RtpPacket ? packet : RtpPacket.deSerialize(packet);\n\n if (\n this.options.payloadType != undefined &&\n this.options.payloadType !== rtp.header.payloadType\n ) {\n if (this.options.clearInvalidPTPacket) {\n rtp.clear();\n }\n return;\n }\n\n this.write({ rtp });\n }\n\n stop() {\n this.controller.enqueue({ eol: true });\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"red.js","sourceRoot":"","sources":["../../../../../src/media/receiver/red.ts"],"names":[],"mappings":";;;AAAA,6BAAwE;AAExE,iEAAiE;AACjE,mEAAmE;AACnE,oEAAoE;AACpE,oEAAoE;AACpE,oEAAoE;AAEpE,kBAAkB;AAClB,oBAAoB;AACpB,oBAAoB;AACpB,oBAAoB;AAEpB,MAAa,eAAe;IAA5B;QACmB,SAAI,GAAG,GAAG,CAAC;QACpB,oBAAe,GAAa,EAAE,CAAC;IAsDzC,CAAC;IApDC,IAAI,CAAC,GAAQ,EAAE,GAAc;QAC3B,MAAM,OAAO,GAAgB,EAAE,CAAC;QAEhC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,cAAc,GAAG,IAAA,aAAS,EAC9B,GAAG,CAAC,MAAM,CAAC,cAAc,EACzB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAC/B,CAAC;YACF,IAAI,eAAe,EAAE;gBACnB,OAAO,CAAC,IAAI,CACV,IAAI,aAAS,CACX,IAAI,aAAS,CAAC;oBACZ,SAAS,EAAE,IAAA,aAAS,EAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,eAAe,CAAC;oBAC5D,WAAW,EAAE,OAAO;oBACpB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;oBACrB,cAAc;oBACd,MAAM,EAAE,IAAI;iBACb,CAAC,EACF,KAAK,CACN,CACF,CAAC;aACH;iBAAM;gBACL,OAAO,CAAC,IAAI,CACV,IAAI,aAAS,CACX,IAAI,aAAS,CAAC;oBACZ,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS;oBAC/B,WAAW,EAAE,OAAO;oBACpB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;oBACrB,cAAc;oBACd,MAAM,EAAE,IAAI;iBACb,CAAC,EACF,KAAK,CACN,CACF,CAAC;aACH;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YACpC,YAAY;YACZ,IAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE;gBAC1D,OAAO,KAAK,CAAC;aACd;iBAAM;gBACL,kBAAkB;gBAClB,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE;oBAC3C,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;iBAC9B;gBACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBACnD,OAAO,IAAI,CAAC;aACb;QACH,CAAC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAxDD,0CAwDC","sourcesContent":["import { Red, RtpHeader, RtpPacket, uint16Add, uint32Add } from \"../..\";\n\n// 0 1 2 3\n// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n// |F| block PT | timestamp offset | block length |\n// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n// 0 1 2 3 4 5 6 7\n// +-+-+-+-+-+-+-+-+\n// |0| Block PT |\n// +-+-+-+-+-+-+-+-+\n\nexport class AudioRedHandler {\n private readonly size = 150;\n private sequenceNumbers: number[] = [];\n\n push(red: Red, rtp: RtpPacket) {\n const packets: RtpPacket[] = [];\n\n red.blocks.forEach(({ blockPT, timestampOffset, block }, i) => {\n const sequenceNumber = uint16Add(\n rtp.header.sequenceNumber,\n -(red.blocks.length - (i + 1))\n );\n if (timestampOffset) {\n packets.push(\n new RtpPacket(\n new RtpHeader({\n timestamp: uint32Add(rtp.header.timestamp, -timestampOffset),\n payloadType: blockPT,\n ssrc: rtp.header.ssrc,\n sequenceNumber,\n marker: true,\n }),\n block\n )\n );\n } else {\n packets.push(\n new RtpPacket(\n new RtpHeader({\n timestamp: rtp.header.timestamp,\n payloadType: blockPT,\n ssrc: rtp.header.ssrc,\n sequenceNumber,\n marker: true,\n }),\n block\n )\n );\n }\n });\n\n const filtered = packets.filter((p) => {\n // duplicate\n if (this.sequenceNumbers.includes(p.header.sequenceNumber)) {\n return false;\n } else {\n // buffer overflow\n if (this.sequenceNumbers.length > this.size) {\n this.sequenceNumbers.shift();\n }\n this.sequenceNumbers.push(p.header.sequenceNumber);\n return true;\n }\n });\n return filtered;\n }\n}\n"]}