@stream-io/video-client 0.5.0 → 0.5.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/CHANGELOG.md +14 -0
- package/README.md +4 -5
- package/dist/index.browser.es.js +277 -173
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +276 -172
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +277 -173
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +7 -2
- package/dist/src/devices/MicrophoneManager.d.ts +1 -0
- package/dist/src/helpers/RNSpeechDetector.d.ts +18 -0
- package/dist/src/stats/types.d.ts +1 -0
- package/dist/src/store/stateStore.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +25 -41
- package/src/__tests__/server-side/call.test.ts +5 -1
- package/src/devices/MicrophoneManager.ts +24 -10
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +126 -0
- package/src/helpers/RNSpeechDetector.ts +112 -0
- package/src/rtc/Publisher.ts +3 -19
- package/src/stats/types.ts +1 -0
- package/src/store/stateStore.ts +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
### [0.5.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.5.1...@stream-io/video-client-0.5.2) (2023-12-11)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **ringing:** Auto-Cancel outgoing calls ([#1217](https://github.com/GetStream/stream-video-js/issues/1217)) ([c4d557b](https://github.com/GetStream/stream-video-js/commit/c4d557b736df8ff0a95166d1f9f0a52d4a57a122)), closes [#1215](https://github.com/GetStream/stream-video-js/issues/1215)
|
|
11
|
+
|
|
12
|
+
### [0.5.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.5.0...@stream-io/video-client-0.5.1) (2023-12-05)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **client:** speaking while muted in React Native using temporary peer connection ([#1207](https://github.com/GetStream/stream-video-js/issues/1207)) ([9093006](https://github.com/GetStream/stream-video-js/commit/90930063503b6dfb83572dad8a31e45b16bf1685))
|
|
18
|
+
|
|
5
19
|
## [0.5.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.4.10...@stream-io/video-client-0.5.0) (2023-11-29)
|
|
6
20
|
|
|
7
21
|
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Official
|
|
1
|
+
# Official JavaScript SDK and Low-Level Client for [Stream Video](https://getstream.io/video/docs/)
|
|
2
2
|
|
|
3
3
|
<img src="../../.readme-assets/Github-Graphic-JS.jpg" alt="Stream Video for JavaScript Header image" style="box-shadow: 0 3px 10px rgb(0 0 0 / 0.2); border-radius: 1rem" />
|
|
4
4
|
|
|
@@ -7,7 +7,6 @@ Low-level Video SDK client for browser and Node.js integrations.
|
|
|
7
7
|
## **Quick Links**
|
|
8
8
|
|
|
9
9
|
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Video
|
|
10
|
-
TODO: add links to docs and tutorials
|
|
11
10
|
|
|
12
11
|
## What is Stream?
|
|
13
12
|
|
|
@@ -15,9 +14,9 @@ Stream allows developers to rapidly deploy scalable feeds, chat messaging and vi
|
|
|
15
14
|
|
|
16
15
|
With Stream's video components, you can use their SDK to build in-app video calling, audio rooms, audio calls, or live streaming. The best place to get started is with their tutorials:
|
|
17
16
|
|
|
18
|
-
- [Video
|
|
19
|
-
- Audio Rooms Tutorial
|
|
20
|
-
- [
|
|
17
|
+
- [Video and Audio Calling Tutorial](https://getstream.io/video/sdk/javascript/tutorial/video-calling/)
|
|
18
|
+
- [Audio Rooms Tutorial](https://getstream.io/video/sdk/javascript/tutorial/audio-room/)
|
|
19
|
+
- [Livestream Tutorial](https://getstream.io/video/sdk/javascript/tutorial/livestreaming/)
|
|
21
20
|
|
|
22
21
|
Stream provides UI components and state handling that make it easy to build video calling for your app. All calls run on Stream's network of edge servers around the world, ensuring optimal latency and reliability.
|
|
23
22
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
|
|
|
4
4
|
import axios, { AxiosHeaders } from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
|
|
7
|
-
import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, merge, from, Observable, debounceTime, concatMap, pairwise, of, filter,
|
|
7
|
+
import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, merge, from, Observable, debounceTime, concatMap, pairwise, of, filter, debounce, timer } from 'rxjs';
|
|
8
8
|
import * as SDP from 'sdp-transform';
|
|
9
9
|
import { UAParser } from 'ua-parser-js';
|
|
10
10
|
import WebSocket from 'isomorphic-ws';
|
|
@@ -6492,7 +6492,7 @@ class Publisher {
|
|
|
6492
6492
|
*/
|
|
6493
6493
|
const handleTrackEnded = async () => {
|
|
6494
6494
|
logger$3('info', `Track ${TrackType[trackType]} has ended, notifying the SFU`);
|
|
6495
|
-
await this.notifyTrackMuteStateChanged(mediaStream,
|
|
6495
|
+
await this.notifyTrackMuteStateChanged(mediaStream, trackType, true);
|
|
6496
6496
|
// clean-up, this event listener needs to run only once.
|
|
6497
6497
|
track.removeEventListener('ended', handleTrackEnded);
|
|
6498
6498
|
};
|
|
@@ -6548,7 +6548,7 @@ class Publisher {
|
|
|
6548
6548
|
}
|
|
6549
6549
|
await transceiver.sender.replaceTrack(track);
|
|
6550
6550
|
}
|
|
6551
|
-
await this.notifyTrackMuteStateChanged(mediaStream,
|
|
6551
|
+
await this.notifyTrackMuteStateChanged(mediaStream, trackType, false);
|
|
6552
6552
|
};
|
|
6553
6553
|
/**
|
|
6554
6554
|
* Stops publishing the given track type to the SFU, if it is currently being published.
|
|
@@ -6570,7 +6570,7 @@ class Publisher {
|
|
|
6570
6570
|
: (transceiver.sender.track.enabled = false);
|
|
6571
6571
|
// We don't need to notify SFU if unpublishing in response to remote soft mute
|
|
6572
6572
|
if (this.state.localParticipant?.publishedTracks.includes(trackType)) {
|
|
6573
|
-
await this.notifyTrackMuteStateChanged(undefined,
|
|
6573
|
+
await this.notifyTrackMuteStateChanged(undefined, trackType, true);
|
|
6574
6574
|
}
|
|
6575
6575
|
}
|
|
6576
6576
|
};
|
|
@@ -6602,7 +6602,7 @@ class Publisher {
|
|
|
6602
6602
|
}
|
|
6603
6603
|
return false;
|
|
6604
6604
|
};
|
|
6605
|
-
this.notifyTrackMuteStateChanged = async (mediaStream,
|
|
6605
|
+
this.notifyTrackMuteStateChanged = async (mediaStream, trackType, isMuted) => {
|
|
6606
6606
|
await this.sfuClient.updateMuteState(trackType, isMuted);
|
|
6607
6607
|
const audioOrVideoOrScreenShareStream = trackTypeToParticipantStreamKey(trackType);
|
|
6608
6608
|
if (isMuted) {
|
|
@@ -7646,131 +7646,6 @@ var rxUtils = /*#__PURE__*/Object.freeze({
|
|
|
7646
7646
|
setCurrentValue: setCurrentValue
|
|
7647
7647
|
});
|
|
7648
7648
|
|
|
7649
|
-
class StreamVideoWriteableStateStore {
|
|
7650
|
-
constructor() {
|
|
7651
|
-
/**
|
|
7652
|
-
* A store keeping data of a successfully connected user over WS to the coordinator server.
|
|
7653
|
-
*/
|
|
7654
|
-
this.connectedUserSubject = new BehaviorSubject(undefined);
|
|
7655
|
-
/**
|
|
7656
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
7657
|
-
*/
|
|
7658
|
-
this.callsSubject = new BehaviorSubject([]);
|
|
7659
|
-
/**
|
|
7660
|
-
* Gets the current value of an observable, or undefined if the observable has
|
|
7661
|
-
* not emitted a value yet.
|
|
7662
|
-
*
|
|
7663
|
-
* @param observable$ the observable to get the value from.
|
|
7664
|
-
*/
|
|
7665
|
-
this.getCurrentValue = getCurrentValue;
|
|
7666
|
-
/**
|
|
7667
|
-
* Updates the value of the provided Subject.
|
|
7668
|
-
* An `update` can either be a new value or a function which takes
|
|
7669
|
-
* the current value and returns a new value.
|
|
7670
|
-
*
|
|
7671
|
-
* @param subject the subject to update.
|
|
7672
|
-
* @param update the update to apply to the subject.
|
|
7673
|
-
* @return the updated value.
|
|
7674
|
-
*/
|
|
7675
|
-
this.setCurrentValue = setCurrentValue;
|
|
7676
|
-
/**
|
|
7677
|
-
* Sets the currently connected user.
|
|
7678
|
-
*
|
|
7679
|
-
* @internal
|
|
7680
|
-
* @param user the user to set as connected.
|
|
7681
|
-
*/
|
|
7682
|
-
this.setConnectedUser = (user) => {
|
|
7683
|
-
return this.setCurrentValue(this.connectedUserSubject, user);
|
|
7684
|
-
};
|
|
7685
|
-
/**
|
|
7686
|
-
* Sets the list of {@link Call} objects created/tracked by this client.
|
|
7687
|
-
* @param calls
|
|
7688
|
-
*/
|
|
7689
|
-
this.setCalls = (calls) => {
|
|
7690
|
-
return this.setCurrentValue(this.callsSubject, calls);
|
|
7691
|
-
};
|
|
7692
|
-
/**
|
|
7693
|
-
* Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
|
|
7694
|
-
*
|
|
7695
|
-
* @param call the call to add.
|
|
7696
|
-
*/
|
|
7697
|
-
this.registerCall = (call) => {
|
|
7698
|
-
if (!this.calls.find((c) => c.cid === call.cid)) {
|
|
7699
|
-
this.setCalls((calls) => [...calls, call]);
|
|
7700
|
-
}
|
|
7701
|
-
};
|
|
7702
|
-
/**
|
|
7703
|
-
* Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
|
|
7704
|
-
*
|
|
7705
|
-
* @param call the call to remove
|
|
7706
|
-
*/
|
|
7707
|
-
this.unregisterCall = (call) => {
|
|
7708
|
-
return this.setCalls((calls) => calls.filter((c) => c !== call));
|
|
7709
|
-
};
|
|
7710
|
-
/**
|
|
7711
|
-
* Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
|
|
7712
|
-
*
|
|
7713
|
-
* @param type the type of call to find.
|
|
7714
|
-
* @param id the id of the call to find.
|
|
7715
|
-
*/
|
|
7716
|
-
this.findCall = (type, id) => {
|
|
7717
|
-
return this.calls.find((c) => c.type === type && c.id === id);
|
|
7718
|
-
};
|
|
7719
|
-
this.connectedUserSubject.subscribe(async (user) => {
|
|
7720
|
-
// leave all calls when the user disconnects.
|
|
7721
|
-
if (!user) {
|
|
7722
|
-
for (const call of this.calls) {
|
|
7723
|
-
getLogger(['client-state'])('info', `User disconnected, leaving call: ${call.cid}`);
|
|
7724
|
-
await call.leave();
|
|
7725
|
-
}
|
|
7726
|
-
}
|
|
7727
|
-
});
|
|
7728
|
-
}
|
|
7729
|
-
/**
|
|
7730
|
-
* The currently connected user.
|
|
7731
|
-
*/
|
|
7732
|
-
get connectedUser() {
|
|
7733
|
-
return this.getCurrentValue(this.connectedUserSubject);
|
|
7734
|
-
}
|
|
7735
|
-
/**
|
|
7736
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
7737
|
-
*/
|
|
7738
|
-
get calls() {
|
|
7739
|
-
return this.getCurrentValue(this.callsSubject);
|
|
7740
|
-
}
|
|
7741
|
-
}
|
|
7742
|
-
/**
|
|
7743
|
-
* A reactive store that exposes state variables in a reactive manner.
|
|
7744
|
-
* You can subscribe to changes of the different state variables.
|
|
7745
|
-
* This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
|
|
7746
|
-
*/
|
|
7747
|
-
class StreamVideoReadOnlyStateStore {
|
|
7748
|
-
constructor(store) {
|
|
7749
|
-
/**
|
|
7750
|
-
* This method allows you the get the current value of a state variable.
|
|
7751
|
-
*
|
|
7752
|
-
* @param observable the observable to get the current value of.
|
|
7753
|
-
* @returns the current value of the observable.
|
|
7754
|
-
*/
|
|
7755
|
-
this.getCurrentValue = getCurrentValue;
|
|
7756
|
-
// convert and expose subjects as observables
|
|
7757
|
-
this.connectedUser$ = store.connectedUserSubject.asObservable();
|
|
7758
|
-
this.calls$ = store.callsSubject.asObservable();
|
|
7759
|
-
}
|
|
7760
|
-
/**
|
|
7761
|
-
* The current user connected over WS to the backend.
|
|
7762
|
-
*/
|
|
7763
|
-
get connectedUser() {
|
|
7764
|
-
return getCurrentValue(this.connectedUser$);
|
|
7765
|
-
}
|
|
7766
|
-
/**
|
|
7767
|
-
* A list of {@link Call} objects created/tracked by this client.
|
|
7768
|
-
*/
|
|
7769
|
-
get calls() {
|
|
7770
|
-
return getCurrentValue(this.calls$);
|
|
7771
|
-
}
|
|
7772
|
-
}
|
|
7773
|
-
|
|
7774
7649
|
/**
|
|
7775
7650
|
* Creates a new combined {@link Comparator<T>} which sorts items by the given comparators.
|
|
7776
7651
|
* The comparators are applied in the order they are given (left -> right).
|
|
@@ -8729,6 +8604,136 @@ class CallState {
|
|
|
8729
8604
|
}
|
|
8730
8605
|
}
|
|
8731
8606
|
|
|
8607
|
+
class StreamVideoWriteableStateStore {
|
|
8608
|
+
constructor() {
|
|
8609
|
+
/**
|
|
8610
|
+
* A store keeping data of a successfully connected user over WS to the coordinator server.
|
|
8611
|
+
*/
|
|
8612
|
+
this.connectedUserSubject = new BehaviorSubject(undefined);
|
|
8613
|
+
/**
|
|
8614
|
+
* A list of {@link Call} objects created/tracked by this client.
|
|
8615
|
+
*/
|
|
8616
|
+
this.callsSubject = new BehaviorSubject([]);
|
|
8617
|
+
/**
|
|
8618
|
+
* Gets the current value of an observable, or undefined if the observable has
|
|
8619
|
+
* not emitted a value yet.
|
|
8620
|
+
*
|
|
8621
|
+
* @param observable$ the observable to get the value from.
|
|
8622
|
+
*/
|
|
8623
|
+
this.getCurrentValue = getCurrentValue;
|
|
8624
|
+
/**
|
|
8625
|
+
* Updates the value of the provided Subject.
|
|
8626
|
+
* An `update` can either be a new value or a function which takes
|
|
8627
|
+
* the current value and returns a new value.
|
|
8628
|
+
*
|
|
8629
|
+
* @param subject the subject to update.
|
|
8630
|
+
* @param update the update to apply to the subject.
|
|
8631
|
+
* @return the updated value.
|
|
8632
|
+
*/
|
|
8633
|
+
this.setCurrentValue = setCurrentValue;
|
|
8634
|
+
/**
|
|
8635
|
+
* Sets the currently connected user.
|
|
8636
|
+
*
|
|
8637
|
+
* @internal
|
|
8638
|
+
* @param user the user to set as connected.
|
|
8639
|
+
*/
|
|
8640
|
+
this.setConnectedUser = (user) => {
|
|
8641
|
+
return this.setCurrentValue(this.connectedUserSubject, user);
|
|
8642
|
+
};
|
|
8643
|
+
/**
|
|
8644
|
+
* Sets the list of {@link Call} objects created/tracked by this client.
|
|
8645
|
+
* @param calls
|
|
8646
|
+
*/
|
|
8647
|
+
this.setCalls = (calls) => {
|
|
8648
|
+
return this.setCurrentValue(this.callsSubject, calls);
|
|
8649
|
+
};
|
|
8650
|
+
/**
|
|
8651
|
+
* Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
|
|
8652
|
+
*
|
|
8653
|
+
* @param call the call to add.
|
|
8654
|
+
*/
|
|
8655
|
+
this.registerCall = (call) => {
|
|
8656
|
+
if (!this.calls.find((c) => c.cid === call.cid)) {
|
|
8657
|
+
this.setCalls((calls) => [...calls, call]);
|
|
8658
|
+
}
|
|
8659
|
+
};
|
|
8660
|
+
/**
|
|
8661
|
+
* Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
|
|
8662
|
+
*
|
|
8663
|
+
* @param call the call to remove
|
|
8664
|
+
*/
|
|
8665
|
+
this.unregisterCall = (call) => {
|
|
8666
|
+
return this.setCalls((calls) => calls.filter((c) => c !== call));
|
|
8667
|
+
};
|
|
8668
|
+
/**
|
|
8669
|
+
* Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
|
|
8670
|
+
*
|
|
8671
|
+
* @param type the type of call to find.
|
|
8672
|
+
* @param id the id of the call to find.
|
|
8673
|
+
*/
|
|
8674
|
+
this.findCall = (type, id) => {
|
|
8675
|
+
return this.calls.find((c) => c.type === type && c.id === id);
|
|
8676
|
+
};
|
|
8677
|
+
this.connectedUserSubject.subscribe(async (user) => {
|
|
8678
|
+
// leave all calls when the user disconnects.
|
|
8679
|
+
if (!user) {
|
|
8680
|
+
const logger = getLogger(['client-state']);
|
|
8681
|
+
for (const call of this.calls) {
|
|
8682
|
+
if (call.state.callingState === CallingState.LEFT)
|
|
8683
|
+
continue;
|
|
8684
|
+
logger('info', `User disconnected, leaving call: ${call.cid}`);
|
|
8685
|
+
await call.leave().catch((err) => {
|
|
8686
|
+
logger('error', `Error leaving call: ${call.cid}`, err);
|
|
8687
|
+
});
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8690
|
+
});
|
|
8691
|
+
}
|
|
8692
|
+
/**
|
|
8693
|
+
* The currently connected user.
|
|
8694
|
+
*/
|
|
8695
|
+
get connectedUser() {
|
|
8696
|
+
return this.getCurrentValue(this.connectedUserSubject);
|
|
8697
|
+
}
|
|
8698
|
+
/**
|
|
8699
|
+
* A list of {@link Call} objects created/tracked by this client.
|
|
8700
|
+
*/
|
|
8701
|
+
get calls() {
|
|
8702
|
+
return this.getCurrentValue(this.callsSubject);
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
/**
|
|
8706
|
+
* A reactive store that exposes state variables in a reactive manner.
|
|
8707
|
+
* You can subscribe to changes of the different state variables.
|
|
8708
|
+
* This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
|
|
8709
|
+
*/
|
|
8710
|
+
class StreamVideoReadOnlyStateStore {
|
|
8711
|
+
constructor(store) {
|
|
8712
|
+
/**
|
|
8713
|
+
* This method allows you the get the current value of a state variable.
|
|
8714
|
+
*
|
|
8715
|
+
* @param observable the observable to get the current value of.
|
|
8716
|
+
* @returns the current value of the observable.
|
|
8717
|
+
*/
|
|
8718
|
+
this.getCurrentValue = getCurrentValue;
|
|
8719
|
+
// convert and expose subjects as observables
|
|
8720
|
+
this.connectedUser$ = store.connectedUserSubject.asObservable();
|
|
8721
|
+
this.calls$ = store.callsSubject.asObservable();
|
|
8722
|
+
}
|
|
8723
|
+
/**
|
|
8724
|
+
* The current user connected over WS to the backend.
|
|
8725
|
+
*/
|
|
8726
|
+
get connectedUser() {
|
|
8727
|
+
return getCurrentValue(this.connectedUser$);
|
|
8728
|
+
}
|
|
8729
|
+
/**
|
|
8730
|
+
* A list of {@link Call} objects created/tracked by this client.
|
|
8731
|
+
*/
|
|
8732
|
+
get calls() {
|
|
8733
|
+
return getCurrentValue(this.calls$);
|
|
8734
|
+
}
|
|
8735
|
+
}
|
|
8736
|
+
|
|
8732
8737
|
/**
|
|
8733
8738
|
* Event handler that watched the delivery of `call.accepted`.
|
|
8734
8739
|
* Once the event is received, the call is joined.
|
|
@@ -9318,7 +9323,7 @@ const createStatsReporter = ({ subscriber, publisher, state, pollingIntervalInMs
|
|
|
9318
9323
|
const transform = (report, opts) => {
|
|
9319
9324
|
const { trackKind, kind } = opts;
|
|
9320
9325
|
const direction = kind === 'subscriber' ? 'inbound-rtp' : 'outbound-rtp';
|
|
9321
|
-
const stats = flatten(report);
|
|
9326
|
+
const stats = flatten$1(report);
|
|
9322
9327
|
const streams = stats
|
|
9323
9328
|
.filter((stat) => stat.type === direction &&
|
|
9324
9329
|
stat.kind === trackKind)
|
|
@@ -9413,7 +9418,7 @@ const aggregate = (stats) => {
|
|
|
9413
9418
|
*
|
|
9414
9419
|
* @param report the report to flatten.
|
|
9415
9420
|
*/
|
|
9416
|
-
const flatten = (report) => {
|
|
9421
|
+
const flatten$1 = (report) => {
|
|
9417
9422
|
const stats = [];
|
|
9418
9423
|
report.forEach((s) => {
|
|
9419
9424
|
stats.push(s);
|
|
@@ -10755,7 +10760,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
10755
10760
|
}
|
|
10756
10761
|
|
|
10757
10762
|
const DETECTION_FREQUENCY_IN_MS = 500;
|
|
10758
|
-
const AUDIO_LEVEL_THRESHOLD = 150;
|
|
10763
|
+
const AUDIO_LEVEL_THRESHOLD$1 = 150;
|
|
10759
10764
|
const FFT_SIZE = 128;
|
|
10760
10765
|
/**
|
|
10761
10766
|
* Creates a new sound detector.
|
|
@@ -10766,7 +10771,7 @@ const FFT_SIZE = 128;
|
|
|
10766
10771
|
* @returns a clean-up function which once invoked stops the sound detector.
|
|
10767
10772
|
*/
|
|
10768
10773
|
const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options = {}) => {
|
|
10769
|
-
const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
|
|
10774
|
+
const { detectionFrequencyInMs = DETECTION_FREQUENCY_IN_MS, audioLevelThreshold = AUDIO_LEVEL_THRESHOLD$1, fftSize = FFT_SIZE, destroyStreamOnStop = true, } = options;
|
|
10770
10775
|
const audioContext = new AudioContext();
|
|
10771
10776
|
const analyser = audioContext.createAnalyser();
|
|
10772
10777
|
analyser.fftSize = fftSize;
|
|
@@ -10805,6 +10810,99 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
|
|
|
10805
10810
|
};
|
|
10806
10811
|
};
|
|
10807
10812
|
|
|
10813
|
+
/**
|
|
10814
|
+
* Flatten the stats report into an array of stats objects.
|
|
10815
|
+
*
|
|
10816
|
+
* @param report the report to flatten.
|
|
10817
|
+
*/
|
|
10818
|
+
const flatten = (report) => {
|
|
10819
|
+
const stats = [];
|
|
10820
|
+
report.forEach((s) => {
|
|
10821
|
+
stats.push(s);
|
|
10822
|
+
});
|
|
10823
|
+
return stats;
|
|
10824
|
+
};
|
|
10825
|
+
const AUDIO_LEVEL_THRESHOLD = 0.2;
|
|
10826
|
+
class RNSpeechDetector {
|
|
10827
|
+
constructor() {
|
|
10828
|
+
this.pc1 = new RTCPeerConnection({});
|
|
10829
|
+
this.pc2 = new RTCPeerConnection({});
|
|
10830
|
+
}
|
|
10831
|
+
/**
|
|
10832
|
+
* Starts the speech detection.
|
|
10833
|
+
*/
|
|
10834
|
+
async start() {
|
|
10835
|
+
try {
|
|
10836
|
+
const audioStream = await navigator.mediaDevices.getUserMedia({
|
|
10837
|
+
audio: true,
|
|
10838
|
+
});
|
|
10839
|
+
this.pc1.addEventListener('icecandidate', async (e) => {
|
|
10840
|
+
await this.pc2.addIceCandidate(e.candidate);
|
|
10841
|
+
});
|
|
10842
|
+
this.pc2.addEventListener('icecandidate', async (e) => {
|
|
10843
|
+
await this.pc1.addIceCandidate(e.candidate);
|
|
10844
|
+
});
|
|
10845
|
+
audioStream
|
|
10846
|
+
.getTracks()
|
|
10847
|
+
.forEach((track) => this.pc1.addTrack(track, audioStream));
|
|
10848
|
+
const offer = await this.pc1.createOffer({});
|
|
10849
|
+
await this.pc2.setRemoteDescription(offer);
|
|
10850
|
+
await this.pc1.setLocalDescription(offer);
|
|
10851
|
+
const answer = await this.pc2.createAnswer();
|
|
10852
|
+
await this.pc1.setRemoteDescription(answer);
|
|
10853
|
+
await this.pc2.setLocalDescription(answer);
|
|
10854
|
+
const audioTracks = audioStream.getAudioTracks();
|
|
10855
|
+
// We need to mute the audio track for this temporary stream, or else you will hear yourself twice while in the call.
|
|
10856
|
+
audioTracks.forEach((track) => (track.enabled = false));
|
|
10857
|
+
}
|
|
10858
|
+
catch (error) {
|
|
10859
|
+
console.error('Error connecting and negotiating between PeerConnections:', error);
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
/**
|
|
10863
|
+
* Stops the speech detection and releases all allocated resources.
|
|
10864
|
+
*/
|
|
10865
|
+
stop() {
|
|
10866
|
+
this.pc1.close();
|
|
10867
|
+
this.pc2.close();
|
|
10868
|
+
if (this.intervalId) {
|
|
10869
|
+
clearInterval(this.intervalId);
|
|
10870
|
+
}
|
|
10871
|
+
}
|
|
10872
|
+
/**
|
|
10873
|
+
* Public method that detects the audio levels and returns the status.
|
|
10874
|
+
*/
|
|
10875
|
+
onSpeakingDetectedStateChange(onSoundDetectedStateChanged) {
|
|
10876
|
+
this.intervalId = setInterval(async () => {
|
|
10877
|
+
const stats = (await this.pc1.getStats());
|
|
10878
|
+
const report = flatten(stats);
|
|
10879
|
+
// Audio levels are present inside stats of type `media-source` and of kind `audio`
|
|
10880
|
+
const audioMediaSourceStats = report.find((stat) => stat.type === 'media-source' &&
|
|
10881
|
+
stat.kind === 'audio');
|
|
10882
|
+
if (audioMediaSourceStats) {
|
|
10883
|
+
const { audioLevel } = audioMediaSourceStats;
|
|
10884
|
+
if (audioLevel) {
|
|
10885
|
+
if (audioLevel >= AUDIO_LEVEL_THRESHOLD) {
|
|
10886
|
+
onSoundDetectedStateChanged({
|
|
10887
|
+
isSoundDetected: true,
|
|
10888
|
+
audioLevel,
|
|
10889
|
+
});
|
|
10890
|
+
}
|
|
10891
|
+
else {
|
|
10892
|
+
onSoundDetectedStateChanged({
|
|
10893
|
+
isSoundDetected: false,
|
|
10894
|
+
audioLevel: 0,
|
|
10895
|
+
});
|
|
10896
|
+
}
|
|
10897
|
+
}
|
|
10898
|
+
}
|
|
10899
|
+
}, 1000);
|
|
10900
|
+
return () => {
|
|
10901
|
+
clearInterval(this.intervalId);
|
|
10902
|
+
};
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10905
|
+
|
|
10808
10906
|
class MicrophoneManager extends InputMediaDeviceManager {
|
|
10809
10907
|
constructor(call) {
|
|
10810
10908
|
super(call, new MicrophoneManagerState(), TrackType.AUDIO);
|
|
@@ -10846,20 +10944,31 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10846
10944
|
return this.call.stopPublish(TrackType.AUDIO, stopTracks);
|
|
10847
10945
|
}
|
|
10848
10946
|
async startSpeakingWhileMutedDetection(deviceId) {
|
|
10947
|
+
await this.stopSpeakingWhileMutedDetection();
|
|
10849
10948
|
if (isReactNative()) {
|
|
10850
|
-
|
|
10949
|
+
this.rnSpeechDetector = new RNSpeechDetector();
|
|
10950
|
+
await this.rnSpeechDetector.start();
|
|
10951
|
+
const unsubscribe = this.rnSpeechDetector?.onSpeakingDetectedStateChange((event) => {
|
|
10952
|
+
this.state.setSpeakingWhileMuted(event.isSoundDetected);
|
|
10953
|
+
});
|
|
10954
|
+
this.soundDetectorCleanup = () => {
|
|
10955
|
+
unsubscribe();
|
|
10956
|
+
this.rnSpeechDetector?.stop();
|
|
10957
|
+
this.rnSpeechDetector = undefined;
|
|
10958
|
+
};
|
|
10959
|
+
}
|
|
10960
|
+
else {
|
|
10961
|
+
// Need to start a new stream that's not connected to publisher
|
|
10962
|
+
const stream = await this.getStream({
|
|
10963
|
+
deviceId,
|
|
10964
|
+
});
|
|
10965
|
+
this.soundDetectorCleanup = createSoundDetector(stream, (event) => {
|
|
10966
|
+
this.state.setSpeakingWhileMuted(event.isSoundDetected);
|
|
10967
|
+
});
|
|
10851
10968
|
}
|
|
10852
|
-
await this.stopSpeakingWhileMutedDetection();
|
|
10853
|
-
// Need to start a new stream that's not connected to publisher
|
|
10854
|
-
const stream = await this.getStream({
|
|
10855
|
-
deviceId,
|
|
10856
|
-
});
|
|
10857
|
-
this.soundDetectorCleanup = createSoundDetector(stream, (event) => {
|
|
10858
|
-
this.state.setSpeakingWhileMuted(event.isSoundDetected);
|
|
10859
|
-
});
|
|
10860
10969
|
}
|
|
10861
10970
|
async stopSpeakingWhileMutedDetection() {
|
|
10862
|
-
if (
|
|
10971
|
+
if (!this.soundDetectorCleanup) {
|
|
10863
10972
|
return;
|
|
10864
10973
|
}
|
|
10865
10974
|
this.state.setSpeakingWhileMuted(false);
|
|
@@ -11811,9 +11920,10 @@ class Call {
|
|
|
11811
11920
|
return this.state.setSortParticipantsBy(criteria);
|
|
11812
11921
|
};
|
|
11813
11922
|
/**
|
|
11923
|
+
* Updates the list of video layers to publish.
|
|
11924
|
+
*
|
|
11814
11925
|
* @internal
|
|
11815
|
-
* @param
|
|
11816
|
-
* @returns
|
|
11926
|
+
* @param enabledLayers the list of layers to enable.
|
|
11817
11927
|
*/
|
|
11818
11928
|
this.updatePublishQuality = async (enabledLayers) => {
|
|
11819
11929
|
return this.publisher?.updateVideoPublishQuality(enabledLayers);
|
|
@@ -12085,35 +12195,29 @@ class Call {
|
|
|
12085
12195
|
this.updateCallMembers = async (data) => {
|
|
12086
12196
|
return this.streamClient.post(`${this.streamClientBasePath}/members`, data);
|
|
12087
12197
|
};
|
|
12198
|
+
/**
|
|
12199
|
+
* Schedules an auto-drop timeout based on the call settings.
|
|
12200
|
+
* Applicable only for ringing calls.
|
|
12201
|
+
*/
|
|
12088
12202
|
this.scheduleAutoDrop = () => {
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
.pipe(pairwise(), tap(([prevSettings, currentSettings]) => {
|
|
12093
|
-
if (!currentSettings || !this.clientStore.connectedUser)
|
|
12203
|
+
clearTimeout(this.dropTimeout);
|
|
12204
|
+
this.leaveCallHooks.add(createSubscription(this.state.settings$, (settings) => {
|
|
12205
|
+
if (!settings)
|
|
12094
12206
|
return;
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
? [
|
|
12098
|
-
prevSettings?.ring.auto_cancel_timeout_ms,
|
|
12099
|
-
currentSettings.ring.auto_cancel_timeout_ms,
|
|
12100
|
-
]
|
|
12101
|
-
: [
|
|
12102
|
-
prevSettings?.ring.incoming_call_timeout_ms,
|
|
12103
|
-
currentSettings.ring.incoming_call_timeout_ms,
|
|
12104
|
-
];
|
|
12105
|
-
if (typeof timeoutMs === 'undefined' ||
|
|
12106
|
-
timeoutMs === prevTimeoutMs ||
|
|
12107
|
-
timeoutMs === 0)
|
|
12207
|
+
// ignore if the call is not ringing
|
|
12208
|
+
if (this.state.callingState !== CallingState.RINGING)
|
|
12108
12209
|
return;
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
.
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12210
|
+
const timeoutInMs = settings.ring.auto_cancel_timeout_ms;
|
|
12211
|
+
// 0 means no auto-drop
|
|
12212
|
+
if (timeoutInMs <= 0)
|
|
12213
|
+
return;
|
|
12214
|
+
clearTimeout(this.dropTimeout);
|
|
12215
|
+
this.dropTimeout = setTimeout(() => {
|
|
12216
|
+
this.leave().catch((err) => {
|
|
12217
|
+
this.logger('error', 'Failed to drop call', err);
|
|
12218
|
+
});
|
|
12219
|
+
}, timeoutInMs);
|
|
12220
|
+
}));
|
|
12117
12221
|
};
|
|
12118
12222
|
/**
|
|
12119
12223
|
* Retrieves the list of recordings for the current call or call session.
|
|
@@ -14037,7 +14141,7 @@ class StreamClient {
|
|
|
14037
14141
|
});
|
|
14038
14142
|
};
|
|
14039
14143
|
this.getUserAgent = () => {
|
|
14040
|
-
const version = "0.5.
|
|
14144
|
+
const version = "0.5.2" ;
|
|
14041
14145
|
return (this.userAgent ||
|
|
14042
14146
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
14043
14147
|
};
|