livekit-client 2.10.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.esm.mjs +358 -231
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/StreamWriter.d.ts +1 -1
- package/dist/src/room/StreamWriter.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +10 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts +2 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
- package/dist/ts4.2/src/room/StreamWriter.d.ts +1 -1
- package/dist/ts4.2/src/room/events.d.ts +2 -1
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +10 -1
- package/dist/ts4.2/src/room/track/create.d.ts +2 -1
- package/package.json +1 -1
- package/src/api/utils.test.ts +12 -0
- package/src/api/utils.ts +4 -7
- package/src/room/DeviceManager.ts +1 -1
- package/src/room/RTCEngine.ts +5 -0
- package/src/room/Room.ts +1 -1
- package/src/room/StreamWriter.ts +1 -1
- package/src/room/defaults.ts +2 -2
- package/src/room/events.ts +1 -0
- package/src/room/participant/LocalParticipant.ts +167 -95
- package/src/room/track/create.ts +103 -48
- package/src/room/track/utils.ts +6 -6
@@ -1,3 +1,4 @@
|
|
1
|
+
import { Mutex } from '@livekit/mutex';
|
1
2
|
import {
|
2
3
|
AddTrackRequest,
|
3
4
|
BackupCodecPolicy,
|
@@ -27,10 +28,11 @@ import {
|
|
27
28
|
UserPacket,
|
28
29
|
protoInt64,
|
29
30
|
} from '@livekit/protocol';
|
31
|
+
import { SignalConnectionState } from '../../api/SignalClient';
|
30
32
|
import type { InternalRoomOptions } from '../../options';
|
31
33
|
import { PCTransportState } from '../PCTransportManager';
|
32
34
|
import type RTCEngine from '../RTCEngine';
|
33
|
-
import { TextStreamWriter } from '../StreamWriter';
|
35
|
+
import { ByteStreamWriter, TextStreamWriter } from '../StreamWriter';
|
34
36
|
import { defaultVideoCodec } from '../defaults';
|
35
37
|
import {
|
36
38
|
DeviceUnsupportedError,
|
@@ -53,6 +55,7 @@ import LocalTrack from '../track/LocalTrack';
|
|
53
55
|
import LocalTrackPublication from '../track/LocalTrackPublication';
|
54
56
|
import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
|
55
57
|
import { Track } from '../track/Track';
|
58
|
+
import { createLocalTracks } from '../track/create';
|
56
59
|
import type {
|
57
60
|
AudioCaptureOptions,
|
58
61
|
BackupVideoCodec,
|
@@ -63,8 +66,6 @@ import type {
|
|
63
66
|
} from '../track/options';
|
64
67
|
import { ScreenSharePresets, VideoPresets, isBackupCodec } from '../track/options';
|
65
68
|
import {
|
66
|
-
constraintsForOptions,
|
67
|
-
extractProcessorsFromOptions,
|
68
69
|
getLogContextFromTrack,
|
69
70
|
getTrackSourceFromProto,
|
70
71
|
mergeDefaultOptions,
|
@@ -72,6 +73,7 @@ import {
|
|
72
73
|
screenCaptureToDisplayMediaStreamOptions,
|
73
74
|
} from '../track/utils';
|
74
75
|
import {
|
76
|
+
type ByteStreamInfo,
|
75
77
|
type ChatMessage,
|
76
78
|
type DataPublishOptions,
|
77
79
|
type SendTextOptions,
|
@@ -104,7 +106,6 @@ import {
|
|
104
106
|
computeTrackBackupEncodings,
|
105
107
|
computeVideoEncodings,
|
106
108
|
getDefaultDegradationPreference,
|
107
|
-
mediaTrackToLocalTrack,
|
108
109
|
} from './publishUtils';
|
109
110
|
|
110
111
|
const STREAM_CHUNK_SIZE = 15_000;
|
@@ -528,9 +529,11 @@ export default class LocalParticipant extends Participant {
|
|
528
529
|
...this.logContext,
|
529
530
|
...getLogContextFromTrack(localTrack),
|
530
531
|
});
|
532
|
+
|
531
533
|
publishPromises.push(this.publishTrack(localTrack, publishOptions));
|
532
534
|
}
|
533
535
|
const publishedTracks = await Promise.all(publishPromises);
|
536
|
+
|
534
537
|
// for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
|
535
538
|
// revisit if we want to return an array of tracks instead for v2
|
536
539
|
[track] = publishedTracks;
|
@@ -612,61 +615,37 @@ export default class LocalParticipant extends Participant {
|
|
612
615
|
this.roomOptions?.videoCaptureDefaults,
|
613
616
|
);
|
614
617
|
|
615
|
-
const { audioProcessor, videoProcessor, optionsWithoutProcessor } =
|
616
|
-
extractProcessorsFromOptions(mergedOptionsWithProcessors);
|
617
|
-
|
618
|
-
const constraints = constraintsForOptions(optionsWithoutProcessor);
|
619
|
-
let stream: MediaStream | undefined;
|
620
618
|
try {
|
621
|
-
|
619
|
+
const tracks = await createLocalTracks(mergedOptionsWithProcessors, {
|
620
|
+
loggerName: this.roomOptions.loggerName,
|
621
|
+
loggerContextCb: () => this.logContext,
|
622
|
+
});
|
623
|
+
const localTracks = tracks.map((track) => {
|
624
|
+
if (isAudioTrack(track)) {
|
625
|
+
this.microphoneError = undefined;
|
626
|
+
track.setAudioContext(this.audioContext);
|
627
|
+
track.source = Track.Source.Microphone;
|
628
|
+
this.emit(ParticipantEvent.AudioStreamAcquired);
|
629
|
+
}
|
630
|
+
if (isVideoTrack(track)) {
|
631
|
+
this.cameraError = undefined;
|
632
|
+
track.source = Track.Source.Camera;
|
633
|
+
}
|
634
|
+
return track;
|
635
|
+
});
|
636
|
+
return localTracks;
|
622
637
|
} catch (err) {
|
623
638
|
if (err instanceof Error) {
|
624
|
-
if (
|
639
|
+
if (options.audio) {
|
625
640
|
this.microphoneError = err;
|
626
641
|
}
|
627
|
-
if (
|
642
|
+
if (options.video) {
|
628
643
|
this.cameraError = err;
|
629
644
|
}
|
630
645
|
}
|
631
646
|
|
632
647
|
throw err;
|
633
648
|
}
|
634
|
-
|
635
|
-
if (constraints.audio) {
|
636
|
-
this.microphoneError = undefined;
|
637
|
-
this.emit(ParticipantEvent.AudioStreamAcquired);
|
638
|
-
}
|
639
|
-
if (constraints.video) {
|
640
|
-
this.cameraError = undefined;
|
641
|
-
}
|
642
|
-
|
643
|
-
return Promise.all(
|
644
|
-
stream.getTracks().map(async (mediaStreamTrack) => {
|
645
|
-
const isAudio = mediaStreamTrack.kind === 'audio';
|
646
|
-
let trackConstraints: MediaTrackConstraints | undefined;
|
647
|
-
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
648
|
-
if (typeof conOrBool !== 'boolean') {
|
649
|
-
trackConstraints = conOrBool;
|
650
|
-
}
|
651
|
-
const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
|
652
|
-
loggerName: this.roomOptions.loggerName,
|
653
|
-
loggerContextCb: () => this.logContext,
|
654
|
-
});
|
655
|
-
if (track.kind === Track.Kind.Video) {
|
656
|
-
track.source = Track.Source.Camera;
|
657
|
-
} else if (track.kind === Track.Kind.Audio) {
|
658
|
-
track.source = Track.Source.Microphone;
|
659
|
-
track.setAudioContext(this.audioContext);
|
660
|
-
}
|
661
|
-
track.mediaStream = stream;
|
662
|
-
if (isAudioTrack(track) && audioProcessor) {
|
663
|
-
await track.setProcessor(audioProcessor);
|
664
|
-
} else if (isVideoTrack(track) && videoProcessor) {
|
665
|
-
await track.setProcessor(videoProcessor);
|
666
|
-
}
|
667
|
-
return track;
|
668
|
-
}),
|
669
|
-
);
|
670
649
|
}
|
671
650
|
|
672
651
|
/**
|
@@ -862,7 +841,47 @@ export default class LocalParticipant extends Participant {
|
|
862
841
|
if (opts.source) {
|
863
842
|
track.source = opts.source;
|
864
843
|
}
|
865
|
-
const publishPromise =
|
844
|
+
const publishPromise = new Promise<LocalTrackPublication>(async (resolve, reject) => {
|
845
|
+
try {
|
846
|
+
if (this.engine.client.currentState !== SignalConnectionState.CONNECTED) {
|
847
|
+
this.log.debug('deferring track publication until signal is connected', {
|
848
|
+
...this.logContext,
|
849
|
+
track: getLogContextFromTrack(track),
|
850
|
+
});
|
851
|
+
const onSignalConnected = async () => {
|
852
|
+
try {
|
853
|
+
const publication = await this.publish(track, opts, isStereo);
|
854
|
+
resolve(publication);
|
855
|
+
} catch (e) {
|
856
|
+
reject(e);
|
857
|
+
}
|
858
|
+
};
|
859
|
+
setTimeout(() => {
|
860
|
+
this.engine.off(EngineEvent.SignalConnected, onSignalConnected);
|
861
|
+
reject(
|
862
|
+
new PublishTrackError(
|
863
|
+
'publishing rejected as engine not connected within timeout',
|
864
|
+
408,
|
865
|
+
),
|
866
|
+
);
|
867
|
+
}, 15_000);
|
868
|
+
this.engine.once(EngineEvent.SignalConnected, onSignalConnected);
|
869
|
+
this.engine.on(EngineEvent.Closing, () => {
|
870
|
+
this.engine.off(EngineEvent.SignalConnected, onSignalConnected);
|
871
|
+
reject(new PublishTrackError('publishing rejected as engine closed', 499));
|
872
|
+
});
|
873
|
+
} else {
|
874
|
+
try {
|
875
|
+
const publication = await this.publish(track, opts, isStereo);
|
876
|
+
resolve(publication);
|
877
|
+
} catch (e) {
|
878
|
+
reject(e);
|
879
|
+
}
|
880
|
+
}
|
881
|
+
} catch (e) {
|
882
|
+
reject(e);
|
883
|
+
}
|
884
|
+
});
|
866
885
|
this.pendingPublishPromises.set(track, publishPromise);
|
867
886
|
try {
|
868
887
|
const publication = await publishPromise;
|
@@ -1713,23 +1732,62 @@ export default class LocalParticipant extends Participant {
|
|
1713
1732
|
onProgress?: (progress: number) => void;
|
1714
1733
|
},
|
1715
1734
|
) {
|
1716
|
-
const
|
1717
|
-
const header = new DataStream_Header({
|
1718
|
-
totalLength: numberToBigInt(totalLength),
|
1719
|
-
mimeType: options?.mimeType ?? file.type,
|
1735
|
+
const writer = await this.streamBytes({
|
1720
1736
|
streamId,
|
1737
|
+
totalSize: file.size,
|
1738
|
+
name: file.name,
|
1739
|
+
mimeType: options?.mimeType ?? file.type,
|
1721
1740
|
topic: options?.topic,
|
1722
|
-
|
1741
|
+
destinationIdentities: options?.destinationIdentities,
|
1742
|
+
});
|
1743
|
+
const reader = file.stream().getReader();
|
1744
|
+
while (true) {
|
1745
|
+
const { done, value } = await reader.read();
|
1746
|
+
if (done) {
|
1747
|
+
break;
|
1748
|
+
}
|
1749
|
+
await writer.write(value);
|
1750
|
+
}
|
1751
|
+
await writer.close();
|
1752
|
+
return writer.info;
|
1753
|
+
}
|
1754
|
+
|
1755
|
+
async streamBytes(options?: {
|
1756
|
+
name?: string;
|
1757
|
+
topic?: string;
|
1758
|
+
attributes?: Record<string, string>;
|
1759
|
+
destinationIdentities?: Array<string>;
|
1760
|
+
streamId?: string;
|
1761
|
+
mimeType?: string;
|
1762
|
+
totalSize?: number;
|
1763
|
+
}) {
|
1764
|
+
const streamId = options?.streamId ?? crypto.randomUUID();
|
1765
|
+
const destinationIdentities = options?.destinationIdentities;
|
1766
|
+
|
1767
|
+
const info: ByteStreamInfo = {
|
1768
|
+
id: streamId,
|
1769
|
+
mimeType: options?.mimeType ?? 'application/octet-stream',
|
1770
|
+
topic: options?.topic ?? '',
|
1771
|
+
timestamp: Date.now(),
|
1772
|
+
attributes: options?.attributes,
|
1773
|
+
size: options?.totalSize,
|
1774
|
+
name: options?.name ?? 'unknown',
|
1775
|
+
};
|
1776
|
+
|
1777
|
+
const header = new DataStream_Header({
|
1778
|
+
totalLength: numberToBigInt(info.size ?? 0),
|
1779
|
+
mimeType: info.mimeType,
|
1780
|
+
streamId,
|
1781
|
+
topic: info.topic,
|
1723
1782
|
timestamp: numberToBigInt(Date.now()),
|
1724
1783
|
contentHeader: {
|
1725
1784
|
case: 'byteHeader',
|
1726
1785
|
value: new DataStream_ByteHeader({
|
1727
|
-
name:
|
1786
|
+
name: info.name,
|
1728
1787
|
}),
|
1729
1788
|
},
|
1730
1789
|
});
|
1731
1790
|
|
1732
|
-
const destinationIdentities = options?.destinationIdentities;
|
1733
1791
|
const packet = new DataPacket({
|
1734
1792
|
destinationIdentities,
|
1735
1793
|
value: {
|
@@ -1739,47 +1797,61 @@ export default class LocalParticipant extends Participant {
|
|
1739
1797
|
});
|
1740
1798
|
|
1741
1799
|
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1800
|
+
|
1801
|
+
let chunkId = 0;
|
1802
|
+
const writeMutex = new Mutex();
|
1803
|
+
const engine = this.engine;
|
1804
|
+
const log = this.log;
|
1805
|
+
|
1806
|
+
const writableStream = new WritableStream<Uint8Array>({
|
1807
|
+
async write(chunk) {
|
1808
|
+
const unlock = await writeMutex.lock();
|
1809
|
+
|
1810
|
+
let byteOffset = 0;
|
1811
|
+
try {
|
1812
|
+
while (byteOffset < chunk.byteLength) {
|
1813
|
+
const subChunk = chunk.slice(byteOffset, byteOffset + STREAM_CHUNK_SIZE);
|
1814
|
+
await engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
1815
|
+
const chunkPacket = new DataPacket({
|
1816
|
+
destinationIdentities,
|
1817
|
+
value: {
|
1818
|
+
case: 'streamChunk',
|
1819
|
+
value: new DataStream_Chunk({
|
1820
|
+
content: subChunk,
|
1821
|
+
streamId,
|
1822
|
+
chunkIndex: numberToBigInt(chunkId),
|
1823
|
+
}),
|
1824
|
+
},
|
1825
|
+
});
|
1826
|
+
await engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
|
1827
|
+
chunkId += 1;
|
1828
|
+
byteOffset += subChunk.byteLength;
|
1829
|
+
}
|
1830
|
+
} finally {
|
1831
|
+
unlock();
|
1832
|
+
}
|
1833
|
+
},
|
1834
|
+
async close() {
|
1835
|
+
const trailer = new DataStream_Trailer({
|
1836
|
+
streamId,
|
1837
|
+
});
|
1838
|
+
const trailerPacket = new DataPacket({
|
1839
|
+
destinationIdentities,
|
1840
|
+
value: {
|
1841
|
+
case: 'streamTrailer',
|
1842
|
+
value: trailer,
|
1843
|
+
},
|
1844
|
+
});
|
1845
|
+
await engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
|
1846
|
+
},
|
1847
|
+
abort(err) {
|
1848
|
+
log.error('Sink error:', err);
|
1780
1849
|
},
|
1781
1850
|
});
|
1782
|
-
|
1851
|
+
|
1852
|
+
const byteWriter = new ByteStreamWriter(writableStream, info);
|
1853
|
+
|
1854
|
+
return byteWriter;
|
1783
1855
|
}
|
1784
1856
|
|
1785
1857
|
/**
|
package/src/room/track/create.ts
CHANGED
@@ -2,6 +2,7 @@ import DeviceManager from '../DeviceManager';
|
|
2
2
|
import { audioDefaults, videoDefaults } from '../defaults';
|
3
3
|
import { DeviceUnsupportedError, TrackInvalidError } from '../errors';
|
4
4
|
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
|
5
|
+
import type { LoggerOptions } from '../types';
|
5
6
|
import { isAudioTrack, isSafari17, isVideoTrack, unwrapConstraint } from '../utils';
|
6
7
|
import LocalAudioTrack from './LocalAudioTrack';
|
7
8
|
import type LocalTrack from './LocalTrack';
|
@@ -29,72 +30,126 @@ import {
|
|
29
30
|
*/
|
30
31
|
export async function createLocalTracks(
|
31
32
|
options?: CreateLocalTracksOptions,
|
33
|
+
loggerOptions?: LoggerOptions,
|
32
34
|
): Promise<Array<LocalTrack>> {
|
33
35
|
// set default options to true
|
34
|
-
options
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
const internalOptions = { ...(options ?? {}) };
|
37
|
+
let attemptExactMatch = false;
|
38
|
+
let retryAudioOptions: AudioCaptureOptions | undefined | boolean = options?.audio;
|
39
|
+
let retryVideoOptions: VideoCaptureOptions | undefined | boolean = options?.video;
|
40
|
+
// if the user passes a device id as a string, we default to exact match
|
41
|
+
if (
|
42
|
+
internalOptions.audio &&
|
43
|
+
typeof internalOptions.audio === 'object' &&
|
44
|
+
typeof internalOptions.audio.deviceId === 'string'
|
45
|
+
) {
|
46
|
+
const deviceId: string = internalOptions.audio.deviceId;
|
47
|
+
internalOptions.audio.deviceId = { exact: deviceId };
|
48
|
+
attemptExactMatch = true;
|
49
|
+
retryAudioOptions = {
|
50
|
+
...internalOptions.audio,
|
51
|
+
deviceId: { ideal: deviceId },
|
52
|
+
};
|
53
|
+
}
|
54
|
+
if (
|
55
|
+
internalOptions.video &&
|
56
|
+
typeof internalOptions.video === 'object' &&
|
57
|
+
typeof internalOptions.video.deviceId === 'string'
|
58
|
+
) {
|
59
|
+
const deviceId: string = internalOptions.video.deviceId;
|
60
|
+
internalOptions.video.deviceId = { exact: deviceId };
|
61
|
+
attemptExactMatch = true;
|
62
|
+
retryVideoOptions = {
|
63
|
+
...internalOptions.video,
|
64
|
+
deviceId: { ideal: deviceId },
|
65
|
+
};
|
66
|
+
}
|
67
|
+
// TODO if internal options don't have device Id specified, set it to 'default'
|
68
|
+
if (
|
69
|
+
internalOptions.audio === true ||
|
70
|
+
(typeof internalOptions.audio === 'object' && !internalOptions.audio.deviceId)
|
71
|
+
) {
|
72
|
+
internalOptions.audio = { deviceId: 'default' };
|
73
|
+
}
|
74
|
+
if (
|
75
|
+
internalOptions.video === true ||
|
76
|
+
(typeof internalOptions.video === 'object' && !internalOptions.video.deviceId)
|
77
|
+
) {
|
78
|
+
internalOptions.video = { deviceId: 'default' };
|
79
|
+
}
|
80
|
+
const { audioProcessor, videoProcessor } = extractProcessorsFromOptions(internalOptions);
|
81
|
+
const opts = mergeDefaultOptions(internalOptions, audioDefaults, videoDefaults);
|
40
82
|
const constraints = constraintsForOptions(opts);
|
41
83
|
|
42
84
|
// Keep a reference to the promise on DeviceManager and await it in getLocalDevices()
|
43
85
|
// works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363
|
44
86
|
const mediaPromise = navigator.mediaDevices.getUserMedia(constraints);
|
45
87
|
|
46
|
-
if (
|
88
|
+
if (internalOptions.audio) {
|
47
89
|
DeviceManager.userMediaPromiseMap.set('audioinput', mediaPromise);
|
48
90
|
mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('audioinput'));
|
49
91
|
}
|
50
|
-
if (
|
92
|
+
if (internalOptions.video) {
|
51
93
|
DeviceManager.userMediaPromiseMap.set('videoinput', mediaPromise);
|
52
94
|
mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('videoinput'));
|
53
95
|
}
|
96
|
+
try {
|
97
|
+
const stream = await mediaPromise;
|
98
|
+
return await Promise.all(
|
99
|
+
stream.getTracks().map(async (mediaStreamTrack) => {
|
100
|
+
const isAudio = mediaStreamTrack.kind === 'audio';
|
101
|
+
let trackOptions = isAudio ? opts!.audio : opts!.video;
|
102
|
+
if (typeof trackOptions === 'boolean' || !trackOptions) {
|
103
|
+
trackOptions = {};
|
104
|
+
}
|
105
|
+
let trackConstraints: MediaTrackConstraints | undefined;
|
106
|
+
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
107
|
+
if (typeof conOrBool !== 'boolean') {
|
108
|
+
trackConstraints = conOrBool;
|
109
|
+
}
|
54
110
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
trackConstraints = conOrBool;
|
67
|
-
}
|
68
|
-
|
69
|
-
// update the constraints with the device id the user gave permissions to in the permission prompt
|
70
|
-
// otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
|
71
|
-
const newDeviceId = mediaStreamTrack.getSettings().deviceId;
|
72
|
-
if (
|
73
|
-
trackConstraints?.deviceId &&
|
74
|
-
unwrapConstraint(trackConstraints.deviceId) !== newDeviceId
|
75
|
-
) {
|
76
|
-
trackConstraints.deviceId = newDeviceId;
|
77
|
-
} else if (!trackConstraints) {
|
78
|
-
trackConstraints = { deviceId: newDeviceId };
|
79
|
-
}
|
111
|
+
// update the constraints with the device id the user gave permissions to in the permission prompt
|
112
|
+
// otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts
|
113
|
+
const newDeviceId = mediaStreamTrack.getSettings().deviceId;
|
114
|
+
if (
|
115
|
+
trackConstraints?.deviceId &&
|
116
|
+
unwrapConstraint(trackConstraints.deviceId) !== newDeviceId
|
117
|
+
) {
|
118
|
+
trackConstraints.deviceId = newDeviceId;
|
119
|
+
} else if (!trackConstraints) {
|
120
|
+
trackConstraints = { deviceId: newDeviceId };
|
121
|
+
}
|
80
122
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
123
|
+
const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, loggerOptions);
|
124
|
+
if (track.kind === Track.Kind.Video) {
|
125
|
+
track.source = Track.Source.Camera;
|
126
|
+
} else if (track.kind === Track.Kind.Audio) {
|
127
|
+
track.source = Track.Source.Microphone;
|
128
|
+
}
|
129
|
+
track.mediaStream = stream;
|
88
130
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
131
|
+
if (isAudioTrack(track) && audioProcessor) {
|
132
|
+
await track.setProcessor(audioProcessor);
|
133
|
+
} else if (isVideoTrack(track) && videoProcessor) {
|
134
|
+
await track.setProcessor(videoProcessor);
|
135
|
+
}
|
94
136
|
|
95
|
-
|
96
|
-
|
97
|
-
|
137
|
+
return track;
|
138
|
+
}),
|
139
|
+
);
|
140
|
+
} catch (e) {
|
141
|
+
if (!attemptExactMatch) {
|
142
|
+
throw e;
|
143
|
+
}
|
144
|
+
return createLocalTracks(
|
145
|
+
{
|
146
|
+
...options,
|
147
|
+
audio: retryAudioOptions,
|
148
|
+
video: retryVideoOptions,
|
149
|
+
},
|
150
|
+
loggerOptions,
|
151
|
+
);
|
152
|
+
}
|
98
153
|
}
|
99
154
|
|
100
155
|
/**
|
package/src/room/track/utils.ts
CHANGED
@@ -33,7 +33,7 @@ export function mergeDefaultOptions(
|
|
33
33
|
clonedOptions.audio as Record<string, unknown>,
|
34
34
|
audioDefaults as Record<string, unknown>,
|
35
35
|
);
|
36
|
-
clonedOptions.audio.deviceId ??= 'default';
|
36
|
+
clonedOptions.audio.deviceId ??= { ideal: 'default' };
|
37
37
|
if (audioProcessor || defaultAudioProcessor) {
|
38
38
|
clonedOptions.audio.processor = audioProcessor ?? defaultAudioProcessor;
|
39
39
|
}
|
@@ -43,7 +43,7 @@ export function mergeDefaultOptions(
|
|
43
43
|
clonedOptions.video as Record<string, unknown>,
|
44
44
|
videoDefaults as Record<string, unknown>,
|
45
45
|
);
|
46
|
-
clonedOptions.video.deviceId ??= 'default';
|
46
|
+
clonedOptions.video.deviceId ??= { ideal: 'default' };
|
47
47
|
if (videoProcessor || defaultVideoProcessor) {
|
48
48
|
clonedOptions.video.processor = videoProcessor ?? defaultVideoProcessor;
|
49
49
|
}
|
@@ -81,9 +81,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
|
|
81
81
|
}
|
82
82
|
});
|
83
83
|
constraints.video = videoOptions;
|
84
|
-
constraints.video.deviceId ??= 'default';
|
84
|
+
constraints.video.deviceId ??= { ideal: 'default' };
|
85
85
|
} else {
|
86
|
-
constraints.video = options.video ? { deviceId: 'default' } : false;
|
86
|
+
constraints.video = options.video ? { deviceId: { ideal: 'default' } } : false;
|
87
87
|
}
|
88
88
|
} else {
|
89
89
|
constraints.video = false;
|
@@ -92,9 +92,9 @@ export function constraintsForOptions(options: CreateLocalTracksOptions): MediaS
|
|
92
92
|
if (options.audio) {
|
93
93
|
if (typeof options.audio === 'object') {
|
94
94
|
constraints.audio = options.audio;
|
95
|
-
constraints.audio.deviceId ??= 'default';
|
95
|
+
constraints.audio.deviceId ??= { ideal: 'default' };
|
96
96
|
} else {
|
97
|
-
constraints.audio = { deviceId: 'default' };
|
97
|
+
constraints.audio = { deviceId: { ideal: 'default' } };
|
98
98
|
}
|
99
99
|
} else {
|
100
100
|
constraints.audio = false;
|