diff --git a/docs/labs.md b/docs/labs.md index 06a6efd0d1..3fe3711507 100644 --- a/docs/labs.md +++ b/docs/labs.md @@ -196,3 +196,7 @@ Threading allows users to branch out a new conversation from the main timeline o Threads can be access by clicking their summary below the root event on the room timeline. Users can find a comprehensive list of threads by click the icon on the room header button. This feature might work in degraded mode if the homeserver a user is connected to does not advertise support for the unstable feature `org.matrix.msc3440` when calling the `/versions` API endpoint. + +## Voice & video rooms (`feature_voice_rooms`) [In Development] + +Enables support for creating and joining voice & video rooms, which are persistent voice chats that users can jump in and out of. diff --git a/src/vector/jitsi/index.ts b/src/vector/jitsi/index.ts index c6783d5a6b..d62b76f5ac 100644 --- a/src/vector/jitsi/index.ts +++ b/src/vector/jitsi/index.ts @@ -56,6 +56,8 @@ let widgetApi: WidgetApi; let meetApi: any; // JitsiMeetExternalAPI let skipOurWelcomeScreen = false; +const ack = (ev: CustomEvent) => widgetApi.transport.reply(ev.detail, {}); + (async function() { try { // Queue a config.json lookup asap, so we can use it later on. We want this to be concurrent with @@ -133,7 +135,6 @@ let skipOurWelcomeScreen = false; if (widgetApi) { await readyPromise; - await widgetApi.setAlwaysOnScreen(false); // start off as detachable from the screen // See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) { @@ -142,12 +143,48 @@ let skipOurWelcomeScreen = false; logger.log("Got OpenID Connect token"); } - // TODO: register widgetApi listeners for PTT controls (https://github.com/vector-im/element-web/issues/12795) - + widgetApi.on(`action:${ElementWidgetActions.JoinCall}`, + (ev: CustomEvent) => { + joinConference(); + ack(ev); + }, + ); widgetApi.on(`action:${ElementWidgetActions.HangupCall}`, (ev: CustomEvent) => { - if (meetApi) meetApi.executeCommand('hangup'); - widgetApi.transport.reply(ev.detail, {}); // ack + meetApi?.executeCommand('hangup'); + ack(ev); + }, + ); + widgetApi.on(`action:${ElementWidgetActions.MuteAudio}`, + async (ev: CustomEvent) => { + ack(ev); + if (meetApi && !await meetApi.isAudioMuted()) { + meetApi.executeCommand('toggleAudio'); + } + }, + ); + widgetApi.on(`action:${ElementWidgetActions.UnmuteAudio}`, + async (ev: CustomEvent) => { + ack(ev); + if (meetApi && await meetApi.isAudioMuted()) { + meetApi.executeCommand('toggleAudio'); + } + }, + ); + widgetApi.on(`action:${ElementWidgetActions.MuteVideo}`, + async (ev: CustomEvent) => { + ack(ev); + if (meetApi && !await meetApi.isVideoMuted()) { + meetApi.executeCommand('toggleVideo'); + } + }, + ); + widgetApi.on(`action:${ElementWidgetActions.UnmuteVideo}`, + async (ev: CustomEvent) => { + ack(ev); + if (meetApi && await meetApi.isVideoMuted()) { + meetApi.executeCommand('toggleVideo'); + } }, ); widgetApi.on(`action:${ElementWidgetActions.StartLiveStream}`, @@ -160,7 +197,7 @@ let skipOurWelcomeScreen = false; //rtmpStreamKey: ev.detail.data.rtmpStreamKey, youtubeStreamKey: ev.detail.data.rtmpStreamKey, }); - widgetApi.transport.reply(ev.detail, {}); // ack + ack(ev); } else { widgetApi.transport.reply(ev.detail, { error: { message: "Conference not joined" } }); } @@ -293,6 +330,7 @@ function joinConference() { // event handler bound in HTML // ignored promise because we don't care if it works // noinspection JSIgnoredPromiseFromCall widgetApi.setAlwaysOnScreen(true); + widgetApi.transport.send(ElementWidgetActions.JoinCall, {}); } }); @@ -300,9 +338,13 @@ function joinConference() { // event handler bound in HTML switchVisibleContainers(); if (widgetApi) { + // We send the hangup event before setAlwaysOnScreen, because the latter + // can cause the receiving side to instantly stop listening. // ignored promise because we don't care if it works // noinspection JSIgnoredPromiseFromCall - widgetApi.setAlwaysOnScreen(false); + widgetApi.transport.send(ElementWidgetActions.HangupCall, {}).then(() => + widgetApi.setAlwaysOnScreen(false), + ); } document.getElementById("jitsiContainer").innerHTML = ""; @@ -312,4 +354,22 @@ function joinConference() { // event handler bound in HTML skipToJitsiSplashScreen(); } }); + + meetApi.on("audioMuteStatusChanged", ({ muted }) => { + const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio; + widgetApi.transport.send(action, {}); + }); + + meetApi.on("videoMuteStatusChanged", ({ muted }) => { + const action = muted ? ElementWidgetActions.MuteVideo : ElementWidgetActions.UnmuteVideo; + widgetApi.transport.send(action, {}); + }); + + ["videoConferenceJoined", "participantJoined", "participantLeft"].forEach(event => { + meetApi.on(event, () => { + widgetApi?.transport.send(ElementWidgetActions.CallParticipants, { + participants: meetApi.getParticipantsInfo(), + }); + }); + }); }