aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/standalone/videos/embed.ts
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/standalone/videos/embed.ts')
-rw-r--r--client/src/standalone/videos/embed.ts228
1 files changed, 85 insertions, 143 deletions
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 6e37ce193..78b812ffd 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -3,7 +3,6 @@ import '../../assets/player/shared/dock/peertube-dock-component'
3import '../../assets/player/shared/dock/peertube-dock-plugin' 3import '../../assets/player/shared/dock/peertube-dock-plugin'
4import { PeerTubeServerError } from 'src/types' 4import { PeerTubeServerError } from 'src/types'
5import videojs from 'video.js' 5import videojs from 'video.js'
6import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
7import { 6import {
8 HTMLServerConfig, 7 HTMLServerConfig,
9 ResultList, 8 ResultList,
@@ -13,7 +12,7 @@ import {
13 VideoPlaylistElement, 12 VideoPlaylistElement,
14 VideoState 13 VideoState
15} from '../../../../shared/models' 14} from '../../../../shared/models'
16import { PeertubePlayerManager } from '../../assets/player' 15import { PeerTubePlayer } from '../../assets/player/peertube-player'
17import { TranslationsManager } from '../../assets/player/translations-manager' 16import { TranslationsManager } from '../../assets/player/translations-manager'
18import { getParamString, logger, videoRequiresFileToken } from '../../root-helpers' 17import { getParamString, logger, videoRequiresFileToken } from '../../root-helpers'
19import { PeerTubeEmbedApi } from './embed-api' 18import { PeerTubeEmbedApi } from './embed-api'
@@ -21,7 +20,7 @@ import {
21 AuthHTTP, 20 AuthHTTP,
22 LiveManager, 21 LiveManager,
23 PeerTubePlugin, 22 PeerTubePlugin,
24 PlayerManagerOptions, 23 PlayerOptionsBuilder,
25 PlaylistFetcher, 24 PlaylistFetcher,
26 PlaylistTracker, 25 PlaylistTracker,
27 Translations, 26 Translations,
@@ -36,17 +35,23 @@ export class PeerTubeEmbed {
36 config: HTMLServerConfig 35 config: HTMLServerConfig
37 36
38 private translationsPromise: Promise<{ [id: string]: string }> 37 private translationsPromise: Promise<{ [id: string]: string }>
39 private PeertubePlayerManagerModulePromise: Promise<any> 38 private PeerTubePlayerManagerModulePromise: Promise<any>
40 39
41 private readonly http: AuthHTTP 40 private readonly http: AuthHTTP
42 private readonly videoFetcher: VideoFetcher 41 private readonly videoFetcher: VideoFetcher
43 private readonly playlistFetcher: PlaylistFetcher 42 private readonly playlistFetcher: PlaylistFetcher
44 private readonly peertubePlugin: PeerTubePlugin 43 private readonly peertubePlugin: PeerTubePlugin
45 private readonly playerHTML: PlayerHTML 44 private readonly playerHTML: PlayerHTML
46 private readonly playerManagerOptions: PlayerManagerOptions 45 private readonly playerOptionsBuilder: PlayerOptionsBuilder
47 private readonly liveManager: LiveManager 46 private readonly liveManager: LiveManager
48 47
48 private peertubePlayer: PeerTubePlayer
49
49 private playlistTracker: PlaylistTracker 50 private playlistTracker: PlaylistTracker
51
52 private alreadyInitialized = false
53 private alreadyPlayed = false
54
50 private videoPassword: string 55 private videoPassword: string
51 private requiresPassword: boolean 56 private requiresPassword: boolean
52 57
@@ -59,7 +64,7 @@ export class PeerTubeEmbed {
59 this.playlistFetcher = new PlaylistFetcher(this.http) 64 this.playlistFetcher = new PlaylistFetcher(this.http)
60 this.peertubePlugin = new PeerTubePlugin(this.http) 65 this.peertubePlugin = new PeerTubePlugin(this.http)
61 this.playerHTML = new PlayerHTML(videoWrapperId) 66 this.playerHTML = new PlayerHTML(videoWrapperId)
62 this.playerManagerOptions = new PlayerManagerOptions(this.playerHTML, this.videoFetcher, this.peertubePlugin) 67 this.playerOptionsBuilder = new PlayerOptionsBuilder(this.playerHTML, this.videoFetcher, this.peertubePlugin)
63 this.liveManager = new LiveManager(this.playerHTML) 68 this.liveManager = new LiveManager(this.playerHTML)
64 this.requiresPassword = false 69 this.requiresPassword = false
65 70
@@ -81,14 +86,14 @@ export class PeerTubeEmbed {
81 } 86 }
82 87
83 getScope () { 88 getScope () {
84 return this.playerManagerOptions.getScope() 89 return this.playerOptionsBuilder.getScope()
85 } 90 }
86 91
87 // --------------------------------------------------------------------------- 92 // ---------------------------------------------------------------------------
88 93
89 async init () { 94 async init () {
90 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language) 95 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
91 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager') 96 this.PeerTubePlayerManagerModulePromise = import('../../assets/player/peertube-player')
92 97
93 // Issue when we parsed config from HTML, fallback to API 98 // Issue when we parsed config from HTML, fallback to API
94 if (!this.config) { 99 if (!this.config) {
@@ -102,7 +107,7 @@ export class PeerTubeEmbed {
102 107
103 if (!videoId) return 108 if (!videoId) return
104 109
105 return this.loadVideoAndBuildPlayer({ uuid: videoId, autoplayFromPreviousVideo: false, forceAutoplay: false }) 110 return this.loadVideoAndBuildPlayer({ uuid: videoId, forceAutoplay: false })
106 } 111 }
107 112
108 private async initPlaylist () { 113 private async initPlaylist () {
@@ -137,7 +142,7 @@ export class PeerTubeEmbed {
137 } 142 }
138 143
139 private initializeApi () { 144 private initializeApi () {
140 if (this.playerManagerOptions.hasAPIEnabled()) { 145 if (this.playerOptionsBuilder.hasAPIEnabled()) {
141 if (this.api) { 146 if (this.api) {
142 this.api.reInit() 147 this.api.reInit()
143 return 148 return
@@ -159,7 +164,7 @@ export class PeerTubeEmbed {
159 164
160 this.playlistTracker.setCurrentElement(next) 165 this.playlistTracker.setCurrentElement(next)
161 166
162 return this.loadVideoAndBuildPlayer({ uuid: next.video.uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }) 167 return this.loadVideoAndBuildPlayer({ uuid: next.video.uuid, forceAutoplay: false })
163 } 168 }
164 169
165 async playPreviousPlaylistVideo () { 170 async playPreviousPlaylistVideo () {
@@ -171,7 +176,7 @@ export class PeerTubeEmbed {
171 176
172 this.playlistTracker.setCurrentElement(previous) 177 this.playlistTracker.setCurrentElement(previous)
173 178
174 await this.loadVideoAndBuildPlayer({ uuid: previous.video.uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }) 179 await this.loadVideoAndBuildPlayer({ uuid: previous.video.uuid, forceAutoplay: false })
175 } 180 }
176 181
177 getCurrentPlaylistPosition () { 182 getCurrentPlaylistPosition () {
@@ -182,10 +187,9 @@ export class PeerTubeEmbed {
182 187
183 private async loadVideoAndBuildPlayer (options: { 188 private async loadVideoAndBuildPlayer (options: {
184 uuid: string 189 uuid: string
185 autoplayFromPreviousVideo: boolean
186 forceAutoplay: boolean 190 forceAutoplay: boolean
187 }) { 191 }) {
188 const { uuid, autoplayFromPreviousVideo, forceAutoplay } = options 192 const { uuid, forceAutoplay } = options
189 193
190 try { 194 try {
191 const { 195 const {
@@ -194,7 +198,7 @@ export class PeerTubeEmbed {
194 storyboardsPromise 198 storyboardsPromise
195 } = await this.videoFetcher.loadVideo({ videoId: uuid, videoPassword: this.videoPassword }) 199 } = await this.videoFetcher.loadVideo({ videoId: uuid, videoPassword: this.videoPassword })
196 200
197 return this.buildVideoPlayer({ videoResponse, captionsPromise, storyboardsPromise, autoplayFromPreviousVideo, forceAutoplay }) 201 return this.buildVideoPlayer({ videoResponse, captionsPromise, storyboardsPromise, forceAutoplay })
198 } catch (err) { 202 } catch (err) {
199 203
200 if (await this.handlePasswordError(err)) this.loadVideoAndBuildPlayer({ ...options }) 204 if (await this.handlePasswordError(err)) this.loadVideoAndBuildPlayer({ ...options })
@@ -206,20 +210,14 @@ export class PeerTubeEmbed {
206 videoResponse: Response 210 videoResponse: Response
207 storyboardsPromise: Promise<Response> 211 storyboardsPromise: Promise<Response>
208 captionsPromise: Promise<Response> 212 captionsPromise: Promise<Response>
209 autoplayFromPreviousVideo: boolean
210 forceAutoplay: boolean 213 forceAutoplay: boolean
211 }) { 214 }) {
212 const { videoResponse, captionsPromise, storyboardsPromise, autoplayFromPreviousVideo, forceAutoplay } = options 215 const { videoResponse, captionsPromise, storyboardsPromise, forceAutoplay } = options
213
214 this.resetPlayerElement()
215 216
216 const videoInfoPromise = videoResponse.json() 217 const videoInfoPromise = videoResponse.json()
217 .then(async (videoInfo: VideoDetails) => { 218 .then(async (videoInfo: VideoDetails) => {
218 this.playerManagerOptions.loadParams(this.config, videoInfo) 219 this.playerOptionsBuilder.loadParams(this.config, videoInfo)
219 220
220 if (!autoplayFromPreviousVideo && !this.playerManagerOptions.hasAutoplay()) {
221 this.playerHTML.buildPlaceholder(videoInfo)
222 }
223 const live = videoInfo.isLive 221 const live = videoInfo.isLive
224 ? await this.videoFetcher.loadLive(videoInfo) 222 ? await this.videoFetcher.loadLive(videoInfo)
225 : undefined 223 : undefined
@@ -235,89 +233,78 @@ export class PeerTubeEmbed {
235 { video, live, videoFileToken }, 233 { video, live, videoFileToken },
236 translations, 234 translations,
237 captionsResponse, 235 captionsResponse,
238 storyboardsResponse, 236 storyboardsResponse
239 PeertubePlayerManagerModule
240 ] = await Promise.all([ 237 ] = await Promise.all([
241 videoInfoPromise, 238 videoInfoPromise,
242 this.translationsPromise, 239 this.translationsPromise,
243 captionsPromise, 240 captionsPromise,
244 storyboardsPromise, 241 storyboardsPromise,
245 this.PeertubePlayerManagerModulePromise 242 this.buildPlayerIfNeeded()
246 ]) 243 ])
247 244
248 await this.peertubePlugin.loadPlugins(this.config, translations) 245 // If already played, we are in a playlist so we don't want to display the poster between videos
246 if (!this.alreadyPlayed) {
247 this.peertubePlayer.setPoster(window.location.origin + video.previewPath)
248 }
249 249
250 const PlayerManager: typeof PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager 250 const playlist = this.playlistTracker
251 ? {
252 onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer({ uuid, forceAutoplay: false }),
251 253
252 const playerOptions = await this.playerManagerOptions.getPlayerOptions({ 254 playlistTracker: this.playlistTracker,
255 playNext: () => this.playNextPlaylistVideo(),
256 playPrevious: () => this.playPreviousPlaylistVideo()
257 }
258 : undefined
259
260 const loadOptions = await this.playerOptionsBuilder.getPlayerLoadOptions({
253 video, 261 video,
254 captionsResponse, 262 captionsResponse,
255 autoplayFromPreviousVideo,
256 translations, 263 translations,
257 serverConfig: this.config,
258 264
259 storyboardsResponse, 265 storyboardsResponse,
260 266
261 authorizationHeader: () => this.http.getHeaderTokenValue(),
262 videoFileToken: () => videoFileToken, 267 videoFileToken: () => videoFileToken,
263 videoPassword: () => this.videoPassword, 268 videoPassword: () => this.videoPassword,
264 requiresPassword: this.requiresPassword, 269 requiresPassword: this.requiresPassword,
265 270
266 onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer({ uuid, autoplayFromPreviousVideo: true, forceAutoplay: false }), 271 playlist,
267
268 playlistTracker: this.playlistTracker,
269 playNextPlaylistVideo: () => this.playNextPlaylistVideo(),
270 playPreviousPlaylistVideo: () => this.playPreviousPlaylistVideo(),
271 272
272 live, 273 live,
273 forceAutoplay 274 forceAutoplay,
275 alreadyPlayed: this.alreadyPlayed
274 }) 276 })
277 await this.peertubePlayer.load(loadOptions)
275 278
276 this.player = await PlayerManager.initialize(this.playerManagerOptions.getMode(), playerOptions, (player: videojs.Player) => { 279 if (!this.alreadyInitialized) {
277 this.player = player 280 this.player = this.peertubePlayer.getPlayer();
278 })
279 281
280 this.player.on('customError', (event: any, data: any) => { 282 (window as any)['videojsPlayer'] = this.player
281 const message = data?.err?.message || ''
282 if (!message.includes('from xs param')) return
283 283
284 this.player.dispose() 284 this.buildCSS()
285 this.playerHTML.removePlayerElement() 285 this.initializeApi()
286 this.playerHTML.displayError('This video is not available because the remote instance is not responding.', translations) 286 }
287 });
288 287
289 (window as any)['videojsPlayer'] = this.player 288 this.alreadyInitialized = true
290 289
291 this.buildCSS() 290 this.player.one('play', () => {
292 this.buildPlayerDock(video) 291 this.alreadyPlayed = true
293 this.initializeApi() 292 })
294 293
295 this.playerHTML.removePlaceholder()
296 if (this.videoPassword) this.playerHTML.removeVideoPasswordBlock() 294 if (this.videoPassword) this.playerHTML.removeVideoPasswordBlock()
297 295
298 if (this.isPlaylistEmbed()) {
299 await this.buildPlayerPlaylistUpnext()
300
301 this.player.playlist().updateSelected()
302
303 this.player.on('stopped', () => {
304 this.playNextPlaylistVideo()
305 })
306 }
307
308 if (video.isLive) { 296 if (video.isLive) {
309 this.liveManager.listenForChanges({ 297 this.liveManager.listenForChanges({
310 video, 298 video,
311 onPublishedVideo: () => { 299 onPublishedVideo: () => {
312 this.liveManager.stopListeningForChanges(video) 300 this.liveManager.stopListeningForChanges(video)
313 this.loadVideoAndBuildPlayer({ uuid: video.uuid, autoplayFromPreviousVideo: false, forceAutoplay: true }) 301 this.loadVideoAndBuildPlayer({ uuid: video.uuid, forceAutoplay: true })
314 } 302 }
315 }) 303 })
316 304
317 if (video.state.id === VideoState.WAITING_FOR_LIVE || video.state.id === VideoState.LIVE_ENDED) { 305 if (video.state.id === VideoState.WAITING_FOR_LIVE || video.state.id === VideoState.LIVE_ENDED) {
318 this.liveManager.displayInfo({ state: video.state.id, translations }) 306 this.liveManager.displayInfo({ state: video.state.id, translations })
319 307 this.peertubePlayer.disable()
320 this.disablePlayer()
321 } else { 308 } else {
322 this.correctlyHandleLiveEnding(translations) 309 this.correctlyHandleLiveEnding(translations)
323 } 310 }
@@ -326,74 +313,15 @@ export class PeerTubeEmbed {
326 this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video }) 313 this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video })
327 } 314 }
328 315
329 private resetPlayerElement () {
330 if (this.player) {
331 this.player.dispose()
332 this.player = undefined
333 }
334
335 const playerElement = document.createElement('video')
336 playerElement.className = 'video-js vjs-peertube-skin'
337 playerElement.setAttribute('playsinline', 'true')
338
339 this.playerHTML.setPlayerElement(playerElement)
340 this.playerHTML.addPlayerElementToDOM()
341 }
342
343 private async buildPlayerPlaylistUpnext () {
344 const translations = await this.translationsPromise
345
346 this.player.upnext({
347 timeout: 10000, // 10s
348 headText: peertubeTranslate('Up Next', translations),
349 cancelText: peertubeTranslate('Cancel', translations),
350 suspendedText: peertubeTranslate('Autoplay is suspended', translations),
351 getTitle: () => this.playlistTracker.nextVideoTitle(),
352 next: () => this.playNextPlaylistVideo(),
353 condition: () => !!this.playlistTracker.getNextPlaylistElement(),
354 suspended: () => false
355 })
356 }
357
358 private buildPlayerDock (videoInfo: VideoDetails) {
359 if (!this.playerManagerOptions.hasControls()) return
360
361 // On webtorrent fallback, player may have been disposed
362 if (!this.player.player_) return
363
364 const title = this.playerManagerOptions.hasTitle()
365 ? videoInfo.name
366 : undefined
367
368 const description = this.playerManagerOptions.hasWarningTitle() && this.playerManagerOptions.hasP2PEnabled()
369 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
370 : undefined
371
372 if (!title && !description) return
373
374 const availableAvatars = videoInfo.channel.avatars.filter(a => a.width < 50)
375 const avatar = availableAvatars.length !== 0
376 ? availableAvatars[0]
377 : undefined
378
379 this.player.peertubeDock({
380 title,
381 description,
382 avatarUrl: title && avatar
383 ? avatar.path
384 : undefined
385 })
386 }
387
388 private buildCSS () { 316 private buildCSS () {
389 const body = document.getElementById('custom-css') 317 const body = document.getElementById('custom-css')
390 318
391 if (this.playerManagerOptions.hasBigPlayBackgroundColor()) { 319 if (this.playerOptionsBuilder.hasBigPlayBackgroundColor()) {
392 body.style.setProperty('--embedBigPlayBackgroundColor', this.playerManagerOptions.getBigPlayBackgroundColor()) 320 body.style.setProperty('--embedBigPlayBackgroundColor', this.playerOptionsBuilder.getBigPlayBackgroundColor())
393 } 321 }
394 322
395 if (this.playerManagerOptions.hasForegroundColor()) { 323 if (this.playerOptionsBuilder.hasForegroundColor()) {
396 body.style.setProperty('--embedForegroundColor', this.playerManagerOptions.getForegroundColor()) 324 body.style.setProperty('--embedForegroundColor', this.playerOptionsBuilder.getForegroundColor())
397 } 325 }
398 } 326 }
399 327
@@ -415,23 +343,10 @@ export class PeerTubeEmbed {
415 // Display the live ended information 343 // Display the live ended information
416 this.liveManager.displayInfo({ state: VideoState.LIVE_ENDED, translations }) 344 this.liveManager.displayInfo({ state: VideoState.LIVE_ENDED, translations })
417 345
418 this.disablePlayer() 346 this.peertubePlayer.disable()
419 }) 347 })
420 } 348 }
421 349
422 private disablePlayer () {
423 if (this.player.isFullscreen()) {
424 this.player.exitFullscreen()
425 }
426
427 // Disable player
428 this.player.hasStarted(false)
429 this.player.removeClass('vjs-has-autoplay')
430 this.player.bigPlayButton.hide();
431
432 (this.player.el() as HTMLElement).style.pointerEvents = 'none'
433 }
434
435 private async handlePasswordError (err: PeerTubeServerError) { 350 private async handlePasswordError (err: PeerTubeServerError) {
436 let incorrectPassword: boolean = null 351 let incorrectPassword: boolean = null
437 if (err.serverCode === ServerErrorCode.VIDEO_REQUIRES_PASSWORD) incorrectPassword = false 352 if (err.serverCode === ServerErrorCode.VIDEO_REQUIRES_PASSWORD) incorrectPassword = false
@@ -447,6 +362,33 @@ export class PeerTubeEmbed {
447 return true 362 return true
448 } 363 }
449 364
365 private async buildPlayerIfNeeded () {
366 if (this.peertubePlayer) {
367 this.peertubePlayer.enable()
368
369 return
370 }
371
372 const playerElement = document.createElement('video')
373 playerElement.className = 'video-js vjs-peertube-skin'
374 playerElement.setAttribute('playsinline', 'true')
375
376 this.playerHTML.setPlayerElement(playerElement)
377 this.playerHTML.addPlayerElementToDOM()
378
379 const [ { PeerTubePlayer } ] = await Promise.all([
380 this.PeerTubePlayerManagerModulePromise,
381 this.peertubePlugin.loadPlugins(this.config, await this.translationsPromise)
382 ])
383
384 const constructorOptions = this.playerOptionsBuilder.getPlayerConstructorOptions({
385 serverConfig: this.config,
386 authorizationHeader: () => this.http.getHeaderTokenValue()
387 })
388 this.peertubePlayer = new PeerTubePlayer(constructorOptions)
389
390 this.player = this.peertubePlayer.getPlayer()
391 }
450} 392}
451 393
452PeerTubeEmbed.main() 394PeerTubeEmbed.main()