diff --git a/static/sfu.html b/static/sfu.html
index e6010ce..63c20ac 100644
--- a/static/sfu.html
+++ b/static/sfu.html
@@ -146,14 +146,12 @@
diff --git a/static/sfu.js b/static/sfu.js
index 3e336ad..b740e25 100644
--- a/static/sfu.js
+++ b/static/sfu.js
@@ -29,7 +29,7 @@ let fallbackUserPass = null;
* @param {string} username
* @param {string} password
*/
-function setUserPass(username, password) {
+function storeUserPass(username, password) {
let userpass = {username: username, password: password};
try {
window.sessionStorage.setItem('userpass', JSON.stringify(userpass));
@@ -41,9 +41,12 @@ function setUserPass(username, password) {
}
/**
+ * Returns null if the user hasn't logged in yet.
+ *
* @returns {userpass}
*/
function getUserPass() {
+ /** @type{userpass} */
let userpass;
try {
let json = window.sessionStorage.getItem('userpass');
@@ -56,6 +59,8 @@ function getUserPass() {
}
/**
+ * Return null if the user hasn't logged in yet.
+ *
* @returns {string}
*/
function getUsername() {
@@ -65,6 +70,104 @@ function getUsername() {
return userpass.username;
}
+/**
+ * @typedef {Object} settings
+ * @property {boolean} [localMute]
+ * @property {string} [video]
+ * @property {string} [audio]
+ * @property {string} [send]
+ * @property {string} [request]
+ * @property {boolean} [activityDetection]
+ */
+
+/** @type{settings} */
+let fallbackSettings = null;
+
+/**
+ * @param {settings} settings
+ */
+function storeSettings(settings) {
+ try {
+ window.sessionStorage.setItem('settings', JSON.stringify(settings));
+ fallbackSettings = null;
+ } catch(e) {
+ console.warn("Couldn't store password:", e);
+ fallbackSettings = settings;
+ }
+}
+
+/**
+ * This always returns a dictionary.
+ *
+ * @returns {settings}
+ */
+
+function getSettings() {
+ /** @type{settings} */
+ let settings;
+ try {
+ let json = window.sessionStorage.getItem('settings');
+ settings = JSON.parse(json);
+ } catch(e) {
+ console.warn("Couldn't retrieve password:", e);
+ settings = fallbackSettings;
+ }
+ return settings || {};
+}
+
+/**
+ * @param {settings} settings
+ */
+function updateSettings(settings) {
+ let s = getSettings();
+ for(let key in settings)
+ s[key] = settings[key];
+ storeSettings(s);
+}
+
+function reflectSettings() {
+ let settings = getSettings();
+ let store = false;
+
+ setLocalMute(settings.localMute);
+
+ let videoselect =
+ /** @type {HTMLSelectElement} */(document.getElementById('videoselect'));
+ if(!settings.video || !selectOptionAvailable(videoselect, settings.video)) {
+ settings.video = selectOptionDefault(videoselect);
+ store = true;
+ }
+ videoselect.value = settings.video;
+
+ let audioselect =
+ /** @type {HTMLSelectElement} */(document.getElementById('audioselect'));
+ if(!settings.audio || !selectOptionAvailable(audioselect, settings.audio)) {
+ settings.audio = selectOptionDefault(audioselect);
+ store = true;
+ }
+ audioselect.value = settings.audio;
+
+ if(settings.request)
+ document.getElementById('requestselect').value = settings.request;
+ else {
+ settings.request = document.getElementById('requestselect').value;
+ store = true;
+ }
+
+ if(settings.send)
+ document.getElementById('sendselect').value = settings.send;
+ else {
+ settings.send = document.getElementById('sendselect').value;
+ store = true;
+ }
+
+ document.getElementById('activitybox').checked = settings.activityDetection;
+
+ if(store)
+ storeSettings(settings);
+
+}
+
function showVideo() {
let width = window.innerWidth;
let video_container = document.getElementById('video-container');
@@ -130,7 +233,7 @@ function gotConnected() {
let up = getUserPass();
this.login(up.username, up.password);
this.join(group);
- this.request(document.getElementById('requestselect').value);
+ this.request(getSettings().request);
}
/**
@@ -166,7 +269,7 @@ function gotDownStream(c) {
setMediaStatus(c);
}
c.onstats = gotDownStats;
- if(document.getElementById('activitybox').checked)
+ if(getSettings().activityDetection)
c.setStatsInterval(activityDetectionInterval);
}
@@ -226,22 +329,14 @@ function setButtonsVisibility() {
setVisibility('mediaoptions', permissions.present);
}
-/** @type {boolean} */
-let localMute = false;
-
-function toggleLocalMute() {
- setLocalMute(!localMute);
-}
-
/**
* @param {boolean} mute
*/
function setLocalMute(mute) {
- localMute = mute;
- muteLocalTracks(localMute);
+ muteLocalTracks(mute);
let button = document.getElementById('mutebutton');
let icon = button.querySelector("span .fa");
- if(localMute){
+ if(mute){
icon.classList.add('fa-microphone-slash');
icon.classList.remove('fa-microphone');
button.classList.add('muted');
@@ -254,17 +349,22 @@ function setLocalMute(mute) {
document.getElementById('videoselect').onchange = function(e) {
e.preventDefault();
+ updateSettings({video: this.value});
changePresentation();
};
document.getElementById('audioselect').onchange = function(e) {
e.preventDefault();
+ updateSettings({audio: this.value});
changePresentation();
};
document.getElementById('mutebutton').onclick = function(e) {
e.preventDefault();
- toggleLocalMute();
+ let localMute = getSettings().localMute;
+ localMute = !localMute;
+ updateSettings({localMute: localMute})
+ setLocalMute(localMute);
}
document.getElementById('sharebutton').onclick = function(e) {
@@ -279,7 +379,7 @@ document.getElementById('unsharebutton').onclick = function(e) {
/** @returns {number} */
function getMaxVideoThroughput() {
- let v = document.getElementById('sendselect').value;
+ let v = getSettings().send;
switch(v) {
case 'lowest':
return 150000;
@@ -296,6 +396,7 @@ function getMaxVideoThroughput() {
}
document.getElementById('sendselect').onchange = async function(e) {
+ updateSettings({send: this.value});
let t = getMaxVideoThroughput();
for(let id in serverConnection.up) {
let c = serverConnection.up[id];
@@ -306,6 +407,7 @@ document.getElementById('sendselect').onchange = async function(e) {
document.getElementById('requestselect').onchange = function(e) {
e.preventDefault();
+ updateSettings({request: this.value});
serverConnection.request(this.value);
};
@@ -314,13 +416,14 @@ const activityDetectionPeriod = 700;
const activityDetectionThreshold = 0.2;
document.getElementById('activitybox').onchange = function(e) {
+ updateSettings({activityDetection: this.checked});
for(let id in serverConnection.down) {
let c = serverConnection.down[id];
if(this.checked)
c.setStatsInterval(activityDetectionInterval);
else {
- setActive(c, false);
c.setStatsInterval(0);
+ setActive(c, false);
}
}
}
@@ -385,24 +488,19 @@ function gotDownStats(stats) {
}
/**
- * @param {string} value
+ * @param {HTMLSelectElement} select
+ * @param {string} label
+ * @param {string} [value]
*/
-function mapMediaOption(value) {
- switch(value) {
- case 'default':
- return true;
- case 'off':
- return false;
- default:
- return {deviceId: value};
- }
-}
-
function addSelectOption(select, label, value) {
if(!value)
value = label;
for(let i = 0; i < select.children.length; i++) {
- if(select.children[i].value === value) {
+ let child = /** @type {HTMLOptionElement} */ (select.children[i]);
+ if(child.value === value) {
+ if(child.label !== label) {
+ child.label = label;
+ }
return;
}
}
@@ -413,12 +511,46 @@ function addSelectOption(select, label, value) {
select.appendChild(option);
}
-// media names might not be available before we call getDisplayMedia. So
-// we call this lazily.
+/**
+ * @param {HTMLSelectElement} select
+ * @param {string} value
+ */
+function selectOptionAvailable(select, value) {
+ let children = select.children;
+ for(let i = 0; i < children.length; i++) {
+ let child = /** @type {HTMLOptionElement} */ (select.children[i]);
+ if(child.value === value) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * @param {HTMLSelectElement} select
+ * @returns {string}
+ */
+function selectOptionDefault(select) {
+ /* First non-empty option. */
+ for(let i = 0; i < select.children.length; i++) {
+ let child = /** @type {HTMLOptionElement} */ (select.children[i]);
+ if(child.value)
+ return child.value;
+ }
+ /* The empty option is always available. */
+ return '';
+}
+
+/* media names might not be available before we call getDisplayMedia. So
+ we call this twice, the second time to update the menu with user-readable
+ labels. */
/** @type {boolean} */
let mediaChoicesDone = false;
-async function setMediaChoices() {
+/**
+ * @param{boolean} done
+ */
+async function setMediaChoices(done) {
if(mediaChoicesDone)
return;
@@ -449,9 +581,10 @@ async function setMediaChoices() {
}
});
- mediaChoicesDone = true;
+ mediaChoicesDone = done;
}
+
/**
* @param {string} [id]
*/
@@ -508,8 +641,10 @@ async function addLocalMedia(id) {
if(!getUserPass())
return;
- let audio = mapMediaOption(document.getElementById('audioselect').value);
- let video = mapMediaOption(document.getElementById('videoselect').value);
+ let settings = getSettings();
+
+ let audio = settings.audio ? {deviceId: settings.audio} : false;
+ let video = settings.video ? {deviceId: settings.video} : false;
let old = id && serverConnection.up[id];
@@ -534,15 +669,16 @@ async function addLocalMedia(id) {
return;
}
- setMediaChoices();
+ setMediaChoices(true);
let c = newUpStream(id);
c.kind = 'local';
c.stream = stream;
+ let mute = getSettings().localMute;
stream.getTracks().forEach(t => {
c.labels[t.id] = t.kind
- if(t.kind == 'audio' && localMute)
+ if(t.kind == 'audio' && mute)
t.enabled = false;
let sender = c.pc.addTrack(t, stream);
});
@@ -1212,7 +1348,7 @@ document.getElementById('userform').onsubmit = function(e) {
e.preventDefault();
let username = document.getElementById('username').value.trim();
let password = document.getElementById('password').value;
- setUserPass(username, password);
+ storeUserPass(username, password);
serverConnect();
};
@@ -1222,7 +1358,6 @@ document.getElementById('disconnectbutton').onclick = function(e) {
if (user_box.classList.contains("show")) {
user_box.classList.toggle("show");
}
-
};
function openNav() {
@@ -1328,7 +1463,7 @@ function start() {
document.getElementById('title').textContent = title;
}
- setLocalMute(localMute);
+ setMediaChoices(false).then(e => reflectSettings());
document.getElementById("user").classList.add('invisible');
document.getElementById("login-container").classList.remove('invisible');