mediaconnection.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. /**
  20. * Wraps WebRTC's media streams.
  21. * To get one, use {@apilink Peer.call} or listen for the {@apilink PeerEvents | `call`} event.
  22. */
  23. export class MediaConnection extends BaseConnection<MediaConnectionEvents> {
  24. private static readonly ID_PREFIX = "mc_";
  25. private _negotiator: Negotiator<MediaConnectionEvents, MediaConnection>;
  26. private _localStream: MediaStream;
  27. private _remoteStream: MediaStream;
  28. /**
  29. * For media connections, this is always 'media'.
  30. */
  31. get type() {
  32. return ConnectionType.Media;
  33. }
  34. get localStream(): MediaStream {
  35. return this._localStream;
  36. }
  37. get remoteStream(): MediaStream {
  38. return this._remoteStream;
  39. }
  40. constructor(peerId: string, provider: Peer, options: any) {
  41. super(peerId, provider, options);
  42. this._localStream = this.options._stream;
  43. this.connectionId =
  44. this.options.connectionId ||
  45. MediaConnection.ID_PREFIX + util.randomToken();
  46. this._negotiator = new Negotiator(this);
  47. if (this._localStream) {
  48. this._negotiator.startConnection({
  49. _stream: this._localStream,
  50. originator: true,
  51. });
  52. }
  53. }
  54. addStream(remoteStream) {
  55. logger.log("Receiving stream", remoteStream);
  56. this._remoteStream = remoteStream;
  57. super.emit("stream", remoteStream); // Should we call this `open`?
  58. }
  59. handleMessage(message: ServerMessage): void {
  60. const type = message.type;
  61. const payload = message.payload;
  62. switch (message.type) {
  63. case ServerMessageType.Answer:
  64. // Forward to negotiator
  65. this._negotiator.handleSDP(type, payload.sdp);
  66. this._open = true;
  67. break;
  68. case ServerMessageType.Candidate:
  69. this._negotiator.handleCandidate(payload.candidate);
  70. break;
  71. default:
  72. logger.warn(`Unrecognized message type:${type} from peer:${this.peer}`);
  73. break;
  74. }
  75. }
  76. /**
  77. * When receiving a {@apilink PeerEvents | `call`} event on a peer, you can call
  78. * `answer` on the media connection provided by the callback to accept the call
  79. * and optionally send your own media stream.
  80. *
  81. * @param stream A WebRTC media stream.
  82. * @param options
  83. * @returns
  84. */
  85. answer(stream?: MediaStream, options: AnswerOption = {}): void {
  86. if (this._localStream) {
  87. logger.warn(
  88. "Local stream already exists on this MediaConnection. Are you answering a call twice?",
  89. );
  90. return;
  91. }
  92. this._localStream = stream;
  93. if (options && options.sdpTransform) {
  94. this.options.sdpTransform = options.sdpTransform;
  95. }
  96. this._negotiator.startConnection({
  97. ...this.options._payload,
  98. _stream: stream,
  99. });
  100. // Retrieve lost messages stored because PeerConnection not set up.
  101. const messages = this.provider._getMessages(this.connectionId);
  102. for (const message of messages) {
  103. this.handleMessage(message);
  104. }
  105. this._open = true;
  106. }
  107. /**
  108. * Exposed functionality for users.
  109. */
  110. /**
  111. * Closes the media connection.
  112. */
  113. close(): void {
  114. if (this._negotiator) {
  115. this._negotiator.cleanup();
  116. this._negotiator = null;
  117. }
  118. this._localStream = null;
  119. this._remoteStream = null;
  120. if (this.provider) {
  121. this.provider._removeConnection(this);
  122. this.provider = null;
  123. }
  124. if (this.options && this.options._stream) {
  125. this.options._stream = null;
  126. }
  127. if (!this.open) {
  128. return;
  129. }
  130. this._open = false;
  131. super.emit("close");
  132. }
  133. }