diff options
Diffstat (limited to 'client-web/source/user')
-rw-r--r-- | client-web/source/user/local.ts | 68 | ||||
-rw-r--r-- | client-web/source/user/mod.ts | 49 | ||||
-rw-r--r-- | client-web/source/user/remote.ts | 64 |
3 files changed, 84 insertions, 97 deletions
diff --git a/client-web/source/user/local.ts b/client-web/source/user/local.ts index 9b34dba..4b057ee 100644 --- a/client-web/source/user/local.ts +++ b/client-web/source/user/local.ts @@ -7,12 +7,12 @@ import { get_rnnoise_node } from "../rnnoise.ts"; import { Room } from "../room.ts"; import { TrackHandle } from "../track_handle.ts"; import { User } from "./mod.ts"; -import { ROOM_CONTAINER } from "../index.ts"; import { ediv } from "../helper.ts"; -import { ChatMessage } from "../../../common/packets.d.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") @@ -22,28 +22,27 @@ export class LocalUser extends User { this.add_initial_tracks() log("usermodel", `added local user: ${this.display_name}`) } - leave() { // we might never need this but ok - this.room.local_user = undefined as unknown as LocalUser - super.leave() - ROOM_CONTAINER.removeChild(this.el) - } + leave() { throw new Error("local users cant leave"); } add_initial_tracks() { - if (PREFS.microphone_enabled) this.publish_track(this.create_mic_track()) - if (PREFS.camera_enabled) this.publish_track(this.create_camera_track()) - if (PREFS.screencast_enabled) this.publish_track(this.create_screencast_track()) + 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()) } - chat(message: ChatMessage) { - this.room.signaling.send_relay({ chat: message }) - } - - add_initial_to_remote(u: RemoteUser) { - this.tracks.forEach(t => u.peer.addTrack(t.track)) + 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") @@ -53,21 +52,30 @@ export class LocalUser extends User { mic_toggle.value = "Microphone" camera_toggle.value = "Camera" screen_toggle.value = "Screencast" - mic_toggle.addEventListener("click", () => this.publish_track(this.create_mic_track())) - camera_toggle.addEventListener("click", () => this.publish_track(this.create_camera_track())) - screen_toggle.addEventListener("click", () => this.publish_track(this.create_screencast_track())) + 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 publish_track(tp: Promise<TrackHandle>) { + async await_add_resource(tp: Promise<Resource>) { log("media", "awaiting track") - let t!: TrackHandle; + 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 }) + } + send_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 => { @@ -77,7 +85,7 @@ export class LocalUser extends User { }) } - async create_camera_track() { + async create_camera_res() { log("media", "requesting user media (camera)") const user_media = await window.navigator.mediaDevices.getUserMedia({ video: { @@ -86,10 +94,11 @@ export class LocalUser extends User { width: { ideal: PREFS.video_resolution } } }) - return new TrackHandle(user_media.getVideoTracks()[0], true) + const t = new TrackHandle(user_media.getVideoTracks()[0], true) + return new TrackResource(this, { id: t.id, kind: "video", label: "Camera" }, t) } - async create_screencast_track() { + async create_screencast_res() { log("media", "requesting user media (screen)") const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: { @@ -97,10 +106,11 @@ export class LocalUser extends User { width: { ideal: PREFS.video_resolution } }, }) - return new TrackHandle(user_media.getVideoTracks()[0], true) + const t = new TrackHandle(user_media.getVideoTracks()[0], true) + return new TrackResource(this, { id: t.id, kind: "video", label: "Screen" }, t) } - async create_mic_track() { + async create_mic_res() { log("media", "requesting user media (audio)") const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: { @@ -136,6 +146,6 @@ export class LocalUser extends User { clear_gain_cb() destination.disconnect() }) - return t + return new TrackResource(this, { id: t.id, kind: "audio", label: "Microphone" }, t) } } diff --git a/client-web/source/user/mod.ts b/client-web/source/user/mod.ts index 581ac7e..59c58b7 100644 --- a/client-web/source/user/mod.ts +++ b/client-web/source/user/mod.ts @@ -2,15 +2,13 @@ import { epre, espan } from "../helper.ts"; import { ROOM_CONTAINER } from "../index.ts"; -import { log } from "../logger.ts" +import { Resource } from "../resource/mod.ts"; import { Room } from "../room.ts" -import { TrackHandle } from "../track_handle.ts"; - export abstract class User { protected el: HTMLElement public local = false - public tracks: Set<TrackHandle> = new Set() + public resources: Map<string, Resource> = new Map() private name_el = espan("") protected stats_el = epre("", { class: "stats" }) @@ -31,21 +29,6 @@ export abstract class User { this.room.users.delete(this.id) } - 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) - }) - } - setup_view() { const info_el = document.createElement("div") info_el.classList.add("info") @@ -54,32 +37,4 @@ export abstract class User { info_el.append(this.name_el, this.stats_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 index acd52ac..110fd40 100644 --- a/client-web/source/user/remote.ts +++ b/client-web/source/user/remote.ts @@ -1,16 +1,21 @@ /// <reference lib="dom" /> import { RelayMessage } from "../../../common/packets.d.ts"; +import { Resource } from "../resource/mod.ts"; import { notify } from "../helper.ts"; import { ROOM_CONTAINER, RTC_CONFIG } from "../index.ts" import { log } from "../logger.ts" import { PREFS } from "../preferences/mod.ts"; import { Room } from "../room.ts" import { TrackHandle } from "../track_handle.ts"; -import { User } from "./mod.ts" +import { User } from "./mod.ts"; +import { TrackResource } from "../resource/track.ts"; export class RemoteUser extends User { peer: RTCPeerConnection + senders: Map<string, RTCRtpSender> = new Map() + data_channels: Map<string, RTCDataChannel> = new Map() + negotiation_busy = false constructor(room: Room, id: number) { @@ -28,7 +33,9 @@ export class RemoteUser extends User { this.peer.ontrack = ev => { const t = ev.track log("media", `remote track: ${this.display_name}`, t) - this.add_track(new TrackHandle(t)) + const r = this.resources.get(t.label) + if (r instanceof TrackResource) { r.track = new TrackHandle(t); r.state = "running" } + else log({ scope: "media", warn: true }, "got a track for a resource that should use data channel") this.update_stats() } this.peer.onnegotiationneeded = () => { @@ -38,26 +45,13 @@ export class RemoteUser extends User { this.update_stats() } this.peer.onicecandidateerror = () => { - console.log("onicecandidateerror") log({ scope: "webrtc", warn: true }, "ICE error") this.update_stats() } - this.peer.oniceconnectionstatechange = () => { - console.log("oniceconnectionstatechange") - this.update_stats() - } - this.peer.onicegatheringstatechange = () => { - console.log("onicegatheringstatechange") - this.update_stats() - } - this.peer.onsignalingstatechange = () => { - console.log("onsignalingstatechange") - this.update_stats() - } - this.peer.onconnectionstatechange = () => { - console.log("onconnectionstatechange") - this.update_stats() - } + this.peer.oniceconnectionstatechange = () => { this.update_stats() } + this.peer.onicegatheringstatechange = () => { this.update_stats() } + this.peer.onsignalingstatechange = () => { this.update_stats() } + this.peer.onconnectionstatechange = () => { this.update_stats() } this.update_stats() } leave() { @@ -78,6 +72,34 @@ export class RemoteUser extends User { this.name = message.identify.username if (PREFS.notify_join) notify(`${this.display_name} joined`) } + if (message.provide) { + const d = Resource.create(this, message.provide) + if (!d) return + this.el.append(d.el) + this.resources.set(message.provide.id, d) + } + if (message.provide_stop) { + this.resources.get(message.provide_stop.id)?.el.remove() + this.resources.delete(message.provide_stop.id) + } + if (message.request) { + const r = this.room.local_user.resources.get(message.request.id) + if (!r) return log({ scope: "*", warn: true }, "somebody requested an unknown resource") + if (r instanceof TrackResource) { + if (!r.track) throw new Error("local resources not avail"); + const sender = this.peer.addTrack(r.track.track) + this.senders.set(r.track.id, sender) + r.track.addEventListener("end", () => { this.senders.delete(r.track?.id ?? "") }) + } + } + if (message.request_stop) { + const sender = this.senders.get(message.request_stop.id) + if (!sender) return log({ scope: "*", warn: true }, "somebody requested us to stop transmitting an unknown resource") + this.peer.removeTrack(sender) + } + } + send_to(message: RelayMessage) { + this.room.signaling.send_relay(message, this.id) } async update_stats() { @@ -111,7 +133,7 @@ export class RemoteUser extends User { const offer_description = await this.peer.createOffer() await this.peer.setLocalDescription(offer_description) log("webrtc", `sent offer: ${this.display_name}`, { offer: offer_description.sdp }) - this.room.signaling.send_relay({ offer: offer_description.sdp }, this.id) + this.send_to({ offer: offer_description.sdp }) } async on_offer(offer: string) { this.negotiation_busy = true @@ -124,7 +146,7 @@ export class RemoteUser extends User { const answer_description = await this.peer.createAnswer() await this.peer.setLocalDescription(answer_description) log("webrtc", `sent answer: ${this.display_name}`, { answer: answer_description.sdp }) - this.room.signaling.send_relay({ answer: answer_description.sdp }, this.id) + this.send_to({ answer: answer_description.sdp }) this.negotiation_busy = false } async on_answer(answer: string) { |