diff options
Diffstat (limited to 'client/src/standalone')
-rw-r--r-- | client/src/standalone/videos/embed.html | 3 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 156 |
2 files changed, 98 insertions, 61 deletions
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html index b7cf13ec2..c3b6e08ca 100644 --- a/client/src/standalone/videos/embed.html +++ b/client/src/standalone/videos/embed.html | |||
@@ -6,6 +6,7 @@ | |||
6 | <meta charset="UTF-8"> | 6 | <meta charset="UTF-8"> |
7 | <meta name="viewport" content="width=device-width, initial-scale=1"> | 7 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
8 | <meta name="robots" content="noindex"> | 8 | <meta name="robots" content="noindex"> |
9 | <meta property="og:platform" content="PeerTube" /> | ||
9 | 10 | ||
10 | <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> | 11 | <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> |
11 | </head> | 12 | </head> |
@@ -13,7 +14,7 @@ | |||
13 | <body> | 14 | <body> |
14 | 15 | ||
15 | <div id="error-block"> | 16 | <div id="error-block"> |
16 | <h1 id="error-title">Sorry</h1> | 17 | <h1 id="error-title"></h1> |
17 | 18 | ||
18 | <div id="error-content"></div> | 19 | <div id="error-content"></div> |
19 | </div> | 20 | </div> |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 3a09f285e..32bf42e12 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -17,17 +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 | // FIXME: something weird with our path definition in tsconfig and typings | ||
21 | // @ts-ignore | ||
22 | import * as vjs from 'video.js' | ||
23 | |||
24 | import * as Channel from 'jschannel' | 20 | import * as Channel from 'jschannel' |
25 | 21 | ||
26 | import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' | 22 | import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' |
27 | import { addContextMenu, getServerTranslations, getVideojsOptions, loadLocaleInVideoJS } from '../../assets/player/peertube-player' | ||
28 | import { PeerTubeResolution } from '../player/definitions' | 23 | import { PeerTubeResolution } from '../player/definitions' |
29 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 24 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
30 | 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' | ||
31 | 33 | ||
32 | /** | 34 | /** |
33 | * 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 |
@@ -73,16 +75,16 @@ class PeerTubeEmbedApi { | |||
73 | } | 75 | } |
74 | 76 | ||
75 | private setResolution (resolutionId: number) { | 77 | private setResolution (resolutionId: number) { |
76 | if (resolutionId === -1 && this.embed.player.peertube().isAutoResolutionForbidden()) return | 78 | if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return |
77 | 79 | ||
78 | // Auto resolution | 80 | // Auto resolution |
79 | if (resolutionId === -1) { | 81 | if (resolutionId === -1) { |
80 | this.embed.player.peertube().enableAutoResolution() | 82 | this.embed.player.webtorrent().enableAutoResolution() |
81 | return | 83 | return |
82 | } | 84 | } |
83 | 85 | ||
84 | this.embed.player.peertube().disableAutoResolution() | 86 | this.embed.player.webtorrent().disableAutoResolution() |
85 | this.embed.player.peertube().updateResolution(resolutionId) | 87 | this.embed.player.webtorrent().updateResolution(resolutionId) |
86 | } | 88 | } |
87 | 89 | ||
88 | /** | 90 | /** |
@@ -122,15 +124,17 @@ class PeerTubeEmbedApi { | |||
122 | 124 | ||
123 | // PeerTube specific capabilities | 125 | // PeerTube specific capabilities |
124 | 126 | ||
125 | this.embed.player.peertube().on('autoResolutionUpdate', () => this.loadResolutions()) | 127 | if (this.embed.player.webtorrent) { |
126 | 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 | } | ||
127 | } | 131 | } |
128 | 132 | ||
129 | private loadResolutions () { | 133 | private loadWebTorrentResolutions () { |
130 | let resolutions = [] | 134 | let resolutions = [] |
131 | let currentResolutionId = this.embed.player.peertube().getCurrentResolutionId() | 135 | let currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId() |
132 | 136 | ||
133 | for (const videoFile of this.embed.player.peertube().videoFiles) { | 137 | for (const videoFile of this.embed.player.webtorrent().videoFiles) { |
134 | let label = videoFile.resolution.label | 138 | let label = videoFile.resolution.label |
135 | if (videoFile.fps && videoFile.fps >= 50) { | 139 | if (videoFile.fps && videoFile.fps >= 50) { |
136 | label += videoFile.fps | 140 | label += videoFile.fps |
@@ -164,6 +168,7 @@ class PeerTubeEmbed { | |||
164 | subtitle: string | 168 | subtitle: string |
165 | enableApi = false | 169 | enableApi = false |
166 | startTime: number | string = 0 | 170 | startTime: number | string = 0 |
171 | mode: PlayerMode | ||
167 | scope = 'peertube' | 172 | scope = 'peertube' |
168 | 173 | ||
169 | static async main () { | 174 | static async main () { |
@@ -192,27 +197,33 @@ class PeerTubeEmbed { | |||
192 | element.parentElement.removeChild(element) | 197 | element.parentElement.removeChild(element) |
193 | } | 198 | } |
194 | 199 | ||
195 | displayError (text: string) { | 200 | displayError (text: string, translations?: { [ id: string ]: string }) { |
196 | // Remove video element | 201 | // Remove video element |
197 | if (this.videoElement) this.removeElement(this.videoElement) | 202 | if (this.videoElement) this.removeElement(this.videoElement) |
198 | 203 | ||
199 | document.title = 'Sorry - ' + text | 204 | const translatedText = peertubeTranslate(text, translations) |
205 | const translatedSorry = peertubeTranslate('Sorry', translations) | ||
206 | |||
207 | document.title = translatedSorry + ' - ' + translatedText | ||
200 | 208 | ||
201 | const errorBlock = document.getElementById('error-block') | 209 | const errorBlock = document.getElementById('error-block') |
202 | errorBlock.style.display = 'flex' | 210 | errorBlock.style.display = 'flex' |
203 | 211 | ||
212 | const errorTitle = document.getElementById('error-title') | ||
213 | errorTitle.innerHTML = peertubeTranslate('Sorry', translations) | ||
214 | |||
204 | const errorText = document.getElementById('error-content') | 215 | const errorText = document.getElementById('error-content') |
205 | errorText.innerHTML = text | 216 | errorText.innerHTML = translatedText |
206 | } | 217 | } |
207 | 218 | ||
208 | videoNotFound () { | 219 | videoNotFound (translations?: { [ id: string ]: string }) { |
209 | const text = 'This video does not exist.' | 220 | const text = 'This video does not exist.' |
210 | this.displayError(text) | 221 | this.displayError(text, translations) |
211 | } | 222 | } |
212 | 223 | ||
213 | videoFetchError () { | 224 | videoFetchError (translations?: { [ id: string ]: string }) { |
214 | const text = 'We cannot fetch the video. Please try again later.' | 225 | const text = 'We cannot fetch the video. Please try again later.' |
215 | this.displayError(text) | 226 | this.displayError(text, translations) |
216 | } | 227 | } |
217 | 228 | ||
218 | getParamToggle (params: URLSearchParams, name: string, defaultValue?: boolean) { | 229 | getParamToggle (params: URLSearchParams, name: string, defaultValue?: boolean) { |
@@ -251,6 +262,8 @@ class PeerTubeEmbed { | |||
251 | this.scope = this.getParamString(params, 'scope', this.scope) | 262 | this.scope = this.getParamString(params, 'scope', this.scope) |
252 | this.subtitle = this.getParamString(params, 'subtitle') | 263 | this.subtitle = this.getParamString(params, 'subtitle') |
253 | this.startTime = this.getParamString(params, 'start') | 264 | this.startTime = this.getParamString(params, 'start') |
265 | |||
266 | this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' | ||
254 | } catch (err) { | 267 | } catch (err) { |
255 | console.error('Cannot get params from URL.', err) | 268 | console.error('Cannot get params from URL.', err) |
256 | } | 269 | } |
@@ -260,17 +273,16 @@ class PeerTubeEmbed { | |||
260 | const urlParts = window.location.pathname.split('/') | 273 | const urlParts = window.location.pathname.split('/') |
261 | const videoId = urlParts[ urlParts.length - 1 ] | 274 | const videoId = urlParts[ urlParts.length - 1 ] |
262 | 275 | ||
263 | const [ , serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ | 276 | const [ serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ |
264 | loadLocaleInVideoJS(window.location.origin, vjs, navigator.language), | 277 | PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), |
265 | getServerTranslations(window.location.origin, navigator.language), | ||
266 | this.loadVideoInfo(videoId), | 278 | this.loadVideoInfo(videoId), |
267 | this.loadVideoCaptions(videoId) | 279 | this.loadVideoCaptions(videoId) |
268 | ]) | 280 | ]) |
269 | 281 | ||
270 | if (!videoResponse.ok) { | 282 | if (!videoResponse.ok) { |
271 | if (videoResponse.status === 404) return this.videoNotFound() | 283 | if (videoResponse.status === 404) return this.videoNotFound(serverTranslations) |
272 | 284 | ||
273 | return this.videoFetchError() | 285 | return this.videoFetchError(serverTranslations) |
274 | } | 286 | } |
275 | 287 | ||
276 | const videoInfo: VideoDetails = await videoResponse.json() | 288 | const videoInfo: VideoDetails = await videoResponse.json() |
@@ -286,50 +298,74 @@ class PeerTubeEmbed { | |||
286 | 298 | ||
287 | this.loadParams() | 299 | this.loadParams() |
288 | 300 | ||
289 | const videojsOptions = getVideojsOptions({ | 301 | const options: PeertubePlayerManagerOptions = { |
290 | autoplay: this.autoplay, | 302 | common: { |
291 | controls: this.controls, | 303 | autoplay: this.autoplay, |
292 | muted: this.muted, | 304 | controls: this.controls, |
293 | loop: this.loop, | 305 | muted: this.muted, |
294 | startTime: this.startTime, | 306 | loop: this.loop, |
295 | subtitle: this.subtitle, | 307 | captions: videoCaptions.length !== 0, |
296 | 308 | startTime: this.startTime, | |
297 | videoCaptions, | 309 | subtitle: this.subtitle, |
298 | inactivityTimeout: 1500, | 310 | |
299 | videoViewUrl: this.getVideoUrl(videoId) + '/views', | 311 | videoCaptions, |
300 | playerElement: this.videoElement, | 312 | inactivityTimeout: 1500, |
301 | videoFiles: videoInfo.files, | 313 | videoViewUrl: this.getVideoUrl(videoId) + '/views', |
302 | videoDuration: videoInfo.duration, | 314 | |
303 | enableHotkeys: true, | 315 | playerElement: this.videoElement, |
304 | peertubeLink: true, | 316 | onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element, |
305 | poster: window.location.origin + videoInfo.previewPath, | 317 | |
306 | theaterMode: false | 318 | videoDuration: videoInfo.duration, |
307 | }) | 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 | } | ||
308 | 333 | ||
309 | this.playerOptions = videojsOptions | 334 | if (this.mode === 'p2p-media-loader') { |
310 | this.player = vjs(this.videoContainerId, videojsOptions, () => { | 335 | const hlsPlaylist = videoInfo.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
311 | this.player.on('customError', (event: any, data: any) => 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 | } | ||
312 | 347 | ||
313 | window[ 'videojsPlayer' ] = this.player | 348 | this.player = await PeertubePlayerManager.initialize(this.mode, options) |
314 | 349 | ||
315 | if (this.controls) { | 350 | this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) |
316 | this.player.dock({ | ||
317 | title: videoInfo.name, | ||
318 | description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') | ||
319 | }) | ||
320 | } | ||
321 | 351 | ||
322 | addContextMenu(this.player, window.location.origin + videoInfo.embedPath) | 352 | window[ 'videojsPlayer' ] = this.player |
323 | 353 | ||
324 | this.initializeApi() | 354 | if (this.controls) { |
325 | }) | 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() | ||
326 | } | 362 | } |
327 | 363 | ||
328 | private handleError (err: Error) { | 364 | private handleError (err: Error, translations?: { [ id: string ]: string }) { |
329 | if (err.message.indexOf('from xs param') !== -1) { | 365 | if (err.message.indexOf('from xs param') !== -1) { |
330 | this.player.dispose() | 366 | this.player.dispose() |
331 | this.videoElement = null | 367 | this.videoElement = null |
332 | 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) |
333 | return | 369 | return |
334 | } | 370 | } |
335 | } | 371 | } |