Changeset View
Changeset View
Standalone View
Standalone View
src/resources/vue/Meet/Room.vue
Show First 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | <div id="meet-component"> | ||||
</div> | </div> | ||||
<div class="input-group mt-2" v-if="session.config && session.config.requires_password"> | <div class="input-group mt-2" v-if="session.config && session.config.requires_password"> | ||||
<label for="setup-password" class="input-group-prepend mb-0"> | <label for="setup-password" class="input-group-prepend mb-0"> | ||||
<span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span> | <span class="input-group-text" title="Password"><svg-icon icon="key"></svg-icon></span> | ||||
</label> | </label> | ||||
<input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password"> | <input type="password" class="form-control" id="setup-password" v-model="password" placeholder="Password"> | ||||
</div> | </div> | ||||
<div class="mt-3"> | <div class="mt-3"> | ||||
<button v-if="roomState == 'ready' || roomState == 424 || roomState == 425" | <button type="button" | ||||
type="button" | |||||
@click="joinSession" | @click="joinSession" | ||||
:class="'btn w-100 btn-' + (roomState == 'ready' ? 'success' : 'primary')" | :disabled="roomState == 'init' || roomState == 427 || roomState == 404" | ||||
>JOIN</button> | :class="'btn w-100 btn-' + (isRoomReady() ? 'success' : 'primary')" | ||||
<button v-if="roomState == 423" | > | ||||
type="button" | <span v-if="isRoomReady()">JOIN NOW</span> | ||||
@click="joinSession" | <span v-else-if="roomState == 423">I'm the owner</span> | ||||
class="btn btn-primary w-100" | <span v-else>JOIN</span> | ||||
>I'm the owner</button> | </button> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="mt-4 col-sm-12"> | <div class="mt-4 col-sm-12"> | ||||
<status-message :status="roomState" :status-labels="roomStateLabels"></status-message> | <status-message :status="roomState" :status-labels="roomStateLabels"></status-message> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | export default { | ||||
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.', | 425: 'The room is ready. Please, provide a valid password.', | ||||
426: 'The room is locked. Please, enter your name and try again.', | |||||
427: 'Waiting for permission to join the room.', | |||||
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 11 Lines | export default { | ||||
clearTimeout(window.roomRequest) | clearTimeout(window.roomRequest) | ||||
if (this.meet) { | if (this.meet) { | ||||
this.meet.leaveRoom() | this.meet.leaveRoom() | ||||
} | } | ||||
}, | }, | ||||
methods: { | methods: { | ||||
authSuccess() { | authSuccess() { | ||||
// The user (owner) authentication succeeded | // The user authentication succeeded, we still don't know it's really the room owner | ||||
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) { | configUpdate(config) { | ||||
this.session.config = Object.assign({}, this.session.config, config) | this.session.config = Object.assign({}, this.session.config, config) | ||||
}, | }, | ||||
initSession(init) { | initSession(init) { | ||||
this.post = { | this.post = { | ||||
password: this.password, | password: this.password, | ||||
nickname: this.nickname, | nickname: this.nickname, | ||||
screenShare: this.canShareScreen ? 1 : 0, | screenShare: this.canShareScreen ? 1 : 0, | ||||
init: init ? 1 : 0 | init: init ? 1 : 0, | ||||
picture: init ? this.makePicture() : '', | |||||
requestId: this.requestId() | |||||
} | } | ||||
$('#setup-password').removeClass('is-invalid') | $('#setup-password,#setup-nickname').removeClass('is-invalid') | ||||
axios.post('/api/v4/openvidu/rooms/' + this.room, this.post, { 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) { | ||||
Show All 15 Lines | export default { | ||||
break; | break; | ||||
case '425': | case '425': | ||||
// Missing/invalid password | // Missing/invalid password | ||||
if (init) { | if (init) { | ||||
$('#setup-password').addClass('is-invalid').focus() | $('#setup-password').addClass('is-invalid').focus() | ||||
} | } | ||||
break; | break; | ||||
case '426': | |||||
// Locked room prerequisites error | |||||
if (init && !$('#setup-nickname').val()) { | |||||
$('#setup-nickname').addClass('is-invalid').focus() | |||||
} | |||||
break; | |||||
case '427': | |||||
// Waiting for the owner's approval to join | |||||
// Update room state every 10 seconds | |||||
window.roomRequest = setTimeout(() => { this.initSession(true) }, 10000) | |||||
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') | ||||
} | } | ||||
}, | }, | ||||
isRoomReady() { | |||||
return ['ready', '424', '425', '426', '427'].includes(this.roomState) | |||||
}, | |||||
// An event received by the room owner when a participant is asking for a permission to join the room | |||||
joinRequest(data) { | |||||
// The toast for this user request already exists, ignore | |||||
// It's not really needed as we do this on server-side already | |||||
if ($('#i' + data.requestId).length) { | |||||
return | |||||
} | |||||
// FIXME: Should the message close button act as the Deny button? Do we need the Deny button? | |||||
let body = $( | |||||
`<div>` | |||||
+ `<div class="picture"><img src="${data.picture}"></div>` | |||||
+ `<div class="content">` | |||||
+ `<p class="mb-2"></p>` | |||||
+ `<div class="text-right">` | |||||
+ `<button type="button" class="btn btn-sm btn-success accept">Accept</button>` | |||||
+ `<button type="button" class="btn btn-sm btn-danger deny ml-2">Deny</button>` | |||||
) | |||||
this.$toast.message({ | |||||
className: 'join-request', | |||||
icon: 'user', | |||||
timeout: 0, | |||||
title: 'Join request', | |||||
// titleClassName: '', | |||||
body: body.html(), | |||||
onShow: element => { | |||||
const id = data.requestId | |||||
$(element).find('p').text((data.nickname || '') + ' requested to join.') | |||||
// add id attribute, so we can identify it | |||||
$(element).attr('id', 'i' + id) | |||||
// add action to the buttons | |||||
.find('button.accept,button.deny').on('click', e => { | |||||
const action = $(e.target).is('.accept') ? 'accept' : 'deny' | |||||
axios.post('/api/v4/openvidu/rooms/' + this.room + '/request/' + id + '/' + action) | |||||
.then(response => { | |||||
$('#i' + id).remove() | |||||
}) | |||||
}) | |||||
} | |||||
}) | |||||
}, | |||||
// Entering the room | |||||
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 || this.roomState == 425) { | if (this.roomState != 'ready') { | ||||
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') | ||||
Show All 14 Lines | export default { | ||||
// FIXME: Where exactly the user should land? Currently he'll land | // FIXME: Where exactly the user should land? Currently he'll land | ||||
// on dashboard (if he's logged in) or login form (if he's not). | // on dashboard (if he's logged in) or login form (if he's not). | ||||
window.location = window.config['app.url'] | window.location = window.config['app.url'] | ||||
}).modal() | }).modal() | ||||
} | } | ||||
} | } | ||||
if (this.session.owner) { | |||||
this.session.onJoinRequest = data => { this.joinRequest(data) } | |||||
} | |||||
this.meet.joinRoom(this.session) | this.meet.joinRoom(this.session) | ||||
}, | }, | ||||
logout() { | logout() { | ||||
if (this.session.owner) { | const logout = () => { | ||||
axios.post('/api/v4/openvidu/rooms/' + this.room + '/close') | |||||
.then(response => { | |||||
this.meet.leaveRoom() | this.meet.leaveRoom() | ||||
this.meet = null | this.meet = null | ||||
window.location = window.config['app.url'] | window.location = window.config['app.url'] | ||||
}) | } | ||||
if (this.session.owner) { | |||||
axios.post('/api/v4/openvidu/rooms/' + this.room + '/close').then(logout) | |||||
} else { | } else { | ||||
this.meet.leaveRoom() | logout() | ||||
this.meet = null | } | ||||
window.location = window.config['app.url'] | }, | ||||
makePicture() { | |||||
const video = $("#setup-preview video")[0]; | |||||
// Skip if video is not "playing" | |||||
if (!video.videoWidth || !this.camera) { | |||||
return '' | |||||
} | } | ||||
// we're going to crop a square from the video and resize it | |||||
const maxSize = 64 | |||||
// Calculate sizing | |||||
let sh = Math.floor(video.videoHeight / 1.5) | |||||
let sw = sh | |||||
let sx = (video.videoWidth - sw) / 2 | |||||
let sy = (video.videoHeight - sh) / 2 | |||||
let dh = Math.min(sh, maxSize) | |||||
let dw = sh < maxSize ? sw : Math.floor(sw * dh/sh) | |||||
const canvas = $("<canvas>")[0]; | |||||
canvas.width = dw; | |||||
canvas.height = dh; | |||||
// draw the image on the canvas (square cropped and resized) | |||||
canvas.getContext('2d').drawImage(video, sx, sy, sw, sh, 0, 0, dw, dh); | |||||
// convert it to a usable data URL (png format) | |||||
return canvas.toDataURL(); | |||||
}, | |||||
requestId() { | |||||
if (!this.reqId) { | |||||
// FIXME: Shall we use some UUID generator? Or better something that identifies the | |||||
// user/browser so we could deny the join request for a longer time. | |||||
// I'm thinking about e.g. a bad actor knocking again and again and again, | |||||
// we don't want the room owner to be bothered every few seconds. | |||||
// Maybe a solution would be to store the identifier in the browser storage | |||||
// This would not prevent hackers from sending the new identifier on every request, | |||||
// but could make sure that it is kept after page refresh for the avg user. | |||||
// This will create max. 24-char numeric string | |||||
this.reqId = (String(Date.now()) + String(Math.random()).substring(2)).substring(0, 24) | |||||
} | |||||
return this.reqId | |||||
}, | }, | ||||
securityOptions() { | securityOptions() { | ||||
$('#security-options-dialog').modal() | $('#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') | ||||
▲ Show 20 Lines • Show All 93 Lines • Show Last 20 Lines |