livekit-client
Advanced tools
Comparing version 2.9.1 to 2.9.2
@@ -21,2 +21,3 @@ import type TypedEmitter from 'typed-emitter'; | ||
description: string; | ||
data?: any; | ||
}; | ||
@@ -27,2 +28,3 @@ export interface CheckerOptions { | ||
connectOptions?: RoomConnectOptions; | ||
protocol?: 'udp' | 'tcp'; | ||
} | ||
@@ -37,4 +39,4 @@ declare const Checker_base: new () => TypedEmitter<CheckerCallbacks>; | ||
logs: Array<LogMessage>; | ||
errorsAsWarnings: boolean; | ||
name: string; | ||
options: CheckerOptions; | ||
constructor(url: string, token: string, options?: CheckerOptions); | ||
@@ -45,5 +47,6 @@ abstract get description(): string; | ||
protected isSuccess(): boolean; | ||
protected connect(): Promise<Room>; | ||
protected connect(url?: string): Promise<Room>; | ||
protected disconnect(): Promise<void>; | ||
protected skip(): void; | ||
protected switchProtocol(protocol: 'udp' | 'tcp' | 'tls'): Promise<void>; | ||
protected appendMessage(message: string): void; | ||
@@ -50,0 +53,0 @@ protected appendWarning(message: string): void; |
@@ -5,3 +5,4 @@ import { Checker } from './Checker'; | ||
perform(): Promise<void>; | ||
checkForVideo(track: MediaStreamTrack): Promise<void>; | ||
} | ||
//# sourceMappingURL=publishVideo.d.ts.map |
@@ -23,2 +23,4 @@ import type TypedEmitter from 'typed-emitter'; | ||
checkPublishVideo(): Promise<CheckInfo>; | ||
checkConnectionProtocol(): Promise<CheckInfo>; | ||
checkCloudRegion(): Promise<CheckInfo>; | ||
} | ||
@@ -25,0 +27,0 @@ type ConnectionCheckCallbacks = { |
@@ -45,4 +45,4 @@ import { Mutex } from '@livekit/mutex'; | ||
export * from './version'; | ||
export { ConnectionQuality, ConnectionState, CriticalTimers, DataPacket_Kind, DefaultReconnectPolicy, DisconnectReason, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, Participant, RemoteAudioTrack, RemoteParticipant, ParticipantKind, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, SubscriptionError, TrackPublication, compareVersions, createAudioAnalyser, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, isBrowserSupported, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, Mutex, isAudioTrack, isLocalTrack, isRemoteTrack, isVideoTrack, isLocalParticipant, isRemoteParticipant, }; | ||
export type { AudioAnalyserOptions, ElementInfo, LiveKitReactNativeInfo, ParticipantTrackPermission, AudioReceiverStats, AudioSenderStats, VideoReceiverStats, VideoSenderStats, ReconnectContext, ReconnectPolicy, TrackType, }; | ||
export { ConnectionQuality, ConnectionState, CriticalTimers, DataPacket_Kind, DefaultReconnectPolicy, DisconnectReason, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, Participant, RemoteAudioTrack, RemoteParticipant, ParticipantKind, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, SubscriptionError, TrackPublication, TrackType, compareVersions, createAudioAnalyser, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, isBrowserSupported, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, Mutex, isAudioTrack, isLocalTrack, isRemoteTrack, isVideoTrack, isLocalParticipant, isRemoteParticipant, }; | ||
export type { AudioAnalyserOptions, ElementInfo, LiveKitReactNativeInfo, ParticipantTrackPermission, AudioReceiverStats, AudioSenderStats, VideoReceiverStats, VideoSenderStats, ReconnectContext, ReconnectPolicy, }; | ||
//# sourceMappingURL=index.d.ts.map |
import type { DataStream_Chunk } from '@livekit/protocol'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamChunk, TextStreamInfo } from './types'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
declare abstract class BaseStreamReader<T extends BaseStreamInfo> { | ||
@@ -44,4 +44,4 @@ protected reader: ReadableStream<DataStream_Chunk>; | ||
[Symbol.asyncIterator](): { | ||
next: () => Promise<IteratorResult<TextStreamChunk>>; | ||
return(): Promise<IteratorResult<TextStreamChunk>>; | ||
next: () => Promise<IteratorResult<string>>; | ||
return(): Promise<IteratorResult<string>>; | ||
}; | ||
@@ -48,0 +48,0 @@ readAll(): Promise<string>; |
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
declare class BaseStreamWriter<T, InfoType extends BaseStreamInfo> { | ||
protected writableStream: WritableStream<[T, number?]>; | ||
protected defaultWriter: WritableStreamDefaultWriter<[T, number?]>; | ||
protected writableStream: WritableStream<T>; | ||
protected defaultWriter: WritableStreamDefaultWriter<T>; | ||
protected onClose?: () => void; | ||
readonly info: InfoType; | ||
constructor(writableStream: WritableStream<[T, number?]>, info: InfoType, onClose?: () => void); | ||
constructor(writableStream: WritableStream<T>, info: InfoType, onClose?: () => void); | ||
write(chunk: T): Promise<void>; | ||
@@ -9,0 +9,0 @@ close(): Promise<void>; |
@@ -92,7 +92,2 @@ import type { DataStream_Chunk } from '@livekit/protocol'; | ||
} | ||
export type TextStreamChunk = { | ||
index: number; | ||
current: string; | ||
collected: string; | ||
}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -118,2 +118,3 @@ import { ChatMessage as ChatMessageModel, ClientInfo, DisconnectReason, Transcription as TranscriptionModel } from '@livekit/protocol'; | ||
export declare function isRemoteParticipant(p: Participant): p is RemoteParticipant; | ||
export declare function splitUtf8(s: string, n: number): string[]; | ||
//# sourceMappingURL=utils.d.ts.map |
@@ -21,2 +21,3 @@ import type TypedEmitter from 'typed-emitter'; | ||
description: string; | ||
data?: any; | ||
}; | ||
@@ -27,2 +28,3 @@ export interface CheckerOptions { | ||
connectOptions?: RoomConnectOptions; | ||
protocol?: 'udp' | 'tcp'; | ||
} | ||
@@ -37,4 +39,4 @@ declare const Checker_base: new () => TypedEmitter<CheckerCallbacks>; | ||
logs: Array<LogMessage>; | ||
errorsAsWarnings: boolean; | ||
name: string; | ||
options: CheckerOptions; | ||
constructor(url: string, token: string, options?: CheckerOptions); | ||
@@ -45,5 +47,6 @@ abstract get description(): string; | ||
protected isSuccess(): boolean; | ||
protected connect(): Promise<Room>; | ||
protected connect(url?: string): Promise<Room>; | ||
protected disconnect(): Promise<void>; | ||
protected skip(): void; | ||
protected switchProtocol(protocol: 'udp' | 'tcp' | 'tls'): Promise<void>; | ||
protected appendMessage(message: string): void; | ||
@@ -50,0 +53,0 @@ protected appendWarning(message: string): void; |
@@ -5,3 +5,4 @@ import { Checker } from './Checker'; | ||
perform(): Promise<void>; | ||
checkForVideo(track: MediaStreamTrack): Promise<void>; | ||
} | ||
//# sourceMappingURL=publishVideo.d.ts.map |
@@ -23,2 +23,4 @@ import type TypedEmitter from 'typed-emitter'; | ||
checkPublishVideo(): Promise<CheckInfo>; | ||
checkConnectionProtocol(): Promise<CheckInfo>; | ||
checkCloudRegion(): Promise<CheckInfo>; | ||
} | ||
@@ -25,0 +27,0 @@ type ConnectionCheckCallbacks = { |
@@ -46,4 +46,4 @@ import { Mutex } from '@livekit/mutex'; | ||
export * from './version'; | ||
export { ConnectionQuality, ConnectionState, CriticalTimers, DataPacket_Kind, DefaultReconnectPolicy, DisconnectReason, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, Participant, RemoteAudioTrack, RemoteParticipant, ParticipantKind, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, SubscriptionError, TrackPublication, compareVersions, createAudioAnalyser, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, isBrowserSupported, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, Mutex, isAudioTrack, isLocalTrack, isRemoteTrack, isVideoTrack, isLocalParticipant, isRemoteParticipant, }; | ||
export type { AudioAnalyserOptions, ElementInfo, LiveKitReactNativeInfo, ParticipantTrackPermission, AudioReceiverStats, AudioSenderStats, VideoReceiverStats, VideoSenderStats, ReconnectContext, ReconnectPolicy, TrackType, }; | ||
export { ConnectionQuality, ConnectionState, CriticalTimers, DataPacket_Kind, DefaultReconnectPolicy, DisconnectReason, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, Participant, RemoteAudioTrack, RemoteParticipant, ParticipantKind, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, SubscriptionError, TrackPublication, TrackType, compareVersions, createAudioAnalyser, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, isBrowserSupported, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, Mutex, isAudioTrack, isLocalTrack, isRemoteTrack, isVideoTrack, isLocalParticipant, isRemoteParticipant, }; | ||
export type { AudioAnalyserOptions, ElementInfo, LiveKitReactNativeInfo, ParticipantTrackPermission, AudioReceiverStats, AudioSenderStats, VideoReceiverStats, VideoSenderStats, ReconnectContext, ReconnectPolicy, }; | ||
//# sourceMappingURL=index.d.ts.map |
import type { DataStream_Chunk } from '@livekit/protocol'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamChunk, TextStreamInfo } from './types'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
declare abstract class BaseStreamReader<T extends BaseStreamInfo> { | ||
@@ -44,4 +44,4 @@ protected reader: ReadableStream<DataStream_Chunk>; | ||
[Symbol.asyncIterator](): { | ||
next: () => Promise<IteratorResult<TextStreamChunk>>; | ||
return(): Promise<IteratorResult<TextStreamChunk>>; | ||
next: () => Promise<IteratorResult<string>>; | ||
return(): Promise<IteratorResult<string>>; | ||
}; | ||
@@ -48,0 +48,0 @@ readAll(): Promise<string>; |
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
declare class BaseStreamWriter<T, InfoType extends BaseStreamInfo> { | ||
protected writableStream: WritableStream<[ | ||
T, | ||
number? | ||
]>; | ||
protected defaultWriter: WritableStreamDefaultWriter<[ | ||
T, | ||
number? | ||
]>; | ||
protected writableStream: WritableStream<T>; | ||
protected defaultWriter: WritableStreamDefaultWriter<T>; | ||
protected onClose?: () => void; | ||
readonly info: InfoType; | ||
constructor(writableStream: WritableStream<[ | ||
T, | ||
number? | ||
]>, info: InfoType, onClose?: () => void); | ||
constructor(writableStream: WritableStream<T>, info: InfoType, onClose?: () => void); | ||
write(chunk: T): Promise<void>; | ||
@@ -18,0 +9,0 @@ close(): Promise<void>; |
@@ -92,7 +92,2 @@ import type { DataStream_Chunk } from '@livekit/protocol'; | ||
} | ||
export type TextStreamChunk = { | ||
index: number; | ||
current: string; | ||
collected: string; | ||
}; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -118,2 +118,3 @@ import { ChatMessage as ChatMessageModel, ClientInfo, DisconnectReason, Transcription as TranscriptionModel } from '@livekit/protocol'; | ||
export declare function isRemoteParticipant(p: Participant): p is RemoteParticipant; | ||
export declare function splitUtf8(s: string, n: number): string[]; | ||
//# sourceMappingURL=utils.d.ts.map |
{ | ||
"name": "livekit-client", | ||
"version": "2.9.1", | ||
"version": "2.9.2", | ||
"description": "JavaScript/TypeScript client SDK for LiveKit", | ||
@@ -36,3 +36,3 @@ "main": "./dist/livekit-client.umd.js", | ||
"repository": "git@github.com:livekit/client-sdk-js.git", | ||
"author": "David Zhao <david@davidzhao.com>", | ||
"author": "LiveKit <hello@livekit.io>", | ||
"license": "Apache-2.0", | ||
@@ -43,8 +43,8 @@ "dependencies": { | ||
"events": "^3.3.0", | ||
"loglevel": "^1.8.0", | ||
"sdp-transform": "^2.14.1", | ||
"loglevel": "^1.9.2", | ||
"sdp-transform": "^2.15.0", | ||
"ts-debounce": "^4.0.0", | ||
"tslib": "2.8.1", | ||
"typed-emitter": "^2.1.0", | ||
"webrtc-adapter": "^9.0.0" | ||
"webrtc-adapter": "^9.0.1" | ||
}, | ||
@@ -54,3 +54,3 @@ "devDependencies": { | ||
"@babel/preset-env": "7.26.0", | ||
"@bufbuild/protoc-gen-es": "^1.3.0", | ||
"@bufbuild/protoc-gen-es": "^1.10.0", | ||
"@changesets/cli": "2.27.11", | ||
@@ -62,7 +62,7 @@ "@livekit/changesets-changelog-github": "^0.0.4", | ||
"@rollup/plugin-node-resolve": "16.0.0", | ||
"@rollup/plugin-terser": "^0.4.0", | ||
"@size-limit/file": "^8.2.4", | ||
"@size-limit/webpack": "^8.2.4", | ||
"@trivago/prettier-plugin-sort-imports": "^4.1.1", | ||
"@types/events": "^3.0.0", | ||
"@rollup/plugin-terser": "^0.4.4", | ||
"@size-limit/file": "^8.2.6", | ||
"@size-limit/webpack": "^8.2.6", | ||
"@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||
"@types/events": "^3.0.3", | ||
"@types/sdp-transform": "2.4.9", | ||
@@ -76,11 +76,11 @@ "@types/ua-parser-js": "0.7.39", | ||
"eslint-config-prettier": "9.1.0", | ||
"eslint-plugin-ecmascript-compat": "^3.0.0", | ||
"eslint-plugin-ecmascript-compat": "^3.2.1", | ||
"eslint-plugin-import": "2.31.0", | ||
"gh-pages": "6.3.0", | ||
"happy-dom": "^15.7.4", | ||
"prettier": "^3.0.0", | ||
"happy-dom": "^15.10.2", | ||
"prettier": "^3.4.2", | ||
"rollup": "4.29.1", | ||
"rollup-plugin-delete": "^2.0.0", | ||
"rollup-plugin-delete": "^2.1.0", | ||
"rollup-plugin-typescript2": "0.36.0", | ||
"size-limit": "^8.2.4", | ||
"size-limit": "^8.2.6", | ||
"typedoc": "0.27.6", | ||
@@ -90,3 +90,3 @@ "typedoc-plugin-no-inherit": "1.4.0", | ||
"vite": "5.4.12", | ||
"vitest": "^1.0.0" | ||
"vitest": "^1.6.0" | ||
}, | ||
@@ -93,0 +93,0 @@ "scripts": { |
@@ -6,2 +6,5 @@ import { EventEmitter } from 'events'; | ||
import Room, { ConnectionState } from '../../room/Room'; | ||
import { RoomEvent } from '../../room/events'; | ||
import type { SimulationScenario } from '../../room/types'; | ||
import { sleep } from '../../room/utils'; | ||
@@ -26,2 +29,3 @@ type LogMessage = { | ||
description: string; | ||
data?: any; | ||
}; | ||
@@ -33,2 +37,3 @@ | ||
connectOptions?: RoomConnectOptions; | ||
protocol?: 'udp' | 'tcp'; | ||
} | ||
@@ -49,6 +54,6 @@ | ||
errorsAsWarnings: boolean = false; | ||
name: string; | ||
options: CheckerOptions = {}; | ||
constructor(url: string, token: string, options: CheckerOptions = {}) { | ||
@@ -61,5 +66,3 @@ super(); | ||
this.connectOptions = options.connectOptions; | ||
if (options.errorsAsWarnings) { | ||
this.errorsAsWarnings = options.errorsAsWarnings; | ||
} | ||
this.options = options; | ||
} | ||
@@ -81,3 +84,3 @@ | ||
if (err instanceof Error) { | ||
if (this.errorsAsWarnings) { | ||
if (this.options.errorsAsWarnings) { | ||
this.appendWarning(err.message); | ||
@@ -110,7 +113,10 @@ } else { | ||
protected async connect(): Promise<Room> { | ||
protected async connect(url?: string): Promise<Room> { | ||
if (this.room.state === ConnectionState.Connected) { | ||
return this.room; | ||
} | ||
await this.room.connect(this.url, this.token, this.connectOptions); | ||
if (!url) { | ||
url = this.url; | ||
} | ||
await this.room.connect(url, this.token, this.connectOptions); | ||
return this.room; | ||
@@ -131,2 +137,29 @@ } | ||
protected async switchProtocol(protocol: 'udp' | 'tcp' | 'tls') { | ||
let hasReconnecting = false; | ||
let hasReconnected = false; | ||
this.room.on(RoomEvent.Reconnecting, () => { | ||
hasReconnecting = true; | ||
}); | ||
this.room.once(RoomEvent.Reconnected, () => { | ||
hasReconnected = true; | ||
}); | ||
this.room.simulateScenario(`force-${protocol}` as SimulationScenario); | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
if (!hasReconnecting) { | ||
// no need to wait for reconnection | ||
return; | ||
} | ||
// wait for 10 seconds for reconnection | ||
const timeout = Date.now() + 10000; | ||
while (Date.now() < timeout) { | ||
if (hasReconnected) { | ||
return; | ||
} | ||
await sleep(100); | ||
} | ||
throw new Error(`Could not reconnect using ${protocol} protocol after 10 seconds`); | ||
} | ||
protected appendMessage(message: string) { | ||
@@ -133,0 +166,0 @@ this.logs.push({ level: 'info', message }); |
import { createLocalAudioTrack } from '../../room/track/create'; | ||
import { detectSilence } from '../../room/track/utils'; | ||
import { Checker } from './Checker'; | ||
@@ -13,2 +14,9 @@ | ||
const track = await createLocalAudioTrack(); | ||
const trackIsSilent = await detectSilence(track, 1000); | ||
if (trackIsSilent) { | ||
throw new Error('unable to detect audio from microphone'); | ||
} | ||
this.appendMessage('detected audio from microphone'); | ||
room.localParticipant.publishTrack(track); | ||
@@ -15,0 +23,0 @@ // wait for a few seconds to publish |
@@ -13,2 +13,6 @@ import { createLocalVideoTrack } from '../../room/track/create'; | ||
const track = await createLocalVideoTrack(); | ||
// check if we have video from camera | ||
await this.checkForVideo(track.mediaStreamTrack); | ||
room.localParticipant.publishTrack(track); | ||
@@ -37,2 +41,50 @@ // wait for a few seconds to publish | ||
} | ||
async checkForVideo(track: MediaStreamTrack) { | ||
const stream = new MediaStream(); | ||
stream.addTrack(track.clone()); | ||
// Create video element to check frames | ||
const video = document.createElement('video'); | ||
video.srcObject = stream; | ||
video.muted = true; | ||
await new Promise<void>((resolve) => { | ||
video.onplay = () => { | ||
setTimeout(() => { | ||
const canvas = document.createElement('canvas'); | ||
const settings = track.getSettings(); | ||
const width = settings.width ?? video.videoWidth ?? 1280; | ||
const height = settings.height ?? video.videoHeight ?? 720; | ||
canvas.width = width; | ||
canvas.height = height; | ||
const ctx = canvas.getContext('2d')!; | ||
// Draw video frame to canvas | ||
ctx.drawImage(video, 0, 0); | ||
// Get image data and check if all pixels are black | ||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | ||
const data = imageData.data; | ||
let isAllBlack = true; | ||
for (let i = 0; i < data.length; i += 4) { | ||
if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) { | ||
isAllBlack = false; | ||
break; | ||
} | ||
} | ||
if (isAllBlack) { | ||
this.appendError('camera appears to be producing only black frames'); | ||
} else { | ||
this.appendMessage('received video frames'); | ||
} | ||
resolve(); | ||
}, 1000); | ||
}; | ||
video.play(); | ||
}); | ||
video.remove(); | ||
} | ||
} |
@@ -5,2 +5,4 @@ import { EventEmitter } from 'events'; | ||
import { CheckStatus, Checker } from './checks/Checker'; | ||
import { CloudRegionCheck } from './checks/cloudRegion'; | ||
import { ConnectionProtocolCheck, type ProtocolStats } from './checks/connectionProtocol'; | ||
import { PublishAudioCheck } from './checks/publishAudio'; | ||
@@ -90,2 +92,15 @@ import { PublishVideoCheck } from './checks/publishVideo'; | ||
} | ||
async checkConnectionProtocol() { | ||
const info = await this.createAndRunCheck(ConnectionProtocolCheck); | ||
if (info.data && 'protocol' in info.data) { | ||
const stats = info.data as ProtocolStats; | ||
this.options.protocol = stats.protocol; | ||
} | ||
return info; | ||
} | ||
async checkCloudRegion() { | ||
return this.createAndRunCheck(CloudRegionCheck); | ||
} | ||
} | ||
@@ -92,0 +107,0 @@ |
@@ -98,2 +98,3 @@ import { Mutex } from '@livekit/mutex'; | ||
TrackPublication, | ||
TrackType, | ||
compareVersions, | ||
@@ -131,3 +132,2 @@ createAudioAnalyser, | ||
ReconnectPolicy, | ||
TrackType, | ||
}; |
@@ -259,2 +259,6 @@ import log from '../../logger'; | ||
// disable simulcast for screenshare backup codec since L1Tx is used by primary codec | ||
if (track.source === Track.Source.ScreenShare && opts.simulcast) { | ||
opts.simulcast = false; | ||
} | ||
const encodings = computeVideoEncodings( | ||
@@ -261,0 +265,0 @@ track.source === Track.Source.ScreenShare, |
import type { DataStream_Chunk } from '@livekit/protocol'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamChunk, TextStreamInfo } from './types'; | ||
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
import { bigIntToNumber } from './utils'; | ||
@@ -127,3 +127,3 @@ | ||
return { | ||
next: async (): Promise<IteratorResult<TextStreamChunk>> => { | ||
next: async (): Promise<IteratorResult<string>> => { | ||
try { | ||
@@ -138,10 +138,3 @@ const { done, value } = await reader.read(); | ||
done: false, | ||
value: { | ||
index: bigIntToNumber(value.chunkIndex), | ||
current: decoder.decode(value.content), | ||
collected: Array.from(this.receivedChunks.values()) | ||
.sort((a, b) => bigIntToNumber(a.chunkIndex) - bigIntToNumber(b.chunkIndex)) | ||
.map((chunk) => decoder.decode(chunk.content)) | ||
.join(''), | ||
}, | ||
value: decoder.decode(value.content), | ||
}; | ||
@@ -155,3 +148,3 @@ } | ||
async return(): Promise<IteratorResult<TextStreamChunk>> { | ||
async return(): Promise<IteratorResult<string>> { | ||
reader.releaseLock(); | ||
@@ -164,7 +157,7 @@ return { done: true, value: undefined }; | ||
async readAll(): Promise<string> { | ||
let latestString: string = ''; | ||
for await (const { collected } of this) { | ||
latestString = collected; | ||
let finalString: string = ''; | ||
for await (const chunk of this) { | ||
finalString += chunk; | ||
} | ||
return latestString; | ||
return finalString; | ||
} | ||
@@ -171,0 +164,0 @@ } |
import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types'; | ||
class BaseStreamWriter<T, InfoType extends BaseStreamInfo> { | ||
protected writableStream: WritableStream<[T, number?]>; | ||
protected writableStream: WritableStream<T>; | ||
protected defaultWriter: WritableStreamDefaultWriter<[T, number?]>; | ||
protected defaultWriter: WritableStreamDefaultWriter<T>; | ||
@@ -12,3 +12,3 @@ protected onClose?: () => void; | ||
constructor(writableStream: WritableStream<[T, number?]>, info: InfoType, onClose?: () => void) { | ||
constructor(writableStream: WritableStream<T>, info: InfoType, onClose?: () => void) { | ||
this.writableStream = writableStream; | ||
@@ -21,3 +21,3 @@ this.defaultWriter = writableStream.getWriter(); | ||
write(chunk: T): Promise<void> { | ||
return this.defaultWriter.write([chunk]); | ||
return this.defaultWriter.write(chunk); | ||
} | ||
@@ -24,0 +24,0 @@ |
@@ -5,3 +5,3 @@ import DeviceManager from '../DeviceManager'; | ||
import { mediaTrackToLocalTrack } from '../participant/publishUtils'; | ||
import { isAudioTrack, isSafari17, isVideoTrack } from '../utils'; | ||
import { isAudioTrack, isSafari17, isVideoTrack, unwrapConstraint } from '../utils'; | ||
import LocalAudioTrack from './LocalAudioTrack'; | ||
@@ -72,6 +72,10 @@ import type LocalTrack from './LocalTrack'; | ||
// otherwise each track restart (e.g. mute - unmute) will try to initialize the device again -> causing additional permission prompts | ||
if (trackConstraints) { | ||
trackConstraints.deviceId = mediaStreamTrack.getSettings().deviceId; | ||
} else { | ||
trackConstraints = { deviceId: mediaStreamTrack.getSettings().deviceId }; | ||
const newDeviceId = mediaStreamTrack.getSettings().deviceId; | ||
if ( | ||
trackConstraints?.deviceId && | ||
unwrapConstraint(trackConstraints.deviceId) !== newDeviceId | ||
) { | ||
trackConstraints.deviceId = newDeviceId; | ||
} else if (!trackConstraints) { | ||
trackConstraints = { deviceId: newDeviceId }; | ||
} | ||
@@ -78,0 +82,0 @@ |
@@ -235,2 +235,3 @@ import { Mutex } from '@livekit/mutex'; | ||
} | ||
this._constraints.deviceId = deviceId; | ||
@@ -317,2 +318,3 @@ | ||
} | ||
const { deviceId, ...otherConstraints } = this._constraints; | ||
this.log.debug('restarting track with constraints', { ...this.logContext, constraints }); | ||
@@ -326,5 +328,5 @@ | ||
if (this.kind === Track.Kind.Video) { | ||
streamConstraints.video = constraints; | ||
streamConstraints.video = deviceId ? { deviceId } : true; | ||
} else { | ||
streamConstraints.audio = constraints; | ||
streamConstraints.audio = deviceId ? { deviceId } : true; | ||
} | ||
@@ -346,2 +348,3 @@ | ||
const newTrack = mediaStream.getTracks()[0]; | ||
await newTrack.applyConstraints(otherConstraints); | ||
newTrack.addEventListener('ended', this.handleEnded); | ||
@@ -348,0 +351,0 @@ this.log.debug('re-acquired MediaStreamTrack', this.logContext); |
@@ -122,7 +122,1 @@ import type { DataStream_Chunk } from '@livekit/protocol'; | ||
export interface TextStreamInfo extends BaseStreamInfo {} | ||
export type TextStreamChunk = { | ||
index: number; | ||
current: string; | ||
collected: string; | ||
}; |
@@ -628,1 +628,17 @@ import { | ||
} | ||
export function splitUtf8(s: string, n: number): string[] { | ||
// adapted from https://stackoverflow.com/a/6043797 | ||
const result: string[] = []; | ||
while (s.length > n) { | ||
let k = n; | ||
// Move back to find the start of a UTF-8 character | ||
while ((s.charCodeAt(k) & 0xc0) === 0x80) { | ||
k--; | ||
} | ||
result.push(s.slice(0, k)); | ||
s = s.slice(k); | ||
} | ||
result.push(s); | ||
return result; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
6292701
313
57011
Updatedloglevel@^1.9.2
Updatedsdp-transform@^2.15.0
Updatedwebrtc-adapter@^9.0.1