Changeset View
Changeset View
Standalone View
Standalone View
src/resources/js/meet/app.js
Show All 31 Lines | function Meet(container) | ||||
let microphones = [] // List of user audio devices | let microphones = [] // List of user audio devices | ||||
let connections = {} // Connected users in the session | let connections = {} // Connected users in the session | ||||
let containerWidth | let containerWidth | ||||
let containerHeight | let containerHeight | ||||
let chatCount = 0 | let chatCount = 0 | ||||
let volumeElement | let volumeElement | ||||
let setupProps | let setupProps | ||||
let subscribersContainer | |||||
OV = new OpenVidu() | OV = new OpenVidu() | ||||
screenOV = new OpenVidu() | screenOV = new OpenVidu() | ||||
// If there's anything to do, do it here. | // If there's anything to do, do it here. | ||||
//OV.setAdvancedConfiguration(config) | //OV.setAdvancedConfiguration(config) | ||||
// Disable all logging except errors | // Disable all logging except errors | ||||
Show All 17 Lines | function Meet(container) | ||||
this.switchScreen = switchScreen | this.switchScreen = switchScreen | ||||
this.switchVideo = switchVideo | this.switchVideo = switchVideo | ||||
this.updateSession = updateSession | this.updateSession = updateSession | ||||
/** | /** | ||||
* Join the room session | * Join the room session | ||||
* | * | ||||
* @param data Session metadata and event handlers (session, token, shareToken, nickname, role, | * @param data Session metadata and event handlers (session, token, shareToken, nickname, | ||||
* chatElement, menuElement, onDestroy, onJoinRequest) | * canPublish, chatElement, menuElement, onDestroy, onJoinRequest) | ||||
*/ | */ | ||||
function joinRoom(data) { | function joinRoom(data) { | ||||
resize(); | resize(); | ||||
volumeMeterStop() | volumeMeterStop() | ||||
data.params = { | data.params = { | ||||
nickname: data.nickname, // user nickname | nickname: data.nickname, // user nickname | ||||
// avatar: undefined // avatar image | // avatar: undefined // avatar image | ||||
} | } | ||||
// Create a container for subscribers | |||||
if (!subscribersContainer) { | |||||
subscribersContainer = $('<div id="meet-subscribers">').appendTo(container).get(0) | |||||
} | |||||
sessionData = data | sessionData = data | ||||
// Init a session | // Init a session | ||||
session = OV.initSession() | session = OV.initSession() | ||||
// Handle connection creation events | // Handle connection creation events | ||||
session.on('connectionCreated', event => { | session.on('connectionCreated', event => { | ||||
// Ignore the current user connection | // Ignore the current user connection | ||||
if (event.connection.role) { | if (event.connection.role) { | ||||
role = event.connection.role | role = event.connection.role | ||||
return | return | ||||
} | } | ||||
// This is the first event executed when a user joins in. | // This is the first event executed when a user joins in. | ||||
// We'll create the video wrapper here, which will be re-used | // We'll create the video wrapper here, which can be re-used | ||||
// in 'streamCreated' event handler. | // in 'streamCreated' event handler. | ||||
// Note: For a user with no cam/mic enabled streamCreated event | // Note: For a user with a subscriber role 'streamCreated' event | ||||
// is not being dispatched at all | // is not being dispatched at all | ||||
// TODO: We may consider placing users with no video enabled | let metadata = connectionData(event.connection) | ||||
// in a separate place, so they do not fill the precious | |||||
// screen estate | |||||
let connectionId = event.connection.connectionId | let connectionId = event.connection.connectionId | ||||
let metadata = JSON.parse(event.connection.data) | |||||
metadata.connId = connectionId | metadata.connId = connectionId | ||||
let wrapper = videoWrapperCreate(container, metadata) | |||||
connections[connectionId] = { | let element = participantCreate(metadata) | ||||
element: wrapper | |||||
} | |||||
updateLayout() | connections[connectionId] = { element } | ||||
resize() | |||||
// Send the current user status to the connecting user | // Send the current user status to the connecting user | ||||
// otherwise e.g. nickname might be not up to date | // otherwise e.g. nickname might be not up to date | ||||
signalUserUpdate(event.connection) | signalUserUpdate(event.connection) | ||||
}) | }) | ||||
session.on('connectionDestroyed', event => { | session.on('connectionDestroyed', event => { | ||||
let conn = connections[event.connection.connectionId] | let conn = connections[event.connection.connectionId] | ||||
if (conn) { | if (conn) { | ||||
$(conn.element).remove() | if ($(conn.element).is('.meet-video')) { | ||||
numOfVideos-- | numOfVideos-- | ||||
updateLayout() | } | ||||
$(conn.element).remove() | |||||
delete connections[event.connection.connectionId] | delete connections[event.connection.connectionId] | ||||
} | } | ||||
resize() | |||||
}) | }) | ||||
// On every new Stream received... | // On every new Stream received... | ||||
session.on('streamCreated', event => { | session.on('streamCreated', event => { | ||||
let connection = event.stream.connection | let connection = event.stream.connection | ||||
let connectionId = connection.connectionId | let connectionId = connection.connectionId | ||||
let metadata = JSON.parse(connection.data) | let metadata = connectionData(connection) | ||||
let wrapper = connections[connectionId].element | let wrapper = connections[connectionId].element | ||||
let props = { | let props = { | ||||
// Prepend the video element so it is always before the watermark element | // Prepend the video element so it is always before the watermark element | ||||
insertMode: 'PREPEND' | insertMode: 'PREPEND' | ||||
} | } | ||||
// Subscribe to the Stream to receive it | // Subscribe to the Stream to receive it | ||||
let subscriber = session.subscribe(event.stream, wrapper, props); | let subscriber = session.subscribe(event.stream, wrapper, props); | ||||
subscriber.on('videoElementCreated', event => { | subscriber.on('videoElementCreated', event => { | ||||
$(event.element).prop({ | $(event.element).prop({ | ||||
tabindex: -1 | tabindex: -1 | ||||
}) | }) | ||||
updateLayout() | resize() | ||||
}) | }) | ||||
/* | /* | ||||
subscriber.on('videoElementDestroyed', event => { | subscriber.on('videoElementDestroyed', event => { | ||||
}) | }) | ||||
*/ | */ | ||||
// Update the wrapper controls/status | // Update the wrapper controls/status | ||||
videoWrapperUpdate(wrapper, event.stream) | participantUpdate(wrapper, event.stream) | ||||
}) | }) | ||||
/* | /* | ||||
session.on('streamDestroyed', event => { | session.on('streamDestroyed', event => { | ||||
}) | }) | ||||
*/ | */ | ||||
// Handle session disconnection events | // Handle session disconnection events | ||||
session.on('sessionDisconnected', event => { | session.on('sessionDisconnected', event => { | ||||
if (data.onDestroy) { | if (data.onDestroy) { | ||||
data.onDestroy(event) | data.onDestroy(event) | ||||
} | } | ||||
updateLayout() | resize() | ||||
}) | }) | ||||
// Handle signals from all participants | // Handle signals from all participants | ||||
session.on('signal', signalEventHandler) | session.on('signal', signalEventHandler) | ||||
// Connect with the token | // Connect with the token | ||||
session.connect(data.token, data.params) | session.connect(data.token, data.params) | ||||
.then(() => { | .then(() => { | ||||
let params = { publisher: true, audioActive, videoActive } | let wrapper | ||||
let wrapper = videoWrapperCreate(container, Object.assign({}, data.params, params)) | let params = { self: true, canPublish: data.canPublish, audioActive, videoActive } | ||||
params = Object.assign({}, data.params, params) | |||||
publisher.on('videoElementCreated', event => { | publisher.on('videoElementCreated', event => { | ||||
$(event.element).prop({ | $(event.element).prop({ | ||||
muted: true, // Mute local video to avoid feedback | muted: true, // Mute local video to avoid feedback | ||||
disablePictureInPicture: true, // this does not work in Firefox | disablePictureInPicture: true, // this does not work in Firefox | ||||
tabindex: -1 | tabindex: -1 | ||||
}) | }) | ||||
updateLayout() | resize() | ||||
}) | }) | ||||
publisher.createVideoElement(wrapper, 'PREPEND') | wrapper = participantCreate(params) | ||||
sessionData.wrapper = wrapper | |||||
// Publish the stream | if (data.canPublish) { | ||||
if (sessionData.role != 'SUBSCRIBER') { | publisher.createVideoElement(wrapper, 'PREPEND') | ||||
session.publish(publisher) | session.publish(publisher) | ||||
} | } | ||||
resize() | |||||
sessionData.wrapper = wrapper | |||||
}) | }) | ||||
.catch(error => { | .catch(error => { | ||||
console.error('There was an error connecting to the session: ', error.message); | console.error('There was an error connecting to the session: ', error.message); | ||||
}) | }) | ||||
// Prepare the chat | // Prepare the chat | ||||
setupChat() | setupChat() | ||||
} | } | ||||
▲ Show 20 Lines • Show All 213 Lines • ▼ Show 20 Lines | function signalEventHandler(signal) { | ||||
let conn, data | let conn, data | ||||
let connId = signal.from ? signal.from.connectionId : null | let connId = signal.from ? signal.from.connectionId : null | ||||
switch (signal.type) { | switch (signal.type) { | ||||
case 'signal:userChanged': | case 'signal:userChanged': | ||||
if (conn = connections[connId]) { | if (conn = connections[connId]) { | ||||
data = JSON.parse(signal.data) | data = JSON.parse(signal.data) | ||||
videoWrapperUpdate(conn.element, data) | participantUpdate(conn.element, data) | ||||
nicknameUpdate(data.nickname, connId) | nicknameUpdate(data.nickname, connId) | ||||
} | } | ||||
break | break | ||||
case 'signal:chat': | case 'signal:chat': | ||||
data = JSON.parse(signal.data) | data = JSON.parse(signal.data) | ||||
data.id = connId | data.id = connId | ||||
pushChatMessage(data) | pushChatMessage(data) | ||||
▲ Show 20 Lines • Show All 127 Lines • ▼ Show 20 Lines | function switchAudio() { | ||||
// TODO: If user has no devices or denied access to them in the setup, | // TODO: If user has no devices or denied access to them in the setup, | ||||
// the button will just not work. Find a way to make it working | // the button will just not work. Find a way to make it working | ||||
// after user unlocks his devices. For now he has to refresh | // after user unlocks his devices. For now he has to refresh | ||||
// the page and join the room again. | // the page and join the room again. | ||||
if (microphones.length) { | if (microphones.length) { | ||||
try { | try { | ||||
publisher.publishAudio(!audioActive) | publisher.publishAudio(!audioActive) | ||||
audioActive = !audioActive | audioActive = !audioActive | ||||
videoWrapperUpdate(sessionData.wrapper, { audioActive }) | participantUpdate(sessionData.wrapper, { audioActive }) | ||||
signalUserUpdate() | signalUserUpdate() | ||||
} catch (e) { | } catch (e) { | ||||
console.error(e) | console.error(e) | ||||
} | } | ||||
} | } | ||||
return audioActive | return audioActive | ||||
} | } | ||||
/** | /** | ||||
* Mute/Unmute video for current session publisher | * Mute/Unmute video for current session publisher | ||||
*/ | */ | ||||
function switchVideo() { | function switchVideo() { | ||||
// TODO: If user has no devices or denied access to them in the setup, | // TODO: If user has no devices or denied access to them in the setup, | ||||
// the button will just not work. Find a way to make it working | // the button will just not work. Find a way to make it working | ||||
// after user unlocks his devices. For now he has to refresh | // after user unlocks his devices. For now he has to refresh | ||||
// the page and join the room again. | // the page and join the room again. | ||||
if (cameras.length) { | if (cameras.length) { | ||||
try { | try { | ||||
publisher.publishVideo(!videoActive) | publisher.publishVideo(!videoActive) | ||||
videoActive = !videoActive | videoActive = !videoActive | ||||
videoWrapperUpdate(sessionData.wrapper, { videoActive }) | participantUpdate(sessionData.wrapper, { videoActive }) | ||||
signalUserUpdate() | signalUserUpdate() | ||||
} catch (e) { | } catch (e) { | ||||
console.error(e) | console.error(e) | ||||
} | } | ||||
} | } | ||||
return videoActive | return videoActive | ||||
} | } | ||||
Show All 39 Lines | function nicknameUpdate(nickname, connectionId) { | ||||
if (elem.data('id') == connectionId) { | if (elem.data('id') == connectionId) { | ||||
elem.find('.nickname').text(nickname || '') | elem.find('.nickname').text(nickname || '') | ||||
} | } | ||||
}) | }) | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Create a participant element in the matrix. Depending on the `canPublish` | |||||
* 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 | |||||
* | |||||
* @return The element | |||||
*/ | |||||
function participantCreate(params) { | |||||
if (params.canPublish) { | |||||
return publisherCreate(params) | |||||
} | |||||
return subscriberCreate(params) | |||||
} | |||||
/** | |||||
* Create a <video> element wrapper with controls | * Create a <video> element wrapper with controls | ||||
* | * | ||||
* @param container The parent element | |||||
* @param params Connection metadata/params | * @param params Connection metadata/params | ||||
*/ | */ | ||||
function videoWrapperCreate(container, params) { | function publisherCreate(params) { | ||||
// Create the element | // Create the element | ||||
let wrapper = $('<div class="meet-video">').html( | let wrapper = $( | ||||
svgIcon('user', 'fas', 'watermark') | '<div class="meet-video">' | ||||
+ '<div class="dropdown">' | + svgIcon('user', 'fas', 'watermark') | ||||
+ '<a href="#" class="nickname btn btn-link" title="Nickname" aria-haspopup="true" aria-expanded="false" role="button">' | |||||
+ '<span class="content"></span>' | |||||
+ '<span class="icon">' + svgIcon('user') + '</span>' | |||||
+ '</a>' | |||||
+ '<div class="dropdown-menu">' | |||||
+ '<a class="dropdown-item action-dismiss" href="#">Dismiss</a>' | |||||
+ '</div>' | |||||
+ '</div>' | |||||
+ '<div class="controls">' | + '<div class="controls">' | ||||
+ '<button type="button" class="btn btn-link link-audio hidden" title="Mute audio">' + svgIcon('volume-mute') + '</button>' | + '<button type="button" class="btn btn-link link-audio hidden" title="Mute audio">' + svgIcon('volume-mute') + '</button>' | ||||
+ '<button type="button" class="btn btn-link link-fullscreen closed hidden" title="Full screen">' + svgIcon('expand') + '</button>' | + '<button type="button" class="btn btn-link link-fullscreen closed hidden" title="Full screen">' + svgIcon('expand') + '</button>' | ||||
+ '<button type="button" class="btn btn-link link-fullscreen open hidden" title="Full screen">' + svgIcon('compress') + '</button>' | + '<button type="button" class="btn btn-link link-fullscreen open hidden" title="Full screen">' + svgIcon('compress') + '</button>' | ||||
+ '</div>' | + '</div>' | ||||
+ '<div class="status">' | + '<div class="status">' | ||||
+ '<span class="bg-danger status-audio hidden">' + svgIcon('microphone') + '</span>' | + '<span class="bg-danger status-audio hidden">' + svgIcon('microphone') + '</span>' | ||||
+ '<span class="bg-danger status-video hidden">' + svgIcon('video') + '</span>' | + '<span class="bg-danger status-video hidden">' + svgIcon('video') + '</span>' | ||||
+ '</div>' | + '</div>' | ||||
+ '</div>' | |||||
) | ) | ||||
if (params.publisher) { | // Append the nickname widget | ||||
// Add events for nickname change | wrapper.find('.controls').before(nicknameWidget(params)) | ||||
let nickname = wrapper.addClass('publisher').find('.nickname') | |||||
let editable = nickname.find('.content')[0] | |||||
let editableEnable = () => { | |||||
editable.contentEditable = true | |||||
editable.focus() | |||||
} | |||||
let editableUpdate = () => { | |||||
editable.contentEditable = false | |||||
sessionData.params.nickname = editable.innerText | |||||
signalUserUpdate() | |||||
nicknameUpdate(editable.innerText, session.connection.connectionId) | |||||
} | |||||
nickname.on('click', editableEnable) | if (!params.self) { | ||||
// Enable audio mute button | |||||
$(editable).on('blur', editableUpdate) | |||||
.on('click', editableEnable) | |||||
.on('keydown', e => { | |||||
// Enter or Esc | |||||
if (e.keyCode == 13 || e.keyCode == 27) { | |||||
editableUpdate() | |||||
return false | |||||
} | |||||
}) | |||||
} else { | |||||
wrapper.find('.link-audio').removeClass('hidden') | wrapper.find('.link-audio').removeClass('hidden') | ||||
.on('click', e => { | .on('click', e => { | ||||
let video = wrapper.find('video')[0] | let video = wrapper.find('video')[0] | ||||
video.muted = !video.muted | video.muted = !video.muted | ||||
wrapper.find('.link-audio')[video.muted ? 'addClass' : 'removeClass']('text-danger') | wrapper.find('.link-audio')[video.muted ? 'addClass' : 'removeClass']('text-danger') | ||||
}) | }) | ||||
if (role == 'MODERATOR') { | |||||
wrapper.addClass('moderated') | |||||
wrapper.find('.nickname').attr({title: 'Options', 'data-toggle': 'dropdown'}).dropdown() | |||||
wrapper.find('.action-dismiss').on('click', e => { | |||||
if (sessionData.onDismiss) { | |||||
sessionData.onDismiss(params.connId) | |||||
} | |||||
}) | |||||
} | |||||
} | } | ||||
videoWrapperUpdate(wrapper, params) | participantUpdate(wrapper, params) | ||||
// Fullscreen control | // Fullscreen control | ||||
if (document.fullscreenEnabled) { | if (document.fullscreenEnabled) { | ||||
wrapper.find('.link-fullscreen.closed').removeClass('hidden') | wrapper.find('.link-fullscreen.closed').removeClass('hidden') | ||||
.on('click', () => { | .on('click', () => { | ||||
wrapper.get(0).requestFullscreen() | wrapper.get(0).requestFullscreen() | ||||
}) | }) | ||||
wrapper.find('.link-fullscreen.open') | wrapper.find('.link-fullscreen.open') | ||||
.on('click', () => { | .on('click', () => { | ||||
document.exitFullscreen() | document.exitFullscreen() | ||||
}) | }) | ||||
wrapper.on('fullscreenchange', () => { | wrapper.on('fullscreenchange', () => { | ||||
// const enabled = document.fullscreenElement | // const enabled = document.fullscreenElement | ||||
wrapper.find('.link-fullscreen.closed').toggleClass('hidden') | wrapper.find('.link-fullscreen.closed').toggleClass('hidden') | ||||
wrapper.find('.link-fullscreen.open').toggleClass('hidden') | wrapper.find('.link-fullscreen.open').toggleClass('hidden') | ||||
wrapper.toggleClass('fullscreen') | wrapper.toggleClass('fullscreen') | ||||
}) | }) | ||||
} | } | ||||
numOfVideos++ | numOfVideos++ | ||||
return wrapper[params.publisher ? 'prependTo' : 'appendTo'](container).get(0) | // Remove the subscriber element, if exists | ||||
$('#subscriber-' + params.connId).remove() | |||||
return wrapper[params.self ? 'prependTo' : 'appendTo'](container).get(0) | |||||
} | } | ||||
/** | /** | ||||
* Update the <video> wrapper controls | * Update the <video> wrapper controls | ||||
* | * | ||||
* @param wrapper The wrapper element | * @param wrapper The wrapper element | ||||
* @param params Connection metadata/params | * @param params Connection metadata/params | ||||
*/ | */ | ||||
function videoWrapperUpdate(wrapper, params) { | function participantUpdate(wrapper, params) { | ||||
const $element = $(wrapper) | |||||
if ('audioActive' in params) { | if ('audioActive' in params) { | ||||
$(wrapper).find('.status-audio')[params.audioActive ? 'addClass' : 'removeClass']('hidden') | $element.find('.status-audio')[params.audioActive ? 'addClass' : 'removeClass']('hidden') | ||||
} | } | ||||
if ('videoActive' in params) { | if ('videoActive' in params) { | ||||
$(wrapper).find('.status-video')[params.videoActive ? 'addClass' : 'removeClass']('hidden') | $element.find('.status-video')[params.videoActive ? 'addClass' : 'removeClass']('hidden') | ||||
} | } | ||||
if ('nickname' in params) { | if ('nickname' in params) { | ||||
$(wrapper).find('.nickname > .content').text(params.nickname) | $element.find('.meet-nickname > .content').text(params.nickname) | ||||
} | |||||
if (params.self) { | |||||
$element.addClass('self') | |||||
} | |||||
if (role == 'MODERATOR') { | |||||
$element.addClass('moderated') | |||||
} | |||||
} | |||||
/** | |||||
* Create a tag-like element for a subscriber participant | |||||
* | |||||
* @param params Connection metadata/params | |||||
*/ | |||||
function subscriberCreate(params) { | |||||
// Create the element | |||||
let wrapper = $('<div class="meet-subscriber">').append(nicknameWidget(params)) | |||||
participantUpdate(wrapper, params) | |||||
return wrapper[params.self ? 'prependTo' : 'appendTo'](subscribersContainer) | |||||
.attr('id', 'subscriber-' + params.connId) | |||||
.get(0) | |||||
} | |||||
/** | |||||
* Create a tag-like nickname widget | |||||
* | |||||
* @param object params Connection metadata/params | |||||
*/ | |||||
function nicknameWidget(params) { | |||||
// Create the element | |||||
let element = $( | |||||
'<div class="dropdown">' | |||||
+ '<a href="#" class="meet-nickname btn" title="Nickname" aria-haspopup="true" aria-expanded="false" role="button">' | |||||
+ '<span class="content"></span>' | |||||
+ '<span class="icon">' + svgIcon('user') + '</span>' | |||||
+ '</a>' | |||||
+ '<div class="dropdown-menu">' | |||||
+ '<a class="dropdown-item action-dismiss" href="#">Dismiss</a>' | |||||
+ '</div>' | |||||
+ '</div>' | |||||
) | |||||
let nickname = element.find('.meet-nickname') | |||||
.addClass('btn btn-outline-' + (params.self ? 'primary' : 'secondary')) | |||||
if (params.self) { | |||||
// Add events for nickname change | |||||
let editable = element.find('.content')[0] | |||||
let editableEnable = () => { | |||||
editable.contentEditable = true | |||||
editable.focus() | |||||
} | } | ||||
let editableUpdate = () => { | |||||
editable.contentEditable = false | |||||
sessionData.params.nickname = editable.innerText | |||||
signalUserUpdate() | |||||
nicknameUpdate(editable.innerText, session.connection.connectionId) | |||||
} | |||||
nickname.on('click', editableEnable) | |||||
$(editable).on('blur', editableUpdate) | |||||
.on('keydown', e => { | |||||
// Enter or Esc | |||||
if (e.keyCode == 13 || e.keyCode == 27) { | |||||
editableUpdate() | |||||
return false | |||||
} | |||||
}) | |||||
} else if (role == 'MODERATOR') { | |||||
nickname.attr({title: 'Options', 'data-toggle': 'dropdown'}) | |||||
.dropdown({boundary: container}) | |||||
element.find('.action-dismiss').on('click', e => { | |||||
if (sessionData.onDismiss) { | |||||
sessionData.onDismiss(params.connId) | |||||
} | |||||
}) | |||||
} | |||||
return element.get(0) | |||||
} | } | ||||
/** | /** | ||||
* Window onresize event handler (updates room layout) | * Window onresize event handler (updates room layout) | ||||
*/ | */ | ||||
function resize() { | function resize() { | ||||
containerWidth = container.offsetWidth | containerWidth = container.offsetWidth | ||||
containerHeight = container.offsetHeight | containerHeight = container.offsetHeight | ||||
if (subscribersContainer) { | |||||
containerHeight -= subscribersContainer.offsetHeight | |||||
} | |||||
updateLayout() | updateLayout() | ||||
$(container).parent()[window.screen.width <= 768 ? 'addClass' : 'removeClass']('mobile') | $(container).parent()[window.screen.width <= 768 ? 'addClass' : 'removeClass']('mobile') | ||||
} | } | ||||
/** | /** | ||||
* Update the room "matrix" layout | * Update the room "matrix" layout | ||||
*/ | */ | ||||
function updateLayout() { | function updateLayout() { | ||||
▲ Show 20 Lines • Show All 192 Lines • ▼ Show 20 Lines | */ | ||||
* Stop the volume meter | * Stop the volume meter | ||||
*/ | */ | ||||
function volumeMeterStop() { | function volumeMeterStop() { | ||||
if (publisher && volumeElement) { | if (publisher && volumeElement) { | ||||
publisher.off('streamAudioVolumeChange') | publisher.off('streamAudioVolumeChange') | ||||
volumeElement.firstChild.style.height = 0 | volumeElement.firstChild.style.height = 0 | ||||
} | } | ||||
} | } | ||||
function connectionData(connection) { | |||||
// Note: we're sending a json from two sources (server-side when | |||||
// creating a token/connection, and client-side when joining the session) | |||||
// OpenVidu is unable to merge these two objects into one, for it it is only | |||||
// two strings, so it puts a "%/%" separator in between, we'll replace it with comma | |||||
// to get one parseable json object | |||||
return JSON.parse(connection.data.replace('}%/%{', ',')) | |||||
} | |||||
} | } | ||||
export default Meet | export default Meet |