Changeset View
Changeset View
Standalone View
Standalone View
src/resources/vue/Meet/Room.vue
Show All 14 Lines | <div id="meet-component"> | ||||
<svg-icon icon="align-left"></svg-icon> | <svg-icon icon="align-left"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-fullscreen closed hidden" @click="switchFullscreen" title="Full screen"> | <button class="btn btn-link 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 btn-link link-fullscreen open hidden" @click="switchFullscreen" title="Full screen"> | ||||
<svg-icon icon="compress"></svg-icon> | <svg-icon icon="compress"></svg-icon> | ||||
</button> | </button> | ||||
<button class="btn btn-link link-security" v-if="session && session.owner" @click="securityOptions" title="Security options"> | |||||
<svg-icon icon="shield-alt"></svg-icon> | |||||
</button> | |||||
<button class="btn btn-link link-logout" @click="logout" title="Leave session"> | <button class="btn btn-link 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> | ||||
<div class="card-text"> | <div class="card-text"> | ||||
<form class="setup-form row"> | <form class="setup-form row"> | ||||
<div id="setup-preview" class="col-sm-6 mb-3 mb-sm-0"> | <div id="setup-preview" class="col-sm-6 mb-3 mb-sm-0"> | ||||
<video class="rounded"></video> | <video class="rounded"></video> | ||||
<div class="volume"><div class="bar"></div></div> | <div class="volume"><div class="bar"></div></div> | ||||
</div> | </div> | ||||
<div class="col-sm-6"> | <div class="col-sm-6 align-self-center"> | ||||
<div class="form-group"> | <div class="input-group"> | ||||
<label for="setup-microphone">Microphone</label> | <label for="setup-microphone" class="input-group-prepend mb-0"> | ||||
<span class="input-group-text" title="Microphone"><svg-icon icon="microphone"></svg-icon></span> | |||||
</label> | |||||
<select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange"> | <select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange"> | ||||
<option value="">None</option> | <option value="">None</option> | ||||
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option> | <option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option> | ||||
</select> | </select> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="input-group mt-2"> | ||||
<label for="setup-camera">Camera</label> | <label for="setup-camera" class="input-group-prepend mb-0"> | ||||
<span class="input-group-text" title="Camera"><svg-icon icon="video"></svg-icon></span> | |||||
</label> | |||||
<select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange"> | <select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange"> | ||||
<option value="">None</option> | <option value="">None</option> | ||||
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option> | <option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option> | ||||
</select> | </select> | ||||
</div> | </div> | ||||
<div class="form-group mb-0"> | <div class="input-group mt-2"> | ||||
<label for="setup-nickname">Nickname</label> | <label for="setup-nickname" class="input-group-prepend mb-0"> | ||||
<input class="form-control" type="text" id="setup-nickname" v-model="nickname"> | <span class="input-group-text" title="Nickname"><svg-icon icon="user"></svg-icon></span> | ||||
</div> | </label> | ||||
<input class="form-control" type="text" id="setup-nickname" v-model="nickname" placeholder="Your name"> | |||||
</div> | |||||
<div class="input-group mt-2" v-if="session.config && session.config.requires_password"> | |||||
<label for="setup-password" class="input-group-prepend mb-0"> | |||||
<span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span> | |||||
</label> | |||||
<input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password"> | |||||
</div> | </div> | ||||
<div class="text-center mt-4 col-sm-12"> | <div class="mt-3"> | ||||
<status-message :status="roomState" :status-labels="roomStateLabels" class="mb-3"></status-message> | <button v-if="roomState == 'ready' || roomState == 424 || roomState == 425" | ||||
<button v-if="roomState == 'ready' || roomState == 424" | |||||
type="button" | type="button" | ||||
@click="joinSession" | @click="joinSession" | ||||
class="btn btn-primary pl-5 pr-5" | :class="'btn w-100 btn-' + (roomState == 'ready' ? 'success' : 'primary')" | ||||
>JOIN</button> | >JOIN</button> | ||||
<button v-if="roomState == 423" | <button v-if="roomState == 423" | ||||
type="button" | type="button" | ||||
@click="joinSession" | @click="joinSession" | ||||
class="btn btn-primary pl-5 pr-5" | class="btn btn-primary w-100" | ||||
>I'm the owner</button> | >I'm the owner</button> | ||||
</div> | </div> | ||||
</div> | |||||
<div class="mt-4 col-sm-12"> | |||||
<status-message :status="roomState" :status-labels="roomStateLabels"></status-message> | |||||
</div> | |||||
</form> | </form> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div id="meet-session-layout" class="d-flex hidden"> | <div id="meet-session-layout" class="d-flex hidden"> | ||||
<div id="meet-session"></div> | <div id="meet-session"></div> | ||||
<div id="meet-chat"> | <div id="meet-chat"> | ||||
Show All 19 Lines | <div id="meet-component"> | ||||
<p>The session has been closed by the room owner.</p> | <p>The session has been closed by the room owner.</p> | ||||
</div> | </div> | ||||
<div class="modal-footer"> | <div class="modal-footer"> | ||||
<button type="button" class="btn btn-danger modal-action" data-dismiss="modal">Close</button> | <button type="button" class="btn btn-danger modal-action" data-dismiss="modal">Close</button> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<session-security-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></session-security-options> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import Meet from '../../js/meet/app.js' | import Meet from '../../js/meet/app.js' | ||||
import StatusMessage from '../Widgets/StatusMessage' | import StatusMessage from '../Widgets/StatusMessage' | ||||
import LogonForm from '../Login' | import LogonForm from '../Login' | ||||
import SessionSecurityOptions from './SessionSecurityOptions' | |||||
export default { | export default { | ||||
components: { | components: { | ||||
LogonForm, | LogonForm, | ||||
SessionSecurityOptions, | |||||
StatusMessage | StatusMessage | ||||
}, | }, | ||||
data() { | data() { | ||||
return { | return { | ||||
setup: { | setup: { | ||||
cameras: [], | cameras: [], | ||||
microphones: [], | microphones: [], | ||||
}, | }, | ||||
canShareScreen: false, | canShareScreen: false, | ||||
camera: '', | camera: '', | ||||
meet: null, | meet: null, | ||||
microphone: '', | microphone: '', | ||||
nickname: '', | nickname: '', | ||||
password: '', | |||||
room: null, | room: null, | ||||
roomState: 'init', | roomState: 'init', | ||||
roomStateLabels: { | roomStateLabels: { | ||||
init: 'Checking the room...', | init: 'Checking the room...', | ||||
404: 'The room does not exist.', | 404: 'The room does not exist.', | ||||
423: 'The room is closed. Please, wait for the owner to start the session.', | 423: 'The room is closed. Please, wait for the owner to start the session.', | ||||
424: 'The room is closed. It will be open for others after you join.', | 424: 'The room is closed. It will be open for others after you join.', | ||||
425: 'The room is ready. Please, provide a valid password.', | |||||
500: 'Failed to create a session. Server error.' | 500: 'Failed to create a session. Server error.' | ||||
}, | }, | ||||
session: {} | session: {} | ||||
} | } | ||||
}, | }, | ||||
mounted() { | mounted() { | ||||
this.room = this.$route.params.room | this.room = this.$route.params.room | ||||
Show All 18 Lines | export default { | ||||
authSuccess() { | authSuccess() { | ||||
// The user (owner) authentication succeeded | // The user (owner) authentication succeeded | ||||
this.roomState = 'init' | this.roomState = 'init' | ||||
this.initSession() | this.initSession() | ||||
$('#meet-setup').removeClass('hidden') | $('#meet-setup').removeClass('hidden') | ||||
$('#meet-auth').addClass('hidden') | $('#meet-auth').addClass('hidden') | ||||
}, | }, | ||||
configUpdate(config) { | |||||
this.session.config = Object.assign({}, this.session.config, config) | |||||
}, | |||||
initSession(init) { | initSession(init) { | ||||
let params = [] | this.post = { | ||||
password: this.password, | |||||
if (this.canShareScreen) { | nickname: this.nickname, | ||||
params.push('screenShare=1') | screenShare: this.canShareScreen ? 1 : 0, | ||||
} | init: init ? 1 : 0 | ||||
if (init) { | |||||
params.push('init=1') | |||||
} | } | ||||
const url = '/api/v4/openvidu/rooms/' + this.room + '?' + params.join('&') | $('#setup-password').removeClass('is-invalid') | ||||
axios.get(url, { ignoreErrors: true }) | axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true }) | ||||
.then(response => { | .then(response => { | ||||
// Response data contains: session, token and shareToken | // Response data contains: session, token and shareToken | ||||
this.roomState = 'ready' | this.roomState = 'ready' | ||||
this.session = response.data | this.session = response.data | ||||
if (init) { | if (init) { | ||||
this.joinSession() | this.joinSession() | ||||
} | } | ||||
}) | }) | ||||
.catch(error => { | .catch(error => { | ||||
this.roomState = String(error.response.status) | this.roomState = String(error.response.status) | ||||
if (error.response.data && error.response.data.config) { | |||||
this.session.config = error.response.data.config | |||||
} | |||||
switch (this.roomState) { | |||||
case '423': | |||||
// Waiting for the owner to open the room... | // Waiting for the owner to open the room... | ||||
if (error.response.status == 423) { | |||||
// Update room state every 10 seconds | // Update room state every 10 seconds | ||||
window.roomRequest = setTimeout(() => { this.initSession() }, 10000) | window.roomRequest = setTimeout(() => { this.initSession() }, 10000) | ||||
break; | |||||
case '425': | |||||
// Missing/invalid password | |||||
if (init) { | |||||
$('#setup-password').addClass('is-invalid').focus() | |||||
} | |||||
break; | |||||
} | } | ||||
}) | }) | ||||
if (document.fullscreenEnabled) { | if (document.fullscreenEnabled) { | ||||
$('#meet-session-menu').find('.link-fullscreen.closed').removeClass('hidden') | $('#meet-session-menu').find('.link-fullscreen.closed').removeClass('hidden') | ||||
} | } | ||||
}, | }, | ||||
joinSession() { | joinSession() { | ||||
if (this.roomState == 423) { | if (this.roomState == 423) { | ||||
$('#meet-setup').addClass('hidden') | $('#meet-setup').addClass('hidden') | ||||
$('#meet-auth').removeClass('hidden') | $('#meet-auth').removeClass('hidden') | ||||
return | return | ||||
} | } | ||||
if (this.roomState == 424) { | if (this.roomState == 424 || this.roomState == 425) { | ||||
this.initSession(true) | this.initSession(true) | ||||
return | return | ||||
} | } | ||||
clearTimeout(window.roomRequest) | clearTimeout(window.roomRequest) | ||||
$('#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.nickname = this.nickname | this.session.nickname = this.nickname | ||||
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.onDestroy = event => { | this.session.onDestroy = event => { | ||||
// TODO: Handle nicely other reasons: disconnect, forceDisconnectByUser, | // TODO: Handle nicely other reasons: disconnect, forceDisconnectByUser, | ||||
// forceDisconnectByServer, networkDisconnect? | // forceDisconnectByServer, networkDisconnect? | ||||
if (event.reason == 'sessionClosedByServer' && !this.session.owner) { | if (event.reason == 'sessionClosedByServer' && !this.session.owner) { | ||||
$('#leave-dialog').on('hide.bs.modal', () => { | $('#leave-dialog').on('hide.bs.modal', () => { | ||||
Show All 16 Lines | export default { | ||||
window.location = window.config['app.url'] | window.location = window.config['app.url'] | ||||
}) | }) | ||||
} else { | } else { | ||||
this.meet.leaveRoom() | this.meet.leaveRoom() | ||||
this.meet = null | this.meet = null | ||||
window.location = window.config['app.url'] | window.location = window.config['app.url'] | ||||
} | } | ||||
}, | }, | ||||
securityOptions() { | |||||
$('#security-options-dialog').modal() | |||||
}, | |||||
setMenuItem(type, state, disabled) { | setMenuItem(type, state, disabled) { | ||||
let button = $('#meet-session-menu').find('.link-' + type) | let button = $('#meet-session-menu').find('.link-' + type) | ||||
button[state ? 'removeClass' : 'addClass']('text-danger') | button[state ? 'removeClass' : 'addClass']('text-danger') | ||||
if (disabled !== undefined) { | if (disabled !== undefined) { | ||||
button.prop('disabled', disabled) | button.prop('disabled', disabled) | ||||
} | } | ||||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | export default { | ||||
}, | }, | ||||
switchScreen() { | switchScreen() { | ||||
this.meet.switchScreen(enabled => { | this.meet.switchScreen(enabled => { | ||||
this.setMenuItem('screen', enabled) | this.setMenuItem('screen', enabled) | ||||
// After one screen sharing session ended request a new token | // After one screen sharing session ended request a new token | ||||
// for the next screen sharing session | // for the next screen sharing session | ||||
if (!enabled) { | if (!enabled) { | ||||
axios.get('/api/v4/openvidu/rooms/' + this.room, { ignoreErrors: true }) | // TODO: This might need to be a different route. E.g. the room password might have | ||||
// changed since user joined the session | |||||
axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { ignoreErrors: true }) | |||||
.then(response => { | .then(response => { | ||||
// Response data contains: session, token and shareToken | // Response data contains: session, token and shareToken | ||||
this.session.shareToken = response.data.token | this.session.shareToken = response.data.token | ||||
this.meet.updateSession(this.session) | this.meet.updateSession(this.session) | ||||
}) | }) | ||||
} | } | ||||
}) | }) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
</script> | </script> |