Add generated UI notification sounds
This commit is contained in:
+176
-9
@@ -76,6 +76,7 @@ let probingState = null;
|
||||
|
||||
* @property {boolean} [preprocessing]
|
||||
* @property {boolean} [hqaudio]
|
||||
* @property {boolean} [notificationSounds]
|
||||
* @property {boolean} [forceRelay]
|
||||
*/
|
||||
|
||||
@@ -248,10 +249,150 @@ function reflectSettings() {
|
||||
store = true;
|
||||
}
|
||||
|
||||
if(settings.hasOwnProperty('notificationSounds')) {
|
||||
getInputElement('notificationsoundsbox').checked =
|
||||
settings.notificationSounds;
|
||||
} else {
|
||||
settings.notificationSounds =
|
||||
getInputElement('notificationsoundsbox').checked;
|
||||
store = true;
|
||||
}
|
||||
|
||||
if(store)
|
||||
storeSettings(settings);
|
||||
}
|
||||
|
||||
/** @type {AudioContext} */
|
||||
let notificationAudioContext = null;
|
||||
let userNotificationSoundsReady = false;
|
||||
|
||||
function notificationSoundsEnabled() {
|
||||
return !!getSettings().notificationSounds;
|
||||
}
|
||||
|
||||
function getNotificationAudioContext() {
|
||||
if(!notificationSoundsEnabled())
|
||||
return null;
|
||||
|
||||
let AudioContextConstructor =
|
||||
window.AudioContext || window.webkitAudioContext;
|
||||
if(!AudioContextConstructor)
|
||||
return null;
|
||||
|
||||
if(!notificationAudioContext)
|
||||
notificationAudioContext = new AudioContextConstructor();
|
||||
|
||||
if(notificationAudioContext.state === 'suspended')
|
||||
notificationAudioContext.resume().catch(e => {
|
||||
console.warn('Could not resume notification audio:', e);
|
||||
});
|
||||
|
||||
return notificationAudioContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} start
|
||||
* @param {number} duration
|
||||
* @param {number} fromFrequency
|
||||
* @param {number} toFrequency
|
||||
* @param {number} gainValue
|
||||
* @param {OscillatorType} [type]
|
||||
*/
|
||||
function scheduleGlide(start, duration, fromFrequency, toFrequency, gainValue, type) {
|
||||
let context = getNotificationAudioContext();
|
||||
if(!context)
|
||||
return;
|
||||
|
||||
let oscillator = context.createOscillator();
|
||||
let gain = context.createGain();
|
||||
oscillator.type = type || 'sine';
|
||||
oscillator.frequency.setValueAtTime(fromFrequency, start);
|
||||
oscillator.frequency.exponentialRampToValueAtTime(toFrequency, start + duration);
|
||||
gain.gain.setValueAtTime(0, start);
|
||||
gain.gain.linearRampToValueAtTime(gainValue, start + 0.02);
|
||||
gain.gain.exponentialRampToValueAtTime(0.0001, start + duration);
|
||||
oscillator.connect(gain);
|
||||
gain.connect(context.destination);
|
||||
oscillator.start(start);
|
||||
oscillator.stop(start + duration + 0.02);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} offset
|
||||
* @param {number} duration
|
||||
* @param {number} frequency
|
||||
* @param {number} gainValue
|
||||
* @param {OscillatorType} [type]
|
||||
*/
|
||||
function scheduleTone(offset, duration, frequency, gainValue, type) {
|
||||
let context = getNotificationAudioContext();
|
||||
if(!context)
|
||||
return;
|
||||
|
||||
let start = context.currentTime + offset;
|
||||
let oscillator = context.createOscillator();
|
||||
let gain = context.createGain();
|
||||
oscillator.type = type || 'sine';
|
||||
oscillator.frequency.setValueAtTime(frequency, start);
|
||||
gain.gain.setValueAtTime(0, start);
|
||||
gain.gain.linearRampToValueAtTime(gainValue, start + 0.015);
|
||||
gain.gain.exponentialRampToValueAtTime(0.0001, start + duration);
|
||||
oscillator.connect(gain);
|
||||
gain.connect(context.destination);
|
||||
oscillator.start(start);
|
||||
oscillator.stop(start + duration + 0.02);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function playNotificationSound(name) {
|
||||
let context = getNotificationAudioContext();
|
||||
if(!context)
|
||||
return;
|
||||
|
||||
let now = context.currentTime;
|
||||
switch(name) {
|
||||
case 'hand-raised':
|
||||
scheduleGlide(now, 0.36, 420, 1320, 0.035, 'sine');
|
||||
break;
|
||||
case 'hand-lowered':
|
||||
scheduleGlide(now, 0.34, 1180, 360, 0.03, 'sine');
|
||||
break;
|
||||
case 'user-joined':
|
||||
scheduleTone(0, 0.18, 660, 0.035, 'sine');
|
||||
scheduleTone(0.18, 0.26, 880, 0.032, 'sine');
|
||||
break;
|
||||
case 'user-left':
|
||||
scheduleTone(0, 0.16, 660, 0.025, 'triangle');
|
||||
scheduleTone(0.14, 0.22, 440, 0.022, 'triangle');
|
||||
break;
|
||||
case 'recording-started':
|
||||
scheduleTone(0, 0.16, 523.25, 0.03, 'sine');
|
||||
scheduleTone(0.09, 0.18, 659.25, 0.026, 'sine');
|
||||
scheduleTone(0.18, 0.24, 783.99, 0.024, 'sine');
|
||||
break;
|
||||
case 'recording-stopped':
|
||||
scheduleTone(0, 0.16, 783.99, 0.026, 'sine');
|
||||
scheduleTone(0.12, 0.18, 659.25, 0.024, 'sine');
|
||||
scheduleTone(0.24, 0.24, 392, 0.022, 'sine');
|
||||
break;
|
||||
case 'chat-message':
|
||||
scheduleTone(0, 0.09, 880, 0.018, 'triangle');
|
||||
scheduleTone(0.08, 0.11, 1174.66, 0.014, 'triangle');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function unlockNotificationAudio() {
|
||||
getNotificationAudioContext();
|
||||
}
|
||||
|
||||
document.addEventListener('pointerdown', unlockNotificationAudio);
|
||||
document.addEventListener('keydown', unlockNotificationAudio);
|
||||
|
||||
/**
|
||||
* Returns true if we should use the mobile layout. This should be kept
|
||||
* in sync with the CSS.
|
||||
@@ -589,6 +730,15 @@ getInputElement('hqaudiobox').onchange = function(e) {
|
||||
updateSettings({hqaudio: this.checked});
|
||||
};
|
||||
|
||||
getInputElement('notificationsoundsbox').onchange = function(e) {
|
||||
e.preventDefault();
|
||||
if(!(this instanceof HTMLInputElement))
|
||||
throw new Error('Unexpected type for this');
|
||||
updateSettings({notificationSounds: this.checked});
|
||||
if(this.checked)
|
||||
getNotificationAudioContext();
|
||||
};
|
||||
|
||||
document.getElementById('mutebutton').onclick = function(e) {
|
||||
e.preventDefault();
|
||||
let localMute = getSettings().localMute;
|
||||
@@ -1053,7 +1203,7 @@ async function addLocalMedia(localId) {
|
||||
/** @type{boolean|MediaTrackConstraints} */
|
||||
let audio = false;
|
||||
if(settings.audio === 'default') {
|
||||
audio = true;
|
||||
audio = {};
|
||||
} else if(settings.audio && settings.audio !== 'off') {
|
||||
audio = {deviceId: settings.audio};
|
||||
}
|
||||
@@ -1609,7 +1759,7 @@ function addUser(id, userinfo) {
|
||||
user.classList.add("user-p");
|
||||
user.setAttribute('role', 'button');
|
||||
user.setAttribute('tabindex', '0');
|
||||
setUserStatus(id, user, userinfo);
|
||||
setUserStatus(id, user, userinfo, false);
|
||||
user.addEventListener('click', function(e) {
|
||||
let elt = e.currentTarget;
|
||||
if(!elt || !(elt instanceof HTMLElement))
|
||||
@@ -1664,32 +1814,36 @@ function changeUser(id, userinfo) {
|
||||
console.warn('Unknown user ' + id);
|
||||
return;
|
||||
}
|
||||
setUserStatus(id, elt, userinfo);
|
||||
setUserStatus(id, elt, userinfo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {HTMLElement} elt
|
||||
* @param {user} userinfo
|
||||
* @param {boolean} notify
|
||||
*/
|
||||
function setUserStatus(id, elt, userinfo) {
|
||||
function setUserStatus(id, elt, userinfo, notify) {
|
||||
let username = userinfo.username ? userinfo.username : '(anon)';
|
||||
elt.textContent = username;
|
||||
|
||||
let wasRaised = elt.classList.contains('user-status-raisehand');
|
||||
let isRemote = id !== (serverConnection && serverConnection.id);
|
||||
if(userinfo.data.raisehand) {
|
||||
elt.classList.add('user-status-raisehand');
|
||||
elt.setAttribute('aria-label', `${username} (hand raised)`);
|
||||
// Announce when hand is newly raised (not on initial load)
|
||||
if(!wasRaised && id !== (serverConnection && serverConnection.id)) {
|
||||
if(notify && !wasRaised) {
|
||||
let announcement = document.getElementById('chat-announcements');
|
||||
if(announcement) {
|
||||
if(announcement && isRemote) {
|
||||
announcement.textContent = `${username} raised their hand`;
|
||||
}
|
||||
playNotificationSound('hand-raised');
|
||||
}
|
||||
} else {
|
||||
elt.classList.remove('user-status-raisehand');
|
||||
elt.setAttribute('aria-label', username);
|
||||
if(notify && wasRaised)
|
||||
playNotificationSound('hand-lowered');
|
||||
}
|
||||
|
||||
let microphone=false;
|
||||
@@ -1727,10 +1881,14 @@ function gotUser(id, kind) {
|
||||
switch(kind) {
|
||||
case 'add':
|
||||
addUser(id, serverConnection.users[id]);
|
||||
if(userNotificationSoundsReady && id !== serverConnection.id)
|
||||
playNotificationSound('user-joined');
|
||||
if(Object.keys(serverConnection.users).length == 3)
|
||||
reconsiderSendParameters();
|
||||
break;
|
||||
case 'delete':
|
||||
if(userNotificationSoundsReady && id !== serverConnection.id)
|
||||
playNotificationSound('user-left');
|
||||
delUser(id);
|
||||
if(Object.keys(serverConnection.users).length < 3)
|
||||
scheduleReconsiderParameters();
|
||||
@@ -1869,6 +2027,9 @@ async function gotJoined(kind, hall, perms, status, data, error, message) {
|
||||
serverConnection.username
|
||||
);
|
||||
openSafariStream();
|
||||
window.setTimeout(() => {
|
||||
userNotificationSoundsReady = true;
|
||||
}, 1000);
|
||||
if(kind === 'change')
|
||||
return;
|
||||
break;
|
||||
@@ -2121,10 +2282,14 @@ function gotUserMessage(id, dest, username, time, privileged, kind, error, messa
|
||||
return;
|
||||
}
|
||||
let text = '' + message;
|
||||
if(text === 'Recording started')
|
||||
if(text === 'Recording started') {
|
||||
announceUrgent(text);
|
||||
else
|
||||
playNotificationSound('recording-started');
|
||||
} else {
|
||||
announceChat(text);
|
||||
if(text === 'Recording stopped')
|
||||
playNotificationSound('recording-stopped');
|
||||
}
|
||||
displayMessage(text);
|
||||
break;
|
||||
}
|
||||
@@ -2462,6 +2627,8 @@ function addToChatbox(id, peerId, dest, nick, time, privileged, history, kind, m
|
||||
if(serverConnection && peerId === serverConnection.id)
|
||||
speaker = 'You';
|
||||
announceChat(speaker ? `${speaker}: ${messageText}` : messageText);
|
||||
if(serverConnection && peerId && peerId !== serverConnection.id)
|
||||
playNotificationSound('chat-message');
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user