@stream-io/video-client 1.6.0 → 1.6.2

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/dist/index.cjs.js CHANGED
@@ -6756,7 +6756,7 @@ const logLevels = Object.freeze({
6756
6756
  warn: 3,
6757
6757
  error: 4,
6758
6758
  });
6759
- let logger$2;
6759
+ let logger$1;
6760
6760
  let level = 'info';
6761
6761
  const logToConsole = (logLevel, message, ...args) => {
6762
6762
  let logMethod;
@@ -6790,7 +6790,7 @@ const logToConsole = (logLevel, message, ...args) => {
6790
6790
  logMethod(message, ...args);
6791
6791
  };
6792
6792
  const setLogger = (l, lvl) => {
6793
- logger$2 = l;
6793
+ logger$1 = l;
6794
6794
  if (lvl) {
6795
6795
  setLogLevel(lvl);
6796
6796
  }
@@ -6800,7 +6800,7 @@ const setLogLevel = (l) => {
6800
6800
  };
6801
6801
  const getLogLevel = () => level;
6802
6802
  const getLogger = (withTags) => {
6803
- const loggerMethod = logger$2 || logToConsole;
6803
+ const loggerMethod = logger$1 || logToConsole;
6804
6804
  const tags = (withTags || []).filter(Boolean).join(':');
6805
6805
  const result = (logLevel, message, ...args) => {
6806
6806
  if (logLevels[logLevel] >= logLevels[level]) {
@@ -6841,50 +6841,74 @@ const retryable = async (rpc, signal) => {
6841
6841
  return result;
6842
6842
  };
6843
6843
 
6844
+ /**
6845
+ * Returns back a list of sorted codecs, with the preferred codec first.
6846
+ *
6847
+ * @param kind the kind of codec to get.
6848
+ * @param preferredCodec the codec to prioritize (vp8, h264, vp9, av1...).
6849
+ * @param codecToRemove the codec to exclude from the list.
6850
+ */
6844
6851
  const getPreferredCodecs = (kind, preferredCodec, codecToRemove) => {
6845
- const logger = getLogger(['codecs']);
6846
- if (!('getCapabilities' in RTCRtpReceiver)) {
6847
- logger('warn', 'RTCRtpReceiver.getCapabilities is not supported');
6852
+ if (!('getCapabilities' in RTCRtpReceiver))
6848
6853
  return;
6849
- }
6850
- const cap = RTCRtpReceiver.getCapabilities(kind);
6851
- if (!cap)
6854
+ const capabilities = RTCRtpReceiver.getCapabilities(kind);
6855
+ if (!capabilities)
6852
6856
  return;
6853
- const matched = [];
6854
- const partialMatched = [];
6855
- const unmatched = [];
6856
- cap.codecs.forEach((c) => {
6857
- const codec = c.mimeType.toLowerCase();
6858
- logger('debug', `Found supported codec: ${codec}`);
6859
- const shouldRemoveCodec = codecToRemove && codec === `${kind}/${codecToRemove.toLowerCase()}`;
6857
+ const preferred = [];
6858
+ const partiallyPreferred = [];
6859
+ const unpreferred = [];
6860
+ const preferredCodecMimeType = `${kind}/${preferredCodec.toLowerCase()}`;
6861
+ const codecToRemoveMimeType = codecToRemove && `${kind}/${codecToRemove.toLowerCase()}`;
6862
+ for (const codec of capabilities.codecs) {
6863
+ const codecMimeType = codec.mimeType.toLowerCase();
6864
+ const shouldRemoveCodec = codecMimeType === codecToRemoveMimeType;
6860
6865
  if (shouldRemoveCodec)
6861
- return;
6862
- const matchesCodec = codec === `${kind}/${preferredCodec.toLowerCase()}`;
6863
- if (!matchesCodec) {
6864
- unmatched.push(c);
6865
- return;
6866
+ continue; // skip this codec
6867
+ const isPreferredCodec = codecMimeType === preferredCodecMimeType;
6868
+ if (!isPreferredCodec) {
6869
+ unpreferred.push(codec);
6870
+ continue;
6866
6871
  }
6867
- // for h264 codecs that have sdpFmtpLine available, use only if the
6868
- // profile-level-id is 42e01f for cross-browser compatibility
6869
- if (codec === 'h264') {
6870
- if (c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=42e01f')) {
6871
- matched.push(c);
6872
- }
6873
- else {
6874
- partialMatched.push(c);
6875
- }
6876
- return;
6872
+ // h264 is a special case, we want to prioritize the baseline codec with
6873
+ // profile-level-id is 42e01f and packetization-mode=0 for maximum
6874
+ // cross-browser compatibility.
6875
+ // this branch covers the other cases, such as vp8.
6876
+ if (codecMimeType !== 'video/h264') {
6877
+ preferred.push(codec);
6878
+ continue;
6877
6879
  }
6878
- matched.push(c);
6879
- });
6880
- return [...matched, ...partialMatched, ...unmatched];
6880
+ const sdpFmtpLine = codec.sdpFmtpLine;
6881
+ if (!sdpFmtpLine || !sdpFmtpLine.includes('profile-level-id=42e01f')) {
6882
+ // this is not the baseline h264 codec, prioritize it lower
6883
+ partiallyPreferred.push(codec);
6884
+ continue;
6885
+ }
6886
+ // packetization-mode mode is optional; when not present it defaults to 0:
6887
+ // https://datatracker.ietf.org/doc/html/rfc6184#section-6.2
6888
+ if (sdpFmtpLine.includes('packetization-mode=0') ||
6889
+ !sdpFmtpLine.includes('packetization-mode')) {
6890
+ preferred.unshift(codec);
6891
+ }
6892
+ else {
6893
+ preferred.push(codec);
6894
+ }
6895
+ }
6896
+ // return a sorted list of codecs, with the preferred codecs first
6897
+ return [...preferred, ...partiallyPreferred, ...unpreferred];
6881
6898
  };
6899
+ /**
6900
+ * Returns a generic SDP for the given direction.
6901
+ * We use this SDP to send it as part of our JoinRequest so that the SFU
6902
+ * can use it to determine client's codec capabilities.
6903
+ *
6904
+ * @param direction the direction of the transceiver.
6905
+ */
6882
6906
  const getGenericSdp = async (direction) => {
6883
6907
  const tempPc = new RTCPeerConnection();
6884
6908
  tempPc.addTransceiver('video', { direction });
6885
6909
  tempPc.addTransceiver('audio', { direction });
6886
6910
  const offer = await tempPc.createOffer();
6887
- let sdp = offer.sdp ?? '';
6911
+ const sdp = offer.sdp ?? '';
6888
6912
  tempPc.getTransceivers().forEach((t) => {
6889
6913
  t.stop?.();
6890
6914
  });
@@ -7903,6 +7927,13 @@ class CallState {
7903
7927
  this.setOwnCapabilities = (capabilities) => {
7904
7928
  return this.setCurrentValue(this.ownCapabilitiesSubject, capabilities);
7905
7929
  };
7930
+ /**
7931
+ * Sets the time when this call has been ended.
7932
+ * @param endedAt the time when this call has been ended.
7933
+ */
7934
+ this.setEndedAt = (endedAt) => {
7935
+ return this.setCurrentValue(this.endedAtSubject, endedAt);
7936
+ };
7906
7937
  /**
7907
7938
  * Will try to find the participant with the given sessionId in the current call.
7908
7939
  *
@@ -8089,7 +8120,7 @@ class CallState {
8089
8120
  this.setCurrentValue(this.createdAtSubject, new Date(call.created_at));
8090
8121
  this.setCurrentValue(this.updatedAtSubject, new Date(call.updated_at));
8091
8122
  this.setCurrentValue(this.startsAtSubject, call.starts_at ? new Date(call.starts_at) : undefined);
8092
- this.setCurrentValue(this.endedAtSubject, call.ended_at ? new Date(call.ended_at) : undefined);
8123
+ this.setEndedAt(call.ended_at ? new Date(call.ended_at) : undefined);
8093
8124
  this.setCurrentValue(this.createdBySubject, call.created_by);
8094
8125
  this.setCurrentValue(this.customSubject, call.custom);
8095
8126
  this.setCurrentValue(this.egressSubject, call.egress);
@@ -8738,7 +8769,7 @@ const enableHighQualityAudio = (sdp, trackMid, maxBitrate = 510000) => {
8738
8769
  return SDP__namespace.write(parsedSdp);
8739
8770
  };
8740
8771
 
8741
- const version = "1.6.0" ;
8772
+ const version = "1.6.2" ;
8742
8773
  const [major, minor, patch] = version.split('.');
8743
8774
  let sdkInfo = {
8744
8775
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -10028,15 +10059,37 @@ const watchCallRejected = (call) => {
10028
10059
  * Event handler that watches the delivery of `call.ended` Websocket event.
10029
10060
  */
10030
10061
  const watchCallEnded = (call) => {
10031
- return async function onCallEnded() {
10062
+ return function onCallEnded() {
10032
10063
  const { callingState } = call.state;
10033
10064
  if (callingState === exports.CallingState.RINGING ||
10034
10065
  callingState === exports.CallingState.JOINED ||
10035
10066
  callingState === exports.CallingState.JOINING) {
10036
- await call.leave({ reason: 'call.ended event received' });
10067
+ call.leave({ reason: 'call.ended event received' }).catch((err) => {
10068
+ call.logger('error', 'Failed to leave call after call.ended ', err);
10069
+ });
10037
10070
  }
10038
10071
  };
10039
10072
  };
10073
+ /**
10074
+ * Watches for `callEnded` events.
10075
+ */
10076
+ const watchSfuCallEnded = (call) => {
10077
+ return call.on('callEnded', async (e) => {
10078
+ if (call.state.callingState === exports.CallingState.LEFT)
10079
+ return;
10080
+ try {
10081
+ // `call.ended` event arrived after the call is already left
10082
+ // and all event handlers are detached. We need to manually
10083
+ // update the call state to reflect the call has ended.
10084
+ call.state.setEndedAt(new Date());
10085
+ const reason = CallEndedReason[e.reason];
10086
+ await call.leave({ reason: `callEnded received: ${reason}` });
10087
+ }
10088
+ catch (err) {
10089
+ call.logger('error', 'Failed to leave call after being ended by the SFU', err);
10090
+ }
10091
+ });
10092
+ };
10040
10093
 
10041
10094
  /**
10042
10095
  * Event handler that watches for `callGrantsUpdated` events.
@@ -10064,7 +10117,6 @@ const watchCallGrantsUpdated = (state) => {
10064
10117
  };
10065
10118
  };
10066
10119
 
10067
- const logger$1 = getLogger(['events']);
10068
10120
  /**
10069
10121
  * An event responder which handles the `changePublishQuality` event.
10070
10122
  */
@@ -10110,7 +10162,7 @@ const watchLiveEnded = (dispatcher, call) => {
10110
10162
  return;
10111
10163
  if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
10112
10164
  call.leave({ reason: 'live ended' }).catch((err) => {
10113
- logger$1('error', 'Failed to leave call after live ended', err);
10165
+ call.logger('error', 'Failed to leave call after live ended', err);
10114
10166
  });
10115
10167
  }
10116
10168
  });
@@ -10122,8 +10174,9 @@ const watchSfuErrorReports = (dispatcher) => {
10122
10174
  return dispatcher.on('error', (e) => {
10123
10175
  if (!e.error)
10124
10176
  return;
10177
+ const logger = getLogger(['SfuClient']);
10125
10178
  const { error, reconnectStrategy } = e;
10126
- logger$1('error', 'SFU reported error', {
10179
+ logger('error', 'SFU reported error', {
10127
10180
  code: ErrorCode[error.code],
10128
10181
  reconnectStrategy: WebsocketReconnectStrategy[reconnectStrategy],
10129
10182
  message: error.message,
@@ -10141,17 +10194,6 @@ const watchPinsUpdated = (state) => {
10141
10194
  state.setServerSidePins(pins);
10142
10195
  };
10143
10196
  };
10144
- /**
10145
- * Watches for `callEnded` events.
10146
- */
10147
- const watchSfuCallEnded = (call) => {
10148
- return call.on('callEnded', (e) => {
10149
- const reason = CallEndedReason[e.reason];
10150
- call.leave({ reason }).catch((err) => {
10151
- logger$1('error', 'Failed to leave call after call ended by the SFU', err);
10152
- });
10153
- });
10154
- };
10155
10197
 
10156
10198
  /**
10157
10199
  * An event handler that handles soft mutes.
@@ -16069,7 +16111,7 @@ class StreamClient {
16069
16111
  });
16070
16112
  };
16071
16113
  this.getUserAgent = () => {
16072
- const version = "1.6.0" ;
16114
+ const version = "1.6.2" ;
16073
16115
  return (this.userAgent ||
16074
16116
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
16075
16117
  };