]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/standalone/videos/embed.ts
decouple video abuse details from embed, add embed to block list details
[github/Chocobozzz/PeerTube.git] / client / src / standalone / videos / embed.ts
CommitLineData
202e7223
C
1import './embed.scss'
2
3f9c4955 3import {
3f9c4955
C
4 peertubeTranslate,
5 ResultList,
6 ServerConfig,
7 VideoDetails
8} from '../../../../shared'
59c76ffa 9import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
09209296
C
10import {
11 P2PMediaLoaderOptions,
09209296
C
12 PeertubePlayerManagerOptions,
13 PlayerMode
14} from '../../assets/player/peertube-player-manager'
15import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
5efab546 16import { PeerTubeEmbedApi } from './embed-api'
3f9c4955 17import { TranslationsManager } from '../../assets/player/translations-manager'
512decf3 18import videojs from 'video.js'
abb3097e 19import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
71ab65d0 20import { AuthUser } from '@app/core/auth/auth-user.model'
abb3097e
C
21
22type Translations = { [ id: string ]: string }
202e7223 23
5efab546 24export class PeerTubeEmbed {
902aa3a0 25 videoElement: HTMLVideoElement
7e37e111 26 player: videojs.Player
902aa3a0 27 api: PeerTubeEmbedApi = null
3b019808
C
28 autoplay: boolean
29 controls: boolean
30 muted: boolean
31 loop: boolean
32 subtitle: string
902aa3a0 33 enableApi = false
1f6824c9 34 startTime: number | string = 0
f0a39880 35 stopTime: number | string
5efab546
C
36
37 title: boolean
38 warningTitle: boolean
08d9ba0f 39 peertubeLink: boolean
5efab546
C
40 bigPlayBackgroundColor: string
41 foregroundColor: string
42
3b6f205c 43 mode: PlayerMode
902aa3a0
C
44 scope = 'peertube'
45
71ab65d0
RK
46 user: AuthUser
47 headers = new Headers()
48
902aa3a0 49 static async main () {
c6352f2c 50 const videoContainerId = 'video-container'
99941732
WL
51 const embed = new PeerTubeEmbed(videoContainerId)
52 await embed.init()
53 }
902aa3a0
C
54
55 constructor (private videoContainerId: string) {
56 this.videoElement = document.getElementById(videoContainerId) as HTMLVideoElement
57 }
58
99941732
WL
59 getVideoUrl (id: string) {
60 return window.location.origin + '/api/v1/videos/' + id
61 }
d4f3fea6 62
99941732 63 loadVideoInfo (videoId: string): Promise<Response> {
71ab65d0 64 return fetch(this.getVideoUrl(videoId), { headers: this.headers })
99941732 65 }
d4f3fea6 66
16f7022b 67 loadVideoCaptions (videoId: string): Promise<Response> {
71ab65d0 68 return fetch(this.getVideoUrl(videoId) + '/captions', { headers: this.headers })
16f7022b
C
69 }
70
31b6ddf8
C
71 loadConfig (): Promise<Response> {
72 return fetch('/api/v1/config')
73 }
74
99941732
WL
75 removeElement (element: HTMLElement) {
76 element.parentElement.removeChild(element)
77 }
d4f3fea6 78
abb3097e 79 displayError (text: string, translations?: Translations) {
99941732 80 // Remove video element
6d88de72 81 if (this.videoElement) this.removeElement(this.videoElement)
99941732 82
ad3fa0c5
C
83 const translatedText = peertubeTranslate(text, translations)
84 const translatedSorry = peertubeTranslate('Sorry', translations)
85
86 document.title = translatedSorry + ' - ' + translatedText
99941732
WL
87
88 const errorBlock = document.getElementById('error-block')
89 errorBlock.style.display = 'flex'
90
ad3fa0c5
C
91 const errorTitle = document.getElementById('error-title')
92 errorTitle.innerHTML = peertubeTranslate('Sorry', translations)
93
99941732 94 const errorText = document.getElementById('error-content')
ad3fa0c5 95 errorText.innerHTML = translatedText
99941732
WL
96 }
97
abb3097e 98 videoNotFound (translations?: Translations) {
99941732 99 const text = 'This video does not exist.'
ad3fa0c5 100 this.displayError(text, translations)
99941732
WL
101 }
102
abb3097e 103 videoFetchError (translations?: Translations) {
99941732 104 const text = 'We cannot fetch the video. Please try again later.'
ad3fa0c5 105 this.displayError(text, translations)
99941732
WL
106 }
107
3b019808 108 getParamToggle (params: URLSearchParams, name: string, defaultValue?: boolean) {
99941732
WL
109 return params.has(name) ? (params.get(name) === '1' || params.get(name) === 'true') : defaultValue
110 }
d4f3fea6 111
3b019808 112 getParamString (params: URLSearchParams, name: string, defaultValue?: string) {
99941732
WL
113 return params.has(name) ? params.get(name) : defaultValue
114 }
da99ccf2 115
902aa3a0 116 async init () {
99941732 117 try {
71ab65d0 118 this.user = AuthUser.load()
99941732
WL
119 await this.initCore()
120 } catch (e) {
121 console.error(e)
122 }
123 }
124
902aa3a0
C
125 private initializeApi () {
126 if (!this.enableApi) return
127
128 this.api = new PeerTubeEmbedApi(this)
129 this.api.initialize()
130 }
131
0f2f274c 132 private loadParams (video: VideoDetails) {
da99ccf2 133 try {
c4710631 134 const params = new URL(window.location.toString()).searchParams
99941732 135
31b6ddf8
C
136 this.autoplay = this.getParamToggle(params, 'autoplay', false)
137 this.controls = this.getParamToggle(params, 'controls', true)
64645512 138 this.muted = this.getParamToggle(params, 'muted', undefined)
31b6ddf8 139 this.loop = this.getParamToggle(params, 'loop', false)
5efab546 140 this.title = this.getParamToggle(params, 'title', true)
99941732 141 this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
5efab546 142 this.warningTitle = this.getParamToggle(params, 'warningTitle', true)
08d9ba0f 143 this.peertubeLink = this.getParamToggle(params, 'peertubeLink', true)
f37bad63 144
3b019808
C
145 this.scope = this.getParamString(params, 'scope', this.scope)
146 this.subtitle = this.getParamString(params, 'subtitle')
147 this.startTime = this.getParamString(params, 'start')
f0a39880 148 this.stopTime = this.getParamString(params, 'stop')
3b6f205c 149
5efab546
C
150 this.bigPlayBackgroundColor = this.getParamString(params, 'bigPlayBackgroundColor')
151 this.foregroundColor = this.getParamString(params, 'foregroundColor')
152
0f2f274c
C
153 const modeParam = this.getParamString(params, 'mode')
154
155 if (modeParam) {
156 if (modeParam === 'p2p-media-loader') this.mode = 'p2p-media-loader'
157 else this.mode = 'webtorrent'
158 } else {
159 if (Array.isArray(video.streamingPlaylists) && video.streamingPlaylists.length !== 0) this.mode = 'p2p-media-loader'
160 else this.mode = 'webtorrent'
161 }
da99ccf2
C
162 } catch (err) {
163 console.error('Cannot get params from URL.', err)
164 }
99941732
WL
165 }
166
902aa3a0 167 private async initCore () {
6385c0cb
C
168 const urlParts = window.location.pathname.split('/')
169 const videoId = urlParts[ urlParts.length - 1 ]
99941732 170
71ab65d0
RK
171 if (this.user) {
172 this.headers.set('Authorization', `${this.user.getTokenType()} ${this.user.getAccessToken()}`)
173 }
174
3f9c4955
C
175 const videoPromise = this.loadVideoInfo(videoId)
176 const captionsPromise = this.loadVideoCaptions(videoId)
177 const configPromise = this.loadConfig()
178
179 const translationsPromise = TranslationsManager.getServerTranslations(window.location.origin, navigator.language)
180 const videoResponse = await videoPromise
99941732 181
16f7022b 182 if (!videoResponse.ok) {
3f9c4955
C
183 const serverTranslations = await translationsPromise
184
ad3fa0c5 185 if (videoResponse.status === 404) return this.videoNotFound(serverTranslations)
99941732 186
ad3fa0c5 187 return this.videoFetchError(serverTranslations)
99941732
WL
188 }
189
16f7022b 190 const videoInfo: VideoDetails = await videoResponse.json()
3f9c4955
C
191 this.loadPlaceholder(videoInfo)
192
193 const PeertubePlayerManagerModulePromise = import('../../assets/player/peertube-player-manager')
194
195 const promises = [ translationsPromise, captionsPromise, configPromise, PeertubePlayerManagerModulePromise ]
196 const [ serverTranslations, captionsResponse, configResponse, PeertubePlayerManagerModule ] = await Promise.all(promises)
197
198 const PeertubePlayerManager = PeertubePlayerManagerModule.PeertubePlayerManager
5efab546 199 const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
99941732 200
0f2f274c 201 this.loadParams(videoInfo)
da99ccf2 202
2adfc7ea
C
203 const options: PeertubePlayerManagerOptions = {
204 common: {
205 autoplay: this.autoplay,
206 controls: this.controls,
207 muted: this.muted,
208 loop: this.loop,
209 captions: videoCaptions.length !== 0,
210 startTime: this.startTime,
f0a39880 211 stopTime: this.stopTime,
2adfc7ea
C
212 subtitle: this.subtitle,
213
214 videoCaptions,
35f0a5e6 215 inactivityTimeout: 2500,
2adfc7ea 216 videoViewUrl: this.getVideoUrl(videoId) + '/views',
6ec0b75b 217
2adfc7ea 218 playerElement: this.videoElement,
6ec0b75b
C
219 onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element,
220
2adfc7ea
C
221 videoDuration: videoInfo.duration,
222 enableHotkeys: true,
08d9ba0f 223 peertubeLink: this.peertubeLink,
2adfc7ea 224 poster: window.location.origin + videoInfo.previewPath,
3d9a63d3 225 theaterButton: false,
2adfc7ea
C
226
227 serverUrl: window.location.origin,
228 language: navigator.language,
229 embedUrl: window.location.origin + videoInfo.embedPath
6ec0b75b
C
230 },
231
232 webtorrent: {
233 videoFiles: videoInfo.files
2adfc7ea 234 }
3b6f205c 235 }
2adfc7ea 236
3b6f205c 237 if (this.mode === 'p2p-media-loader') {
09209296
C
238 const hlsPlaylist = videoInfo.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
239
3b6f205c
C
240 Object.assign(options, {
241 p2pMediaLoader: {
09209296
C
242 playlistUrl: hlsPlaylist.playlistUrl,
243 segmentsSha256Url: hlsPlaylist.segmentsSha256Url,
244 redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl),
245 trackerAnnounce: videoInfo.trackerUrls,
5a71acd2 246 videoFiles: hlsPlaylist.files
09209296 247 } as P2PMediaLoaderOptions
3b6f205c 248 })
2adfc7ea 249 }
202e7223 250
7e37e111 251 this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: videojs.Player) => this.player = player)
2adfc7ea 252 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
99941732 253
2adfc7ea 254 window[ 'videojsPlayer' ] = this.player
902aa3a0 255
5efab546
C
256 this.buildCSS()
257
258 await this.buildDock(videoInfo, configResponse)
259
260 this.initializeApi()
3f9c4955
C
261
262 this.removePlaceholder()
5efab546
C
263 }
264
265 private handleError (err: Error, translations?: { [ id: string ]: string }) {
266 if (err.message.indexOf('from xs param') !== -1) {
267 this.player.dispose()
268 this.videoElement = null
269 this.displayError('This video is not available because the remote instance is not responding.', translations)
270 return
271 }
272 }
273
274 private async buildDock (videoInfo: VideoDetails, configResponse: Response) {
abb3097e 275 if (!this.controls) return
5efab546 276
818c449b
C
277 // On webtorrent fallback, player may have been disposed
278 if (!this.player.player_) return
5efab546 279
abb3097e 280 const title = this.title ? videoInfo.name : undefined
31b6ddf8 281
abb3097e
C
282 const config: ServerConfig = await configResponse.json()
283 const description = config.tracker.enabled && this.warningTitle
284 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
285 : undefined
286
287 this.player.dock({
288 title,
289 description
290 })
5efab546 291 }
16f7022b 292
5efab546
C
293 private buildCSS () {
294 const body = document.getElementById('custom-css')
295
296 if (this.bigPlayBackgroundColor) {
297 body.style.setProperty('--embedBigPlayBackgroundColor', this.bigPlayBackgroundColor)
298 }
299
300 if (this.foregroundColor) {
301 body.style.setProperty('--embedForegroundColor', this.foregroundColor)
302 }
99941732 303 }
6d88de72 304
5efab546
C
305 private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> {
306 if (captionsResponse.ok) {
307 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
308
309 return data.map(c => ({
310 label: peertubeTranslate(c.language.label, serverTranslations),
311 language: c.language.id,
312 src: window.location.origin + c.captionPath
313 }))
6d88de72 314 }
5efab546
C
315
316 return []
6d88de72 317 }
3f9c4955
C
318
319 private loadPlaceholder (video: VideoDetails) {
320 const placeholder = this.getPlaceholderElement()
321
322 const url = window.location.origin + video.previewPath
323 placeholder.style.backgroundImage = `url("${url}")`
324 }
325
326 private removePlaceholder () {
327 const placeholder = this.getPlaceholderElement()
328 placeholder.parentElement.removeChild(placeholder)
329 }
330
331 private getPlaceholderElement () {
332 return document.getElementById('placeholder-preview')
333 }
99941732
WL
334}
335
336PeerTubeEmbed.main()
902aa3a0 337 .catch(err => console.error('Cannot init embed.', err))