diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -429,6 +429,22 @@
break;
+ case 'language':
+ // Only the moderator can do it
+ if (!$this->isModerator($connection->room)) {
+ return $this->errorResponse(403);
+ }
+
+ if ($value) {
+ if (preg_match('/^[a-z]{2}$/', $value)) {
+ $connection->metadata = ['language' => $value] + $connection->metadata;
+ }
+ } else {
+ $connection->metadata = array_diff_key($connection->metadata, ['language' => 0]);
+ }
+
+ break;
+
case 'role':
// Only the moderator can do it
if (!$this->isModerator($connection->room)) {
@@ -452,6 +468,11 @@
$connection->metadata = array_diff_key($connection->metadata, ['hand' => 0]);
}
+ // Non-publisher cannot be a language interpreter
+ if (!($value & Room::ROLE_PUBLISHER)) {
+ $connection->metadata = array_diff_key($connection->metadata, ['language' => 0]);
+ }
+
$connection->{$key} = $value;
break;
}
diff --git a/src/app/Observers/OpenVidu/ConnectionObserver.php b/src/app/Observers/OpenVidu/ConnectionObserver.php
--- a/src/app/Observers/OpenVidu/ConnectionObserver.php
+++ b/src/app/Observers/OpenVidu/ConnectionObserver.php
@@ -26,12 +26,19 @@
// participant browser to do this.
}
- // Rised hand state change
- $newState = $connection->metadata['hand'] ?? null;
- $oldState = $this->getOriginal($connection, 'metadata')['hand'] ?? null;
+ // Detect metadata changes for specified properties
+ $keys = [
+ 'hand' => 'bool',
+ 'language' => '',
+ ];
- if ($newState !== $oldState) {
- $params['hand'] = !empty($newState);
+ foreach ($keys as $key => $type) {
+ $newState = $connection->metadata[$key] ?? null;
+ $oldState = $this->getOriginal($connection, 'metadata')[$key] ?? null;
+
+ if ($newState !== $oldState) {
+ $params[$key] = $type == 'bool' ? !empty($newState) : $newState;
+ }
}
// Send the signal to all participants
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -194,6 +194,7 @@
return [
'role' => $item->role,
'hand' => $item->metadata['hand'] ?? 0,
+ 'language' => $item->metadata['language'] ?? null,
];
})
// Sort by order in the queue, so UI can re-build the existing queue in order
diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -62,6 +62,7 @@
this.setupSetAudioDevice = setupSetAudioDevice
this.setupSetVideoDevice = setupSetVideoDevice
this.switchAudio = switchAudio
+ this.switchChannel = switchChannel
this.switchScreen = switchScreen
this.switchVideo = switchVideo
this.updateSession = updateSession
@@ -91,6 +92,8 @@
* nickname - Participant name,
* role - connection (participant) role(s),
* connections - Optional metadata for other users connections (current state),
+ * channel - Selected interpreted language channel (two-letter language code)
+ * languages - Supported languages (code-to-label map)
* chatElement - DOM element for the chat widget,
* menuElement - DOM element of the room toolbar,
* queueElement - DOM element for the Q&A queue (users with a raised hand)
@@ -117,6 +120,9 @@
subscribersContainer = $('
').appendTo(container).get(0)
}
+ // TODO: Make sure all supported callbacks exist, so we don't have to check
+ // their existence everywhere anymore
+
sessionData = data
// Init a session
@@ -201,6 +207,8 @@
if (session.connection.connectionId == connectionId) {
metadata = sessionData
+ metadata.audioActive = audioActive
+ metadata.videoActive = videoActive
}
if (metadata) {
@@ -259,11 +267,19 @@
// Here we expect connections in a proper queue order
Object.keys(data.connections || {}).forEach(key => {
let conn = data.connections[key]
+
if (conn.hand) {
conn.connectionId = key
connectionHandUp(conn)
}
})
+
+ sessionData.channels = getChannels(data.connections)
+
+ // Inform the vue component, so it can update some UI controls
+ if (sessionData.channels.length && sessionData.onSessionDataUpdate) {
+ sessionData.onSessionDataUpdate(sessionData)
+ }
})
.catch(error => {
console.error('There was an error connecting to the session: ', error.message);
@@ -709,6 +725,18 @@
}
}
+ /**
+ * Switch interpreted language channel
+ *
+ * @param channel Two-letter language code
+ */
+ function switchChannel(channel) {
+ sessionData.channel = channel
+
+ // Mute/unmute all connections depending on the selected channel
+ participantUpdateAll()
+ }
+
/**
* Mute/Unmute audio for current session publisher
*/
@@ -785,7 +813,7 @@
*/
function connectionUpdate(data) {
let conn = connections[data.connectionId]
-
+ let refresh = false
let handUpdate = conn => {
if ('hand' in data && data.hand != conn.hand) {
if (data.hand) {
@@ -803,13 +831,6 @@
const isPublisher = sessionData.role & Roles.PUBLISHER
const isModerator = sessionData.role & Roles.MODERATOR
- // Inform the vue component, so it can update some UI controls
- let update = () => {
- if (sessionData.onSessionDataUpdate) {
- sessionData.onSessionDataUpdate(data)
- }
- }
-
// demoted to a subscriber
if ('role' in data && isPublisher && !rolePublisher) {
session.unpublish(publisher)
@@ -823,20 +844,15 @@
handUpdate(sessionData)
// merge the changed data into internal session metadata object
- Object.keys(data).forEach(key => { sessionData[key] = data[key] })
+ sessionData = Object.assign({}, sessionData, data, { audioActive, videoActive })
// update the participant element
sessionData.element = participantUpdate(sessionData.element, sessionData)
// promoted/demoted to/from a moderator
if ('role' in data) {
- if ((!isModerator && roleModerator) || (isModerator && !roleModerator)) {
- // Update all participants, to enable/disable the popup menu
- Object.keys(connections).forEach(key => {
- const conn = connections[key]
- participantUpdate(conn.element, conn)
- })
- }
+ // Update all participants, to enable/disable the popup menu
+ refresh = (!isModerator && roleModerator) || (isModerator && !roleModerator)
}
// Inform the vue component, so it can update some UI controls
@@ -846,9 +862,12 @@
if ('role' in data && !isPublisher && rolePublisher) {
publisher.createVideoElement(sessionData.element, 'PREPEND')
session.publish(publisher).then(() => {
- data.audioActive = publisher.stream.audioActive
- data.videoActive = publisher.stream.videoActive
- update()
+ sessionData.audioActive = publisher.stream.audioActive
+ sessionData.videoActive = publisher.stream.videoActive
+
+ if (sessionData.onSessionDataUpdate) {
+ sessionData.onSessionDataUpdate(sessionData)
+ }
})
// Open the media setup dialog
@@ -873,6 +892,24 @@
conn.element = participantUpdate(conn.element, conn)
}
+
+ // Update channels list
+ sessionData.channels = getChannels(connections)
+
+ // The channel user was using has been removed (or rather the participant stopped being an interpreter)
+ if (sessionData.channel && !sessionData.channels.includes(sessionData.channel)) {
+ sessionData.channel = null
+ refresh = true
+ }
+
+ if (refresh) {
+ participantUpdateAll()
+ }
+
+ // Inform the vue component, so it can update some UI controls
+ if (sessionData.onSessionDataUpdate) {
+ sessionData.onSessionDataUpdate(sessionData)
+ }
}
/**
@@ -928,19 +965,22 @@
* parameter it will be a video element wrapper inside the matrix or a simple
* tag-like element on the subscribers list.
*
- * @param params Connection metadata/params
+ * @param params Connection metadata/params
+ * @param content Optional content to prepend to the element
*
* @return The element
*/
- function participantCreate(params) {
+ function participantCreate(params, content) {
let element
params.isSelf = params.isSelf || session.connection.connectionId == params.connectionId
- if (params.role & Roles.PUBLISHER || params.role & Roles.SCREEN) {
- element = publisherCreate(params)
+ if ((!params.language && params.role & Roles.PUBLISHER) || params.role & Roles.SCREEN) {
+ // publishers and shared screens
+ element = publisherCreate(params, content)
} else {
- element = subscriberCreate(params)
+ // subscribers and language interpreters
+ element = subscriberCreate(params, content)
}
setTimeout(resize, 50);
@@ -951,9 +991,10 @@
/**
* Create a