livekit-client 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/SignalClient.d.ts +11 -3
- package/dist/api/SignalClient.js +92 -28
- package/dist/api/SignalClient.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +5 -0
- package/dist/proto/livekit_models.d.ts +48 -0
- package/dist/proto/livekit_models.js +367 -5
- package/dist/proto/livekit_models.js.map +1 -1
- package/dist/proto/livekit_rtc.d.ts +50 -11
- package/dist/proto/livekit_rtc.js +300 -22
- package/dist/proto/livekit_rtc.js.map +1 -1
- package/dist/room/PCTransport.js +4 -0
- package/dist/room/PCTransport.js.map +1 -1
- package/dist/room/RTCEngine.d.ts +10 -2
- package/dist/room/RTCEngine.js +182 -42
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.d.ts +15 -0
- package/dist/room/Room.js +165 -20
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +42 -20
- package/dist/room/events.js +41 -19
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts +25 -4
- package/dist/room/participant/LocalParticipant.js +50 -23
- package/dist/room/participant/LocalParticipant.js.map +1 -1
- package/dist/room/participant/Participant.d.ts +3 -1
- package/dist/room/participant/Participant.js +1 -0
- package/dist/room/participant/Participant.js.map +1 -1
- package/dist/room/participant/ParticipantTrackPermission.d.ts +19 -0
- package/dist/room/participant/ParticipantTrackPermission.js +16 -0
- package/dist/room/participant/ParticipantTrackPermission.js.map +1 -0
- package/dist/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/room/participant/RemoteParticipant.js +11 -16
- package/dist/room/participant/RemoteParticipant.js.map +1 -1
- package/dist/room/participant/publishUtils.js +1 -1
- package/dist/room/participant/publishUtils.js.map +1 -1
- package/dist/room/participant/publishUtils.test.js +9 -0
- package/dist/room/participant/publishUtils.test.js.map +1 -1
- package/dist/room/track/LocalTrack.d.ts +0 -3
- package/dist/room/track/LocalTrack.js +1 -6
- package/dist/room/track/LocalTrack.js.map +1 -1
- package/dist/room/track/LocalTrackPublication.d.ts +5 -1
- package/dist/room/track/LocalTrackPublication.js +15 -5
- package/dist/room/track/LocalTrackPublication.js.map +1 -1
- package/dist/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/room/track/LocalVideoTrack.js +7 -6
- package/dist/room/track/LocalVideoTrack.js.map +1 -1
- package/dist/room/track/RemoteAudioTrack.d.ts +5 -14
- package/dist/room/track/RemoteAudioTrack.js +7 -32
- package/dist/room/track/RemoteAudioTrack.js.map +1 -1
- package/dist/room/track/RemoteTrack.d.ts +14 -0
- package/dist/room/track/RemoteTrack.js +47 -0
- package/dist/room/track/RemoteTrack.js.map +1 -0
- package/dist/room/track/RemoteTrackPublication.d.ts +10 -2
- package/dist/room/track/RemoteTrackPublication.js +51 -12
- package/dist/room/track/RemoteTrackPublication.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +3 -9
- package/dist/room/track/RemoteVideoTrack.js +8 -29
- package/dist/room/track/RemoteVideoTrack.js.map +1 -1
- package/dist/room/track/Track.d.ts +3 -0
- package/dist/room/track/Track.js +14 -5
- package/dist/room/track/Track.js.map +1 -1
- package/dist/room/track/TrackPublication.d.ts +14 -1
- package/dist/room/track/TrackPublication.js +24 -7
- package/dist/room/track/TrackPublication.js.map +1 -1
- package/dist/room/track/create.js +5 -0
- package/dist/room/track/create.js.map +1 -1
- package/dist/room/utils.d.ts +2 -0
- package/dist/room/utils.js +32 -1
- package/dist/room/utils.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +5 -3
- package/src/api/SignalClient.ts +444 -0
- package/src/connect.ts +100 -0
- package/src/index.ts +47 -0
- package/src/logger.ts +22 -0
- package/src/options.ts +152 -0
- package/src/proto/livekit_models.ts +1863 -0
- package/src/proto/livekit_rtc.ts +3415 -0
- package/src/room/DeviceManager.ts +57 -0
- package/src/room/PCTransport.ts +86 -0
- package/src/room/RTCEngine.ts +582 -0
- package/src/room/Room.ts +840 -0
- package/src/room/errors.ts +65 -0
- package/src/room/events.ts +398 -0
- package/src/room/participant/LocalParticipant.ts +685 -0
- package/src/room/participant/Participant.ts +214 -0
- package/src/room/participant/ParticipantTrackPermission.ts +32 -0
- package/src/room/participant/RemoteParticipant.ts +241 -0
- package/src/room/participant/publishUtils.test.ts +105 -0
- package/src/room/participant/publishUtils.ts +180 -0
- package/src/room/stats.ts +130 -0
- package/src/room/track/LocalAudioTrack.ts +112 -0
- package/src/room/track/LocalTrack.ts +124 -0
- package/src/room/track/LocalTrackPublication.ts +66 -0
- package/src/room/track/LocalVideoTrack.test.ts +70 -0
- package/src/room/track/LocalVideoTrack.ts +416 -0
- package/src/room/track/RemoteAudioTrack.ts +58 -0
- package/src/room/track/RemoteTrack.ts +59 -0
- package/src/room/track/RemoteTrackPublication.ts +198 -0
- package/src/room/track/RemoteVideoTrack.ts +213 -0
- package/src/room/track/Track.ts +307 -0
- package/src/room/track/TrackPublication.ts +120 -0
- package/src/room/track/create.ts +120 -0
- package/src/room/track/defaults.ts +23 -0
- package/src/room/track/options.ts +229 -0
- package/src/room/track/types.ts +8 -0
- package/src/room/track/utils.test.ts +93 -0
- package/src/room/track/utils.ts +76 -0
- package/src/room/utils.ts +74 -0
- package/src/version.ts +2 -0
- package/.github/workflows/publish.yaml +0 -55
- package/.github/workflows/test.yaml +0 -36
- package/example/index.html +0 -248
- package/example/sample.ts +0 -621
- package/example/styles.css +0 -144
- package/example/webpack.config.js +0 -33
@@ -0,0 +1,120 @@
|
|
1
|
+
import { EventEmitter } from 'events';
|
2
|
+
import { TrackInfo } from '../../proto/livekit_models';
|
3
|
+
import { TrackEvent } from '../events';
|
4
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
5
|
+
import LocalVideoTrack from './LocalVideoTrack';
|
6
|
+
import RemoteAudioTrack from './RemoteAudioTrack';
|
7
|
+
import RemoteVideoTrack from './RemoteVideoTrack';
|
8
|
+
import { Track } from './Track';
|
9
|
+
|
10
|
+
export class TrackPublication extends EventEmitter {
|
11
|
+
kind: Track.Kind;
|
12
|
+
|
13
|
+
trackName: string;
|
14
|
+
|
15
|
+
trackSid: Track.SID;
|
16
|
+
|
17
|
+
track?: Track;
|
18
|
+
|
19
|
+
source: Track.Source;
|
20
|
+
|
21
|
+
/** MimeType of the published track */
|
22
|
+
mimeType?: string;
|
23
|
+
|
24
|
+
/** dimension of the original published stream, video-only */
|
25
|
+
dimensions?: Track.Dimensions;
|
26
|
+
|
27
|
+
/** true if track was simulcasted to server, video-only */
|
28
|
+
simulcasted?: boolean;
|
29
|
+
|
30
|
+
/** @internal */
|
31
|
+
trackInfo?: TrackInfo;
|
32
|
+
|
33
|
+
protected metadataMuted: boolean = false;
|
34
|
+
|
35
|
+
constructor(kind: Track.Kind, id: string, name: string) {
|
36
|
+
super();
|
37
|
+
this.kind = kind;
|
38
|
+
this.trackSid = id;
|
39
|
+
this.trackName = name;
|
40
|
+
this.source = Track.Source.Unknown;
|
41
|
+
}
|
42
|
+
|
43
|
+
/** @internal */
|
44
|
+
setTrack(track?: Track) {
|
45
|
+
if (this.track) {
|
46
|
+
this.track.off(TrackEvent.Muted, this.handleMuted);
|
47
|
+
this.track.off(TrackEvent.Unmuted, this.handleUnmuted);
|
48
|
+
}
|
49
|
+
|
50
|
+
this.track = track;
|
51
|
+
|
52
|
+
if (track) {
|
53
|
+
// forward events
|
54
|
+
track.on(TrackEvent.Muted, this.handleMuted);
|
55
|
+
track.on(TrackEvent.Unmuted, this.handleUnmuted);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
get isMuted(): boolean {
|
60
|
+
return this.metadataMuted;
|
61
|
+
}
|
62
|
+
|
63
|
+
get isEnabled(): boolean {
|
64
|
+
return true;
|
65
|
+
}
|
66
|
+
|
67
|
+
get isSubscribed(): boolean {
|
68
|
+
return this.track !== undefined;
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* an [AudioTrack] if this publication holds an audio track
|
73
|
+
*/
|
74
|
+
get audioTrack(): LocalAudioTrack | RemoteAudioTrack | undefined {
|
75
|
+
if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
|
76
|
+
return this.track;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* an [VideoTrack] if this publication holds a video track
|
82
|
+
*/
|
83
|
+
get videoTrack(): LocalVideoTrack | RemoteVideoTrack | undefined {
|
84
|
+
if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
|
85
|
+
return this.track;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
handleMuted = () => {
|
90
|
+
this.emit(TrackEvent.Muted);
|
91
|
+
};
|
92
|
+
|
93
|
+
handleUnmuted = () => {
|
94
|
+
this.emit(TrackEvent.Unmuted);
|
95
|
+
};
|
96
|
+
|
97
|
+
/** @internal */
|
98
|
+
updateInfo(info: TrackInfo) {
|
99
|
+
this.trackSid = info.sid;
|
100
|
+
this.trackName = info.name;
|
101
|
+
this.source = Track.sourceFromProto(info.source);
|
102
|
+
this.mimeType = info.mimeType;
|
103
|
+
if (this.kind === Track.Kind.Video && info.width > 0) {
|
104
|
+
this.dimensions = {
|
105
|
+
width: info.width,
|
106
|
+
height: info.height,
|
107
|
+
};
|
108
|
+
this.simulcasted = info.simulcast;
|
109
|
+
}
|
110
|
+
this.trackInfo = info;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
export namespace TrackPublication {
|
115
|
+
export enum SubscriptionStatus {
|
116
|
+
Subscribed = 'subscribed',
|
117
|
+
NotAllowed = 'not_allowed',
|
118
|
+
Unsubscribed = 'unsubscribed',
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import { TrackInvalidError } from '../errors';
|
2
|
+
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
|
3
|
+
import { audioDefaults, videoDefaults } from './defaults';
|
4
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
5
|
+
import LocalTrack from './LocalTrack';
|
6
|
+
import LocalVideoTrack from './LocalVideoTrack';
|
7
|
+
import {
|
8
|
+
AudioCaptureOptions, CreateLocalTracksOptions, ScreenShareCaptureOptions,
|
9
|
+
VideoCaptureOptions, VideoPresets,
|
10
|
+
} from './options';
|
11
|
+
import { Track } from './Track';
|
12
|
+
import { constraintsForOptions, mergeDefaultOptions } from './utils';
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Creates a local video and audio track at the same time. When acquiring both
|
16
|
+
* audio and video tracks together, it'll display a single permission prompt to
|
17
|
+
* the user instead of two separate ones.
|
18
|
+
* @param options
|
19
|
+
*/
|
20
|
+
export async function createLocalTracks(
|
21
|
+
options?: CreateLocalTracksOptions,
|
22
|
+
): Promise<Array<LocalTrack>> {
|
23
|
+
// set default options to true
|
24
|
+
options ??= {};
|
25
|
+
options.audio ??= true;
|
26
|
+
options.video ??= true;
|
27
|
+
|
28
|
+
const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
|
29
|
+
const constraints = constraintsForOptions(opts);
|
30
|
+
const stream = await navigator.mediaDevices.getUserMedia(
|
31
|
+
constraints,
|
32
|
+
);
|
33
|
+
return stream.getTracks().map((mediaStreamTrack) => {
|
34
|
+
const isAudio = mediaStreamTrack.kind === 'audio';
|
35
|
+
let trackOptions = isAudio ? options!.audio : options!.video;
|
36
|
+
if (typeof trackOptions === 'boolean' || !trackOptions) {
|
37
|
+
trackOptions = {};
|
38
|
+
}
|
39
|
+
let trackConstraints: MediaTrackConstraints | undefined;
|
40
|
+
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
41
|
+
if (typeof conOrBool !== 'boolean') {
|
42
|
+
trackConstraints = conOrBool;
|
43
|
+
}
|
44
|
+
const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints);
|
45
|
+
if (track.kind === Track.Kind.Video) {
|
46
|
+
track.source = Track.Source.Camera;
|
47
|
+
} else if (track.kind === Track.Kind.Audio) {
|
48
|
+
track.source = Track.Source.Microphone;
|
49
|
+
}
|
50
|
+
return track;
|
51
|
+
});
|
52
|
+
}
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Creates a [[LocalVideoTrack]] with getUserMedia()
|
56
|
+
* @param options
|
57
|
+
*/
|
58
|
+
export async function createLocalVideoTrack(
|
59
|
+
options?: VideoCaptureOptions,
|
60
|
+
): Promise<LocalVideoTrack> {
|
61
|
+
const tracks = await createLocalTracks({
|
62
|
+
audio: false,
|
63
|
+
video: options,
|
64
|
+
});
|
65
|
+
return <LocalVideoTrack>tracks[0];
|
66
|
+
}
|
67
|
+
|
68
|
+
export async function createLocalAudioTrack(
|
69
|
+
options?: AudioCaptureOptions,
|
70
|
+
): Promise<LocalAudioTrack> {
|
71
|
+
const tracks = await createLocalTracks({
|
72
|
+
audio: options,
|
73
|
+
video: false,
|
74
|
+
});
|
75
|
+
return <LocalAudioTrack>tracks[0];
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Creates a screen capture tracks with getDisplayMedia().
|
80
|
+
* A LocalVideoTrack is always created and returned.
|
81
|
+
* If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
|
82
|
+
*/
|
83
|
+
export async function createLocalScreenTracks(
|
84
|
+
options?: ScreenShareCaptureOptions,
|
85
|
+
): Promise<Array<LocalTrack>> {
|
86
|
+
if (options === undefined) {
|
87
|
+
options = {};
|
88
|
+
}
|
89
|
+
if (options.resolution === undefined) {
|
90
|
+
options.resolution = VideoPresets.fhd.resolution;
|
91
|
+
}
|
92
|
+
|
93
|
+
let videoConstraints: MediaTrackConstraints | boolean = true;
|
94
|
+
if (options.resolution) {
|
95
|
+
videoConstraints = {
|
96
|
+
width: options.resolution.width,
|
97
|
+
height: options.resolution.height,
|
98
|
+
};
|
99
|
+
}
|
100
|
+
// typescript definition is missing getDisplayMedia: https://github.com/microsoft/TypeScript/issues/33232
|
101
|
+
// @ts-ignore
|
102
|
+
const stream: MediaStream = await navigator.mediaDevices.getDisplayMedia({
|
103
|
+
audio: options.audio ?? false,
|
104
|
+
video: videoConstraints,
|
105
|
+
});
|
106
|
+
|
107
|
+
const tracks = stream.getVideoTracks();
|
108
|
+
if (tracks.length === 0) {
|
109
|
+
throw new TrackInvalidError('no video track found');
|
110
|
+
}
|
111
|
+
const screenVideo = new LocalVideoTrack(tracks[0]);
|
112
|
+
screenVideo.source = Track.Source.ScreenShare;
|
113
|
+
const localTracks: Array<LocalTrack> = [screenVideo];
|
114
|
+
if (stream.getAudioTracks().length > 0) {
|
115
|
+
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0]);
|
116
|
+
screenAudio.source = Track.Source.ScreenShareAudio;
|
117
|
+
localTracks.push(screenAudio);
|
118
|
+
}
|
119
|
+
return localTracks;
|
120
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import {
|
2
|
+
AudioCaptureOptions, AudioPresets, ScreenSharePresets,
|
3
|
+
TrackPublishDefaults, VideoCaptureOptions, VideoPresets,
|
4
|
+
} from './options';
|
5
|
+
|
6
|
+
export const publishDefaults: TrackPublishDefaults = {
|
7
|
+
audioBitrate: AudioPresets.speech.maxBitrate,
|
8
|
+
dtx: true,
|
9
|
+
simulcast: true,
|
10
|
+
screenShareEncoding: ScreenSharePresets.hd_15.encoding,
|
11
|
+
stopMicTrackOnMute: false,
|
12
|
+
};
|
13
|
+
|
14
|
+
export const audioDefaults: AudioCaptureOptions = {
|
15
|
+
autoGainControl: true,
|
16
|
+
channelCount: 1,
|
17
|
+
echoCancellation: true,
|
18
|
+
noiseSuppression: true,
|
19
|
+
};
|
20
|
+
|
21
|
+
export const videoDefaults: VideoCaptureOptions = {
|
22
|
+
resolution: VideoPresets.qhd.resolution,
|
23
|
+
};
|
@@ -0,0 +1,229 @@
|
|
1
|
+
import { Track } from './Track';
|
2
|
+
|
3
|
+
export interface TrackPublishDefaults {
|
4
|
+
/**
|
5
|
+
* encoding parameters for camera track
|
6
|
+
*/
|
7
|
+
videoEncoding?: VideoEncoding;
|
8
|
+
|
9
|
+
/**
|
10
|
+
* encoding parameters for screen share track
|
11
|
+
*/
|
12
|
+
screenShareEncoding?: VideoEncoding;
|
13
|
+
|
14
|
+
/**
|
15
|
+
* codec, defaults to vp8
|
16
|
+
*/
|
17
|
+
videoCodec?: VideoCodec;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* max audio bitrate, defaults to [[AudioPresets.speech]]
|
21
|
+
*/
|
22
|
+
audioBitrate?: number;
|
23
|
+
|
24
|
+
/**
|
25
|
+
* dtx (Discontinuous Transmission of audio), defaults to true
|
26
|
+
*/
|
27
|
+
dtx?: boolean;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* use simulcast, defaults to true.
|
31
|
+
* When using simulcast, LiveKit will publish up to three versions of the stream
|
32
|
+
* at various resolutions.
|
33
|
+
*/
|
34
|
+
simulcast?: boolean;
|
35
|
+
|
36
|
+
/**
|
37
|
+
* For local tracks, stop the underlying MediaStreamTrack when the track is muted (or paused)
|
38
|
+
* on some platforms, this option is necessary to disable the microphone recording indicator.
|
39
|
+
* Note: when this is enabled, and BT devices are connected, they will transition between
|
40
|
+
* profiles (e.g. HFP to A2DP) and there will be an audible difference in playback.
|
41
|
+
*
|
42
|
+
* defaults to false
|
43
|
+
*/
|
44
|
+
stopMicTrackOnMute?: boolean;
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Options when publishing tracks
|
49
|
+
*/
|
50
|
+
export interface TrackPublishOptions extends TrackPublishDefaults {
|
51
|
+
/**
|
52
|
+
* set a track name
|
53
|
+
*/
|
54
|
+
name?: string;
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Source of track, camera, microphone, or screen
|
58
|
+
*/
|
59
|
+
source?: Track.Source;
|
60
|
+
}
|
61
|
+
|
62
|
+
export interface CreateLocalTracksOptions {
|
63
|
+
/**
|
64
|
+
* audio track options, true to create with defaults. false if audio shouldn't be created
|
65
|
+
* default true
|
66
|
+
*/
|
67
|
+
audio?: boolean | AudioCaptureOptions;
|
68
|
+
|
69
|
+
/**
|
70
|
+
* video track options, true to create with defaults. false if video shouldn't be created
|
71
|
+
* default true
|
72
|
+
*/
|
73
|
+
video?: boolean | VideoCaptureOptions;
|
74
|
+
}
|
75
|
+
|
76
|
+
export interface VideoCaptureOptions {
|
77
|
+
/**
|
78
|
+
* A ConstrainDOMString object specifying a device ID or an array of device
|
79
|
+
* IDs which are acceptable and/or required.
|
80
|
+
*/
|
81
|
+
deviceId?: ConstrainDOMString;
|
82
|
+
|
83
|
+
/**
|
84
|
+
* a facing or an array of facings which are acceptable and/or required.
|
85
|
+
*/
|
86
|
+
facingMode?: 'user' | 'environment' | 'left' | 'right';
|
87
|
+
|
88
|
+
resolution?: VideoResolution;
|
89
|
+
}
|
90
|
+
|
91
|
+
export interface ScreenShareCaptureOptions {
|
92
|
+
/**
|
93
|
+
* true to capture audio shared. browser support for audio capturing in
|
94
|
+
* screenshare is limited: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility
|
95
|
+
*/
|
96
|
+
audio?: boolean;
|
97
|
+
|
98
|
+
/** capture resolution, defaults to full HD */
|
99
|
+
resolution?: VideoResolution;
|
100
|
+
}
|
101
|
+
|
102
|
+
export interface AudioCaptureOptions {
|
103
|
+
/**
|
104
|
+
* specifies whether automatic gain control is preferred and/or required
|
105
|
+
*/
|
106
|
+
autoGainControl?: ConstrainBoolean;
|
107
|
+
|
108
|
+
/**
|
109
|
+
* the channel count or range of channel counts which are acceptable and/or required
|
110
|
+
*/
|
111
|
+
channelCount?: ConstrainULong;
|
112
|
+
|
113
|
+
/**
|
114
|
+
* A ConstrainDOMString object specifying a device ID or an array of device
|
115
|
+
* IDs which are acceptable and/or required.
|
116
|
+
*/
|
117
|
+
deviceId?: ConstrainDOMString;
|
118
|
+
|
119
|
+
/**
|
120
|
+
* whether or not echo cancellation is preferred and/or required
|
121
|
+
*/
|
122
|
+
echoCancellation?: ConstrainBoolean;
|
123
|
+
|
124
|
+
/**
|
125
|
+
* the latency or range of latencies which are acceptable and/or required.
|
126
|
+
*/
|
127
|
+
latency?: ConstrainDouble;
|
128
|
+
|
129
|
+
/**
|
130
|
+
* whether noise suppression is preferred and/or required.
|
131
|
+
*/
|
132
|
+
noiseSuppression?: ConstrainBoolean;
|
133
|
+
|
134
|
+
/**
|
135
|
+
* the sample rate or range of sample rates which are acceptable and/or required.
|
136
|
+
*/
|
137
|
+
sampleRate?: ConstrainULong;
|
138
|
+
|
139
|
+
/**
|
140
|
+
* sample size or range of sample sizes which are acceptable and/or required.
|
141
|
+
*/
|
142
|
+
sampleSize?: ConstrainULong;
|
143
|
+
}
|
144
|
+
|
145
|
+
export interface VideoResolution {
|
146
|
+
width: number;
|
147
|
+
height: number;
|
148
|
+
frameRate?: number;
|
149
|
+
aspectRatio?: number;
|
150
|
+
}
|
151
|
+
|
152
|
+
export interface VideoEncoding {
|
153
|
+
maxBitrate: number;
|
154
|
+
maxFramerate?: number;
|
155
|
+
}
|
156
|
+
|
157
|
+
export class VideoPreset {
|
158
|
+
encoding: VideoEncoding;
|
159
|
+
|
160
|
+
width: number;
|
161
|
+
|
162
|
+
height: number;
|
163
|
+
|
164
|
+
constructor(width: number, height: number, maxBitrate: number, maxFramerate?: number) {
|
165
|
+
this.width = width;
|
166
|
+
this.height = height;
|
167
|
+
this.encoding = {
|
168
|
+
maxBitrate,
|
169
|
+
maxFramerate,
|
170
|
+
};
|
171
|
+
}
|
172
|
+
|
173
|
+
get resolution(): VideoResolution {
|
174
|
+
return {
|
175
|
+
width: this.width,
|
176
|
+
height: this.height,
|
177
|
+
frameRate: this.encoding.maxFramerate,
|
178
|
+
aspectRatio: this.width / this.height,
|
179
|
+
};
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
export interface AudioPreset {
|
184
|
+
maxBitrate: number;
|
185
|
+
}
|
186
|
+
|
187
|
+
export type VideoCodec = 'vp8' | 'h264';
|
188
|
+
|
189
|
+
export namespace AudioPresets {
|
190
|
+
export const telephone: AudioPreset = {
|
191
|
+
maxBitrate: 12_000,
|
192
|
+
};
|
193
|
+
export const speech: AudioPreset = {
|
194
|
+
maxBitrate: 20_000,
|
195
|
+
};
|
196
|
+
export const music: AudioPreset = {
|
197
|
+
maxBitrate: 32_000,
|
198
|
+
};
|
199
|
+
}
|
200
|
+
|
201
|
+
/**
|
202
|
+
* Sane presets for video resolution/encoding
|
203
|
+
*/
|
204
|
+
export const VideoPresets = {
|
205
|
+
qvga: new VideoPreset(320, 180, 120_000, 10),
|
206
|
+
vga: new VideoPreset(640, 360, 300_000, 20),
|
207
|
+
qhd: new VideoPreset(960, 540, 600_000, 25),
|
208
|
+
hd: new VideoPreset(1280, 720, 2_000_000, 30),
|
209
|
+
fhd: new VideoPreset(1920, 1080, 3_000_000, 30),
|
210
|
+
};
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Four by three presets
|
214
|
+
*/
|
215
|
+
export const VideoPresets43 = {
|
216
|
+
qvga: new VideoPreset(240, 180, 90_000, 10),
|
217
|
+
vga: new VideoPreset(480, 360, 225_000, 20),
|
218
|
+
qhd: new VideoPreset(720, 540, 450_000, 25),
|
219
|
+
hd: new VideoPreset(960, 720, 1_500_000, 30),
|
220
|
+
fhd: new VideoPreset(1440, 1080, 2_800_000, 30),
|
221
|
+
};
|
222
|
+
|
223
|
+
export const ScreenSharePresets = {
|
224
|
+
vga: new VideoPreset(640, 360, 200_000, 3),
|
225
|
+
hd_8: new VideoPreset(1280, 720, 400_000, 5),
|
226
|
+
hd_15: new VideoPreset(1280, 720, 1_000_000, 15),
|
227
|
+
fhd_15: new VideoPreset(1920, 1080, 1_500_000, 15),
|
228
|
+
fhd_30: new VideoPreset(1920, 1080, 3_000_000, 30),
|
229
|
+
};
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
2
|
+
import LocalVideoTrack from './LocalVideoTrack';
|
3
|
+
import RemoteAudioTrack from './RemoteAudioTrack';
|
4
|
+
import RemoteVideoTrack from './RemoteVideoTrack';
|
5
|
+
|
6
|
+
export type RemoteTrack = RemoteAudioTrack | RemoteVideoTrack;
|
7
|
+
export type AudioTrack = RemoteAudioTrack | LocalAudioTrack;
|
8
|
+
export type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import {
|
2
|
+
AudioCaptureOptions, VideoCaptureOptions, VideoPresets,
|
3
|
+
} from './options';
|
4
|
+
import { constraintsForOptions, mergeDefaultOptions } from './utils';
|
5
|
+
|
6
|
+
describe('mergeDefaultOptions', () => {
|
7
|
+
const audioDefaults: AudioCaptureOptions = {
|
8
|
+
autoGainControl: true,
|
9
|
+
channelCount: 2,
|
10
|
+
};
|
11
|
+
const videoDefaults: VideoCaptureOptions = {
|
12
|
+
deviceId: 'video123',
|
13
|
+
resolution: VideoPresets.fhd.resolution,
|
14
|
+
};
|
15
|
+
|
16
|
+
it('does not enable undefined options', () => {
|
17
|
+
const opts = mergeDefaultOptions(undefined, audioDefaults, videoDefaults);
|
18
|
+
expect(opts.audio).toEqual(undefined);
|
19
|
+
expect(opts.video).toEqual(undefined);
|
20
|
+
});
|
21
|
+
|
22
|
+
it('does not enable explicitly disabled', () => {
|
23
|
+
const opts = mergeDefaultOptions({
|
24
|
+
video: false,
|
25
|
+
});
|
26
|
+
expect(opts.audio).toEqual(undefined);
|
27
|
+
expect(opts.video).toEqual(false);
|
28
|
+
});
|
29
|
+
|
30
|
+
it('accepts true for options', () => {
|
31
|
+
const opts = mergeDefaultOptions({
|
32
|
+
audio: true,
|
33
|
+
}, audioDefaults, videoDefaults);
|
34
|
+
expect(opts.audio).toEqual(audioDefaults);
|
35
|
+
expect(opts.video).toEqual(undefined);
|
36
|
+
});
|
37
|
+
|
38
|
+
it('enables overriding specific fields', () => {
|
39
|
+
const opts = mergeDefaultOptions({
|
40
|
+
audio: { channelCount: 1 },
|
41
|
+
}, audioDefaults, videoDefaults);
|
42
|
+
const audioOpts = opts.audio as AudioCaptureOptions;
|
43
|
+
expect(audioOpts.channelCount).toEqual(1);
|
44
|
+
expect(audioOpts.autoGainControl).toEqual(true);
|
45
|
+
});
|
46
|
+
|
47
|
+
it('does not override explicit false', () => {
|
48
|
+
const opts = mergeDefaultOptions({
|
49
|
+
audio: { autoGainControl: false },
|
50
|
+
}, audioDefaults, videoDefaults);
|
51
|
+
const audioOpts = opts.audio as AudioCaptureOptions;
|
52
|
+
expect(audioOpts.autoGainControl).toEqual(false);
|
53
|
+
});
|
54
|
+
});
|
55
|
+
|
56
|
+
describe('constraintsForOptions', () => {
|
57
|
+
it('correctly enables audio bool', () => {
|
58
|
+
const constraints = constraintsForOptions({
|
59
|
+
audio: true,
|
60
|
+
});
|
61
|
+
expect(constraints.audio).toEqual(true);
|
62
|
+
expect(constraints.video).toEqual(false);
|
63
|
+
});
|
64
|
+
|
65
|
+
it('converts audio options correctly', () => {
|
66
|
+
const constraints = constraintsForOptions({
|
67
|
+
audio: {
|
68
|
+
noiseSuppression: true,
|
69
|
+
echoCancellation: false,
|
70
|
+
},
|
71
|
+
});
|
72
|
+
const audioOpts = constraints.audio as MediaTrackConstraints;
|
73
|
+
expect(Object.keys(audioOpts)).toEqual(['noiseSuppression', 'echoCancellation']);
|
74
|
+
expect(audioOpts.noiseSuppression).toEqual(true);
|
75
|
+
expect(audioOpts.echoCancellation).toEqual(false);
|
76
|
+
});
|
77
|
+
|
78
|
+
it('converts video options correctly', () => {
|
79
|
+
const constraints = constraintsForOptions({
|
80
|
+
video: {
|
81
|
+
resolution: VideoPresets.hd.resolution,
|
82
|
+
facingMode: 'user',
|
83
|
+
deviceId: 'video123',
|
84
|
+
},
|
85
|
+
});
|
86
|
+
const videoOpts = constraints.video as MediaTrackConstraints;
|
87
|
+
expect(Object.keys(videoOpts)).toEqual(['width', 'height', 'frameRate', 'aspectRatio', 'facingMode', 'deviceId']);
|
88
|
+
expect(videoOpts.width).toEqual(VideoPresets.hd.resolution.width);
|
89
|
+
expect(videoOpts.height).toEqual(VideoPresets.hd.resolution.height);
|
90
|
+
expect(videoOpts.frameRate).toEqual(VideoPresets.hd.resolution.frameRate);
|
91
|
+
expect(videoOpts.aspectRatio).toEqual(VideoPresets.hd.resolution.aspectRatio);
|
92
|
+
});
|
93
|
+
});
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import {
|
2
|
+
AudioCaptureOptions, CreateLocalTracksOptions,
|
3
|
+
VideoCaptureOptions,
|
4
|
+
} from './options';
|
5
|
+
|
6
|
+
export function mergeDefaultOptions(
|
7
|
+
options?: CreateLocalTracksOptions,
|
8
|
+
audioDefaults?: AudioCaptureOptions,
|
9
|
+
videoDefaults?: VideoCaptureOptions,
|
10
|
+
): CreateLocalTracksOptions {
|
11
|
+
const opts: CreateLocalTracksOptions = {
|
12
|
+
...options,
|
13
|
+
};
|
14
|
+
if (opts.audio === true) opts.audio = {};
|
15
|
+
if (opts.video === true) opts.video = {};
|
16
|
+
|
17
|
+
// use defaults
|
18
|
+
if (opts.audio) {
|
19
|
+
mergeObjectWithoutOverwriting(opts.audio as Record<string, unknown>,
|
20
|
+
audioDefaults as Record<string, unknown>);
|
21
|
+
}
|
22
|
+
if (opts.video) {
|
23
|
+
mergeObjectWithoutOverwriting(opts.video as Record<string, unknown>,
|
24
|
+
videoDefaults as Record<string, unknown>);
|
25
|
+
}
|
26
|
+
return opts;
|
27
|
+
}
|
28
|
+
|
29
|
+
function mergeObjectWithoutOverwriting(
|
30
|
+
mainObject: Record<string, unknown>,
|
31
|
+
objectToMerge: Record<string, unknown>,
|
32
|
+
): Record<string, unknown> {
|
33
|
+
Object.keys(objectToMerge).forEach((key) => {
|
34
|
+
if (mainObject[key] === undefined) mainObject[key] = objectToMerge[key];
|
35
|
+
});
|
36
|
+
return mainObject;
|
37
|
+
}
|
38
|
+
|
39
|
+
export function constraintsForOptions(options: CreateLocalTracksOptions): MediaStreamConstraints {
|
40
|
+
const constraints: MediaStreamConstraints = {};
|
41
|
+
|
42
|
+
if (options.video) {
|
43
|
+
// default video options
|
44
|
+
if (typeof options.video === 'object') {
|
45
|
+
const videoOptions: MediaTrackConstraints = {};
|
46
|
+
const target = videoOptions as Record<string, unknown>;
|
47
|
+
const source = options.video as Record<string, unknown>;
|
48
|
+
Object.keys(source).forEach((key) => {
|
49
|
+
switch (key) {
|
50
|
+
case 'resolution':
|
51
|
+
// flatten VideoResolution fields
|
52
|
+
mergeObjectWithoutOverwriting(target, source.resolution as Record<string, unknown>);
|
53
|
+
break;
|
54
|
+
default:
|
55
|
+
target[key] = source[key];
|
56
|
+
}
|
57
|
+
});
|
58
|
+
constraints.video = videoOptions;
|
59
|
+
} else {
|
60
|
+
constraints.video = options.video;
|
61
|
+
}
|
62
|
+
} else {
|
63
|
+
constraints.video = false;
|
64
|
+
}
|
65
|
+
|
66
|
+
if (options.audio) {
|
67
|
+
if (typeof options.audio === 'object') {
|
68
|
+
constraints.audio = options.audio;
|
69
|
+
} else {
|
70
|
+
constraints.audio = true;
|
71
|
+
}
|
72
|
+
} else {
|
73
|
+
constraints.audio = false;
|
74
|
+
}
|
75
|
+
return constraints;
|
76
|
+
}
|