aboutsummaryrefslogtreecommitdiff
path: root/client-web/source/local_user.ts
blob: 85a2a23cddbae237703296b4f620863b67c509d4 (plain)
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
/// <reference lib="dom" />

import { log } from "./logger.ts";
import { PREFS } from "./preferences.ts";
import { RemoteUser } from "./remote_user.ts";
import { get_rnnoise_node } from "./rnnoise.ts";
import { Room } from "./room.ts";
import { TrackHandle } from "./track_handle.ts";
import { User } from "./user.ts";

export class LocalUser extends User {
    mic_gain?: GainNode
    default_gain: number = PREFS.microphone_gain

    constructor(room: Room, id: number, name: string) {
        super(room, id, name)
        this.el.classList.add("local")
        this.local = true
        this.create_controls()
        this.add_initial_tracks()
        log("usermodel", `added local user: ${this.name}`)
    }

    async add_initial_tracks() {
        if (PREFS.microphone_enabled) this.publish_track(await this.create_mic_track())
        if (PREFS.camera_enabled) this.publish_track(await this.create_camera_track())
        if (PREFS.screencast_enabled) this.publish_track(await this.create_screencast_track())
    }

    publish_track(t: TrackHandle) {
        this.room.remote_users.forEach(u => u.peer.addTrack(t.track))
        this.add_track(t)
        t.addEventListener("ended", () => {
            this.room.remote_users.forEach(u => {
                u.peer.getSenders().forEach(s => {
                    if (s.track == t.track) u.peer.removeTrack(s)
                })
            })
        })
    }

    add_initial_to_remote(u: RemoteUser) {
        this.tracks.forEach(t => u.peer.addTrack(t.track))
    }

    create_controls() {
        const mic_toggle = document.createElement("input")
        const camera_toggle = document.createElement("input")
        const screen_toggle = document.createElement("input")
        mic_toggle.type = camera_toggle.type = screen_toggle.type = "button"
        mic_toggle.value = "Microphone"
        camera_toggle.value = "Camera"
        screen_toggle.value = "Screencast"

        const create = async (_e: HTMLElement, tp: Promise<TrackHandle>) => {
            log("media", "awaiting track")
            const t = await tp
            log("media", "got track")
            this.publish_track(t)
        }

        mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track()))
        camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track()))
        screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screencast_track()))

        const el = document.createElement("div")
        el.classList.add("local-controls")
        el.append(mic_toggle, camera_toggle, screen_toggle)
        document.body.append(el)
    }


    async create_camera_track() {
        log("media", "requesting user media (camera)")
        const user_media = await window.navigator.mediaDevices.getUserMedia({ video: true })
        return new TrackHandle(user_media.getVideoTracks()[0], true)
    }
    async create_screencast_track() {
        log("media", "requesting user media (screen)")
        const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true })
        return new TrackHandle(user_media.getVideoTracks()[0], true)
    }
    async create_mic_track() {
        log("media", "requesting user media (audio)")
        const audio_contraints = PREFS.rnnoise ? {
            channelCount: { ideal: 1 },
            noiseSuppression: { ideal: false },
            echoCancellation: { ideal: true },
            autoGainControl: { ideal: true },
        } : {
            channelCount: { ideal: 1 },
            noiseSuppression: { ideal: false },
            echoCancellation: { ideal: true },
            autoGainControl: { ideal: true },
        };

        const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints })
        const context = new AudioContext()
        const source = context.createMediaStreamSource(user_media)
        const destination = context.createMediaStreamDestination()
        const gain = context.createGain()
        gain.gain.value = this.default_gain
        this.mic_gain = gain

        let rnnoise: RNNoiseNode;
        if (PREFS.rnnoise) {
            rnnoise = await get_rnnoise_node(context)
            source.connect(rnnoise)
            rnnoise.connect(gain)
        } else {
            source.connect(gain)
        }
        gain.connect(destination)

        const t = new TrackHandle(destination.stream.getAudioTracks()[0], true)

        t.addEventListener("ended", () => {
            user_media.getTracks().forEach(t => t.stop())
            source.disconnect()
            if (rnnoise) rnnoise.disconnect()
            gain.disconnect()
            destination.disconnect()
            this.mic_gain = undefined
        })

        return t
    }
}