diff options
Diffstat (limited to 'client/src/standalone/videos/shared')
-rw-r--r-- | client/src/standalone/videos/shared/index.ts | 2 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/player-html.ts | 19 | ||||
-rw-r--r-- | client/src/standalone/videos/shared/player-options-builder.ts (renamed from client/src/standalone/videos/shared/player-manager-options.ts) | 261 |
3 files changed, 159 insertions, 123 deletions
diff --git a/client/src/standalone/videos/shared/index.ts b/client/src/standalone/videos/shared/index.ts index 928b8e270..dcc522ac6 100644 --- a/client/src/standalone/videos/shared/index.ts +++ b/client/src/standalone/videos/shared/index.ts | |||
@@ -2,7 +2,7 @@ export * from './auth-http' | |||
2 | export * from './peertube-plugin' | 2 | export * from './peertube-plugin' |
3 | export * from './live-manager' | 3 | export * from './live-manager' |
4 | export * from './player-html' | 4 | export * from './player-html' |
5 | export * from './player-manager-options' | 5 | export * from './player-options-builder' |
6 | export * from './playlist-fetcher' | 6 | export * from './playlist-fetcher' |
7 | export * from './playlist-tracker' | 7 | export * from './playlist-tracker' |
8 | export * from './translations' | 8 | export * from './translations' |
diff --git a/client/src/standalone/videos/shared/player-html.ts b/client/src/standalone/videos/shared/player-html.ts index a0846d9d7..0defa0d70 100644 --- a/client/src/standalone/videos/shared/player-html.ts +++ b/client/src/standalone/videos/shared/player-html.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { peertubeTranslate } from '../../../../../shared/core-utils/i18n' | 1 | import { peertubeTranslate } from '../../../../../shared/core-utils/i18n' |
2 | import { VideoDetails } from '../../../../../shared/models' | ||
3 | import { logger } from '../../../root-helpers' | 2 | import { logger } from '../../../root-helpers' |
4 | import { Translations } from './translations' | 3 | import { Translations } from './translations' |
5 | 4 | ||
@@ -59,7 +58,6 @@ export class PlayerHTML { | |||
59 | const { incorrectPassword, translations } = options | 58 | const { incorrectPassword, translations } = options |
60 | return new Promise((resolve) => { | 59 | return new Promise((resolve) => { |
61 | 60 | ||
62 | this.removePlaceholder() | ||
63 | this.wrapperElement.style.display = 'none' | 61 | this.wrapperElement.style.display = 'none' |
64 | 62 | ||
65 | const translatedTitle = peertubeTranslate('This video is password protected', translations) | 63 | const translatedTitle = peertubeTranslate('This video is password protected', translations) |
@@ -107,19 +105,6 @@ export class PlayerHTML { | |||
107 | this.wrapperElement.style.display = 'block' | 105 | this.wrapperElement.style.display = 'block' |
108 | } | 106 | } |
109 | 107 | ||
110 | buildPlaceholder (video: VideoDetails) { | ||
111 | const placeholder = this.getPlaceholderElement() | ||
112 | |||
113 | const url = window.location.origin + video.previewPath | ||
114 | placeholder.style.backgroundImage = `url("${url}")` | ||
115 | placeholder.style.display = 'block' | ||
116 | } | ||
117 | |||
118 | removePlaceholder () { | ||
119 | const placeholder = this.getPlaceholderElement() | ||
120 | placeholder.style.display = 'none' | ||
121 | } | ||
122 | |||
123 | displayInformation (text: string, translations: Translations) { | 108 | displayInformation (text: string, translations: Translations) { |
124 | if (this.informationElement) this.removeInformation() | 109 | if (this.informationElement) this.removeInformation() |
125 | 110 | ||
@@ -137,10 +122,6 @@ export class PlayerHTML { | |||
137 | this.informationElement = undefined | 122 | this.informationElement = undefined |
138 | } | 123 | } |
139 | 124 | ||
140 | private getPlaceholderElement () { | ||
141 | return document.getElementById('placeholder-preview') | ||
142 | } | ||
143 | |||
144 | private removeElement (element: HTMLElement) { | 125 | private removeElement (element: HTMLElement) { |
145 | element.parentElement.removeChild(element) | 126 | element.parentElement.removeChild(element) |
146 | } | 127 | } |
diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-options-builder.ts index 3c7521bc2..8a4e32444 100644 --- a/client/src/standalone/videos/shared/player-manager-options.ts +++ b/client/src/standalone/videos/shared/player-options-builder.ts | |||
@@ -10,7 +10,7 @@ import { | |||
10 | VideoState, | 10 | VideoState, |
11 | VideoStreamingPlaylistType | 11 | VideoStreamingPlaylistType |
12 | } from '../../../../../shared/models' | 12 | } from '../../../../../shared/models' |
13 | import { P2PMediaLoaderOptions, PeertubePlayerManagerOptions, PlayerMode, VideoJSCaption } from '../../../assets/player' | 13 | import { HLSOptions, PeerTubePlayerContructorOptions, PeerTubePlayerLoadOptions, PlayerMode, VideoJSCaption } from '../../../assets/player' |
14 | import { | 14 | import { |
15 | getBoolOrDefault, | 15 | getBoolOrDefault, |
16 | getParamString, | 16 | getParamString, |
@@ -27,7 +27,7 @@ import { PlaylistTracker } from './playlist-tracker' | |||
27 | import { Translations } from './translations' | 27 | import { Translations } from './translations' |
28 | import { VideoFetcher } from './video-fetcher' | 28 | import { VideoFetcher } from './video-fetcher' |
29 | 29 | ||
30 | export class PlayerManagerOptions { | 30 | export class PlayerOptionsBuilder { |
31 | private autoplay: boolean | 31 | private autoplay: boolean |
32 | 32 | ||
33 | private controls: boolean | 33 | private controls: boolean |
@@ -141,10 +141,10 @@ export class PlayerManagerOptions { | |||
141 | 141 | ||
142 | if (modeParam) { | 142 | if (modeParam) { |
143 | if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader' | 143 | if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader' |
144 | else this.mode = 'webtorrent' | 144 | else this.mode = 'web-video' |
145 | } else { | 145 | } else { |
146 | if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader' | 146 | if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader' |
147 | else this.mode = 'webtorrent' | 147 | else this.mode = 'web-video' |
148 | } | 148 | } |
149 | } catch (err) { | 149 | } catch (err) { |
150 | logger.error('Cannot get params from URL.', err) | 150 | logger.error('Cannot get params from URL.', err) |
@@ -153,7 +153,47 @@ export class PlayerManagerOptions { | |||
153 | 153 | ||
154 | // --------------------------------------------------------------------------- | 154 | // --------------------------------------------------------------------------- |
155 | 155 | ||
156 | async getPlayerOptions (options: { | 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: { | ||
157 | video: VideoDetails | 197 | video: VideoDetails |
158 | captionsResponse: Response | 198 | captionsResponse: Response |
159 | 199 | ||
@@ -161,39 +201,35 @@ export class PlayerManagerOptions { | |||
161 | 201 | ||
162 | live?: LiveVideo | 202 | live?: LiveVideo |
163 | 203 | ||
204 | alreadyPlayed: boolean | ||
164 | forceAutoplay: boolean | 205 | forceAutoplay: boolean |
165 | 206 | ||
166 | authorizationHeader: () => string | ||
167 | videoFileToken: () => string | 207 | videoFileToken: () => string |
168 | 208 | ||
169 | videoPassword: () => string | 209 | videoPassword: () => string |
170 | requiresPassword: boolean | 210 | requiresPassword: boolean |
171 | 211 | ||
172 | serverConfig: HTMLServerConfig | ||
173 | |||
174 | autoplayFromPreviousVideo: boolean | ||
175 | |||
176 | translations: Translations | 212 | translations: Translations |
177 | 213 | ||
178 | playlistTracker?: PlaylistTracker | 214 | playlist?: { |
179 | playNextPlaylistVideo?: () => any | 215 | playlistTracker: PlaylistTracker |
180 | playPreviousPlaylistVideo?: () => any | 216 | playNext: () => any |
181 | onVideoUpdate?: (uuid: string) => any | 217 | playPrevious: () => any |
182 | }) { | 218 | onVideoUpdate: (uuid: string) => any |
219 | } | ||
220 | }): Promise<PeerTubePlayerLoadOptions> { | ||
183 | const { | 221 | const { |
184 | video, | 222 | video, |
185 | captionsResponse, | 223 | captionsResponse, |
186 | autoplayFromPreviousVideo, | ||
187 | videoFileToken, | 224 | videoFileToken, |
188 | videoPassword, | 225 | videoPassword, |
189 | requiresPassword, | 226 | requiresPassword, |
190 | translations, | 227 | translations, |
228 | alreadyPlayed, | ||
191 | forceAutoplay, | 229 | forceAutoplay, |
192 | playlistTracker, | 230 | playlist, |
193 | live, | 231 | live, |
194 | storyboardsResponse, | 232 | storyboardsResponse |
195 | authorizationHeader, | ||
196 | serverConfig | ||
197 | } = options | 233 | } = options |
198 | 234 | ||
199 | const [ videoCaptions, storyboard ] = await Promise.all([ | 235 | const [ videoCaptions, storyboard ] = await Promise.all([ |
@@ -201,88 +237,56 @@ export class PlayerManagerOptions { | |||
201 | this.buildStoryboard(storyboardsResponse) | 237 | this.buildStoryboard(storyboardsResponse) |
202 | ]) | 238 | ]) |
203 | 239 | ||
204 | const playerOptions: PeertubePlayerManagerOptions = { | 240 | return { |
205 | common: { | 241 | mode: this.mode, |
206 | // Autoplay in playlist mode | ||
207 | autoplay: autoplayFromPreviousVideo ? true : this.autoplay, | ||
208 | forceAutoplay, | ||
209 | |||
210 | controls: this.controls, | ||
211 | controlBar: this.controlBar, | ||
212 | |||
213 | muted: this.muted, | ||
214 | loop: this.loop, | ||
215 | |||
216 | p2pEnabled: this.p2pEnabled, | ||
217 | |||
218 | captions: videoCaptions.length !== 0, | ||
219 | subtitle: this.subtitle, | ||
220 | 242 | ||
221 | storyboard, | 243 | autoplay: forceAutoplay || alreadyPlayed || this.autoplay, |
244 | forceAutoplay, | ||
222 | 245 | ||
223 | startTime: playlistTracker | 246 | p2pEnabled: this.p2pEnabled, |
224 | ? playlistTracker.getCurrentElement().startTimestamp | ||
225 | : this.startTime, | ||
226 | stopTime: playlistTracker | ||
227 | ? playlistTracker.getCurrentElement().stopTimestamp | ||
228 | : this.stopTime, | ||
229 | 247 | ||
230 | playbackRate: this.playbackRate, | 248 | subtitle: this.subtitle, |
231 | 249 | ||
232 | videoCaptions, | 250 | storyboard, |
233 | inactivityTimeout: 2500, | ||
234 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), | ||
235 | videoViewIntervalMs: 5000, | ||
236 | metricsUrl: window.location.origin + '/api/v1/metrics/playback', | ||
237 | 251 | ||
238 | videoShortUUID: video.shortUUID, | 252 | startTime: playlist |
239 | videoUUID: video.uuid, | 253 | ? playlist.playlistTracker.getCurrentElement().startTimestamp |
254 | : this.startTime, | ||
255 | stopTime: playlist | ||
256 | ? playlist.playlistTracker.getCurrentElement().stopTimestamp | ||
257 | : this.stopTime, | ||
240 | 258 | ||
241 | playerElement: this.playerHTML.getPlayerElement(), | 259 | videoCaptions, |
242 | onPlayerElementChange: (element: HTMLVideoElement) => { | 260 | videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), |
243 | this.playerHTML.setPlayerElement(element) | ||
244 | }, | ||
245 | 261 | ||
246 | videoDuration: video.duration, | 262 | videoShortUUID: video.shortUUID, |
247 | enableHotkeys: true, | 263 | videoUUID: video.uuid, |
248 | 264 | ||
249 | peertubeLink: this.peertubeLink, | 265 | duration: video.duration, |
250 | instanceName: serverConfig.instance.name, | ||
251 | 266 | ||
252 | poster: window.location.origin + video.previewPath, | 267 | poster: window.location.origin + video.previewPath, |
253 | theaterButton: false, | ||
254 | 268 | ||
255 | serverUrl: window.location.origin, | 269 | embedUrl: window.location.origin + video.embedPath, |
256 | language: navigator.language, | 270 | embedTitle: video.name, |
257 | embedUrl: window.location.origin + video.embedPath, | ||
258 | embedTitle: video.name, | ||
259 | 271 | ||
260 | requiresUserAuth: videoRequiresUserAuth(video), | 272 | requiresUserAuth: videoRequiresUserAuth(video), |
261 | authorizationHeader, | 273 | videoFileToken, |
262 | videoFileToken, | ||
263 | 274 | ||
264 | requiresPassword, | 275 | requiresPassword, |
265 | videoPassword, | 276 | videoPassword, |
266 | 277 | ||
267 | errorNotifier: () => { | 278 | ...this.buildLiveOptions(video, live), |
268 | // Empty, we don't have a notifier in the embed | ||
269 | }, | ||
270 | 279 | ||
271 | ...this.buildLiveOptions(video, live), | 280 | ...this.buildPlaylistOptions(playlist), |
272 | 281 | ||
273 | ...this.buildPlaylistOptions(options) | 282 | dock: this.buildDockOptions(video), |
274 | }, | ||
275 | 283 | ||
276 | webtorrent: { | 284 | webVideo: { |
277 | videoFiles: video.files | 285 | videoFiles: video.files |
278 | }, | 286 | }, |
279 | 287 | ||
280 | ...this.buildP2PMediaLoaderOptions(video), | 288 | hls: this.buildHLSOptions(video) |
281 | |||
282 | pluginsManager: this.peertubePlugin.getPluginsManager() | ||
283 | } | 289 | } |
284 | |||
285 | return playerOptions | ||
286 | } | 290 | } |
287 | 291 | ||
288 | private buildLiveOptions (video: VideoDetails, live: LiveVideo) { | 292 | private buildLiveOptions (video: VideoDetails, live: LiveVideo) { |
@@ -308,15 +312,27 @@ export class PlayerManagerOptions { | |||
308 | } | 312 | } |
309 | } | 313 | } |
310 | 314 | ||
311 | private buildPlaylistOptions (options: { | 315 | private buildPlaylistOptions (options?: { |
312 | playlistTracker?: PlaylistTracker | 316 | playlistTracker: PlaylistTracker |
313 | playNextPlaylistVideo?: () => any | 317 | playNext: () => any |
314 | playPreviousPlaylistVideo?: () => any | 318 | playPrevious: () => any |
315 | onVideoUpdate?: (uuid: string) => any | 319 | onVideoUpdate: (uuid: string) => any |
316 | }) { | 320 | }) { |
317 | const { playlistTracker, playNextPlaylistVideo, playPreviousPlaylistVideo, onVideoUpdate } = options | 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 | } | ||
318 | 334 | ||
319 | if (!playlistTracker) return {} | 335 | const { playlistTracker, playNext, playPrevious, onVideoUpdate } = options |
320 | 336 | ||
321 | return { | 337 | return { |
322 | playlist: { | 338 | playlist: { |
@@ -332,27 +348,37 @@ export class PlayerManagerOptions { | |||
332 | } | 348 | } |
333 | }, | 349 | }, |
334 | 350 | ||
335 | nextVideo: () => playNextPlaylistVideo(), | 351 | previousVideo: { |
336 | hasNextVideo: () => playlistTracker.hasNextPlaylistElement(), | 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 | }, | ||
337 | 363 | ||
338 | previousVideo: () => playPreviousPlaylistVideo(), | 364 | upnext: { |
339 | hasPreviousVideo: () => playlistTracker.hasPreviousPlaylistElement() | 365 | isEnabled: () => true, |
366 | isSuspended: () => false, | ||
367 | timeout: 0 | ||
368 | } | ||
340 | } | 369 | } |
341 | } | 370 | } |
342 | 371 | ||
343 | private buildP2PMediaLoaderOptions (video: VideoDetails) { | 372 | private buildHLSOptions (video: VideoDetails): HLSOptions { |
344 | if (this.mode !== 'p2p-media-loader') return {} | ||
345 | |||
346 | const hlsPlaylist = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | 373 | const hlsPlaylist = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
374 | if (!hlsPlaylist) return undefined | ||
347 | 375 | ||
348 | return { | 376 | return { |
349 | p2pMediaLoader: { | 377 | playlistUrl: hlsPlaylist.playlistUrl, |
350 | playlistUrl: hlsPlaylist.playlistUrl, | 378 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, |
351 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | 379 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), |
352 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | 380 | trackerAnnounce: video.trackerUrls, |
353 | trackerAnnounce: video.trackerUrls, | 381 | videoFiles: hlsPlaylist.files |
354 | videoFiles: hlsPlaylist.files | ||
355 | } as P2PMediaLoaderOptions | ||
356 | } | 382 | } |
357 | } | 383 | } |
358 | 384 | ||
@@ -374,6 +400,35 @@ export class PlayerManagerOptions { | |||
374 | 400 | ||
375 | // --------------------------------------------------------------------------- | 401 | // --------------------------------------------------------------------------- |
376 | 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 | |||
377 | private isP2PEnabled (config: HTMLServerConfig, video: Video) { | 432 | private isP2PEnabled (config: HTMLServerConfig, video: Video) { |
378 | const userP2PEnabled = getBoolOrDefault( | 433 | const userP2PEnabled = getBoolOrDefault( |
379 | peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED), | 434 | peertubeLocalStorage.getItem(UserLocalStorageKeys.P2P_ENABLED), |