@whereby.com/browser-sdk 2.0.0-alpha14 → 2.0.0-alpha16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -89,6 +89,26 @@ function MyCallUX( { roomUrl, localStream }) {
89
89
 
90
90
  ```
91
91
 
92
+ ##### Usage with Next.js
93
+ If you are integrating these React hooks with Next.js, you need to ensure your custom video experience components are
94
+ reneded client side, as the underlying APIs we use are only available in the browser context. Simply add `"use client";`
95
+ to the top of component, like in the following example:
96
+
97
+ ```js
98
+ "use client";
99
+
100
+ import { VideoView, useLocalMedia } from "@whereby.com/browser-sdk";
101
+
102
+ export default function MyNextVideoExperience() {
103
+ const { state, actions } = useLocalMedia({ audio: false, video: true });
104
+
105
+ return (
106
+ <p>{ state.localStream && <VideoView muted stream={state.localStream} /> }</p>
107
+ );
108
+ }
109
+
110
+ ```
111
+
92
112
  ### Web component for embedding
93
113
 
94
114
  Use the `<whereby-embed />` web component to make use of Whereby's pre-built responsive UI. Refer to our [documentation](https://docs.whereby.com/embedding-rooms/in-a-web-page/using-the-whereby-embed-element) to learn which attributes are supported.
package/dist/lib.cjs CHANGED
@@ -150,7 +150,7 @@ heresy.define("WherebyEmbed", {
150
150
  if (roomUrl.searchParams.get("roomKey")) {
151
151
  this.url.searchParams.append("roomKey", roomUrl.searchParams.get("roomKey"));
152
152
  }
153
- Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha14", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
153
+ Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "2.0.0-alpha16", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang })), (metadata && { metadata })), (groups && { groups })), (virtualBackgroundUrl && { virtualBackgroundUrl })), (avatarUrl && { avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce(
154
154
  // add to URL if set in any way
155
155
  (o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
156
156
  if (!this.url.searchParams.has(k) && typeof v === "string") {
@@ -6825,6 +6825,7 @@ class VegaRtcManager {
6825
6825
  clientId,
6826
6826
  stream: webcamStream,
6827
6827
  streamId: camStreamId,
6828
+ streamType: "webcam",
6828
6829
  });
6829
6830
 
6830
6831
  clientState.hasEmittedWebcamStream = true;
@@ -6836,6 +6837,7 @@ class VegaRtcManager {
6836
6837
  clientId,
6837
6838
  stream: screenStream,
6838
6839
  streamId: screenShareStreamId,
6840
+ streamType: "screenshare",
6839
6841
  });
6840
6842
 
6841
6843
  clientState.hasEmittedScreenStream = true;
@@ -8338,6 +8340,15 @@ class RemoteParticipant extends RoomParticipant {
8338
8340
  this.newJoiner = newJoiner;
8339
8341
  this.streams = streams.map((streamId) => ({ id: streamId, state: newJoiner ? "new_accept" : "to_accept" }));
8340
8342
  }
8343
+ addStream(streamId, state) {
8344
+ this.streams.push({ id: streamId, state });
8345
+ }
8346
+ removeStream(streamId) {
8347
+ const index = this.streams.findIndex((s) => s.id === streamId);
8348
+ if (index !== -1) {
8349
+ this.streams.splice(index, 1);
8350
+ }
8351
+ }
8341
8352
  updateStreamState(streamId, state) {
8342
8353
  const stream = this.streams.find((s) => s.id === streamId);
8343
8354
  if (stream) {
@@ -8379,6 +8390,8 @@ class RoomConnection extends TypedEventTarget {
8379
8390
  super();
8380
8391
  this.localParticipant = null;
8381
8392
  this.remoteParticipants = [];
8393
+ this.screenshares = [];
8394
+ this._deviceCredentials = null;
8382
8395
  this._ownsLocalMedia = false;
8383
8396
  this.organizationId = "";
8384
8397
  this.roomConnectionStatus = "";
@@ -8438,6 +8451,14 @@ class RoomConnection extends TypedEventTarget {
8438
8451
  this.signalSocket.on("knocker_left", this._handleKnockerLeft.bind(this));
8439
8452
  this.signalSocket.on("room_joined", this._handleRoomJoined.bind(this));
8440
8453
  this.signalSocket.on("room_knocked", this._handleRoomKnocked.bind(this));
8454
+ this.signalSocket.on("cloud_recording_stopped", this._handleCloudRecordingStopped.bind(this));
8455
+ this.signalSocket.on("screenshare_started", this._handleScreenshareStarted.bind(this));
8456
+ this.signalSocket.on("screenshare_stopped", this._handleScreenshareStopped.bind(this));
8457
+ this.signalSocket.on("streaming_stopped", this._handleStreamingStopped.bind(this));
8458
+ this.signalSocket.on("disconnect", this._handleDisconnect.bind(this));
8459
+ this.signalSocket.on("connect_error", this._handleDisconnect.bind(this));
8460
+ this.signalSocketManager = this.signalSocket.getManager();
8461
+ this.signalSocketManager.on("reconnect", this._handleReconnect.bind(this));
8441
8462
  // Set up local media listeners
8442
8463
  this.localMedia.addEventListener("camera_enabled", (e) => {
8443
8464
  const { enabled } = e.detail;
@@ -8479,7 +8500,35 @@ class RoomConnection extends TypedEventTarget {
8479
8500
  _handleNewChatMessage(message) {
8480
8501
  this.dispatchEvent(new CustomEvent("chat_message", { detail: message }));
8481
8502
  }
8503
+ _handleCloudRecordingStarted({ client }) {
8504
+ this.dispatchEvent(new CustomEvent("cloud_recording_started", {
8505
+ detail: {
8506
+ status: "recording",
8507
+ startedAt: client.startedCloudRecordingAt
8508
+ ? new Date(client.startedCloudRecordingAt).getTime()
8509
+ : new Date().getTime(),
8510
+ },
8511
+ }));
8512
+ }
8513
+ _handleStreamingStarted() {
8514
+ this.dispatchEvent(new CustomEvent("streaming_started", {
8515
+ detail: {
8516
+ status: "streaming",
8517
+ // We don't have the streaming start time stored on the
8518
+ // server, so we use the current time instead. This gives
8519
+ // an invalid timestamp for "Client B" if "Client A" has
8520
+ // been streaming for a while before "Client B" joins.
8521
+ startedAt: new Date().getTime(),
8522
+ },
8523
+ }));
8524
+ }
8482
8525
  _handleNewClient({ client }) {
8526
+ if (client.role.roleName === "recorder") {
8527
+ this._handleCloudRecordingStarted({ client });
8528
+ }
8529
+ if (client.role.roleName === "streamer") {
8530
+ this._handleStreamingStarted();
8531
+ }
8483
8532
  if (NON_PERSON_ROLES.includes(client.role.roleName)) {
8484
8533
  return;
8485
8534
  }
@@ -8571,8 +8620,17 @@ class RoomConnection extends TypedEventTarget {
8571
8620
  if (!localClient)
8572
8621
  throw new Error("Missing local client");
8573
8622
  this.localParticipant = new LocalParticipant(Object.assign(Object.assign({}, localClient), { stream: this.localMedia.stream || undefined }));
8623
+ const recorderClient = clients.find((c) => c.role.roleName === "recorder");
8624
+ if (recorderClient) {
8625
+ this._handleCloudRecordingStarted({ client: recorderClient });
8626
+ }
8627
+ const streamerClient = clients.find((c) => c.role.roleName === "streamer");
8628
+ if (streamerClient) {
8629
+ this._handleStreamingStarted();
8630
+ }
8574
8631
  this.remoteParticipants = clients
8575
8632
  .filter((c) => c.id !== selfId)
8633
+ .filter((c) => !NON_PERSON_ROLES.includes(c.role.roleName))
8576
8634
  .map((c) => new RemoteParticipant(Object.assign(Object.assign({}, c), { newJoiner: false })));
8577
8635
  this.roomConnectionStatus = "connected";
8578
8636
  this.dispatchEvent(new CustomEvent("room_joined", {
@@ -8592,6 +8650,54 @@ class RoomConnection extends TypedEventTarget {
8592
8650
  detail: { participantId: clientId, displayName },
8593
8651
  }));
8594
8652
  }
8653
+ _handleReconnect() {
8654
+ this.logger.log("Reconnected to signal socket");
8655
+ this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
8656
+ this.signalSocket.once("device_identified", () => {
8657
+ this._joinRoom();
8658
+ });
8659
+ }
8660
+ _handleDisconnect() {
8661
+ this.roomConnectionStatus = "disconnected";
8662
+ this.dispatchEvent(new CustomEvent("room_connection_status_changed", {
8663
+ detail: {
8664
+ roomConnectionStatus: this.roomConnectionStatus,
8665
+ },
8666
+ }));
8667
+ }
8668
+ _handleCloudRecordingStopped() {
8669
+ this.dispatchEvent(new CustomEvent("cloud_recording_stopped"));
8670
+ }
8671
+ _handleStreamingStopped() {
8672
+ this.dispatchEvent(new CustomEvent("streaming_stopped"));
8673
+ }
8674
+ _handleScreenshareStarted(screenshare) {
8675
+ const { clientId: participantId, streamId: id, hasAudioTrack } = screenshare;
8676
+ const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
8677
+ if (!remoteParticipant) {
8678
+ this.logger.log("WARN: Could not find participant for screenshare");
8679
+ return;
8680
+ }
8681
+ const foundScreenshare = this.screenshares.find((s) => s.id === id);
8682
+ if (foundScreenshare) {
8683
+ this.logger.log("WARN: Screenshare already exists");
8684
+ return;
8685
+ }
8686
+ remoteParticipant.addStream(id, "to_accept");
8687
+ this._handleAcceptStreams([remoteParticipant]);
8688
+ this.screenshares = [...this.screenshares, { participantId, id, hasAudioTrack, stream: undefined }];
8689
+ }
8690
+ _handleScreenshareStopped(screenshare) {
8691
+ const { clientId: participantId, streamId: id } = screenshare;
8692
+ const remoteParticipant = this.remoteParticipants.find((p) => p.id === participantId);
8693
+ if (!remoteParticipant) {
8694
+ this.logger.log("WARN: Could not find participant for screenshare");
8695
+ return;
8696
+ }
8697
+ remoteParticipant.removeStream(id);
8698
+ this.screenshares = this.screenshares.filter((s) => !(s.participantId === participantId && s.id === id));
8699
+ this.dispatchEvent(new CustomEvent("screenshare_stopped", { detail: { participantId, id } }));
8700
+ }
8595
8701
  _handleRtcEvent(eventName, data) {
8596
8702
  if (eventName === "rtc_manager_created") {
8597
8703
  return this._handleRtcManagerCreated(data);
@@ -8599,6 +8705,9 @@ class RoomConnection extends TypedEventTarget {
8599
8705
  else if (eventName === "stream_added") {
8600
8706
  return this._handleStreamAdded(data);
8601
8707
  }
8708
+ else if (eventName === "rtc_manager_destroyed") {
8709
+ return this._handleRtcManagerDestroyed();
8710
+ }
8602
8711
  else {
8603
8712
  this.logger.log(`Unhandled RTC event ${eventName}`);
8604
8713
  }
@@ -8614,6 +8723,9 @@ class RoomConnection extends TypedEventTarget {
8614
8723
  this._handleAcceptStreams(this.remoteParticipants);
8615
8724
  }
8616
8725
  }
8726
+ _handleRtcManagerDestroyed() {
8727
+ this.rtcManager = undefined;
8728
+ }
8617
8729
  _handleAcceptStreams(remoteParticipants) {
8618
8730
  var _a, _b;
8619
8731
  if (!this.rtcManager) {
@@ -8662,13 +8774,24 @@ class RoomConnection extends TypedEventTarget {
8662
8774
  });
8663
8775
  });
8664
8776
  }
8665
- _handleStreamAdded({ clientId, stream, streamId }) {
8777
+ _handleStreamAdded({ clientId, stream, streamId, streamType }) {
8666
8778
  const remoteParticipant = this.remoteParticipants.find((p) => p.id === clientId);
8667
8779
  if (!remoteParticipant) {
8668
8780
  this.logger.log("WARN: Could not find participant for incoming stream");
8669
8781
  return;
8670
8782
  }
8671
- this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
8783
+ const remoteParticipantStream = remoteParticipant.streams.find((s) => s.id === streamId);
8784
+ if ((remoteParticipant.stream && remoteParticipant.stream.id === streamId) ||
8785
+ (!remoteParticipant.stream && streamType === "webcam") ||
8786
+ (!remoteParticipant.stream &&
8787
+ !streamType &&
8788
+ remoteParticipantStream &&
8789
+ remoteParticipant.streams.indexOf(remoteParticipantStream) < 1)) {
8790
+ this.dispatchEvent(new CustomEvent("participant_stream_added", { detail: { participantId: clientId, stream, streamId } }));
8791
+ return;
8792
+ }
8793
+ // screenshare
8794
+ this.dispatchEvent(new CustomEvent("screenshare_started", { detail: { participantId: clientId, stream, id: streamId } }));
8672
8795
  }
8673
8796
  _joinRoom() {
8674
8797
  this.signalSocket.emit("join_room", {
@@ -8712,9 +8835,9 @@ class RoomConnection extends TypedEventTarget {
8712
8835
  yield this.localMedia.start();
8713
8836
  }
8714
8837
  // Identify device on signal connection
8715
- const deviceCredentials = yield this.credentialsService.getCredentials();
8838
+ this._deviceCredentials = yield this.credentialsService.getCredentials();
8716
8839
  this.logger.log("Connected to signal socket");
8717
- this.signalSocket.emit("identify_device", { deviceCredentials });
8840
+ this.signalSocket.emit("identify_device", { deviceCredentials: this._deviceCredentials });
8718
8841
  this.signalSocket.once("device_identified", () => {
8719
8842
  this._joinRoom();
8720
8843
  });
@@ -8785,11 +8908,20 @@ class RoomConnection extends TypedEventTarget {
8785
8908
 
8786
8909
  const initialState = {
8787
8910
  chatMessages: [],
8788
- roomConnectionStatus: "",
8911
+ cloudRecording: {
8912
+ status: "",
8913
+ startedAt: null,
8914
+ },
8789
8915
  isJoining: false,
8790
8916
  joinError: null,
8791
8917
  mostRecentChatMessage: null,
8792
8918
  remoteParticipants: [],
8919
+ roomConnectionStatus: "",
8920
+ screenshares: [],
8921
+ streaming: {
8922
+ status: "",
8923
+ startedAt: null,
8924
+ },
8793
8925
  waitingParticipants: [],
8794
8926
  };
8795
8927
  function updateParticipant(remoteParticipants, participantId, updates) {
@@ -8804,10 +8936,27 @@ function updateParticipant(remoteParticipants, participantId, updates) {
8804
8936
  ...remoteParticipants.slice(index + 1),
8805
8937
  ];
8806
8938
  }
8939
+ function addScreenshare(screenshares, screenshare) {
8940
+ const existingScreenshare = screenshares.find((ss) => ss.id === screenshare.id);
8941
+ if (existingScreenshare) {
8942
+ return screenshares;
8943
+ }
8944
+ return [...screenshares, screenshare];
8945
+ }
8807
8946
  function reducer(state, action) {
8808
8947
  switch (action.type) {
8809
8948
  case "CHAT_MESSAGE":
8810
8949
  return Object.assign(Object.assign({}, state), { chatMessages: [...state.chatMessages, action.payload], mostRecentChatMessage: action.payload });
8950
+ case "CLOUD_RECORDING_STARTED":
8951
+ return Object.assign(Object.assign({}, state), { cloudRecording: {
8952
+ status: action.payload.status,
8953
+ startedAt: action.payload.startedAt,
8954
+ } });
8955
+ case "CLOUD_RECORDING_STOPPED":
8956
+ return Object.assign(Object.assign({}, state), { cloudRecording: {
8957
+ status: "",
8958
+ startedAt: null,
8959
+ } });
8811
8960
  case "ROOM_JOINED":
8812
8961
  return Object.assign(Object.assign({}, state), { localParticipant: action.payload.localParticipant, remoteParticipants: action.payload.remoteParticipants, waitingParticipants: action.payload.waitingParticipants, roomConnectionStatus: "connected" });
8813
8962
  case "ROOM_CONNECTION_STATUS_CHANGED":
@@ -8836,6 +8985,25 @@ function reducer(state, action) {
8836
8985
  if (!state.localParticipant)
8837
8986
  return state;
8838
8987
  return Object.assign(Object.assign({}, state), { localParticipant: Object.assign(Object.assign({}, state.localParticipant), { displayName: action.payload.displayName }) });
8988
+ case "SCREENSHARE_STARTED":
8989
+ return Object.assign(Object.assign({}, state), { screenshares: addScreenshare(state.screenshares, {
8990
+ participantId: action.payload.participantId,
8991
+ id: action.payload.id,
8992
+ hasAudioTrack: action.payload.hasAudioTrack,
8993
+ stream: action.payload.stream,
8994
+ }) });
8995
+ case "SCREENSHARE_STOPPED":
8996
+ return Object.assign(Object.assign({}, state), { screenshares: state.screenshares.filter((ss) => ss.participantId !== action.payload.participantId || ss.id !== action.payload.id) });
8997
+ case "STREAMING_STARTED":
8998
+ return Object.assign(Object.assign({}, state), { streaming: {
8999
+ status: action.payload.status,
9000
+ startedAt: action.payload.startedAt,
9001
+ } });
9002
+ case "STREAMING_STOPPED":
9003
+ return Object.assign(Object.assign({}, state), { streaming: {
9004
+ status: "",
9005
+ startedAt: null,
9006
+ } });
8839
9007
  case "WAITING_PARTICIPANT_JOINED":
8840
9008
  return Object.assign(Object.assign({}, state), { waitingParticipants: [
8841
9009
  ...state.waitingParticipants,
@@ -8858,6 +9026,13 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
8858
9026
  const chatMessage = e.detail;
8859
9027
  dispatch({ type: "CHAT_MESSAGE", payload: chatMessage });
8860
9028
  });
9029
+ roomConnection.addEventListener("cloud_recording_started", (e) => {
9030
+ const { status, startedAt } = e.detail;
9031
+ dispatch({ type: "CLOUD_RECORDING_STARTED", payload: { status, startedAt } });
9032
+ });
9033
+ roomConnection.addEventListener("cloud_recording_stopped", () => {
9034
+ dispatch({ type: "CLOUD_RECORDING_STOPPED" });
9035
+ });
8861
9036
  roomConnection.addEventListener("participant_audio_enabled", (e) => {
8862
9037
  const { participantId, isAudioEnabled } = e.detail;
8863
9038
  dispatch({ type: "PARTICIPANT_AUDIO_ENABLED", payload: { participantId, isAudioEnabled } });
@@ -8890,6 +9065,21 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
8890
9065
  const { participantId, displayName } = e.detail;
8891
9066
  dispatch({ type: "PARTICIPANT_METADATA_CHANGED", payload: { participantId, displayName } });
8892
9067
  });
9068
+ roomConnection.addEventListener("screenshare_started", (e) => {
9069
+ const { participantId, id, hasAudioTrack, stream } = e.detail;
9070
+ dispatch({ type: "SCREENSHARE_STARTED", payload: { participantId, id, hasAudioTrack, stream } });
9071
+ });
9072
+ roomConnection.addEventListener("screenshare_stopped", (e) => {
9073
+ const { participantId, id } = e.detail;
9074
+ dispatch({ type: "SCREENSHARE_STOPPED", payload: { participantId, id } });
9075
+ });
9076
+ roomConnection.addEventListener("streaming_started", (e) => {
9077
+ const { status, startedAt } = e.detail;
9078
+ dispatch({ type: "STREAMING_STARTED", payload: { status, startedAt } });
9079
+ });
9080
+ roomConnection.addEventListener("streaming_stopped", () => {
9081
+ dispatch({ type: "STREAMING_STOPPED" });
9082
+ });
8893
9083
  roomConnection.addEventListener("waiting_participant_joined", (e) => {
8894
9084
  const { participantId, displayName } = e.detail;
8895
9085
  dispatch({ type: "WAITING_PARTICIPANT_JOINED", payload: { participantId, displayName } });
@@ -8936,7 +9126,7 @@ function useRoomConnection(roomUrl, roomConnectionOptions) {
8936
9126
  };
8937
9127
  }
8938
9128
 
8939
- const sdkVersion = "2.0.0-alpha14";
9129
+ const sdkVersion = "2.0.0-alpha16";
8940
9130
 
8941
9131
  exports.VideoView = VideoView;
8942
9132
  exports.sdkVersion = sdkVersion;