Changeset View
Changeset View
Standalone View
Standalone View
src/resources/js/meet/app.js
import anchorme from 'anchorme' | import anchorme from 'anchorme' | ||||
import { library } from '@fortawesome/fontawesome-svg-core' | import { library } from '@fortawesome/fontawesome-svg-core' | ||||
import { OpenVidu } from 'openvidu-browser' | import { OpenVidu } from 'openvidu-browser' | ||||
function Meet(container) | function Meet(container) | ||||
{ | { | ||||
let OV // OpenVidu object to initialize a session | let OV // OpenVidu object to initialize a session | ||||
let session // Session object where the user will connect | let session // Session object where the user will connect | ||||
let publisher // Publisher object which the user will publish | let publisher // Publisher object which the user will publish | ||||
let audioActive = false // True if the audio track of the publisher is active | let audioActive = false // True if the audio track of the publisher is active | ||||
let videoActive = false // True if the video track of the publisher is active | let videoActive = false // True if the video track of the publisher is active | ||||
let numOfVideos = 0 // Keeps track of the number of videos that are being shown | let numOfVideos = 0 // Keeps track of the number of videos that are being shown | ||||
let audioSource = '' // Currently selected microphone | let audioSource = '' // Currently selected microphone | ||||
let videoSource = '' // Currently selected camera | let videoSource = '' // Currently selected camera | ||||
let sessionData // Room session metadata | let sessionData // Room session metadata | ||||
let role // Current user role | |||||
let screenOV // OpenVidu object to initialize a screen sharing session | let screenOV // OpenVidu object to initialize a screen sharing session | ||||
let screenSession // Session object where the user will connect for screen sharing | let screenSession // Session object where the user will connect for screen sharing | ||||
let screenPublisher // Publisher object which the user will publish the screen sharing | let screenPublisher // Publisher object which the user will publish the screen sharing | ||||
let publisherDefaults = { | let publisherDefaults = { | ||||
publishAudio: true, // Whether to start publishing with your audio unmuted or not | publishAudio: true, // Whether to start publishing with your audio unmuted or not | ||||
publishVideo: true, // Whether to start publishing with your video enabled or not | publishVideo: true, // Whether to start publishing with your video enabled or not | ||||
Show All 40 Lines | function Meet(container) | ||||
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, | * @param data Session metadata and event handlers (session, token, shareToken, nickname, | ||||
* chatElement, menuElement, onDestroy) | * 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 | ||||
} | } | ||||
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 | |||||
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 will 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 no cam/mic enabled streamCreated event | ||||
// is not being dispatched at all | // is not being dispatched at all | ||||
// TODO: We may consider placing users with no video enabled | // TODO: We may consider placing users with no video enabled | ||||
// in a separate place, so they do not fill the precious | // in a separate place, so they do not fill the precious | ||||
// screen estate | // screen estate | ||||
let connectionId = event.connection.connectionId | let connectionId = event.connection.connectionId | ||||
let metadata = JSON.parse(event.connection.data) | let metadata = JSON.parse(event.connection.data) | ||||
metadata.connId = connectionId | |||||
let wrapper = videoWrapperCreate(container, metadata) | let wrapper = videoWrapperCreate(container, metadata) | ||||
connections[connectionId] = { | connections[connectionId] = { | ||||
element: wrapper | element: wrapper | ||||
} | } | ||||
updateLayout() | updateLayout() | ||||
▲ Show 20 Lines • Show All 293 Lines • ▼ Show 20 Lines | function setupChat() { | ||||
}) | }) | ||||
} | } | ||||
/** | /** | ||||
* Signal events handler | * Signal events handler | ||||
*/ | */ | ||||
function signalEventHandler(signal) { | function signalEventHandler(signal) { | ||||
let conn, data | let conn, data | ||||
let connId = signal.from.connectionId | 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) | videoWrapperUpdate(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) | ||||
break | break | ||||
case 'signal:joinRequest': | |||||
if (sessionData.onJoinRequest) { | |||||
sessionData.onJoinRequest(JSON.parse(signal.data)) | |||||
} | |||||
break; | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Send the chat message to other participants | * Send the chat message to other participants | ||||
* | * | ||||
* @param message Message string | * @param message Message string | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 198 Lines • ▼ Show 20 Lines | */ | ||||
* Create a <video> element wrapper with controls | * Create a <video> element wrapper with controls | ||||
* | * | ||||
* @param container The parent element | * @param container The parent element | ||||
* @param params Connection metadata/params | * @param params Connection metadata/params | ||||
*/ | */ | ||||
function videoWrapperCreate(container, params) { | function videoWrapperCreate(container, params) { | ||||
// Create the element | // Create the element | ||||
let wrapper = $('<div class="meet-video">').html( | let wrapper = $('<div class="meet-video">').html( | ||||
`${svgIcon("user", 'fas', 'watermark')} | svgIcon('user', 'fas', 'watermark') | ||||
<div class="nickname" title="Nickname"> | + '<div class="dropdown">' | ||||
<span></span> | + '<a href="#" class="nickname btn btn-link" title="Nickname" aria-haspopup="true" aria-expanded="false" role="button">' | ||||
<button type="button" class="btn btn-link">${svgIcon('user')}</button> | + '<span class="content"></span>' | ||||
</div> | + '<span class="icon">' + svgIcon('user') + '</span>' | ||||
<div class="controls"> | + '</a>' | ||||
<button type="button" class="btn btn-link link-audio hidden" title="Mute audio">${svgIcon('volume-mute')}</button> | + '<div class="dropdown-menu">' | ||||
<button type="button" class="btn btn-link link-fullscreen closed hidden" title="Full screen">${svgIcon('expand')}</button> | + '<a class="dropdown-item action-dismiss" href="#">Dismiss</a>' | ||||
<button type="button" class="btn btn-link link-fullscreen open hidden" title="Full screen">${svgIcon('compress')}</button> | + '</div>' | ||||
</div> | + '</div>' | ||||
<div class="status"> | + '<div class="controls">' | ||||
<span class="bg-danger status-audio hidden">${svgIcon('microphone')}</span> | + '<button type="button" class="btn btn-link link-audio hidden" title="Mute audio">' + svgIcon('volume-mute') + '</button>' | ||||
<span class="bg-danger status-video hidden">${svgIcon('video')}</span> | + '<button type="button" class="btn btn-link link-fullscreen closed hidden" title="Full screen">' + svgIcon('expand') + '</button>' | ||||
</div>` | + '<button type="button" class="btn btn-link link-fullscreen open hidden" title="Full screen">' + svgIcon('compress') + '</button>' | ||||
+ '</div>' | |||||
+ '<div class="status">' | |||||
+ '<span class="bg-danger status-audio hidden">' + svgIcon('microphone') + '</span>' | |||||
+ '<span class="bg-danger status-video hidden">' + svgIcon('video') + '</span>' | |||||
+ '</div>' | |||||
) | ) | ||||
if (params.publisher) { | if (params.publisher) { | ||||
// Add events for nickname change | // Add events for nickname change | ||||
let nickname = wrapper.addClass('publisher').find('.nickname') | let nickname = wrapper.addClass('publisher').find('.nickname') | ||||
let editable = nickname.find('span').get(0) | let editable = nickname.find('.content')[0] | ||||
let editableEnable = () => { | let editableEnable = () => { | ||||
editable.contentEditable = true | editable.contentEditable = true | ||||
editable.focus() | editable.focus() | ||||
} | } | ||||
let editableUpdate = () => { | let editableUpdate = () => { | ||||
editable.contentEditable = false | editable.contentEditable = false | ||||
sessionData.params.nickname = editable.innerText | sessionData.params.nickname = editable.innerText | ||||
signalUserUpdate() | signalUserUpdate() | ||||
nicknameUpdate(editable.innerText, session.connection.connectionId) | nicknameUpdate(editable.innerText, session.connection.connectionId) | ||||
} | } | ||||
nickname.on('click', editableEnable) | nickname.on('click', editableEnable) | ||||
$(editable).on('blur', editableUpdate) | $(editable).on('blur', editableUpdate) | ||||
.on('click', editableEnable) | .on('click', editableEnable) | ||||
.on('keydown', e => { | .on('keydown', e => { | ||||
// Enter or Esc | // Enter or Esc | ||||
if (e.keyCode == 13 || e.keyCode == 27) { | if (e.keyCode == 13 || e.keyCode == 27) { | ||||
editableUpdate() | editableUpdate() | ||||
return false | return false | ||||
} | } | ||||
}) | }) | ||||
} else { | } else { | ||||
wrapper.find('.nickname > svg').addClass('hidden') | |||||
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) | videoWrapperUpdate(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', () => { | ||||
Show All 29 Lines | function videoWrapperUpdate(wrapper, params) { | ||||
$(wrapper).find('.status-audio')[params.audioActive ? 'addClass' : 'removeClass']('hidden') | $(wrapper).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') | $(wrapper).find('.status-video')[params.videoActive ? 'addClass' : 'removeClass']('hidden') | ||||
} | } | ||||
if ('nickname' in params) { | if ('nickname' in params) { | ||||
$(wrapper).find('.nickname > span').text(params.nickname) | $(wrapper).find('.nickname > .content').text(params.nickname) | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Window onresize event handler (updates room layout) | * Window onresize event handler (updates room layout) | ||||
*/ | */ | ||||
function resize() { | function resize() { | ||||
containerWidth = container.offsetWidth | containerWidth = container.offsetWidth | ||||
▲ Show 20 Lines • Show All 212 Lines • Show Last 20 Lines |