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