diff options
author | MetaMuffin <metamuffin@yandex.com> | 2021-08-06 09:44:50 +0200 |
---|---|---|
committer | MetaMuffin <metamuffin@yandex.com> | 2021-08-06 09:44:50 +0200 |
commit | 57785600bb67dc1163945bd19ff3bbb9f0aab0c2 (patch) | |
tree | 02712d28da139eb0236d00e5727ecd324ab04d09 | |
parent | 9a44e9370cb3398bb4bb08a4a85f1d303a90031f (diff) | |
download | keks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar keks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar.bz2 keks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar.zst |
b
-rw-r--r-- | source/client/index.ts | 117 | ||||
-rw-r--r-- | source/client/local_user.ts | 65 | ||||
-rw-r--r-- | source/client/remote_user.ts | 26 | ||||
-rw-r--r-- | source/client/room.ts | 16 | ||||
-rw-r--r-- | source/client/user.ts | 82 |
5 files changed, 103 insertions, 203 deletions
diff --git a/source/client/index.ts b/source/client/index.ts index 31c8b1f..ce3533f 100644 --- a/source/client/index.ts +++ b/source/client/index.ts @@ -16,11 +16,7 @@ export const users: Map<string, User> = new Map() window.onload = () => main() -export var local_media: MediaStream - - export async function main() { - await init_local_media() if (window.location.pathname.startsWith("/room/")) { const room_name = window.location.pathname.substr("/room/".length) let room = new Room(room_name) @@ -29,116 +25,3 @@ export async function main() { //TODO show ui for joining rooms } } - -export async function init_local_media() { - log("media", "requesting user media") - local_media = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - log("media", `got ${local_media.getTracks().length} local streams"`, local_media.getTracks()) -} - - - - -// async function setup_webrtc() { -// document.body.innerHTML = "" - -// pc = new RTCPeerConnection(servers) - -// local_stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }) -// remote_stream = new MediaStream() - -// local_stream.getTracks().forEach(t => pc.addTrack(t, local_stream)) - -// pc.ontrack = ev => { -// console.log("peer got remote tracks", ev.streams); -// ev.streams[0].getTracks().forEach(t => remote_stream.addTrack(t)) -// } - - -// const ls_el = document.createElement("video") -// const rs_el = document.createElement("video") -// ls_el.muted = true -// ls_el.autoplay = rs_el.autoplay = true -// ls_el.setAttribute("playsinline", "1") -// rs_el.setAttribute("playsinline", "1") -// ls_el.srcObject = local_stream -// rs_el.srcObject = remote_stream - -// document.body.append(ls_el, rs_el) - -// } - -// interface Offer { -// sdp: any, -// type: any -// } - -// async function offer(id: string) { -// const ws = new WebSocket(`ws://${window.location.host}/offer/${id}`) -// ws.onclose = ev => console.log("websocket closed: " + ev.reason); -// await new Promise<void>(r => ws.onopen = () => r()) - -// console.log("websocket opened") - -// pc.onicecandidate = ev => { -// const candidate = ev.candidate?.toJSON() -// if (!candidate) return -// ws.send(JSON.stringify({ candidate })) -// console.log("sent ice candidate", ev.candidate); -// } - -// const offer_description = await pc.createOffer() -// await pc.setLocalDescription(offer_description); - -// const offer: Offer = { sdp: offer_description.sdp, type: offer_description.type }; - -// ws.send(JSON.stringify({ offer })) - -// ws.onmessage = ev => { -// const s = JSON.parse(ev.data) -// if (s.answer) { -// console.log("got answer", s.answer); -// const answer_description = new RTCSessionDescription(s.answer) -// pc.setRemoteDescription(answer_description) -// } -// if (s.candidate) { -// console.log("got candidate", s.candidate); -// const candidate = new RTCIceCandidate(s.candidate) -// pc.addIceCandidate(candidate) -// } -// } - -// } - -// async function answer(id: string) { -// const ws = new WebSocket(`ws://${window.location.host}/answer/${id}`) -// ws.onclose = ev => console.log("websocket closed: " + ev.reason); -// await new Promise<void>(r => ws.onopen = () => r()) -// console.log("websocket opened"); - - -// pc.onicecandidate = ev => { -// const candidate = ev.candidate?.toJSON() -// if (!candidate) return -// ws.send(JSON.stringify({ candidate })) -// console.log("sent ice candidate", candidate); -// } - -// ws.onmessage = async ev => { -// const s = JSON.parse(ev.data) -// if (s.offer) { -// console.log("got offer", s.offer); -// await pc.setRemoteDescription(new RTCSessionDescription(s.offer)) - -// const answer_description = await pc.createAnswer() -// await pc.setLocalDescription(answer_description) - -// const answer: Offer = { type: answer_description.type, sdp: answer_description.sdp } -// ws.send(JSON.stringify({ answer })) -// } -// if (s.candidate) { -// console.log("got candidate", s.candidate); -// pc.addIceCandidate(new RTCIceCandidate(s.candidate)) -// } -// } -// } diff --git a/source/client/local_user.ts b/source/client/local_user.ts index 10a87b7..fcad4f3 100644 --- a/source/client/local_user.ts +++ b/source/client/local_user.ts @@ -4,31 +4,66 @@ import { User } from "./user"; export class LocalUser extends User { + + private audio_track?: MediaStreamTrack + private video_track?: MediaStreamTrack + constructor(room: Room, name: string) { super(room, name) - this.get_streams() + this.create_controls() + //@ts-ignore + window.ea = () => this.enable_audio() + //@ts-ignore + window.da = () => this.disable_audio() + //@ts-ignore + window.ev = () => this.enable_video() + //@ts-ignore + window.dv = () => this.disable_video() } - async get_streams() { - const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - await new Promise<void>(r => setTimeout(() => r(), 3000)) - for (const t of user_media.getTracks()) { - this.add_track(t) - } + create_controls() { + setTimeout(() => { + this.enable_video() + }, 3000) } - add_tracks_to_remote(u: RemoteUser) { - this.stream.forEach(t => { - u.peer.addTrack(t, new MediaStream()) - }) + async add_initial_to_remote(ru: RemoteUser) { + if (this.audio_track) ru.peer.addTrack(this.audio_track, new MediaStream()) + if (this.video_track) ru.peer.addTrack(this.video_track, new MediaStream()) } - add_track(t: MediaStreamTrack) { - this.update_view() - this.stream.push(t) + async enable_video() { + if (this.video_track) return + const user_media = await window.navigator.mediaDevices.getUserMedia({ video: true }) + console.log(user_media.getVideoTracks()); + const t = this.video_track = user_media.getVideoTracks()[0] + this.room.remote_users.forEach(u => u.peer.addTrack(t)) + } + async enable_audio() { + if (this.audio_track) return + const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: true }) + const t = this.audio_track = user_media.getAudioTracks()[0] + this.room.remote_users.forEach(u => u.peer.addTrack(t)) + } + async disable_video() { + if (!this.video_track) return + this.video_track = undefined this.room.remote_users.forEach(u => { - u.peer.addTrack(t) + u.peer.getSenders().forEach(s => { + if (s.track == this.video_track) u.peer.removeTrack(s) + }) }) } + async disable_audio() { + if (!this.audio_track) return + this.audio_track = undefined + this.room.remote_users.forEach(u => { + u.peer.getSenders().forEach(s => { + if (s.track == this.audio_track) u.peer.removeTrack(s) + }) + }) + } + + }
\ No newline at end of file diff --git a/source/client/remote_user.ts b/source/client/remote_user.ts index f859dd9..c7a9249 100644 --- a/source/client/remote_user.ts +++ b/source/client/remote_user.ts @@ -6,6 +6,7 @@ import { User } from "./user" export class RemoteUser extends User { peer: RTCPeerConnection + negotiation_busy: boolean = false constructor(room: Room, name: string) { super(room, name) @@ -16,16 +17,26 @@ export class RemoteUser extends User { } this.peer.ontrack = ev => { log("media", "remote track", ev.streams) - if (!ev.streams.length) return console.warn("no remote tracks") - ev.streams.forEach(s => s.getTracks().forEach(t => { - if (this.stream.find(u => u.id == t.id)) return - this.stream.push(t) - this.update_view() - })) + console.log(ev.track); + this.stream.addTrack(ev.track) + // if (!ev.streams.length) return console.warn("no remote tracks") + // ev.streams.forEach(s => s.getTracks().forEach(t => { + // this.stream.addTrack(t) + // })) + this.update_view() + } + this.peer.onnegotiationneeded = async () => { + log("webrtc", "negotiation needed") + while (this.negotiation_busy) { + await new Promise<void>(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 } @@ -33,6 +44,7 @@ export class RemoteUser extends User { this.room.websocket_send({ receiver: this.name, offer }) } async on_offer(offer: RTCSessionDescriptionInit) { + this.negotiation_busy = true log("webrtc", "got offer", offer) const offer_description = new RTCSessionDescription(offer) await this.peer.setRemoteDescription(offer_description) @@ -44,11 +56,13 @@ export class RemoteUser extends User { const answer = { type: answer_description.type, sdp: answer_description.sdp } log("webrtc", "sent answer", answer) this.room.websocket_send({ receiver: this.name, answer }) + this.negotiation_busy = false } async on_answer(answer: RTCSessionDescriptionInit) { log("webrtc", "got answer", answer) const answer_description = new RTCSessionDescription(answer) await this.peer.setRemoteDescription(answer_description) + this.negotiation_busy = false } add_ice_candidate(candidate: RTCIceCandidateInit) { diff --git a/source/client/room.ts b/source/client/room.ts index bea1582..9d315ba 100644 --- a/source/client/room.ts +++ b/source/client/room.ts @@ -1,12 +1,16 @@ import { log } from "./logger"; import { CSPacket, SCPacket } from "./types"; +import { RemoteUser } from "./remote_user"; import { User } from "./user"; +import { LocalUser } from "./local_user"; export class Room { el: HTMLElement name: string users: Map<string, User> = new Map() + remote_users: Map<string, RemoteUser> = new Map() + local_user: LocalUser websocket: WebSocket constructor(name: string) { @@ -19,6 +23,7 @@ export class Room { this.websocket.onmessage = (ev) => { this.websocket_message(JSON.parse(ev.data)) } + this.local_user = new LocalUser(this, Math.random().toString()) } websocket_send(data: CSPacket) { @@ -28,15 +33,20 @@ export class Room { websocket_message(packet: SCPacket) { if (packet.join) { log("*", `${this.name} ${packet.sender} joined`); - this.users.set(packet.sender, new User(this, packet.sender, !packet.stable)) + const ru = new RemoteUser(this, packet.sender) + this.local_user.add_initial_to_remote(ru) + if (!packet.stable) ru.offer() + this.users.set(packet.sender, ru) + this.remote_users.set(packet.sender, ru) return } - const sender = this.users.get(packet.sender) + const sender = this.remote_users.get(packet.sender) if (!sender) return console.warn(`unknown sender ${packet.sender}`) if (packet.leave) { log("*", `${this.name} ${packet.sender} left`); sender.leave() this.users.delete(packet.sender) + this.remote_users.delete(packet.sender) return } if (!packet.data) return console.warn("dataless packet") @@ -50,6 +60,6 @@ export class Room { } websocket_open() { log("ws", "websocket opened"); - this.websocket.send(Math.random().toString()) + this.websocket.send(this.local_user.name) } }
\ No newline at end of file diff --git a/source/client/user.ts b/source/client/user.ts index 7848c8c..b7ee24a 100644 --- a/source/client/user.ts +++ b/source/client/user.ts @@ -1,80 +1,38 @@ -import { local_media } from "." -import { log } from "./logger" import { Room } from "./room" - -export class User { - el: HTMLElement - el_video: HTMLVideoElement - +export abstract class User { name: string + room: Room - peer: RTCPeerConnection + el: HTMLElement + view_el?: HTMLElement - room: Room - stream: MediaStream + local: boolean = false + stream: MediaStream = new MediaStream() - constructor(room: Room, name: string, offer: boolean) { + constructor(room: Room, name: string) { this.name = name this.room = room - this.stream = new MediaStream() this.el = document.createElement("div") - this.el_video = document.createElement("video") - this.el.append(this.el_video) - this.el_video.autoplay = true - this.el_video.setAttribute("playsinline", "1") - this.room.el.appendChild(this.el) - - this.peer = new RTCPeerConnection() - local_media.getTracks().forEach(t => this.peer.addTrack(t, local_media)) - this.peer.onicecandidate = ev => { - if (!ev.candidate) return - room.websocket_send({ ice_candiate: ev.candidate.toJSON(), receiver: this.name }) - } - this.peer.ontrack = ev => { - log("media", "remote track", ev.streams) - if (!ev.streams.length) return console.warn("no remote tracks") - ev.streams[0].getTracks().forEach(t => { - this.stream.addTrack(t) - }) - } - if (offer) this.offer() + this.room.el.append(this.el) + this.update_view() } - async offer() { - 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", offer) - this.room.websocket_send({ receiver: this.name, offer }) - } - async on_offer(offer: RTCSessionDescriptionInit) { - log("webrtc", "got offer", offer) - const offer_description = new RTCSessionDescription(offer) - await this.peer.setRemoteDescription(offer_description) - this.answer(offer) - } - async answer(offer: RTCSessionDescriptionInit) { - 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", answer) - this.room.websocket_send({ receiver: this.name, answer }) - } - async on_answer(answer: RTCSessionDescriptionInit) { - log("webrtc", "got answer", answer) - const answer_description = new RTCSessionDescription(answer) - await this.peer.setRemoteDescription(answer_description) + update_view() { + if (this.view_el) this.el.removeChild(this.view_el) + this.view_el = this.create_view() + this.el.appendChild(this.view_el) } - add_ice_candidate(candidate: RTCIceCandidateInit) { - this.peer.addIceCandidate(new RTCIceCandidate(candidate)) - } + create_view() { + const el = document.createElement("video") + el.autoplay = true + el.toggleAttribute("playsinline") + el.srcObject = this.stream + console.log(el); - leave() { - this.peer.close() - this.room.el.removeChild(this.el) + return el } }
\ No newline at end of file |