aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/standalone/videos
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/standalone/videos')
-rw-r--r--client/src/standalone/videos/embed-api.ts130
-rw-r--r--client/src/standalone/videos/embed.html2
-rw-r--r--client/src/standalone/videos/embed.ts205
3 files changed, 188 insertions, 149 deletions
diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts
new file mode 100644
index 000000000..169e371da
--- /dev/null
+++ b/client/src/standalone/videos/embed-api.ts
@@ -0,0 +1,130 @@
1import './embed.scss'
2
3import * as Channel from 'jschannel'
4import { PeerTubeResolution } from '../player/definitions'
5import { PeerTubeEmbed } from './embed'
6
7/**
8 * Embed API exposes control of the embed player to the outside world via
9 * JSChannels and window.postMessage
10 */
11export class PeerTubeEmbedApi {
12 private channel: Channel.MessagingChannel
13 private isReady = false
14 private resolutions: PeerTubeResolution[] = null
15
16 constructor (private embed: PeerTubeEmbed) {
17 }
18
19 initialize () {
20 this.constructChannel()
21 this.setupStateTracking()
22
23 // We're ready!
24
25 this.notifyReady()
26 }
27
28 private get element () {
29 return this.embed.videoElement
30 }
31
32 private constructChannel () {
33 const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
34
35 channel.bind('play', (txn, params) => this.embed.player.play())
36 channel.bind('pause', (txn, params) => this.embed.player.pause())
37 channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
38 channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
39 channel.bind('getVolume', (txn, value) => this.embed.player.volume())
40 channel.bind('isReady', (txn, params) => this.isReady)
41 channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
42 channel.bind('getResolutions', (txn, params) => this.resolutions)
43 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
44 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
45 channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
46
47 this.channel = channel
48 }
49
50 private setResolution (resolutionId: number) {
51 if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
52
53 // Auto resolution
54 if (resolutionId === -1) {
55 this.embed.player.webtorrent().enableAutoResolution()
56 return
57 }
58
59 this.embed.player.webtorrent().disableAutoResolution()
60 this.embed.player.webtorrent().updateResolution(resolutionId)
61 }
62
63 /**
64 * Let the host know that we're ready to go!
65 */
66 private notifyReady () {
67 this.isReady = true
68 this.channel.notify({ method: 'ready', params: true })
69 }
70
71 private setupStateTracking () {
72 let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
73
74 setInterval(() => {
75 const position = this.element.currentTime
76 const volume = this.element.volume
77
78 this.channel.notify({
79 method: 'playbackStatusUpdate',
80 params: {
81 position,
82 volume,
83 playbackState: currentState
84 }
85 })
86 }, 500)
87
88 this.element.addEventListener('play', ev => {
89 currentState = 'playing'
90 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
91 })
92
93 this.element.addEventListener('pause', ev => {
94 currentState = 'paused'
95 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
96 })
97
98 // PeerTube specific capabilities
99
100 if (this.embed.player.webtorrent) {
101 this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
102 this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
103 }
104 }
105
106 private loadWebTorrentResolutions () {
107 const resolutions = []
108 const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
109
110 for (const videoFile of this.embed.player.webtorrent().videoFiles) {
111 let label = videoFile.resolution.label
112 if (videoFile.fps && videoFile.fps >= 50) {
113 label += videoFile.fps
114 }
115
116 resolutions.push({
117 id: videoFile.resolution.id,
118 label,
119 src: videoFile.magnetUri,
120 active: videoFile.resolution.id === currentResolutionId
121 })
122 }
123
124 this.resolutions = resolutions
125 this.channel.notify({
126 method: 'resolutionUpdate',
127 params: this.resolutions
128 })
129 }
130}
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index c3b6e08ca..5a15bf552 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -11,7 +11,7 @@
11 <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" />
12 </head> 12 </head>
13 13
14 <body> 14 <body id="custom-css">
15 15
16 <div id="error-block"> 16 <div id="error-block">
17 <h1 id="error-title"></h1> 17 <h1 id="error-title"></h1>
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 707f04253..cfe8e94b1 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,9 +1,6 @@
1import './embed.scss' 1import './embed.scss'
2 2
3import * as Channel from 'jschannel'
4
5import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared' 3import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared'
6import { PeerTubeResolution } from '../player/definitions'
7import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 4import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
8import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 5import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
9import { 6import {
@@ -13,133 +10,9 @@ import {
13 PlayerMode 10 PlayerMode
14} from '../../assets/player/peertube-player-manager' 11} from '../../assets/player/peertube-player-manager'
15import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 12import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
13import { PeerTubeEmbedApi } from './embed-api'
16 14
17/** 15export class PeerTubeEmbed {
18 * Embed API exposes control of the embed player to the outside world via
19 * JSChannels and window.postMessage
20 */
21class PeerTubeEmbedApi {
22 private channel: Channel.MessagingChannel
23 private isReady = false
24 private resolutions: PeerTubeResolution[] = null
25
26 constructor (private embed: PeerTubeEmbed) {
27 }
28
29 initialize () {
30 this.constructChannel()
31 this.setupStateTracking()
32
33 // We're ready!
34
35 this.notifyReady()
36 }
37
38 private get element () {
39 return this.embed.videoElement
40 }
41
42 private constructChannel () {
43 const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
44
45 channel.bind('play', (txn, params) => this.embed.player.play())
46 channel.bind('pause', (txn, params) => this.embed.player.pause())
47 channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
48 channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
49 channel.bind('getVolume', (txn, value) => this.embed.player.volume())
50 channel.bind('isReady', (txn, params) => this.isReady)
51 channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
52 channel.bind('getResolutions', (txn, params) => this.resolutions)
53 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
54 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
55 channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
56
57 this.channel = channel
58 }
59
60 private setResolution (resolutionId: number) {
61 if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
62
63 // Auto resolution
64 if (resolutionId === -1) {
65 this.embed.player.webtorrent().enableAutoResolution()
66 return
67 }
68
69 this.embed.player.webtorrent().disableAutoResolution()
70 this.embed.player.webtorrent().updateResolution(resolutionId)
71 }
72
73 /**
74 * Let the host know that we're ready to go!
75 */
76 private notifyReady () {
77 this.isReady = true
78 this.channel.notify({ method: 'ready', params: true })
79 }
80
81 private setupStateTracking () {
82 let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
83
84 setInterval(() => {
85 const position = this.element.currentTime
86 const volume = this.element.volume
87
88 this.channel.notify({
89 method: 'playbackStatusUpdate',
90 params: {
91 position,
92 volume,
93 playbackState: currentState
94 }
95 })
96 }, 500)
97
98 this.element.addEventListener('play', ev => {
99 currentState = 'playing'
100 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
101 })
102
103 this.element.addEventListener('pause', ev => {
104 currentState = 'paused'
105 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
106 })
107
108 // PeerTube specific capabilities
109
110 if (this.embed.player.webtorrent) {
111 this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
112 this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
113 }
114 }
115
116 private loadWebTorrentResolutions () {
117 const resolutions = []
118 const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
119
120 for (const videoFile of this.embed.player.webtorrent().videoFiles) {
121 let label = videoFile.resolution.label
122 if (videoFile.fps && videoFile.fps >= 50) {
123 label += videoFile.fps
124 }
125
126 resolutions.push({
127 id: videoFile.resolution.id,
128 label,
129 src: videoFile.magnetUri,
130 active: videoFile.resolution.id === currentResolutionId
131 })
132 }
133
134 this.resolutions = resolutions
135 this.channel.notify({
136 method: 'resolutionUpdate',
137 params: this.resolutions
138 })
139 }
140}
141
142class PeerTubeEmbed {
143 videoElement: HTMLVideoElement 16 videoElement: HTMLVideoElement
144 player: any 17 player: any
145 playerOptions: any 18 playerOptions: any
@@ -152,6 +25,12 @@ class PeerTubeEmbed {
152 enableApi = false 25 enableApi = false
153 startTime: number | string = 0 26 startTime: number | string = 0
154 stopTime: number | string 27 stopTime: number | string
28
29 title: boolean
30 warningTitle: boolean
31 bigPlayBackgroundColor: string
32 foregroundColor: string
33
155 mode: PlayerMode 34 mode: PlayerMode
156 scope = 'peertube' 35 scope = 'peertube'
157 36
@@ -245,13 +124,18 @@ class PeerTubeEmbed {
245 this.controls = this.getParamToggle(params, 'controls', true) 124 this.controls = this.getParamToggle(params, 'controls', true)
246 this.muted = this.getParamToggle(params, 'muted', false) 125 this.muted = this.getParamToggle(params, 'muted', false)
247 this.loop = this.getParamToggle(params, 'loop', false) 126 this.loop = this.getParamToggle(params, 'loop', false)
127 this.title = this.getParamToggle(params, 'title', true)
248 this.enableApi = this.getParamToggle(params, 'api', this.enableApi) 128 this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
129 this.warningTitle = this.getParamToggle(params, 'warningTitle', true)
249 130
250 this.scope = this.getParamString(params, 'scope', this.scope) 131 this.scope = this.getParamString(params, 'scope', this.scope)
251 this.subtitle = this.getParamString(params, 'subtitle') 132 this.subtitle = this.getParamString(params, 'subtitle')
252 this.startTime = this.getParamString(params, 'start') 133 this.startTime = this.getParamString(params, 'start')
253 this.stopTime = this.getParamString(params, 'stop') 134 this.stopTime = this.getParamString(params, 'stop')
254 135
136 this.bigPlayBackgroundColor = this.getParamString(params, 'bigPlayBackgroundColor')
137 this.foregroundColor = this.getParamString(params, 'foregroundColor')
138
255 this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' 139 this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent'
256 } catch (err) { 140 } catch (err) {
257 console.error('Cannot get params from URL.', err) 141 console.error('Cannot get params from URL.', err)
@@ -276,15 +160,7 @@ class PeerTubeEmbed {
276 } 160 }
277 161
278 const videoInfo: VideoDetails = await videoResponse.json() 162 const videoInfo: VideoDetails = await videoResponse.json()
279 let videoCaptions: VideoJSCaption[] = [] 163 const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
280 if (captionsResponse.ok) {
281 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
282 videoCaptions = data.map(c => ({
283 label: peertubeTranslate(c.language.label, serverTranslations),
284 language: c.language.id,
285 src: window.location.origin + c.captionPath
286 }))
287 }
288 164
289 this.loadParams() 165 this.loadParams()
290 166
@@ -337,33 +213,66 @@ class PeerTubeEmbed {
337 } 213 }
338 214
339 this.player = await PeertubePlayerManager.initialize(this.mode, options) 215 this.player = await PeertubePlayerManager.initialize(this.mode, options)
340
341 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) 216 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
342 217
343 window[ 'videojsPlayer' ] = this.player 218 window[ 'videojsPlayer' ] = this.player
344 219
220 this.buildCSS()
221
222 await this.buildDock(videoInfo, configResponse)
223
224 this.initializeApi()
225 }
226
227 private handleError (err: Error, translations?: { [ id: string ]: string }) {
228 if (err.message.indexOf('from xs param') !== -1) {
229 this.player.dispose()
230 this.videoElement = null
231 this.displayError('This video is not available because the remote instance is not responding.', translations)
232 return
233 }
234 }
235
236 private async buildDock (videoInfo: VideoDetails, configResponse: Response) {
345 if (this.controls) { 237 if (this.controls) {
238 const title = this.title ? videoInfo.name : undefined
239
346 const config: ServerConfig = await configResponse.json() 240 const config: ServerConfig = await configResponse.json()
347 const description = config.tracker.enabled 241 const description = config.tracker.enabled && this.warningTitle
348 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' 242 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
349 : undefined 243 : undefined
350 244
351 this.player.dock({ 245 this.player.dock({
352 title: videoInfo.name, 246 title,
353 description 247 description
354 }) 248 })
355 } 249 }
250 }
356 251
357 this.initializeApi() 252 private buildCSS () {
253 const body = document.getElementById('custom-css')
254
255 if (this.bigPlayBackgroundColor) {
256 body.style.setProperty('--embedBigPlayBackgroundColor', this.bigPlayBackgroundColor)
257 }
258
259 if (this.foregroundColor) {
260 body.style.setProperty('--embedForegroundColor', this.foregroundColor)
261 }
358 } 262 }
359 263
360 private handleError (err: Error, translations?: { [ id: string ]: string }) { 264 private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> {
361 if (err.message.indexOf('from xs param') !== -1) { 265 if (captionsResponse.ok) {
362 this.player.dispose() 266 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
363 this.videoElement = null 267
364 this.displayError('This video is not available because the remote instance is not responding.', translations) 268 return data.map(c => ({
365 return 269 label: peertubeTranslate(c.language.label, serverTranslations),
270 language: c.language.id,
271 src: window.location.origin + c.captionPath
272 }))
366 } 273 }
274
275 return []
367 } 276 }
368} 277}
369 278