diff options
author | metamuffin <metamuffin@disroot.org> | 2022-10-03 11:28:16 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2022-10-03 11:28:16 +0200 |
commit | 4e99a3325318c902cd78ea9f760f46d79acde5c0 (patch) | |
tree | cc2bc54f4a0eb27db2b5d38dfbb785c1e9b84bd6 /client-web/source/resource | |
parent | fa44b02da29a0bd1b60026d4f6ffd6c9748a09da (diff) | |
download | keks-meet-4e99a3325318c902cd78ea9f760f46d79acde5c0.tar keks-meet-4e99a3325318c902cd78ea9f760f46d79acde5c0.tar.bz2 keks-meet-4e99a3325318c902cd78ea9f760f46d79acde5c0.tar.zst |
riesencommit (part 1)
Diffstat (limited to 'client-web/source/resource')
-rw-r--r-- | client-web/source/resource/file.ts | 44 | ||||
-rw-r--r-- | client-web/source/resource/mod.ts | 167 | ||||
-rw-r--r-- | client-web/source/resource/track.ts | 160 |
3 files changed, 258 insertions, 113 deletions
diff --git a/client-web/source/resource/file.ts b/client-web/source/resource/file.ts index 690c765..b6629bc 100644 --- a/client-web/source/resource/file.ts +++ b/client-web/source/resource/file.ts @@ -1,11 +1,39 @@ -import { TrackHandle } from "../track_handle.ts"; -import { Resource } from "./mod.ts"; - -export class FileResource extends Resource { - - on_track(_track: TrackHandle): HTMLElement { - throw new Error("Method not implemented."); - } +import { ediv } from "../helper.ts"; +import { LocalResource, ResourceHandlerDecl } from "./mod.ts"; +export const resource_file: ResourceHandlerDecl = { + kind: "file", + new_remote(info, _user, _enable) { + return { + info, + el: ediv(), + on_statechange(_s) { }, + on_enable(_track, _disable) { + return { + on_disable() { + } + } + } + } + } } + +export function create_file_res(): Promise<LocalResource> { + const picker = document.createElement("input") + picker.type = "file" + picker.click() + return new Promise((resolve, reject) => { + picker.addEventListener("change", () => { + if (!picker.files) return reject() + const f = picker.files.item(0) + if (!f) return reject() + resolve({ + info: { kind: "file", id: Math.random().toString(), label: f.name, size: f.size }, + destroy() { /* TODO */ }, + el: ediv(), + on_request(_user, _create_channel) { return _create_channel("TODO") } + }) + }) + }) +}
\ No newline at end of file diff --git a/client-web/source/resource/mod.ts b/client-web/source/resource/mod.ts index 716d42b..f99a252 100644 --- a/client-web/source/resource/mod.ts +++ b/client-web/source/resource/mod.ts @@ -6,84 +6,111 @@ /// <reference lib="dom" /> import { ProvideInfo } from "../../../common/packets.d.ts" -import { ediv } from "../helper.ts"; import { TrackHandle } from "../track_handle.ts"; -import { LocalUser } from "../user/local.ts"; -import { User } from "../user/mod.ts" import { RemoteUser } from "../user/remote.ts" -import { TrackResource } from "./track.ts"; +import { resource_file } from "./file.ts"; +import { resource_track } from "./track.ts"; -export type ChannelState = "enabled" | "await_enable" | "disabled" | "await_disable" -export abstract class Resource extends EventTarget { - local: boolean - el: HTMLElement = ediv({ class: ["channel"] }) - inner_el?: HTMLElement +// export abstract class Resource extends EventTarget { +// abstract transport_method: TransportMethod +// local: boolean +// el: HTMLElement = ediv({ class: ["channel"] }) +// inner_el?: HTMLElement - constructor( - public user: User, - public info: ProvideInfo, - ) { - super() - this.local = this.user instanceof LocalUser - const button = document.createElement("button") - button.onclick = () => { - this.state == "enabled" ? this.request_stop() : this.request() - } - this.addEventListener("statechange", () => { - if (this.user instanceof LocalUser) button.textContent = "End", button.disabled = false - else if (this.state == "enabled") button.textContent = "Disable", button.disabled = false - else if (this.state == "disabled") button.textContent = `Enable ${this.info.kind}`, button.disabled = false - else button.textContent = "Working…", button.disabled = true; - }) - this.dispatchEvent(new CustomEvent("statechange")) - this.el.append(button) - } +// constructor( +// public user: User, +// public info: ProvideInfo, +// ) { +// super() +// this.local = this.user instanceof LocalUser +// const button = document.createElement("button") +// button.onclick = () => { +// this.state == "enabled" ? this.request_stop() : this.request() +// } +// this.addEventListener("statechange", () => { +// if (this.local) button.textContent = "End", button.disabled = false +// else if (this.state == "enabled") button.textContent = "Disable", button.disabled = false +// else if (this.state == "disabled") button.textContent = `Enable ${this.info.kind}`, button.disabled = false +// else button.textContent = "Working…", button.disabled = true; +// }) +// this.dispatchEvent(new CustomEvent("statechange")) +// this.el.append(button) +// } - static create(user: User, info: ProvideInfo): Resource { - if (info.kind == "audio" || info.kind == "video") return new TrackResource(user, info) - else throw new Error("blub"); - } +// static create(user: User, info: ProvideInfo): Resource { +// } - private _state: ChannelState = "disabled" - get state() { return this._state } - set state(value: ChannelState) { - const old_value = this._state - this._state = value - if (value != old_value) this.dispatchEvent(new CustomEvent("statechange")) - } +// private _state: ChannelState = "disabled" +// get state() { return this._state } +// set state(value: ChannelState) { +// const old_value = this._state +// this._state = value +// if (value != old_value) this.dispatchEvent(new CustomEvent("statechange")) +// } - private _track?: TrackHandle - get track() { return this._track } - set track(value: TrackHandle | undefined) { - const handle_end = () => { - this.track = undefined - this.state = "disabled" - this.inner_el?.remove() - if (this.user instanceof LocalUser) this.destroy() - } - this._track?.removeEventListener("ended", handle_end) - this._track = value - if (value) this.el.append(this.inner_el = this.on_track(value)) - if (value) this.state = "enabled" - else this.state = "disabled" - this._track?.addEventListener("ended", handle_end) - } +// private _channel?: TrackHandle | RTCDataChannel +// get channel() { return this._channel } +// set channel(value: TrackHandle | RTCDataChannel | undefined) { +// const handle_end = () => { +// this.channel = undefined +// this.state = "disabled" +// this.inner_el?.remove() +// if (this.user instanceof LocalUser) this.destroy() +// } +// this._channel?.removeEventListener("ended", handle_end) +// this._channel = value +// if (value) this.el.append(this.inner_el = this.on_channel(value)) +// if (value) this.state = "enabled" +// else this.state = "disabled" +// this._channel?.addEventListener("ended", handle_end) +// } - abstract on_track(_track: TrackHandle): HTMLElement +// abstract on_channel(channel: TrackHandle | RTCDataChannel): HTMLElement +// abstract on_request(): void; - destroy() { this.dispatchEvent(new CustomEvent("destroy")) } +// destroy() { this.dispatchEvent(new CustomEvent("destroy")) } - request() { - if (!(this.user instanceof RemoteUser)) return - this.state = "await_enable" - this.user.send_to({ request: { id: this.info.id } }) - } - request_stop() { - if (this.user instanceof RemoteUser) { - this.state = "await_disable" - this.user.send_to({ request_stop: { id: this.info.id } }) - } else if (this.user instanceof LocalUser) { - this.destroy() - } - } +// request() { +// if (!(this.user instanceof RemoteUser)) return +// this.state = "await_enable" +// this.user.send_to({ request: { id: this.info.id } }) +// } +// request_stop() { +// if (this.user instanceof RemoteUser) { +// this.state = "await_disable" +// this.user.send_to({ request_stop: { id: this.info.id } }) +// } else if (this.user instanceof LocalUser) { +// this.destroy() +// } +// } +// } + +export type TransportMethod = "data-channel" | "track" +export type RemoteResourceState = "connected" | "disconnected" | "await_connect" | "await_disconnect" +export interface ResourceHandlerDecl { + kind: string + new_remote(info: ProvideInfo, user: RemoteUser, enable: () => void): RemoteResource +} +export interface RemoteResource { + el: HTMLElement + info: ProvideInfo, + on_statechange(state: RemoteResourceState): void + on_enable(t: TrackHandle | RTCDataChannel, disable: () => void): void +} +export interface LocalResource { + el: HTMLElement + info: ProvideInfo, + destroy(): void + on_request(user: RemoteUser, create_channel: (label: string) => RTCDataChannel): TrackHandle | RTCDataChannel } + +const RESOURCE_HANDLERS: ResourceHandlerDecl[] = [resource_file, resource_track] + +export function new_remote_resource(user: RemoteUser, info: ProvideInfo): RemoteResource | undefined { + const h = RESOURCE_HANDLERS.find(h => h.kind == info.kind) + if (!h) return undefined + const res = h.new_remote(info, user, () => { + user.request_resource(res) + }) + return res +}
\ No newline at end of file diff --git a/client-web/source/resource/track.ts b/client-web/source/resource/track.ts index 1ae6f94..0e99416 100644 --- a/client-web/source/resource/track.ts +++ b/client-web/source/resource/track.ts @@ -4,48 +4,138 @@ Copyright (C) 2022 metamuffin <metamuffin@disroot.org> */ /// <reference lib="dom" /> - import { ProvideInfo } from "../../../common/packets.d.ts"; +import { ebutton, ediv } from "../helper.ts"; +import { log } from "../logger.ts"; +import { on_pref_changed, PREFS } from "../preferences/mod.ts"; +import { get_rnnoise_node } from "../rnnoise.ts"; import { TrackHandle } from "../track_handle.ts"; -import { User } from "../user/mod.ts"; -import { Resource } from "./mod.ts"; +import { LocalResource, ResourceHandlerDecl } from "./mod.ts"; -export class TrackResource extends Resource { - constructor(user: User, info: ProvideInfo, track?: TrackHandle) { - super(user, info) - this.track = track +export const resource_track: ResourceHandlerDecl = { + kind: "track", + new_remote: (info, _user, enable) => { + const enable_button = ebutton("Enable", { + onclick: self => { + self.disabled = true; + self.textContent = "Awaiting track…"; + enable() + } + }) + return { + info, + el: ediv({}, enable_button), + on_statechange() { }, + on_enable(track, disable) { + this.el.removeChild(enable_button) + this.el.append(ebutton("Disable", { + onclick: (self) => { + disable() + this.el.appendChild(enable_button) + self.disabled = true + enable_button.disabled = false + enable_button.textContent = "Enable"; + self.remove() + } + })) + if (!(track instanceof TrackHandle)) return console.warn("aservuoivasretuoip"); + this.el.append(create_track_display(track)) + } + } } +} - destroy() { - this.track?.end() - super.destroy() +export function new_local_track(info: ProvideInfo, track: TrackHandle): LocalResource { + return { + info, + el: ediv({}, + create_track_display(track) + ), + destroy() { track.end() }, + on_request(_user, _create_channel) { + return track + } } +} - on_track(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 stream = new MediaStream([track.track]) - media_el.srcObject = stream - media_el.classList.add("media") - media_el.autoplay = true - media_el.controls = true - if (track.local) media_el.muted = true - el.append(media_el) +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 stream = new MediaStream([track.track]) + media_el.srcObject = stream + media_el.classList.add("media") + media_el.autoplay = true + media_el.controls = true + 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() + }) + return el +} - if (track.local) { - const end_button = document.createElement("button") - end_button.textContent = "End" - end_button.addEventListener("click", () => { - track?.end() - }) - el.append(end_button) +export async function create_camera_res() { + log("media", "requesting user media (camera)") + const user_media = await window.navigator.mediaDevices.getUserMedia({ + video: { + facingMode: { ideal: PREFS.camera_facing_mode }, + frameRate: { ideal: PREFS.video_fps }, + width: { ideal: PREFS.video_resolution } } - this.el.append(el) - track.addEventListener("ended", () => { - media_el.srcObject = null // TODO - el.remove() - }) - return el + }) + const t = new TrackHandle(user_media.getVideoTracks()[0], true) + return new_local_track({ id: t.id, kind: "track", track_kind: "video", label: "Camera" }, t) +} + +export async function create_screencast_res() { + log("media", "requesting user media (screen)") + const user_media = await window.navigator.mediaDevices.getDisplayMedia({ + video: { + frameRate: { ideal: PREFS.video_fps }, + width: { ideal: PREFS.video_resolution } + }, + }) + const t = new TrackHandle(user_media.getVideoTracks()[0], true) + return new_local_track({ id: t.id, kind: "track", track_kind: "video", label: "Screen" }, t) +} + +export async function create_mic_res() { + log("media", "requesting user media (audio)") + const user_media = await window.navigator.mediaDevices.getUserMedia({ + audio: { + channelCount: { ideal: 1 }, + noiseSuppression: { ideal: PREFS.rnnoise ? false : PREFS.native_noise_suppression }, + echoCancellation: { ideal: PREFS.echo_cancellation }, + autoGainControl: { ideal: PREFS.auto_gain_control }, + } + }) + const context = new AudioContext() + const source = context.createMediaStreamSource(user_media) + const destination = context.createMediaStreamDestination() + const gain = context.createGain() + gain.gain.value = PREFS.microphone_gain + const clear_gain_cb = on_pref_changed("microphone_gain", () => gain.gain.value = PREFS.microphone_gain) + + let rnnoise: RNNoiseNode; + if (PREFS.rnnoise) { + rnnoise = await get_rnnoise_node(context) + source.connect(rnnoise) + rnnoise.connect(gain) + } else { + source.connect(gain) } -}
\ No newline at end of file + gain.connect(destination) + + const t = new TrackHandle(destination.stream.getAudioTracks()[0], true) + t.addEventListener("ended", () => { + user_media.getTracks().forEach(t => t.stop()) + source.disconnect() + if (rnnoise) rnnoise.disconnect() + gain.disconnect() + clear_gain_cb() + destination.disconnect() + }) + return new_local_track({ id: t.id, kind: "track", track_kind: "audio", label: "Microphone" }, t) +} |