/// import { log } from "../logger.ts"; import { on_pref_changed, PREFS } from "../preferences/mod.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"; import { ediv } from "../helper.ts"; import { ChatMessage, ProvideInfo } from "../../../common/packets.d.ts"; import { TrackResource } from "../resource/track.ts"; import { Resource } from "../resource/mod.ts"; export class LocalUser extends User { constructor(room: Room, id: number) { super(room, id) this.el.classList.add("local") this.local = true this.name = PREFS.username this.create_controls() this.add_initial_tracks() log("usermodel", `added local user: ${this.display_name}`) } leave() { throw new Error("local users cant leave"); } add_initial_tracks() { if (PREFS.microphone_enabled) this.await_add_resource(this.create_mic_res()) if (PREFS.camera_enabled) this.await_add_resource(this.create_camera_res()) if (PREFS.screencast_enabled) this.await_add_resource(this.create_screencast_res()) } provide_initial_to_remote(u: RemoteUser) { this.resources.forEach(t => { if (t instanceof TrackResource && t.track) u.peer.addTrack(t.track.track) }) } identify(recipient?: number) { if (this.name) this.room.signaling.send_relay({ identify: { username: this.name } }, recipient) } chat(message: ChatMessage) { this.room.signaling.send_relay({ chat: message }) } 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" mic_toggle.addEventListener("click", () => this.await_add_resource(this.create_mic_res())) camera_toggle.addEventListener("click", () => this.await_add_resource(this.create_camera_res())) screen_toggle.addEventListener("click", () => this.await_add_resource(this.create_screencast_res())) return ediv({ class: "local-controls" }, mic_toggle, camera_toggle, screen_toggle) } async await_add_resource(tp: Promise) { log("media", "awaiting track") let t!: Resource; try { t = await tp } catch (_) { log("media", "request failed") } if (!t) return log("media", "got track") this.add_resource(t) } add_resource(r: Resource) { this.resources.set(r.info.id, r) this.el.append(r.el) const provide: ProvideInfo = r.info this.room.signaling.send_relay({ provide }) r.on_destroy = () => { this.el.removeChild(r.el); this.room.signaling.send_relay({ provide_stop: { id: r.info.id } }) } } send_track(t: TrackHandle) { this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) t.addEventListener("ended", () => { this.room.remote_users.forEach(u => { u.peer.getSenders().forEach(s => { if (s.track == t.track) u.peer.removeTrack(s) }) }) }) } async 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 } } }) const t = new TrackHandle(user_media.getVideoTracks()[0], true) return new TrackResource(this, { id: t.id, kind: "video", label: "Camera" }, t) } async 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 TrackResource(this, { id: t.id, kind: "video", label: "Screen" }, t) } async 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) } 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 TrackResource(this, { id: t.id, kind: "audio", label: "Microphone" }, t) } }