Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Jitsi by updating device mute updates over postMessage API #27858

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 35 additions & 26 deletions src/vector/jitsi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import type {
JitsiMeetExternalAPIConstructor,
ExternalAPIEventCallbacks,
JitsiMeetExternalAPI as _JitsiMeetExternalAPI,
AudioMuteStatusChangedEvent,
LogEvent,
VideoMuteStatusChangedEvent,
ExternalAPIOptions as _ExternalAPIOptions,
Expand Down Expand Up @@ -103,6 +102,14 @@ let widgetApi: WidgetApi | undefined;
let meetApi: _JitsiMeetExternalAPI | undefined;
let skipOurWelcomeScreen = false;

async function checkAudioVideoEnabled(): Promise<[audioEnabled: boolean, videoEnabled: boolean]> {
if (!meetApi) return [false, false];
const [audioEnabled, videoEnabled] = (await Promise.all([meetApi.isAudioMuted(), meetApi.isVideoMuted()])).map(
(muted) => !muted,
);
return [audioEnabled, videoEnabled];
}

const setupCompleted = (async (): Promise<string | void> => {
try {
// Queue a config.json lookup asap, so we can use it later on. We want this to be concurrent with
Expand Down Expand Up @@ -159,16 +166,15 @@ const setupCompleted = (async (): Promise<string | void> => {

const handleAction = (
action: WidgetApiAction,
handler: (request: IWidgetApiRequestData) => Promise<void>,
handler: (request: IWidgetApiRequestData) => Promise<IWidgetApiResponseData | void>,
): void => {
widgetApi!.on(`action:${action}`, async (ev: CustomEvent<IWidgetApiRequest>) => {
ev.preventDefault();
await setupCompleted;

let response: IWidgetApiResponseData;
try {
await handler(ev.detail.data);
response = {};
response = (await handler(ev.detail.data)) ?? {};
} catch (e) {
if (e instanceof Error) {
response = { error: { message: e.message } };
Expand All @@ -194,25 +200,26 @@ const setupCompleted = (async (): Promise<string | void> => {
meetApi?.executeCommand("hangup");
}
});
handleAction(ElementWidgetActions.MuteAudio, async () => {
if (meetApi && !(await meetApi.isAudioMuted())) {
meetApi.executeCommand("toggleAudio");
handleAction(ElementWidgetActions.DeviceMute, async (params) => {
if (!meetApi) return;

const [audioEnabled, videoEnabled] = await checkAudioVideoEnabled();

if (Object.keys(params).length === 0) {
// Handle query
return {
audio_enabled: audioEnabled,
video_enabled: videoEnabled,
};
}
});
handleAction(ElementWidgetActions.UnmuteAudio, async () => {
if (meetApi && (await meetApi.isAudioMuted())) {

if (params.audio_enabled !== audioEnabled) {
meetApi.executeCommand("toggleAudio");
}
});
handleAction(ElementWidgetActions.MuteVideo, async () => {
if (meetApi && !(await meetApi.isVideoMuted())) {
meetApi.executeCommand("toggleVideo");
}
});
handleAction(ElementWidgetActions.UnmuteVideo, async () => {
if (meetApi && (await meetApi.isVideoMuted())) {
if (params.video_enabled !== videoEnabled) {
meetApi.executeCommand("toggleVideo");
}
return params;
});
handleAction(ElementWidgetActions.TileLayout, async () => {
meetApi?.executeCommand("setTileView", true);
Expand Down Expand Up @@ -473,7 +480,7 @@ async function joinConference(audioInput?: string | null, videoInput?: string |
meetApi.on("videoConferenceLeft", onVideoConferenceLeft);
meetApi.on("readyToClose", closeConference as ExternalAPIEventCallbacks["readyToClose"]);
meetApi.on("errorOccurred", onErrorOccurred);
meetApi.on("audioMuteStatusChanged", onAudioMuteStatusChanged);
meetApi.on("audioMuteStatusChanged", onMuteStatusChanged);
meetApi.on("videoMuteStatusChanged", onVideoMuteStatusChanged);

(["videoConferenceJoined", "participantJoined", "participantLeft"] as const).forEach((event) => {
Expand Down Expand Up @@ -523,9 +530,13 @@ const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorO
}
};

const onAudioMuteStatusChanged = ({ muted }: AudioMuteStatusChangedEvent): void => {
const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio;
void widgetApi?.transport.send(action, {});
const onMuteStatusChanged = async (): Promise<void> => {
if (!meetApi) return;
const [audioEnabled, videoEnabled] = await checkAudioVideoEnabled();
void widgetApi?.transport.send(ElementWidgetActions.DeviceMute, {
audio_enabled: audioEnabled,
video_enabled: videoEnabled,
});
};

const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void => {
Expand All @@ -534,11 +545,9 @@ const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void
// hanging up, which we need to ignore by padding the timeout here,
// otherwise the React SDK will mistakenly think the user turned off
// their video by hand
setTimeout(() => {
if (meetApi) void widgetApi?.transport.send(ElementWidgetActions.MuteVideo, {});
}, 200);
setTimeout(() => onMuteStatusChanged, 200);
} else {
void widgetApi?.transport.send(ElementWidgetActions.UnmuteVideo, {});
void onMuteStatusChanged();
}
};

Expand Down
Loading