aboutsummaryrefslogtreecommitdiff
path: root/source/client
diff options
context:
space:
mode:
Diffstat (limited to 'source/client')
-rw-r--r--source/client/index.ts192
-rw-r--r--source/client/room.ts53
-rw-r--r--source/client/types.ts13
-rw-r--r--source/client/user.ts60
4 files changed, 222 insertions, 96 deletions
diff --git a/source/client/index.ts b/source/client/index.ts
index 3841c8a..87421f2 100644
--- a/source/client/index.ts
+++ b/source/client/index.ts
@@ -1,130 +1,130 @@
+import { Room } from "./room"
export const servers = {
- iceServers: [
- {
- urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"]
- }
- ],
+ iceServers: [{ urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] }],
iceCandidatePoolSize: 10,
}
-let remote_stream: MediaStream, local_stream: MediaStream;
-let pc: RTCPeerConnection;
+export interface User {
+ peer: RTCPeerConnection
+ stream: MediaStream,
+}
-window.onload = async () => {
+export const users: Map<string, User> = new Map()
- if (window.location.search.startsWith("?offer=")) {
- await setup_webrtc()
- await offer(window.location.search.substr("?offer=".length))
- } else if (window.location.search.startsWith("?answer=")) {
- await setup_webrtc()
- await answer(window.location.search.substr("?answer=".length))
- } else {
+window.onload = async () => {
+ if (window.location.pathname.startsWith("/room/")) {
+ const room_name = window.location.pathname.substr("/room/".length)
+ let room = new Room(room_name)
+ document.body.append(room.el)
+ } else {
+ //TODO show ui for joining rooms
}
-
}
-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()
+// async function setup_webrtc() {
+// document.body.innerHTML = ""
- local_stream.getTracks().forEach(t => pc.addTrack(t, local_stream))
+// pc = new RTCPeerConnection(servers)
- pc.ontrack = ev => {
- console.log("peer got remote tracks", ev.streams);
- ev.streams[0].getTracks().forEach(t => remote_stream.addTrack(t))
- }
+// local_stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
+// remote_stream = new MediaStream()
+// local_stream.getTracks().forEach(t => pc.addTrack(t, local_stream))
- 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
+// pc.ontrack = ev => {
+// console.log("peer got remote tracks", ev.streams);
+// ev.streams[0].getTracks().forEach(t => remote_stream.addTrack(t))
+// }
- document.body.append(ls_el, rs_el)
-}
+// 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
-interface Offer {
- sdp: any,
- type: any
-}
+// document.body.append(ls_el, rs_el)
-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")
+// interface Offer {
+// sdp: any,
+// type: any
+// }
- pc.onicecandidate = ev => {
- const candidate = ev.candidate?.toJSON()
- if (!candidate) return
- ws.send(JSON.stringify({ candidate }))
- console.log("sent ice candidate", ev.candidate);
- }
+// 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())
- const offer_description = await pc.createOffer()
- await pc.setLocalDescription(offer_description);
+// console.log("websocket opened")
- const offer: Offer = { sdp: offer_description.sdp, type: offer_description.type };
+// pc.onicecandidate = ev => {
+// const candidate = ev.candidate?.toJSON()
+// if (!candidate) return
+// ws.send(JSON.stringify({ candidate }))
+// console.log("sent ice candidate", ev.candidate);
+// }
- ws.send(JSON.stringify({ offer }))
+// const offer_description = await pc.createOffer()
+// await pc.setLocalDescription(offer_description);
- 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)
- }
- }
+// const offer: Offer = { sdp: offer_description.sdp, type: offer_description.type };
-}
+// ws.send(JSON.stringify({ offer }))
-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");
+// 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)
+// }
+// }
+// }
- pc.onicecandidate = ev => {
- const candidate = ev.candidate?.toJSON()
- if (!candidate) return
- ws.send(JSON.stringify({ candidate }))
- console.log("sent ice candidate", 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");
- 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)
+// pc.onicecandidate = ev => {
+// const candidate = ev.candidate?.toJSON()
+// if (!candidate) return
+// ws.send(JSON.stringify({ candidate }))
+// console.log("sent ice candidate", candidate);
+// }
- 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))
- }
- }
-}
+// 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/room.ts b/source/client/room.ts
new file mode 100644
index 0000000..100f6cf
--- /dev/null
+++ b/source/client/room.ts
@@ -0,0 +1,53 @@
+import { CSPacket, SCPacket} from "./types";
+import { User } from "./user";
+
+
+export class Room {
+ el: HTMLElement
+ name: string
+ users: Map<string, User> = new Map()
+ websocket: WebSocket
+ local_user: User
+
+ constructor(name: string) {
+ this.name = name
+ this.el = document.createElement("div")
+
+ this.websocket = new WebSocket(`ws://${window.location.host}/room/${encodeURIComponent(name)}`)
+ this.websocket.onclose = () => this.websocket_close()
+ this.websocket.onopen = () => this.websocket_open()
+ this.websocket.onmessage = (ev) => {
+ this.websocket_message(JSON.parse(ev.data))
+ }
+ // const name = prompt() ?? "nameless user"
+ const uname = Math.random().toString()
+ this.local_user = new User(this, uname, true)
+ }
+
+ websocket_send(data: CSPacket) {
+ this.websocket.send(JSON.stringify(data))
+ }
+ websocket_message(packet: SCPacket) {
+ console.log("websocket message", packet);
+ if (packet.join) {
+ this.users.set(packet.sender, new User(this, packet.sender))
+ return
+ }
+ const sender = this.users.get(packet.sender)
+ if (!sender) return console.warn(`unknown sender ${packet.sender}`)
+ if (packet.leave) {
+ sender.leave()
+ this.users.delete(packet.sender)
+ return
+ }
+
+ if (packet.data.ice_candiate) sender.add_ice_candidate(packet.data.ice_candiate)
+ }
+ websocket_close() {
+ console.log("websocket closed");
+ }
+ websocket_open() {
+ console.log("websocket opened");
+ this.websocket.send(this.local_user.name)
+ }
+} \ No newline at end of file
diff --git a/source/client/types.ts b/source/client/types.ts
new file mode 100644
index 0000000..b235120
--- /dev/null
+++ b/source/client/types.ts
@@ -0,0 +1,13 @@
+
+export interface SCPacket {
+ sender: string,
+ data: CSPacket,
+ join?: boolean,
+ leave?: boolean
+}
+export interface CSPacket {
+ receiver?: string
+ ice_candiate?: RTCIceCandidateInit
+}
+
+
diff --git a/source/client/user.ts b/source/client/user.ts
new file mode 100644
index 0000000..fa60012
--- /dev/null
+++ b/source/client/user.ts
@@ -0,0 +1,60 @@
+import { Room } from "./room"
+
+
+
+export class User {
+ el: HTMLElement
+ el_video: HTMLVideoElement
+
+ name: string
+ local: boolean
+ peer: RTCPeerConnection
+ room: Room
+ stream: MediaStream
+
+ constructor(room: Room, name: string, local?: boolean) {
+ this.name = name
+ this.room = room
+ this.local = !!local
+ 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.muted = this.local
+ this.el_video.setAttribute("playsinline", "1")
+
+ this.peer = new RTCPeerConnection()
+ this.peer.onicecandidate = ev => {
+ if (!ev.candidate) return
+ room.websocket_send({ ice_candiate: ev.candidate.toJSON(), receiver: this.name })
+ console.log("sent rtc candidate", ev.candidate);
+ }
+ this.peer.ontrack = ev => {
+ console.log("got remote track", ev.streams);
+ ev.streams[0].getTracks().forEach(t => {
+ this.stream.addTrack(t)
+ })
+ }
+
+ if (this.local) this.get_local_media().then(stream => {
+ this.stream = stream
+ this.el_video.srcObject = stream
+ })
+
+ this.room.el.appendChild(this.el)
+ }
+
+ async get_local_media(): Promise<MediaStream> {
+ return await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
+ }
+
+ add_ice_candidate(candidate: RTCIceCandidateInit) {
+ this.peer.addIceCandidate(new RTCIceCandidate(candidate))
+ }
+
+
+ leave() {
+ this.room.el.removeChild(this.el)
+ }
+} \ No newline at end of file