livekit-client
Advanced tools
Comparing version 2.1.0 to 2.1.1
@@ -11,6 +11,6 @@ import { type AddTrackRequest, type ConnectionQualityUpdate, DataPacket, DataPacket_Kind, DisconnectReason, type JoinResponse, ParticipantInfo, Room as RoomModel, SpeakerInfo, type StreamStateUpdate, SubscribedQualityUpdate, type SubscriptionPermissionUpdate, type SubscriptionResponse, TrackInfo, TrackUnpublishedResponse, UserPacket } from '@livekit/protocol'; | ||
import type LocalTrackPublication from './track/LocalTrackPublication'; | ||
import type LocalVideoTrack from './track/LocalVideoTrack'; | ||
import LocalVideoTrack from './track/LocalVideoTrack'; | ||
import type { SimulcastTrackInfo } from './track/LocalVideoTrack'; | ||
import type RemoteTrackPublication from './track/RemoteTrackPublication'; | ||
import { Track } from './track/Track'; | ||
import type { Track } from './track/Track'; | ||
import type { TrackPublishOptions, VideoCodec } from './track/options'; | ||
@@ -94,3 +94,2 @@ declare const RTCEngine_base: new () => TypedEventEmitter<EngineEventCallbacks>; | ||
private handleBufferedAmountLow; | ||
private setPreferredCodec; | ||
createSender(track: LocalTrack, opts: TrackPublishOptions, encodings?: RTCRtpEncodingParameters[]): Promise<RTCRtpSender>; | ||
@@ -97,0 +96,0 @@ createSimulcastSender(track: LocalVideoTrack, simulcastTrack: SimulcastTrackInfo, opts: TrackPublishOptions, encodings?: RTCRtpEncodingParameters[]): Promise<RTCRtpSender | undefined>; |
@@ -18,3 +18,2 @@ import { ClientInfo } from '@livekit/protocol'; | ||
export declare function supportsSetSinkId(elm?: HTMLMediaElement): boolean; | ||
export declare function supportsSetCodecPreferences(transceiver: RTCRtpTransceiver): boolean; | ||
export declare function isBrowserSupported(): boolean; | ||
@@ -26,2 +25,3 @@ export declare function isFireFox(): boolean; | ||
export declare function isMobile(): boolean; | ||
export declare function isE2EESimulcastSupported(): boolean | undefined; | ||
export declare function isWeb(): boolean; | ||
@@ -28,0 +28,0 @@ export declare function isReactNative(): boolean; |
@@ -7,2 +7,3 @@ export type DetectableBrowser = 'Chrome' | 'Firefox' | 'Safari'; | ||
os?: DetectableOS; | ||
osVersion?: string; | ||
}; | ||
@@ -9,0 +10,0 @@ /** |
@@ -12,6 +12,6 @@ import type { AddTrackRequest, ConnectionQualityUpdate, JoinResponse, StreamStateUpdate, SubscriptionPermissionUpdate, SubscriptionResponse } from '@livekit/protocol'; | ||
import type LocalTrackPublication from './track/LocalTrackPublication'; | ||
import type LocalVideoTrack from './track/LocalVideoTrack'; | ||
import LocalVideoTrack from './track/LocalVideoTrack'; | ||
import type { SimulcastTrackInfo } from './track/LocalVideoTrack'; | ||
import type RemoteTrackPublication from './track/RemoteTrackPublication'; | ||
import { Track } from './track/Track'; | ||
import type { Track } from './track/Track'; | ||
import type { TrackPublishOptions, VideoCodec } from './track/options'; | ||
@@ -95,3 +95,2 @@ declare const RTCEngine_base: new () => TypedEventEmitter<EngineEventCallbacks>; | ||
private handleBufferedAmountLow; | ||
private setPreferredCodec; | ||
createSender(track: LocalTrack, opts: TrackPublishOptions, encodings?: RTCRtpEncodingParameters[]): Promise<RTCRtpSender>; | ||
@@ -98,0 +97,0 @@ createSimulcastSender(track: LocalVideoTrack, simulcastTrack: SimulcastTrackInfo, opts: TrackPublishOptions, encodings?: RTCRtpEncodingParameters[]): Promise<RTCRtpSender | undefined>; |
@@ -18,3 +18,2 @@ import { ClientInfo } from '@livekit/protocol'; | ||
export declare function supportsSetSinkId(elm?: HTMLMediaElement): boolean; | ||
export declare function supportsSetCodecPreferences(transceiver: RTCRtpTransceiver): boolean; | ||
export declare function isBrowserSupported(): boolean; | ||
@@ -26,2 +25,3 @@ export declare function isFireFox(): boolean; | ||
export declare function isMobile(): boolean; | ||
export declare function isE2EESimulcastSupported(): boolean | undefined; | ||
export declare function isWeb(): boolean; | ||
@@ -28,0 +28,0 @@ export declare function isReactNative(): boolean; |
@@ -7,2 +7,3 @@ export type DetectableBrowser = 'Chrome' | 'Firefox' | 'Safari'; | ||
os?: DetectableOS; | ||
osVersion?: string; | ||
}; | ||
@@ -9,0 +10,0 @@ /** |
{ | ||
"name": "livekit-client", | ||
"version": "2.1.0", | ||
"version": "2.1.1", | ||
"description": "JavaScript/TypeScript client SDK for LiveKit", | ||
@@ -5,0 +5,0 @@ "main": "./dist/livekit-client.umd.js", |
@@ -43,5 +43,5 @@ import { | ||
Future, | ||
isE2EESimulcastSupported, | ||
isFireFox, | ||
isSVCCodec, | ||
isSafari, | ||
isSafari17, | ||
@@ -625,6 +625,5 @@ isWeb, | ||
// disable simulcast if e2ee is set on safari | ||
if (isSafari() && this.roomOptions.e2ee) { | ||
if (!isE2EESimulcastSupported() && this.roomOptions.e2ee) { | ||
this.log.info( | ||
`End-to-end encryption is set up, simulcast publishing will be disabled on Safari`, | ||
`End-to-end encryption is set up, simulcast publishing will be disabled on Safari versions and iOS browsers running iOS < v17.2`, | ||
{ | ||
@@ -832,3 +831,2 @@ ...this.logContext, | ||
}); | ||
/* @ts-ignore */ | ||
opts.videoCodec = updatedCodec; | ||
@@ -835,0 +833,0 @@ |
@@ -477,4 +477,2 @@ import { EventEmitter } from 'events'; | ||
} catch (e) { | ||
// this error cannot always be caught. | ||
// If the local description has a setCodecPreferences error, this error will be uncaught | ||
let msg = 'unknown error'; | ||
@@ -481,0 +479,0 @@ if (e instanceof Error) { |
@@ -56,18 +56,10 @@ import { | ||
import type LocalTrackPublication from './track/LocalTrackPublication'; | ||
import type LocalVideoTrack from './track/LocalVideoTrack'; | ||
import LocalVideoTrack from './track/LocalVideoTrack'; | ||
import type { SimulcastTrackInfo } from './track/LocalVideoTrack'; | ||
import type RemoteTrackPublication from './track/RemoteTrackPublication'; | ||
import { Track } from './track/Track'; | ||
import type { Track } from './track/Track'; | ||
import type { TrackPublishOptions, VideoCodec } from './track/options'; | ||
import { getTrackPublicationInfo } from './track/utils'; | ||
import type { LoggerOptions } from './types'; | ||
import { | ||
Mutex, | ||
isVideoCodec, | ||
isWeb, | ||
sleep, | ||
supportsAddTrack, | ||
supportsSetCodecPreferences, | ||
supportsTransceiver, | ||
} from './utils'; | ||
import { Mutex, isVideoCodec, isWeb, sleep, supportsAddTrack, supportsTransceiver } from './utils'; | ||
@@ -675,47 +667,2 @@ const lossyDataChannel = '_lossy'; | ||
private setPreferredCodec( | ||
transceiver: RTCRtpTransceiver, | ||
kind: Track.Kind, | ||
videoCodec: VideoCodec, | ||
) { | ||
if (!('getCapabilities' in RTCRtpReceiver)) { | ||
return; | ||
} | ||
// when setting codec preferences, the capabilites need to be read from the RTCRtpReceiver | ||
const cap = RTCRtpReceiver.getCapabilities(kind); | ||
if (!cap) return; | ||
this.log.debug('get receiver capabilities', { ...this.logContext, cap }); | ||
const matched: RTCRtpCodecCapability[] = []; | ||
const partialMatched: RTCRtpCodecCapability[] = []; | ||
const unmatched: RTCRtpCodecCapability[] = []; | ||
cap.codecs.forEach((c) => { | ||
const codec = c.mimeType.toLowerCase(); | ||
if (codec === 'audio/opus') { | ||
matched.push(c); | ||
return; | ||
} | ||
const matchesVideoCodec = codec === `video/${videoCodec}`; | ||
if (!matchesVideoCodec) { | ||
unmatched.push(c); | ||
return; | ||
} | ||
// for h264 codecs that have sdpFmtpLine available, use only if the | ||
// profile-level-id is 42e01f for cross-browser compatibility | ||
if (videoCodec === 'h264') { | ||
if (c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=42e01f')) { | ||
matched.push(c); | ||
} else { | ||
partialMatched.push(c); | ||
} | ||
return; | ||
} | ||
matched.push(c); | ||
}); | ||
if (supportsSetCodecPreferences(transceiver)) { | ||
transceiver.setCodecPreferences(matched.concat(partialMatched, unmatched)); | ||
} | ||
} | ||
async createSender( | ||
@@ -771,2 +718,6 @@ track: LocalTrack, | ||
if (track instanceof LocalVideoTrack) { | ||
track.codec = opts.videoCodec; | ||
} | ||
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly', streams }; | ||
@@ -782,6 +733,2 @@ if (encodings) { | ||
if (track.kind === Track.Kind.Video && opts.videoCodec) { | ||
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec); | ||
track.codec = opts.videoCodec; | ||
} | ||
return transceiver.sender; | ||
@@ -811,3 +758,2 @@ } | ||
} | ||
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec); | ||
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender); | ||
@@ -814,0 +760,0 @@ return transceiver.sender; |
import { ClientInfo, ClientInfo_SDK } from '@livekit/protocol'; | ||
import type { DetectableBrowser } from '../utils/browserParser'; | ||
import { getBrowser } from '../utils/browserParser'; | ||
@@ -110,27 +109,2 @@ import { protocolVersion, version } from '../version'; | ||
const setCodecPreferencesVersions: Record<DetectableBrowser, string> = { | ||
Chrome: '100', | ||
Safari: '15', | ||
Firefox: '100', | ||
}; | ||
export function supportsSetCodecPreferences(transceiver: RTCRtpTransceiver): boolean { | ||
if (!isWeb()) { | ||
return false; | ||
} | ||
if (!('setCodecPreferences' in transceiver)) { | ||
return false; | ||
} | ||
const browser = getBrowser(); | ||
if (!browser?.name || !browser.version) { | ||
// version is required | ||
return false; | ||
} | ||
const v = setCodecPreferencesVersions[browser.name]; | ||
if (v) { | ||
return compareVersions(browser.version, v) >= 0; | ||
} | ||
return false; | ||
} | ||
export function isBrowserSupported() { | ||
@@ -162,5 +136,33 @@ if (typeof RTCPeerConnection === 'undefined') { | ||
if (!isWeb()) return false; | ||
return /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent); | ||
return ( | ||
// @ts-expect-error `userAgentData` is not yet part of typescript | ||
navigator.userAgentData?.mobile ?? | ||
/Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent) | ||
); | ||
} | ||
export function isE2EESimulcastSupported() { | ||
const browser = getBrowser(); | ||
const supportedSafariVersion = '17.2'; // see https://bugs.webkit.org/show_bug.cgi?id=257803 | ||
if (browser) { | ||
if (browser.name !== 'Safari' && browser.os !== 'iOS') { | ||
return true; | ||
} else if ( | ||
browser.os === 'iOS' && | ||
browser.osVersion && | ||
compareVersions(supportedSafariVersion, browser.osVersion) >= 0 | ||
) { | ||
return true; | ||
} else if ( | ||
browser.name === 'Safari' && | ||
compareVersions(supportedSafariVersion, browser.version) >= 0 | ||
) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
} | ||
export function isWeb(): boolean { | ||
@@ -167,0 +169,0 @@ return typeof document !== 'undefined'; |
@@ -32,2 +32,3 @@ import { describe, expect, it } from 'vitest'; | ||
expect(details?.os).toBe('macOS'); | ||
expect(details?.osVersion).toBe('10.15.7'); | ||
}); | ||
@@ -39,2 +40,3 @@ it('parses Safari iOS correctly', () => { | ||
expect(details?.os).toBe('iOS'); | ||
expect(details?.osVersion).toBe('16.5.1'); | ||
}); | ||
@@ -51,2 +53,3 @@ it('parses Firefox correctly', () => { | ||
expect(details?.os).toBe('iOS'); | ||
expect(details?.osVersion).toBe('13.4.1'); | ||
}); | ||
@@ -63,2 +66,3 @@ it('parses Chrome correctly', () => { | ||
expect(details?.os).toBe('iOS'); | ||
expect(details?.osVersion).toBe('16.5'); | ||
}); | ||
@@ -65,0 +69,0 @@ it('detects brave as chromium based', () => { |
@@ -13,2 +13,3 @@ // tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js | ||
os?: DetectableOS; | ||
osVersion?: string; | ||
}; | ||
@@ -21,3 +22,3 @@ | ||
*/ | ||
export function getBrowser(userAgent?: string, force = true) { | ||
export function getBrowser(userAgent?: string, force = true): BrowserDetails | undefined { | ||
if (typeof userAgent === 'undefined' && typeof navigator === 'undefined') { | ||
@@ -42,2 +43,3 @@ return; | ||
os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined, | ||
osVersion: getOSVersion(ua), | ||
}; | ||
@@ -54,2 +56,3 @@ return browser; | ||
os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined, | ||
osVersion: getOSVersion(ua), | ||
}; | ||
@@ -68,2 +71,3 @@ | ||
os: ua.includes('mobile/') ? 'iOS' : 'macOS', | ||
osVersion: getOSVersion(ua), | ||
}; | ||
@@ -80,1 +84,7 @@ | ||
} | ||
function getOSVersion(ua: string) { | ||
return ua.includes('mac os') | ||
? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') | ||
: undefined; | ||
} |
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
5421614
48443