aboutsummaryrefslogtreecommitdiff
path: root/source/client
diff options
context:
space:
mode:
authorMetaMuffin <metamuffin@yandex.com>2021-08-06 09:44:50 +0200
committerMetaMuffin <metamuffin@yandex.com>2021-08-06 09:44:50 +0200
commit57785600bb67dc1163945bd19ff3bbb9f0aab0c2 (patch)
tree02712d28da139eb0236d00e5727ecd324ab04d09 /source/client
parent9a44e9370cb3398bb4bb08a4a85f1d303a90031f (diff)
downloadkeks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar
keks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar.bz2
keks-meet-57785600bb67dc1163945bd19ff3bbb9f0aab0c2.tar.zst
b
Diffstat (limited to 'source/client')
-rw-r--r--source/client/index.ts117
-rw-r--r--source/client/local_user.ts65
-rw-r--r--source/client/remote_user.ts26
-rw-r--r--source/client/room.ts16
-rw-r--r--source/client/user.ts82
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