@voltkit-rtc/client 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +35 -2
- package/dist/cjs/index.d.ts +33 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +61 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/quality.d.ts +45 -0
- package/dist/cjs/quality.d.ts.map +1 -0
- package/dist/cjs/quality.js +83 -0
- package/dist/cjs/quality.js.map +1 -0
- package/dist/esm/index.d.ts +33 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +56 -16
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/quality.d.ts +45 -0
- package/dist/esm/quality.d.ts.map +1 -0
- package/dist/esm/quality.js +77 -0
- package/dist/esm/quality.js.map +1 -0
- package/package.json +3 -2
- package/src/index.ts +74 -16
- package/src/quality.ts +91 -0
package/src/quality.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video quality tiers and the simulcast layers sent for each.
|
|
3
|
+
*
|
|
4
|
+
* A project can publish at 720p or 480p on any plan. 1080p uses more
|
|
5
|
+
* bandwidth, so the server allows it only for projects that have it enabled,
|
|
6
|
+
* and during the free trial; the join result says which (`video.maxQuality`),
|
|
7
|
+
* and the server caps each publisher's incoming bitrate to match.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type VideoQuality = '1080p' | '720p' | '480p';
|
|
11
|
+
|
|
12
|
+
export const VIDEO_QUALITIES: readonly VideoQuality[] = ['1080p', '720p', '480p'];
|
|
13
|
+
|
|
14
|
+
/** What the server allows this project to publish, from the join result. */
|
|
15
|
+
export interface VideoPolicy {
|
|
16
|
+
maxQuality: '1080p' | '720p';
|
|
17
|
+
/** The server's cap on one publisher's incoming bitrate, in bits per second. */
|
|
18
|
+
maxBitrate?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Servers from before quality tiers send no policy; they get the default tier. */
|
|
22
|
+
export const DEFAULT_VIDEO_POLICY: VideoPolicy = { maxQuality: '720p' };
|
|
23
|
+
|
|
24
|
+
/** Each tier's 16:9 frame. */
|
|
25
|
+
const TIER_SIZE: Record<VideoQuality, { width: number; height: number }> = {
|
|
26
|
+
'1080p': { width: 1920, height: 1080 },
|
|
27
|
+
'720p': { width: 1280, height: 720 },
|
|
28
|
+
'480p': { width: 854, height: 480 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const RANK: Record<VideoQuality, number> = { '480p': 0, '720p': 1, '1080p': 2 };
|
|
32
|
+
|
|
33
|
+
/** The lower of what was asked for and what the project may use. */
|
|
34
|
+
export function allowedQuality(requested: VideoQuality | undefined, policy: VideoPolicy = DEFAULT_VIDEO_POLICY): VideoQuality {
|
|
35
|
+
const max = policy.maxQuality === '1080p' ? '1080p' : '720p';
|
|
36
|
+
const wanted = requested && requested in RANK ? requested : max;
|
|
37
|
+
return RANK[wanted] <= RANK[max] ? wanted : max;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The bitrate a layer of this many pixels gets, at up to 30 fps.
|
|
42
|
+
* About 1080p 2.5 Mbps, 720p 1.8 Mbps, 540p 1.2 Mbps, 480p 750 kbps,
|
|
43
|
+
* 360p 600 kbps, 240p 250 kbps, smaller 200 kbps.
|
|
44
|
+
*/
|
|
45
|
+
export function layerBitrate(pixels: number): number {
|
|
46
|
+
if (pixels >= 1_800_000) return 2_500_000;
|
|
47
|
+
if (pixels >= 800_000) return 1_800_000;
|
|
48
|
+
if (pixels >= 450_000) return 1_200_000;
|
|
49
|
+
if (pixels >= 360_000) return 750_000;
|
|
50
|
+
if (pixels >= 200_000) return 600_000;
|
|
51
|
+
if (pixels >= 100_000) return 250_000;
|
|
52
|
+
return 200_000;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VideoEncoding {
|
|
56
|
+
rid?: string;
|
|
57
|
+
scaleResolutionDownBy: number;
|
|
58
|
+
maxBitrate: number;
|
|
59
|
+
scalabilityMode: 'L1T3';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The simulcast layers for a camera picture of `width` × `height` at
|
|
64
|
+
* `quality`.
|
|
65
|
+
*
|
|
66
|
+
* The top layer is the picture scaled down to the tier (never up). The number
|
|
67
|
+
* of layers follows what browsers really send for a picture that size: Chrome
|
|
68
|
+
* sends three layers from 960×540 up, two from 480×270, one below that, and
|
|
69
|
+
* when a picture is too small for the layers asked for it drops the TOP ones,
|
|
70
|
+
* which would leave viewers with half or a quarter of the resolution. Asking
|
|
71
|
+
* for exactly as many layers as will be sent keeps the full picture.
|
|
72
|
+
*/
|
|
73
|
+
export function videoEncodings(width: number, height: number, quality: VideoQuality): VideoEncoding[] {
|
|
74
|
+
const tier = TIER_SIZE[quality] ?? TIER_SIZE['720p'];
|
|
75
|
+
// Size unknown (some tracks don't report it): assume the tier's own frame.
|
|
76
|
+
const w = width > 0 && height > 0 ? width : tier.width;
|
|
77
|
+
const h = width > 0 && height > 0 ? height : tier.height;
|
|
78
|
+
const scale = Math.max(1, Math.max(w, h) / tier.width);
|
|
79
|
+
const top = (w / scale) * (h / scale);
|
|
80
|
+
const layers = top >= 960 * 540 ? 3 : top >= 480 * 270 ? 2 : 1;
|
|
81
|
+
const round = (value: number) => Math.round(value * 1000) / 1000;
|
|
82
|
+
const layer = (divisor: number, rid?: string): VideoEncoding => ({
|
|
83
|
+
...(rid ? { rid } : {}),
|
|
84
|
+
scaleResolutionDownBy: round(scale * divisor),
|
|
85
|
+
maxBitrate: layerBitrate(top / (divisor * divisor)),
|
|
86
|
+
scalabilityMode: 'L1T3',
|
|
87
|
+
});
|
|
88
|
+
if (layers === 3) return [layer(4, 'q'), layer(2, 'h'), layer(1, 'f')];
|
|
89
|
+
if (layers === 2) return [layer(2, 'h'), layer(1, 'f')];
|
|
90
|
+
return [layer(1)];
|
|
91
|
+
}
|