mediaconnection.ts 4.7 KB

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