@stream-io/video-client 1.20.2 → 1.21.0
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 +6 -0
- package/dist/index.browser.es.js +389 -162
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +389 -162
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +389 -162
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -0
- package/dist/src/StreamSfuClient.d.ts +1 -1
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +0 -20
- package/dist/src/devices/SpeakerState.d.ts +0 -20
- package/dist/src/gen/video/sfu/models/models.d.ts +50 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +29 -3
- package/dist/src/rpc/createClient.d.ts +1 -1
- package/dist/src/rtc/BasePeerConnection.d.ts +12 -7
- package/dist/src/rtc/Publisher.d.ts +4 -4
- package/dist/src/stats/CallStateStatsReporter.d.ts +5 -3
- package/dist/src/stats/SfuStatsReporter.d.ts +5 -1
- package/dist/src/stats/rtc/StatsTracer.d.ts +54 -0
- package/dist/src/stats/rtc/Tracer.d.ts +1 -5
- package/dist/src/stats/rtc/index.d.ts +2 -0
- package/dist/src/stats/rtc/types.d.ts +19 -0
- package/dist/src/stats/types.d.ts +13 -0
- package/package.json +1 -1
- package/src/Call.ts +10 -2
- package/src/StreamSfuClient.ts +30 -18
- package/src/devices/CameraManagerState.ts +3 -2
- package/src/devices/InputMediaDeviceManagerState.ts +10 -31
- package/src/devices/MicrophoneManagerState.ts +3 -2
- package/src/devices/ScreenShareState.ts +5 -4
- package/src/devices/SpeakerState.ts +7 -26
- package/src/gen/video/sfu/models/models.ts +84 -0
- package/src/gen/video/sfu/signal_rpc/signal.ts +50 -2
- package/src/helpers/DynascaleManager.ts +0 -9
- package/src/rpc/createClient.ts +1 -1
- package/src/rtc/BasePeerConnection.ts +37 -15
- package/src/rtc/Publisher.ts +17 -15
- package/src/rtc/Subscriber.ts +2 -0
- package/src/rtc/__tests__/Publisher.test.ts +19 -0
- package/src/rtc/__tests__/Subscriber.test.ts +15 -1
- package/src/stats/CallStateStatsReporter.ts +19 -31
- package/src/stats/SfuStatsReporter.ts +37 -12
- package/src/stats/rtc/StatsTracer.ts +289 -0
- package/src/stats/rtc/Tracer.ts +1 -6
- package/src/stats/rtc/__tests__/Tracer.test.ts +57 -0
- package/src/stats/rtc/index.ts +3 -0
- package/src/stats/rtc/mediaDevices.ts +4 -3
- package/src/stats/rtc/pc.ts +6 -76
- package/src/stats/rtc/types.ts +22 -0
- package/src/stats/types.ts +15 -0
package/dist/index.es.js
CHANGED
|
@@ -61,16 +61,17 @@ if (typeof navigator !== 'undefined' &&
|
|
|
61
61
|
const original = target[method];
|
|
62
62
|
if (!original)
|
|
63
63
|
continue;
|
|
64
|
+
let mark = 0;
|
|
64
65
|
target[method] = async function tracedMethod(constraints) {
|
|
65
|
-
const tag = `navigator.mediaDevices.${method}`;
|
|
66
|
+
const tag = `navigator.mediaDevices.${method}.${mark++}`;
|
|
66
67
|
trace(tag, constraints);
|
|
67
68
|
try {
|
|
68
69
|
const stream = await original.call(target, constraints);
|
|
69
|
-
trace(`${tag}OnSuccess`, dumpStream(stream));
|
|
70
|
+
trace(`${tag}.OnSuccess`, dumpStream(stream));
|
|
70
71
|
return stream;
|
|
71
72
|
}
|
|
72
73
|
catch (err) {
|
|
73
|
-
trace(`${tag}OnFailure`, err.name);
|
|
74
|
+
trace(`${tag}.OnFailure`, err.name);
|
|
74
75
|
throw err;
|
|
75
76
|
}
|
|
76
77
|
};
|
|
@@ -1795,6 +1796,47 @@ class AppleState$Type extends MessageType {
|
|
|
1795
1796
|
* @generated MessageType for protobuf message stream.video.sfu.models.AppleState
|
|
1796
1797
|
*/
|
|
1797
1798
|
const AppleState = new AppleState$Type();
|
|
1799
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
1800
|
+
class PerformanceStats$Type extends MessageType {
|
|
1801
|
+
constructor() {
|
|
1802
|
+
super('stream.video.sfu.models.PerformanceStats', [
|
|
1803
|
+
{
|
|
1804
|
+
no: 1,
|
|
1805
|
+
name: 'track_type',
|
|
1806
|
+
kind: 'enum',
|
|
1807
|
+
T: () => [
|
|
1808
|
+
'stream.video.sfu.models.TrackType',
|
|
1809
|
+
TrackType,
|
|
1810
|
+
'TRACK_TYPE_',
|
|
1811
|
+
],
|
|
1812
|
+
},
|
|
1813
|
+
{ no: 2, name: 'codec', kind: 'message', T: () => Codec },
|
|
1814
|
+
{
|
|
1815
|
+
no: 3,
|
|
1816
|
+
name: 'avg_frame_time_ms',
|
|
1817
|
+
kind: 'scalar',
|
|
1818
|
+
T: 2 /*ScalarType.FLOAT*/,
|
|
1819
|
+
},
|
|
1820
|
+
{ no: 4, name: 'avg_fps', kind: 'scalar', T: 2 /*ScalarType.FLOAT*/ },
|
|
1821
|
+
{
|
|
1822
|
+
no: 5,
|
|
1823
|
+
name: 'video_dimension',
|
|
1824
|
+
kind: 'message',
|
|
1825
|
+
T: () => VideoDimension,
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
no: 6,
|
|
1829
|
+
name: 'target_bitrate',
|
|
1830
|
+
kind: 'scalar',
|
|
1831
|
+
T: 5 /*ScalarType.INT32*/,
|
|
1832
|
+
},
|
|
1833
|
+
]);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.PerformanceStats
|
|
1838
|
+
*/
|
|
1839
|
+
const PerformanceStats = new PerformanceStats$Type();
|
|
1798
1840
|
|
|
1799
1841
|
var models = /*#__PURE__*/Object.freeze({
|
|
1800
1842
|
__proto__: null,
|
|
@@ -1820,6 +1862,7 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
1820
1862
|
Participant: Participant,
|
|
1821
1863
|
ParticipantCount: ParticipantCount,
|
|
1822
1864
|
get PeerType () { return PeerType; },
|
|
1865
|
+
PerformanceStats: PerformanceStats,
|
|
1823
1866
|
Pin: Pin,
|
|
1824
1867
|
PublishOption: PublishOption,
|
|
1825
1868
|
RTMPIngress: RTMPIngress,
|
|
@@ -1999,6 +2042,27 @@ class SendStatsRequest$Type extends MessageType {
|
|
|
1999
2042
|
kind: 'scalar',
|
|
2000
2043
|
T: 9 /*ScalarType.STRING*/,
|
|
2001
2044
|
},
|
|
2045
|
+
{ no: 15, name: 'rtc_stats', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2046
|
+
{
|
|
2047
|
+
no: 16,
|
|
2048
|
+
name: 'encode_stats',
|
|
2049
|
+
kind: 'message',
|
|
2050
|
+
repeat: 1 /*RepeatType.PACKED*/,
|
|
2051
|
+
T: () => PerformanceStats,
|
|
2052
|
+
},
|
|
2053
|
+
{
|
|
2054
|
+
no: 17,
|
|
2055
|
+
name: 'decode_stats',
|
|
2056
|
+
kind: 'message',
|
|
2057
|
+
repeat: 1 /*RepeatType.PACKED*/,
|
|
2058
|
+
T: () => PerformanceStats,
|
|
2059
|
+
},
|
|
2060
|
+
{
|
|
2061
|
+
no: 18,
|
|
2062
|
+
name: 'unified_session_id',
|
|
2063
|
+
kind: 'scalar',
|
|
2064
|
+
T: 9 /*ScalarType.STRING*/,
|
|
2065
|
+
},
|
|
2002
2066
|
]);
|
|
2003
2067
|
}
|
|
2004
2068
|
}
|
|
@@ -5470,30 +5534,17 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
|
|
|
5470
5534
|
}
|
|
5471
5535
|
}
|
|
5472
5536
|
}
|
|
5473
|
-
const [subscriberStats, publisherStats] = await Promise.all([
|
|
5474
|
-
subscriber
|
|
5475
|
-
.getStats()
|
|
5476
|
-
.then((report) => transform(report, {
|
|
5477
|
-
kind: 'subscriber',
|
|
5478
|
-
trackKind: 'video',
|
|
5479
|
-
publisher,
|
|
5480
|
-
}))
|
|
5481
|
-
.then(aggregate),
|
|
5482
|
-
publisher
|
|
5483
|
-
? publisher
|
|
5484
|
-
.getStats()
|
|
5485
|
-
.then((report) => transform(report, {
|
|
5486
|
-
kind: 'publisher',
|
|
5487
|
-
trackKind: 'video',
|
|
5488
|
-
publisher,
|
|
5489
|
-
}))
|
|
5490
|
-
.then(aggregate)
|
|
5491
|
-
: getEmptyStats(),
|
|
5492
|
-
]);
|
|
5493
5537
|
const [subscriberRawStats, publisherRawStats] = await Promise.all([
|
|
5494
5538
|
getRawStatsForTrack('subscriber'),
|
|
5495
5539
|
publisher ? getRawStatsForTrack('publisher') : undefined,
|
|
5496
5540
|
]);
|
|
5541
|
+
const process = (report, kind) => aggregate(transform(report, { kind, trackKind: 'video', publisher }));
|
|
5542
|
+
const subscriberStats = subscriberRawStats
|
|
5543
|
+
? process(subscriberRawStats, 'subscriber')
|
|
5544
|
+
: getEmptyStats();
|
|
5545
|
+
const publisherStats = publisherRawStats
|
|
5546
|
+
? process(publisherRawStats, 'publisher')
|
|
5547
|
+
: getEmptyStats();
|
|
5497
5548
|
state.setCallStatsReport({
|
|
5498
5549
|
datacenter,
|
|
5499
5550
|
publisherStats,
|
|
@@ -5651,7 +5702,7 @@ const aggregate = (stats) => {
|
|
|
5651
5702
|
return report;
|
|
5652
5703
|
};
|
|
5653
5704
|
|
|
5654
|
-
const version = "1.
|
|
5705
|
+
const version = "1.21.0";
|
|
5655
5706
|
const [major, minor, patch] = version.split('.');
|
|
5656
5707
|
let sdkInfo = {
|
|
5657
5708
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5788,7 +5839,7 @@ const getClientDetails = async () => {
|
|
|
5788
5839
|
};
|
|
5789
5840
|
|
|
5790
5841
|
class SfuStatsReporter {
|
|
5791
|
-
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, }) {
|
|
5842
|
+
constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, unifiedSessionId, }) {
|
|
5792
5843
|
this.logger = getLogger(['SfuStatsReporter']);
|
|
5793
5844
|
this.inputDevices = new Map();
|
|
5794
5845
|
this.observeDevice = (device, kind) => {
|
|
@@ -5845,31 +5896,40 @@ class SfuStatsReporter {
|
|
|
5845
5896
|
};
|
|
5846
5897
|
this.run = async (telemetry) => {
|
|
5847
5898
|
const [subscriberStats, publisherStats] = await Promise.all([
|
|
5848
|
-
this.subscriber.
|
|
5849
|
-
this.publisher?.
|
|
5899
|
+
this.subscriber.stats.get(),
|
|
5900
|
+
this.publisher?.stats.get(),
|
|
5850
5901
|
]);
|
|
5851
|
-
|
|
5852
|
-
|
|
5902
|
+
this.subscriber.tracer?.trace('getstats', subscriberStats.delta);
|
|
5903
|
+
if (publisherStats) {
|
|
5904
|
+
this.publisher?.tracer?.trace('getstats', publisherStats.delta);
|
|
5905
|
+
}
|
|
5906
|
+
const subscriberTrace = this.subscriber.tracer?.take();
|
|
5907
|
+
const publisherTrace = this.publisher?.tracer?.take();
|
|
5853
5908
|
const mediaTrace = tracer.take();
|
|
5854
5909
|
const sfuTrace = this.sfuClient.getTrace();
|
|
5855
|
-
const
|
|
5910
|
+
const traces = [
|
|
5856
5911
|
...mediaTrace.snapshot,
|
|
5857
5912
|
...(sfuTrace?.snapshot ?? []),
|
|
5858
5913
|
...(publisherTrace?.snapshot ?? []),
|
|
5914
|
+
...(subscriberTrace?.snapshot ?? []),
|
|
5859
5915
|
];
|
|
5860
5916
|
try {
|
|
5861
5917
|
await this.sfuClient.sendStats({
|
|
5862
5918
|
sdk: this.sdkName,
|
|
5863
5919
|
sdkVersion: this.sdkVersion,
|
|
5864
5920
|
webrtcVersion: this.webRTCVersion,
|
|
5865
|
-
subscriberStats,
|
|
5866
|
-
|
|
5867
|
-
? JSON.stringify(
|
|
5868
|
-
: '',
|
|
5869
|
-
|
|
5870
|
-
publisherRtcStats:
|
|
5921
|
+
subscriberStats: JSON.stringify(flatten(subscriberStats.stats)),
|
|
5922
|
+
publisherStats: publisherStats
|
|
5923
|
+
? JSON.stringify(flatten(publisherStats.stats))
|
|
5924
|
+
: '[]',
|
|
5925
|
+
subscriberRtcStats: '',
|
|
5926
|
+
publisherRtcStats: '',
|
|
5927
|
+
rtcStats: JSON.stringify(traces),
|
|
5928
|
+
encodeStats: publisherStats?.performanceStats ?? [],
|
|
5929
|
+
decodeStats: subscriberStats.performanceStats,
|
|
5871
5930
|
audioDevices: this.inputDevices.get('mic'),
|
|
5872
5931
|
videoDevices: this.inputDevices.get('camera'),
|
|
5932
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
5873
5933
|
deviceState: getDeviceState(),
|
|
5874
5934
|
telemetry,
|
|
5875
5935
|
});
|
|
@@ -5902,6 +5962,16 @@ class SfuStatsReporter {
|
|
|
5902
5962
|
this.inputDevices.clear();
|
|
5903
5963
|
clearInterval(this.intervalId);
|
|
5904
5964
|
this.intervalId = undefined;
|
|
5965
|
+
clearTimeout(this.timeoutId);
|
|
5966
|
+
this.timeoutId = undefined;
|
|
5967
|
+
};
|
|
5968
|
+
this.scheduleOne = (timeout) => {
|
|
5969
|
+
clearTimeout(this.timeoutId);
|
|
5970
|
+
this.timeoutId = setTimeout(() => {
|
|
5971
|
+
this.run().catch((err) => {
|
|
5972
|
+
this.logger('warn', 'Failed to report stats', err);
|
|
5973
|
+
});
|
|
5974
|
+
}, timeout);
|
|
5905
5975
|
};
|
|
5906
5976
|
this.sfuClient = sfuClient;
|
|
5907
5977
|
this.options = options;
|
|
@@ -5910,6 +5980,7 @@ class SfuStatsReporter {
|
|
|
5910
5980
|
this.microphone = microphone;
|
|
5911
5981
|
this.camera = camera;
|
|
5912
5982
|
this.state = state;
|
|
5983
|
+
this.unifiedSessionId = unifiedSessionId;
|
|
5913
5984
|
const { sdk, browser } = clientDetails;
|
|
5914
5985
|
this.sdkName = getSdkName(sdk);
|
|
5915
5986
|
this.sdkVersion = getSdkVersion(sdk);
|
|
@@ -5932,47 +6003,25 @@ const traceRTCPeerConnection = (pc, trace) => {
|
|
|
5932
6003
|
trace('ontrack', `${e.track.kind}:${e.track.id} ${streams}`);
|
|
5933
6004
|
});
|
|
5934
6005
|
pc.addEventListener('signalingstatechange', () => {
|
|
5935
|
-
trace('
|
|
6006
|
+
trace('signalingstatechange', pc.signalingState);
|
|
5936
6007
|
});
|
|
5937
6008
|
pc.addEventListener('iceconnectionstatechange', () => {
|
|
5938
|
-
trace('
|
|
6009
|
+
trace('iceconnectionstatechange', pc.iceConnectionState);
|
|
5939
6010
|
});
|
|
5940
6011
|
pc.addEventListener('icegatheringstatechange', () => {
|
|
5941
|
-
trace('
|
|
6012
|
+
trace('icegatheringstatechange', pc.iceGatheringState);
|
|
5942
6013
|
});
|
|
5943
6014
|
pc.addEventListener('connectionstatechange', () => {
|
|
5944
|
-
trace('
|
|
6015
|
+
trace('connectionstatechange', pc.connectionState);
|
|
5945
6016
|
});
|
|
5946
6017
|
pc.addEventListener('negotiationneeded', () => {
|
|
5947
|
-
trace('
|
|
6018
|
+
trace('negotiationneeded', undefined);
|
|
5948
6019
|
});
|
|
5949
6020
|
pc.addEventListener('datachannel', ({ channel }) => {
|
|
5950
|
-
trace('
|
|
5951
|
-
});
|
|
5952
|
-
let prev = {};
|
|
5953
|
-
const getStats = () => {
|
|
5954
|
-
pc.getStats(null)
|
|
5955
|
-
.then((stats) => {
|
|
5956
|
-
const now = toObject(stats);
|
|
5957
|
-
trace('getstats', deltaCompression(prev, now));
|
|
5958
|
-
prev = now;
|
|
5959
|
-
})
|
|
5960
|
-
.catch((err) => {
|
|
5961
|
-
trace('getstatsOnFailure', err.toString());
|
|
5962
|
-
});
|
|
5963
|
-
};
|
|
5964
|
-
const interval = setInterval(() => {
|
|
5965
|
-
getStats();
|
|
5966
|
-
}, 8000);
|
|
5967
|
-
pc.addEventListener('connectionstatechange', () => {
|
|
5968
|
-
const state = pc.connectionState;
|
|
5969
|
-
if (state === 'connected' || state === 'failed') {
|
|
5970
|
-
getStats();
|
|
5971
|
-
}
|
|
6021
|
+
trace('datachannel', [channel.id, channel.label]);
|
|
5972
6022
|
});
|
|
5973
6023
|
const origClose = pc.close;
|
|
5974
6024
|
pc.close = function tracedClose() {
|
|
5975
|
-
clearInterval(interval);
|
|
5976
6025
|
trace('close', undefined);
|
|
5977
6026
|
return origClose.call(this);
|
|
5978
6027
|
};
|
|
@@ -6002,9 +6051,162 @@ const traceRTCPeerConnection = (pc, trace) => {
|
|
|
6002
6051
|
};
|
|
6003
6052
|
}
|
|
6004
6053
|
};
|
|
6005
|
-
|
|
6054
|
+
|
|
6055
|
+
/**
|
|
6056
|
+
* StatsTracer is a class that collects and processes WebRTC stats.
|
|
6057
|
+
* It is used to track the performance of the WebRTC connection
|
|
6058
|
+
* and to provide information about the media streams.
|
|
6059
|
+
* It is used by both the Publisher and Subscriber classes.
|
|
6060
|
+
*
|
|
6061
|
+
* @internal
|
|
6062
|
+
*/
|
|
6063
|
+
class StatsTracer {
|
|
6064
|
+
/**
|
|
6065
|
+
* Creates a new StatsTracer instance.
|
|
6066
|
+
*/
|
|
6067
|
+
constructor(pc, peerType, trackIdToTrackType) {
|
|
6068
|
+
this.previousStats = {};
|
|
6069
|
+
this.frameTimeHistory = [];
|
|
6070
|
+
this.fpsHistory = [];
|
|
6071
|
+
/**
|
|
6072
|
+
* Get the stats from the RTCPeerConnection.
|
|
6073
|
+
* When called, it will return the stats for the current connection.
|
|
6074
|
+
* It will also return the delta between the current stats and the previous stats.
|
|
6075
|
+
* This is used to track the performance of the connection.
|
|
6076
|
+
*
|
|
6077
|
+
* @internal
|
|
6078
|
+
*/
|
|
6079
|
+
this.get = async () => {
|
|
6080
|
+
const stats = await this.pc.getStats();
|
|
6081
|
+
const currentStats = toObject(stats);
|
|
6082
|
+
const performanceStats = this.withOverrides(this.peerType === PeerType.SUBSCRIBER
|
|
6083
|
+
? this.getDecodeStats(currentStats)
|
|
6084
|
+
: this.getEncodeStats(currentStats));
|
|
6085
|
+
const delta = deltaCompression(this.previousStats, currentStats);
|
|
6086
|
+
// store the current data for the next iteration
|
|
6087
|
+
this.previousStats = currentStats;
|
|
6088
|
+
this.frameTimeHistory = this.frameTimeHistory.slice(-2);
|
|
6089
|
+
this.fpsHistory = this.fpsHistory.slice(-2);
|
|
6090
|
+
return { performanceStats, delta, stats };
|
|
6091
|
+
};
|
|
6092
|
+
/**
|
|
6093
|
+
* Collects encode stats from the RTCPeerConnection.
|
|
6094
|
+
*/
|
|
6095
|
+
this.getEncodeStats = (currentStats) => {
|
|
6096
|
+
const encodeStats = [];
|
|
6097
|
+
for (const rtp of Object.values(currentStats)) {
|
|
6098
|
+
if (rtp.type !== 'outbound-rtp')
|
|
6099
|
+
continue;
|
|
6100
|
+
const { codecId, framesSent = 0, kind, id, totalEncodeTime = 0, framesPerSecond = 0, frameHeight = 0, frameWidth = 0, targetBitrate = 0, mediaSourceId, } = rtp;
|
|
6101
|
+
if (kind === 'audio' || !this.previousStats[id])
|
|
6102
|
+
continue;
|
|
6103
|
+
const prevRtp = this.previousStats[id];
|
|
6104
|
+
const deltaTotalEncodeTime = totalEncodeTime - (prevRtp.totalEncodeTime || 0);
|
|
6105
|
+
const deltaFramesSent = framesSent - (prevRtp.framesSent || 0);
|
|
6106
|
+
const framesEncodeTime = deltaFramesSent > 0
|
|
6107
|
+
? (deltaTotalEncodeTime / deltaFramesSent) * 1000
|
|
6108
|
+
: 0;
|
|
6109
|
+
this.frameTimeHistory.push(framesEncodeTime);
|
|
6110
|
+
this.fpsHistory.push(framesPerSecond);
|
|
6111
|
+
let trackType = TrackType.VIDEO;
|
|
6112
|
+
if (mediaSourceId && currentStats[mediaSourceId]) {
|
|
6113
|
+
const mediaSource = currentStats[mediaSourceId];
|
|
6114
|
+
trackType =
|
|
6115
|
+
this.trackIdToTrackType.get(mediaSource.trackIdentifier) || trackType;
|
|
6116
|
+
}
|
|
6117
|
+
encodeStats.push({
|
|
6118
|
+
trackType,
|
|
6119
|
+
codec: getCodecFromStats(currentStats, codecId),
|
|
6120
|
+
avgFrameTimeMs: average(this.frameTimeHistory),
|
|
6121
|
+
avgFps: average(this.fpsHistory),
|
|
6122
|
+
targetBitrate: Math.round(targetBitrate),
|
|
6123
|
+
videoDimension: { width: frameWidth, height: frameHeight },
|
|
6124
|
+
});
|
|
6125
|
+
}
|
|
6126
|
+
return encodeStats;
|
|
6127
|
+
};
|
|
6128
|
+
/**
|
|
6129
|
+
* Collects decode stats from the RTCPeerConnection.
|
|
6130
|
+
*/
|
|
6131
|
+
this.getDecodeStats = (currentStats) => {
|
|
6132
|
+
let rtp = undefined;
|
|
6133
|
+
let max = 0;
|
|
6134
|
+
for (const item of Object.values(currentStats)) {
|
|
6135
|
+
if (item.type !== 'inbound-rtp')
|
|
6136
|
+
continue;
|
|
6137
|
+
const rtpItem = item;
|
|
6138
|
+
const { kind, frameWidth = 0, frameHeight = 0 } = rtpItem;
|
|
6139
|
+
const area = frameWidth * frameHeight;
|
|
6140
|
+
if (kind === 'video' && area > max) {
|
|
6141
|
+
rtp = rtpItem;
|
|
6142
|
+
max = area;
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
if (!rtp || !this.previousStats[rtp.id])
|
|
6146
|
+
return [];
|
|
6147
|
+
const prevRtp = this.previousStats[rtp.id];
|
|
6148
|
+
const { framesDecoded = 0, framesPerSecond = 0, totalDecodeTime = 0, trackIdentifier, } = rtp;
|
|
6149
|
+
const deltaTotalDecodeTime = totalDecodeTime - (prevRtp.totalDecodeTime || 0);
|
|
6150
|
+
const deltaFramesDecoded = framesDecoded - (prevRtp.framesDecoded || 0);
|
|
6151
|
+
const framesDecodeTime = deltaFramesDecoded > 0
|
|
6152
|
+
? (deltaTotalDecodeTime / deltaFramesDecoded) * 1000
|
|
6153
|
+
: 0;
|
|
6154
|
+
this.frameTimeHistory.push(framesDecodeTime);
|
|
6155
|
+
this.fpsHistory.push(framesPerSecond);
|
|
6156
|
+
const trackType = this.trackIdToTrackType.get(trackIdentifier) || TrackType.VIDEO;
|
|
6157
|
+
return [
|
|
6158
|
+
PerformanceStats.create({
|
|
6159
|
+
trackType,
|
|
6160
|
+
codec: getCodecFromStats(currentStats, rtp.codecId),
|
|
6161
|
+
avgFrameTimeMs: average(this.frameTimeHistory),
|
|
6162
|
+
avgFps: average(this.fpsHistory),
|
|
6163
|
+
videoDimension: { width: rtp.frameWidth, height: rtp.frameHeight },
|
|
6164
|
+
}),
|
|
6165
|
+
];
|
|
6166
|
+
};
|
|
6167
|
+
/**
|
|
6168
|
+
* Applies cost overrides to the performance stats.
|
|
6169
|
+
* This is used to override the default encode/decode times with custom values.
|
|
6170
|
+
* This is useful for testing and debugging purposes, and it shouldn't be used in production.
|
|
6171
|
+
*/
|
|
6172
|
+
this.withOverrides = (performanceStats) => {
|
|
6173
|
+
if (this.costOverrides) {
|
|
6174
|
+
for (const s of performanceStats) {
|
|
6175
|
+
const override = this.costOverrides.get(s.trackType);
|
|
6176
|
+
if (override !== undefined) {
|
|
6177
|
+
// override the average encode/decode time with the provided cost.
|
|
6178
|
+
// format: [override].[original-frame-time]
|
|
6179
|
+
s.avgFrameTimeMs = override + (s.avgFrameTimeMs || 0) / 1000;
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
return performanceStats;
|
|
6184
|
+
};
|
|
6185
|
+
/**
|
|
6186
|
+
* Set the encode/decode cost for a specific track type.
|
|
6187
|
+
* This is used to override the default encode/decode times with custom values.
|
|
6188
|
+
* This is useful for testing and debugging purposes, and it shouldn't be used in production.
|
|
6189
|
+
*
|
|
6190
|
+
* @internal
|
|
6191
|
+
*/
|
|
6192
|
+
this.setCost = (cost, trackType = TrackType.VIDEO) => {
|
|
6193
|
+
if (!this.costOverrides)
|
|
6194
|
+
this.costOverrides = new Map();
|
|
6195
|
+
this.costOverrides.set(trackType, cost);
|
|
6196
|
+
};
|
|
6197
|
+
this.pc = pc;
|
|
6198
|
+
this.peerType = peerType;
|
|
6199
|
+
this.trackIdToTrackType = trackIdToTrackType;
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
/**
|
|
6203
|
+
* Convert the stat report to an object.
|
|
6204
|
+
*
|
|
6205
|
+
* @param report the stat report to convert.
|
|
6206
|
+
*/
|
|
6207
|
+
const toObject = (report) => {
|
|
6006
6208
|
const obj = {};
|
|
6007
|
-
|
|
6209
|
+
report.forEach((v, k) => {
|
|
6008
6210
|
obj[k] = v;
|
|
6009
6211
|
});
|
|
6010
6212
|
return obj;
|
|
@@ -6027,12 +6229,13 @@ const deltaCompression = (oldStats, newStats) => {
|
|
|
6027
6229
|
}
|
|
6028
6230
|
}
|
|
6029
6231
|
let timestamp = -Infinity;
|
|
6030
|
-
|
|
6232
|
+
const values = Object.values(newStats);
|
|
6233
|
+
for (const report of values) {
|
|
6031
6234
|
if (report.timestamp > timestamp) {
|
|
6032
6235
|
timestamp = report.timestamp;
|
|
6033
6236
|
}
|
|
6034
6237
|
}
|
|
6035
|
-
for (const report of
|
|
6238
|
+
for (const report of values) {
|
|
6036
6239
|
if (report.timestamp === timestamp) {
|
|
6037
6240
|
report.timestamp = 0;
|
|
6038
6241
|
}
|
|
@@ -6040,6 +6243,27 @@ const deltaCompression = (oldStats, newStats) => {
|
|
|
6040
6243
|
newStats.timestamp = timestamp;
|
|
6041
6244
|
return newStats;
|
|
6042
6245
|
};
|
|
6246
|
+
/**
|
|
6247
|
+
* Calculates an average value.
|
|
6248
|
+
*/
|
|
6249
|
+
const average = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
6250
|
+
/**
|
|
6251
|
+
* Create a Codec object from the codec stats.
|
|
6252
|
+
*
|
|
6253
|
+
* @param stats the stats report.
|
|
6254
|
+
* @param codecId the codec ID to look for.
|
|
6255
|
+
*/
|
|
6256
|
+
const getCodecFromStats = (stats, codecId) => {
|
|
6257
|
+
if (!codecId || !stats[codecId])
|
|
6258
|
+
return;
|
|
6259
|
+
const codecStats = stats[codecId];
|
|
6260
|
+
return Codec.create({
|
|
6261
|
+
name: codecStats.mimeType.split('/').pop(), // video/av1 -> av1
|
|
6262
|
+
clockRate: codecStats.clockRate,
|
|
6263
|
+
payloadType: codecStats.payloadType,
|
|
6264
|
+
fmtp: codecStats.sdpFmtpLine,
|
|
6265
|
+
});
|
|
6266
|
+
};
|
|
6043
6267
|
|
|
6044
6268
|
/**
|
|
6045
6269
|
* A base class for the `Publisher` and `Subscriber` classes.
|
|
@@ -6049,9 +6273,10 @@ class BasePeerConnection {
|
|
|
6049
6273
|
/**
|
|
6050
6274
|
* Constructs a new `BasePeerConnection` instance.
|
|
6051
6275
|
*/
|
|
6052
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onUnrecoverableError, logTag,
|
|
6276
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onUnrecoverableError, logTag, enableTracing, }) {
|
|
6053
6277
|
this.isIceRestarting = false;
|
|
6054
6278
|
this.isDisposed = false;
|
|
6279
|
+
this.trackIdToTrackType = new Map();
|
|
6055
6280
|
this.subscriptions = [];
|
|
6056
6281
|
/**
|
|
6057
6282
|
* Handles events synchronously.
|
|
@@ -6099,10 +6324,10 @@ class BasePeerConnection {
|
|
|
6099
6324
|
return this.pc.getStats(selector);
|
|
6100
6325
|
};
|
|
6101
6326
|
/**
|
|
6102
|
-
*
|
|
6327
|
+
* Maps the given track ID to the corresponding track type.
|
|
6103
6328
|
*/
|
|
6104
|
-
this.
|
|
6105
|
-
return this.
|
|
6329
|
+
this.getTrackType = (trackId) => {
|
|
6330
|
+
return this.trackIdToTrackType.get(trackId);
|
|
6106
6331
|
};
|
|
6107
6332
|
/**
|
|
6108
6333
|
* Handles the ICECandidate event and
|
|
@@ -6136,6 +6361,24 @@ class BasePeerConnection {
|
|
|
6136
6361
|
}
|
|
6137
6362
|
return JSON.stringify(candidate.toJSON());
|
|
6138
6363
|
};
|
|
6364
|
+
/**
|
|
6365
|
+
* Handles the ConnectionStateChange event.
|
|
6366
|
+
*/
|
|
6367
|
+
this.onConnectionStateChange = async () => {
|
|
6368
|
+
const state = this.pc.connectionState;
|
|
6369
|
+
this.logger('debug', `Connection state changed`, state);
|
|
6370
|
+
if (!this.tracer)
|
|
6371
|
+
return;
|
|
6372
|
+
if (state === 'connected' || state === 'failed') {
|
|
6373
|
+
try {
|
|
6374
|
+
const stats = await this.stats.get();
|
|
6375
|
+
this.tracer.trace('getstats', stats.delta);
|
|
6376
|
+
}
|
|
6377
|
+
catch (err) {
|
|
6378
|
+
this.tracer.trace('getstatsOnFailure', err.toString());
|
|
6379
|
+
}
|
|
6380
|
+
}
|
|
6381
|
+
};
|
|
6139
6382
|
/**
|
|
6140
6383
|
* Handles the ICE connection state change event.
|
|
6141
6384
|
*/
|
|
@@ -6192,18 +6435,19 @@ class BasePeerConnection {
|
|
|
6192
6435
|
logTag,
|
|
6193
6436
|
]);
|
|
6194
6437
|
this.pc = new RTCPeerConnection(connectionConfig);
|
|
6195
|
-
if (enableTracing) {
|
|
6196
|
-
const tag = `${logTag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`;
|
|
6197
|
-
this.tracer = new Tracer(tag);
|
|
6198
|
-
this.tracer.trace('clientDetails', clientDetails);
|
|
6199
|
-
this.tracer.trace('create', connectionConfig);
|
|
6200
|
-
traceRTCPeerConnection(this.pc, this.tracer.trace);
|
|
6201
|
-
}
|
|
6202
6438
|
this.pc.addEventListener('icecandidate', this.onIceCandidate);
|
|
6203
6439
|
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError);
|
|
6204
6440
|
this.pc.addEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
|
|
6205
6441
|
this.pc.addEventListener('icegatheringstatechange', this.onIceGatherChange);
|
|
6206
6442
|
this.pc.addEventListener('signalingstatechange', this.onSignalingChange);
|
|
6443
|
+
this.pc.addEventListener('connectionstatechange', this.onConnectionStateChange);
|
|
6444
|
+
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
6445
|
+
if (enableTracing) {
|
|
6446
|
+
const tag = `${logTag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}-${sfuClient.edgeName}`;
|
|
6447
|
+
this.tracer = new Tracer(tag);
|
|
6448
|
+
this.tracer.trace('create', connectionConfig);
|
|
6449
|
+
traceRTCPeerConnection(this.pc, this.tracer.trace);
|
|
6450
|
+
}
|
|
6207
6451
|
}
|
|
6208
6452
|
/**
|
|
6209
6453
|
* Disposes the `RTCPeerConnection` instance.
|
|
@@ -6569,7 +6813,7 @@ class Publisher extends BasePeerConnection {
|
|
|
6569
6813
|
}
|
|
6570
6814
|
else {
|
|
6571
6815
|
const previousTrack = transceiver.sender.track;
|
|
6572
|
-
await
|
|
6816
|
+
await this.updateTransceiver(transceiver, trackToPublish, trackType);
|
|
6573
6817
|
if (!isReactNative()) {
|
|
6574
6818
|
this.stopTrack(previousTrack);
|
|
6575
6819
|
}
|
|
@@ -6591,8 +6835,20 @@ class Publisher extends BasePeerConnection {
|
|
|
6591
6835
|
const trackType = publishOption.trackType;
|
|
6592
6836
|
this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
|
|
6593
6837
|
this.transceiverCache.add(publishOption, transceiver);
|
|
6838
|
+
this.trackIdToTrackType.set(track.id, trackType);
|
|
6594
6839
|
await this.negotiate();
|
|
6595
6840
|
};
|
|
6841
|
+
/**
|
|
6842
|
+
* Updates the transceiver with the given track and track type.
|
|
6843
|
+
*/
|
|
6844
|
+
this.updateTransceiver = async (transceiver, track, trackType) => {
|
|
6845
|
+
const sender = transceiver.sender;
|
|
6846
|
+
if (sender.track)
|
|
6847
|
+
this.trackIdToTrackType.delete(sender.track.id);
|
|
6848
|
+
await sender.replaceTrack(track);
|
|
6849
|
+
if (track)
|
|
6850
|
+
this.trackIdToTrackType.set(track.id, trackType);
|
|
6851
|
+
};
|
|
6596
6852
|
/**
|
|
6597
6853
|
* Synchronizes the current Publisher state with the provided publish options.
|
|
6598
6854
|
*/
|
|
@@ -6622,7 +6878,7 @@ class Publisher extends BasePeerConnection {
|
|
|
6622
6878
|
continue;
|
|
6623
6879
|
// it is safe to stop the track here, it is a clone
|
|
6624
6880
|
this.stopTrack(transceiver.sender.track);
|
|
6625
|
-
await
|
|
6881
|
+
await this.updateTransceiver(transceiver, null, publishOption.trackType);
|
|
6626
6882
|
}
|
|
6627
6883
|
};
|
|
6628
6884
|
/**
|
|
@@ -6642,18 +6898,6 @@ class Publisher extends BasePeerConnection {
|
|
|
6642
6898
|
}
|
|
6643
6899
|
return false;
|
|
6644
6900
|
};
|
|
6645
|
-
/**
|
|
6646
|
-
* Maps the given track ID to the corresponding track type.
|
|
6647
|
-
*/
|
|
6648
|
-
this.getTrackType = (trackId) => {
|
|
6649
|
-
for (const transceiverId of this.transceiverCache.items()) {
|
|
6650
|
-
const { publishOption, transceiver } = transceiverId;
|
|
6651
|
-
if (transceiver.sender.track?.id === trackId) {
|
|
6652
|
-
return publishOption.trackType;
|
|
6653
|
-
}
|
|
6654
|
-
}
|
|
6655
|
-
return undefined;
|
|
6656
|
-
};
|
|
6657
6901
|
/**
|
|
6658
6902
|
* Stops the cloned track that is being published to the SFU.
|
|
6659
6903
|
*/
|
|
@@ -6947,6 +7191,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
6947
7191
|
if (!trackType) {
|
|
6948
7192
|
return this.logger('error', `Unknown track type: ${rawTrackType}`);
|
|
6949
7193
|
}
|
|
7194
|
+
this.trackIdToTrackType.set(e.track.id, trackType);
|
|
6950
7195
|
if (!participantToUpdate) {
|
|
6951
7196
|
this.logger('warn', `[onTrack]: Received track for unknown participant: ${trackId}`, e);
|
|
6952
7197
|
this.state.registerOrphanedTrack({
|
|
@@ -7290,12 +7535,22 @@ class StreamSfuClient {
|
|
|
7290
7535
|
*/
|
|
7291
7536
|
this.abortController = new AbortController();
|
|
7292
7537
|
this.createWebSocket = () => {
|
|
7538
|
+
const eventsToTrace = {
|
|
7539
|
+
callEnded: true,
|
|
7540
|
+
changePublishQuality: true,
|
|
7541
|
+
error: true,
|
|
7542
|
+
goAway: true,
|
|
7543
|
+
};
|
|
7293
7544
|
this.signalWs = createWebSocketSignalChannel({
|
|
7294
7545
|
logTag: this.logTag,
|
|
7295
7546
|
endpoint: `${this.credentials.server.ws_endpoint}?tag=${this.logTag}`,
|
|
7296
7547
|
onMessage: (message) => {
|
|
7297
7548
|
this.lastMessageTimestamp = new Date();
|
|
7298
7549
|
this.scheduleConnectionCheck();
|
|
7550
|
+
const eventKind = message.eventPayload.oneofKind;
|
|
7551
|
+
if (eventsToTrace[eventKind]) {
|
|
7552
|
+
this.tracer?.trace(eventKind, message);
|
|
7553
|
+
}
|
|
7299
7554
|
this.dispatcher.dispatch(message, this.logTag);
|
|
7300
7555
|
},
|
|
7301
7556
|
});
|
|
@@ -7388,7 +7643,8 @@ class StreamSfuClient {
|
|
|
7388
7643
|
};
|
|
7389
7644
|
this.sendStats = async (stats) => {
|
|
7390
7645
|
await this.joinTask;
|
|
7391
|
-
|
|
7646
|
+
// NOTE: we don't retry sending stats
|
|
7647
|
+
return this.rpc.sendStats({ ...stats, sessionId: this.sessionId });
|
|
7392
7648
|
};
|
|
7393
7649
|
this.startNoiseCancellation = async () => {
|
|
7394
7650
|
await this.joinTask;
|
|
@@ -7440,7 +7696,7 @@ class StreamSfuClient {
|
|
|
7440
7696
|
unsubscribe();
|
|
7441
7697
|
current.reject(new Error('Waiting for "joinResponse" has timed out'));
|
|
7442
7698
|
}, this.joinResponseTimeout);
|
|
7443
|
-
|
|
7699
|
+
const joinRequest = SfuRequest.create({
|
|
7444
7700
|
requestPayload: {
|
|
7445
7701
|
oneofKind: 'joinRequest',
|
|
7446
7702
|
joinRequest: JoinRequest.create({
|
|
@@ -7449,7 +7705,9 @@ class StreamSfuClient {
|
|
|
7449
7705
|
token: this.credentials.token,
|
|
7450
7706
|
}),
|
|
7451
7707
|
},
|
|
7452
|
-
})
|
|
7708
|
+
});
|
|
7709
|
+
this.tracer?.trace('joinRequest', joinRequest);
|
|
7710
|
+
await this.send(joinRequest);
|
|
7453
7711
|
return current.promise;
|
|
7454
7712
|
};
|
|
7455
7713
|
this.ping = async () => {
|
|
@@ -7510,7 +7768,9 @@ class StreamSfuClient {
|
|
|
7510
7768
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
7511
7769
|
this.logTag = logTag;
|
|
7512
7770
|
this.logger = getLogger(['SfuClient', logTag]);
|
|
7513
|
-
this.tracer = enableTracing
|
|
7771
|
+
this.tracer = enableTracing
|
|
7772
|
+
? new Tracer(`${logTag}-${this.edgeName}`)
|
|
7773
|
+
: undefined;
|
|
7514
7774
|
this.rpc = createSignalClient({
|
|
7515
7775
|
baseUrl: server.url,
|
|
7516
7776
|
interceptors: [
|
|
@@ -8410,7 +8670,6 @@ class DynascaleManager {
|
|
|
8410
8670
|
const { selectedDevice } = this.speaker.state;
|
|
8411
8671
|
if (selectedDevice && 'setSinkId' in audioElement) {
|
|
8412
8672
|
audioElement.setSinkId(selectedDevice);
|
|
8413
|
-
tracer.trace('navigator.mediaDevices.setSinkId', selectedDevice);
|
|
8414
8673
|
}
|
|
8415
8674
|
}
|
|
8416
8675
|
});
|
|
@@ -8420,7 +8679,6 @@ class DynascaleManager {
|
|
|
8420
8679
|
: this.speaker.state.selectedDevice$.subscribe((deviceId) => {
|
|
8421
8680
|
if (deviceId) {
|
|
8422
8681
|
audioElement.setSinkId(deviceId);
|
|
8423
|
-
tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
|
|
8424
8682
|
}
|
|
8425
8683
|
});
|
|
8426
8684
|
const volumeSubscription = combineLatest([
|
|
@@ -9492,25 +9750,6 @@ class InputMediaDeviceManagerState {
|
|
|
9492
9750
|
* The default constraints for the device.
|
|
9493
9751
|
*/
|
|
9494
9752
|
this.defaultConstraints$ = this.defaultConstraintsSubject.asObservable();
|
|
9495
|
-
/**
|
|
9496
|
-
* Gets the current value of an observable, or undefined if the observable has
|
|
9497
|
-
* not emitted a value yet.
|
|
9498
|
-
*
|
|
9499
|
-
* @param observable$ the observable to get the value from.
|
|
9500
|
-
*/
|
|
9501
|
-
this.getCurrentValue = getCurrentValue;
|
|
9502
|
-
/**
|
|
9503
|
-
* Updates the value of the provided Subject.
|
|
9504
|
-
* An `update` can either be a new value or a function which takes
|
|
9505
|
-
* the current value and returns a new value.
|
|
9506
|
-
*
|
|
9507
|
-
* @internal
|
|
9508
|
-
*
|
|
9509
|
-
* @param subject the subject to update.
|
|
9510
|
-
* @param update the update to apply to the subject.
|
|
9511
|
-
* @return the updated value.
|
|
9512
|
-
*/
|
|
9513
|
-
this.setCurrentValue = setCurrentValue;
|
|
9514
9753
|
this.hasBrowserPermission$ = permission
|
|
9515
9754
|
? permission.asObservable().pipe(shareReplay(1))
|
|
9516
9755
|
: of(true);
|
|
@@ -9525,39 +9764,39 @@ class InputMediaDeviceManagerState {
|
|
|
9525
9764
|
* The device status
|
|
9526
9765
|
*/
|
|
9527
9766
|
get status() {
|
|
9528
|
-
return
|
|
9767
|
+
return getCurrentValue(this.status$);
|
|
9529
9768
|
}
|
|
9530
9769
|
/**
|
|
9531
9770
|
* The requested device status. Useful for optimistic UIs
|
|
9532
9771
|
*/
|
|
9533
9772
|
get optimisticStatus() {
|
|
9534
|
-
return
|
|
9773
|
+
return getCurrentValue(this.optimisticStatus$);
|
|
9535
9774
|
}
|
|
9536
9775
|
/**
|
|
9537
9776
|
* The currently selected device
|
|
9538
9777
|
*/
|
|
9539
9778
|
get selectedDevice() {
|
|
9540
|
-
return
|
|
9779
|
+
return getCurrentValue(this.selectedDevice$);
|
|
9541
9780
|
}
|
|
9542
9781
|
/**
|
|
9543
9782
|
* The current media stream, or `undefined` if the device is currently disabled.
|
|
9544
9783
|
*/
|
|
9545
9784
|
get mediaStream() {
|
|
9546
|
-
return
|
|
9785
|
+
return getCurrentValue(this.mediaStream$);
|
|
9547
9786
|
}
|
|
9548
9787
|
/**
|
|
9549
9788
|
* @internal
|
|
9550
9789
|
* @param status
|
|
9551
9790
|
*/
|
|
9552
9791
|
setStatus(status) {
|
|
9553
|
-
|
|
9792
|
+
setCurrentValue(this.statusSubject, status);
|
|
9554
9793
|
}
|
|
9555
9794
|
/**
|
|
9556
9795
|
* @internal
|
|
9557
9796
|
* @param pendingStatus
|
|
9558
9797
|
*/
|
|
9559
9798
|
setPendingStatus(pendingStatus) {
|
|
9560
|
-
|
|
9799
|
+
setCurrentValue(this.optimisticStatusSubject, pendingStatus);
|
|
9561
9800
|
}
|
|
9562
9801
|
/**
|
|
9563
9802
|
* Updates the `mediaStream` state variable.
|
|
@@ -9568,7 +9807,7 @@ class InputMediaDeviceManagerState {
|
|
|
9568
9807
|
* as this is the stream that holds the actual deviceId information.
|
|
9569
9808
|
*/
|
|
9570
9809
|
setMediaStream(stream, rootStream) {
|
|
9571
|
-
|
|
9810
|
+
setCurrentValue(this.mediaStreamSubject, stream);
|
|
9572
9811
|
if (rootStream) {
|
|
9573
9812
|
this.setDevice(this.getDeviceIdFromStream(rootStream));
|
|
9574
9813
|
}
|
|
@@ -9578,13 +9817,13 @@ class InputMediaDeviceManagerState {
|
|
|
9578
9817
|
* @param deviceId the device id to set.
|
|
9579
9818
|
*/
|
|
9580
9819
|
setDevice(deviceId) {
|
|
9581
|
-
|
|
9820
|
+
setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
9582
9821
|
}
|
|
9583
9822
|
/**
|
|
9584
9823
|
* Gets the default constraints for the device.
|
|
9585
9824
|
*/
|
|
9586
9825
|
get defaultConstraints() {
|
|
9587
|
-
return
|
|
9826
|
+
return getCurrentValue(this.defaultConstraints$);
|
|
9588
9827
|
}
|
|
9589
9828
|
/**
|
|
9590
9829
|
* Sets the default constraints for the device.
|
|
@@ -9593,7 +9832,7 @@ class InputMediaDeviceManagerState {
|
|
|
9593
9832
|
* @param constraints the constraints to set.
|
|
9594
9833
|
*/
|
|
9595
9834
|
setDefaultConstraints(constraints) {
|
|
9596
|
-
|
|
9835
|
+
setCurrentValue(this.defaultConstraintsSubject, constraints);
|
|
9597
9836
|
}
|
|
9598
9837
|
}
|
|
9599
9838
|
|
|
@@ -9611,13 +9850,13 @@ class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
9611
9850
|
* back - means the camera facing the environment
|
|
9612
9851
|
*/
|
|
9613
9852
|
get direction() {
|
|
9614
|
-
return
|
|
9853
|
+
return getCurrentValue(this.direction$);
|
|
9615
9854
|
}
|
|
9616
9855
|
/**
|
|
9617
9856
|
* @internal
|
|
9618
9857
|
*/
|
|
9619
9858
|
setDirection(direction) {
|
|
9620
|
-
|
|
9859
|
+
setCurrentValue(this.directionSubject, direction);
|
|
9621
9860
|
}
|
|
9622
9861
|
/**
|
|
9623
9862
|
* @internal
|
|
@@ -9790,13 +10029,13 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
9790
10029
|
* This feature is not available in the React Native SDK.
|
|
9791
10030
|
*/
|
|
9792
10031
|
get speakingWhileMuted() {
|
|
9793
|
-
return
|
|
10032
|
+
return getCurrentValue(this.speakingWhileMuted$);
|
|
9794
10033
|
}
|
|
9795
10034
|
/**
|
|
9796
10035
|
* @internal
|
|
9797
10036
|
*/
|
|
9798
10037
|
setSpeakingWhileMuted(isSpeaking) {
|
|
9799
|
-
|
|
10038
|
+
setCurrentValue(this.speakingWhileMutedSubject, isSpeaking);
|
|
9800
10039
|
}
|
|
9801
10040
|
getDeviceIdFromStream(stream) {
|
|
9802
10041
|
const [track] = stream.getAudioTracks();
|
|
@@ -10248,19 +10487,19 @@ class ScreenShareState extends InputMediaDeviceManagerState {
|
|
|
10248
10487
|
* The current screen share audio status.
|
|
10249
10488
|
*/
|
|
10250
10489
|
get audioEnabled() {
|
|
10251
|
-
return
|
|
10490
|
+
return getCurrentValue(this.audioEnabled$);
|
|
10252
10491
|
}
|
|
10253
10492
|
/**
|
|
10254
10493
|
* Set the current screen share audio status.
|
|
10255
10494
|
*/
|
|
10256
10495
|
setAudioEnabled(isEnabled) {
|
|
10257
|
-
|
|
10496
|
+
setCurrentValue(this.audioEnabledSubject, isEnabled);
|
|
10258
10497
|
}
|
|
10259
10498
|
/**
|
|
10260
10499
|
* The current screen share settings.
|
|
10261
10500
|
*/
|
|
10262
10501
|
get settings() {
|
|
10263
|
-
return
|
|
10502
|
+
return getCurrentValue(this.settings$);
|
|
10264
10503
|
}
|
|
10265
10504
|
/**
|
|
10266
10505
|
* Set the current screen share settings.
|
|
@@ -10268,7 +10507,7 @@ class ScreenShareState extends InputMediaDeviceManagerState {
|
|
|
10268
10507
|
* @param settings the screen share settings to set.
|
|
10269
10508
|
*/
|
|
10270
10509
|
setSettings(settings) {
|
|
10271
|
-
|
|
10510
|
+
setCurrentValue(this.settingsSubject, settings);
|
|
10272
10511
|
}
|
|
10273
10512
|
}
|
|
10274
10513
|
|
|
@@ -10347,25 +10586,6 @@ class SpeakerState {
|
|
|
10347
10586
|
* [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
|
|
10348
10587
|
*/
|
|
10349
10588
|
this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
|
|
10350
|
-
/**
|
|
10351
|
-
* Gets the current value of an observable, or undefined if the observable has
|
|
10352
|
-
* not emitted a value yet.
|
|
10353
|
-
*
|
|
10354
|
-
* @param observable$ the observable to get the value from.
|
|
10355
|
-
*/
|
|
10356
|
-
this.getCurrentValue = getCurrentValue;
|
|
10357
|
-
/**
|
|
10358
|
-
* Updates the value of the provided Subject.
|
|
10359
|
-
* An `update` can either be a new value or a function which takes
|
|
10360
|
-
* the current value and returns a new value.
|
|
10361
|
-
*
|
|
10362
|
-
* @internal
|
|
10363
|
-
*
|
|
10364
|
-
* @param subject the subject to update.
|
|
10365
|
-
* @param update the update to apply to the subject.
|
|
10366
|
-
* @return the updated value.
|
|
10367
|
-
*/
|
|
10368
|
-
this.setCurrentValue = setCurrentValue;
|
|
10369
10589
|
this.selectedDevice$ = this.selectedDeviceSubject
|
|
10370
10590
|
.asObservable()
|
|
10371
10591
|
.pipe(distinctUntilChanged());
|
|
@@ -10379,7 +10599,7 @@ class SpeakerState {
|
|
|
10379
10599
|
* Note: this feature is not supported in React Native
|
|
10380
10600
|
*/
|
|
10381
10601
|
get selectedDevice() {
|
|
10382
|
-
return
|
|
10602
|
+
return getCurrentValue(this.selectedDevice$);
|
|
10383
10603
|
}
|
|
10384
10604
|
/**
|
|
10385
10605
|
* The currently selected volume
|
|
@@ -10387,21 +10607,22 @@ class SpeakerState {
|
|
|
10387
10607
|
* Note: this feature is not supported in React Native
|
|
10388
10608
|
*/
|
|
10389
10609
|
get volume() {
|
|
10390
|
-
return
|
|
10610
|
+
return getCurrentValue(this.volume$);
|
|
10391
10611
|
}
|
|
10392
10612
|
/**
|
|
10393
10613
|
* @internal
|
|
10394
10614
|
* @param deviceId
|
|
10395
10615
|
*/
|
|
10396
10616
|
setDevice(deviceId) {
|
|
10397
|
-
|
|
10617
|
+
setCurrentValue(this.selectedDeviceSubject, deviceId);
|
|
10618
|
+
tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
|
|
10398
10619
|
}
|
|
10399
10620
|
/**
|
|
10400
10621
|
* @internal
|
|
10401
10622
|
* @param volume
|
|
10402
10623
|
*/
|
|
10403
10624
|
setVolume(volume) {
|
|
10404
|
-
|
|
10625
|
+
setCurrentValue(this.volumeSubject, volume);
|
|
10405
10626
|
}
|
|
10406
10627
|
}
|
|
10407
10628
|
|
|
@@ -10782,6 +11003,7 @@ class Call {
|
|
|
10782
11003
|
this.leaveCallHooks.forEach((hook) => hook());
|
|
10783
11004
|
this.initialized = false;
|
|
10784
11005
|
this.hasJoinedOnce = false;
|
|
11006
|
+
this.unifiedSessionId = undefined;
|
|
10785
11007
|
this.ringingSubject.next(false);
|
|
10786
11008
|
this.cancelAutoDrop();
|
|
10787
11009
|
this.clientStore.unregisterCall(this);
|
|
@@ -11209,7 +11431,6 @@ class Call {
|
|
|
11209
11431
|
state: this.state,
|
|
11210
11432
|
connectionConfig,
|
|
11211
11433
|
logTag: String(this.sfuClientTag),
|
|
11212
|
-
clientDetails,
|
|
11213
11434
|
enableTracing,
|
|
11214
11435
|
onUnrecoverableError: (reason) => {
|
|
11215
11436
|
this.reconnect(WebsocketReconnectStrategy.REJOIN, reason).catch((err) => {
|
|
@@ -11231,7 +11452,6 @@ class Call {
|
|
|
11231
11452
|
connectionConfig,
|
|
11232
11453
|
publishOptions,
|
|
11233
11454
|
logTag: String(this.sfuClientTag),
|
|
11234
|
-
clientDetails,
|
|
11235
11455
|
enableTracing,
|
|
11236
11456
|
onUnrecoverableError: (reason) => {
|
|
11237
11457
|
this.reconnect(WebsocketReconnectStrategy.REJOIN, reason).catch((err) => {
|
|
@@ -11250,6 +11470,7 @@ class Call {
|
|
|
11250
11470
|
});
|
|
11251
11471
|
this.sfuStatsReporter?.stop();
|
|
11252
11472
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
11473
|
+
this.unifiedSessionId ?? (this.unifiedSessionId = sfuClient.sessionId);
|
|
11253
11474
|
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
|
|
11254
11475
|
clientDetails,
|
|
11255
11476
|
options: statsOptions,
|
|
@@ -11258,6 +11479,7 @@ class Call {
|
|
|
11258
11479
|
microphone: this.microphone,
|
|
11259
11480
|
camera: this.camera,
|
|
11260
11481
|
state: this.state,
|
|
11482
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
11261
11483
|
});
|
|
11262
11484
|
this.sfuStatsReporter.start();
|
|
11263
11485
|
}
|
|
@@ -11627,6 +11849,11 @@ class Call {
|
|
|
11627
11849
|
trackTypes.push(TrackType.SCREEN_SHARE_AUDIO);
|
|
11628
11850
|
}
|
|
11629
11851
|
}
|
|
11852
|
+
if (track.kind === 'video') {
|
|
11853
|
+
// schedules calibration report - the SFU will use the performance stats
|
|
11854
|
+
// to adjust the quality thresholds as early as possible
|
|
11855
|
+
this.sfuStatsReporter?.scheduleOne(3000);
|
|
11856
|
+
}
|
|
11630
11857
|
await this.updateLocalStreamState(mediaStream, ...trackTypes);
|
|
11631
11858
|
};
|
|
11632
11859
|
/**
|
|
@@ -13486,7 +13713,7 @@ class StreamClient {
|
|
|
13486
13713
|
this.getUserAgent = () => {
|
|
13487
13714
|
if (!this.cachedUserAgent) {
|
|
13488
13715
|
const { clientAppIdentifier = {} } = this.options;
|
|
13489
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
13716
|
+
const { sdkName = 'js', sdkVersion = "1.21.0", ...extras } = clientAppIdentifier;
|
|
13490
13717
|
this.cachedUserAgent = [
|
|
13491
13718
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
13492
13719
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|