Skip to content

Commit

Permalink
Add support for capture options on set-enabled APIs (#251)
Browse files Browse the repository at this point in the history
* add support for respective capture options on set-enabled APIs

* add changeset

* fix audiocapture options

* start publishing multiple tracks simultaneously

* typo

* make change a minor version change

Co-authored-by: Théo Monnom <theo.monnom@outlook.com>
  • Loading branch information
lukasIO and theomonnom committed Jun 14, 2022
1 parent cdc7e5c commit 48a5e3b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-pears-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'livekit-client': minor
---

Add optional CaptureOptions for set-enabled methods
11 changes: 10 additions & 1 deletion example/sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
VideoCodec,
VideoQuality,
RemoteVideoTrack,
RemoteTrackPublication,
LogLevel,
} from '../src/index';

Expand Down Expand Up @@ -238,7 +239,7 @@ const appActions = {
const enabled = currentRoom.localParticipant.isScreenShareEnabled;
appendLog(`${enabled ? 'stopping' : 'starting'} screen share`);
setButtonDisabled('share-screen-button', true);
await currentRoom.localParticipant.setScreenShareEnabled(!enabled);
await currentRoom.localParticipant.setScreenShareEnabled(!enabled, { audio: true });
setButtonDisabled('share-screen-button', false);
updateButtonsForPublishState();
},
Expand Down Expand Up @@ -585,6 +586,7 @@ function renderScreenShare() {
let screenSharePub: TrackPublication | undefined = currentRoom.localParticipant.getTrack(
Track.Source.ScreenShare,
);
let screenShareAudioPub: RemoteTrackPublication | undefined;
if (!screenSharePub) {
currentRoom.participants.forEach((p) => {
if (screenSharePub) {
Expand All @@ -595,6 +597,10 @@ function renderScreenShare() {
if (pub?.isSubscribed) {
screenSharePub = pub;
}
const audioPub = p.getTrack(Track.Source.ScreenShareAudio);
if (audioPub?.isSubscribed) {
screenShareAudioPub = audioPub;
}
});
} else {
participant = currentRoom.localParticipant;
Expand All @@ -604,6 +610,9 @@ function renderScreenShare() {
div.style.display = 'block';
const videoElm = <HTMLVideoElement>$('screenshare-video');
screenSharePub.videoTrack?.attach(videoElm);
if (screenShareAudioPub) {
screenShareAudioPub.audioTrack?.attach(videoElm);
}
videoElm.onresize = () => {
updateVideoSize(videoElm, <HTMLSpanElement>$('screenshare-resolution'));
};
Expand Down
72 changes: 56 additions & 16 deletions src/room/participant/LocalParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import LocalVideoTrack, {
videoLayersFromEncodings,
} from '../track/LocalVideoTrack';
import {
AudioCaptureOptions,
CreateLocalTracksOptions,
ScreenShareCaptureOptions,
ScreenSharePresets,
TrackPublishOptions,
VideoCaptureOptions,
VideoCodec,
} from '../track/options';
import { Track } from '../track/Track';
Expand Down Expand Up @@ -117,8 +119,11 @@ export default class LocalParticipant extends Participant {
* If a track has already published, it'll mute or unmute the track.
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
*/
setCameraEnabled(enabled: boolean): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.Camera, enabled);
setCameraEnabled(
enabled: boolean,
options?: VideoCaptureOptions,
): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.Camera, enabled, options);
}

/**
Expand All @@ -127,16 +132,22 @@ export default class LocalParticipant extends Participant {
* If a track has already published, it'll mute or unmute the track.
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
*/
setMicrophoneEnabled(enabled: boolean): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.Microphone, enabled);
setMicrophoneEnabled(
enabled: boolean,
options?: AudioCaptureOptions,
): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.Microphone, enabled, options);
}

/**
* Start or stop sharing a participant's screen
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
*/
setScreenShareEnabled(enabled: boolean): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.ScreenShare, enabled);
setScreenShareEnabled(
enabled: boolean,
options?: ScreenShareCaptureOptions,
): Promise<LocalTrackPublication | undefined> {
return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options);
}

/** @internal */
Expand All @@ -155,16 +166,32 @@ export default class LocalParticipant extends Participant {
* Resolves with LocalTrackPublication if successful and void otherwise
*/
private async setTrackEnabled(
source: Track.Source,
source: Extract<Track.Source, Track.Source.Camera>,
enabled: boolean,
): Promise<LocalTrackPublication | undefined> {
options?: VideoCaptureOptions,
): Promise<LocalTrackPublication | undefined>;
private async setTrackEnabled(
source: Extract<Track.Source, Track.Source.Microphone>,
enabled: boolean,
options?: AudioCaptureOptions,
): Promise<LocalTrackPublication | undefined>;
private async setTrackEnabled(
source: Extract<Track.Source, Track.Source.ScreenShare>,
enabled: boolean,
options?: ScreenShareCaptureOptions,
): Promise<LocalTrackPublication | undefined>;
private async setTrackEnabled(
source: Track.Source,
enabled: true,
options?: VideoCaptureOptions | AudioCaptureOptions | ScreenShareCaptureOptions,
) {
log.debug('setTrackEnabled', { source, enabled });
let track = this.getTrack(source);
if (enabled) {
if (track) {
await track.unmute();
} else {
let localTrack: LocalTrack | undefined;
let localTracks: Array<LocalTrack> | undefined;
if (this.pendingPublishing.has(source)) {
log.info('skipping duplicate published source', { source });
// no-op it's already been requested
Expand All @@ -174,23 +201,32 @@ export default class LocalParticipant extends Participant {
try {
switch (source) {
case Track.Source.Camera:
[localTrack] = await this.createTracks({
video: true,
localTracks = await this.createTracks({
video: (options as VideoCaptureOptions | undefined) ?? true,
});

break;
case Track.Source.Microphone:
[localTrack] = await this.createTracks({
audio: true,
localTracks = await this.createTracks({
audio: (options as AudioCaptureOptions | undefined) ?? true,
});
break;
case Track.Source.ScreenShare:
[localTrack] = await this.createScreenTracks({ audio: false });
localTracks = await this.createScreenTracks({
...(options as ScreenShareCaptureOptions | undefined),
});
break;
default:
throw new TrackInvalidError(source);
}

track = await this.publishTrack(localTrack);
const publishPromises: Array<Promise<LocalTrackPublication>> = [];
for (const localTrack of localTracks) {
publishPromises.push(this.publishTrack(localTrack));
}
const publishedTracks = await Promise.all(publishPromises);
// for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
// revisit if we want to return an array of tracks instead for v2
[track] = publishedTracks;
} catch (e) {
if (e instanceof Error && !(e instanceof TrackInvalidError)) {
this.emit(ParticipantEvent.MediaDevicesError, e);
Expand All @@ -204,6 +240,10 @@ export default class LocalParticipant extends Participant {
// screenshare cannot be muted, unpublish instead
if (source === Track.Source.ScreenShare) {
track = this.unpublishTrack(track.track);
const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
if (screenAudioTrack && screenAudioTrack.track) {
this.unpublishTrack(screenAudioTrack.track);
}
} else {
await track.mute();
}
Expand Down

0 comments on commit 48a5e3b

Please sign in to comment.