diff options
Diffstat (limited to 'client/src/standalone/videos/embed.ts')
-rw-r--r-- | client/src/standalone/videos/embed.ts | 182 |
1 files changed, 111 insertions, 71 deletions
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index ea3436c7c..32bf42e12 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -17,14 +17,19 @@ import 'core-js/es6/set' | |||
17 | // For google bot that uses Chrome 41 and does not understand fetch | 17 | // For google bot that uses Chrome 41 and does not understand fetch |
18 | import 'whatwg-fetch' | 18 | import 'whatwg-fetch' |
19 | 19 | ||
20 | import * as vjs from 'video.js' | ||
21 | import * as Channel from 'jschannel' | 20 | import * as Channel from 'jschannel' |
22 | 21 | ||
23 | import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' | 22 | import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' |
24 | import { addContextMenu, getServerTranslations, getVideojsOptions, loadLocaleInVideoJS } from '../../assets/player/peertube-player' | ||
25 | import { PeerTubeResolution } from '../player/definitions' | 23 | import { PeerTubeResolution } from '../player/definitions' |
26 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 24 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
27 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 25 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' |
26 | import { | ||
27 | P2PMediaLoaderOptions, | ||
28 | PeertubePlayerManager, | ||
29 | PeertubePlayerManagerOptions, | ||
30 | PlayerMode | ||
31 | } from '../../assets/player/peertube-player-manager' | ||
32 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | ||
28 | 33 | ||
29 | /** | 34 | /** |
30 | * Embed API exposes control of the embed player to the outside world via | 35 | * Embed API exposes control of the embed player to the outside world via |
@@ -70,16 +75,16 @@ class PeerTubeEmbedApi { | |||
70 | } | 75 | } |
71 | 76 | ||
72 | private setResolution (resolutionId: number) { | 77 | private setResolution (resolutionId: number) { |
73 | if (resolutionId === -1 && this.embed.player.peertube().isAutoResolutionForbidden()) return | 78 | if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return |
74 | 79 | ||
75 | // Auto resolution | 80 | // Auto resolution |
76 | if (resolutionId === -1) { | 81 | if (resolutionId === -1) { |
77 | this.embed.player.peertube().enableAutoResolution() | 82 | this.embed.player.webtorrent().enableAutoResolution() |
78 | return | 83 | return |
79 | } | 84 | } |
80 | 85 | ||
81 | this.embed.player.peertube().disableAutoResolution() | 86 | this.embed.player.webtorrent().disableAutoResolution() |
82 | this.embed.player.peertube().updateResolution(resolutionId) | 87 | this.embed.player.webtorrent().updateResolution(resolutionId) |
83 | } | 88 | } |
84 | 89 | ||
85 | /** | 90 | /** |
@@ -119,15 +124,17 @@ class PeerTubeEmbedApi { | |||
119 | 124 | ||
120 | // PeerTube specific capabilities | 125 | // PeerTube specific capabilities |
121 | 126 | ||
122 | this.embed.player.peertube().on('autoResolutionUpdate', () => this.loadResolutions()) | 127 | if (this.embed.player.webtorrent) { |
123 | this.embed.player.peertube().on('videoFileUpdate', () => this.loadResolutions()) | 128 | this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions()) |
129 | this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions()) | ||
130 | } | ||
124 | } | 131 | } |
125 | 132 | ||
126 | private loadResolutions () { | 133 | private loadWebTorrentResolutions () { |
127 | let resolutions = [] | 134 | let resolutions = [] |
128 | let currentResolutionId = this.embed.player.peertube().getCurrentResolutionId() | 135 | let currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId() |
129 | 136 | ||
130 | for (const videoFile of this.embed.player.peertube().videoFiles) { | 137 | for (const videoFile of this.embed.player.webtorrent().videoFiles) { |
131 | let label = videoFile.resolution.label | 138 | let label = videoFile.resolution.label |
132 | if (videoFile.fps && videoFile.fps >= 50) { | 139 | if (videoFile.fps && videoFile.fps >= 50) { |
133 | label += videoFile.fps | 140 | label += videoFile.fps |
@@ -154,12 +161,14 @@ class PeerTubeEmbed { | |||
154 | player: any | 161 | player: any |
155 | playerOptions: any | 162 | playerOptions: any |
156 | api: PeerTubeEmbedApi = null | 163 | api: PeerTubeEmbedApi = null |
157 | autoplay = false | 164 | autoplay: boolean |
158 | controls = true | 165 | controls: boolean |
159 | muted = false | 166 | muted: boolean |
160 | loop = false | 167 | loop: boolean |
168 | subtitle: string | ||
161 | enableApi = false | 169 | enableApi = false |
162 | startTime: number | string = 0 | 170 | startTime: number | string = 0 |
171 | mode: PlayerMode | ||
163 | scope = 'peertube' | 172 | scope = 'peertube' |
164 | 173 | ||
165 | static async main () { | 174 | static async main () { |
@@ -188,34 +197,40 @@ class PeerTubeEmbed { | |||
188 | element.parentElement.removeChild(element) | 197 | element.parentElement.removeChild(element) |
189 | } | 198 | } |
190 | 199 | ||
191 | displayError (text: string) { | 200 | displayError (text: string, translations?: { [ id: string ]: string }) { |
192 | // Remove video element | 201 | // Remove video element |
193 | if (this.videoElement) this.removeElement(this.videoElement) | 202 | if (this.videoElement) this.removeElement(this.videoElement) |
194 | 203 | ||
195 | document.title = 'Sorry - ' + text | 204 | const translatedText = peertubeTranslate(text, translations) |
205 | const translatedSorry = peertubeTranslate('Sorry', translations) | ||
206 | |||
207 | document.title = translatedSorry + ' - ' + translatedText | ||
196 | 208 | ||
197 | const errorBlock = document.getElementById('error-block') | 209 | const errorBlock = document.getElementById('error-block') |
198 | errorBlock.style.display = 'flex' | 210 | errorBlock.style.display = 'flex' |
199 | 211 | ||
212 | const errorTitle = document.getElementById('error-title') | ||
213 | errorTitle.innerHTML = peertubeTranslate('Sorry', translations) | ||
214 | |||
200 | const errorText = document.getElementById('error-content') | 215 | const errorText = document.getElementById('error-content') |
201 | errorText.innerHTML = text | 216 | errorText.innerHTML = translatedText |
202 | } | 217 | } |
203 | 218 | ||
204 | videoNotFound () { | 219 | videoNotFound (translations?: { [ id: string ]: string }) { |
205 | const text = 'This video does not exist.' | 220 | const text = 'This video does not exist.' |
206 | this.displayError(text) | 221 | this.displayError(text, translations) |
207 | } | 222 | } |
208 | 223 | ||
209 | videoFetchError () { | 224 | videoFetchError (translations?: { [ id: string ]: string }) { |
210 | const text = 'We cannot fetch the video. Please try again later.' | 225 | const text = 'We cannot fetch the video. Please try again later.' |
211 | this.displayError(text) | 226 | this.displayError(text, translations) |
212 | } | 227 | } |
213 | 228 | ||
214 | getParamToggle (params: URLSearchParams, name: string, defaultValue: boolean) { | 229 | getParamToggle (params: URLSearchParams, name: string, defaultValue?: boolean) { |
215 | return params.has(name) ? (params.get(name) === '1' || params.get(name) === 'true') : defaultValue | 230 | return params.has(name) ? (params.get(name) === '1' || params.get(name) === 'true') : defaultValue |
216 | } | 231 | } |
217 | 232 | ||
218 | getParamString (params: URLSearchParams, name: string, defaultValue: string) { | 233 | getParamString (params: URLSearchParams, name: string, defaultValue?: string) { |
219 | return params.has(name) ? params.get(name) : defaultValue | 234 | return params.has(name) ? params.get(name) : defaultValue |
220 | } | 235 | } |
221 | 236 | ||
@@ -238,36 +253,36 @@ class PeerTubeEmbed { | |||
238 | try { | 253 | try { |
239 | let params = new URL(window.location.toString()).searchParams | 254 | let params = new URL(window.location.toString()).searchParams |
240 | 255 | ||
241 | this.autoplay = this.getParamToggle(params, 'autoplay', this.autoplay) | 256 | this.autoplay = this.getParamToggle(params, 'autoplay') |
242 | this.controls = this.getParamToggle(params, 'controls', this.controls) | 257 | this.controls = this.getParamToggle(params, 'controls') |
243 | this.muted = this.getParamToggle(params, 'muted', this.muted) | 258 | this.muted = this.getParamToggle(params, 'muted') |
244 | this.loop = this.getParamToggle(params, 'loop', this.loop) | 259 | this.loop = this.getParamToggle(params, 'loop') |
245 | this.enableApi = this.getParamToggle(params, 'api', this.enableApi) | 260 | this.enableApi = this.getParamToggle(params, 'api', this.enableApi) |
261 | |||
246 | this.scope = this.getParamString(params, 'scope', this.scope) | 262 | this.scope = this.getParamString(params, 'scope', this.scope) |
263 | this.subtitle = this.getParamString(params, 'subtitle') | ||
264 | this.startTime = this.getParamString(params, 'start') | ||
247 | 265 | ||
248 | const startTimeParamString = params.get('start') | 266 | this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' |
249 | if (startTimeParamString) this.startTime = startTimeParamString | ||
250 | } catch (err) { | 267 | } catch (err) { |
251 | console.error('Cannot get params from URL.', err) | 268 | console.error('Cannot get params from URL.', err) |
252 | } | 269 | } |
253 | } | 270 | } |
254 | 271 | ||
255 | private async initCore () { | 272 | private async initCore () { |
256 | const urlParts = window.location.href.split('/') | 273 | const urlParts = window.location.pathname.split('/') |
257 | const lastPart = urlParts[ urlParts.length - 1 ] | 274 | const videoId = urlParts[ urlParts.length - 1 ] |
258 | const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] | ||
259 | 275 | ||
260 | const [ , serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ | 276 | const [ serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ |
261 | loadLocaleInVideoJS(window.location.origin, vjs, navigator.language), | 277 | PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), |
262 | getServerTranslations(window.location.origin, navigator.language), | ||
263 | this.loadVideoInfo(videoId), | 278 | this.loadVideoInfo(videoId), |
264 | this.loadVideoCaptions(videoId) | 279 | this.loadVideoCaptions(videoId) |
265 | ]) | 280 | ]) |
266 | 281 | ||
267 | if (!videoResponse.ok) { | 282 | if (!videoResponse.ok) { |
268 | if (videoResponse.status === 404) return this.videoNotFound() | 283 | if (videoResponse.status === 404) return this.videoNotFound(serverTranslations) |
269 | 284 | ||
270 | return this.videoFetchError() | 285 | return this.videoFetchError(serverTranslations) |
271 | } | 286 | } |
272 | 287 | ||
273 | const videoInfo: VideoDetails = await videoResponse.json() | 288 | const videoInfo: VideoDetails = await videoResponse.json() |
@@ -283,49 +298,74 @@ class PeerTubeEmbed { | |||
283 | 298 | ||
284 | this.loadParams() | 299 | this.loadParams() |
285 | 300 | ||
286 | const videojsOptions = getVideojsOptions({ | 301 | const options: PeertubePlayerManagerOptions = { |
287 | autoplay: this.autoplay, | 302 | common: { |
288 | controls: this.controls, | 303 | autoplay: this.autoplay, |
289 | muted: this.muted, | 304 | controls: this.controls, |
290 | loop: this.loop, | 305 | muted: this.muted, |
291 | startTime: this.startTime, | 306 | loop: this.loop, |
292 | 307 | captions: videoCaptions.length !== 0, | |
293 | videoCaptions, | 308 | startTime: this.startTime, |
294 | inactivityTimeout: 1500, | 309 | subtitle: this.subtitle, |
295 | videoViewUrl: this.getVideoUrl(videoId) + '/views', | 310 | |
296 | playerElement: this.videoElement, | 311 | videoCaptions, |
297 | videoFiles: videoInfo.files, | 312 | inactivityTimeout: 1500, |
298 | videoDuration: videoInfo.duration, | 313 | videoViewUrl: this.getVideoUrl(videoId) + '/views', |
299 | enableHotkeys: true, | 314 | |
300 | peertubeLink: true, | 315 | playerElement: this.videoElement, |
301 | poster: window.location.origin + videoInfo.previewPath, | 316 | onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element, |
302 | theaterMode: false | 317 | |
303 | }) | 318 | videoDuration: videoInfo.duration, |
319 | enableHotkeys: true, | ||
320 | peertubeLink: true, | ||
321 | poster: window.location.origin + videoInfo.previewPath, | ||
322 | theaterMode: false, | ||
323 | |||
324 | serverUrl: window.location.origin, | ||
325 | language: navigator.language, | ||
326 | embedUrl: window.location.origin + videoInfo.embedPath | ||
327 | }, | ||
328 | |||
329 | webtorrent: { | ||
330 | videoFiles: videoInfo.files | ||
331 | } | ||
332 | } | ||
304 | 333 | ||
305 | this.playerOptions = videojsOptions | 334 | if (this.mode === 'p2p-media-loader') { |
306 | this.player = vjs(this.videoContainerId, videojsOptions, () => { | 335 | const hlsPlaylist = videoInfo.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
307 | this.player.on('customError', (event, data) => this.handleError(data.err)) | 336 | |
337 | Object.assign(options, { | ||
338 | p2pMediaLoader: { | ||
339 | playlistUrl: hlsPlaylist.playlistUrl, | ||
340 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | ||
341 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | ||
342 | trackerAnnounce: videoInfo.trackerUrls, | ||
343 | videoFiles: videoInfo.files | ||
344 | } as P2PMediaLoaderOptions | ||
345 | }) | ||
346 | } | ||
308 | 347 | ||
309 | window[ 'videojsPlayer' ] = this.player | 348 | this.player = await PeertubePlayerManager.initialize(this.mode, options) |
310 | 349 | ||
311 | if (this.controls) { | 350 | this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) |
312 | this.player.dock({ | ||
313 | title: videoInfo.name, | ||
314 | description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') | ||
315 | }) | ||
316 | } | ||
317 | 351 | ||
318 | addContextMenu(this.player, window.location.origin + videoInfo.embedPath) | 352 | window[ 'videojsPlayer' ] = this.player |
319 | 353 | ||
320 | this.initializeApi() | 354 | if (this.controls) { |
321 | }) | 355 | this.player.dock({ |
356 | title: videoInfo.name, | ||
357 | description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') | ||
358 | }) | ||
359 | } | ||
360 | |||
361 | this.initializeApi() | ||
322 | } | 362 | } |
323 | 363 | ||
324 | private handleError (err: Error) { | 364 | private handleError (err: Error, translations?: { [ id: string ]: string }) { |
325 | if (err.message.indexOf('from xs param') !== -1) { | 365 | if (err.message.indexOf('from xs param') !== -1) { |
326 | this.player.dispose() | 366 | this.player.dispose() |
327 | this.videoElement = null | 367 | this.videoElement = null |
328 | this.displayError('This video is not available because the remote instance is not responding.') | 368 | this.displayError('This video is not available because the remote instance is not responding.', translations) |
329 | return | 369 | return |
330 | } | 370 | } |
331 | } | 371 | } |