From 0d4102fc2decaf2e1136bc55a261a9e85488791b Mon Sep 17 00:00:00 2001 From: metamuffin Date: Fri, 9 Sep 2022 13:03:48 +0200 Subject: move files --- client-web/source/local_user.ts | 131 --------------------------------------- client-web/source/remote_user.ts | 74 ---------------------- client-web/source/room.ts | 6 +- client-web/source/user.ts | 75 ---------------------- client-web/source/user/local.ts | 131 +++++++++++++++++++++++++++++++++++++++ client-web/source/user/mod.ts | 75 ++++++++++++++++++++++ client-web/source/user/remote.ts | 74 ++++++++++++++++++++++ 7 files changed, 283 insertions(+), 283 deletions(-) delete mode 100644 client-web/source/local_user.ts delete mode 100644 client-web/source/remote_user.ts delete mode 100644 client-web/source/user.ts create mode 100644 client-web/source/user/local.ts create mode 100644 client-web/source/user/mod.ts create mode 100644 client-web/source/user/remote.ts (limited to 'client-web/source') diff --git a/client-web/source/local_user.ts b/client-web/source/local_user.ts deleted file mode 100644 index 02d8da1..0000000 --- a/client-web/source/local_user.ts +++ /dev/null @@ -1,131 +0,0 @@ -/// - -import { log } from "./logger.ts"; -import { PREFS } from "./preferences.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { get_rnnoise_node } from "./rnnoise.ts"; -import { Room } from "./room.ts"; -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts"; - -export class LocalUser extends User { - mic_gain?: GainNode - default_gain: number = PREFS.microphone_gain - - constructor(room: Room, id: number) { - super(room, id) - this.el.classList.add("local") - this.local = true - this.create_controls() - this.add_initial_tracks() - log("usermodel", `added local user: ${this.display_name}`) - } - - async add_initial_tracks() { - if (PREFS.microphone_enabled) this.publish_track(await this.create_mic_track()) - if (PREFS.camera_enabled) this.publish_track(await this.create_camera_track()) - if (PREFS.screencast_enabled) this.publish_track(await this.create_screencast_track()) - } - - publish_track(t: TrackHandle) { - this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) - this.add_track(t) - t.addEventListener("ended", () => { - this.room.remote_users.forEach(u => { - u.peer.getSenders().forEach(s => { - if (s.track == t.track) u.peer.removeTrack(s) - }) - }) - }) - } - - add_initial_to_remote(u: RemoteUser) { - this.tracks.forEach(t => u.peer.addTrack(t.track)) - } - - create_controls() { - const mic_toggle = document.createElement("input") - const camera_toggle = document.createElement("input") - const screen_toggle = document.createElement("input") - mic_toggle.type = camera_toggle.type = screen_toggle.type = "button" - mic_toggle.value = "Microphone" - camera_toggle.value = "Camera" - screen_toggle.value = "Screencast" - - const create = async (_e: HTMLElement, tp: Promise) => { - log("media", "awaiting track") - const t = await tp - log("media", "got track") - this.publish_track(t) - } - - mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track())) - camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track())) - screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screencast_track())) - - const el = document.createElement("div") - el.classList.add("local-controls") - el.append(mic_toggle, camera_toggle, screen_toggle) - document.body.append(el) - } - - async create_camera_track() { - log("media", "requesting user media (camera)") - const user_media = await window.navigator.mediaDevices.getUserMedia({ - video: { facingMode: { ideal: PREFS.camera_facing_mode } } - }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - - async create_screencast_track() { - log("media", "requesting user media (screen)") - const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - - async create_mic_track() { - log("media", "requesting user media (audio)") - const audio_contraints = PREFS.rnnoise ? { - channelCount: { ideal: 1 }, - noiseSuppression: { ideal: false }, - echoCancellation: { ideal: true }, - autoGainControl: { ideal: true }, - } : { - channelCount: { ideal: 1 }, - noiseSuppression: { ideal: false }, - echoCancellation: { ideal: true }, - autoGainControl: { ideal: true }, - }; - - const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints }) - const context = new AudioContext() - const source = context.createMediaStreamSource(user_media) - const destination = context.createMediaStreamDestination() - const gain = context.createGain() - gain.gain.value = this.default_gain - this.mic_gain = gain - - let rnnoise: RNNoiseNode; - if (PREFS.rnnoise) { - rnnoise = await get_rnnoise_node(context) - source.connect(rnnoise) - rnnoise.connect(gain) - } else { - source.connect(gain) - } - 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() - destination.disconnect() - this.mic_gain = undefined - }) - - return t - } -} diff --git a/client-web/source/remote_user.ts b/client-web/source/remote_user.ts deleted file mode 100644 index 5283a7b..0000000 --- a/client-web/source/remote_user.ts +++ /dev/null @@ -1,74 +0,0 @@ -/// - -import { servers } from "./index.ts" -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts" - -export class RemoteUser extends User { - peer: RTCPeerConnection - negotiation_busy = false - - constructor(room: Room, id: number) { - super(room, id) - log("usermodel", `added remote user: ${id}`) - this.peer = new RTCPeerConnection(servers) - this.peer.onicecandidate = ev => { - if (!ev.candidate) return - room.signaling.send_relay({ ice_candidate: ev.candidate.toJSON() }, this.id) - } - this.peer.ontrack = ev => { - const t = ev.track - log("media", `remote track: ${this.name}`, t) - this.add_track(new TrackHandle(t)) - } - this.peer.onnegotiationneeded = async () => { - log("webrtc", `negotiation needed: ${this.name}`) - while (this.negotiation_busy) { - await new Promise(r => setTimeout(() => r(), 100)) - } - this.offer() - } - } - - async offer() { - this.negotiation_busy = true - const offer_description = await this.peer.createOffer() - await this.peer.setLocalDescription(offer_description) - const offer = { type: offer_description.type, sdp: offer_description.sdp } - log("webrtc", `sent offer: ${this.name}`, { a: offer }) - this.room.signaling.send_relay({ offer }, this.id) - } - async on_offer(offer: RTCSessionDescriptionInit) { - this.negotiation_busy = true - log("webrtc", `got offer: ${this.name}`, { a: offer }) - const offer_description = new RTCSessionDescription(offer) - await this.peer.setRemoteDescription(offer_description) - this.answer() - } - async answer() { - const answer_description = await this.peer.createAnswer() - await this.peer.setLocalDescription(answer_description) - const answer = { type: answer_description.type, sdp: answer_description.sdp } - log("webrtc", `sent answer: ${this.name}`, { a: answer }) - this.room.signaling.send_relay({ answer }, this.id) - this.negotiation_busy = false - } - async on_answer(answer: RTCSessionDescriptionInit) { - log("webrtc", `got answer: ${this.name}`, { a: answer }) - const answer_description = new RTCSessionDescription(answer) - await this.peer.setRemoteDescription(answer_description) - this.negotiation_busy = false - } - - add_ice_candidate(candidate: RTCIceCandidateInit) { - this.peer.addIceCandidate(new RTCIceCandidate(candidate)) - } - - leave() { - log("usermodel", `remove remote user: ${this.name}`) - this.peer.close() - this.room.el.removeChild(this.el) - } -} \ No newline at end of file diff --git a/client-web/source/room.ts b/client-web/source/room.ts index 961478e..8552162 100644 --- a/client-web/source/room.ts +++ b/client-web/source/room.ts @@ -1,9 +1,9 @@ /// import { log } from "./logger.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { User } from "./user.ts"; -import { LocalUser } from "./local_user.ts"; +import { RemoteUser } from "./user/remote.ts"; +import { User } from "./user/mod.ts"; +import { LocalUser } from "./user/local.ts"; import { ClientboundPacket, RelayMessage } from "../../common/packets.d.ts"; import { SignalingConnection } from "./protocol/mod.ts"; import { ediv } from "./helper.ts"; diff --git a/client-web/source/user.ts b/client-web/source/user.ts deleted file mode 100644 index 0bdf250..0000000 --- a/client-web/source/user.ts +++ /dev/null @@ -1,75 +0,0 @@ -/// - -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; - - -export abstract class User { - protected el: HTMLElement - public local = false - public name?: string - protected tracks: Set = new Set() - - constructor(public room: Room, public id: number) { - this.el = document.createElement("div") - this.el.classList.add("user") - this.room.el.append(this.el) - this.setup_view() - } - - add_track(t: TrackHandle) { - this.tracks.add(t) - this.create_track_element(t) - t.addEventListener("ended", () => { - log("media", "track ended", t) - this.tracks.delete(t) - }) - t.addEventListener("mute", () => { - log("media", "track muted", t) - }) - t.addEventListener("unmute", () => { - log("media", "track unmuted", t) - }) - } - - get display_name() { return this.name ?? `guest (${this.id})` } - - setup_view() { - const info_el = document.createElement("div") - info_el.classList.add("info") - const name_el = document.createElement("span") - name_el.textContent = this.display_name - name_el.classList.add("name") - info_el.append(name_el) - this.el.append(info_el) - } - - create_track_element(t: TrackHandle) { - const is_video = t.kind == "video" - const media_el = is_video ? document.createElement("video") : document.createElement("audio") - const stream = new MediaStream([t.track]) - media_el.srcObject = stream - media_el.classList.add("media") - media_el.autoplay = true - media_el.controls = true - - if (this.local) media_el.muted = true - - const el = document.createElement("div") - if (t.local) { - const end_button = document.createElement("button") - end_button.textContent = "End" - end_button.addEventListener("click", () => { - t.end() - }) - el.append(end_button) - } - el.append(media_el) - this.el.append(el) - t.addEventListener("ended", () => { - media_el.srcObject = null - el.remove() - }) - } -} \ No newline at end of file diff --git a/client-web/source/user/local.ts b/client-web/source/user/local.ts new file mode 100644 index 0000000..38bcfb9 --- /dev/null +++ b/client-web/source/user/local.ts @@ -0,0 +1,131 @@ +/// + +import { log } from "../logger.ts"; +import { PREFS } from "../preferences.ts"; +import { RemoteUser } from "./remote.ts"; +import { get_rnnoise_node } from "../rnnoise.ts"; +import { Room } from "../room.ts"; +import { TrackHandle } from "../track_handle.ts"; +import { User } from "./mod.ts"; + +export class LocalUser extends User { + mic_gain?: GainNode + default_gain: number = PREFS.microphone_gain + + constructor(room: Room, id: number) { + super(room, id) + this.el.classList.add("local") + this.local = true + this.create_controls() + this.add_initial_tracks() + log("usermodel", `added local user: ${this.display_name}`) + } + + async add_initial_tracks() { + if (PREFS.microphone_enabled) this.publish_track(await this.create_mic_track()) + if (PREFS.camera_enabled) this.publish_track(await this.create_camera_track()) + if (PREFS.screencast_enabled) this.publish_track(await this.create_screencast_track()) + } + + publish_track(t: TrackHandle) { + this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) + this.add_track(t) + t.addEventListener("ended", () => { + this.room.remote_users.forEach(u => { + u.peer.getSenders().forEach(s => { + if (s.track == t.track) u.peer.removeTrack(s) + }) + }) + }) + } + + add_initial_to_remote(u: RemoteUser) { + this.tracks.forEach(t => u.peer.addTrack(t.track)) + } + + create_controls() { + const mic_toggle = document.createElement("input") + const camera_toggle = document.createElement("input") + const screen_toggle = document.createElement("input") + mic_toggle.type = camera_toggle.type = screen_toggle.type = "button" + mic_toggle.value = "Microphone" + camera_toggle.value = "Camera" + screen_toggle.value = "Screencast" + + const create = async (_e: HTMLElement, tp: Promise) => { + log("media", "awaiting track") + const t = await tp + log("media", "got track") + this.publish_track(t) + } + + mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track())) + camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track())) + screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screencast_track())) + + const el = document.createElement("div") + el.classList.add("local-controls") + el.append(mic_toggle, camera_toggle, screen_toggle) + document.body.append(el) + } + + async create_camera_track() { + log("media", "requesting user media (camera)") + const user_media = await window.navigator.mediaDevices.getUserMedia({ + video: { facingMode: { ideal: PREFS.camera_facing_mode } } + }) + return new TrackHandle(user_media.getVideoTracks()[0], true) + } + + async create_screencast_track() { + log("media", "requesting user media (screen)") + const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true }) + return new TrackHandle(user_media.getVideoTracks()[0], true) + } + + async create_mic_track() { + log("media", "requesting user media (audio)") + const audio_contraints = PREFS.rnnoise ? { + channelCount: { ideal: 1 }, + noiseSuppression: { ideal: false }, + echoCancellation: { ideal: true }, + autoGainControl: { ideal: true }, + } : { + channelCount: { ideal: 1 }, + noiseSuppression: { ideal: false }, + echoCancellation: { ideal: true }, + autoGainControl: { ideal: true }, + }; + + const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints }) + const context = new AudioContext() + const source = context.createMediaStreamSource(user_media) + const destination = context.createMediaStreamDestination() + const gain = context.createGain() + gain.gain.value = this.default_gain + this.mic_gain = gain + + let rnnoise: RNNoiseNode; + if (PREFS.rnnoise) { + rnnoise = await get_rnnoise_node(context) + source.connect(rnnoise) + rnnoise.connect(gain) + } else { + source.connect(gain) + } + 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() + destination.disconnect() + this.mic_gain = undefined + }) + + return t + } +} diff --git a/client-web/source/user/mod.ts b/client-web/source/user/mod.ts new file mode 100644 index 0000000..6cb8715 --- /dev/null +++ b/client-web/source/user/mod.ts @@ -0,0 +1,75 @@ +/// + +import { log } from "../logger.ts" +import { Room } from "../room.ts" +import { TrackHandle } from "../track_handle.ts"; + + +export abstract class User { + protected el: HTMLElement + public local = false + public name?: string + protected tracks: Set = new Set() + + constructor(public room: Room, public id: number) { + this.el = document.createElement("div") + this.el.classList.add("user") + this.room.el.append(this.el) + this.setup_view() + } + + add_track(t: TrackHandle) { + this.tracks.add(t) + this.create_track_element(t) + t.addEventListener("ended", () => { + log("media", "track ended", t) + this.tracks.delete(t) + }) + t.addEventListener("mute", () => { + log("media", "track muted", t) + }) + t.addEventListener("unmute", () => { + log("media", "track unmuted", t) + }) + } + + get display_name() { return this.name ?? `guest (${this.id})` } + + setup_view() { + const info_el = document.createElement("div") + info_el.classList.add("info") + const name_el = document.createElement("span") + name_el.textContent = this.display_name + name_el.classList.add("name") + info_el.append(name_el) + this.el.append(info_el) + } + + create_track_element(t: TrackHandle) { + const is_video = t.kind == "video" + const media_el = is_video ? document.createElement("video") : document.createElement("audio") + const stream = new MediaStream([t.track]) + media_el.srcObject = stream + media_el.classList.add("media") + media_el.autoplay = true + media_el.controls = true + + if (this.local) media_el.muted = true + + const el = document.createElement("div") + if (t.local) { + const end_button = document.createElement("button") + end_button.textContent = "End" + end_button.addEventListener("click", () => { + t.end() + }) + el.append(end_button) + } + el.append(media_el) + this.el.append(el) + t.addEventListener("ended", () => { + media_el.srcObject = null + el.remove() + }) + } +} \ No newline at end of file diff --git a/client-web/source/user/remote.ts b/client-web/source/user/remote.ts new file mode 100644 index 0000000..7ded214 --- /dev/null +++ b/client-web/source/user/remote.ts @@ -0,0 +1,74 @@ +/// + +import { servers } from "../index.ts" +import { log } from "../logger.ts" +import { Room } from "../room.ts" +import { TrackHandle } from "../track_handle.ts"; +import { User } from "./mod.ts" + +export class RemoteUser extends User { + peer: RTCPeerConnection + negotiation_busy = false + + constructor(room: Room, id: number) { + super(room, id) + log("usermodel", `added remote user: ${id}`) + this.peer = new RTCPeerConnection(servers) + this.peer.onicecandidate = ev => { + if (!ev.candidate) return + room.signaling.send_relay({ ice_candidate: ev.candidate.toJSON() }, this.id) + } + this.peer.ontrack = ev => { + const t = ev.track + log("media", `remote track: ${this.name}`, t) + this.add_track(new TrackHandle(t)) + } + this.peer.onnegotiationneeded = async () => { + log("webrtc", `negotiation needed: ${this.name}`) + while (this.negotiation_busy) { + await new Promise(r => setTimeout(() => r(), 100)) + } + this.offer() + } + } + + async offer() { + this.negotiation_busy = true + const offer_description = await this.peer.createOffer() + await this.peer.setLocalDescription(offer_description) + const offer = { type: offer_description.type, sdp: offer_description.sdp } + log("webrtc", `sent offer: ${this.name}`, { a: offer }) + this.room.signaling.send_relay({ offer }, this.id) + } + async on_offer(offer: RTCSessionDescriptionInit) { + this.negotiation_busy = true + log("webrtc", `got offer: ${this.name}`, { a: offer }) + const offer_description = new RTCSessionDescription(offer) + await this.peer.setRemoteDescription(offer_description) + this.answer() + } + async answer() { + const answer_description = await this.peer.createAnswer() + await this.peer.setLocalDescription(answer_description) + const answer = { type: answer_description.type, sdp: answer_description.sdp } + log("webrtc", `sent answer: ${this.name}`, { a: answer }) + this.room.signaling.send_relay({ answer }, this.id) + this.negotiation_busy = false + } + async on_answer(answer: RTCSessionDescriptionInit) { + log("webrtc", `got answer: ${this.name}`, { a: answer }) + const answer_description = new RTCSessionDescription(answer) + await this.peer.setRemoteDescription(answer_description) + this.negotiation_busy = false + } + + add_ice_candidate(candidate: RTCIceCandidateInit) { + this.peer.addIceCandidate(new RTCIceCandidate(candidate)) + } + + leave() { + log("usermodel", `remove remote user: ${this.name}`) + this.peer.close() + this.room.el.removeChild(this.el) + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2