mediaconnection.ts 4.5 KB

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