Latest code. Working voice, still needs some ui cleanup and accessibility fixes but in decent shape.
This commit is contained in:
+6
-4
@@ -70,7 +70,8 @@
|
||||
</button>
|
||||
<h2 id="chat-history-heading" class="sr-only">Chat History</h2>
|
||||
<div id="box"></div>
|
||||
<div id="chat-announcements" aria-live="polite" aria-atomic="true" class="sr-only"></div>
|
||||
<div id="chat-announcements" role="status" aria-live="polite" aria-atomic="true" class="sr-only"></div>
|
||||
<div id="audio-streams" aria-hidden="true"></div>
|
||||
<div class="reply">
|
||||
<form id="inputform">
|
||||
<textarea id="input" class="form-reply" aria-label="Message or /command"></textarea>
|
||||
@@ -105,11 +106,11 @@
|
||||
<legend>Enable at start:</legend>
|
||||
<div class="present-switch">
|
||||
<p class="switch-radio">
|
||||
<input id="presentoff" type="radio" name="presentradio" value="" checked/>
|
||||
<input id="presentoff" type="radio" name="presentradio" value=""/>
|
||||
<label for="presentoff">Nothing</label>
|
||||
</p>
|
||||
<p class="switch-radio">
|
||||
<input id="presentmike" type="radio" name="presentradio" value="mike"/>
|
||||
<input id="presentmike" type="radio" name="presentradio" value="mike" checked/>
|
||||
<label for="presentmike">Microphone</label>
|
||||
</p>
|
||||
</div>
|
||||
@@ -157,7 +158,8 @@
|
||||
<legend>Media Options</legend>
|
||||
<label for="audioselect" class="sidenav-label-first">Microphone:</label>
|
||||
<select id="audioselect" class="select select-inline">
|
||||
<option value="">off</option>
|
||||
<option value="default">default microphone</option>
|
||||
<option value="off">off</option>
|
||||
</select>
|
||||
|
||||
<form>
|
||||
|
||||
+83
-66
@@ -800,7 +800,7 @@ let mediaChoicesDone = false;
|
||||
* Populate the media choices menu.
|
||||
*
|
||||
* Since media names might not be available before we call
|
||||
* getDisplayMedia, we call this function twice, the second time in order
|
||||
* getUserMedia, we call this function twice, the second time in order
|
||||
* to update the menu with user-readable labels.
|
||||
*
|
||||
* @param{boolean} done
|
||||
@@ -818,10 +818,17 @@ async function setMediaChoices(done) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cn = 1, mn = 1;
|
||||
let mn = 1;
|
||||
|
||||
devices.forEach(d => {
|
||||
let label = d.label;
|
||||
if(d.kind === 'audioinput' && d.deviceId) {
|
||||
if(!label)
|
||||
label = `Microphone ${mn}`;
|
||||
addSelectOption(getSelectElement('audioselect'),
|
||||
label, d.deviceId);
|
||||
mn++;
|
||||
}
|
||||
});
|
||||
|
||||
mediaChoicesDone = done;
|
||||
@@ -1031,9 +1038,18 @@ async function addLocalMedia(localId) {
|
||||
let settings = getSettings();
|
||||
|
||||
/** @type{boolean|MediaTrackConstraints} */
|
||||
let audio = settings.audio ? {deviceId: settings.audio} : false;
|
||||
let audio = false;
|
||||
if(settings.audio === 'default') {
|
||||
audio = true;
|
||||
} else if(settings.audio && settings.audio !== 'off') {
|
||||
audio = {deviceId: settings.audio};
|
||||
}
|
||||
if(!audio) {
|
||||
displayMessage('Please choose a microphone before enabling audio.');
|
||||
return;
|
||||
}
|
||||
|
||||
if(audio) {
|
||||
if(audio !== true) {
|
||||
if(!settings.preprocessing) {
|
||||
audio.echoCancellation = false;
|
||||
audio.noiseSuppression = false;
|
||||
@@ -1080,17 +1096,6 @@ async function addLocalMedia(localId) {
|
||||
}
|
||||
setButtonsVisibility();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
async function addFileMedia(file) {
|
||||
displayError('Local file playback is not supported in audio-only mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MediaStream} s
|
||||
*/
|
||||
@@ -1182,6 +1187,26 @@ async function setMedia(c) {
|
||||
if(!stream)
|
||||
return;
|
||||
c.userdata.media = stream;
|
||||
|
||||
let media = /** @type {HTMLAudioElement} */
|
||||
(document.getElementById('media-' + c.localId));
|
||||
if(!media) {
|
||||
media = document.createElement('audio');
|
||||
media.id = 'media-' + c.localId;
|
||||
media.autoplay = true;
|
||||
media.muted = !!c.up;
|
||||
media.classList.add('media');
|
||||
|
||||
let container = document.getElementById('audio-streams');
|
||||
if(!container)
|
||||
throw new Error("Couldn't find audio-streams");
|
||||
container.appendChild(media);
|
||||
}
|
||||
|
||||
if(media.srcObject !== stream)
|
||||
media.srcObject = stream;
|
||||
|
||||
setMediaStatus(c);
|
||||
}
|
||||
|
||||
|
||||
@@ -1201,8 +1226,13 @@ function showHideMedia(c, elt) {
|
||||
* @param {Stream} c
|
||||
*/
|
||||
function resetMedia(c) {
|
||||
// Audio-only: no media elements to reset
|
||||
return;
|
||||
let media = /** @type {HTMLAudioElement} */
|
||||
(document.getElementById('media-' + c.localId));
|
||||
if(!media) {
|
||||
console.error("Resetting unknown media element")
|
||||
return;
|
||||
}
|
||||
media.srcObject = media.srcObject;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1254,6 +1284,12 @@ function setVolumeButton(muted, button, slider) {
|
||||
* @param {string} localId
|
||||
*/
|
||||
function delMedia(localId) {
|
||||
let media = document.getElementById('media-' + localId);
|
||||
if(media) {
|
||||
if(media instanceof HTMLMediaElement)
|
||||
media.srcObject = null;
|
||||
media.remove();
|
||||
}
|
||||
setButtonsVisibility();
|
||||
}
|
||||
|
||||
@@ -1274,12 +1310,20 @@ function setMediaStatus(c) {
|
||||
}
|
||||
if(good) {
|
||||
media.classList.remove('media-failed');
|
||||
if(c.userdata.play) {
|
||||
if(media instanceof HTMLMediaElement)
|
||||
if(media instanceof HTMLMediaElement) {
|
||||
if(!c.up && !c.userdata.playing) {
|
||||
c.userdata.playing = true;
|
||||
media.play().catch(e => {
|
||||
c.userdata.playing = false;
|
||||
console.error(e);
|
||||
displayError(e);
|
||||
});
|
||||
} else if(c.userdata.play) {
|
||||
media.play().catch(e => {
|
||||
console.error(e);
|
||||
displayError(e);
|
||||
});
|
||||
}
|
||||
delete(c.userdata.play);
|
||||
}
|
||||
} else {
|
||||
@@ -1500,8 +1544,6 @@ function userMenu(elt) {
|
||||
inviteMenu();
|
||||
}});
|
||||
}
|
||||
if(serverConnection.permissions.indexOf('present') >= 0 && canFile())
|
||||
items.push({label: 'Broadcast file', onClick: presentFile});
|
||||
items.push({label: 'Restart media', onClick: renegotiateStreams});
|
||||
} else {
|
||||
items.push({label: 'Send file', onClick: () => {
|
||||
@@ -2239,6 +2281,20 @@ function formatTime(time) {
|
||||
/** @type {lastMessage} */
|
||||
let lastMessage = {};
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
function announceChat(text) {
|
||||
let announcement = document.getElementById('chat-announcements');
|
||||
if(!announcement)
|
||||
return;
|
||||
|
||||
announcement.textContent = '';
|
||||
window.setTimeout(() => {
|
||||
announcement.textContent = text;
|
||||
}, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} peerId
|
||||
@@ -2359,11 +2415,11 @@ function addToChatbox(id, peerId, dest, nick, time, privileged, history, kind, m
|
||||
|
||||
// Announce new messages to screen readers (but not history)
|
||||
if(!history) {
|
||||
let announcement = document.getElementById('chat-announcements');
|
||||
if(announcement) {
|
||||
let messageText = typeof message === 'string' ? message : body.textContent;
|
||||
announcement.textContent = nick ? `${nick}: ${messageText}` : messageText;
|
||||
}
|
||||
let messageText = typeof message === 'string' ? message : body.textContent;
|
||||
let speaker = nick;
|
||||
if(serverConnection && peerId === serverConnection.id)
|
||||
speaker = 'You';
|
||||
announceChat(speaker ? `${speaker}: ${messageText}` : messageText);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -3014,45 +3070,6 @@ commands.unraise = {
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
function canFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function presentFile() {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept="audio/*";
|
||||
input.onchange = function(e) {
|
||||
if(!(this instanceof HTMLInputElement))
|
||||
throw new Error('Unexpected type for this');
|
||||
let files = this.files;
|
||||
for(let i = 0; i < files.length; i++) {
|
||||
addFileMedia(files[i]).catch(e => {
|
||||
console.error(e);
|
||||
displayError(e);
|
||||
});
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
commands.presentfile = {
|
||||
description: 'broadcast an audio file',
|
||||
f: (c, r) => {
|
||||
presentFile();
|
||||
},
|
||||
predicate: () => {
|
||||
if(!canFile())
|
||||
return 'Your browser does not support presenting arbitrary files';
|
||||
if(!serverConnection || !serverConnection.permissions ||
|
||||
serverConnection.permissions.indexOf('present') < 0)
|
||||
return 'You are not authorised to present.';
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
*/
|
||||
@@ -3355,7 +3372,7 @@ document.getElementById('loginform').onsubmit = async function(e) {
|
||||
presentRequested = 'mike';
|
||||
else
|
||||
presentRequested = null;
|
||||
getInputElement('presentoff').checked = true;
|
||||
getInputElement('presentmike').checked = true;
|
||||
|
||||
// Connect to the server, gotConnected will join.
|
||||
serverConnect();
|
||||
|
||||
@@ -99,15 +99,6 @@ function formatTrack(table, track) {
|
||||
tr.appendChild(document.createElement('td'));
|
||||
tr.appendChild(document.createElement('td'));
|
||||
let td = document.createElement('td');
|
||||
let layer = '';
|
||||
if(track.sid || track.maxSid)
|
||||
layer = layer + `s${track.sid}/${track.maxSid}`;
|
||||
if(track.tid || track.maxTid) {
|
||||
if(layer !== '')
|
||||
layer = layer + '+';
|
||||
layer = layer + `t${track.tid}/${track.maxTid}`;
|
||||
}
|
||||
td.textContent = layer;
|
||||
tr.appendChild(td);
|
||||
let td2 = document.createElement('td');
|
||||
if(track.maxBitrate)
|
||||
|
||||
Reference in New Issue
Block a user