summaryrefslogtreecommitdiff
path: root/client-web/source/resource
diff options
context:
space:
mode:
Diffstat (limited to 'client-web/source/resource')
-rw-r--r--client-web/source/resource/file.ts44
-rw-r--r--client-web/source/resource/mod.ts167
-rw-r--r--client-web/source/resource/track.ts160
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)
+}