Changeset View
Changeset View
Standalone View
Standalone View
src/resources/vue/Meet/Room.vue
<template> | <template> | ||||
<div id="meet-component"> | <div id="meet-component"> | ||||
<div id="meet-session-toolbar" class="hidden"> | <div id="meet-session-toolbar" class="hidden"> | ||||
<span id="meet-session-logo" v-html="$root.logo()"></span> | <span id="meet-session-logo" v-html="$root.logo()"></span> | ||||
<div id="meet-session-menu"> | <div id="meet-session-menu"> | ||||
<button class="btn btn-link link-audio" @click="switchSound" :disabled="!isPublisher()" title="Mute audio"> | <button class="btn link-audio" :class="{ 'text-primary' : audioActive }" @click="switchSound" :disabled="!isPublisher()" :title="audioActive ? 'Mute audio' : 'Unmute audio'"> | ||||
<svg-icon icon="microphone-slash"></svg-icon> | <svg-icon :icon="audioActive ? 'microphone' : 'microphone-slash'"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-video" @click="switchVideo" :disabled="!isPublisher()" title="Mute video"> | <button class="btn link-video" :class="{ 'text-primary' : videoActive }" @click="switchVideo" :disabled="!isPublisher()" :title="videoActive ? 'Mute video' : 'Unmute video'"> | ||||
<svg-icon icon="video-slash"></svg-icon> | <svg-icon :icon="videoActive ? 'video' : 'video-slash'"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-screen text-danger" @click="switchScreen" :disabled="!canShareScreen || !isPublisher()" title="Share screen"> | <button class="btn link-screen" :class="{ 'text-danger' : screenShareActive }" @click="switchScreen" :disabled="!canShareScreen || !isPublisher()" title="Share screen"> | ||||
<svg-icon icon="desktop"></svg-icon> | <svg-icon icon="desktop"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-hand text-danger" v-if="!isPublisher()" @click="switchHand" title="Raise hand"> | <button class="btn link-hand" :class="{ 'text-primary' : handRaised }" v-if="!isPublisher()" @click="switchHand" :title="handRaised ? 'Lower hand' : 'Raise hand'"> | ||||
<svg-icon icon="hand-paper"></svg-icon> | <svg-icon icon="hand-paper"></svg-icon> | ||||
</button> | </button> | ||||
<span id="channel-select" :style="'display:' + (channels.length ? '' : 'none')" class="dropdown"> | <span id="channel-select" :style="'display:' + (channels.length ? '' : 'none')" class="dropdown"> | ||||
<button class="btn btn-link link-channel" title="Interpreted language channel" aria-haspopup="true" aria-expanded="false" data-toggle="dropdown"> | <button class="btn link-channel" title="Interpreted language channel" aria-haspopup="true" aria-expanded="false" data-toggle="dropdown"> | ||||
<svg-icon icon="headphones"></svg-icon> | <svg-icon icon="headphones"></svg-icon> | ||||
<span class="badge badge-danger" v-if="session.channel">{{ session.channel.toUpperCase() }}</span> | <span class="badge badge-danger" v-if="session.channel">{{ session.channel.toUpperCase() }}</span> | ||||
</button> | </button> | ||||
<div class="dropdown-menu"> | <div class="dropdown-menu"> | ||||
<a :class="'dropdown-item' + (!session.channel ? ' active' : '')" href="#" data-code="" @click="switchChannel">- none -</a> | <a :class="'dropdown-item' + (!session.channel ? ' active' : '')" href="#" data-code="" @click="switchChannel">- none -</a> | ||||
<a v-for="code in channels" :key="code" href="#" @click="switchChannel" :data-code="code" | <a v-for="code in channels" :key="code" href="#" @click="switchChannel" :data-code="code" | ||||
:class="'dropdown-item' + (session.channel == code ? ' active' : '')" | :class="'dropdown-item' + (session.channel == code ? ' active' : '')" | ||||
>{{ languages[code] }}</a> | >{{ languages[code] }}</a> | ||||
</div> | </div> | ||||
</span> | </span> | ||||
<button class="btn btn-link link-chat text-danger" @click="switchChat" title="Chat"> | <button class="btn link-chat" @click="switchChat" title="Chat"> | ||||
<svg-icon icon="comment"></svg-icon> | <svg-icon icon="comment"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-fullscreen closed hidden" @click="switchFullscreen" title="Full screen"> | <button class="btn link-fullscreen closed hidden" @click="switchFullscreen" title="Full screen"> | ||||
<svg-icon icon="expand"></svg-icon> | <svg-icon icon="expand"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-fullscreen open hidden" @click="switchFullscreen" title="Full screen"> | <button class="btn link-fullscreen open hidden" @click="switchFullscreen" title="Exit full screen"> | ||||
<svg-icon icon="compress"></svg-icon> | <svg-icon icon="compress"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-options" v-if="isRoomOwner()" @click="roomOptions" title="Room options"> | <button class="btn link-options" v-if="isRoomOwner()" @click="roomOptions" title="Room options"> | ||||
<svg-icon icon="cog"></svg-icon> | <svg-icon icon="cog"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-logout" @click="logout" title="Leave session"> | <button class="btn link-logout" @click="logout" title="Leave session"> | ||||
<svg-icon icon="power-off"></svg-icon> | <svg-icon icon="power-off"></svg-icon> | ||||
</button> | </button> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div id="meet-setup" class="card container mt-2 mt-md-5 mb-5"> | <div id="meet-setup" class="card container mt-2 mt-md-5 mb-5"> | ||||
<div class="card-body"> | <div class="card-body"> | ||||
<div class="card-title">Set up your session</div> | <div class="card-title">Set up your session</div> | ||||
▲ Show 20 Lines • Show All 213 Lines • ▼ Show 20 Lines | export default { | ||||
324: 'The room is closed. It will be open for others after you join.', | 324: 'The room is closed. It will be open for others after you join.', | ||||
325: 'The room is ready. Please, provide a valid password.', | 325: 'The room is ready. Please, provide a valid password.', | ||||
326: 'The room is locked. Please, enter your name and try again.', | 326: 'The room is locked. Please, enter your name and try again.', | ||||
327: 'Waiting for permission to join the room.', | 327: 'Waiting for permission to join the room.', | ||||
404: 'The room does not exist.', | 404: 'The room does not exist.', | ||||
429: 'Too many requests. Please, wait.', | 429: 'Too many requests. Please, wait.', | ||||
500: 'Failed to connect to the room. Server error.' | 500: 'Failed to connect to the room. Server error.' | ||||
}, | }, | ||||
session: {} | session: {}, | ||||
audioActive: false, | |||||
videoActive: false, | |||||
handRaised: false, | |||||
screenShareActive: false | |||||
} | } | ||||
}, | }, | ||||
mounted() { | mounted() { | ||||
this.room = this.$route.params.room | this.room = this.$route.params.room | ||||
// Initialize OpenVidu and do some basic checks | // Initialize OpenVidu and do some basic checks | ||||
this.meet = new Meet($('#meet-session')[0]); | this.meet = new Meet($('#meet-session')[0]); | ||||
this.canShareScreen = this.meet.isScreenSharingSupported() | this.canShareScreen = this.meet.isScreenSharingSupported() | ||||
▲ Show 20 Lines • Show All 217 Lines • ▼ Show 20 Lines | export default { | ||||
this.session.languages = this.languages | this.session.languages = this.languages | ||||
this.session.menuElement = $('#meet-session-menu')[0] | this.session.menuElement = $('#meet-session-menu')[0] | ||||
this.session.chatElement = $('#meet-chat')[0] | this.session.chatElement = $('#meet-chat')[0] | ||||
this.session.queueElement = $('#meet-queue')[0] | this.session.queueElement = $('#meet-queue')[0] | ||||
this.session.onSuccess = () => { | this.session.onSuccess = () => { | ||||
$('#app').addClass('meet') | $('#app').addClass('meet') | ||||
$('#meet-setup').addClass('hidden') | $('#meet-setup').addClass('hidden') | ||||
$('#meet-session-toolbar,#meet-session-layout').removeClass('hidden') | $('#meet-session-toolbar,#meet-session-layout').removeClass('hidden') | ||||
if (!this.canShareScreen) { | |||||
this.setMenuItem('screen', false, true) | |||||
} | |||||
} | } | ||||
this.session.onError = () => { | this.session.onError = () => { | ||||
this.roomState = 500 | this.roomState = 500 | ||||
} | } | ||||
this.session.onDestroy = event => { | this.session.onDestroy = event => { | ||||
// TODO: Display different message for each reason: forceDisconnectByUser, | // TODO: Display different message for each reason: forceDisconnectByUser, | ||||
// forceDisconnectByServer, sessionClosedByServer? | // forceDisconnectByServer, sessionClosedByServer? | ||||
if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.isRoomOwner()) { | if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.isRoomOwner()) { | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | export default { | ||||
localStorage.setItem(key, this.reqId) | localStorage.setItem(key, this.reqId) | ||||
} | } | ||||
return this.reqId | return this.reqId | ||||
}, | }, | ||||
roomOptions() { | roomOptions() { | ||||
$('#room-options-dialog').modal() | $('#room-options-dialog').modal() | ||||
}, | }, | ||||
setMenuItem(type, state, disabled) { | |||||
let button = $('#meet-session-menu').find('.link-' + type) | |||||
button[state ? 'removeClass' : 'addClass']('text-danger') | |||||
if (disabled !== undefined) { | |||||
button.prop('disabled', disabled) | |||||
} | |||||
}, | |||||
setupMedia() { | setupMedia() { | ||||
let dialog = $('#media-setup-dialog') | let dialog = $('#media-setup-dialog') | ||||
if (!dialog.find('video').length) { | if (!dialog.find('video').length) { | ||||
$('#meet-setup').find('video,div.volume').appendTo(dialog.find('.media-setup-preview')) | $('#meet-setup').find('video,div.volume').appendTo(dialog.find('.media-setup-preview')) | ||||
} | } | ||||
dialog.on('show.bs.modal', () => { this.meet.setupStart() }) | dialog.on('show.bs.modal', () => { this.meet.setupStart() }) | ||||
.on('hide.bs.modal', () => { this.meet.setupStop() }) | .on('hide.bs.modal', () => { this.meet.setupStop() }) | ||||
.modal() | .modal() | ||||
}, | }, | ||||
setupSession() { | setupSession() { | ||||
this.meet.setupStart({ | this.meet.setupStart({ | ||||
videoElement: $('#meet-setup video')[0], | videoElement: $('#meet-setup video')[0], | ||||
volumeElement: $('#meet-setup .volume')[0], | volumeElement: $('#meet-setup .volume')[0], | ||||
onSuccess: setup => { | onSuccess: setup => { | ||||
this.setup = setup | this.setup = setup | ||||
this.microphone = setup.audioSource | this.microphone = setup.audioSource | ||||
this.camera = setup.videoSource | this.camera = setup.videoSource | ||||
this.setMenuItem('audio', setup.audioActive) | this.audioActive = setup.audioActive | ||||
this.setMenuItem('video', setup.videoActive) | this.videoActive = setup.videoActive | ||||
}, | }, | ||||
onError: error => { | onError: error => { | ||||
this.setMenuItem('audio', false, true) | this.audioActive = false | ||||
this.setMenuItem('video', false, true) | this.videoActive = false | ||||
} | } | ||||
}) | }) | ||||
}, | }, | ||||
setupCameraChange() { | setupCameraChange() { | ||||
this.meet.setupSetVideoDevice(this.camera).then(enabled => { | this.meet.setupSetVideoDevice(this.camera).then(enabled => { | ||||
this.setMenuItem('video', enabled) | this.videoActive = enabled | ||||
}) | }) | ||||
}, | }, | ||||
setupMicrophoneChange() { | setupMicrophoneChange() { | ||||
this.meet.setupSetAudioDevice(this.microphone).then(enabled => { | this.meet.setupSetAudioDevice(this.microphone).then(enabled => { | ||||
this.setMenuItem('audio', enabled) | this.audioActive = enabled | ||||
}) | }) | ||||
}, | }, | ||||
switchChannel(e) { | switchChannel(e) { | ||||
let channel = $(e.target).data('code') | let channel = $(e.target).data('code') | ||||
this.$set(this.session, 'channel', channel) | this.$set(this.session, 'channel', channel) | ||||
this.meet.switchChannel(channel) | this.meet.switchChannel(channel) | ||||
}, | }, | ||||
switchChat() { | switchChat() { | ||||
let chat = $('#meet-chat') | let chat = $('#meet-chat') | ||||
let enabled = chat.is('.open') | let enabled = chat.is('.open') | ||||
this.setMenuItem('chat', !enabled) | |||||
chat.toggleClass('open') | chat.toggleClass('open') | ||||
if (!enabled) { | if (!enabled) { | ||||
chat.find('textarea').focus() | chat.find('textarea').focus() | ||||
} | } | ||||
// Trigger resize, so participant matrix can update its layout | // Trigger resize, so participant matrix can update its layout | ||||
window.dispatchEvent(new Event('resize')); | window.dispatchEvent(new Event('resize')); | ||||
Show All 11 Lines | export default { | ||||
if (document.fullscreenElement) { | if (document.fullscreenElement) { | ||||
document.exitFullscreen() | document.exitFullscreen() | ||||
} else { | } else { | ||||
element.requestFullscreen() | element.requestFullscreen() | ||||
} | } | ||||
}, | }, | ||||
switchHand() { | switchHand() { | ||||
let enabled = $('#meet-session-menu').find('.link-hand').is('.text-danger') | this.updateSelf({ hand: !this.handRaised }) | ||||
this.updateSelf({ hand: enabled }, () => { this.setMenuItem('hand', enabled) }) | |||||
}, | }, | ||||
switchSound() { | switchSound() { | ||||
const enabled = this.meet.switchAudio() | const enabled = this.meet.switchAudio() | ||||
this.setMenuItem('audio', enabled) | this.audioActive = enabled | ||||
}, | }, | ||||
switchVideo() { | switchVideo() { | ||||
const enabled = this.meet.switchVideo() | const enabled = this.meet.switchVideo() | ||||
this.setMenuItem('video', enabled) | this.videoActive = enabled | ||||
}, | }, | ||||
switchScreen() { | switchScreen() { | ||||
const switchScreenAction = () => { | const switchScreenAction = () => { | ||||
this.meet.switchScreen((enabled, error) => { | this.meet.switchScreen((enabled, error) => { | ||||
this.setMenuItem('screen', enabled) | this.screenShareActive = enabled | ||||
if (!enabled && !error) { | if (!enabled && !error) { | ||||
// Closing a screen sharing connection invalidates the token | // Closing a screen sharing connection invalidates the token | ||||
delete this.session.shareToken | delete this.session.shareToken | ||||
} | } | ||||
}) | }) | ||||
} | } | ||||
if (this.session.shareToken || !$('#meet-session-menu').find('.link-screen').is('.text-danger')) { | if (this.session.shareToken || this.screenShareActive) { | ||||
switchScreenAction() | switchScreenAction() | ||||
} else { | } else { | ||||
axios.post('/api/v4/openvidu/rooms/' + this.room + '/connections') | axios.post('/api/v4/openvidu/rooms/' + this.room + '/connections') | ||||
.then(response => { | .then(response => { | ||||
this.session.shareToken = response.data.token | this.session.shareToken = response.data.token | ||||
this.meet.updateSession(this.session) | this.meet.updateSession(this.session) | ||||
switchScreenAction() | switchScreenAction() | ||||
}) | }) | ||||
Show All 13 Lines | export default { | ||||
}) | }) | ||||
}, | }, | ||||
updateSession(data) { | updateSession(data) { | ||||
this.session = data | this.session = data | ||||
this.channels = data.channels || [] | this.channels = data.channels || [] | ||||
const isPublisher = this.isPublisher() | const isPublisher = this.isPublisher() | ||||
this.setMenuItem('video', isPublisher ? data.videoActive : false) | this.videoActive = isPublisher ? data.videoActive : false | ||||
this.setMenuItem('audio', isPublisher ? data.audioActive : false) | this.audioActive = isPublisher ? data.audioActive : false | ||||
this.setMenuItem('hand', data.hand) | |||||
this.handRaised = data.hand | |||||
} | } | ||||
} | } | ||||
} | } | ||||
</script> | </script> |