Checkpoint audio-only Skald fork work
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
// Copyright (c) 2024 by Juliusz Chroboczek.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
|
||||
let imageSegmenter;
|
||||
|
||||
async function loadImageSegmenter(model) {
|
||||
let module = await import('/third-party/tasks-vision/vision_bundle.mjs');
|
||||
let vision = await module.FilesetResolver.forVisionTasks(
|
||||
"/third-party/tasks-vision/wasm"
|
||||
);
|
||||
|
||||
return await module.ImageSegmenter.createFromOptions(vision, {
|
||||
baseOptions: {
|
||||
modelAssetPath: model,
|
||||
},
|
||||
outputCategoryMask: true,
|
||||
outputConfidenceMasks: false,
|
||||
runningMode: 'VIDEO',
|
||||
});
|
||||
}
|
||||
|
||||
async function foregroundMask(bitmap, timestamp) {
|
||||
if(!(bitmap instanceof ImageBitmap))
|
||||
throw new Error('Bad type for worker data');
|
||||
|
||||
try {
|
||||
let width = bitmap.width;
|
||||
let height = bitmap.height;
|
||||
let p = new Promise((resolve, reject) =>
|
||||
imageSegmenter.segmentForVideo(
|
||||
bitmap, timestamp,
|
||||
result => resolve(result),
|
||||
));
|
||||
let result = await p;
|
||||
/** @type{Uint8Array} */
|
||||
let mask = result.categoryMask.getAsUint8Array();
|
||||
let id = new ImageData(width, height);
|
||||
for(let i = 0; i < mask.length; i++)
|
||||
id.data[4 * i + 3] = mask[i];
|
||||
result.close();
|
||||
|
||||
let ib = await createImageBitmap(id);
|
||||
return {
|
||||
bitmap: bitmap,
|
||||
mask: ib,
|
||||
};
|
||||
} catch(e) {
|
||||
bitmap.close();
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
|
||||
onmessage = async e => {
|
||||
try {
|
||||
let data = e.data;
|
||||
if(data.model) {
|
||||
if(imageSegmenter)
|
||||
throw new Error("image segmenter already initialised");
|
||||
imageSegmenter = await loadImageSegmenter(data.model);
|
||||
if(!imageSegmenter)
|
||||
throw new Error("loadImageSegmenter returned null");
|
||||
postMessage(null);
|
||||
} else if(data.bitmap) {
|
||||
if(imageSegmenter == null)
|
||||
throw new Error("image segmenter not initialised");
|
||||
let mask = await foregroundMask(data.bitmap, data.timestamp);
|
||||
postMessage(mask, [mask.bitmap, mask.mask]);
|
||||
} else {
|
||||
throw new Error("unexpected message type");
|
||||
}
|
||||
} catch(e) {
|
||||
postMessage(e);
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ document.getElementById('passwordform').onsubmit = async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let parms = new URLSearchParams(window.location.search);
|
||||
let group = parms.get('group');
|
||||
if(!group) {
|
||||
displayError("Couldn't determine group");
|
||||
let hall = parms.get('hall');
|
||||
if(!hall) {
|
||||
displayError("Couldn't determine hall");
|
||||
return;
|
||||
}
|
||||
let user = parms.get('username');
|
||||
@@ -44,7 +44,7 @@ document.getElementById('passwordform').onsubmit = async function(e) {
|
||||
}
|
||||
|
||||
try {
|
||||
await setPassword(group, user, false, new1, old);
|
||||
await setPassword(hall, user, false, new1, old);
|
||||
document.getElementById('old').value = '';
|
||||
document.getElementById('new1').value = '';
|
||||
document.getElementById('new2').value = '';
|
||||
|
||||
@@ -7,15 +7,10 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div><button id="start">Start</button></p>
|
||||
<div><button id="start">Start</button></div>
|
||||
|
||||
<p id="status"></p>
|
||||
<p id="error"></p>
|
||||
|
||||
<div><button id="show" disabled>Show/hide yourself</button></div>
|
||||
|
||||
<div id="videos"></div>
|
||||
|
||||
<div id="chat"></div>
|
||||
|
||||
<script src="/protocol.js" defer></script>
|
||||
|
||||
+12
-177
@@ -1,4 +1,4 @@
|
||||
/* Skald client example. */
|
||||
/* Skald client example - audio-only. */
|
||||
|
||||
/**
|
||||
* The main function.
|
||||
@@ -6,7 +6,7 @@
|
||||
* @param {string} url
|
||||
*/
|
||||
async function start(url) {
|
||||
// fetch the group information
|
||||
// fetch the hall information
|
||||
let r = await fetch(url + ".status");
|
||||
if(!r.ok) {
|
||||
throw new Error(`${r.status} ${r.statusText}`);
|
||||
@@ -23,7 +23,7 @@ async function start(url) {
|
||||
if(token) {
|
||||
serverConnect(status, token);
|
||||
} else if(status.authPortal) {
|
||||
window.location.href = groupStatus.authPortal
|
||||
window.location.href = status.authPortal
|
||||
return;
|
||||
} else {
|
||||
serverConnect(status, null);
|
||||
@@ -33,7 +33,7 @@ async function start(url) {
|
||||
/**
|
||||
* Display the connection status.
|
||||
*
|
||||
* @parm {string} status
|
||||
* @param {string} status
|
||||
*/
|
||||
function displayStatus(status) {
|
||||
let c = document.getElementById('status');
|
||||
@@ -43,38 +43,29 @@ function displayStatus(status) {
|
||||
/**
|
||||
* Connect to the server.
|
||||
*
|
||||
* @parm {Object} status
|
||||
* @parm {string} token
|
||||
* @param {Object} status
|
||||
* @param {string} token
|
||||
*/
|
||||
function serverConnect(status, token) {
|
||||
// create the connection to the server
|
||||
let conn = new ServerConnection();
|
||||
conn.onconnected = async function() {
|
||||
displayStatus('Connected');
|
||||
let creds = token ?
|
||||
{type: 'token', token: token} :
|
||||
{type: 'password', password: ''};
|
||||
// join the group and wait for the onjoined callback
|
||||
await this.join("public", "example-user", creds);
|
||||
};
|
||||
conn.onchat = onChat;
|
||||
conn.onusermessage = onUserMessage;
|
||||
conn.ondownstream = onDownStream;
|
||||
conn.onclose = function() {
|
||||
displayStatus('Disconnected');
|
||||
}
|
||||
conn.onjoined = onJoined;
|
||||
|
||||
// connect and wait for the onconnected callback
|
||||
conn.connect(status.endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever we receive a chat message.
|
||||
*
|
||||
* @this {ServerConnection}
|
||||
* @parm {string} username
|
||||
* @parm {string} message
|
||||
*/
|
||||
function onChat(id, dest, username, time, privileged, history, kind, message) {
|
||||
let p = document.createElement('p');
|
||||
@@ -85,11 +76,6 @@ function onChat(id, dest, username, time, privileged, history, kind, message) {
|
||||
|
||||
/**
|
||||
* Called whenever we receive a user message.
|
||||
*
|
||||
* @this {ServerConnection}
|
||||
* @parm {string} username
|
||||
* @parm {string} message
|
||||
* @parm {string} kind
|
||||
*/
|
||||
function onUserMessage(id, dest, username, time, privileged, kind, error, message) {
|
||||
switch(kind) {
|
||||
@@ -113,57 +99,13 @@ function onUserMessage(id, dest, username, time, privileged, kind, error, messag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the camera stream, if any.
|
||||
*
|
||||
* @parm {string} conn
|
||||
* @returns {Stream}
|
||||
* Called when we join or leave a hall.
|
||||
*/
|
||||
function cameraStream(conn) {
|
||||
for(let id in conn.up) {
|
||||
let s = conn.up[id];
|
||||
if(s.label === 'camera')
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the show/hide button.
|
||||
*
|
||||
* @parm{ServerConnection} conn
|
||||
* @parm{boolean} enable
|
||||
*/
|
||||
function enableShow(conn, enable) {
|
||||
let b = /** @type{HTMLButtonElement} */(document.getElementById('show'));
|
||||
if(enable) {
|
||||
b.onclick = function() {
|
||||
let s = cameraStream(conn);
|
||||
if(!s)
|
||||
showCamera(conn);
|
||||
else
|
||||
hide(conn, s);
|
||||
}
|
||||
b.disabled = false;
|
||||
} else {
|
||||
b.disabled = true;
|
||||
b.onclick = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we join or leave a group.
|
||||
*
|
||||
* @this {ServerConnection}
|
||||
* @parm {string} kind
|
||||
* @parm {string} message}
|
||||
*/
|
||||
async function onJoined(kind, group, perms, status, data, error, message) {
|
||||
async function onJoined(kind, hall, perms, status, data, error, message) {
|
||||
switch(kind) {
|
||||
case 'fail':
|
||||
displayError(message);
|
||||
enableShow(this, false);
|
||||
this.close();
|
||||
break;
|
||||
case 'redirect':
|
||||
@@ -172,15 +114,13 @@ async function onJoined(kind, group, perms, status, data, error, message) {
|
||||
return;
|
||||
case 'leave':
|
||||
displayStatus('Connected');
|
||||
enableShow(this, false);
|
||||
this.close();
|
||||
break;
|
||||
case 'join':
|
||||
case 'change':
|
||||
displayStatus(`Connected as ${this.username} in group ${this.group}.`);
|
||||
enableShow(this, true);
|
||||
// request videos from the server
|
||||
this.request({'': ['audio', 'video']});
|
||||
displayStatus(`Connected as ${this.username} in hall ${this.hall}.`);
|
||||
// request audio from the server
|
||||
this.request({'': ['audio']});
|
||||
break;
|
||||
default:
|
||||
displayError(`Unexpected state ${kind}.`);
|
||||
@@ -189,115 +129,10 @@ async function onJoined(kind, group, perms, status, data, error, message) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a video element. We encode the stream's id in the element's id
|
||||
* in order to avoid having a global hash table that maps ids to video
|
||||
* elements.
|
||||
*
|
||||
* @parm {string} id
|
||||
* @returns {HTMLVideoElement}
|
||||
*/
|
||||
function makeVideoElement(id) {
|
||||
let v = document.createElement('video');
|
||||
v.id = 'video-' + id;
|
||||
let container = document.getElementById('videos');
|
||||
container.appendChild(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the video element that shows a given id.
|
||||
*
|
||||
* @parm {string} id
|
||||
* @returns {HTMLVideoElement}
|
||||
*/
|
||||
function getVideoElement(id) {
|
||||
let v = document.getElementById('video-' + id);
|
||||
return /** @type{HTMLVideoElement} */(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the camera and broadcast yourself to the group.
|
||||
*
|
||||
* @parm {ServerConnection} conn
|
||||
*/
|
||||
async function showCamera(conn) {
|
||||
let ms = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
|
||||
|
||||
/* Send the new stream to the server */
|
||||
let s = conn.newUpStream();
|
||||
s.label = 'camera';
|
||||
s.setStream(ms);
|
||||
let v = makeVideoElement(s.localId);
|
||||
s.onclose = function(replace) {
|
||||
s.stream.getTracks().forEach(t => t.stop());
|
||||
v.srcObject = null;
|
||||
v.parentNode.removeChild(v);
|
||||
}
|
||||
|
||||
function addTrack(t) {
|
||||
t.oneneded = function(e) {
|
||||
ms.onaddtrack = null;
|
||||
s.onremovetrack = null;
|
||||
s.close();
|
||||
}
|
||||
s.pc.addTransceiver(t, {
|
||||
direction: 'sendonly',
|
||||
streams: [ms],
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure all future tracks are added.
|
||||
s.onaddtrack = function(e) {
|
||||
addTrack(e.track);
|
||||
}
|
||||
// Add any existing tracks.
|
||||
ms.getTracks().forEach(addTrack);
|
||||
|
||||
// Connect the MediaStream to the video element and start playing.
|
||||
v.srcObject = ms;
|
||||
v.muted = true;
|
||||
v.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop broadcasting.
|
||||
*
|
||||
* @parm {ServerConnection} conn
|
||||
* @parm {Stream} s
|
||||
*/
|
||||
async function hide(conn, s) {
|
||||
s.stream.getTracks().forEach(t => t.stop());
|
||||
s.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server pushes a stream.
|
||||
*
|
||||
* @this {ServerConnection}
|
||||
* @parm {Stream} c
|
||||
*/
|
||||
function onDownStream(s) {
|
||||
s.onclose = function(replace) {
|
||||
let v = getVideoElement(s.localId);
|
||||
v.srcObject = null;
|
||||
v.parentNode.removeChild(v);
|
||||
}
|
||||
s.ondowntrack = function(track, transceiver, stream) {
|
||||
let v = getVideoElement(s.localId);
|
||||
if(v.srcObject !== stream)
|
||||
v.srcObject = stream;
|
||||
}
|
||||
|
||||
let v = makeVideoElement(s.localId);
|
||||
v.srcObject = s.stream;
|
||||
v.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error message.
|
||||
*
|
||||
* @parm {string} message
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayError(message) {
|
||||
document.getElementById('error').textContent = message;
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
<p id="errormessage"></p>
|
||||
|
||||
<div id="public-halls" class="groups">
|
||||
<div id="public-halls" class="halls">
|
||||
<h2>Public halls</h2>
|
||||
|
||||
<table id="public-halls-table"></table>
|
||||
|
||||
+2
-2
@@ -10,10 +10,10 @@ body {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.groups {
|
||||
.halls {
|
||||
}
|
||||
|
||||
.nogroups {
|
||||
.nohalls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
+21
-21
@@ -23,17 +23,17 @@
|
||||
document.getElementById('hallform').onsubmit = async function(e) {
|
||||
e.preventDefault();
|
||||
clearError();
|
||||
let groupinput = document.getElementById('hall')
|
||||
let hallInput = document.getElementById('hall')
|
||||
let button = document.getElementById('submitbutton');
|
||||
|
||||
let group = groupinput.value.trim();
|
||||
if(group === '')
|
||||
let hall = hallInput.value.trim();
|
||||
if(hall === '')
|
||||
return;
|
||||
let url = '/hall/' + group + '/';
|
||||
let url = '/hall/' + hall + '/';
|
||||
let statusUrl = url + '.status.json';
|
||||
|
||||
try {
|
||||
groupinput.disabled = true;
|
||||
hallInput.disabled = true;
|
||||
button.disabled = true;
|
||||
try {
|
||||
let resp = await fetch(statusUrl, {
|
||||
@@ -41,7 +41,7 @@ document.getElementById('hallform').onsubmit = async function(e) {
|
||||
});
|
||||
if(!resp.ok) {
|
||||
if(resp.status === 404)
|
||||
displayError('No such group');
|
||||
displayError('No such hall');
|
||||
else
|
||||
displayError(`The server said: ${resp.status} ${resp.statusText}`);
|
||||
return;
|
||||
@@ -51,7 +51,7 @@ document.getElementById('hallform').onsubmit = async function(e) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
groupinput.disabled = false;
|
||||
hallInput.disabled = false;
|
||||
button.disabled = false;
|
||||
}
|
||||
|
||||
@@ -90,38 +90,38 @@ async function listPublicHalls() {
|
||||
l = await r.json();
|
||||
} catch(e) {
|
||||
table.textContent = `Couldn't fetch halls: ${e}`;
|
||||
div.classList.remove('nogroups');
|
||||
div.classList.add('groups');
|
||||
div.classList.remove('nohalls');
|
||||
div.classList.add('halls');
|
||||
return;
|
||||
}
|
||||
|
||||
if (l.length === 0) {
|
||||
table.textContent = '(No halls found.)';
|
||||
div.classList.remove('groups');
|
||||
div.classList.add('nogroups');
|
||||
div.classList.remove('halls');
|
||||
div.classList.add('nohalls');
|
||||
return;
|
||||
}
|
||||
|
||||
div.classList.remove('nogroups');
|
||||
div.classList.add('groups');
|
||||
div.classList.remove('nohalls');
|
||||
div.classList.add('halls');
|
||||
|
||||
for(let i = 0; i < l.length; i++) {
|
||||
let group = l[i];
|
||||
let hall = l[i];
|
||||
let tr = document.createElement('tr');
|
||||
let td = document.createElement('td');
|
||||
let a = document.createElement('a');
|
||||
a.textContent = group.displayName || group.name;
|
||||
a.href = group.location;
|
||||
a.textContent = hall.displayName || hall.name;
|
||||
a.href = hall.location;
|
||||
td.appendChild(a);
|
||||
tr.appendChild(td);
|
||||
let td2 = document.createElement('td');
|
||||
if(group.description)
|
||||
td2.textContent = group.description;
|
||||
if(hall.description)
|
||||
td2.textContent = hall.description;
|
||||
tr.appendChild(td2);
|
||||
let td3 = document.createElement('td');
|
||||
if(!group.redirect) {
|
||||
let locked = group.locked ? ', locked' : '';
|
||||
td3.textContent = `(${group.clientCount} clients${locked})`;
|
||||
if(!hall.redirect) {
|
||||
let locked = hall.locked ? ', locked' : '';
|
||||
td3.textContent = `(${hall.clientCount} clients${locked})`;
|
||||
} else {
|
||||
td3.textContent = '(remote)';
|
||||
}
|
||||
|
||||
+55
-55
@@ -154,133 +154,133 @@ async function updateObject(url, values, etag) {
|
||||
}
|
||||
|
||||
/**
|
||||
* listGroups returns the list of groups.
|
||||
* listHalls returns the list of halls.
|
||||
*
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
async function listGroups() {
|
||||
async function listHalls() {
|
||||
return await listObjects('/skald-api/v0/.halls/');
|
||||
}
|
||||
|
||||
/**
|
||||
* getGroup returns the sanitised description of the given group.
|
||||
* getHall returns the sanitised description of the given hall.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} [etag]
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getGroup(group, etag) {
|
||||
return await getObject(`/skald-api/v0/.halls/${group}`, etag);
|
||||
async function getHall(hall, etag) {
|
||||
return await getObject(`/skald-api/v0/.halls/${hall}`, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* createGroup creates a group. It fails if the group already exists.
|
||||
* createHall creates a hall. It fails if the hall already exists.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {Object} [values]
|
||||
*/
|
||||
async function createGroup(group, values) {
|
||||
return await createObject(`/skald-api/v0/.halls/${group}`, values);
|
||||
async function createHall(hall, values) {
|
||||
return await createObject(`/skald-api/v0/.halls/${hall}`, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteGroup deletes a group.
|
||||
* deleteHall deletes a hall.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} [etag]
|
||||
*/
|
||||
async function deleteGroup(group, etag) {
|
||||
return await deleteObject(`/skald-api/v0/.halls/${group}`, etag);
|
||||
async function deleteHall(hall, etag) {
|
||||
return await deleteObject(`/skald-api/v0/.halls/${hall}`, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* updateGroup modifies a hall definition.
|
||||
* updateHall modifies a hall definition.
|
||||
* Any fields present in values are overriden, any fields absent in values
|
||||
* are left unchanged.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {Object} values
|
||||
* @param {string} [etag]
|
||||
*/
|
||||
async function updateGroup(group, values, etag) {
|
||||
return await updateObject(`/skald-api/v0/.halls/${group}`, values);
|
||||
async function updateHall(hall, values, etag) {
|
||||
return await updateObject(`/skald-api/v0/.halls/${hall}`, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* listUsers lists the users in a given group.
|
||||
* listUsers lists the users in a given hall.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
async function listUsers(group) {
|
||||
return await listObjects(`/skald-api/v0/.halls/${group}/.users/`);
|
||||
async function listUsers(hall) {
|
||||
return await listObjects(`/skald-api/v0/.halls/${hall}/.users/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* userURL returns the URL for a user entry
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {boolean} wildcard
|
||||
*/
|
||||
function userURL(group, user, wildcard) {
|
||||
function userURL(hall, user, wildcard) {
|
||||
if(wildcard)
|
||||
return `/skald-api/v0/.halls/${group}/.wildcard-user`;
|
||||
return `/skald-api/v0/.halls/${hall}/.wildcard-user`;
|
||||
else if(user === "")
|
||||
return `/skald-api/v0/.halls/${group}/.empty-user`;
|
||||
return `/skald-api/v0/.halls/${hall}/.empty-user`;
|
||||
else
|
||||
return `/skald-api/v0/.halls/${group}/.users/${user}`
|
||||
return `/skald-api/v0/.halls/${hall}/.users/${user}`
|
||||
}
|
||||
|
||||
/**
|
||||
* getUser returns a given user entry.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {boolean} wildcard
|
||||
* @param {string} [etag]
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getUser(group, user, wildcard, etag) {
|
||||
return await getObject(userURL(group, user, wildcard), etag);
|
||||
async function getUser(hall, user, wildcard, etag) {
|
||||
return await getObject(userURL(hall, user, wildcard), etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* createUser creates a new user entry. It fails if the user already
|
||||
* exists.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {boolean} wildcard
|
||||
* @param {Object} values
|
||||
*/
|
||||
async function createUser(group, user, wildcard, values) {
|
||||
return await createObject(userURL(group, user, wildcard), values);
|
||||
async function createUser(hall, user, wildcard, values) {
|
||||
return await createObject(userURL(hall, user, wildcard), values);
|
||||
}
|
||||
|
||||
/**
|
||||
* deleteUser deletes a user.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {boolean} wildcard
|
||||
* @param {string} [etag]
|
||||
*/
|
||||
async function deleteUser(group, user, wildcard, etag) {
|
||||
return await deleteObject(userURL(group, user, wildcard), etag);
|
||||
async function deleteUser(hall, user, wildcard, etag) {
|
||||
return await deleteObject(userURL(hall, user, wildcard), etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* updateUser modifies a given user entry.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {Object} values
|
||||
* @param {boolean} wildcard
|
||||
* @param {string} [etag]
|
||||
*/
|
||||
async function updateUser(group, user, wildcard, values, etag) {
|
||||
return await updateObject(userURL(group, user, wildcard), values, etag);
|
||||
async function updateUser(hall, user, wildcard, values, etag) {
|
||||
return await updateObject(userURL(hall, user, wildcard), values, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,13 +288,13 @@ async function updateUser(group, user, wildcard, values, etag) {
|
||||
* If oldpassword is provided, then it is used for authentication instead
|
||||
* of the browser's normal mechanism.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} user
|
||||
* @param {boolean} wildcard
|
||||
* @param {string} password
|
||||
* @param {string} [oldpassword]
|
||||
*/
|
||||
async function setPassword(group, user, wildcard, password, oldpassword) {
|
||||
async function setPassword(hall, user, wildcard, password, oldpassword) {
|
||||
let options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -308,42 +308,42 @@ async function setPassword(group, user, wildcard, password, oldpassword) {
|
||||
`Basic ${btoa(user + ':' + oldpassword)}`
|
||||
}
|
||||
|
||||
let r = await fetch(userURL(group, user, wildcard) + '/.password', options);
|
||||
let r = await fetch(userURL(hall, user, wildcard) + '/.password', options);
|
||||
if(!r.ok)
|
||||
throw httpError(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* listTokens lists the tokens for a given group.
|
||||
* listTokens lists the tokens for a given hall.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @returns {Promise<Array<string>>}
|
||||
*/
|
||||
async function listTokens(group) {
|
||||
return await listObjects(`/skald-api/v0/.halls/${group}/.tokens/`);
|
||||
async function listTokens(hall) {
|
||||
return await listObjects(`/skald-api/v0/.halls/${hall}/.tokens/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* getToken returns a given token.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {string} token
|
||||
* @param {string} [etag]
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getToken(group, token, etag) {
|
||||
return await getObject(`/skald-api/v0/.halls/${group}/.tokens/${token}`,
|
||||
async function getToken(hall, token, etag) {
|
||||
return await getObject(`/skald-api/v0/.halls/${hall}/.tokens/${token}`,
|
||||
etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* createToken creates a new token and returns its name
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {Object} template
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function createToken(group, template) {
|
||||
async function createToken(hall, template) {
|
||||
let options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -353,7 +353,7 @@ async function createToken(group, template) {
|
||||
}
|
||||
|
||||
let r = await fetch(
|
||||
`/skald-api/v0/.halls/${group}/.tokens/`,
|
||||
`/skald-api/v0/.halls/${hall}/.tokens/`,
|
||||
options);
|
||||
if(!r.ok)
|
||||
throw httpError(r);
|
||||
@@ -366,13 +366,13 @@ async function createToken(group, template) {
|
||||
/**
|
||||
* updateToken modifies a token.
|
||||
*
|
||||
* @param {string} group
|
||||
* @param {string} hall
|
||||
* @param {Object} token
|
||||
*/
|
||||
async function updateToken(group, token, etag) {
|
||||
async function updateToken(hall, token, etag) {
|
||||
if(!token.token)
|
||||
throw new Error("Unnamed token");
|
||||
return await updateObject(
|
||||
`/skald-api/v0/.halls/${group}/.tokens/${token.token}`,
|
||||
`/skald-api/v0/.halls/${hall}/.tokens/${token.token}`,
|
||||
token, etag);
|
||||
}
|
||||
|
||||
+32
-32
@@ -83,11 +83,11 @@ function ServerConnection() {
|
||||
*/
|
||||
this.id = newRandomId();
|
||||
/**
|
||||
* The group that we have joined, or null if we haven't joined yet.
|
||||
* The hall that we have joined, or null if we haven't joined yet.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = null;
|
||||
this.hall = null;
|
||||
/**
|
||||
* The username we joined as.
|
||||
*
|
||||
@@ -95,7 +95,7 @@ function ServerConnection() {
|
||||
*/
|
||||
this.username = null;
|
||||
/**
|
||||
* The set of users in this group, including ourself.
|
||||
* The set of users in this hall, including ourself.
|
||||
*
|
||||
* @type {Object<string,user>}
|
||||
*/
|
||||
@@ -186,19 +186,19 @@ function ServerConnection() {
|
||||
*/
|
||||
this.onpeerconnection = null;
|
||||
/**
|
||||
* onuser is called whenever a user in the group changes. The users
|
||||
* onuser is called whenever a user in the hall changes. The users
|
||||
* array has already been updated.
|
||||
*
|
||||
* @type{(this: ServerConnection, id: string, kind: string) => void}
|
||||
*/
|
||||
this.onuser = null;
|
||||
/**
|
||||
* onjoined is called whenever we join or leave a group or whenever the
|
||||
* permissions we have in a group change.
|
||||
* onjoined is called whenever we join or leave a hall or whenever the
|
||||
* permissions we have in a hall change.
|
||||
*
|
||||
* kind is one of 'join', 'fail', 'change' or 'leave'.
|
||||
*
|
||||
* @type{(this: ServerConnection, kind: string, group: string, permissions: Array<string>, status: Object<string,any>, data: Object<string,any>, error: string, message: string) => void}
|
||||
* @type{(this: ServerConnection, kind: string, hall: string, permissions: Array<string>, status: Object<string,any>, data: Object<string,any>, error: string, message: string) => void}
|
||||
*/
|
||||
this.onjoined = null;
|
||||
/**
|
||||
@@ -262,7 +262,7 @@ function ServerConnection() {
|
||||
* @property {Array<string>} [permissions]
|
||||
* @property {Object<string,any>} [status]
|
||||
* @property {Object<string,any>} [data]
|
||||
* @property {string} [group]
|
||||
* @property {string} [hall]
|
||||
* @property {unknown} [value]
|
||||
* @property {boolean} [noecho]
|
||||
* @property {string|number} [time]
|
||||
@@ -365,9 +365,9 @@ ServerConnection.prototype.connect = function(url) {
|
||||
if(sc.onuser)
|
||||
sc.onuser.call(sc, id, 'delete');
|
||||
}
|
||||
if(sc.group && sc.onjoined)
|
||||
sc.onjoined.call(sc, 'leave', sc.group, [], {}, {}, '', '');
|
||||
sc.group = null;
|
||||
if(sc.hall && sc.onjoined)
|
||||
sc.onjoined.call(sc, 'leave', sc.hall, [], {}, {}, '', '');
|
||||
sc.hall = null;
|
||||
sc.username = null;
|
||||
if(sc.pingHandler) {
|
||||
clearInterval(sc.pingHandler);
|
||||
@@ -432,19 +432,19 @@ ServerConnection.prototype.connect = function(url) {
|
||||
sc.permissions = [];
|
||||
sc.rtcConfiguration = null;
|
||||
} else if(m.kind === 'join' || m.kind == 'change') {
|
||||
if(m.kind === 'join' && sc.group) {
|
||||
throw new Error('Joined multiple groups');
|
||||
} else if(m.kind === 'change' && m.group != sc.group) {
|
||||
console.warn('join(change) for inconsistent group');
|
||||
if(m.kind === 'join' && sc.hall) {
|
||||
throw new Error('Joined multiple halls');
|
||||
} else if(m.kind === 'change' && m.hall != sc.hall) {
|
||||
console.warn('join(change) for inconsistent hall');
|
||||
break;
|
||||
}
|
||||
sc.group = m.group;
|
||||
sc.hall = m.hall;
|
||||
sc.username = m.username;
|
||||
sc.permissions = m.permissions || [];
|
||||
sc.rtcConfiguration = m.rtcConfiguration || null;
|
||||
}
|
||||
if(sc.onjoined)
|
||||
sc.onjoined.call(sc, m.kind, m.group,
|
||||
sc.onjoined.call(sc, m.kind, m.hall,
|
||||
m.permissions || [],
|
||||
m.status, m.data,
|
||||
m.error || null, m.value || null);
|
||||
@@ -546,19 +546,19 @@ function parseTime(value) {
|
||||
}
|
||||
|
||||
/**
|
||||
* join requests to join a group. The onjoined callback will be called
|
||||
* join requests to join a hall. The onjoined callback will be called
|
||||
* when we've effectively joined.
|
||||
*
|
||||
* @param {string} group - The name of the group to join.
|
||||
* @param {string} hall - The name of the hall to join.
|
||||
* @param {string} username - the username to join as.
|
||||
* @param {string|Object} credentials - password or authServer.
|
||||
* @param {Object<string,any>} [data] - the initial associated data.
|
||||
*/
|
||||
ServerConnection.prototype.join = async function(group, username, credentials, data) {
|
||||
ServerConnection.prototype.join = async function(hall, username, credentials, data) {
|
||||
let m = {
|
||||
type: 'join',
|
||||
kind: 'join',
|
||||
group: group,
|
||||
hall: hall,
|
||||
};
|
||||
if(typeof username !== 'undefined' && username !== null)
|
||||
m.username = username;
|
||||
@@ -629,16 +629,16 @@ ServerConnection.prototype.join = async function(group, username, credentials, d
|
||||
};
|
||||
|
||||
/**
|
||||
* leave leaves a group. The onjoined callback will be called when we've
|
||||
* leave leaves a hall. The onjoined callback will be called when we've
|
||||
* effectively left.
|
||||
*
|
||||
* @param {string} group - The name of the group to join.
|
||||
* @param {string} hall - The name of the hall to join.
|
||||
*/
|
||||
ServerConnection.prototype.leave = function(group) {
|
||||
ServerConnection.prototype.leave = function(hall) {
|
||||
this.send({
|
||||
type: 'join',
|
||||
kind: 'leave',
|
||||
group: group,
|
||||
hall: hall,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -646,8 +646,8 @@ ServerConnection.prototype.leave = function(group) {
|
||||
* request sets the list of requested tracks
|
||||
*
|
||||
* @param {Object<string,Array<string>>} what
|
||||
* - A dictionary that maps labels to a sequence of 'audio', 'video'
|
||||
* or 'video-low. An entry with an empty label '' provides the default.
|
||||
* - A dictionary that maps labels to a sequence of 'audio'
|
||||
* . An entry with an empty label '' provides the default.
|
||||
*/
|
||||
ServerConnection.prototype.request = function(what) {
|
||||
this.send({
|
||||
@@ -809,14 +809,14 @@ ServerConnection.prototype.userMessage = function(kind, dest, value, noecho) {
|
||||
};
|
||||
|
||||
/**
|
||||
* groupAction sends a request to act on the current group.
|
||||
* hallAction sends a request to act on the current hall.
|
||||
*
|
||||
* @param {string} kind
|
||||
* @param {any} [data]
|
||||
*/
|
||||
ServerConnection.prototype.groupAction = function(kind, data) {
|
||||
ServerConnection.prototype.hallAction = function(kind, data) {
|
||||
this.send({
|
||||
type: 'groupaction',
|
||||
type: 'hallaction',
|
||||
source: this.id,
|
||||
kind: kind,
|
||||
username: this.username,
|
||||
@@ -1459,7 +1459,7 @@ Stream.prototype.restartIce = function () {
|
||||
* request sets the list of tracks. If this is not called, or called with
|
||||
* a null argument, then the default is provided by ServerConnection.request.
|
||||
*
|
||||
* @param {Array<string>} what - a sequence of 'audio', 'video' or 'video-low'.
|
||||
* @param {Array<string>} what - a sequence of 'audio' .
|
||||
*/
|
||||
Stream.prototype.request = function(what) {
|
||||
let c = this;
|
||||
@@ -1501,7 +1501,7 @@ Stream.prototype.updateStats = async function() {
|
||||
if(stid && r.type === 'outbound-rtp') {
|
||||
let id = stid;
|
||||
// Firefox doesn't implement rid, use ssrc
|
||||
// to discriminate simulcast tracks.
|
||||
// to discriminate RTP tracks.
|
||||
id = id + '-' + r.ssrc;
|
||||
if(!('bytesSent' in r))
|
||||
continue;
|
||||
|
||||
@@ -466,17 +466,9 @@ textarea.form-reply {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
height: calc(var(--vh, 1vh) * 100 - 56px);
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.91);
|
||||
/* Display only when showing video */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chat-btn {
|
||||
display: block;
|
||||
/*on top of video peers*/
|
||||
z-index: 1002;
|
||||
font-size: 1.8em;
|
||||
position: absolute;
|
||||
@@ -492,37 +484,9 @@ textarea.form-reply {
|
||||
text-shadow: 0px 0px 1px #b3adad;
|
||||
}
|
||||
|
||||
.collapse-video {
|
||||
left: inherit;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.video-controls, .top-video-controls {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 25px;
|
||||
text-align: center;
|
||||
color: #eaeaea;
|
||||
font-size: 1.1em;
|
||||
opacity: 0;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.video-controls:after, .top-video-controls:after {
|
||||
clear: both;
|
||||
display: table;
|
||||
content: " ";
|
||||
}
|
||||
|
||||
.top-video-controls {
|
||||
text-align: right;
|
||||
bottom: inherit;
|
||||
top: 0;
|
||||
line-height: 1.1;
|
||||
font-size: 1.3em;
|
||||
text-shadow: 1px 1px 2px rgb(90 86 86);
|
||||
}
|
||||
|
||||
.controls-button {
|
||||
padding: 3px 10px;
|
||||
@@ -544,54 +508,14 @@ textarea.form-reply {
|
||||
background: linear-gradient(180deg, rgb(0 0 0 / 20%) 0%, rgb(0 0 0 / 50%) 0%, rgb(0 0 0 / 70%) 100%);
|
||||
}
|
||||
|
||||
.peer:hover > .video-controls, .peer:hover > .top-video-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.video-controls span, .top-video-controls span {
|
||||
margin-right: 20px;
|
||||
transition: opacity .7s ease-out;
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-controls span:last-child, .top-video-controls span:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.video-controls span:hover, .top-video-controls span:hover {
|
||||
opacity: .8;
|
||||
transition: opacity .5s ease-out;
|
||||
}
|
||||
|
||||
.top-video-controls .video-stop{
|
||||
display: flex;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
background: rgba(0,0,0,0.5);
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #eaeaea;
|
||||
}
|
||||
|
||||
.video-controls .volume {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video-controls .video-play {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.video-controls .video-stop {
|
||||
color: #d03e3e;
|
||||
}
|
||||
|
||||
.video-controls span.disabled, .video-controls span.disabled:hover, .top-video-controls span.disabled:hover{
|
||||
opacity: .2;
|
||||
color: #c8c8c8
|
||||
}
|
||||
|
||||
.volume-mute {
|
||||
vertical-align: middle;
|
||||
@@ -609,10 +533,6 @@ textarea.form-reply {
|
||||
transition: opacity .5s ease-out;
|
||||
}
|
||||
|
||||
.video-controls .volume:hover {
|
||||
--ov: 1;
|
||||
--dv: inline;
|
||||
}
|
||||
|
||||
.mobile-container {
|
||||
display: block !important;
|
||||
@@ -794,38 +714,16 @@ h1 {
|
||||
width: 5.8em;
|
||||
}
|
||||
|
||||
#videoselect {
|
||||
text-align-last: center;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
#audioselect {
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
#sharebutton, #unsharebutton {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#unsharebutton {
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
#filterselect {
|
||||
text-align-last: center;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
#sendselect {
|
||||
text-align-last: center;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
#simulcastselect {
|
||||
text-align-last: center;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
#requestselect {
|
||||
text-align-last: center;
|
||||
}
|
||||
@@ -910,58 +808,6 @@ h1 {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
#peers {
|
||||
padding: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
grid-template-rows: repeat(1, auto);
|
||||
row-gap: 5px;
|
||||
column-gap: 10px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: 1000;
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
vertical-align: top!important;
|
||||
}
|
||||
|
||||
.peer {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
position: relative;
|
||||
border: 2px solid rgba(0,0,0,0);
|
||||
background: #80808014;
|
||||
}
|
||||
|
||||
.peer-active {
|
||||
border: 2px solid #610a86;
|
||||
}
|
||||
|
||||
.peer-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.media {
|
||||
width: 100%;
|
||||
max-height: calc(var(--vh, 1vh) * 100 - 76px);
|
||||
padding-bottom: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.media-failed {
|
||||
filter: grayscale(0.5) contrast(0.5);
|
||||
}
|
||||
|
||||
.mirror {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
#inputform {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1066,20 +912,6 @@ legend {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.show-video {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 120px;
|
||||
color: white;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-size: 150%;
|
||||
border-radius: 30px;
|
||||
background: #600aa0;
|
||||
box-shadow: 4px 4px 7px 1px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
.blink {
|
||||
-ms-animation: blink 1.0s linear infinite;
|
||||
@@ -1210,9 +1042,6 @@ header .collapse:hover {
|
||||
content: "\f130";
|
||||
}
|
||||
|
||||
#users > div.user-status-camera::after {
|
||||
content: "\f030";
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font: normal 1em/1 Arial, sans-serif;
|
||||
@@ -1223,11 +1052,6 @@ header .collapse:hover {
|
||||
|
||||
/* END Sidebar Left */
|
||||
|
||||
@media only screen and (min-width: 1025px) {
|
||||
.coln-right .collapse-video, .coln-right .show-video {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
#presentbutton, #unpresentbutton {
|
||||
@@ -1259,18 +1083,7 @@ header .collapse:hover {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: fixed;
|
||||
height: calc(var(--vh, 1vh) * 100 - 56px);
|
||||
top: 56px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.top-video-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
position: fixed;
|
||||
@@ -1304,7 +1117,6 @@ header .collapse:hover {
|
||||
#left-sidebar.active {
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
/* on top of video peers */
|
||||
z-index: 1002;
|
||||
}
|
||||
|
||||
@@ -1336,10 +1148,6 @@ header .collapse:hover {
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
#peers {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+6
-95
@@ -37,12 +37,12 @@
|
||||
|
||||
<ul class="nav-menu">
|
||||
<li>
|
||||
<button id="presentbutton" class="invisible btn btn-success" aria-label="Enable camera and microphone">
|
||||
<button id="presentbutton" class="invisible btn btn-success" aria-label="Enable microphone">
|
||||
<i class="fas fa-play" aria-hidden="true"></i><span class="nav-text"> Enable</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="unpresentbutton" class="invisible btn btn-cancel" aria-label="Disable camera and microphone">
|
||||
<button id="unpresentbutton" class="invisible btn btn-cancel" aria-label="Disable microphone">
|
||||
<i class="fas fa-stop" aria-hidden="true"></i><span class="nav-text"> Disable</span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -52,12 +52,7 @@
|
||||
<span class="nav-text">Mute</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="sharebutton" class="invisible nav-link nav-button" aria-label="Share screen">
|
||||
<span><i class="fas fa-share-square" aria-hidden="true"></i></span>
|
||||
<span class="nav-text">Share Screen</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<button class="nav-button nav-link nav-more" id="openside" aria-label="More options" aria-haspopup="true">
|
||||
<span><i class="fas fa-ellipsis-v" aria-hidden="true"></i></span>
|
||||
@@ -87,20 +82,9 @@
|
||||
</div>
|
||||
<div id="resizer" tabindex="0" role="separator" aria-label="Chat resize handle" aria-orientation="vertical"></div>
|
||||
<div class="coln-right" id="right">
|
||||
<button class="show-video blink invisible" id="show-video" aria-label="Show video">
|
||||
<i class="fas fa-exchange-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="chat-btn show-chat invisible" id="show-chat" aria-label="Show chat">
|
||||
<i class="far fa-comment-alt icon-chat" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="chat-btn collapse-video invisible" id="collapse-video" aria-label="Hide video and show chat">
|
||||
<i class="far fa-comment-alt icon-chat" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="video-container invisible" id="video-container">
|
||||
<div id="expand-video" class="expand-video">
|
||||
<div id="peers"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="captions-container" class="invisible">
|
||||
<div id="captions"></div>
|
||||
</div>
|
||||
@@ -128,10 +112,6 @@
|
||||
<input id="presentmike" type="radio" name="presentradio" value="mike"/>
|
||||
<label for="presentmike">Microphone</label>
|
||||
</p>
|
||||
<p class="switch-radio">
|
||||
<input id="presentboth" type="radio" name="presentradio" value="both"/>
|
||||
<label for="presentboth">Camera and microphone</label>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="clear"></div>
|
||||
@@ -175,25 +155,10 @@
|
||||
<div id="mediaoptions" class="invisible">
|
||||
<fieldset>
|
||||
<legend>Media Options</legend>
|
||||
<label for="videoselect" class="sidenav-label-first">Camera:</label>
|
||||
<select id="videoselect" class="select select-inline">
|
||||
<option value="">off</option>
|
||||
</select>
|
||||
|
||||
<label for="audioselect" class="sidenav-label">Microphone:</label>
|
||||
<label for="audioselect" class="sidenav-label-first">Microphone:</label>
|
||||
<select id="audioselect" class="select select-inline">
|
||||
<option value="">off</option>
|
||||
</select>
|
||||
|
||||
<form>
|
||||
<input id="mirrorbox" type="checkbox" checked/>
|
||||
<label for="mirrorbox">Mirror view</label>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<input id="blackboardbox" type="checkbox"/>
|
||||
<label for="blackboardbox">Blackboard mode</label>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<input id="preprocessingbox" type="checkbox" checked/>
|
||||
@@ -211,13 +176,6 @@
|
||||
<fieldset>
|
||||
<legend>Other Settings</legend>
|
||||
|
||||
<form id="filterform">
|
||||
<label for="filterselect" class="sidenav-label-first">Filter:</label>
|
||||
<select id="filterselect" class="select select-inline">
|
||||
<option value="" selected>none</option>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<form id="sendform">
|
||||
<label for="sendselect" class="sidenav-label-first">Send:</label>
|
||||
<select id="sendselect" class="select select-inline">
|
||||
@@ -228,23 +186,11 @@
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<form id="simulcastform">
|
||||
<label for="simulcastselect" class="sidenav-label-first">Simulcast:</label>
|
||||
<select id="simulcastselect" class="select select-inline">
|
||||
<option value="off">off</option>
|
||||
<option value="auto" selected>auto</option>
|
||||
<option value="on">on</option>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<form id="requestform">
|
||||
<label for="requestselect" class="sidenav-label">Receive:</label>
|
||||
<select id="requestselect" class="select select-inline">
|
||||
<option value="">nothing</option>
|
||||
<option value="audio">audio only</option>
|
||||
<option value="screenshare">screenshare only</option>
|
||||
<option value="everything-low">low quality</option>
|
||||
<option value="everything" selected>everything</option>
|
||||
<option value="audio" selected>audio only</option>
|
||||
</select>
|
||||
</form>
|
||||
|
||||
@@ -252,46 +198,11 @@
|
||||
<input id="activitybox" type="checkbox"/>
|
||||
<label for="activitybox">Activity detection</label>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<input id="displayallbox" type="checkbox"/>
|
||||
<label for="displayallbox">Display audio-only users</label>
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="videocontrols-template" class="invisible">
|
||||
<div class="video-controls vc-overlay">
|
||||
<div class="controls-button controls-left">
|
||||
<button class="video-play" aria-label="Play video">
|
||||
<i class="fas fa-play" aria-hidden="true"></i>
|
||||
</button>
|
||||
<span class="volume">
|
||||
<button class="volume-mute" aria-label="Mute volume">
|
||||
<i class="fas fa-volume-up" aria-hidden="true"></i>
|
||||
</button>
|
||||
<input class="volume-slider" type="range" max="100" value="100" min="0" step="5" aria-label="Volume">
|
||||
</span>
|
||||
</div>
|
||||
<div class="controls-button controls-right">
|
||||
<button class="pip" aria-label="Picture in picture">
|
||||
<i class="far fa-clone" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="fullscreen" aria-label="Fullscreen">
|
||||
<i class="fas fa-expand" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="topvideocontrols-template" class="invisible">
|
||||
<div class="top-video-controls">
|
||||
<div class="controls-button controls-right">
|
||||
<button class="close-icon video-stop" aria-label="Stop video">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<dialog id="invite-dialog">
|
||||
<form method="dialog">
|
||||
|
||||
+115
-1319
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -36,23 +36,23 @@ async function listStats() {
|
||||
}
|
||||
|
||||
if(l.length === 0) {
|
||||
table.textContent = '(No group found.)';
|
||||
table.textContent = '(No hall found.)';
|
||||
return;
|
||||
}
|
||||
|
||||
for(let i = 0; i < l.length; i++)
|
||||
formatGroup(table, l[i]);
|
||||
formatHall(table, l[i]);
|
||||
}
|
||||
|
||||
function formatGroup(table, group) {
|
||||
function formatHall(table, hall) {
|
||||
let tr = document.createElement('tr');
|
||||
let td = document.createElement('td');
|
||||
td.textContent = group.name;
|
||||
td.textContent = hall.name;
|
||||
tr.appendChild(td);
|
||||
table.appendChild(tr);
|
||||
if(group.clients) {
|
||||
for(let i = 0; i < group.clients.length; i++) {
|
||||
let client = group.clients[i];
|
||||
if(hall.clients) {
|
||||
for(let i = 0; i < hall.clients.length; i++) {
|
||||
let client = hall.clients[i];
|
||||
let tr2 = document.createElement('tr');
|
||||
tr2.appendChild(document.createElement('td'));
|
||||
let td2 = document.createElement('td');
|
||||
|
||||
Reference in New Issue
Block a user