aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/standalone/videos/shared/player-options-builder.ts
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/standalone/videos/shared/player-options-builder.ts')
-rw-r--r--client/src/standalone/videos/shared/player-options-builder.ts440
1 files changed, 440 insertions, 0 deletions
diff --git a/client/src/standalone/videos/shared/player-options-builder.ts b/client/src/standalone/videos/shared/player-options-builder.ts
new file mode 100644
index 000000000..8a4e32444
--- /dev/null
+++ b/client/src/standalone/videos/shared/player-options-builder.ts
@@ -0,0 +1,440 @@
1import { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
2import {
3 HTMLServerConfig,
4 LiveVideo,
5 Storyboard,
6 Video,
7 VideoCaption,
8 VideoDetails,
9 VideoPlaylistElement,
10 VideoState,
11 VideoStreamingPlaylistType
12} from '../../../../../shared/models'
13import { HLSOptions, PeerTubePlayerContructorOptions, PeerTubePlayerLoadOptions, PlayerMode, VideoJSCaption } from '../../../assets/player'
14import {
15 getBoolOrDefault,
16 getParamString,
17 getParamToggle,
18 isP2PEnabled,
19 logger,
20 peertubeLocalStorage,
21 UserLocalStorageKeys,
22 videoRequiresUserAuth
23} from '../../../root-helpers'
24import { PeerTubePlugin } from './peertube-plugin'
25import { PlayerHTML } from './player-html'
26import { PlaylistTracker } from './playlist-tracker'
27import { Translations } from './translations'
28import { VideoFetcher } from './video-fetcher'
29
30export class PlayerOptionsBuilder {
31 private autoplay: boolean
32
33 private controls: boolean
34 private controlBar: boolean
35
36 private muted: boolean
37 private loop: boolean
38 private subtitle: string
39 private enableApi = false
40 private startTime: number | string = 0
41 private stopTime: number | string
42 private playbackRate: number | string
43
44 private title: boolean
45 private warningTitle: boolean
46 private peertubeLink: boolean
47 private p2pEnabled: boolean
48 private bigPlayBackgroundColor: string
49 private foregroundColor: string
50
51 private mode: PlayerMode
52 private scope = 'peertube'
53
54 constructor (
55 private readonly playerHTML: PlayerHTML,
56 private readonly videoFetcher: VideoFetcher,
57 private readonly peertubePlugin: PeerTubePlugin
58 ) {}
59
60 hasAPIEnabled () {
61 return this.enableApi
62 }
63
64 hasAutoplay () {
65 return this.autoplay
66 }
67
68 hasControls () {
69 return this.controls
70 }
71
72 hasTitle () {
73 return this.title
74 }
75
76 hasWarningTitle () {
77 return this.warningTitle
78 }
79
80 hasP2PEnabled () {
81 return !!this.p2pEnabled
82 }
83
84 hasBigPlayBackgroundColor () {
85 return !!this.bigPlayBackgroundColor
86 }
87
88 getBigPlayBackgroundColor () {
89 return this.bigPlayBackgroundColor
90 }
91
92 hasForegroundColor () {
93 return !!this.foregroundColor
94 }
95
96 getForegroundColor () {
97 return this.foregroundColor
98 }
99
100 getMode () {
101 return this.mode
102 }
103
104 getScope () {
105 return this.scope
106 }
107
108 // ---------------------------------------------------------------------------
109
110 loadParams (config: HTMLServerConfig, video: VideoDetails) {
111 try {
112 const params = new URL(window.location.toString()).searchParams
113
114 this.autoplay = getParamToggle(params, 'autoplay', false)
115 // Disable auto play on live videos that are not streamed
116 if (video.state.id === VideoState.LIVE_ENDED || video.state.id === VideoState.WAITING_FOR_LIVE) {
117 this.autoplay = false
118 }
119
120 this.controls = getParamToggle(params, 'controls', true)
121 this.controlBar = getParamToggle(params, 'controlBar', true)
122
123 this.muted = getParamToggle(params, 'muted', undefined)
124 this.loop = getParamToggle(params, 'loop', false)
125 this.title = getParamToggle(params, 'title', true)
126 this.enableApi = getParamToggle(params, 'api', this.enableApi)
127 this.warningTitle = getParamToggle(params, 'warningTitle', true)
128 this.peertubeLink = getParamToggle(params, 'peertubeLink', true)
129 this.p2pEnabled = getParamToggle(params, 'p2p', this.isP2PEnabled(config, video))
130
131 this.scope = getParamString(params, 'scope', this.scope)
132 this.subtitle = getParamString(params, 'subtitle')
133 this.startTime = getParamString(params, 'start')
134 this.stopTime = getParamString(params, 'stop')
135 this.playbackRate = getParamString(params, 'playbackRate')
136
137 this.bigPlayBackgroundColor = getParamString(params, 'bigPlayBackgroundColor')
138 this.foregroundColor = getParamString(params, 'foregroundColor')
139
140 const modeParam = getParamString(params, 'mode')
141
142 if (modeParam) {
143 if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader'
144 else this.mode = 'web-video'
145 } else {
146 if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader'
147 else this.mode = 'web-video'
148 }
149 } catch (err) {
150 logger.error('Cannot get params from URL.', err)
151 }
152 }
153
154 // ---------------------------------------------------------------------------
155
156 getPlayerConstructorOptions (options: {
157 serverConfig: HTMLServerConfig
158 authorizationHeader: () => string
159 }): PeerTubePlayerContructorOptions {
160 const { serverConfig, authorizationHeader } = options
161
162 return {
163 controls: this.controls,
164 controlBar: this.controlBar,
165
166 muted: this.muted,
167 loop: this.loop,
168
169 playbackRate: this.playbackRate,
170
171 inactivityTimeout: 2500,
172 videoViewIntervalMs: 5000,
173 metricsUrl: window.location.origin + '/api/v1/metrics/playback',
174
175 authorizationHeader,
176
177 playerElement: () => this.playerHTML.getPlayerElement(),
178 enableHotkeys: true,
179
180 peertubeLink: () => this.peertubeLink,
181 instanceName: serverConfig.instance.name,
182
183 theaterButton: false,
184
185 serverUrl: window.location.origin,
186 language: navigator.language,
187
188 pluginsManager: this.peertubePlugin.getPluginsManager(),
189
190 errorNotifier: () => {
191 // Empty, we don't have a notifier in the embed
192 }
193 }
194 }
195
196 async getPlayerLoadOptions (options: {
197 video: VideoDetails
198 captionsResponse: Response
199
200 storyboardsResponse: Response
201
202 live?: LiveVideo
203
204 alreadyPlayed: boolean
205 forceAutoplay: boolean
206
207 videoFileToken: () => string
208
209 videoPassword: () => string
210 requiresPassword: boolean
211
212 translations: Translations
213
214 playlist?: {
215 playlistTracker: PlaylistTracker
216 playNext: () => any
217 playPrevious: () => any
218 onVideoUpdate: (uuid: string) => any
219 }
220 }): Promise<PeerTubePlayerLoadOptions> {
221 const {
222 video,
223 captionsResponse,
224 videoFileToken,
225 videoPassword,
226 requiresPassword,
227 translations,
228 alreadyPlayed,
229 forceAutoplay,
230 playlist,
231 live,
232 storyboardsResponse
233 } = options
234
235 const [ videoCaptions, storyboard ] = await Promise.all([
236 this.buildCaptions(captionsResponse, translations),
237 this.buildStoryboard(storyboardsResponse)
238 ])
239
240 return {
241 mode: this.mode,
242
243 autoplay: forceAutoplay || alreadyPlayed || this.autoplay,
244 forceAutoplay,
245
246 p2pEnabled: this.p2pEnabled,
247
248 subtitle: this.subtitle,
249
250 storyboard,
251
252 startTime: playlist
253 ? playlist.playlistTracker.getCurrentElement().startTimestamp
254 : this.startTime,
255 stopTime: playlist
256 ? playlist.playlistTracker.getCurrentElement().stopTimestamp
257 : this.stopTime,
258
259 videoCaptions,
260 videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid),
261
262 videoShortUUID: video.shortUUID,
263 videoUUID: video.uuid,
264
265 duration: video.duration,
266
267 poster: window.location.origin + video.previewPath,
268
269 embedUrl: window.location.origin + video.embedPath,
270 embedTitle: video.name,
271
272 requiresUserAuth: videoRequiresUserAuth(video),
273 videoFileToken,
274
275 requiresPassword,
276 videoPassword,
277
278 ...this.buildLiveOptions(video, live),
279
280 ...this.buildPlaylistOptions(playlist),
281
282 dock: this.buildDockOptions(video),
283
284 webVideo: {
285 videoFiles: video.files
286 },
287
288 hls: this.buildHLSOptions(video)
289 }
290 }
291
292 private buildLiveOptions (video: VideoDetails, live: LiveVideo) {
293 if (!video.isLive) return { isLive: false }
294
295 return {
296 isLive: true,
297 liveOptions: {
298 latencyMode: live.latencyMode
299 }
300 }
301 }
302
303 private async buildStoryboard (storyboardsResponse: Response) {
304 const { storyboards } = await storyboardsResponse.json() as { storyboards: Storyboard[] }
305 if (!storyboards || storyboards.length === 0) return undefined
306
307 return {
308 url: window.location.origin + storyboards[0].storyboardPath,
309 height: storyboards[0].spriteHeight,
310 width: storyboards[0].spriteWidth,
311 interval: storyboards[0].spriteDuration
312 }
313 }
314
315 private buildPlaylistOptions (options?: {
316 playlistTracker: PlaylistTracker
317 playNext: () => any
318 playPrevious: () => any
319 onVideoUpdate: (uuid: string) => any
320 }) {
321 if (!options) {
322 return {
323 nextVideo: {
324 enabled: false,
325 displayControlBarButton: false,
326 getVideoTitle: () => ''
327 },
328 previousVideo: {
329 enabled: false,
330 displayControlBarButton: false
331 }
332 }
333 }
334
335 const { playlistTracker, playNext, playPrevious, onVideoUpdate } = options
336
337 return {
338 playlist: {
339 elements: playlistTracker.getPlaylistElements(),
340 playlist: playlistTracker.getPlaylist(),
341
342 getCurrentPosition: () => playlistTracker.getCurrentPosition(),
343
344 onItemClicked: (videoPlaylistElement: VideoPlaylistElement) => {
345 playlistTracker.setCurrentElement(videoPlaylistElement)
346
347 onVideoUpdate(videoPlaylistElement.video.uuid)
348 }
349 },
350
351 previousVideo: {
352 enabled: playlistTracker.hasPreviousPlaylistElement(),
353 handler: () => playPrevious(),
354 displayControlBarButton: true
355 },
356
357 nextVideo: {
358 enabled: playlistTracker.hasNextPlaylistElement(),
359 handler: () => playNext(),
360 getVideoTitle: () => playlistTracker.getNextPlaylistElement()?.video?.name,
361 displayControlBarButton: true
362 },
363
364 upnext: {
365 isEnabled: () => true,
366 isSuspended: () => false,
367 timeout: 0
368 }
369 }
370 }
371
372 private buildHLSOptions (video: VideoDetails): HLSOptions {
373 const hlsPlaylist = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
374 if (!hlsPlaylist) return undefined
375
376 return {
377 playlistUrl: hlsPlaylist.playlistUrl,
378 segmentsSha256Url: hlsPlaylist.segmentsSha256Url,
379 redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl),
380 trackerAnnounce: video.trackerUrls,
381 videoFiles: hlsPlaylist.files
382 }
383 }
384
385 // ---------------------------------------------------------------------------
386
387 private async buildCaptions (captionsResponse: Response, translations: Translations): Promise<VideoJSCaption[]> {
388 if (captionsResponse.ok) {
389 const { data } = await captionsResponse.json()
390
391 return data.map((c: VideoCaption) => ({
392 label: peertubeTranslate(c.language.label, translations),
393 language: c.language.id,
394 src: window.location.origin + c.captionPath
395 }))
396 }
397
398 return []
399 }
400
401 // ---------------------------------------------------------------------------
402
403 private buildDockOptions (videoInfo: VideoDetails) {
404 if (!this.hasControls()) return undefined
405
406 const title = this.hasTitle()
407 ? videoInfo.name
408 : undefined
409
410 const description = this.hasWarningTitle() && this.hasP2PEnabled()
411 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
412 : undefined
413
414 if (!title && !description) return
415
416 const availableAvatars = videoInfo.channel.avatars.filter(a => a.width < 50)
417 const avatar = availableAvatars.length !== 0
418 ? availableAvatars[0]
419 : undefined
420
421 return {
422 title,
423 description,
424 avatarUrl: title && avatar
425 ? avatar.path
426 : undefined
427 }
428 }
429
430 // ---------------------------------------------------------------------------
431
432 private isP2PEnabled (config: HTMLServerConfig, video: Video) {
433 const userP2PEnabled = getBoolOrDefault(
434 peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED),
435 config.defaults.p2p.embed.enabled
436 )
437
438 return isP2PEnabled(video, config, userP2PEnabled)
439 }
440}