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
|
import { log } from "./logger.ts"
import { Room } from "./room.ts"
export abstract class User {
name: string
room: Room
el: HTMLElement
media_el?: HTMLElement
display?: { audio_status_el: HTMLElement, video_status_el: HTMLElement }
local = false
stream: MediaStream = new MediaStream()
constructor(room: Room, name: string) {
this.name = name
this.room = room
this.el = document.createElement("div")
this.el.classList.add("user")
this.room.el.append(this.el)
this.setup_view()
setTimeout(() => this.update_view(), 1)
}
add_track(t: MediaStreamTrack) {
this.stream.addTrack(t)
this.update_view()
t.onended = () => {
log("media", "track ended", t)
this.stream.removeTrack(t)
this.update_view()
}
t.onmute = () => {
log("media", "track muted", t)
this.stream.removeTrack(t)
this.update_view()
}
t.onunmute = () => {
log("media", "track unmuted", t)
this.stream.addTrack(t)
this.update_view()
}
}
setup_view() {
const info_el = document.createElement("div")
info_el.classList.add("info")
const name_el = document.createElement("span")
name_el.textContent = this.name
name_el.classList.add("name")
const audio_status_el = document.createElement("span")
const video_status_el = document.createElement("span")
video_status_el.classList.add("status", "video-status")
audio_status_el.classList.add("status", "audio-status")
audio_status_el.textContent = "A"
video_status_el.textContent = "V"
info_el.append(audio_status_el, video_status_el, name_el)
this.display = { video_status_el, audio_status_el }
this.el.append(info_el)
}
update_view() {
if (this.stream.getAudioTracks().length > 0)
this.display?.audio_status_el.classList.add("enabled")
else this.display?.audio_status_el.classList.remove("enabled")
if (this.stream.getVideoTracks().length > 0)
this.display?.video_status_el.classList.add("enabled")
else this.display?.video_status_el.classList.remove("enabled")
if (this.media_el) this.el.removeChild(this.media_el)
this.media_el = this.create_media_view()
this.el.appendChild(this.media_el)
}
create_media_view() {
const has_video = this.stream.getVideoTracks().length > 0
const has_audio = this.stream.getAudioTracks().length > 0
if (this.local && !has_video) return document.createElement("div")
const media_el = has_video ? document.createElement("video") : document.createElement("audio")
media_el.classList.add("media")
media_el.autoplay = true
if (has_video) media_el.toggleAttribute("playsinline")
media_el.srcObject = this.stream
if (has_video) media_el.addEventListener("click", () => {
media_el.classList.remove("maximized")
})
if (this.local) media_el.muted = true
const controls_el = document.createElement("div")
controls_el.classList.add("media-controls")
if (has_video) {
const pip_el = document.createElement("input")
pip_el.type = "button"
pip_el.addEventListener("click", () => {
// @ts-ignore firefox feature
media_el.requestPictureInPicture()
})
pip_el.value = "Picture-in-Picture"
const max_el = document.createElement("input")
max_el.type = "button"
max_el.addEventListener("click", () => {
media_el.classList.add("maximized")
})
max_el.value = "Maximize"
controls_el.append(max_el, pip_el)
}
if (has_audio) {
// TODO volume controls
}
const el = document.createElement("div")
el.classList.add("media-container")
el.append(media_el, controls_el)
return el
}
}
|