mediaconnection.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 { ServerMessage } from "./servermessage";
  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<MediaConnectionEvents, this>;
  33. private _localStream: MediaStream;
  34. private _remoteStream: MediaStream;
  35. /**
  36. * For media connections, this is always 'media'.
  37. */
  38. get type() {
  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: ServerMessage): void {
  82. const type = message.type;
  83. const payload = message.payload;
  84. switch (message.type) {
  85. case ServerMessageType.Answer:
  86. // Forward to negotiator
  87. void this._negotiator.handleSDP(type, payload.sdp);
  88. this._open = true;
  89. break;
  90. case ServerMessageType.Candidate:
  91. void this._negotiator.handleCandidate(payload.candidate);
  92. break;
  93. default:
  94. logger.warn(`Unrecognized message type:${type} from peer:${this.peer}`);
  95. break;
  96. }
  97. }
  98. /**
  99. * When receiving a {@apilink PeerEvents | `call`} event on a peer, you can call
  100. * `answer` on the media connection provided by the callback to accept the call
  101. * and optionally send your own media stream.
  102. *
  103. * @param stream A WebRTC media stream.
  104. * @param options
  105. * @returns
  106. */
  107. answer(stream?: MediaStream, options: AnswerOption = {}): void {
  108. if (this._localStream) {
  109. logger.warn(
  110. "Local stream already exists on this MediaConnection. Are you answering a call twice?",
  111. );
  112. return;
  113. }
  114. this._localStream = stream;
  115. if (options && options.sdpTransform) {
  116. this.options.sdpTransform = options.sdpTransform;
  117. }
  118. this._negotiator.startConnection({
  119. ...this.options._payload,
  120. _stream: stream,
  121. });
  122. // Retrieve lost messages stored because PeerConnection not set up.
  123. const messages = this.provider._getMessages(this.connectionId);
  124. for (const message of messages) {
  125. this.handleMessage(message);
  126. }
  127. this._open = true;
  128. }
  129. /**
  130. * Exposed functionality for users.
  131. */
  132. /**
  133. * Closes the media connection.
  134. */
  135. close(): void {
  136. if (this._negotiator) {
  137. this._negotiator.cleanup();
  138. this._negotiator = null;
  139. }
  140. this._localStream = null;
  141. this._remoteStream = null;
  142. if (this.provider) {
  143. this.provider._removeConnection(this);
  144. this.provider = null;
  145. }
  146. if (this.options && this.options._stream) {
  147. this.options._stream = null;
  148. }
  149. if (!this.open) {
  150. return;
  151. }
  152. this._open = false;
  153. super.emit("close");
  154. }
  155. }