]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/standalone/videos/embed.ts
Refactor embed
[github/Chocobozzz/PeerTube.git] / client / src / standalone / videos / embed.ts
CommitLineData
202e7223 1import './embed.scss'
57d65032
C
2import '../../assets/player/shared/dock/peertube-dock-component'
3import '../../assets/player/shared/dock/peertube-dock-plugin'
583eb04b 4import videojs from 'video.js'
bd45d503 5import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
f1a0f3b7 6import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
583eb04b 7import { TranslationsManager } from '../../assets/player/translations-manager'
f1a0f3b7 8import { getParamString } from '../../root-helpers'
aea0b0e7 9import { PeerTubeEmbedApi } from './embed-api'
f1a0f3b7
C
10import { AuthHTTP, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
11import { PlayerHTML } from './shared/player-html'
12import { PeertubePlayerManager } from '../../assets/player'
202e7223 13
5efab546 14export class PeerTubeEmbed {
7e37e111 15 player: videojs.Player
902aa3a0 16 api: PeerTubeEmbedApi = null
5abc96fc 17
aea0b0e7
C
18 config: HTMLServerConfig
19
5abc96fc 20 private translationsPromise: Promise<{ [id: string]: string }>
5abc96fc
C
21 private PeertubePlayerManagerModulePromise: Promise<any>
22
f1a0f3b7
C
23 private readonly http: AuthHTTP
24 private readonly videoFetcher: VideoFetcher
25 private readonly playlistFetcher: PlaylistFetcher
26 private readonly peertubePlugin: PeerTubePlugin
27 private readonly playerHTML: PlayerHTML
28 private readonly playerManagerOptions: PlayerManagerOptions
5abc96fc 29
f1a0f3b7 30 private playlistTracker: PlaylistTracker
5abc96fc 31
f1a0f3b7
C
32 constructor (videoWrapperId: string) {
33 this.http = new AuthHTTP()
f9562863 34
f1a0f3b7
C
35 this.videoFetcher = new VideoFetcher(this.http)
36 this.playlistFetcher = new PlaylistFetcher(this.http)
37 this.peertubePlugin = new PeerTubePlugin(this.http)
38 this.playerHTML = new PlayerHTML(videoWrapperId)
39 this.playerManagerOptions = new PlayerManagerOptions(this.playerHTML, this.videoFetcher, this.peertubePlugin)
aea0b0e7
C
40
41 try {
42 this.config = JSON.parse(window['PeerTubeServerConfig'])
43 } catch (err) {
44 console.error('Cannot parse HTML config.', err)
45 }
902aa3a0
C
46 }
47
9df52d66
C
48 static async main () {
49 const videoContainerId = 'video-wrapper'
50 const embed = new PeerTubeEmbed(videoContainerId)
51 await embed.init()
52 }
53
f1a0f3b7
C
54 getPlayerElement () {
55 return this.playerHTML.getPlayerElement()
5abc96fc
C
56 }
57
f1a0f3b7
C
58 getScope () {
59 return this.playerManagerOptions.getScope()
99941732 60 }
d4f3fea6 61
f1a0f3b7 62 // ---------------------------------------------------------------------------
16f7022b 63
f1a0f3b7
C
64 async init () {
65 this.translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
66 this.PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager')
f443a746 67
f1a0f3b7
C
68 // Issue when we parsed config from HTML, fallback to API
69 if (!this.config) {
70 this.config = await this.http.fetch('/api/v1/config', { optionalAuth: false })
71 .then(res => res.json())
72 }
5abc96fc 73
f1a0f3b7
C
74 const videoId = this.isPlaylistEmbed()
75 ? await this.initPlaylist()
76 : this.getResourceId()
fb13852d 77
f1a0f3b7 78 if (!videoId) return
5abc96fc 79
f1a0f3b7 80 return this.loadVideoAndBuildPlayer(videoId)
99941732 81 }
d4f3fea6 82
f1a0f3b7
C
83 private async initPlaylist () {
84 const playlistId = this.getResourceId()
ad3fa0c5 85
f1a0f3b7
C
86 try {
87 const res = await this.playlistFetcher.loadPlaylist(playlistId)
99941732 88
f1a0f3b7
C
89 const [ playlist, playlistElementResult ] = await Promise.all([
90 res.playlistResponse.json() as Promise<VideoPlaylist>,
91 res.videosResponse.json() as Promise<ResultList<VideoPlaylistElement>>
92 ])
99941732 93
f1a0f3b7 94 const allPlaylistElements = await this.playlistFetcher.loadAllPlaylistVideos(playlistId, playlistElementResult)
ad3fa0c5 95
f1a0f3b7 96 this.playlistTracker = new PlaylistTracker(playlist, allPlaylistElements)
2a71d286 97
f1a0f3b7
C
98 const params = new URL(window.location.toString()).searchParams
99 const playlistPositionParam = getParamString(params, 'playlistPosition')
99941732 100
f1a0f3b7
C
101 const position = playlistPositionParam
102 ? parseInt(playlistPositionParam + '', 10)
103 : 1
99941732 104
f1a0f3b7
C
105 this.playlistTracker.setPosition(position)
106 } catch (err) {
107 this.playerHTML.displayError(err.message, await this.translationsPromise)
108 return undefined
109 }
5abc96fc 110
f1a0f3b7 111 return this.playlistTracker.getCurrentElement().video.uuid
5abc96fc
C
112 }
113
f1a0f3b7
C
114 private initializeApi () {
115 if (this.playerManagerOptions.hasAPIEnabled()) {
116 this.api = new PeerTubeEmbedApi(this)
117 this.api.initialize()
118 }
99941732 119 }
d4f3fea6 120
f1a0f3b7 121 // ---------------------------------------------------------------------------
da99ccf2 122
f1a0f3b7
C
123 async playNextPlaylistVideo () {
124 const next = this.playlistTracker.getNextPlaylistElement()
9054a8b6
C
125 if (!next) {
126 console.log('Next element not found in playlist.')
127 return
128 }
129
f1a0f3b7 130 this.playlistTracker.setCurrentElement(next)
9054a8b6 131
f1a0f3b7 132 return this.loadVideoAndBuildPlayer(next.video.uuid)
9054a8b6
C
133 }
134
f1a0f3b7
C
135 async playPreviousPlaylistVideo () {
136 const previous = this.playlistTracker.getPreviousPlaylistElement()
9054a8b6
C
137 if (!previous) {
138 console.log('Previous element not found in playlist.')
139 return
140 }
141
f1a0f3b7 142 this.playlistTracker.setCurrentElement(previous)
9054a8b6 143
f1a0f3b7 144 await this.loadVideoAndBuildPlayer(previous.video.uuid)
9054a8b6
C
145 }
146
f1a0f3b7
C
147 getCurrentPlaylistPosition () {
148 return this.playlistTracker.getCurrentPosition()
902aa3a0
C
149 }
150
f1a0f3b7 151 // ---------------------------------------------------------------------------
5abc96fc 152
f1a0f3b7 153 private async loadVideoAndBuildPlayer (uuid: string) {
be59656c 154 try {
f1a0f3b7 155 const { videoResponse, captionsPromise } = await this.videoFetcher.loadVideo(uuid)
5abc96fc 156
f1a0f3b7 157 return this.buildVideoPlayer(videoResponse, captionsPromise)
be59656c 158 } catch (err) {
f1a0f3b7 159 this.playerHTML.displayError(err.message, await this.translationsPromise)
be59656c 160 }
a950e4c8
C
161 }
162
5abc96fc 163 private async buildVideoPlayer (videoResponse: Response, captionsPromise: Promise<Response>) {
f1a0f3b7 164 const alreadyHadPlayer = this.resetPlayerElement()
aea0b0e7 165
f443a746 166 const videoInfoPromise: Promise<{ video: VideoDetails, live?: LiveVideo }> = videoResponse.json()
5abc96fc 167 .then((videoInfo: VideoDetails) => {
f1a0f3b7 168 this.playerManagerOptions.loadParams(this.config, videoInfo)
200eaf51 169
f1a0f3b7
C
170 if (!alreadyHadPlayer && !this.playerManagerOptions.hasAutoplay()) {
171 this.playerHTML.buildPlaceholder(videoInfo)
172 }
5abc96fc 173
f1a0f3b7
C
174 if (!videoInfo.isLive) {
175 return { video: videoInfo }
176 }
f443a746 177
f1a0f3b7 178 return this.videoFetcher.loadVideoWithLive(videoInfo)
5abc96fc
C
179 })
180
f1a0f3b7 181 const [ { video, live }, translations, captionsResponse, PeertubePlayerManagerModule ] = await Promise.all([
5abc96fc
C
182 videoInfoPromise,
183 this.translationsPromise,
184 captionsPromise,
5abc96fc
C
185 this.PeertubePlayerManagerModulePromise
186 ])
3f9c4955 187
f1a0f3b7 188 await this.peertubePlugin.loadPlugins(this.config, translations)
1a8c2d74 189
f1a0f3b7 190 const PlayerManager: typeof PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
a9bfa85d 191
f1a0f3b7
C
192 const options = await this.playerManagerOptions.getPlayerOptions({
193 video,
194 captionsResponse,
195 alreadyHadPlayer,
196 translations,
197 onVideoUpdate: (uuid: string) => this.loadVideoAndBuildPlayer(uuid),
2adfc7ea 198
f1a0f3b7
C
199 playlistTracker: this.playlistTracker,
200 playNextPlaylistVideo: () => this.playNextPlaylistVideo(),
201 playPreviousPlaylistVideo: () => this.playPreviousPlaylistVideo(),
1a8c2d74 202
f1a0f3b7
C
203 live
204 })
202e7223 205
f1a0f3b7 206 this.player = await PlayerManager.initialize(this.playerManagerOptions.getMode(), options, (player: videojs.Player) => {
9df52d66
C
207 this.player = player
208 })
209
f1a0f3b7
C
210 this.player.on('customError', (event: any, data: any) => {
211 const message = data?.err?.message || ''
212 if (!message.includes('from xs param')) return
213
214 this.player.dispose()
215 this.playerHTML.removePlayerElement()
216 this.playerHTML.displayError('This video is not available because the remote instance is not responding.', translations)
217 })
99941732 218
9df52d66 219 window['videojsPlayer'] = this.player
902aa3a0 220
5efab546 221 this.buildCSS()
f1a0f3b7 222 this.buildPlayerDock(video)
5efab546 223 this.initializeApi()
3f9c4955 224
f1a0f3b7 225 this.playerHTML.removePlaceholder()
5abc96fc
C
226
227 if (this.isPlaylistEmbed()) {
f1a0f3b7 228 await this.buildPlayerPlaylistUpnext()
1a8c2d74 229
4572c3d0 230 this.player.playlist().updateSelected()
1a8c2d74
C
231
232 this.player.on('stopped', () => {
f1a0f3b7 233 this.playNextPlaylistVideo()
1a8c2d74 234 })
5abc96fc 235 }
f9562863 236
f1a0f3b7 237 this.peertubePlugin.getPluginsManager().runHook('action:embed.player.loaded', undefined, { player: this.player, videojs, video })
5abc96fc
C
238 }
239
f1a0f3b7
C
240 private resetPlayerElement () {
241 let alreadyHadPlayer = false
5abc96fc 242
f1a0f3b7
C
243 if (this.player) {
244 this.player.dispose()
245 alreadyHadPlayer = true
246 }
5abc96fc 247
f1a0f3b7
C
248 const playerElement = document.createElement('video')
249 playerElement.className = 'video-js vjs-peertube-skin'
250 playerElement.setAttribute('playsinline', 'true')
5abc96fc 251
f1a0f3b7
C
252 this.playerHTML.setPlayerElement(playerElement)
253 this.playerHTML.addPlayerElementToDOM()
5abc96fc 254
f1a0f3b7 255 return alreadyHadPlayer
5efab546
C
256 }
257
f1a0f3b7
C
258 private async buildPlayerPlaylistUpnext () {
259 const translations = await this.translationsPromise
260
261 this.player.upnext({
262 timeout: 10000, // 10s
263 headText: peertubeTranslate('Up Next', translations),
264 cancelText: peertubeTranslate('Cancel', translations),
265 suspendedText: peertubeTranslate('Autoplay is suspended', translations),
266 getTitle: () => this.playlistTracker.nextVideoTitle(),
267 next: () => this.playNextPlaylistVideo(),
268 condition: () => !!this.playlistTracker.getNextPlaylistElement(),
269 suspended: () => false
270 })
5efab546
C
271 }
272
f1a0f3b7
C
273 private buildPlayerDock (videoInfo: VideoDetails) {
274 if (!this.playerManagerOptions.hasControls()) return
5efab546 275
818c449b
C
276 // On webtorrent fallback, player may have been disposed
277 if (!this.player.player_) return
5efab546 278
f1a0f3b7
C
279 const title = this.playerManagerOptions.hasTitle()
280 ? videoInfo.name
281 : undefined
282
283 const description = this.playerManagerOptions.hasWarningTitle() && this.playerManagerOptions.hasP2PEnabled()
abb3097e
C
284 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
285 : undefined
286
f1a0f3b7
C
287 if (!title && !description) return
288
01dd04cd
C
289 const availableAvatars = videoInfo.channel.avatars.filter(a => a.width < 50)
290 const avatar = availableAvatars.length !== 0
291 ? availableAvatars[0]
292 : undefined
293
f1a0f3b7
C
294 this.player.peertubeDock({
295 title,
296 description,
297 avatarUrl: title && avatar
298 ? avatar.path
299 : undefined
300 })
5efab546 301 }
16f7022b 302
5efab546
C
303 private buildCSS () {
304 const body = document.getElementById('custom-css')
305
f1a0f3b7
C
306 if (this.playerManagerOptions.hasBigPlayBackgroundColor()) {
307 body.style.setProperty('--embedBigPlayBackgroundColor', this.playerManagerOptions.getBigPlayBackgroundColor())
5efab546
C
308 }
309
f1a0f3b7
C
310 if (this.playerManagerOptions.hasForegroundColor()) {
311 body.style.setProperty('--embedForegroundColor', this.playerManagerOptions.getForegroundColor())
5efab546 312 }
99941732 313 }
6d88de72 314
f1a0f3b7 315 // ---------------------------------------------------------------------------
207612df 316
5abc96fc
C
317 private getResourceId () {
318 const urlParts = window.location.pathname.split('/')
9df52d66 319 return urlParts[urlParts.length - 1]
5abc96fc
C
320 }
321
322 private isPlaylistEmbed () {
323 return window.location.pathname.split('/')[1] === 'video-playlists'
324 }
99941732
WL
325}
326
327PeerTubeEmbed.main()
c21a0aa8
C
328 .catch(err => {
329 (window as any).displayIncompatibleBrowser()
330
331 console.error('Cannot init embed.', err)
332 })