1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
/// <reference lib="dom" />
import { RelayMessage } from "../../../common/packets.d.ts";
import { notify } from "../helper.ts";
import { ROOM_CONTAINER, RTC_CONFIG } from "../index.ts"
import { log } from "../logger.ts"
import { PREFS } from "../preferences/mod.ts";
import { Room } from "../room.ts"
import { TrackHandle } from "../track_handle.ts";
import { User } from "./mod.ts"
export class RemoteUser extends User {
peer: RTCPeerConnection
negotiation_busy = false
constructor(room: Room, id: number) {
super(room, id)
room.remote_users.set(this.id, this)
log("usermodel", `added remote user: ${this.display_name}`)
this.peer = new RTCPeerConnection(RTC_CONFIG)
this.peer.onicecandidate = ev => {
if (!ev.candidate) return
this.update_stats()
log("webrtc", `ICE candidate set`, ev.candidate)
room.signaling.send_relay({ ice_candidate: ev.candidate.toJSON() }, this.id)
}
this.peer.ontrack = ev => {
const t = ev.track
this.update_stats()
log("media", `remote track: ${this.display_name}`, t)
this.add_track(new TrackHandle(t))
}
this.peer.onnegotiationneeded = async () => {
log("webrtc", `negotiation needed: ${this.display_name}`)
this.update_stats()
while (this.negotiation_busy) {
await new Promise<void>(r => setTimeout(() => r(), 100))
}
this.offer()
}
this.peer.onicecandidateerror = () => {
log({ scope: "webrtc", warn: true }, "ICE error")
this.update_stats()
}
this.peer.oniceconnectionstatechange = () => {
this.update_stats()
}
this.peer.onicegatheringstatechange = () => {
this.update_stats()
}
this.peer.onsignalingstatechange = () => {
this.update_stats()
}
this.peer.onconnectionstatechange = () => {
this.update_stats()
}
this.update_stats()
}
leave() {
log("usermodel", `remove remote user: ${this.display_name}`)
this.peer.close()
this.room.remote_users.delete(this.id)
super.leave()
ROOM_CONTAINER.removeChild(this.el)
if (PREFS.notify_leave) notify(`${this.display_name} left`)
}
on_relay(message: RelayMessage) {
if (message.chat) this.room.chat.add_message(this, message.chat)
if (message.ice_candidate) this.add_ice_candidate(message.ice_candidate)
if (message.offer) this.on_offer(message.offer)
if (message.answer) this.on_answer(message.answer)
if (message.identify) {
this.name = message.identify.username
if (PREFS.notify_join) notify(`${this.display_name} joined`)
}
}
async update_stats() {
if (!PREFS.webrtc_debug) return
try {
const stats = await this.peer.getStats()
let stuff = "";
stuff += `ice-conn=${this.peer.iceConnectionState}; ice-gathering=${this.peer.iceGatheringState}; signaling=${this.peer.signalingState}\n`
stats.forEach(s => {
console.log("stat", s);
if (s.type == "candidate-pair" && s.selected) {
//@ts-ignore spec is weird....
if (!stats.get) return
//@ts-ignore spec is weird....
const cpstat = stats.get(s.localCandidateId)
if (!cpstat) return
console.log("cp", cpstat);
stuff += `via ${cpstat.candidateType}:${cpstat.protocol}:${cpstat.address}\n`
} else if (s.type == "codec") {
stuff += `using ${s.codecType ?? "dec/enc"}:${s.mimeType}(${s.sdpFmtpLine})\n`
}
})
this.stats_el.textContent = stuff
} catch (e) {
console.warn(e);
}
}
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 }
log("webrtc", `sent offer: ${this.display_name}`, { offer })
this.room.signaling.send_relay({ offer }, this.id)
}
async on_offer(offer: RTCSessionDescriptionInit) {
this.negotiation_busy = true
log("webrtc", `got offer: ${this.display_name}`, { offer })
const offer_description = new RTCSessionDescription(offer)
await this.peer.setRemoteDescription(offer_description)
this.answer()
}
async answer() {
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: ${this.display_name}`, { answer })
this.room.signaling.send_relay({ answer }, this.id)
this.negotiation_busy = false
}
async on_answer(answer: RTCSessionDescriptionInit) {
log("webrtc", `got answer: ${this.display_name}`, { answer })
const answer_description = new RTCSessionDescription(answer)
await this.peer.setRemoteDescription(answer_description)
this.negotiation_busy = false
}
add_ice_candidate(candidate: RTCIceCandidateInit) {
this.peer.addIceCandidate(new RTCIceCandidate(candidate))
this.update_stats()
}
}
|