diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000000..246d2fb95b
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,20 @@
+name: Close stale issues and PRs
+on:
+ workflow_dispatch: {}
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v8
+ with:
+ stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days."
+ stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days."
+ close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
+ close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity."
+ days-before-issue-stale: 30
+ days-before-pr-stale: 45
+ days-before-issue-close: 5
+ days-before-pr-close: 10
diff --git a/README.md b/README.md
index e02d38ae5a..c7c247dcd9 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,34 @@ Once the app has been deployed, you can append the room code at the end of the d
+### Maintaining A Forked Version
+
+Tthe following command will build the roomkit-react package and generate a .tgz file:
+
+```
+yarn && yarn build;
+cd packages/roomkit-react;
+yarn pack
+```
+
+Push your changes and the .tgz file to the forked repository and in the package.json of the app you are building, use the following link for the roomkit-react package:
+
+```
+"@100mslive/roomkit-react":"https://github.com//web-sdks/raw/main/packages/roomkit-react/.tgz",
+```
+
+Re-install the dependencies after updating the package.json and build using the following command:
+
+```
+yarn && yarn build
+```
+
+You can now import the HMSPrebuilt component in the same way as before:
+
+```
+import { HMSPrebuilt } from '@100mslive/roomkit-react';
+```
+
## Contributing
We welcome external contributors or anyone excited to help improve 100ms SDKs. If you'd like to get involved, check out our [contribution guide](./DEVELOPER.MD), and get started exploring the codebase.
diff --git a/examples/prebuilt-react-integration/package.json b/examples/prebuilt-react-integration/package.json
index d42595fd1b..59126e6832 100644
--- a/examples/prebuilt-react-integration/package.json
+++ b/examples/prebuilt-react-integration/package.json
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
- "@100mslive/roomkit-react": "0.3.20",
+ "@100mslive/roomkit-react": "0.3.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
diff --git a/packages/hls-player/package.json b/packages/hls-player/package.json
index 31993c800e..eb5ae37e98 100644
--- a/packages/hls-player/package.json
+++ b/packages/hls-player/package.json
@@ -1,6 +1,6 @@
{
"name": "@100mslive/hls-player",
- "version": "0.3.20",
+ "version": "0.3.21",
"description": "HLS client library which uses HTML5 Video element and Media Source Extension for playback",
"main": "dist/index.cjs.js",
"module": "dist/index.js",
@@ -36,7 +36,7 @@
"author": "100ms",
"license": "MIT",
"dependencies": {
- "@100mslive/hls-stats": "0.4.20",
+ "@100mslive/hls-stats": "0.4.21",
"eventemitter2": "^6.4.9",
"hls.js": "1.4.12"
}
diff --git a/packages/hls-player/src/controllers/HMSHLSPlayer.ts b/packages/hls-player/src/controllers/HMSHLSPlayer.ts
index 40cf97b806..18a24f4bdb 100644
--- a/packages/hls-player/src/controllers/HMSHLSPlayer.ts
+++ b/packages/hls-player/src/controllers/HMSHLSPlayer.ts
@@ -236,7 +236,7 @@ export class HMSHLSPlayer implements IHMSHLSPlayer, IHMSHLSPlayerEventEmitter {
});
};
private volumeEventHandler = () => {
- this._volume = this._videoEl.volume;
+ this._volume = Math.round(this._videoEl.volume * 100);
};
private reConnectToStream = () => {
diff --git a/packages/hls-stats/package.json b/packages/hls-stats/package.json
index 43a02194e3..b078a76ac8 100644
--- a/packages/hls-stats/package.json
+++ b/packages/hls-stats/package.json
@@ -1,6 +1,6 @@
{
"name": "@100mslive/hls-stats",
- "version": "0.4.20",
+ "version": "0.4.21",
"description": "A simple library that provides stats for your hls stream",
"main": "dist/index.cjs.js",
"module": "dist/index.js",
diff --git a/packages/hms-video-store/README.md b/packages/hms-video-store/README.md
index b3f3f333f1..8c37e598cd 100644
--- a/packages/hms-video-store/README.md
+++ b/packages/hms-video-store/README.md
@@ -34,10 +34,10 @@ you want to do -
passed in selector such that whenever the portion changes, the passed in callback is notified.
2. Actions - The actions interface for dispatching actions which in turn may reach
out to server and update the store. Check the interface with detailed doc
- [here](src/core/IHMSActions.ts).
+ [here](./src/IHMSActions.ts).
We also provide optimized and efficient selectors for most common use cases. These are
-available in [this folder](src/core/selectors).
+available in [this folder](./src/selectors).
Important Note: The data received from either getState or Subscribe is immutable, the
object received is frozen, and it is not allowed to mutate it. You'll get an error
diff --git a/packages/hms-video-store/package.json b/packages/hms-video-store/package.json
index 5feeb58a55..ccc9423223 100644
--- a/packages/hms-video-store/package.json
+++ b/packages/hms-video-store/package.json
@@ -1,5 +1,5 @@
{
- "version": "0.12.20",
+ "version": "0.12.21",
"license": "MIT",
"repository": {
"type": "git",
diff --git a/packages/hms-video-store/src/IHMSActions.ts b/packages/hms-video-store/src/IHMSActions.ts
index f53083faf5..d1ab46e0ff 100644
--- a/packages/hms-video-store/src/IHMSActions.ts
+++ b/packages/hms-video-store/src/IHMSActions.ts
@@ -232,16 +232,16 @@ export interface IHMSActions;
/**
* Remove video plugins to the local peer video stream. Eg. Virtual Background, Face Filters etc.
* Video plugins can be added/removed at any time after the video track is available.
- * @param plugin HMSMediaStreamPlugin
* @see HMSMediaStreamPlugin
+ * @param plugins
*/
removePluginsFromVideoStream(plugins: HMSMediaStreamPlugin[]): Promise;
@@ -344,6 +344,7 @@ export interface IHMSActions;
diff --git a/packages/hms-video-store/src/connection/subscribe/subscribeConnection.ts b/packages/hms-video-store/src/connection/subscribe/subscribeConnection.ts
index 3f95f886b2..9120824a24 100644
--- a/packages/hms-video-store/src/connection/subscribe/subscribeConnection.ts
+++ b/packages/hms-video-store/src/connection/subscribe/subscribeConnection.ts
@@ -98,8 +98,11 @@ export default class HMSSubscribeConnection extends HMSConnection {
});
const remote = this.remoteStreams.get(streamId)!;
- const TrackCls = e.track.kind === 'audio' ? HMSRemoteAudioTrack : HMSRemoteVideoTrack;
- const track = new TrackCls(remote, e.track);
+ const isAudioTrack = e.track.kind === 'audio';
+ const TrackCls = isAudioTrack ? HMSRemoteAudioTrack : HMSRemoteVideoTrack;
+ const track = isAudioTrack
+ ? new TrackCls(remote, e.track)
+ : new TrackCls(remote, e.track, undefined, this.isFlagEnabled(InitFlags.FLAG_DISABLE_NONE_LAYER_REQUEST));
// reset the simulcast layer to none when new video tracks are added, UI will subscribe when required
if (e.track.kind === 'video') {
remote.setVideoLayerLocally(HMSSimulcastLayer.NONE, 'addTrack', 'subscribeConnection');
diff --git a/packages/hms-video-store/src/device-manager/DeviceManager.ts b/packages/hms-video-store/src/device-manager/DeviceManager.ts
index 7e3eb32be3..629dd36bc5 100644
--- a/packages/hms-video-store/src/device-manager/DeviceManager.ts
+++ b/packages/hms-video-store/src/device-manager/DeviceManager.ts
@@ -4,7 +4,7 @@ import { ErrorFactory } from '../error/ErrorFactory';
import { HMSException } from '../error/HMSException';
import { EventBus } from '../events/EventBus';
import { DeviceMap, HMSDeviceChangeEvent, SelectedDevices } from '../interfaces';
-import { isIOS } from '../internal';
+import { getAudioDeviceCategory, isIOS } from '../internal';
import { HMSAudioTrackSettingsBuilder, HMSVideoTrackSettingsBuilder } from '../media/settings';
import { HMSLocalAudioTrack, HMSLocalTrack, HMSLocalVideoTrack } from '../media/tracks';
import { Store } from '../sdk/store';
@@ -443,14 +443,14 @@ export class DeviceManager implements HMSDeviceManager {
let earpiece: InputDeviceInfo | null = null;
for (const device of this.audioInput) {
- const label = device.label.toLowerCase();
- if (label.includes('speakerphone')) {
+ const deviceCategory = getAudioDeviceCategory(device.label);
+ if (deviceCategory === 'speakerphone') {
speakerPhone = device;
- } else if (label.includes('wired')) {
+ } else if (deviceCategory === 'wired') {
wired = device;
- } else if (/airpods|buds|wireless|bluetooth/gi.test(label)) {
+ } else if (deviceCategory === 'bluetooth') {
bluetoothDevice = device;
- } else if (label.includes('earpiece')) {
+ } else if (deviceCategory === 'speakerhone') {
earpiece = device;
}
}
diff --git a/packages/hms-video-store/src/index.ts b/packages/hms-video-store/src/index.ts
index d09d847909..aa36a66403 100644
--- a/packages/hms-video-store/src/index.ts
+++ b/packages/hms-video-store/src/index.ts
@@ -20,6 +20,7 @@ export {
simulcastMapping,
DeviceType,
HMSPeerType,
+ getAudioDeviceCategory,
} from './internal';
export type {
diff --git a/packages/hms-video-store/src/interfaces/room.ts b/packages/hms-video-store/src/interfaces/room.ts
index aa7ecafefb..073851b2f0 100644
--- a/packages/hms-video-store/src/interfaces/room.ts
+++ b/packages/hms-video-store/src/interfaces/room.ts
@@ -34,6 +34,7 @@ export interface HMSRoom {
max_size?: number;
large_room_optimization?: boolean;
isEffectsEnabled?: boolean;
+ disableNoneLayerRequest?: boolean;
isVBEnabled?: boolean;
effectsKey?: string;
isHipaaEnabled?: boolean;
diff --git a/packages/hms-video-store/src/media/tracks/HMSLocalAudioTrack.ts b/packages/hms-video-store/src/media/tracks/HMSLocalAudioTrack.ts
index 6446ab825b..b97efa49cd 100644
--- a/packages/hms-video-store/src/media/tracks/HMSLocalAudioTrack.ts
+++ b/packages/hms-video-store/src/media/tracks/HMSLocalAudioTrack.ts
@@ -184,8 +184,8 @@ export class HMSLocalAudioTrack extends HMSAudioTrack {
return;
}
- // Replace silent empty track with an actual audio track, if enabled.
- if (value && isEmptyTrack(this.nativeTrack)) {
+ // Replace silent empty track or muted track(happens when microphone is disabled from address bar in iOS) with an actual audio track, if enabled.
+ if (value && (isEmptyTrack(this.nativeTrack) || this.nativeTrack.muted)) {
await this.replaceTrackWith(this.settings);
}
await super.setEnabled(value);
diff --git a/packages/hms-video-store/src/media/tracks/HMSLocalVideoTrack.ts b/packages/hms-video-store/src/media/tracks/HMSLocalVideoTrack.ts
index de0b4043fa..5e5090ecd8 100644
--- a/packages/hms-video-store/src/media/tracks/HMSLocalVideoTrack.ts
+++ b/packages/hms-video-store/src/media/tracks/HMSLocalVideoTrack.ts
@@ -566,12 +566,7 @@ export class HMSLocalVideoTrack extends HMSVideoTrack {
if (document.visibilityState === 'hidden') {
this.enabledStateBeforeBackground = this.enabled;
if (this.enabled) {
- const track = await this.replaceTrackWithBlank();
- await this.replaceSender(track, this.enabled);
- this.nativeTrack?.stop();
- this.nativeTrack = track;
- } else {
- await this.replaceSender(this.nativeTrack, false);
+ await this.setEnabled(false);
}
// started interruption event
this.eventBus.analytics.publish(
@@ -581,12 +576,9 @@ export class HMSLocalVideoTrack extends HMSVideoTrack {
}),
);
} else {
- HMSLogger.d(this.TAG, 'visibility visibile, restoring track state', this.enabledStateBeforeBackground);
+ HMSLogger.d(this.TAG, 'visibility visible, restoring track state', this.enabledStateBeforeBackground);
if (this.enabledStateBeforeBackground) {
await this.setEnabled(true);
- } else {
- this.nativeTrack.enabled = this.enabledStateBeforeBackground;
- await this.replaceSender(this.nativeTrack, this.enabledStateBeforeBackground);
}
// ended interruption event
this.eventBus.analytics.publish(
@@ -596,6 +588,5 @@ export class HMSLocalVideoTrack extends HMSVideoTrack {
}),
);
}
- this.eventBus.localVideoEnabled.publish({ enabled: this.nativeTrack.enabled, track: this });
};
}
diff --git a/packages/hms-video-store/src/media/tracks/HMSRemoteVideoTrack.ts b/packages/hms-video-store/src/media/tracks/HMSRemoteVideoTrack.ts
index 61581a2c7c..abff98bb41 100644
--- a/packages/hms-video-store/src/media/tracks/HMSRemoteVideoTrack.ts
+++ b/packages/hms-video-store/src/media/tracks/HMSRemoteVideoTrack.ts
@@ -18,9 +18,11 @@ export class HMSRemoteVideoTrack extends HMSVideoTrack {
private history = new TrackHistory();
private preferredLayer: HMSPreferredSimulcastLayer = HMSSimulcastLayer.HIGH;
private bizTrackId!: string;
+ private disableNoneLayerRequest = false;
- constructor(stream: HMSRemoteStream, track: MediaStreamTrack, source?: string) {
+ constructor(stream: HMSRemoteStream, track: MediaStreamTrack, source?: string, disableNoneLayerRequest?: boolean) {
super(stream, track, source);
+ this.disableNoneLayerRequest = !!disableNoneLayerRequest;
this.setVideoHandler(new VideoElementManager(this));
}
@@ -146,10 +148,7 @@ export class HMSRemoteVideoTrack extends HMSVideoTrack {
* @returns {boolean} isDegraded - returns true if degraded
* */
setLayerFromServer(layerUpdate: VideoTrackLayerUpdate) {
- this._degraded =
- this.enabled &&
- (layerUpdate.publisher_degraded || layerUpdate.subscriber_degraded) &&
- layerUpdate.current_layer === HMSSimulcastLayer.NONE;
+ this._degraded = this.getDegradationValue(layerUpdate);
this._degradedAt = this._degraded ? new Date() : this._degradedAt;
const currentLayer = layerUpdate.current_layer;
HMSLogger.d(
@@ -169,8 +168,22 @@ export class HMSRemoteVideoTrack extends HMSVideoTrack {
return this._degraded;
}
+ private getDegradationValue(layerUpdate: VideoTrackLayerUpdate) {
+ return (
+ this.enabled &&
+ (layerUpdate.publisher_degraded || layerUpdate.subscriber_degraded) &&
+ layerUpdate.current_layer === HMSSimulcastLayer.NONE
+ );
+ }
+
private async updateLayer(source: string) {
- const newLayer = this.degraded || !this.enabled || !this.hasSinks() ? HMSSimulcastLayer.NONE : this.preferredLayer;
+ let newLayer: HMSSimulcastLayer = this.preferredLayer;
+ if (this.enabled && this.hasSinks()) {
+ newLayer = this.preferredLayer;
+ // send none only when the flag is not set
+ } else if (!this.disableNoneLayerRequest) {
+ newLayer = HMSSimulcastLayer.NONE;
+ }
if (!this.shouldSendVideoLayer(newLayer, source)) {
return;
}
diff --git a/packages/hms-video-store/src/media/tracks/RemoteVideoTrack.test.ts b/packages/hms-video-store/src/media/tracks/RemoteVideoTrack.test.ts
index 33729c1c3b..70fde7a17f 100644
--- a/packages/hms-video-store/src/media/tracks/RemoteVideoTrack.test.ts
+++ b/packages/hms-video-store/src/media/tracks/RemoteVideoTrack.test.ts
@@ -19,7 +19,7 @@ describe('remoteVideoTrack', () => {
const connection = { sendOverApiDataChannelWithResponse } as unknown as HMSSubscribeConnection;
stream = new HMSRemoteStream(nativeStream, connection);
nativeTrack = { id: trackId, kind: 'video', enabled: true } as MediaStreamTrack;
- track = new HMSRemoteVideoTrack(stream, nativeTrack, 'regular');
+ track = new HMSRemoteVideoTrack(stream, nativeTrack, 'regular', false);
window.MediaStream = jest.fn().mockImplementation(() => ({
addTrack: jest.fn(),
// Add any method you want to mock
@@ -156,3 +156,63 @@ describe('remoteVideoTrack', () => {
expectLayersSent([HMSSimulcastLayer.HIGH, HMSSimulcastLayer.NONE]);
});
});
+
+describe('HMSRemoteVideoTrack with disableNoneLayerRequest', () => {
+ let stream: HMSRemoteStream;
+ let sendOverApiDataChannelWithResponse: jest.Mock;
+ let track: HMSRemoteVideoTrack;
+ let nativeTrack: MediaStreamTrack;
+ let videoElement: HTMLVideoElement;
+ const trackId = 'test-track-id';
+
+ beforeEach(() => {
+ videoElement = document.createElement('video');
+ sendOverApiDataChannelWithResponse = jest.fn();
+ const connection = { sendOverApiDataChannelWithResponse } as unknown as HMSSubscribeConnection;
+ const nativeStream = new MediaStream();
+ stream = new HMSRemoteStream(nativeStream, connection);
+ nativeTrack = { id: trackId, kind: 'video', enabled: true } as MediaStreamTrack;
+ track = new HMSRemoteVideoTrack(stream, nativeTrack, 'regular', true); // disableNoneLayerRequest flag is set
+ track.setTrackId(trackId);
+
+ window.MediaStream = jest.fn().mockImplementation(() => ({
+ addTrack: jest.fn(),
+ }));
+ });
+
+ const expectLayersSent = (layers: HMSSimulcastLayer[]) => {
+ const allCalls = sendOverApiDataChannelWithResponse.mock.calls;
+ expect(allCalls.length).toBe(layers.length);
+ for (let i = 0; i < allCalls.length; i++) {
+ const data = allCalls[i][0];
+ expect(data.params.max_spatial_layer).toBe(layers[i]);
+ }
+ };
+
+ const sfuDegrades = () => {
+ track.setLayerFromServer({
+ subscriber_degraded: true,
+ expected_layer: HMSSimulcastLayer.HIGH,
+ current_layer: HMSSimulcastLayer.NONE,
+ publisher_degraded: false,
+ track_id: trackId,
+ });
+ };
+
+ test('disableNoneLayerRequest - degradation', async () => {
+ await track.addSink(videoElement);
+ expectLayersSent([HMSSimulcastLayer.HIGH]);
+
+ sfuDegrades();
+ expectLayersSent([HMSSimulcastLayer.HIGH]);
+ });
+
+ test('disableNoneLayerRequest - mute and removeSink', async () => {
+ await track.addSink(videoElement);
+ track.setEnabled(false);
+ expectLayersSent([HMSSimulcastLayer.HIGH]);
+
+ await track.removeSink(videoElement);
+ expectLayersSent([HMSSimulcastLayer.HIGH]);
+ });
+});
diff --git a/packages/hms-video-store/src/notification-manager/managers/onDemandTrackManager.ts b/packages/hms-video-store/src/notification-manager/managers/onDemandTrackManager.ts
index 5995d5ca21..d501201a53 100644
--- a/packages/hms-video-store/src/notification-manager/managers/onDemandTrackManager.ts
+++ b/packages/hms-video-store/src/notification-manager/managers/onDemandTrackManager.ts
@@ -67,7 +67,12 @@ export class OnDemandTrackManager extends TrackManager {
const remoteStream = new HMSRemoteStream(new MediaStream(), this.transport.getSubscribeConnection()!);
const emptyTrack = LocalTrackManager.getEmptyVideoTrack();
emptyTrack.enabled = !trackInfo.mute;
- const track = new HMSRemoteVideoTrack(remoteStream, emptyTrack, trackInfo.source);
+ const track = new HMSRemoteVideoTrack(
+ remoteStream,
+ emptyTrack,
+ trackInfo.source,
+ this.store.getRoom()?.disableNoneLayerRequest,
+ );
track.setTrackId(trackInfo.track_id);
track.peerId = hmsPeer.peerId;
track.logIdentifier = hmsPeer.name;
diff --git a/packages/hms-video-store/src/reactive-store/adapter.ts b/packages/hms-video-store/src/reactive-store/adapter.ts
index 245df38a35..c765a4c8b3 100644
--- a/packages/hms-video-store/src/reactive-store/adapter.ts
+++ b/packages/hms-video-store/src/reactive-store/adapter.ts
@@ -159,6 +159,7 @@ export class SDKToHMS {
peerCount: sdkRoom.peerCount,
isLargeRoom: sdkRoom.large_room_optimization,
isEffectsEnabled: sdkRoom.isEffectsEnabled,
+ disableNoneLayerRequest: sdkRoom.disableNoneLayerRequest,
isVBEnabled: sdkRoom.isVBEnabled,
effectsKey: sdkRoom.effectsKey,
isHipaaEnabled: sdkRoom.isHipaaEnabled,
diff --git a/packages/hms-video-store/src/schema/room.ts b/packages/hms-video-store/src/schema/room.ts
index 92a32a78b3..b14b2dacf5 100644
--- a/packages/hms-video-store/src/schema/room.ts
+++ b/packages/hms-video-store/src/schema/room.ts
@@ -40,6 +40,7 @@ export interface HMSRoom {
peerCount?: number;
isLargeRoom?: boolean;
isEffectsEnabled?: boolean;
+ disableNoneLayerRequest?: boolean;
isVBEnabled?: boolean;
effectsKey?: string;
isHipaaEnabled?: boolean;
diff --git a/packages/hms-video-store/src/sdk/models/HMSRoom.ts b/packages/hms-video-store/src/sdk/models/HMSRoom.ts
index 3438b97867..c23b03e0a9 100644
--- a/packages/hms-video-store/src/sdk/models/HMSRoom.ts
+++ b/packages/hms-video-store/src/sdk/models/HMSRoom.ts
@@ -16,6 +16,7 @@ export default class Room implements HMSRoom {
large_room_optimization?: boolean;
transcriptions?: HMSTranscriptionInfo[] = [];
isEffectsEnabled?: boolean;
+ disableNoneLayerRequest?: boolean;
isVBEnabled?: boolean;
effectsKey?: string;
isHipaaEnabled?: boolean;
diff --git a/packages/hms-video-store/src/signal/init/models.ts b/packages/hms-video-store/src/signal/init/models.ts
index a4a8ca7db5..918c4f80e1 100644
--- a/packages/hms-video-store/src/signal/init/models.ts
+++ b/packages/hms-video-store/src/signal/init/models.ts
@@ -62,4 +62,5 @@ export enum InitFlags {
FLAG_HIPAA_ENABLED = 'hipaa',
FLAG_NOISE_CANCELLATION = 'noiseCancellation',
FLAG_SCALE_SCREENSHARE_BASED_ON_PIXELS = 'scaleScreenshareBasedOnPixels',
+ FLAG_DISABLE_NONE_LAYER_REQUEST = 'disableNoneLayerRequest',
}
diff --git a/packages/hms-video-store/src/transport/RetryScheduler.ts b/packages/hms-video-store/src/transport/RetryScheduler.ts
index 8695e5a577..0feaf8c006 100644
--- a/packages/hms-video-store/src/transport/RetryScheduler.ts
+++ b/packages/hms-video-store/src/transport/RetryScheduler.ts
@@ -1,7 +1,7 @@
import { Dependencies as TFCDependencies, TransportFailureCategory as TFC } from './models/TransportFailureCategory';
import { TransportState } from './models/TransportState';
import { HMSException } from '../error/HMSException';
-import { MAX_TRANSPORT_RETRIES, MAX_TRANSPORT_RETRY_DELAY } from '../utils/constants';
+import { MAX_TRANSPORT_RETRY_TIME } from '../utils/constants';
import HMSLogger from '../utils/logger';
import { PromiseWithCallbacks } from '../utils/promise';
@@ -23,7 +23,7 @@ interface ScheduleTaskParams {
error: HMSException;
task: RetryTask;
originalState: TransportState;
- maxFailedRetries?: number;
+ maxRetryTime?: number;
changeState?: boolean;
}
@@ -42,10 +42,10 @@ export class RetryScheduler {
error,
task,
originalState,
- maxFailedRetries = MAX_TRANSPORT_RETRIES,
+ maxRetryTime = MAX_TRANSPORT_RETRY_TIME,
changeState = true,
}: ScheduleTaskParams) {
- await this.scheduleTask({ category, error, changeState, task, originalState, maxFailedRetries });
+ await this.scheduleTask({ category, error, changeState, task, originalState, maxRetryTime, failedAt: Date.now() });
}
reset() {
@@ -65,9 +65,10 @@ export class RetryScheduler {
changeState,
task,
originalState,
- maxFailedRetries = MAX_TRANSPORT_RETRIES,
+ failedAt,
+ maxRetryTime = MAX_TRANSPORT_RETRY_TIME,
failedRetryCount = 0,
- }: ScheduleTaskParams & { failedRetryCount?: number }): Promise {
+ }: ScheduleTaskParams & { failedAt: number; failedRetryCount?: number }): Promise {
HMSLogger.d(this.TAG, 'schedule: ', { category: TFC[category], error });
// First schedule call
@@ -113,8 +114,9 @@ export class RetryScheduler {
}
}
- if (failedRetryCount >= maxFailedRetries || hasFailedDependency) {
- error.description += `. [${TFC[category]}] Could not recover after ${failedRetryCount} tries`;
+ const timeElapsedSinceError = Date.now() - failedAt;
+ if (timeElapsedSinceError >= maxRetryTime || hasFailedDependency) {
+ error.description += `. [${TFC[category]}] Could not recover after ${timeElapsedSinceError} milliseconds`;
if (hasFailedDependency) {
error.description += ` Could not recover all of it's required dependencies - [${(dependencies as Array)
@@ -144,7 +146,7 @@ export class RetryScheduler {
this.onStateChange(TransportState.Reconnecting, error);
}
- const delay = this.getDelayForRetryCount(category, failedRetryCount);
+ const delay = this.getDelayForRetryCount(category);
HMSLogger.d(
this.TAG,
@@ -171,7 +173,10 @@ export class RetryScheduler {
if (changeState && this.inProgress.size === 0) {
this.onStateChange(originalState);
}
- HMSLogger.d(this.TAG, `schedule: [${TFC[category]}] [failedRetryCount=${failedRetryCount}] Recovered ♻️`);
+ HMSLogger.d(
+ this.TAG,
+ `schedule: [${TFC[category]}] [failedRetryCount=${failedRetryCount}] Recovered ♻️ after ${timeElapsedSinceError}ms`,
+ );
} else {
await this.scheduleTask({
category,
@@ -179,25 +184,23 @@ export class RetryScheduler {
changeState,
task,
originalState,
- maxFailedRetries,
+ maxRetryTime,
+ failedAt,
failedRetryCount: failedRetryCount + 1,
});
}
}
- private getBaseDelayForTask(category: TFC, n: number) {
+ private getDelayForRetryCount(category: TFC) {
+ const jitter = category === TFC.JoinWSMessageFailed ? Math.random() * 2 : Math.random();
+ let delaySeconds = 0;
if (category === TFC.JoinWSMessageFailed) {
// linear backoff(2 + jitter for every retry)
- return 2;
+ delaySeconds = 2 + jitter;
+ } else if (category === TFC.SignalDisconnect) {
+ delaySeconds = 1;
}
- // exponential backoff
- return Math.pow(2, n);
- }
-
- private getDelayForRetryCount(category: TFC, n: number) {
- const delay = this.getBaseDelayForTask(category, n);
- const jitter = category === TFC.JoinWSMessageFailed ? Math.random() * 2 : Math.random();
- return Math.round(Math.min(delay + jitter, MAX_TRANSPORT_RETRY_DELAY) * 1000);
+ return delaySeconds * 1000;
}
private async setTimeoutPromise(task: () => Promise, delay: number): Promise {
diff --git a/packages/hms-video-store/src/transport/index.ts b/packages/hms-video-store/src/transport/index.ts
index ade2759a0d..11b418a416 100644
--- a/packages/hms-video-store/src/transport/index.ts
+++ b/packages/hms-video-store/src/transport/index.ts
@@ -36,7 +36,6 @@ import { ISignalEventsObserver } from '../signal/ISignalEventsObserver';
import JsonRpcSignal from '../signal/jsonrpc';
import {
ICE_DISCONNECTION_TIMEOUT,
- MAX_TRANSPORT_RETRIES,
PROTOCOL_SPEC,
PROTOCOL_VERSION,
PUBLISH_STATS_PUSH_INTERVAL,
@@ -352,7 +351,6 @@ export default class HMSTransport {
error,
task,
originalState: this.state,
- maxFailedRetries: MAX_TRANSPORT_RETRIES,
changeState: false,
});
} else {
@@ -923,7 +921,6 @@ export default class HMSTransport {
error: hmsError,
task,
originalState: TransportState.Joined,
- maxFailedRetries: 3,
changeState: false,
});
} else {
@@ -1087,7 +1084,6 @@ export default class HMSTransport {
error,
task: this.retrySubscribeIceFailedTask,
originalState: TransportState.Joined,
- maxFailedRetries: 1,
});
}
}
@@ -1109,6 +1105,7 @@ export default class HMSTransport {
if (room) {
room.effectsKey = this.initConfig.config.vb?.effectsKey;
room.isEffectsEnabled = this.isFlagEnabled(InitFlags.FLAG_EFFECTS_SDK_ENABLED);
+ room.disableNoneLayerRequest = this.isFlagEnabled(InitFlags.FLAG_DISABLE_NONE_LAYER_REQUEST);
room.isVBEnabled = this.isFlagEnabled(InitFlags.FLAG_VB_ENABLED);
room.isHipaaEnabled = this.isFlagEnabled(InitFlags.FLAG_HIPAA_ENABLED);
room.isNoiseCancellationEnabled = this.isFlagEnabled(InitFlags.FLAG_NOISE_CANCELLATION);
diff --git a/packages/hms-video-store/src/utils/constants.ts b/packages/hms-video-store/src/utils/constants.ts
index 4e9632bc96..5f237a85d3 100644
--- a/packages/hms-video-store/src/utils/constants.ts
+++ b/packages/hms-video-store/src/utils/constants.ts
@@ -3,13 +3,12 @@ export const API_DATA_CHANNEL = 'ion-sfu';
export const ANALYTICS_BUFFER_SIZE = 100;
/**
- * Maximum number of retries that transport-layer will try
+ * Maximum time that transport-layer will try
* before giving up on the connection and returning a failure
*
* Refer https://100ms.atlassian.net/browse/HMS-2369
*/
-export const MAX_TRANSPORT_RETRIES = 5;
-export const MAX_TRANSPORT_RETRY_DELAY = 60;
+export const MAX_TRANSPORT_RETRY_TIME = 60_000;
export const DEFAULT_SIGNAL_PING_TIMEOUT = 12_000;
export const DEFAULT_SIGNAL_PING_INTERVAL = 3_000;
diff --git a/packages/hms-video-store/src/utils/media.ts b/packages/hms-video-store/src/utils/media.ts
index 9e669e61aa..c9740bcc0a 100644
--- a/packages/hms-video-store/src/utils/media.ts
+++ b/packages/hms-video-store/src/utils/media.ts
@@ -63,3 +63,17 @@ export const HMSAudioContextHandler: HMSAudioContext = {
}
},
};
+
+export const getAudioDeviceCategory = (deviceLabel: string) => {
+ const label = deviceLabel.toLowerCase();
+ if (label.includes('speakerphone')) {
+ return 'speakerhone';
+ } else if (label.includes('wired')) {
+ return 'wired';
+ } else if (/airpods|buds|wireless|bluetooth/gi.test(label)) {
+ return 'bluetooth';
+ } else if (label.includes('earpiece')) {
+ return 'earpiece';
+ }
+ return 'speakerphone';
+};
diff --git a/packages/hms-virtual-background/package.json b/packages/hms-virtual-background/package.json
index 7e4aef1d06..8aadb05854 100755
--- a/packages/hms-virtual-background/package.json
+++ b/packages/hms-virtual-background/package.json
@@ -1,5 +1,5 @@
{
- "version": "1.13.20",
+ "version": "1.13.21",
"license": "MIT",
"name": "@100mslive/hms-virtual-background",
"author": "100ms",
@@ -62,10 +62,10 @@
"format": "prettier --write src/**/*.ts"
},
"peerDependencies": {
- "@100mslive/hms-video-store": "0.12.20"
+ "@100mslive/hms-video-store": "0.12.21"
},
"devDependencies": {
- "@100mslive/hms-video-store": "0.12.20"
+ "@100mslive/hms-video-store": "0.12.21"
},
"dependencies": {
"@mediapipe/selfie_segmentation": "^0.1.1632777926",
diff --git a/packages/hms-whiteboard/package.json b/packages/hms-whiteboard/package.json
index 1d28822d5d..b311a63ce3 100644
--- a/packages/hms-whiteboard/package.json
+++ b/packages/hms-whiteboard/package.json
@@ -2,7 +2,7 @@
"name": "@100mslive/hms-whiteboard",
"author": "100ms",
"license": "MIT",
- "version": "0.0.10",
+ "version": "0.0.11",
"main": "dist/index.cjs.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/packages/react-icons/package.json b/packages/react-icons/package.json
index 5e92a12522..3e2cbbbf78 100644
--- a/packages/react-icons/package.json
+++ b/packages/react-icons/package.json
@@ -4,7 +4,7 @@
"main": "dist/index.cjs.js",
"module": "dist/index.js",
"typings": "dist/index.d.ts",
- "version": "0.10.20",
+ "version": "0.10.21",
"author": "100ms",
"license": "MIT",
"repository": {
diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json
index 36dccbbf5f..8311a52f08 100644
--- a/packages/react-sdk/package.json
+++ b/packages/react-sdk/package.json
@@ -4,7 +4,7 @@
"main": "dist/index.cjs.js",
"module": "dist/index.js",
"typings": "dist/index.d.ts",
- "version": "0.10.20",
+ "version": "0.10.21",
"author": "100ms",
"license": "MIT",
"repository": {
@@ -48,7 +48,7 @@
"react": ">=16.8 <19.0.0"
},
"dependencies": {
- "@100mslive/hms-video-store": "0.12.20",
+ "@100mslive/hms-video-store": "0.12.21",
"react-resize-detector": "^7.0.0",
"zustand": "^3.6.2"
}
diff --git a/packages/roomkit-react/README.md b/packages/roomkit-react/README.md
index ebf9faad42..92bff98b9c 100644
--- a/packages/roomkit-react/README.md
+++ b/packages/roomkit-react/README.md
@@ -43,10 +43,10 @@ export default App() {
For additional props, refer the [docs](https://www.100ms.live/docs/javascript/v2/quickstart/prebuilt-quickstart#props-for-hmsprebuilt)
-## Cutomization
+## Customisation
While we offer [a no-code way to customize Prebuilt](https://www.100ms.live/docs/get-started/v2/get-started/prebuilt/overview#customize-prebuilt), you can fork your copy of the Prebuilt component and make changes to the code to allow for more fine-tuning.
-Prebuilt customisations are available on [100ms dashboard](https://dashboard.100ms.live)
+Prebuilt customisations are available on [100ms Dashboard](https://dashboard.100ms.live).
### Understanding the Structure
@@ -58,7 +58,7 @@ The `Prebuilt` folder contains the full Prebuilt implementation.
| Component | Description |
|--|--|
-| [RoomLayoutProvider](src/Prebuilt/provider/roomLayoutProvider/index.tsx) | This is a context that contains the configuration from the dashboard [customiser](dashboard.100ms.live). Whatever changes are made in the dashboard customiser are available the next time you join.|
+| [RoomLayoutProvider](src/Prebuilt/provider/roomLayoutProvider/index.tsx) | This is a context that contains the configuration from the dashboard [customiser](https://dashboard.100ms.live/). Whatever changes are made in the dashboard customiser are available the next time you join.|
|[AppStateContext](src/Prebuilt/AppStateContext.tsx) | Contains the logic to switch between different screens, for example, Preview to Meeting, Meeting to Leave. These transitions are based on the roomState that is available from the reactive store (`useHMSStore(selectHMSRoomState)`). |
| [PreviewScreen](src/Prebuilt/components/Preview/PreviewScreen.tsx) | Contains the Preview implementation. Contains the Video tile, video, audio toggles and Virtual background and settings along with the name input.|
| [ConferenceScreen](src/Prebuilt/components/ConferenceScreen.tsx) | This contains the screen once you finish Preview and enter the meeting. This contains the header and footer and the main content.|
@@ -68,11 +68,11 @@ The `Prebuilt` folder contains the full Prebuilt implementation.
### Customising the Styles
-[Base Config](/src/Theme/base.config.ts) has all the variables that you can use. Any changes you want for the theme can be made here. Most likely no additional changes will be required unless you want to introduce new variables.
+[Base Config](./src/Theme/base.config.ts) has all the variables that you can use. Any changes you want for the theme can be made here. Most likely no additional changes will be required unless you want to introduce new variables.
-When [`HMSThemeProvider`](src/Theme/ThemeProvider.tsx) is used at the top level, all the variables will be available for all the children under this component tree.
+When [`HMSThemeProvider`](./src/Theme/ThemeProvider.tsx) is used at the top level, all the variables will be available for all the children under this component tree.
-For components created using the base components like `Box`, `Flex`, `Button` etc, css Prop is available to modify the styles. Within the css prop, you can access the variables from the [base config](/src/Theme/base.config.ts).
+For components created using the base components like `Box`, `Flex`, `Button` etc, css Prop is available to modify the styles. Within the css prop, you can access the variables from the [base config](./src/Theme/base.config.ts).
## Contributing
diff --git a/packages/roomkit-react/package.json b/packages/roomkit-react/package.json
index 64143afab3..ec97436310 100644
--- a/packages/roomkit-react/package.json
+++ b/packages/roomkit-react/package.json
@@ -10,7 +10,7 @@
"prebuilt",
"roomkit"
],
- "version": "0.3.20",
+ "version": "0.3.21",
"author": "100ms",
"license": "MIT",
"repository": {
@@ -75,12 +75,12 @@
"react": ">=17.0.2 <19.0.0"
},
"dependencies": {
- "@100mslive/hls-player": "0.3.20",
+ "@100mslive/hls-player": "0.3.21",
"@100mslive/hms-noise-cancellation": "0.0.1",
- "@100mslive/hms-virtual-background": "1.13.20",
- "@100mslive/hms-whiteboard": "0.0.10",
- "@100mslive/react-icons": "0.10.20",
- "@100mslive/react-sdk": "0.10.20",
+ "@100mslive/hms-virtual-background": "1.13.21",
+ "@100mslive/hms-whiteboard": "0.0.11",
+ "@100mslive/react-icons": "0.10.21",
+ "@100mslive/react-sdk": "0.10.21",
"@100mslive/types-prebuilt": "0.12.12",
"@emoji-mart/data": "^1.0.6",
"@emoji-mart/react": "^1.0.1",
diff --git a/packages/roomkit-react/src/Prebuilt/App.tsx b/packages/roomkit-react/src/Prebuilt/App.tsx
index 94379b53c8..8010a58115 100644
--- a/packages/roomkit-react/src/Prebuilt/App.tsx
+++ b/packages/roomkit-react/src/Prebuilt/App.tsx
@@ -143,10 +143,10 @@ export const HMSPrebuilt = React.forwardRef = {
logo,
@@ -288,7 +288,7 @@ function AppRoutes({
authTokenByRoomCodeEndpoint,
defaultAuthToken,
}: {
- authTokenByRoomCodeEndpoint: string;
+ authTokenByRoomCodeEndpoint?: string;
defaultAuthToken?: string;
}) {
const roomLayout = useRoomLayout();
diff --git a/packages/roomkit-react/src/Prebuilt/AppContext.tsx b/packages/roomkit-react/src/Prebuilt/AppContext.tsx
index 0f8a093fae..8405d2a776 100644
--- a/packages/roomkit-react/src/Prebuilt/AppContext.tsx
+++ b/packages/roomkit-react/src/Prebuilt/AppContext.tsx
@@ -7,7 +7,7 @@ type HMSPrebuiltContextType = {
userName?: string;
userId?: string;
containerSelector: string;
- endpoints?: Record;
+ endpoints?: Record;
onLeave?: () => void;
onJoin?: () => void;
};
diff --git a/packages/roomkit-react/src/Prebuilt/components/AuthToken.tsx b/packages/roomkit-react/src/Prebuilt/components/AuthToken.tsx
index 7243fe1b59..ed83c3a51b 100644
--- a/packages/roomkit-react/src/Prebuilt/components/AuthToken.tsx
+++ b/packages/roomkit-react/src/Prebuilt/components/AuthToken.tsx
@@ -25,7 +25,7 @@ import { APP_DATA } from '../common/constants';
* ui_mode=activespeaker => lands in active speaker mode after joining the room
*/
const AuthToken = React.memo<{
- authTokenByRoomCodeEndpoint: string;
+ authTokenByRoomCodeEndpoint?: string;
defaultAuthToken?: string;
activeState?: PrebuiltStates;
}>(({ authTokenByRoomCodeEndpoint, defaultAuthToken, activeState }) => {
diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx
index 89b123df0d..5ba399c1d4 100644
--- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx
@@ -1,7 +1,28 @@
-import React, { forwardRef } from 'react';
+import React, { forwardRef, useEffect, useState } from 'react';
import { Flex } from '../../../Layout';
export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => {
+ const [width, setWidth] = useState('auto');
+
+ useEffect(() => {
+ const updatingVideoWidth = () => {
+ const videoEl = videoRef.current;
+ if (!videoEl) {
+ return;
+ }
+ if (videoEl.videoWidth > videoEl.videoHeight && width !== '100%') {
+ setWidth('100%');
+ }
+ };
+ const videoEl = videoRef.current;
+ if (!videoEl) {
+ return;
+ }
+ videoEl.addEventListener('loadedmetadata', updatingVideoWidth);
+ return () => {
+ videoEl.removeEventListener('loadedmetadata', updatingVideoWidth);
+ };
+ }, []);
return (
{
justifyContent: 'center',
transition: 'all 0.3s ease-in-out',
'@md': {
- height: 'auto',
'& video': {
- height: '$60 !important',
+ height: props.isFullScreen ? '' : '$60 !important',
},
},
'& video::cue': {
@@ -41,7 +61,7 @@ export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => {
style={{
margin: '0 auto',
objectFit: 'contain',
- width: 'auto',
+ width: width,
height: '100%',
maxWidth: '100%',
}}
diff --git a/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx b/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
index 60a7627521..3a36c01ad8 100644
--- a/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
+++ b/packages/roomkit-react/src/Prebuilt/components/Header/common.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import {
DeviceType,
+ getAudioDeviceCategory,
selectIsLocalVideoEnabled,
selectLocalVideoTrackID,
selectVideoTrackByID,
@@ -78,12 +79,13 @@ export const AudioActions = () => {
if (!audioFiltered) {
return null;
}
+ const deviceCategory = getAudioDeviceCategory(currentSelection.label);
let AudioIcon = ;
- if (currentSelection && currentSelection.label.toLowerCase().includes('bluetooth')) {
+ if (deviceCategory === 'bluetooth') {
AudioIcon = ;
- } else if (currentSelection && currentSelection.label.toLowerCase().includes('wired')) {
+ } else if (deviceCategory === 'wired') {
AudioIcon = ;
- } else if (currentSelection && currentSelection.label.toLowerCase().includes('earpiece')) {
+ } else if (deviceCategory === 'earpiece') {
AudioIcon = ;
}
return (
diff --git a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.tsx b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.tsx
index 51a7c03b40..c775e08a69 100644
--- a/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.tsx
+++ b/packages/roomkit-react/src/Prebuilt/components/MoreSettings/ChangeNameContent.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import { ChevronLeftIcon, CrossIcon } from '@100mslive/react-icons';
import { Button } from '../../../Button';
import { Input } from '../../../Input';
@@ -22,6 +22,16 @@ export const ChangeNameContent = ({
onExit: () => void;
onBackClick: () => void;
}) => {
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (isMobile) {
+ setTimeout(() => {
+ inputRef.current?.focus();
+ }, 200);
+ }
+ }, [isMobile]);
+
return (