summary refs log tree commit diff
path: root/src/webrtc/opcodes/Video.ts
blob: ff20d5a9acd2445e810387935c0c07c3a639e1e1 (plain) (blame)
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
import { Payload, Send, WebSocket } from "@fosscord/gateway";
import { validateSchema, VoiceVideoSchema } from "@fosscord/util";
import { channels, getClients, VoiceOPCodes } from "@fosscord/webrtc";
import { IncomingStreamTrack, SSRCs } from "medooze-media-server";
import SemanticSDP from "semantic-sdp";

export async function onVideo(this: WebSocket, payload: Payload) {
	if (!this.client) return;
	const { transport, channel_id } = this.client;
	if (!transport) return;
	const d = validateSchema("VoiceVideoSchema", payload.d) as VoiceVideoSchema;

	await Send(this, { op: VoiceOPCodes.MEDIA_SINK_WANTS, d: { any: 100 } });

	const id = "stream" + this.user_id;

	var stream = this.client.in.stream!;
	if (!stream) {
		stream = this.client.transport!.createIncomingStream(
			// @ts-ignore
			SemanticSDP.StreamInfo.expand({
				id,
				// @ts-ignore
				tracks: []
			})
		);
		this.client.in.stream = stream;

		const interval = setInterval(() => {
			for (const track of stream.getTracks()) {
				for (const layer of Object.values(track.getStats())) {
					console.log(track.getId(), layer.total);
				}
			}
		}, 5000);

		stream.on("stopped", () => {
			console.log("stream stopped");
			clearInterval(interval);
		});
		this.on("close", () => {
			transport!.stop();
		});
		const out = transport.createOutgoingStream(
			// @ts-ignore
			SemanticSDP.StreamInfo.expand({
				id: "out" + this.user_id,
				// @ts-ignore
				tracks: []
			})
		);
		this.client.out.stream = out;

		const clients = channels.get(channel_id)!;

		clients.forEach((client) => {
			if (client.websocket.user_id === this.user_id) return;
			if (!client.in.stream) return;

			client.in.stream?.getTracks().forEach((track) => {
				attachTrack.call(this, track, client.websocket.user_id);
			});
		});
	}

	if (d.audio_ssrc) {
		handleSSRC.call(this, "audio", { media: d.audio_ssrc, rtx: d.audio_ssrc + 1 });
	}
	if (d.video_ssrc && d.rtx_ssrc) {
		handleSSRC.call(this, "video", { media: d.video_ssrc, rtx: d.rtx_ssrc });
	}
}

function attachTrack(this: WebSocket, track: IncomingStreamTrack, user_id: string) {
	if (!this.client) return;
	const outTrack = this.client.transport!.createOutgoingStreamTrack(track.getMedia());
	outTrack.attachTo(track);
	this.client.out.stream!.addTrack(outTrack);
	var ssrcs = this.client.out.tracks.get(user_id)!;
	if (!ssrcs) ssrcs = this.client.out.tracks.set(user_id, { audio_ssrc: 0, rtx_ssrc: 0, video_ssrc: 0 }).get(user_id)!;

	if (track.getMedia() === "audio") {
		ssrcs.audio_ssrc = outTrack.getSSRCs().media!;
	} else if (track.getMedia() === "video") {
		ssrcs.video_ssrc = outTrack.getSSRCs().media!;
		ssrcs.rtx_ssrc = outTrack.getSSRCs().rtx!;
	}

	Send(this, {
		op: VoiceOPCodes.VIDEO,
		d: {
			user_id: user_id,
			...ssrcs
		} as VoiceVideoSchema
	});
}

function handleSSRC(this: WebSocket, type: "audio" | "video", ssrcs: SSRCs) {
	if (!this.client) return;
	const stream = this.client.in.stream!;
	const transport = this.client.transport!;

	const id = type + ssrcs.media;
	var track = stream.getTrack(id);
	if (!track) {
		console.log("createIncomingStreamTrack", id);
		track = transport.createIncomingStreamTrack(type, { id, ssrcs });
		stream.addTrack(track);

		const clients = getClients(this.client.channel_id)!;
		clients.forEach((client) => {
			if (client.websocket.user_id === this.user_id) return;
			if (!client.out.stream) return;

			attachTrack.call(this, track, client.websocket.user_id);
		});
	}
}