diff options
author | metamuffin <metamuffin@disroot.org> | 2023-01-17 22:34:25 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-01-17 22:34:25 +0100 |
commit | 2fc81eee903efa1f6da77b9f58fd2179356dad9f (patch) | |
tree | 6c047c05c636f366220ca34f3312ccc7fb91e85c | |
parent | f85d40d8c6cc2f3b58d1e0ea7f0382db88fffd4e (diff) | |
parent | 67694f908f7fc76f78566e6f02d89db2d7a19c1d (diff) | |
download | keks-meet-2fc81eee903efa1f6da77b9f58fd2179356dad9f.tar keks-meet-2fc81eee903efa1f6da77b9f58fd2179356dad9f.tar.bz2 keks-meet-2fc81eee903efa1f6da77b9f58fd2179356dad9f.tar.zst |
merge
-rw-r--r-- | Cargo.lock | 53 | ||||
-rw-r--r-- | client-native-rift/src/main.rs | 38 | ||||
-rw-r--r-- | client-web/public/assets/style/master.css | 11 | ||||
-rw-r--r-- | client-web/public/assets/style/room.css | 14 | ||||
-rw-r--r-- | client-web/public/start.html | 3 | ||||
-rw-r--r-- | client-web/source/chat.ts | 9 | ||||
-rw-r--r-- | client-web/source/helper.ts | 24 | ||||
-rw-r--r-- | client-web/source/index.ts | 4 | ||||
-rw-r--r-- | client-web/source/keybinds.ts | 13 | ||||
-rw-r--r-- | client-web/source/menu.ts | 10 | ||||
-rw-r--r-- | client-web/source/preferences/decl.ts | 2 | ||||
-rw-r--r-- | client-web/source/preferences/ui.ts | 11 | ||||
-rw-r--r-- | client-web/source/resource/track.ts | 55 | ||||
-rw-r--r-- | makefile | 3 | ||||
-rw-r--r-- | readme.md | 21 | ||||
-rw-r--r-- | server/makefile | 2 | ||||
-rw-r--r-- | server/src/main.rs | 11 | ||||
-rw-r--r-- | todo.org | 5 |
18 files changed, 201 insertions, 88 deletions
@@ -1496,6 +1496,19 @@ dependencies = [ ] [[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] name = "epaint" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2252,9 +2265,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -2270,7 +2283,7 @@ dependencies = [ "crossbeam-channel", "eframe", "egui", - "env_logger", + "env_logger 0.8.4", "libmpv", "log", "tokio", @@ -2284,7 +2297,7 @@ dependencies = [ "bytes", "clap", "client-native-lib", - "env_logger", + "env_logger 0.8.4", "log", "tokio", ] @@ -2293,7 +2306,7 @@ dependencies = [ name = "keks-meet-server" version = "0.1.0" dependencies = [ - "env_logger", + "env_logger 0.10.0", "futures-util", "hyper", "listenfd", @@ -3178,7 +3191,7 @@ dependencies = [ "bytes", "clap", "client-native-lib", - "env_logger", + "env_logger 0.8.4", "humansize", "indicatif", "log", @@ -4192,9 +4205,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4202,9 +4215,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", "log", @@ -4217,9 +4230,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if", "js-sys", @@ -4229,9 +4242,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4239,9 +4252,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -4252,9 +4265,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "wayland-client" @@ -4343,9 +4356,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/client-native-rift/src/main.rs b/client-native-rift/src/main.rs index 5d0c7d6..45f720a 100644 --- a/client-native-rift/src/main.rs +++ b/client-native-rift/src/main.rs @@ -192,21 +192,29 @@ impl EventHandler for Handler { let writer = writer.clone(); let pos = pos.clone(); Box::pin(async move { - let pos = pos.fetch_add(mesg.data.len(), Ordering::Relaxed); - info!( - "recv {:?} ({} of {})", - mesg.data.len(), - humansize::format_size(pos, DECIMAL), - humansize::format_size(resource.size.unwrap_or(0), DECIMAL), - ); - writer - .write() - .await - .as_mut() - .unwrap() - .write_all(&mesg.data) - .await - .unwrap(); + // TODO + if mesg.is_string { + let s = String::from_utf8((&mesg.data).to_vec()).unwrap(); + if &s == "end" { + info!("EOF reached") + } + } else { + let pos = pos.fetch_add(mesg.data.len(), Ordering::Relaxed); + info!( + "recv {:?} ({} of {})", + mesg.data.len(), + humansize::format_size(pos, DECIMAL), + humansize::format_size(resource.size.unwrap_or(0), DECIMAL), + ); + writer + .write() + .await + .as_mut() + .unwrap() + .write_all(&mesg.data) + .await + .unwrap(); + } }) }) } diff --git a/client-web/public/assets/style/master.css b/client-web/public/assets/style/master.css index caa8c85..7a59d78 100644 --- a/client-web/public/assets/style/master.css +++ b/client-web/public/assets/style/master.css @@ -33,7 +33,7 @@ body { justify-content: left; } -h2 { +h1 { font-weight: 700; margin: 1em; } @@ -46,20 +46,24 @@ button { border: 0px solid transparent; border-radius: 3px; } + input[type="button"]:hover, button:hover { filter: brightness(130%); } + input[type="button"].active, button.active { filter: hue-rotate(20deg); } + input[type="text"], select, input[type="number"] { background-color: var(--bg-dark); border: 1px solid var(--ac-light); } + input:disabled, button:disabled { filter: sepia(90%); @@ -71,15 +75,18 @@ button:disabled { left: 50vw; transform: translate(-50%, -50%); } + .start-box p { margin-bottom: 0.5em; } + .start-box input[type="text"] { margin: 0.5em; font-size: xx-large; } + .start-box input[type="button"] { margin: 0.5em; font-size: x-large; width: calc(100% - 1em); -} +}
\ No newline at end of file diff --git a/client-web/public/assets/style/room.css b/client-web/public/assets/style/room.css index 3f62685..ad84325 100644 --- a/client-web/public/assets/style/room.css +++ b/client-web/public/assets/style/room.css @@ -23,9 +23,11 @@ .user .info .name { font-weight: 400; } + .user.local .info .name { text-decoration: underline; } + .user .info { margin-bottom: 1em; } @@ -44,6 +46,7 @@ .local-controls { display: inline; } + .local-controls::before { content: "|"; } @@ -51,6 +54,7 @@ .transfer-status { background-color: #00000040; } + .progress-bar { z-index: -1; padding: 0px; @@ -59,3 +63,13 @@ border-radius: 3px; background-color: var(--ac-light); } + + +.resource-track>div { + border: 2px solid transparent; + border-radius: 4px; +} + +.resource-track>div.audio-active { + border: 2px solid var(--ac-light); +} diff --git a/client-web/public/start.html b/client-web/public/start.html index afe771e..4766c77 100644 --- a/client-web/public/start.html +++ b/client-web/public/start.html @@ -8,7 +8,7 @@ </head> <body> <div class="start-box"> - <h2>keks-meet</h2> + <h1>keks-meet</h2> <p>A web conferencing application using webrtc</p> <p> keks-meet is free software! It is licenced under the terms of @@ -29,6 +29,7 @@ room_input.type = "text"; room_input.id = "room-id-input"; room_input.placeholder = "Room Secret"; + room_input.ariaLabel = "Room Secret" const submit = document.createElement("input"); submit.type = "button"; diff --git a/client-web/source/chat.ts b/client-web/source/chat.ts index b7c572d..47b0678 100644 --- a/client-web/source/chat.ts +++ b/client-web/source/chat.ts @@ -6,7 +6,7 @@ /// <reference lib="dom" /> import { ChatMessage } from "../../common/packets.d.ts"; -import { ediv, espan, image_view, notify, OverlayUi } from "./helper.ts"; +import { ediv, esection, espan, image_view, notify, OverlayUi } from "./helper.ts"; import { log } from "./logger.ts"; import { PREFS } from "./preferences/mod.ts"; import { Room } from "./room.ts"; @@ -20,13 +20,15 @@ export class Chat extends OverlayUi { constructor(public room: Room) { const send = document.createElement("input") + send.ariaLabel = "send message" send.type = "text" + send.placeholder = "send a message..." - const messages = ediv({ class: "messages" }) + const messages = ediv({ class: "messages", aria_live: "polite" }) const controls = ediv({ class: "controls" }) controls.append(send) messages.append(document.createElement("hr")) - super(ediv({ class: "chat" }, messages, controls)) + super(esection({ class: "chat", aria_label: "chat", role: "dialog" }, messages, controls)) this.messages = messages this.controls = controls this.send_el = send @@ -61,6 +63,7 @@ export class Chat extends OverlayUi { } } + on_show(): void { this.focus() } focus() { this.send_el.focus() } send(msg: ChatMessage) { this.room.local_user.chat(msg) diff --git a/client-web/source/helper.ts b/client-web/source/helper.ts index c23beb6..1133afe 100644 --- a/client-web/source/helper.ts +++ b/client-web/source/helper.ts @@ -9,12 +9,25 @@ import { PREFS } from "./preferences/mod.ts"; const elem = <K extends keyof HTMLElementTagNameMap>(s: K): HTMLElementTagNameMap[K] => document.createElement(s) -interface Opts<El> { class?: string[] | string, id?: string, src?: string, onclick?: (e: El) => void } +interface Opts<El> { + class?: string[] | string, + id?: string, src?: string, + for?: string, + onclick?: (e: El) => void, + role?: "dialog" + aria_label?: string + aria_live?: "polite" | "assertive" + aria_modal?: boolean +} function apply_opts<El extends HTMLElement>(e: El, o: Opts<El> | undefined) { if (!o) return if (o.id) e.id = o.id if (o.onclick) e.onclick = () => o.onclick!(e) + if (o.aria_label) e.ariaLabel = o.aria_label + if (o.aria_live) e.ariaLive = o.aria_live + if (o.for) (e as unknown as HTMLLabelElement).htmlFor = o.for + if (o.aria_modal) e.ariaModal = "true" if (typeof o?.class == "string") e.classList.add(o.class) if (typeof o?.class == "object") e.classList.add(...o.class) } @@ -42,6 +55,9 @@ export const eh5 = elem_with_content("h5") export const eh6 = elem_with_content("h6") export const epre = elem_with_content("pre") export const ediv = elem_with_children("div") +export const efooter = elem_with_children("footer") +export const esection = elem_with_children("section") +export const enav = elem_with_children("nav") export const etr = elem_with_children("tr") export const etd = elem_with_children("td") export const eth = elem_with_children("th") @@ -59,10 +75,12 @@ export class OverlayUi { } get shown() { return this._shown } set shown(v: boolean) { - if (v && !this._shown) OVERLAYS.append(this.el) - if (!v && this._shown) OVERLAYS.removeChild(this.el) + if (v && !this._shown) OVERLAYS.append(this.el), this.on_show() + if (!v && this._shown) OVERLAYS.removeChild(this.el), this.on_hide() this._shown = v } + on_show() { } + on_hide() { } } export function image_view(url: string, opts?: Opts<HTMLElement>): HTMLElement { diff --git a/client-web/source/index.ts b/client-web/source/index.ts index bc52f8b..cc3492f 100644 --- a/client-web/source/index.ts +++ b/client-web/source/index.ts @@ -6,7 +6,7 @@ /// <reference lib="dom" /> import { init_serviceworker } from "./sw/init.ts"; -import { ediv, OVERLAYS } from "./helper.ts"; +import { esection, OVERLAYS } from "./helper.ts"; import { setup_keybinds } from "./keybinds.ts"; import { log, LOGGER_CONTAINER } from "./logger.ts" import { BottomMenu, MenuBr } from "./menu.ts"; @@ -15,7 +15,7 @@ import { SignalingConnection } from "./protocol/mod.ts"; import { Room } from "./room.ts" export const VERSION = "0.1.12" -export const ROOM_CONTAINER = ediv({ class: "room" }) +export const ROOM_CONTAINER = esection({ class: "room", aria_label: "user list" }) export const RTC_CONFIG: RTCConfiguration = { iceServers: [ diff --git a/client-web/source/keybinds.ts b/client-web/source/keybinds.ts index 5463e47..d096501 100644 --- a/client-web/source/keybinds.ts +++ b/client-web/source/keybinds.ts @@ -10,28 +10,23 @@ import { Room } from "./room.ts" import { update_serviceworker } from "./sw/init.ts"; export function setup_keybinds(room: Room) { - let command_mode = false + // let command_mode = false document.body.addEventListener("keydown", ev => { // TODO is there a proper solution? if (ev.target instanceof HTMLInputElement && !(ev.target.type == "button")) return if (ev.repeat) return - if (ev.code == "Enter") { + if (ev.code == "Enter" && ev.ctrlKey) { room.chat.shown = !room.chat.shown if (room.chat.shown) room.chat.focus() ev.preventDefault() // so focused buttons dont trigger } - if (ev.code == "Space") { - command_mode = true - ev.preventDefault() // so focused buttons dont trigger - return - } - if (command_mode) { + if (ev.shiftKey) { if (ev.code == "KeyM" || ev.code == "KeyR") room.local_user.await_add_resource(create_mic_res()) if (ev.code == "KeyS") room.local_user.await_add_resource(create_screencast_res()) if (ev.code == "KeyC" && !ev.ctrlKey) room.local_user.await_add_resource(create_camera_res()) if (ev.code == "KeyC" && ev.ctrlKey) room.local_user.resources.forEach(t => t.destroy()) if (ev.code == "KeyU") if (window.confirm("really update?")) update_serviceworker() } - command_mode = false + // command_mode = false }) } diff --git a/client-web/source/menu.ts b/client-web/source/menu.ts index 9126dc5..035f3aa 100644 --- a/client-web/source/menu.ts +++ b/client-web/source/menu.ts @@ -5,7 +5,7 @@ */ /// <reference lib="dom" /> -import { ebutton, ediv, ep, OverlayUi } from "./helper.ts" +import { ebutton, ediv, efooter, enav, ep, OverlayUi } from "./helper.ts" import { VERSION } from "./index.ts" import { PrefUi } from "./preferences/ui.ts" import { create_file_res } from "./resource/file.ts"; @@ -26,7 +26,7 @@ export class MenuBr extends OverlayUi { return p } - super(ediv({ class: "menu-br" }, + super(efooter({ class: "menu-br" }, ep(`keks-meet ${VERSION}`, { class: "version" }), item("License", "https://codeberg.org/metamuffin/keks-meet/raw/branch/master/COPYING"), item("Source code", "https://codeberg.org/metamuffin/keks-meet"), @@ -41,6 +41,7 @@ export class BottomMenu extends OverlayUi { const chat_toggle = document.createElement("input") chat_toggle.type = "button" chat_toggle.value = "Chat" + chat_toggle.ariaHasPopup = "menu" chat_toggle.onclick = () => { room.chat.shown = !room.chat.shown if (room.chat.shown) chat_toggle.classList.add("active") @@ -50,6 +51,7 @@ export class BottomMenu extends OverlayUi { const prefs_button = document.createElement("input") prefs_button.type = "button" prefs_button.value = "Settings" + prefs_button.ariaHasPopup = "menu" const prefs = new PrefUi() prefs_button.onclick = () => { @@ -58,13 +60,13 @@ export class BottomMenu extends OverlayUi { else prefs_button.classList.remove("active") } - const local_controls = ediv({ class: "local-controls" }, + const local_controls = ediv({ class: "local-controls", aria_label: "local resources" }, ebutton("Microphone", { onclick: () => room.local_user.await_add_resource(create_mic_res()) }), ebutton("Camera", { onclick: () => room.local_user.await_add_resource(create_camera_res()) }), ebutton("Screen", { onclick: () => room.local_user.await_add_resource(create_screencast_res()) }), ebutton("File", { onclick: () => room.local_user.await_add_resource(create_file_res()) }), ) - super(ediv({ class: "bottom-menu" }, chat_toggle, prefs_button, local_controls)) + super(enav({ class: "bottom-menu" }, chat_toggle, prefs_button, local_controls)) } } diff --git a/client-web/source/preferences/decl.ts b/client-web/source/preferences/decl.ts index effd885..f3f8e84 100644 --- a/client-web/source/preferences/decl.ts +++ b/client-web/source/preferences/decl.ts @@ -32,6 +32,8 @@ export const PREF_DECLS = { camera_facing_mode: { type: optional(string), possible_values: ["environment", "user"], description: "Prefer user-facing or env-facing camera" }, auto_gain_control: { type: bool, description: "Automatically adjust mic gain" }, echo_cancellation: { type: bool, description: "Cancel echo" }, + audio_activity_threshold: { type: number, optional: true, default: 0.003, description: "Audio activity threshold" }, + // TODO differenciate between mic, cam and screen optional_audio_default_enable: { type: bool, default: true, description: "Enable audio tracks by default" }, optional_video_default_enable: { type: bool, default: false, description: "Enable video tracks by default" }, diff --git a/client-web/source/preferences/ui.ts b/client-web/source/preferences/ui.ts index bc0d123..2b1d0c7 100644 --- a/client-web/source/preferences/ui.ts +++ b/client-web/source/preferences/ui.ts @@ -5,7 +5,7 @@ */ /// <reference lib="dom" /> -import { ebr, ebutton, ediv, elabel, espan, etd, etr, OverlayUi } from "../helper.ts"; +import { ebr, ebutton, ediv, eh2, elabel, espan, etd, etr, OverlayUi } from "../helper.ts"; import { PREF_DECLS } from "./decl.ts"; import { change_pref, on_pref_changed, PrefDecl, PREFS } from "./mod.ts"; @@ -65,7 +65,7 @@ export class PrefUi extends OverlayUi { if (decl.default === undefined || decl.optional) { const use_opt = document.createElement("input") use_opt.type = "checkbox" - use_opt.id = id + use_opt.id = "enable-" + id use_opt.checked = PREFS[key] !== undefined if (prim_control) prim_control.disabled = !use_opt.checked use_opt.onchange = () => { @@ -79,10 +79,10 @@ export class PrefUi extends OverlayUi { use_opt_ = use_opt; } - const label = elabel(decl.description ?? `[${key}]`, { id }) + const label = elabel(decl.description ?? `[${key}]`, { for: id }) return etr({ class: "pref" }, etd({}, label), etd({}, use_opt_ ?? ""), etd({}, prim_control ?? "")) }) - + const notification_perm = Notification.permission == "granted" ? ediv() : ediv({}, espan("For keks-meet to send notifications, it needs you to grant permission: "), ebutton("Grant", { onclick: () => Notification.requestPermission() }), @@ -95,7 +95,6 @@ export class PrefUi extends OverlayUi { const table = document.createElement("table") table.append(...rows) - super(ediv({ class: "prefs-overlay" }, notification_perm, reset, ebr(), table)) + super(ediv({ class: "prefs-overlay" }, eh2("Settings"), notification_perm, ebr(), table, ebr(), reset)) } - } diff --git a/client-web/source/resource/track.ts b/client-web/source/resource/track.ts index 7d53522..22af16f 100644 --- a/client-web/source/resource/track.ts +++ b/client-web/source/resource/track.ts @@ -5,7 +5,7 @@ */ /// <reference lib="dom" /> import { ProvideInfo } from "../../../common/packets.d.ts"; -import { ebutton, ediv } from "../helper.ts"; +import { ebutton, ediv, elabel } from "../helper.ts"; import { log } from "../logger.ts"; import { on_pref_changed, PREFS } from "../preferences/mod.ts"; import { get_rnnoise_node } from "../rnnoise.ts"; @@ -48,11 +48,12 @@ export const resource_track: ResourceHandlerDecl = { } } -export function new_local_track(info: ProvideInfo, track: TrackHandle): LocalResource { +export function new_local_track(info: ProvideInfo, track: TrackHandle, ...extra_controls: HTMLElement[]): LocalResource { return { info, el: ediv({}, - create_track_display(track) + create_track_display(track), + ...extra_controls ), destroy() { track.end() }, on_request(_user, _create_channel) { @@ -62,24 +63,55 @@ export function new_local_track(info: ProvideInfo, track: TrackHandle): LocalRes } function create_track_display(track: TrackHandle): HTMLElement { - const el = document.createElement("div") const is_video = track.kind == "video" - const media_el = is_video ? document.createElement("video") : document.createElement("audio") + const is_audio = track.kind == "audio" + const stream = new MediaStream([track.track]) + + const el = document.createElement("div") + + const media_el = is_video + ? document.createElement("video") + : document.createElement("audio") + media_el.srcObject = stream media_el.classList.add("media") media_el.autoplay = true media_el.controls = true media_el.addEventListener("pause", () => media_el.play()) + if (track.local) media_el.muted = true el.append(media_el) track.addEventListener("ended", () => { media_el.srcObject = null // TODO // TODO figure out why i wrote todo here el.remove() }) + + if (is_audio && PREFS.audio_activity_threshold !== undefined) check_volume(stream, vol => { + const active = vol > PREFS.audio_activity_threshold + if (active != el.classList.contains("audio-active")) { + if (active) el.classList.add("audio-active") + else el.classList.remove("audio-active") + } + }) + return el } +function check_volume(track: MediaStream, cb: (vol: number) => void) { + const ctx = new AudioContext(); + const s = ctx.createMediaStreamSource(track) + const a = ctx.createAnalyser() + s.connect(a) + const samples = new Float32Array(a.fftSize); + setInterval(() => { + a.getFloatTimeDomainData(samples); + let sum = 0.0; + for (const amplitude of samples) { sum += amplitude * amplitude; } + cb(Math.sqrt(sum / samples.length)) + }, 1000 / 15) +} + export async function create_camera_res() { log("media", "requesting user media (camera)") const user_media = await window.navigator.mediaDevices.getUserMedia({ @@ -141,5 +173,16 @@ export async function create_mic_res() { clear_gain_cb() destination.disconnect() }) - return new_local_track({ id: t.id, kind: "track", track_kind: "audio", label: "Microphone" }, t) + + const mute = document.createElement("input") + mute.type = "checkbox" + mute.onchange = () => { + log("media", mute.checked ? "muted" : "unmuted") + if (mute.checked) gain.gain.value = Number.MIN_VALUE + else gain.gain.value = PREFS.microphone_gain + } + const mute_label = elabel("Mute", { class: "check-button" }) + mute_label.prepend(mute) + + return new_local_track({ id: t.id, kind: "track", track_kind: "audio", label: "Microphone" }, t, mute_label) } @@ -11,6 +11,9 @@ server-build: watch: make -C client-web watch & make -C server watch +watch-public: + make -C client-web watch & + make -C server watch-public install: cargo +nightly install --force --path server cargo +nightly install --force --path client-native-gui @@ -36,6 +36,9 @@ make run # or `make build` When changing code, use `make watch` to re-build things automatically as needed. (run `cargo install systemfd cargo-watch` if needed) +The server's bind address can be controlled using the `BIND` environment +variable. + If you use this project or have any suggestions, please [contact me](https://metamuffin.org/contact) @@ -74,15 +77,15 @@ system works as follows: ## Keybinds -| Keybind | Action | -| --------- | ------------------------------------------------------- | -| `RET` | Toggle chat | -| `SPC M` | Add microphone track | -| `SPC R` | Add microphone track (but with your left hand) | -| `SPC C` | Add camera track | -| `SPC S` | Add screencast track | -| `SPC C-c` | End all tracks | -| `C-v`\* | Paste image in chat (does not require chat to be shown) | +| Keybind | Action | +| ------- | ------------------------------------------------------- | +| `C-RET` | Toggle chat | +| `M` | Add microphone track | +| `R` | Add microphone track (but with your left hand) | +| `C` | Add camera track | +| `S` | Add screencast track | +| `C-c` | End all tracks | +| `C-v`\* | Paste image in chat (does not require chat to be shown) | ## Debugging diff --git a/server/makefile b/server/makefile index 6299482..6097a10 100644 --- a/server/makefile +++ b/server/makefile @@ -5,5 +5,7 @@ run: cargo +nightly run --release watch: systemfd --no-pid -s http::8080 -- cargo watch -x '+nightly run' +watch-public: + systemfd --no-pid -s http::0.0.0.0:8080 -- cargo watch -x '+nightly run' target/release/keks-meet: $(shell find src) Cargo.toml cargo +nightly build --release diff --git a/server/src/main.rs b/server/src/main.rs index 85f1854..413f23a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -12,6 +12,8 @@ use log::{debug, error}; use room::Room; use std::collections::HashMap; use std::convert::Infallible; +use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; use warp::hyper::Server; @@ -69,13 +71,8 @@ async fn run() { Server::from_tcp(l).unwrap() } else { Server::bind( - &( - [127, 0, 0, 1], - std::env::var("PORT") - .map(|p| p.parse().unwrap()) - .unwrap_or(8080), - ) - .into(), + &SocketAddr::from_str(&std::env::var("BIND").unwrap_or(String::from("127.0.0.1:8080"))) + .unwrap(), ) }; let service = warp::service(routes); @@ -5,9 +5,12 @@ * client-web ** show who is watching your stream +** DONE mute +** DONE voice activity indicators +** ABANDONED fix tabbing order for chat ** TODO Make the optional streams UI prettier ** ABANDONED Maybe group tracks in streams to make sure everything is in sync -** How do we implement global hotkeys? +** ABANDONED How do we implement global hotkeys? ** ABANDONED Dont use websocket to send images to not block anything else ** PARTIAL File transfers via data channel (rift) CLOSED: [2022-10-28 Fri 21:48] |