diff options
Diffstat (limited to 'client/src/standalone')
-rw-r--r-- | client/src/standalone/player/definitions.ts | 4 | ||||
-rw-r--r-- | client/src/standalone/player/events.ts | 4 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.html | 1 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 180 | ||||
-rw-r--r-- | client/src/standalone/videos/test-embed.ts | 24 |
5 files changed, 119 insertions, 94 deletions
diff --git a/client/src/standalone/player/definitions.ts b/client/src/standalone/player/definitions.ts index 7f9ef9b6f..afd10541b 100644 --- a/client/src/standalone/player/definitions.ts +++ b/client/src/standalone/player/definitions.ts | |||
@@ -1,6 +1,4 @@ | |||
1 | export interface EventHandler<T> { | 1 | export type EventHandler<T> = (ev: T) => void |
2 | (ev: T): void | ||
3 | } | ||
4 | 2 | ||
5 | export type PlayerEventType = | 3 | export type PlayerEventType = |
6 | 'pause' | 'play' | | 4 | 'pause' | 'play' | |
diff --git a/client/src/standalone/player/events.ts b/client/src/standalone/player/events.ts index f1639ef19..28a13c727 100644 --- a/client/src/standalone/player/events.ts +++ b/client/src/standalone/player/events.ts | |||
@@ -13,13 +13,13 @@ export class EventRegistrar { | |||
13 | private eventRegistrations: PlayerEventRegistrationMap = {} | 13 | private eventRegistrations: PlayerEventRegistrationMap = {} |
14 | 14 | ||
15 | public bindToChannel (channel: Channel.MessagingChannel) { | 15 | public bindToChannel (channel: Channel.MessagingChannel) { |
16 | for (let name of Object.keys(this.eventRegistrations)) { | 16 | for (const name of Object.keys(this.eventRegistrations)) { |
17 | channel.bind(name, (txn, params) => this.fire(name, params)) | 17 | channel.bind(name, (txn, params) => this.fire(name, params)) |
18 | } | 18 | } |
19 | } | 19 | } |
20 | 20 | ||
21 | public registerTypes (names: string[]) { | 21 | public registerTypes (names: string[]) { |
22 | for (let name of names) { | 22 | for (const name of names) { |
23 | this.eventRegistrations[ name ] = { registrations: [] } | 23 | this.eventRegistrations[ name ] = { registrations: [] } |
24 | } | 24 | } |
25 | } | 25 | } |
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html index f79cf68df..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> |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 54b8fb543..707f04253 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -1,33 +1,18 @@ | |||
1 | import './embed.scss' | 1 | import './embed.scss' |
2 | 2 | ||
3 | import 'core-js/es6/symbol' | ||
4 | import 'core-js/es6/object' | ||
5 | import 'core-js/es6/function' | ||
6 | import 'core-js/es6/parse-int' | ||
7 | import 'core-js/es6/parse-float' | ||
8 | import 'core-js/es6/number' | ||
9 | import 'core-js/es6/math' | ||
10 | import 'core-js/es6/string' | ||
11 | import 'core-js/es6/date' | ||
12 | import 'core-js/es6/array' | ||
13 | import 'core-js/es6/regexp' | ||
14 | import 'core-js/es6/map' | ||
15 | import 'core-js/es6/weak-map' | ||
16 | import 'core-js/es6/set' | ||
17 | // For google bot that uses Chrome 41 and does not understand fetch | ||
18 | import 'whatwg-fetch' | ||
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' | 3 | import * as Channel from 'jschannel' |
25 | 4 | ||
26 | import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared' | 5 | import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared' |
27 | import { addContextMenu, getServerTranslations, getVideojsOptions, loadLocaleInVideoJS } from '../../assets/player/peertube-player' | ||
28 | import { PeerTubeResolution } from '../player/definitions' | 6 | import { PeerTubeResolution } from '../player/definitions' |
29 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 7 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
30 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 8 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' |
9 | import { | ||
10 | P2PMediaLoaderOptions, | ||
11 | PeertubePlayerManager, | ||
12 | PeertubePlayerManagerOptions, | ||
13 | PlayerMode | ||
14 | } from '../../assets/player/peertube-player-manager' | ||
15 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | ||
31 | 16 | ||
32 | /** | 17 | /** |
33 | * Embed API exposes control of the embed player to the outside world via | 18 | * Embed API exposes control of the embed player to the outside world via |
@@ -55,7 +40,7 @@ class PeerTubeEmbedApi { | |||
55 | } | 40 | } |
56 | 41 | ||
57 | private constructChannel () { | 42 | private constructChannel () { |
58 | let channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope }) | 43 | const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope }) |
59 | 44 | ||
60 | channel.bind('play', (txn, params) => this.embed.player.play()) | 45 | channel.bind('play', (txn, params) => this.embed.player.play()) |
61 | channel.bind('pause', (txn, params) => this.embed.player.pause()) | 46 | channel.bind('pause', (txn, params) => this.embed.player.pause()) |
@@ -73,16 +58,16 @@ class PeerTubeEmbedApi { | |||
73 | } | 58 | } |
74 | 59 | ||
75 | private setResolution (resolutionId: number) { | 60 | private setResolution (resolutionId: number) { |
76 | if (resolutionId === -1 && this.embed.player.peertube().isAutoResolutionForbidden()) return | 61 | if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return |
77 | 62 | ||
78 | // Auto resolution | 63 | // Auto resolution |
79 | if (resolutionId === -1) { | 64 | if (resolutionId === -1) { |
80 | this.embed.player.peertube().enableAutoResolution() | 65 | this.embed.player.webtorrent().enableAutoResolution() |
81 | return | 66 | return |
82 | } | 67 | } |
83 | 68 | ||
84 | this.embed.player.peertube().disableAutoResolution() | 69 | this.embed.player.webtorrent().disableAutoResolution() |
85 | this.embed.player.peertube().updateResolution(resolutionId) | 70 | this.embed.player.webtorrent().updateResolution(resolutionId) |
86 | } | 71 | } |
87 | 72 | ||
88 | /** | 73 | /** |
@@ -97,8 +82,8 @@ class PeerTubeEmbedApi { | |||
97 | let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted' | 82 | let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted' |
98 | 83 | ||
99 | setInterval(() => { | 84 | setInterval(() => { |
100 | let position = this.element.currentTime | 85 | const position = this.element.currentTime |
101 | let volume = this.element.volume | 86 | const volume = this.element.volume |
102 | 87 | ||
103 | this.channel.notify({ | 88 | this.channel.notify({ |
104 | method: 'playbackStatusUpdate', | 89 | method: 'playbackStatusUpdate', |
@@ -122,15 +107,17 @@ class PeerTubeEmbedApi { | |||
122 | 107 | ||
123 | // PeerTube specific capabilities | 108 | // PeerTube specific capabilities |
124 | 109 | ||
125 | this.embed.player.peertube().on('autoResolutionUpdate', () => this.loadResolutions()) | 110 | if (this.embed.player.webtorrent) { |
126 | this.embed.player.peertube().on('videoFileUpdate', () => this.loadResolutions()) | 111 | this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions()) |
112 | this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions()) | ||
113 | } | ||
127 | } | 114 | } |
128 | 115 | ||
129 | private loadResolutions () { | 116 | private loadWebTorrentResolutions () { |
130 | let resolutions = [] | 117 | const resolutions = [] |
131 | let currentResolutionId = this.embed.player.peertube().getCurrentResolutionId() | 118 | const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId() |
132 | 119 | ||
133 | for (const videoFile of this.embed.player.peertube().videoFiles) { | 120 | for (const videoFile of this.embed.player.webtorrent().videoFiles) { |
134 | let label = videoFile.resolution.label | 121 | let label = videoFile.resolution.label |
135 | if (videoFile.fps && videoFile.fps >= 50) { | 122 | if (videoFile.fps && videoFile.fps >= 50) { |
136 | label += videoFile.fps | 123 | label += videoFile.fps |
@@ -164,6 +151,8 @@ class PeerTubeEmbed { | |||
164 | subtitle: string | 151 | subtitle: string |
165 | enableApi = false | 152 | enableApi = false |
166 | startTime: number | string = 0 | 153 | startTime: number | string = 0 |
154 | stopTime: number | string | ||
155 | mode: PlayerMode | ||
167 | scope = 'peertube' | 156 | scope = 'peertube' |
168 | 157 | ||
169 | static async main () { | 158 | static async main () { |
@@ -188,6 +177,10 @@ class PeerTubeEmbed { | |||
188 | return fetch(this.getVideoUrl(videoId) + '/captions') | 177 | return fetch(this.getVideoUrl(videoId) + '/captions') |
189 | } | 178 | } |
190 | 179 | ||
180 | loadConfig (): Promise<Response> { | ||
181 | return fetch('/api/v1/config') | ||
182 | } | ||
183 | |||
191 | removeElement (element: HTMLElement) { | 184 | removeElement (element: HTMLElement) { |
192 | element.parentElement.removeChild(element) | 185 | element.parentElement.removeChild(element) |
193 | } | 186 | } |
@@ -246,17 +239,20 @@ class PeerTubeEmbed { | |||
246 | 239 | ||
247 | private loadParams () { | 240 | private loadParams () { |
248 | try { | 241 | try { |
249 | let params = new URL(window.location.toString()).searchParams | 242 | const params = new URL(window.location.toString()).searchParams |
250 | 243 | ||
251 | this.autoplay = this.getParamToggle(params, 'autoplay') | 244 | this.autoplay = this.getParamToggle(params, 'autoplay', false) |
252 | this.controls = this.getParamToggle(params, 'controls') | 245 | this.controls = this.getParamToggle(params, 'controls', true) |
253 | this.muted = this.getParamToggle(params, 'muted') | 246 | this.muted = this.getParamToggle(params, 'muted', false) |
254 | this.loop = this.getParamToggle(params, 'loop') | 247 | this.loop = this.getParamToggle(params, 'loop', false) |
255 | this.enableApi = this.getParamToggle(params, 'api', this.enableApi) | 248 | this.enableApi = this.getParamToggle(params, 'api', this.enableApi) |
256 | 249 | ||
257 | this.scope = this.getParamString(params, 'scope', this.scope) | 250 | this.scope = this.getParamString(params, 'scope', this.scope) |
258 | this.subtitle = this.getParamString(params, 'subtitle') | 251 | this.subtitle = this.getParamString(params, 'subtitle') |
259 | this.startTime = this.getParamString(params, 'start') | 252 | this.startTime = this.getParamString(params, 'start') |
253 | this.stopTime = this.getParamString(params, 'stop') | ||
254 | |||
255 | this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' | ||
260 | } catch (err) { | 256 | } catch (err) { |
261 | console.error('Cannot get params from URL.', err) | 257 | console.error('Cannot get params from URL.', err) |
262 | } | 258 | } |
@@ -266,11 +262,11 @@ class PeerTubeEmbed { | |||
266 | const urlParts = window.location.pathname.split('/') | 262 | const urlParts = window.location.pathname.split('/') |
267 | const videoId = urlParts[ urlParts.length - 1 ] | 263 | const videoId = urlParts[ urlParts.length - 1 ] |
268 | 264 | ||
269 | const [ , serverTranslations, videoResponse, captionsResponse ] = await Promise.all([ | 265 | const [ serverTranslations, videoResponse, captionsResponse, configResponse ] = await Promise.all([ |
270 | loadLocaleInVideoJS(window.location.origin, vjs, navigator.language), | 266 | PeertubePlayerManager.getServerTranslations(window.location.origin, navigator.language), |
271 | getServerTranslations(window.location.origin, navigator.language), | ||
272 | this.loadVideoInfo(videoId), | 267 | this.loadVideoInfo(videoId), |
273 | this.loadVideoCaptions(videoId) | 268 | this.loadVideoCaptions(videoId), |
269 | this.loadConfig() | ||
274 | ]) | 270 | ]) |
275 | 271 | ||
276 | if (!videoResponse.ok) { | 272 | if (!videoResponse.ok) { |
@@ -292,43 +288,73 @@ class PeerTubeEmbed { | |||
292 | 288 | ||
293 | this.loadParams() | 289 | this.loadParams() |
294 | 290 | ||
295 | const videojsOptions = getVideojsOptions({ | 291 | const options: PeertubePlayerManagerOptions = { |
296 | autoplay: this.autoplay, | 292 | common: { |
297 | controls: this.controls, | 293 | autoplay: this.autoplay, |
298 | muted: this.muted, | 294 | controls: this.controls, |
299 | loop: this.loop, | 295 | muted: this.muted, |
300 | startTime: this.startTime, | 296 | loop: this.loop, |
301 | subtitle: this.subtitle, | 297 | captions: videoCaptions.length !== 0, |
302 | 298 | startTime: this.startTime, | |
303 | videoCaptions, | 299 | stopTime: this.stopTime, |
304 | inactivityTimeout: 1500, | 300 | subtitle: this.subtitle, |
305 | videoViewUrl: this.getVideoUrl(videoId) + '/views', | 301 | |
306 | playerElement: this.videoElement, | 302 | videoCaptions, |
307 | videoFiles: videoInfo.files, | 303 | inactivityTimeout: 1500, |
308 | videoDuration: videoInfo.duration, | 304 | videoViewUrl: this.getVideoUrl(videoId) + '/views', |
309 | enableHotkeys: true, | 305 | |
310 | peertubeLink: true, | 306 | playerElement: this.videoElement, |
311 | poster: window.location.origin + videoInfo.previewPath, | 307 | onPlayerElementChange: (element: HTMLVideoElement) => this.videoElement = element, |
312 | theaterMode: false | 308 | |
313 | }) | 309 | videoDuration: videoInfo.duration, |
310 | enableHotkeys: true, | ||
311 | peertubeLink: true, | ||
312 | poster: window.location.origin + videoInfo.previewPath, | ||
313 | theaterMode: false, | ||
314 | |||
315 | serverUrl: window.location.origin, | ||
316 | language: navigator.language, | ||
317 | embedUrl: window.location.origin + videoInfo.embedPath | ||
318 | }, | ||
319 | |||
320 | webtorrent: { | ||
321 | videoFiles: videoInfo.files | ||
322 | } | ||
323 | } | ||
314 | 324 | ||
315 | this.playerOptions = videojsOptions | 325 | if (this.mode === 'p2p-media-loader') { |
316 | this.player = vjs(this.videoContainerId, videojsOptions, () => { | 326 | const hlsPlaylist = videoInfo.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
317 | this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) | 327 | |
328 | Object.assign(options, { | ||
329 | p2pMediaLoader: { | ||
330 | playlistUrl: hlsPlaylist.playlistUrl, | ||
331 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | ||
332 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | ||
333 | trackerAnnounce: videoInfo.trackerUrls, | ||
334 | videoFiles: videoInfo.files | ||
335 | } as P2PMediaLoaderOptions | ||
336 | }) | ||
337 | } | ||
318 | 338 | ||
319 | window[ 'videojsPlayer' ] = this.player | 339 | this.player = await PeertubePlayerManager.initialize(this.mode, options) |
320 | 340 | ||
321 | if (this.controls) { | 341 | this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) |
322 | this.player.dock({ | ||
323 | title: videoInfo.name, | ||
324 | description: this.player.localize('Uses P2P, others may know your IP is downloading this video.') | ||
325 | }) | ||
326 | } | ||
327 | 342 | ||
328 | addContextMenu(this.player, window.location.origin + videoInfo.embedPath) | 343 | window[ 'videojsPlayer' ] = this.player |
329 | 344 | ||
330 | this.initializeApi() | 345 | if (this.controls) { |
331 | }) | 346 | const config: ServerConfig = await configResponse.json() |
347 | const description = config.tracker.enabled | ||
348 | ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' | ||
349 | : undefined | ||
350 | |||
351 | this.player.dock({ | ||
352 | title: videoInfo.name, | ||
353 | description | ||
354 | }) | ||
355 | } | ||
356 | |||
357 | this.initializeApi() | ||
332 | } | 358 | } |
333 | 359 | ||
334 | private handleError (err: Error, translations?: { [ id: string ]: string }) { | 360 | private handleError (err: Error, translations?: { [ id: string ]: string }) { |
diff --git a/client/src/standalone/videos/test-embed.ts b/client/src/standalone/videos/test-embed.ts index 30a298573..8e83d92a9 100644 --- a/client/src/standalone/videos/test-embed.ts +++ b/client/src/standalone/videos/test-embed.ts | |||
@@ -7,13 +7,13 @@ window.addEventListener('load', async () => { | |||
7 | const lastPart = urlParts[ urlParts.length - 1 ] | 7 | const lastPart = urlParts[ urlParts.length - 1 ] |
8 | const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] | 8 | const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ] |
9 | 9 | ||
10 | let iframe = document.createElement('iframe') | 10 | const iframe = document.createElement('iframe') |
11 | iframe.src = `/videos/embed/${videoId}?autoplay=1&controls=0&api=1` | 11 | iframe.src = `/videos/embed/${videoId}?autoplay=1&controls=0&api=1` |
12 | let mainElement = document.querySelector('#host') | 12 | const mainElement = document.querySelector('#host') |
13 | mainElement.appendChild(iframe) | 13 | mainElement.appendChild(iframe) |
14 | 14 | ||
15 | console.log(`Document finished loading.`) | 15 | console.log(`Document finished loading.`) |
16 | let player = new PeerTubePlayer(document.querySelector('iframe')) | 16 | const player = new PeerTubePlayer(document.querySelector('iframe')) |
17 | 17 | ||
18 | window[ 'player' ] = player | 18 | window[ 'player' ] = player |
19 | 19 | ||
@@ -21,7 +21,7 @@ window.addEventListener('load', async () => { | |||
21 | await player.ready | 21 | await player.ready |
22 | console.log(`Player is ready.`) | 22 | console.log(`Player is ready.`) |
23 | 23 | ||
24 | let monitoredEvents = [ | 24 | const monitoredEvents = [ |
25 | 'pause', | 25 | 'pause', |
26 | 'play', | 26 | 'play', |
27 | 'playbackStatusUpdate', | 27 | 'playbackStatusUpdate', |
@@ -36,18 +36,18 @@ window.addEventListener('load', async () => { | |||
36 | let playbackRates: number[] = [] | 36 | let playbackRates: number[] = [] |
37 | let currentRate = await player.getPlaybackRate() | 37 | let currentRate = await player.getPlaybackRate() |
38 | 38 | ||
39 | let updateRates = async () => { | 39 | const updateRates = async () => { |
40 | let rateListEl = document.querySelector('#rate-list') | 40 | const rateListEl = document.querySelector('#rate-list') |
41 | rateListEl.innerHTML = '' | 41 | rateListEl.innerHTML = '' |
42 | 42 | ||
43 | playbackRates.forEach(rate => { | 43 | playbackRates.forEach(rate => { |
44 | if (currentRate === rate) { | 44 | if (currentRate === rate) { |
45 | let itemEl = document.createElement('strong') | 45 | const itemEl = document.createElement('strong') |
46 | itemEl.innerText = `${rate} (active)` | 46 | itemEl.innerText = `${rate} (active)` |
47 | itemEl.style.display = 'block' | 47 | itemEl.style.display = 'block' |
48 | rateListEl.appendChild(itemEl) | 48 | rateListEl.appendChild(itemEl) |
49 | } else { | 49 | } else { |
50 | let itemEl = document.createElement('a') | 50 | const itemEl = document.createElement('a') |
51 | itemEl.href = 'javascript:;' | 51 | itemEl.href = 'javascript:;' |
52 | itemEl.innerText = rate.toString() | 52 | itemEl.innerText = rate.toString() |
53 | itemEl.addEventListener('click', () => { | 53 | itemEl.addEventListener('click', () => { |
@@ -66,18 +66,18 @@ window.addEventListener('load', async () => { | |||
66 | updateRates() | 66 | updateRates() |
67 | }) | 67 | }) |
68 | 68 | ||
69 | let updateResolutions = ((resolutions: PeerTubeResolution[]) => { | 69 | const updateResolutions = ((resolutions: PeerTubeResolution[]) => { |
70 | let resolutionListEl = document.querySelector('#resolution-list') | 70 | const resolutionListEl = document.querySelector('#resolution-list') |
71 | resolutionListEl.innerHTML = '' | 71 | resolutionListEl.innerHTML = '' |
72 | 72 | ||
73 | resolutions.forEach(resolution => { | 73 | resolutions.forEach(resolution => { |
74 | if (resolution.active) { | 74 | if (resolution.active) { |
75 | let itemEl = document.createElement('strong') | 75 | const itemEl = document.createElement('strong') |
76 | itemEl.innerText = `${resolution.label} (active)` | 76 | itemEl.innerText = `${resolution.label} (active)` |
77 | itemEl.style.display = 'block' | 77 | itemEl.style.display = 'block' |
78 | resolutionListEl.appendChild(itemEl) | 78 | resolutionListEl.appendChild(itemEl) |
79 | } else { | 79 | } else { |
80 | let itemEl = document.createElement('a') | 80 | const itemEl = document.createElement('a') |
81 | itemEl.href = 'javascript:;' | 81 | itemEl.href = 'javascript:;' |
82 | itemEl.innerText = resolution.label | 82 | itemEl.innerText = resolution.label |
83 | itemEl.addEventListener('click', () => { | 83 | itemEl.addEventListener('click', () => { |