diff options
Diffstat (limited to 'client/src/assets/player/shared/player-options-builder/hls-options-builder.ts')
-rw-r--r-- | client/src/assets/player/shared/player-options-builder/hls-options-builder.ts | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts b/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts new file mode 100644 index 000000000..10df2db5d --- /dev/null +++ b/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts | |||
@@ -0,0 +1,227 @@ | |||
1 | import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core' | ||
2 | import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs' | ||
3 | import { logger } from '@root-helpers/logger' | ||
4 | import { LiveVideoLatencyMode } from '@shared/models' | ||
5 | import { getAverageBandwidthInStore } from '../../peertube-player-local-storage' | ||
6 | import { P2PMediaLoader, P2PMediaLoaderPluginOptions, PeerTubePlayerContructorOptions, PeerTubePlayerLoadOptions } from '../../types' | ||
7 | import { getRtcConfig, isSameOrigin } from '../common' | ||
8 | import { RedundancyUrlManager } from '../p2p-media-loader/redundancy-url-manager' | ||
9 | import { segmentUrlBuilderFactory } from '../p2p-media-loader/segment-url-builder' | ||
10 | import { SegmentValidator } from '../p2p-media-loader/segment-validator' | ||
11 | |||
12 | type ConstructorOptions = | ||
13 | Pick<PeerTubePlayerContructorOptions, 'pluginsManager' | 'serverUrl' | 'authorizationHeader'> & | ||
14 | Pick<PeerTubePlayerLoadOptions, 'videoPassword' | 'requiresUserAuth' | 'videoFileToken' | 'requiresPassword' | | ||
15 | 'isLive' | 'liveOptions' | 'p2pEnabled' | 'hls'> | ||
16 | |||
17 | export class HLSOptionsBuilder { | ||
18 | |||
19 | constructor ( | ||
20 | private options: ConstructorOptions, | ||
21 | private p2pMediaLoaderModule?: any | ||
22 | ) { | ||
23 | |||
24 | } | ||
25 | |||
26 | async getPluginOptions () { | ||
27 | const redundancyUrlManager = new RedundancyUrlManager(this.options.hls.redundancyBaseUrls) | ||
28 | const segmentValidator = new SegmentValidator({ | ||
29 | segmentsSha256Url: this.options.hls.segmentsSha256Url, | ||
30 | authorizationHeader: this.options.authorizationHeader, | ||
31 | requiresUserAuth: this.options.requiresUserAuth, | ||
32 | serverUrl: this.options.serverUrl, | ||
33 | requiresPassword: this.options.requiresPassword, | ||
34 | videoPassword: this.options.videoPassword | ||
35 | }) | ||
36 | |||
37 | const p2pMediaLoaderConfig = await this.options.pluginsManager.runHook( | ||
38 | 'filter:internal.player.p2p-media-loader.options.result', | ||
39 | this.getP2PMediaLoaderOptions({ redundancyUrlManager, segmentValidator }) | ||
40 | ) | ||
41 | const loader = new this.p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() as P2PMediaLoader | ||
42 | |||
43 | const p2pMediaLoader: P2PMediaLoaderPluginOptions = { | ||
44 | requiresUserAuth: this.options.requiresUserAuth, | ||
45 | videoFileToken: this.options.videoFileToken, | ||
46 | |||
47 | redundancyUrlManager, | ||
48 | type: 'application/x-mpegURL', | ||
49 | src: this.options.hls.playlistUrl, | ||
50 | segmentValidator, | ||
51 | loader | ||
52 | } | ||
53 | |||
54 | const hlsjs = { | ||
55 | hlsjsConfig: this.getHLSJSOptions(loader), | ||
56 | |||
57 | levelLabelHandler: (level: { height: number, width: number }) => { | ||
58 | const resolution = Math.min(level.height || 0, level.width || 0) | ||
59 | |||
60 | const file = this.options.hls.videoFiles.find(f => f.resolution.id === resolution) | ||
61 | // We don't have files for live videos | ||
62 | if (!file) return level.height | ||
63 | |||
64 | let label = file.resolution.label | ||
65 | if (file.fps >= 50) label += file.fps | ||
66 | |||
67 | return label | ||
68 | } | ||
69 | } | ||
70 | |||
71 | return { p2pMediaLoader, hlsjs } | ||
72 | } | ||
73 | |||
74 | // --------------------------------------------------------------------------- | ||
75 | |||
76 | private getP2PMediaLoaderOptions (options: { | ||
77 | redundancyUrlManager: RedundancyUrlManager | ||
78 | segmentValidator: SegmentValidator | ||
79 | }): HlsJsEngineSettings { | ||
80 | const { redundancyUrlManager, segmentValidator } = options | ||
81 | |||
82 | let consumeOnly = false | ||
83 | if ((navigator as any)?.connection?.type === 'cellular') { | ||
84 | logger.info('We are on a cellular connection: disabling seeding.') | ||
85 | consumeOnly = true | ||
86 | } | ||
87 | |||
88 | const trackerAnnounce = this.options.hls.trackerAnnounce | ||
89 | .filter(t => t.startsWith('ws')) | ||
90 | |||
91 | const specificLiveOrVODOptions = this.options.isLive | ||
92 | ? this.getP2PMediaLoaderLiveOptions() | ||
93 | : this.getP2PMediaLoaderVODOptions() | ||
94 | |||
95 | return { | ||
96 | loader: { | ||
97 | trackerAnnounce, | ||
98 | rtcConfig: getRtcConfig(), | ||
99 | |||
100 | simultaneousHttpDownloads: 1, | ||
101 | httpFailedSegmentTimeout: 1000, | ||
102 | |||
103 | xhrSetup: (xhr, url) => { | ||
104 | const { requiresUserAuth, requiresPassword } = this.options | ||
105 | |||
106 | if (!(requiresUserAuth || requiresPassword)) return | ||
107 | |||
108 | if (!isSameOrigin(this.options.serverUrl, url)) return | ||
109 | |||
110 | if (requiresPassword) xhr.setRequestHeader('x-peertube-video-password', this.options.videoPassword()) | ||
111 | |||
112 | else xhr.setRequestHeader('Authorization', this.options.authorizationHeader()) | ||
113 | }, | ||
114 | |||
115 | segmentValidator: segmentValidator.validate.bind(segmentValidator), | ||
116 | |||
117 | segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager), | ||
118 | |||
119 | useP2P: this.options.p2pEnabled, | ||
120 | consumeOnly, | ||
121 | |||
122 | ...specificLiveOrVODOptions | ||
123 | }, | ||
124 | segments: { | ||
125 | swarmId: this.options.hls.playlistUrl, | ||
126 | forwardSegmentCount: specificLiveOrVODOptions.p2pDownloadMaxPriority ?? 20 | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | private getP2PMediaLoaderLiveOptions (): Partial<HybridLoaderSettings> { | ||
132 | const base = { | ||
133 | requiredSegmentsPriority: 1 | ||
134 | } | ||
135 | |||
136 | const latencyMode = this.options.liveOptions.latencyMode | ||
137 | |||
138 | switch (latencyMode) { | ||
139 | case LiveVideoLatencyMode.SMALL_LATENCY: | ||
140 | return { | ||
141 | ...base, | ||
142 | |||
143 | useP2P: false, | ||
144 | requiredSegmentsPriority: 10 | ||
145 | } | ||
146 | |||
147 | case LiveVideoLatencyMode.HIGH_LATENCY: | ||
148 | return base | ||
149 | |||
150 | default: | ||
151 | return base | ||
152 | } | ||
153 | } | ||
154 | |||
155 | private getP2PMediaLoaderVODOptions (): Partial<HybridLoaderSettings> { | ||
156 | return { | ||
157 | requiredSegmentsPriority: 3, | ||
158 | skipSegmentBuilderPriority: 1, | ||
159 | |||
160 | cachedSegmentExpiration: 86400000, | ||
161 | cachedSegmentsCount: 100, | ||
162 | |||
163 | httpDownloadMaxPriority: 9, | ||
164 | httpDownloadProbability: 0.06, | ||
165 | httpDownloadProbabilitySkipIfNoPeers: true, | ||
166 | |||
167 | p2pDownloadMaxPriority: 50 | ||
168 | } | ||
169 | } | ||
170 | |||
171 | // --------------------------------------------------------------------------- | ||
172 | |||
173 | private getHLSJSOptions (loader: P2PMediaLoader) { | ||
174 | const specificLiveOrVODOptions = this.options.isLive | ||
175 | ? this.getHLSLiveOptions() | ||
176 | : this.getHLSVODOptions() | ||
177 | |||
178 | const base = { | ||
179 | capLevelToPlayerSize: true, | ||
180 | autoStartLoad: false, | ||
181 | |||
182 | loader, | ||
183 | |||
184 | ...specificLiveOrVODOptions | ||
185 | } | ||
186 | |||
187 | const averageBandwidth = getAverageBandwidthInStore() | ||
188 | if (!averageBandwidth) return base | ||
189 | |||
190 | return { | ||
191 | ...base, | ||
192 | |||
193 | abrEwmaDefaultEstimate: averageBandwidth * 8, // We want bit/s | ||
194 | backBufferLength: 90, | ||
195 | startLevel: -1, | ||
196 | testBandwidth: false, | ||
197 | debug: false | ||
198 | } | ||
199 | } | ||
200 | |||
201 | private getHLSLiveOptions () { | ||
202 | const latencyMode = this.options.liveOptions.latencyMode | ||
203 | |||
204 | switch (latencyMode) { | ||
205 | case LiveVideoLatencyMode.SMALL_LATENCY: | ||
206 | return { | ||
207 | liveSyncDurationCount: 2 | ||
208 | } | ||
209 | |||
210 | case LiveVideoLatencyMode.HIGH_LATENCY: | ||
211 | return { | ||
212 | liveSyncDurationCount: 10 | ||
213 | } | ||
214 | |||
215 | default: | ||
216 | return { | ||
217 | liveSyncDurationCount: 5 | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | |||
222 | private getHLSVODOptions () { | ||
223 | return { | ||
224 | liveSyncDurationCount: 5 | ||
225 | } | ||
226 | } | ||
227 | } | ||