livekit-client 2.15.4 → 2.15.5
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.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +329 -124
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +834 -541
- 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/e2ee/worker/FrameCryptor.d.ts +0 -46
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +6 -10
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -0
- package/dist/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -0
- package/dist/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/src/room/data-stream/outgoing/StreamWriter.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +13 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +32 -19
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +0 -46
- package/dist/ts4.2/src/e2ee/worker/naluUtils.d.ts +27 -0
- package/dist/ts4.2/src/index.d.ts +2 -2
- package/dist/ts4.2/src/room/Room.d.ts +6 -10
- package/dist/ts4.2/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +20 -0
- package/dist/ts4.2/src/room/{StreamReader.d.ts → data-stream/incoming/StreamReader.d.ts} +32 -6
- package/dist/ts4.2/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +27 -0
- package/dist/ts4.2/src/room/{StreamWriter.d.ts → data-stream/outgoing/StreamWriter.d.ts} +1 -1
- package/dist/ts4.2/src/room/errors.d.ts +13 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +32 -19
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +7 -2
- package/dist/ts4.2/src/room/types.d.ts +17 -1
- package/package.json +1 -1
- package/src/e2ee/worker/FrameCryptor.ts +48 -139
- package/src/e2ee/worker/naluUtils.ts +328 -0
- package/src/index.ts +2 -2
- package/src/room/Room.ts +93 -206
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +247 -0
- package/src/room/data-stream/incoming/StreamReader.ts +317 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +316 -0
- package/src/room/{StreamWriter.ts → data-stream/outgoing/StreamWriter.ts} +1 -1
- package/src/room/errors.ts +34 -0
- package/src/room/participant/LocalParticipant.ts +39 -295
- package/src/room/track/LocalAudioTrack.ts +2 -2
- package/src/room/track/LocalTrack.ts +65 -48
- package/src/room/types.ts +22 -1
- package/src/room/utils.ts +2 -2
- package/dist/src/room/StreamReader.d.ts.map +0 -1
- package/dist/src/room/StreamWriter.d.ts.map +0 -1
- package/src/room/StreamReader.ts +0 -170
@@ -1,4 +1,3 @@
|
|
1
|
-
import { Mutex } from '@livekit/mutex';
|
2
1
|
import {
|
3
2
|
AddTrackRequest,
|
4
3
|
AudioTrackFeature,
|
@@ -7,12 +6,6 @@ import {
|
|
7
6
|
Codec,
|
8
7
|
DataPacket,
|
9
8
|
DataPacket_Kind,
|
10
|
-
DataStream_ByteHeader,
|
11
|
-
DataStream_Chunk,
|
12
|
-
DataStream_Header,
|
13
|
-
DataStream_OperationType,
|
14
|
-
DataStream_TextHeader,
|
15
|
-
DataStream_Trailer,
|
16
9
|
Encryption_Type,
|
17
10
|
JoinResponse,
|
18
11
|
ParticipantInfo,
|
@@ -34,7 +27,8 @@ import { SignalConnectionState } from '../../api/SignalClient';
|
|
34
27
|
import type { InternalRoomOptions } from '../../options';
|
35
28
|
import { PCTransportState } from '../PCTransportManager';
|
36
29
|
import type RTCEngine from '../RTCEngine';
|
37
|
-
import
|
30
|
+
import type OutgoingDataStreamManager from '../data-stream/outgoing/OutgoingDataStreamManager';
|
31
|
+
import type { TextStreamWriter } from '../data-stream/outgoing/StreamWriter';
|
38
32
|
import { defaultVideoCodec } from '../defaults';
|
39
33
|
import {
|
40
34
|
DeviceUnsupportedError,
|
@@ -76,10 +70,11 @@ import {
|
|
76
70
|
sourceToKind,
|
77
71
|
} from '../track/utils';
|
78
72
|
import {
|
79
|
-
type ByteStreamInfo,
|
80
73
|
type ChatMessage,
|
81
74
|
type DataPublishOptions,
|
75
|
+
type SendFileOptions,
|
82
76
|
type SendTextOptions,
|
77
|
+
type StreamBytesOptions,
|
83
78
|
type StreamTextOptions,
|
84
79
|
type TextStreamInfo,
|
85
80
|
} from '../types';
|
@@ -96,9 +91,7 @@ import {
|
|
96
91
|
isSafari17Based,
|
97
92
|
isVideoTrack,
|
98
93
|
isWeb,
|
99
|
-
numberToBigInt,
|
100
94
|
sleep,
|
101
|
-
splitUtf8,
|
102
95
|
supportsAV1,
|
103
96
|
supportsVP9,
|
104
97
|
} from '../utils';
|
@@ -112,8 +105,6 @@ import {
|
|
112
105
|
getDefaultDegradationPreference,
|
113
106
|
} from './publishUtils';
|
114
107
|
|
115
|
-
const STREAM_CHUNK_SIZE = 15_000;
|
116
|
-
|
117
108
|
export default class LocalParticipant extends Participant {
|
118
109
|
audioTrackPublications: Map<string, LocalTrackPublication>;
|
119
110
|
|
@@ -157,6 +148,8 @@ export default class LocalParticipant extends Participant {
|
|
157
148
|
|
158
149
|
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>;
|
159
150
|
|
151
|
+
private roomOutgoingDataStreamManager: OutgoingDataStreamManager;
|
152
|
+
|
160
153
|
private pendingSignalRequests: Map<
|
161
154
|
number,
|
162
155
|
{
|
@@ -185,6 +178,7 @@ export default class LocalParticipant extends Participant {
|
|
185
178
|
engine: RTCEngine,
|
186
179
|
options: InternalRoomOptions,
|
187
180
|
roomRpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>>,
|
181
|
+
roomOutgoingDataStreamManager: OutgoingDataStreamManager,
|
188
182
|
) {
|
189
183
|
super(sid, identity, undefined, undefined, undefined, {
|
190
184
|
loggerName: options.loggerName,
|
@@ -203,6 +197,7 @@ export default class LocalParticipant extends Participant {
|
|
203
197
|
]);
|
204
198
|
this.pendingSignalRequests = new Map();
|
205
199
|
this.rpcHandlers = roomRpcHandlers;
|
200
|
+
this.roomOutgoingDataStreamManager = roomOutgoingDataStreamManager;
|
206
201
|
}
|
207
202
|
|
208
203
|
get lastCameraError(): Error | undefined {
|
@@ -1690,6 +1685,7 @@ export default class LocalParticipant extends Participant {
|
|
1690
1685
|
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1691
1686
|
}
|
1692
1687
|
|
1688
|
+
/** @deprecated Consider migrating to {@link sendText} */
|
1693
1689
|
async sendChatMessage(text: string, options?: SendTextOptions): Promise<ChatMessage> {
|
1694
1690
|
const msg = {
|
1695
1691
|
id: crypto.randomUUID(),
|
@@ -1712,6 +1708,7 @@ export default class LocalParticipant extends Participant {
|
|
1712
1708
|
return msg;
|
1713
1709
|
}
|
1714
1710
|
|
1711
|
+
/** @deprecated Consider migrating to {@link sendText} */
|
1715
1712
|
async editChatMessage(editText: string, originalMessage: ChatMessage) {
|
1716
1713
|
const msg = {
|
1717
1714
|
...originalMessage,
|
@@ -1733,300 +1730,47 @@ export default class LocalParticipant extends Participant {
|
|
1733
1730
|
return msg;
|
1734
1731
|
}
|
1735
1732
|
|
1733
|
+
/**
|
1734
|
+
* Sends the given string to participants in the room via the data channel.
|
1735
|
+
* For longer messages, consider using {@link streamText} instead.
|
1736
|
+
*
|
1737
|
+
* @param text The text payload
|
1738
|
+
* @param options.topic Topic identifier used to route the stream to appropriate handlers.
|
1739
|
+
*/
|
1736
1740
|
async sendText(text: string, options?: SendTextOptions): Promise<TextStreamInfo> {
|
1737
|
-
|
1738
|
-
const textInBytes = new TextEncoder().encode(text);
|
1739
|
-
const totalTextLength = textInBytes.byteLength;
|
1740
|
-
|
1741
|
-
const fileIds = options?.attachments?.map(() => crypto.randomUUID());
|
1742
|
-
|
1743
|
-
const progresses = new Array<number>(fileIds ? fileIds.length + 1 : 1).fill(0);
|
1744
|
-
|
1745
|
-
const handleProgress = (progress: number, idx: number) => {
|
1746
|
-
progresses[idx] = progress;
|
1747
|
-
const totalProgress = progresses.reduce((acc, val) => acc + val, 0);
|
1748
|
-
options?.onProgress?.(totalProgress);
|
1749
|
-
};
|
1750
|
-
|
1751
|
-
const writer = await this.streamText({
|
1752
|
-
streamId,
|
1753
|
-
totalSize: totalTextLength,
|
1754
|
-
destinationIdentities: options?.destinationIdentities,
|
1755
|
-
topic: options?.topic,
|
1756
|
-
attachedStreamIds: fileIds,
|
1757
|
-
attributes: options?.attributes,
|
1758
|
-
});
|
1759
|
-
|
1760
|
-
await writer.write(text);
|
1761
|
-
// set text part of progress to 1
|
1762
|
-
handleProgress(1, 0);
|
1763
|
-
|
1764
|
-
await writer.close();
|
1765
|
-
|
1766
|
-
if (options?.attachments && fileIds) {
|
1767
|
-
await Promise.all(
|
1768
|
-
options.attachments.map(async (file, idx) =>
|
1769
|
-
this._sendFile(fileIds[idx], file, {
|
1770
|
-
topic: options.topic,
|
1771
|
-
mimeType: file.type,
|
1772
|
-
onProgress: (progress) => {
|
1773
|
-
handleProgress(progress, idx + 1);
|
1774
|
-
},
|
1775
|
-
}),
|
1776
|
-
),
|
1777
|
-
);
|
1778
|
-
}
|
1779
|
-
return writer.info;
|
1741
|
+
return this.roomOutgoingDataStreamManager.sendText(text, options);
|
1780
1742
|
}
|
1781
1743
|
|
1782
1744
|
/**
|
1745
|
+
* Creates a new TextStreamWriter which can be used to stream text incrementally
|
1746
|
+
* to participants in the room via the data channel.
|
1747
|
+
*
|
1748
|
+
* @param options.topic Topic identifier used to route the stream to appropriate handlers.
|
1749
|
+
*
|
1783
1750
|
* @internal
|
1784
1751
|
* @experimental CAUTION, might get removed in a minor release
|
1785
1752
|
*/
|
1786
1753
|
async streamText(options?: StreamTextOptions): Promise<TextStreamWriter> {
|
1787
|
-
|
1788
|
-
|
1789
|
-
const info: TextStreamInfo = {
|
1790
|
-
id: streamId,
|
1791
|
-
mimeType: 'text/plain',
|
1792
|
-
timestamp: Date.now(),
|
1793
|
-
topic: options?.topic ?? '',
|
1794
|
-
size: options?.totalSize,
|
1795
|
-
attributes: options?.attributes,
|
1796
|
-
};
|
1797
|
-
const header = new DataStream_Header({
|
1798
|
-
streamId,
|
1799
|
-
mimeType: info.mimeType,
|
1800
|
-
topic: info.topic,
|
1801
|
-
timestamp: numberToBigInt(info.timestamp),
|
1802
|
-
totalLength: numberToBigInt(options?.totalSize),
|
1803
|
-
attributes: info.attributes,
|
1804
|
-
contentHeader: {
|
1805
|
-
case: 'textHeader',
|
1806
|
-
value: new DataStream_TextHeader({
|
1807
|
-
version: options?.version,
|
1808
|
-
attachedStreamIds: options?.attachedStreamIds,
|
1809
|
-
replyToStreamId: options?.replyToStreamId,
|
1810
|
-
operationType:
|
1811
|
-
options?.type === 'update'
|
1812
|
-
? DataStream_OperationType.UPDATE
|
1813
|
-
: DataStream_OperationType.CREATE,
|
1814
|
-
}),
|
1815
|
-
},
|
1816
|
-
});
|
1817
|
-
const destinationIdentities = options?.destinationIdentities;
|
1818
|
-
const packet = new DataPacket({
|
1819
|
-
destinationIdentities,
|
1820
|
-
value: {
|
1821
|
-
case: 'streamHeader',
|
1822
|
-
value: header,
|
1823
|
-
},
|
1824
|
-
});
|
1825
|
-
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1826
|
-
|
1827
|
-
let chunkId = 0;
|
1828
|
-
const localP = this;
|
1829
|
-
|
1830
|
-
const writableStream = new WritableStream<string>({
|
1831
|
-
// Implement the sink
|
1832
|
-
async write(text) {
|
1833
|
-
for (const textByteChunk of splitUtf8(text, STREAM_CHUNK_SIZE)) {
|
1834
|
-
await localP.engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
1835
|
-
const chunk = new DataStream_Chunk({
|
1836
|
-
content: textByteChunk,
|
1837
|
-
streamId,
|
1838
|
-
chunkIndex: numberToBigInt(chunkId),
|
1839
|
-
});
|
1840
|
-
const chunkPacket = new DataPacket({
|
1841
|
-
destinationIdentities,
|
1842
|
-
value: {
|
1843
|
-
case: 'streamChunk',
|
1844
|
-
value: chunk,
|
1845
|
-
},
|
1846
|
-
});
|
1847
|
-
await localP.engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
|
1848
|
-
|
1849
|
-
chunkId += 1;
|
1850
|
-
}
|
1851
|
-
},
|
1852
|
-
async close() {
|
1853
|
-
const trailer = new DataStream_Trailer({
|
1854
|
-
streamId,
|
1855
|
-
});
|
1856
|
-
const trailerPacket = new DataPacket({
|
1857
|
-
destinationIdentities,
|
1858
|
-
value: {
|
1859
|
-
case: 'streamTrailer',
|
1860
|
-
value: trailer,
|
1861
|
-
},
|
1862
|
-
});
|
1863
|
-
await localP.engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
|
1864
|
-
},
|
1865
|
-
abort(err) {
|
1866
|
-
console.log('Sink error:', err);
|
1867
|
-
// TODO handle aborts to signal something to receiver side
|
1868
|
-
},
|
1869
|
-
});
|
1870
|
-
|
1871
|
-
let onEngineClose = async () => {
|
1872
|
-
await writer.close();
|
1873
|
-
};
|
1874
|
-
|
1875
|
-
localP.engine.once(EngineEvent.Closing, onEngineClose);
|
1876
|
-
|
1877
|
-
const writer = new TextStreamWriter(writableStream, info, () =>
|
1878
|
-
this.engine.off(EngineEvent.Closing, onEngineClose),
|
1879
|
-
);
|
1880
|
-
|
1881
|
-
return writer;
|
1754
|
+
return this.roomOutgoingDataStreamManager.streamText(options);
|
1882
1755
|
}
|
1883
1756
|
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
},
|
1892
|
-
): Promise<{ id: string }> {
|
1893
|
-
const streamId = crypto.randomUUID();
|
1894
|
-
await this._sendFile(streamId, file, options);
|
1895
|
-
return { id: streamId };
|
1896
|
-
}
|
1897
|
-
|
1898
|
-
private async _sendFile(
|
1899
|
-
streamId: string,
|
1900
|
-
file: File,
|
1901
|
-
options?: {
|
1902
|
-
mimeType?: string;
|
1903
|
-
topic?: string;
|
1904
|
-
encryptionType?: Encryption_Type.NONE;
|
1905
|
-
destinationIdentities?: Array<string>;
|
1906
|
-
onProgress?: (progress: number) => void;
|
1907
|
-
},
|
1908
|
-
) {
|
1909
|
-
const writer = await this.streamBytes({
|
1910
|
-
streamId,
|
1911
|
-
totalSize: file.size,
|
1912
|
-
name: file.name,
|
1913
|
-
mimeType: options?.mimeType ?? file.type,
|
1914
|
-
topic: options?.topic,
|
1915
|
-
destinationIdentities: options?.destinationIdentities,
|
1916
|
-
});
|
1917
|
-
const reader = file.stream().getReader();
|
1918
|
-
while (true) {
|
1919
|
-
const { done, value } = await reader.read();
|
1920
|
-
if (done) {
|
1921
|
-
break;
|
1922
|
-
}
|
1923
|
-
await writer.write(value);
|
1924
|
-
}
|
1925
|
-
await writer.close();
|
1926
|
-
return writer.info;
|
1757
|
+
/** Send a File to all participants in the room via the data channel.
|
1758
|
+
* @param file The File object payload
|
1759
|
+
* @param options.topic Topic identifier used to route the stream to appropriate handlers.
|
1760
|
+
* @param options.onProgress A callback function used to monitor the upload progress percentage.
|
1761
|
+
*/
|
1762
|
+
async sendFile(file: File, options?: SendFileOptions): Promise<{ id: string }> {
|
1763
|
+
return this.roomOutgoingDataStreamManager.sendFile(file, options);
|
1927
1764
|
}
|
1928
1765
|
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
}) {
|
1938
|
-
const streamId = options?.streamId ?? crypto.randomUUID();
|
1939
|
-
const destinationIdentities = options?.destinationIdentities;
|
1940
|
-
|
1941
|
-
const info: ByteStreamInfo = {
|
1942
|
-
id: streamId,
|
1943
|
-
mimeType: options?.mimeType ?? 'application/octet-stream',
|
1944
|
-
topic: options?.topic ?? '',
|
1945
|
-
timestamp: Date.now(),
|
1946
|
-
attributes: options?.attributes,
|
1947
|
-
size: options?.totalSize,
|
1948
|
-
name: options?.name ?? 'unknown',
|
1949
|
-
};
|
1950
|
-
|
1951
|
-
const header = new DataStream_Header({
|
1952
|
-
totalLength: numberToBigInt(info.size ?? 0),
|
1953
|
-
mimeType: info.mimeType,
|
1954
|
-
streamId,
|
1955
|
-
topic: info.topic,
|
1956
|
-
timestamp: numberToBigInt(Date.now()),
|
1957
|
-
attributes: info.attributes,
|
1958
|
-
contentHeader: {
|
1959
|
-
case: 'byteHeader',
|
1960
|
-
value: new DataStream_ByteHeader({
|
1961
|
-
name: info.name,
|
1962
|
-
}),
|
1963
|
-
},
|
1964
|
-
});
|
1965
|
-
|
1966
|
-
const packet = new DataPacket({
|
1967
|
-
destinationIdentities,
|
1968
|
-
value: {
|
1969
|
-
case: 'streamHeader',
|
1970
|
-
value: header,
|
1971
|
-
},
|
1972
|
-
});
|
1973
|
-
|
1974
|
-
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1975
|
-
|
1976
|
-
let chunkId = 0;
|
1977
|
-
const writeMutex = new Mutex();
|
1978
|
-
const engine = this.engine;
|
1979
|
-
const log = this.log;
|
1980
|
-
|
1981
|
-
const writableStream = new WritableStream<Uint8Array>({
|
1982
|
-
async write(chunk) {
|
1983
|
-
const unlock = await writeMutex.lock();
|
1984
|
-
|
1985
|
-
let byteOffset = 0;
|
1986
|
-
try {
|
1987
|
-
while (byteOffset < chunk.byteLength) {
|
1988
|
-
const subChunk = chunk.slice(byteOffset, byteOffset + STREAM_CHUNK_SIZE);
|
1989
|
-
await engine.waitForBufferStatusLow(DataPacket_Kind.RELIABLE);
|
1990
|
-
const chunkPacket = new DataPacket({
|
1991
|
-
destinationIdentities,
|
1992
|
-
value: {
|
1993
|
-
case: 'streamChunk',
|
1994
|
-
value: new DataStream_Chunk({
|
1995
|
-
content: subChunk,
|
1996
|
-
streamId,
|
1997
|
-
chunkIndex: numberToBigInt(chunkId),
|
1998
|
-
}),
|
1999
|
-
},
|
2000
|
-
});
|
2001
|
-
await engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
|
2002
|
-
chunkId += 1;
|
2003
|
-
byteOffset += subChunk.byteLength;
|
2004
|
-
}
|
2005
|
-
} finally {
|
2006
|
-
unlock();
|
2007
|
-
}
|
2008
|
-
},
|
2009
|
-
async close() {
|
2010
|
-
const trailer = new DataStream_Trailer({
|
2011
|
-
streamId,
|
2012
|
-
});
|
2013
|
-
const trailerPacket = new DataPacket({
|
2014
|
-
destinationIdentities,
|
2015
|
-
value: {
|
2016
|
-
case: 'streamTrailer',
|
2017
|
-
value: trailer,
|
2018
|
-
},
|
2019
|
-
});
|
2020
|
-
await engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
|
2021
|
-
},
|
2022
|
-
abort(err) {
|
2023
|
-
log.error('Sink error:', err);
|
2024
|
-
},
|
2025
|
-
});
|
2026
|
-
|
2027
|
-
const byteWriter = new ByteStreamWriter(writableStream, info);
|
2028
|
-
|
2029
|
-
return byteWriter;
|
1766
|
+
/**
|
1767
|
+
* Stream bytes incrementally to participants in the room via the data channel.
|
1768
|
+
* For sending files, consider using {@link sendFile} instead.
|
1769
|
+
*
|
1770
|
+
* @param options.topic Topic identifier used to route the stream to appropriate handlers.
|
1771
|
+
*/
|
1772
|
+
async streamBytes(options?: StreamBytesOptions) {
|
1773
|
+
return this.roomOutgoingDataStreamManager.streamBytes(options);
|
2030
1774
|
}
|
2031
1775
|
|
2032
1776
|
/**
|
@@ -169,7 +169,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
169
169
|
};
|
170
170
|
|
171
171
|
async setProcessor(processor: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions>) {
|
172
|
-
const unlock = await this.
|
172
|
+
const unlock = await this.trackChangeLock.lock();
|
173
173
|
try {
|
174
174
|
if (!isReactNative() && !this.audioContext) {
|
175
175
|
throw Error(
|
@@ -177,7 +177,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
177
177
|
);
|
178
178
|
}
|
179
179
|
if (this.processor) {
|
180
|
-
await this.
|
180
|
+
await this.internalStopProcessor();
|
181
181
|
}
|
182
182
|
|
183
183
|
const processorOptions = {
|
@@ -57,15 +57,13 @@ export default abstract class LocalTrack<
|
|
57
57
|
|
58
58
|
protected processor?: TrackProcessor<TrackKind, any>;
|
59
59
|
|
60
|
-
protected processorLock: Mutex;
|
61
|
-
|
62
60
|
protected audioContext?: AudioContext;
|
63
61
|
|
64
62
|
protected manuallyStopped: boolean = false;
|
65
63
|
|
66
64
|
protected localTrackRecorder: LocalTrackRecorder<typeof this> | undefined;
|
67
65
|
|
68
|
-
|
66
|
+
protected trackChangeLock: Mutex;
|
69
67
|
|
70
68
|
/**
|
71
69
|
*
|
@@ -86,9 +84,14 @@ export default abstract class LocalTrack<
|
|
86
84
|
this.providedByUser = userProvidedTrack;
|
87
85
|
this.muteLock = new Mutex();
|
88
86
|
this.pauseUpstreamLock = new Mutex();
|
89
|
-
this.
|
90
|
-
this.
|
91
|
-
|
87
|
+
this.trackChangeLock = new Mutex();
|
88
|
+
this.trackChangeLock.lock().then(async (unlock) => {
|
89
|
+
try {
|
90
|
+
await this.setMediaStreamTrack(mediaTrack, true);
|
91
|
+
} finally {
|
92
|
+
unlock();
|
93
|
+
}
|
94
|
+
});
|
92
95
|
|
93
96
|
// added to satisfy TS compiler, constraints are synced with MediaStreamTrack
|
94
97
|
this._constraints = mediaTrack.getConstraints();
|
@@ -171,27 +174,22 @@ export default abstract class LocalTrack<
|
|
171
174
|
}
|
172
175
|
let processedTrack: MediaStreamTrack | undefined;
|
173
176
|
if (this.processor && newTrack) {
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
throw TypeError('cannot set processor on track of unknown kind');
|
179
|
-
}
|
177
|
+
this.log.debug('restarting processor', this.logContext);
|
178
|
+
if (this.kind === 'unknown') {
|
179
|
+
throw TypeError('cannot set processor on track of unknown kind');
|
180
|
+
}
|
180
181
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
}
|
186
|
-
await this.processor.restart({
|
187
|
-
track: newTrack,
|
188
|
-
kind: this.kind,
|
189
|
-
element: this.processorElement,
|
190
|
-
});
|
191
|
-
processedTrack = this.processor.processedTrack;
|
192
|
-
} finally {
|
193
|
-
unlock();
|
182
|
+
if (this.processorElement) {
|
183
|
+
attachToElement(newTrack, this.processorElement);
|
184
|
+
// ensure the processorElement itself stays muted
|
185
|
+
this.processorElement.muted = true;
|
194
186
|
}
|
187
|
+
await this.processor.restart({
|
188
|
+
track: newTrack,
|
189
|
+
kind: this.kind,
|
190
|
+
element: this.processorElement,
|
191
|
+
});
|
192
|
+
processedTrack = this.processor.processedTrack;
|
195
193
|
}
|
196
194
|
if (this.sender && this.sender.transport?.state !== 'closed') {
|
197
195
|
await this.sender.replaceTrack(processedTrack ?? newTrack);
|
@@ -290,36 +288,42 @@ export default abstract class LocalTrack<
|
|
290
288
|
track: MediaStreamTrack,
|
291
289
|
userProvidedOrOptions: boolean | ReplaceTrackOptions | undefined,
|
292
290
|
) {
|
293
|
-
|
294
|
-
|
295
|
-
|
291
|
+
const unlock = await this.trackChangeLock.lock();
|
292
|
+
try {
|
293
|
+
if (!this.sender) {
|
294
|
+
throw new TrackInvalidError('unable to replace an unpublished track');
|
295
|
+
}
|
296
296
|
|
297
|
-
|
298
|
-
|
297
|
+
let userProvidedTrack: boolean | undefined;
|
298
|
+
let stopProcessor: boolean | undefined;
|
299
299
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
300
|
+
if (typeof userProvidedOrOptions === 'boolean') {
|
301
|
+
userProvidedTrack = userProvidedOrOptions;
|
302
|
+
} else if (userProvidedOrOptions !== undefined) {
|
303
|
+
userProvidedTrack = userProvidedOrOptions.userProvidedTrack;
|
304
|
+
stopProcessor = userProvidedOrOptions.stopProcessor;
|
305
|
+
}
|
306
306
|
|
307
|
-
|
307
|
+
this.providedByUser = userProvidedTrack ?? true;
|
308
308
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
309
|
+
this.log.debug('replace MediaStreamTrack', this.logContext);
|
310
|
+
await this.setMediaStreamTrack(track);
|
311
|
+
// this must be synced *after* setting mediaStreamTrack above, since it relies
|
312
|
+
// on the previous state in order to cleanup
|
313
313
|
|
314
|
-
|
315
|
-
|
314
|
+
if (stopProcessor && this.processor) {
|
315
|
+
await this.internalStopProcessor();
|
316
|
+
}
|
317
|
+
return this;
|
318
|
+
} finally {
|
319
|
+
unlock();
|
316
320
|
}
|
317
|
-
return this;
|
318
321
|
}
|
319
322
|
|
320
323
|
protected async restart(constraints?: MediaTrackConstraints) {
|
321
324
|
this.manuallyStopped = false;
|
322
|
-
const unlock = await this.
|
325
|
+
const unlock = await this.trackChangeLock.lock();
|
326
|
+
|
323
327
|
try {
|
324
328
|
if (!constraints) {
|
325
329
|
constraints = this._constraints;
|
@@ -518,7 +522,7 @@ export default abstract class LocalTrack<
|
|
518
522
|
* @returns
|
519
523
|
*/
|
520
524
|
async setProcessor(processor: TrackProcessor<TrackKind>, showProcessedStreamLocally = true) {
|
521
|
-
const unlock = await this.
|
525
|
+
const unlock = await this.trackChangeLock.lock();
|
522
526
|
try {
|
523
527
|
this.log.debug('setting up processor', this.logContext);
|
524
528
|
|
@@ -534,7 +538,7 @@ export default abstract class LocalTrack<
|
|
534
538
|
this.log.debug('processor initialized', this.logContext);
|
535
539
|
|
536
540
|
if (this.processor) {
|
537
|
-
await this.
|
541
|
+
await this.internalStopProcessor();
|
538
542
|
}
|
539
543
|
if (this.kind === 'unknown') {
|
540
544
|
throw TypeError('cannot set processor on track of unknown kind');
|
@@ -589,8 +593,21 @@ export default abstract class LocalTrack<
|
|
589
593
|
* @returns
|
590
594
|
*/
|
591
595
|
async stopProcessor(keepElement = true) {
|
592
|
-
|
596
|
+
const unlock = await this.trackChangeLock.lock();
|
597
|
+
try {
|
598
|
+
await this.internalStopProcessor(keepElement);
|
599
|
+
} finally {
|
600
|
+
unlock();
|
601
|
+
}
|
602
|
+
}
|
593
603
|
|
604
|
+
/**
|
605
|
+
* @internal
|
606
|
+
* This method assumes the caller has acquired a trackChangeLock already.
|
607
|
+
* The public facing method for stopping the processor is `stopProcessor` and it wraps this method in the trackChangeLock.
|
608
|
+
*/
|
609
|
+
protected async internalStopProcessor(keepElement = true) {
|
610
|
+
if (!this.processor) return;
|
594
611
|
this.log.debug('stopping processor', this.logContext);
|
595
612
|
this.processor.processedTrack?.stop();
|
596
613
|
await this.processor.destroy();
|
package/src/room/types.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
import type { DataStream_Chunk } from '@livekit/protocol';
|
1
|
+
import type { DataStream_Chunk, Encryption_Type } from '@livekit/protocol';
|
2
|
+
import type { Future } from './utils';
|
2
3
|
|
3
4
|
export type SimulationOptions = {
|
4
5
|
publish?: {
|
@@ -35,6 +36,24 @@ export interface StreamTextOptions {
|
|
35
36
|
attributes?: Record<string, string>;
|
36
37
|
}
|
37
38
|
|
39
|
+
export type StreamBytesOptions = {
|
40
|
+
name?: string;
|
41
|
+
topic?: string;
|
42
|
+
attributes?: Record<string, string>;
|
43
|
+
destinationIdentities?: Array<string>;
|
44
|
+
streamId?: string;
|
45
|
+
mimeType?: string;
|
46
|
+
totalSize?: number;
|
47
|
+
};
|
48
|
+
|
49
|
+
export type SendFileOptions = Pick<
|
50
|
+
StreamBytesOptions,
|
51
|
+
'topic' | 'mimeType' | 'destinationIdentities'
|
52
|
+
> & {
|
53
|
+
onProgress?: (progress: number) => void;
|
54
|
+
encryptionType?: Encryption_Type.NONE;
|
55
|
+
};
|
56
|
+
|
38
57
|
export type DataPublishOptions = {
|
39
58
|
/**
|
40
59
|
* whether to send this as reliable or lossy.
|
@@ -105,6 +124,8 @@ export interface StreamController<T extends DataStream_Chunk> {
|
|
105
124
|
controller: ReadableStreamDefaultController<T>;
|
106
125
|
startTime: number;
|
107
126
|
endTime?: number;
|
127
|
+
sendingParticipantIdentity: string;
|
128
|
+
outOfBandFailureRejectingFuture: Future<never>;
|
108
129
|
}
|
109
130
|
|
110
131
|
export interface BaseStreamInfo {
|
package/src/room/utils.ts
CHANGED
@@ -538,13 +538,13 @@ export function unwrapConstraint(constraint: ConstrainDOMString | ConstrainULong
|
|
538
538
|
if (Array.isArray(constraint)) {
|
539
539
|
return constraint[0];
|
540
540
|
}
|
541
|
-
if (constraint.exact) {
|
541
|
+
if (constraint.exact !== undefined) {
|
542
542
|
if (Array.isArray(constraint.exact)) {
|
543
543
|
return constraint.exact[0];
|
544
544
|
}
|
545
545
|
return constraint.exact;
|
546
546
|
}
|
547
|
-
if (constraint.ideal) {
|
547
|
+
if (constraint.ideal !== undefined) {
|
548
548
|
if (Array.isArray(constraint.ideal)) {
|
549
549
|
return constraint.ideal[0];
|
550
550
|
}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"StreamReader.d.ts","sourceRoot":"","sources":["../../../src/room/StreamReader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9E,uBAAe,gBAAgB,CAAC,CAAC,SAAS,cAAc;IACtD,SAAS,CAAC,MAAM,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAEnD,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEjC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;IAEnB,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC;IAEhC,IAAI,IAAI,MAEP;gBAEW,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAC,EAAE,MAAM;IAOrF,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAErE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAEpD,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;CACxD;AAED,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,cAAc,CAAC;IACpE,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,gBAAgB;IAQrD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAEpD,CAAC,MAAM,CAAC,aAAa,CAAC;oBAIF,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;kBAenC,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;;IAOjD,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;CAO5C;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,cAAc,CAAC;IACpE,OAAO,CAAC,cAAc,CAAgC;IAEtD;;;OAGG;gBAED,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,cAAc,CAAC,gBAAgB,CAAC,EACxC,eAAe,CAAC,EAAE,MAAM;IAM1B,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,gBAAgB;IAerD;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAEpD;;;;OAIG;IACH,CAAC,MAAM,CAAC,aAAa,CAAC;oBAKF,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;kBAmB/B,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;;IAO7C,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CAOjC;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAClC,IAAI,CAAC;AAEV,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAClC,IAAI,CAAC"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"StreamWriter.d.ts","sourceRoot":"","sources":["../../../src/room/StreamWriter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9E,cAAM,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,cAAc;IACvD,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAE5C,SAAS,CAAC,aAAa,EAAE,2BAA2B,CAAC,CAAC,CAAC,CAAC;IAExD,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;gBAEZ,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI;IAOnF,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxB,KAAK;CAKZ;AAED,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,MAAM,EAAE,cAAc,CAAC;CAAG;AAEjF,qBAAa,gBAAiB,SAAQ,gBAAgB,CAAC,UAAU,EAAE,cAAc,CAAC;CAAG"}
|