aboutsummaryrefslogtreecommitdiff
path: root/source/client
diff options
context:
space:
mode:
Diffstat (limited to 'source/client')
-rw-r--r--source/client/index.ts16
-rw-r--r--source/client/logger.ts15
-rw-r--r--source/client/room.ts24
-rw-r--r--source/client/types.ts9
-rw-r--r--source/client/user.ts52
5 files changed, 85 insertions, 31 deletions
diff --git a/source/client/index.ts b/source/client/index.ts
index 87421f2..31c8b1f 100644
--- a/source/client/index.ts
+++ b/source/client/index.ts
@@ -1,3 +1,4 @@
+import { log } from "./logger"
import { Room } from "./room"
export const servers = {
@@ -13,7 +14,13 @@ export interface User {
export const users: Map<string, User> = new Map()
-window.onload = async () => {
+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)
@@ -23,6 +30,13 @@ window.onload = async () => {
}
}
+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() {
diff --git a/source/client/logger.ts b/source/client/logger.ts
new file mode 100644
index 0000000..7d61570
--- /dev/null
+++ b/source/client/logger.ts
@@ -0,0 +1,15 @@
+
+export type LogTag = "webrtc" | "ws" | "media" | "*"
+const log_tag_color: { [key in LogTag]: string } = {
+ "*": "#FF0000",
+ webrtc: "#990099",
+ media: "#999900",
+ ws: "#009999"
+}
+
+// TODO maybe log time aswell
+export function log(tag: LogTag, message: string, ...data: any[]) {
+ console.log(`%c[${tag}] ${message}`, "color:" + log_tag_color[tag], ...data);
+}
+
+
diff --git a/source/client/room.ts b/source/client/room.ts
index 100f6cf..bea1582 100644
--- a/source/client/room.ts
+++ b/source/client/room.ts
@@ -1,4 +1,5 @@
-import { CSPacket, SCPacket} from "./types";
+import { log } from "./logger";
+import { CSPacket, SCPacket } from "./types";
import { User } from "./user";
@@ -7,7 +8,6 @@ export class Room {
name: string
users: Map<string, User> = new Map()
websocket: WebSocket
- local_user: User
constructor(name: string) {
this.name = name
@@ -19,35 +19,37 @@ export class Room {
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) {
+ log("ws", `-> ${data.receiver ?? "*"}`, data)
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))
+ log("*", `${this.name} ${packet.sender} joined`);
+ this.users.set(packet.sender, new User(this, packet.sender, !packet.stable))
return
}
const sender = this.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)
return
}
-
+ if (!packet.data) return console.warn("dataless packet")
+ log("ws", `<- ${packet.sender}: `, packet.data);
if (packet.data.ice_candiate) sender.add_ice_candidate(packet.data.ice_candiate)
+ if (packet.data.offer) sender.on_offer(packet.data.offer)
+ if (packet.data.answer) sender.on_answer(packet.data.answer)
}
websocket_close() {
- console.log("websocket closed");
+ log("ws", "websocket closed");
}
websocket_open() {
- console.log("websocket opened");
- this.websocket.send(this.local_user.name)
+ log("ws", "websocket opened");
+ this.websocket.send(Math.random().toString())
}
} \ No newline at end of file
diff --git a/source/client/types.ts b/source/client/types.ts
index b235120..7ce23ea 100644
--- a/source/client/types.ts
+++ b/source/client/types.ts
@@ -1,13 +1,16 @@
export interface SCPacket {
sender: string,
- data: CSPacket,
- join?: boolean,
- leave?: boolean
+ data?: CSPacket,
+ join?: boolean, // user just joined
+ leave?: boolean, // user left
+ stable?: boolean // user "joined" because you joined aka. user was already there
}
export interface CSPacket {
receiver?: string
ice_candiate?: RTCIceCandidateInit
+ offer?: RTCSessionDescriptionInit
+ answer?: RTCSessionDescriptionInit
}
diff --git a/source/client/user.ts b/source/client/user.ts
index fa60012..7848c8c 100644
--- a/source/client/user.ts
+++ b/source/client/user.ts
@@ -1,3 +1,5 @@
+import { local_media } from "."
+import { log } from "./logger"
import { Room } from "./room"
@@ -7,54 +9,72 @@ export class User {
el_video: HTMLVideoElement
name: string
- local: boolean
+
peer: RTCPeerConnection
+
room: Room
stream: MediaStream
- constructor(room: Room, name: string, local?: boolean) {
+
+ constructor(room: Room, name: string, offer: 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.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 })
- console.log("sent rtc candidate", ev.candidate);
}
this.peer.ontrack = ev => {
- console.log("got remote track", ev.streams);
+ 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 (this.local) this.get_local_media().then(stream => {
- this.stream = stream
- this.el_video.srcObject = stream
- })
-
- this.room.el.appendChild(this.el)
+ if (offer) this.offer()
}
- async get_local_media(): Promise<MediaStream> {
- return await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
+ 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)
}
add_ice_candidate(candidate: RTCIceCandidateInit) {
this.peer.addIceCandidate(new RTCIceCandidate(candidate))
}
-
leave() {
+ this.peer.close()
this.room.el.removeChild(this.el)
}
} \ No newline at end of file