1
Fork 0
mirror of https://github.com/jech/galene.git synced 2024-11-25 01:55:57 +01:00
galene/static/example/example.js
Juliusz Chroboczek 7151fad149 Simplify the initial connection protocol.
The ServerConnection.connect method is no longer async,
we rely on the onconnected callback only.  The onconnected
callback is now only called after the initial handshake
completes.  There is a new onerror callback.
2024-06-10 21:12:47 +02:00

314 lines
7.4 KiB
JavaScript

/* Galene client example. */
/**
* The main function.
*
* @param {string} url
*/
async function start(url) {
// fetch the group information
let r = await fetch(url + ".status");
if(!r.ok) {
throw new Error(`${r.status} ${r.statusText}`);
}
let status = await r.json();
// parse a token in the URL.
let token = null;
let parms = new URLSearchParams(window.location.search);
if(parms.has('token'))
token = parms.get('token');
// connect to the server
if(token) {
serverConnect(status, token);
} else if(status.authPortal) {
window.location.href = groupStatus.authPortal
return;
} else {
serverConnect(status, null);
}
}
/**
* Display the connection status.
*
* @parm {string} status
*/
function displayStatus(status) {
let c = document.getElementById('status');
c.textContent = status;
}
/**
* Connect to the server.
*
* @parm {Object} status
* @parm {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');
p.textContent = `${username}${dest ? ' → ' + dest : ''}: ${message}`;
let container = document.getElementById('chat');
container.appendChild(p);
}
/**
* 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) {
case 'kicked':
case 'error':
case 'warning':
case 'info':
if(!privileged) {
console.error(`Got unprivileged message of kind ${kind}`);
return;
}
displayError(message);
break;
case 'clearchat':
if(!privileged) {
console.error(`Got unprivileged message of kind ${kind}`);
return;
}
document.getElementById('chat').textContent = '';
break;
}
}
/**
* Find the camera stream, if any.
*
* @parm {string} conn
* @returns {Stream}
*/
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) {
switch(kind) {
case 'fail':
displayError(message);
enableShow(this, false);
this.close();
break;
case 'redirect':
this.close();
document.location.href = 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']});
break;
default:
displayError(`Unexpected state ${kind}.`);
this.close();
break;
}
}
/**
* 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
*/
function displayError(message) {
document.getElementById('error').textContent = message;
}
document.getElementById('start').onclick = async function(e) {
let button = /** @type{HTMLButtonElement} */(this);
button.hidden = true;
try {
await start("/group/public/");
} catch(e) {
displayError(e);
};
}