From 48a5e3ba605f25530dc1194bb714fab1d7bfa490 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 14 Jun 2022 09:14:07 +0200 Subject: [PATCH] Add support for capture options on set-enabled APIs (#251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .changeset/honest-pears-retire.md | 5 ++ example/sample.ts | 11 +++- src/room/participant/LocalParticipant.ts | 72 ++++++++++++++++++------ 3 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 .changeset/honest-pears-retire.md diff --git a/.changeset/honest-pears-retire.md b/.changeset/honest-pears-retire.md new file mode 100644 index 000000000..82068d0b5 --- /dev/null +++ b/.changeset/honest-pears-retire.md @@ -0,0 +1,5 @@ +--- +'livekit-client': minor +--- + +Add optional CaptureOptions for set-enabled methods diff --git a/example/sample.ts b/example/sample.ts index d3d645be5..be89f83e9 100644 --- a/example/sample.ts +++ b/example/sample.ts @@ -19,6 +19,7 @@ import { VideoCodec, VideoQuality, RemoteVideoTrack, + RemoteTrackPublication, LogLevel, } from '../src/index'; @@ -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(); }, @@ -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) { @@ -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; @@ -604,6 +610,9 @@ function renderScreenShare() { div.style.display = 'block'; const videoElm = $('screenshare-video'); screenSharePub.videoTrack?.attach(videoElm); + if (screenShareAudioPub) { + screenShareAudioPub.audioTrack?.attach(videoElm); + } videoElm.onresize = () => { updateVideoSize(videoElm, $('screenshare-resolution')); }; diff --git a/src/room/participant/LocalParticipant.ts b/src/room/participant/LocalParticipant.ts index 621675357..ee56f22e3 100644 --- a/src/room/participant/LocalParticipant.ts +++ b/src/room/participant/LocalParticipant.ts @@ -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'; @@ -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 { - return this.setTrackEnabled(Track.Source.Camera, enabled); + setCameraEnabled( + enabled: boolean, + options?: VideoCaptureOptions, + ): Promise { + return this.setTrackEnabled(Track.Source.Camera, enabled, options); } /** @@ -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 { - return this.setTrackEnabled(Track.Source.Microphone, enabled); + setMicrophoneEnabled( + enabled: boolean, + options?: AudioCaptureOptions, + ): Promise { + 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 { - return this.setTrackEnabled(Track.Source.ScreenShare, enabled); + setScreenShareEnabled( + enabled: boolean, + options?: ScreenShareCaptureOptions, + ): Promise { + return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options); } /** @internal */ @@ -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, enabled: boolean, - ): Promise { + options?: VideoCaptureOptions, + ): Promise; + private async setTrackEnabled( + source: Extract, + enabled: boolean, + options?: AudioCaptureOptions, + ): Promise; + private async setTrackEnabled( + source: Extract, + enabled: boolean, + options?: ScreenShareCaptureOptions, + ): Promise; + 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 | undefined; if (this.pendingPublishing.has(source)) { log.info('skipping duplicate published source', { source }); // no-op it's already been requested @@ -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> = []; + 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); @@ -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(); }