diff options
Diffstat (limited to 'source/client')
-rw-r--r-- | source/client/index.ts | 16 | ||||
-rw-r--r-- | source/client/logger.ts | 15 | ||||
-rw-r--r-- | source/client/room.ts | 24 | ||||
-rw-r--r-- | source/client/types.ts | 9 | ||||
-rw-r--r-- | source/client/user.ts | 52 |
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 |