mediaconnection.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { util } from "./util";
  2. import logger from "./logger";
  3. import { Negotiator } from "./negotiator";
  4. import { ConnectionType, ServerMessageType } from "./enums";
  5. import type { Peer } from "./peer";
  6. import { BaseConnection, type BaseConnectionEvents } from "./baseconnection";
  7. import type { IncomingServerMessage } from "./serverMessages";
  8. import type { AnswerOption } from "./optionInterfaces";
  9. export interface MediaConnectionEvents extends BaseConnectionEvents<never> {
  10. /**
  11. * Emitted when a connection to the PeerServer is established.
  12. *
  13. * ```ts
  14. * mediaConnection.on('stream', (stream) => { ... });
  15. * ```
  16. */
  17. stream: (stream: MediaStream) => void;
  18. /**
  19. * Emitted when the auxiliary data channel is established.
  20. * After this event, hanging up will close the connection cleanly on the remote peer.
  21. * @beta
  22. */
  23. willCloseOnRemote: () => void;
  24. }
  25. /**
  26. * Wraps WebRTC's media streams.
  27. * To get one, use {@apilink Peer.call} or listen for the {@apilink PeerEvents | `call`} event.
  28. */
  29. export class MediaConnection extends BaseConnection<MediaConnectionEvents> {
  30. private static readonly ID_PREFIX = "mc_";
  31. readonly label: string;
  32. private _negotiator: Negotiator;
  33. private _localStream: MediaStream;
  34. private _remoteStream: MediaStream;
  35. /**
  36. * For media connections, this is always 'media'.
  37. */
  38. get type(): ConnectionType.Media {
  39. return ConnectionType.Media;
  40. }
  41. get localStream(): MediaStream {
  42. return this._localStream;
  43. }
  44. get remoteStream(): MediaStream {
  45. return this._remoteStream;
  46. }
  47. constructor(peerId: string, provider: Peer, options: any) {
  48. super(peerId, provider, options);
  49. this._localStream = this.options._stream;
  50. this.connectionId =
  51. this.options.connectionId ||
  52. MediaConnection.ID_PREFIX + util.randomToken();
  53. this._negotiator = new Negotiator(this);
  54. if (this._localStream) {
  55. this._negotiator.startConnection({
  56. _stream: this._localStream,
  57. originator: true,
  58. });
  59. }
  60. }
  61. /** Called by the Negotiator when the DataChannel is ready. */
  62. override _initializeDataChannel(dc: RTCDataChannel): void {
  63. this.dataChannel = dc;
  64. this.dataChannel.onopen = () => {
  65. logger.log(`DC#${this.connectionId} dc connection success`);
  66. this.emit("willCloseOnRemote");
  67. };
  68. this.dataChannel.onclose = () => {
  69. logger.log(`DC#${this.connectionId} dc closed for:`, this.peer);
  70. this.close();
  71. };
  72. }
  73. addStream(remoteStream) {
  74. logger.log("Receiving stream", remoteStream);
  75. this._remoteStream = remoteStream;
  76. super.emit("stream", remoteStream); // Should we call this `open`?
  77. }
  78. /**
  79. * @internal
  80. */
  81. handleMessage(message: IncomingServerMessage): void {
  82. const type = message.type;
  83. switch (message.type) {
  84. case ServerMessageType.Answer:
  85. // Forward to negotiator
  86. void this._negotiator.handleSDP(type, message.payload.sdp);
  87. this._open = true;
  88. break;
  89. case ServerMessageType.Candidate:
  90. void this._negotiator.handleCandidate(message.payload.candidate);
  91. break;
  92. default:
  93. logger.warn(`Unrecognized message type:${type} from peer:${this.peer}`);
  94. break;
  95. }
  96. }
  97. /**
  98. * When receiving a {@apilink PeerEvents | `call`} event on a peer, you can call
  99. * `answer` on the media connection provided by the callback to accept the call
  100. * and optionally send your own media stream.
  101. *
  102. * @param stream A WebRTC media stream.
  103. * @param options
  104. * @returns
  105. */
  106. answer(stream?: MediaStream, options: AnswerOption = {}): void {
  107. if (this._localStream) {
  108. logger.warn(
  109. "Local stream already exists on this MediaConnection. Are you answering a call twice?",
  110. );
  111. return;
  112. }
  113. this._localStream = stream;
  114. if (options && options.sdpTransform) {
  115. this.options.sdpTransform = options.sdpTransform;
  116. }
  117. this._negotiator.startConnection({
  118. ...this.options._payload,
  119. _stream: stream,
  120. });
  121. // Retrieve lost messages stored because PeerConnection not set up.
  122. const messages = this.provider._getMessages(this.connectionId);
  123. for (const message of messages) {
  124. this.handleMessage(message);
  125. }
  126. this._open = true;
  127. }
  128. /**
  129. * Exposed functionality for users.
  130. */
  131. /**
  132. * Closes the media connection.
  133. */
  134. close(): void {
  135. if (this._negotiator) {
  136. this._negotiator.cleanup();
  137. this._negotiator = null;
  138. }
  139. this._localStream = null;
  140. this._remoteStream = null;
  141. if (this.provider) {
  142. this.provider._removeConnection(this);
  143. this.provider = null;
  144. }
  145. if (this.options && this.options._stream) {
  146. this.options._stream = null;
  147. }
  148. if (!this.open) {
  149. return;
  150. }
  151. this._open = false;
  152. super.emit("close");
  153. }
  154. }