Skip to content

Commit

Permalink
feat(MeetingSdkAdapter): disable audio/video controls when no media
Browse files Browse the repository at this point in the history
  • Loading branch information
BenjaminGolya authored and cipak committed Jun 8, 2021
1 parent 07e51fb commit 863cd79
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 33 deletions.
2 changes: 1 addition & 1 deletion scripts/puppeteer.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function handleAudio() {
webexSDKAdapter.meetingsAdapter.meetingControls['mute-audio'].display(MEETING_ID).subscribe((data) => {
const muteAudio = document.getElementById('mute-audio');

muteAudio.innerHTML = `${data.tooltip} audio`;
muteAudio.innerHTML = data.tooltip;
});
}

Expand Down
82 changes: 60 additions & 22 deletions src/MeetingsSDKAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,14 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
const sdkMeeting = this.fetchMeeting(ID);

try {
const isInSession = this.meetings[ID].remoteAudio !== null;
let audioEnabled = this.meetings[ID].localAudio !== null;

if (audioEnabled) {
const isInSession = !!this.meetings[ID].remoteAudio;
const noAudio = !this.meetings[ID].disabledLocalAudio && !this.meetings[ID].localAudio;
const audioEnabled = !!this.meetings[ID].localAudio;
let state;

if (noAudio) {
state = MeetingControlState.DISABLED;
} else if (audioEnabled) {
// Mute the audio only if there is an active meeting
if (isInSession) {
await sdkMeeting.muteAudio();
Expand All @@ -512,7 +516,7 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
// Store the current local audio stream to avoid an extra request call
this.meetings[ID].disabledLocalAudio = this.meetings[ID].localAudio;
this.meetings[ID].localAudio = null;
audioEnabled = false;
state = MeetingControlState.INACTIVE;
} else {
// Unmute the audio only if there is an active meeting
if (isInSession) {
Expand All @@ -522,14 +526,14 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
// Retrieve the stored local audio stream
this.meetings[ID].localAudio = this.meetings[ID].disabledLocalAudio;
this.meetings[ID].disabledLocalAudio = null;
audioEnabled = true;
state = MeetingControlState.ACTIVE;
}

// Due to SDK limitation around local media updates,
// we need to emit a custom event for audio mute updates
sdkMeeting.emit(EVENT_MEDIA_LOCAL_UPDATE, {
control: AUDIO_CONTROL,
state: audioEnabled,
state,
});
} catch (error) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -560,10 +564,25 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
state: MeetingControlState.INACTIVE,
text: null,
};
const disabled = {
ID: AUDIO_CONTROL,
icon: 'microphone-muted_28',
tooltip: 'No microphone available',
state: MeetingControlState.DISABLED,
text: null,
};
const states = {
[MeetingControlState.ACTIVE]: unmuted,
[MeetingControlState.INACTIVE]: muted,
[MeetingControlState.DISABLED]: disabled,
};

const getDisplayData$ = Observable.create((observer) => {
const initialState$ = Observable.create((observer) => {
if (sdkMeeting) {
observer.next(unmuted);
const meeting = this.meetings[ID] || {};
const noAudio = !meeting.disabledLocalAudio && !meeting.localAudio;

observer.next(noAudio ? disabled : unmuted);
} else {
observer.error(new Error(`Could not find meeting with ID "${ID}" to add audio control`));
}
Expand All @@ -573,10 +592,10 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {

const localMediaUpdateEvent$ = fromEvent(sdkMeeting, EVENT_MEDIA_LOCAL_UPDATE).pipe(
filter((event) => event.control === AUDIO_CONTROL),
map(({state}) => (state ? unmuted : muted)),
map(({state}) => states[state]),
);

return concat(getDisplayData$, localMediaUpdateEvent$);
return concat(initialState$, localMediaUpdateEvent$);
}

/**
Expand All @@ -590,10 +609,14 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
const sdkMeeting = this.fetchMeeting(ID);

try {
const isInSession = this.meetings[ID].remoteVideo !== null;
let videoEnabled = this.meetings[ID].localVideo !== null;

if (videoEnabled) {
const isInSession = !!this.meetings[ID].remoteVideo;
const noVideo = !this.meetings[ID].disabledLocalVideo && !this.meetings[ID].localVideo;
const videoEnabled = !!this.meetings[ID].localVideo;
let state;

if (noVideo) {
state = MeetingControlState.DISABLED;
} else if (videoEnabled) {
// Mute the video only if there is an active meeting
if (isInSession) {
await sdkMeeting.muteVideo();
Expand All @@ -602,7 +625,7 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
// Store the current local video stream to avoid an extra request call
this.meetings[ID].disabledLocalVideo = this.meetings[ID].localVideo;
this.meetings[ID].localVideo = null;
videoEnabled = false;
state = MeetingControlState.INACTIVE;
} else {
// Unmute the video only if there is an active meeting
if (isInSession) {
Expand All @@ -612,14 +635,14 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
// Retrieve the stored local video stream
this.meetings[ID].localVideo = this.meetings[ID].disabledLocalVideo;
this.meetings[ID].disabledLocalVideo = null;
videoEnabled = true;
state = MeetingControlState.ACTIVE;
}

// Due to SDK limitation around local media updates,
// we need to emit a custom event for video mute updates
sdkMeeting.emit(EVENT_MEDIA_LOCAL_UPDATE, {
control: VIDEO_CONTROL,
state: videoEnabled,
state,
});
} catch (error) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -650,10 +673,25 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {
state: MeetingControlState.INACTIVE,
text: null,
};
const disabled = {
ID: VIDEO_CONTROL,
icon: 'camera-muted_28',
tooltip: 'No camera available',
state: MeetingControlState.DISABLED,
text: null,
};
const states = {
[MeetingControlState.ACTIVE]: unmuted,
[MeetingControlState.INACTIVE]: muted,
[MeetingControlState.DISABLED]: disabled,
};

const getDisplayData$ = Observable.create((observer) => {
const initialState$ = Observable.create((observer) => {
if (sdkMeeting) {
observer.next(unmuted);
const meeting = this.meetings[ID] || {};
const noVideo = !meeting.disabledLocalVideo && !meeting.localVideo;

observer.next(noVideo ? disabled : unmuted);
} else {
observer.error(new Error(`Could not find meeting with ID "${ID}" to add video control`));
}
Expand All @@ -663,10 +701,10 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter {

const localMediaUpdateEvent$ = fromEvent(sdkMeeting, EVENT_MEDIA_LOCAL_UPDATE).pipe(
filter((event) => event.control === VIDEO_CONTROL),
map(({state}) => (state ? unmuted : muted)),
map(({state}) => states[state]),
);

return concat(getDisplayData$, localMediaUpdateEvent$);
return concat(initialState$, localMediaUpdateEvent$);
}

/**
Expand Down
26 changes: 16 additions & 10 deletions src/MeetingsSDKAdapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,8 @@ describe('Meetings SDK Adapter', () => {
expect(dataDisplay).toMatchObject({
ID: 'mute-audio',
icon: 'microphone-muted_28',
tooltip: 'Mute',
state: 'inactive',
tooltip: 'No microphone available',
state: 'disabled',
text: null,
});
done();
Expand Down Expand Up @@ -492,15 +492,18 @@ describe('Meetings SDK Adapter', () => {

expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-audio',
state: false,
state: 'inactive',
});
});

test('unmutes audio if the the audio track is disabled', async () => {
meetingSDKAdapter.meetings[meetingID].localAudio = null;
await meetingSDKAdapter.handleLocalAudio(meetingID);

expect(mockSDKMeeting.unmuteAudio).toHaveBeenCalled();
expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-audio',
state: 'disabled',
});
});

test('localAudio property should be defined once the audio track is unmuted', async () => {
Expand All @@ -517,7 +520,7 @@ describe('Meetings SDK Adapter', () => {

expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-audio',
state: true,
state: 'disabled',
});
});

Expand All @@ -539,8 +542,8 @@ describe('Meetings SDK Adapter', () => {
expect(dataDisplay).toMatchObject({
ID: 'mute-video',
icon: 'camera-muted_28',
tooltip: 'Stop video',
state: 'inactive',
tooltip: 'No camera available',
state: 'disabled',
text: null,
});
done();
Expand Down Expand Up @@ -600,15 +603,18 @@ describe('Meetings SDK Adapter', () => {

expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-video',
state: false,
state: 'inactive',
});
});

test('unmutes video if the video track is disabled', async () => {
meetingSDKAdapter.meetings[meetingID].localVideo = null;
await meetingSDKAdapter.handleLocalVideo(meetingID);

expect(mockSDKMeeting.unmuteVideo).toHaveBeenCalled();
expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-video',
state: 'disabled',
});
});

test('localVideo property should be defined once the video track is unmuted', async () => {
Expand All @@ -625,7 +631,7 @@ describe('Meetings SDK Adapter', () => {

expect(mockSDKMeeting.emit).toHaveBeenCalledWith('adapter:media:local:update', {
control: 'mute-video',
state: true,
state: 'disabled',
});
});

Expand Down

0 comments on commit 863cd79

Please sign in to comment.