]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/standalone/videos/embed.ts
Fix transcoding
[github/Chocobozzz/PeerTube.git] / client / src / standalone / videos / embed.ts
CommitLineData
202e7223
C
1import './embed.scss'
2
d1bd87e0
C
3import 'core-js/es6/symbol'
4import 'core-js/es6/object'
5import 'core-js/es6/function'
6import 'core-js/es6/parse-int'
7import 'core-js/es6/parse-float'
8import 'core-js/es6/number'
9import 'core-js/es6/math'
10import 'core-js/es6/string'
11import 'core-js/es6/date'
12import 'core-js/es6/array'
13import 'core-js/es6/regexp'
14import 'core-js/es6/map'
15import 'core-js/es6/weak-map'
16import 'core-js/es6/set'
cd4d7a2c
C
17// For google bot that uses Chrome 41 and does not understand fetch
18import 'whatwg-fetch'
19
244b4ae3 20const vjs = require('video.js')
99941732 21import * as Channel from 'jschannel'
c6352f2c 22
3dfa8494
C
23import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared'
24import { addContextMenu, getServerTranslations, getVideojsOptions, loadLocaleInVideoJS } from '../../assets/player/peertube-player'
902aa3a0 25import { PeerTubeResolution } from '../player/definitions'
16f7022b 26import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
59c76ffa 27import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
202e7223 28
99941732 29/**
902aa3a0 30 * Embed API exposes control of the embed player to the outside world via
99941732
WL
31 * JSChannels and window.postMessage
32 */
33class PeerTubeEmbedApi {
902aa3a0 34 private channel: Channel.MessagingChannel
99941732 35 private isReady = false
902aa3a0
C
36 private resolutions: PeerTubeResolution[] = null
37
38 constructor (private embed: PeerTubeEmbed) {
39 }
d4f3fea6 40
902aa3a0 41 initialize () {
99941732
WL
42 this.constructChannel()
43 this.setupStateTracking()
d4f3fea6 44
99941732 45 // We're ready!
d4f3fea6 46
99941732
WL
47 this.notifyReady()
48 }
902aa3a0
C
49
50 private get element () {
99941732
WL
51 return this.embed.videoElement
52 }
d4f3fea6 53
902aa3a0 54 private constructChannel () {
99941732 55 let channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
902aa3a0 56
99941732
WL
57 channel.bind('play', (txn, params) => this.embed.player.play())
58 channel.bind('pause', (txn, params) => this.embed.player.pause())
59 channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
60 channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
61 channel.bind('getVolume', (txn, value) => this.embed.player.volume())
62 channel.bind('isReady', (txn, params) => this.isReady)
63 channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
64 channel.bind('getResolutions', (txn, params) => this.resolutions)
65 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
66 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
67 channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
d4f3fea6 68
99941732
WL
69 this.channel = channel
70 }
d4f3fea6 71
902aa3a0
C
72 private setResolution (resolutionId: number) {
73 if (resolutionId === -1 && this.embed.player.peertube().isAutoResolutionForbidden()) return
99941732
WL
74
75 // Auto resolution
76 if (resolutionId === -1) {
77 this.embed.player.peertube().enableAutoResolution()
78 return
79 }
80
81 this.embed.player.peertube().disableAutoResolution()
82 this.embed.player.peertube().updateResolution(resolutionId)
83 }
84
85 /**
86 * Let the host know that we're ready to go!
87 */
902aa3a0 88 private notifyReady () {
99941732
WL
89 this.isReady = true
90 this.channel.notify({ method: 'ready', params: true })
91 }
92
902aa3a0
C
93 private setupStateTracking () {
94 let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
99941732
WL
95
96 setInterval(() => {
97 let position = this.element.currentTime
98 let volume = this.element.volume
99
100 this.channel.notify({
101 method: 'playbackStatusUpdate',
102 params: {
103 position,
104 volume,
902aa3a0 105 playbackState: currentState
99941732
WL
106 }
107 })
108 }, 500)
109
110 this.element.addEventListener('play', ev => {
111 currentState = 'playing'
112 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
113 })
114
115 this.element.addEventListener('pause', ev => {
116 currentState = 'paused'
117 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
118 })
119
120 // PeerTube specific capabilities
121
122 this.embed.player.peertube().on('autoResolutionUpdate', () => this.loadResolutions())
123 this.embed.player.peertube().on('videoFileUpdate', () => this.loadResolutions())
124 }
125
902aa3a0 126 private loadResolutions () {
99941732
WL
127 let resolutions = []
128 let currentResolutionId = this.embed.player.peertube().getCurrentResolutionId()
129
130 for (const videoFile of this.embed.player.peertube().videoFiles) {
131 let label = videoFile.resolution.label
132 if (videoFile.fps && videoFile.fps >= 50) {
133 label += videoFile.fps
134 }
d4f3fea6 135
99941732
WL
136 resolutions.push({
137 id: videoFile.resolution.id,
138 label,
139 src: videoFile.magnetUri,
140 active: videoFile.resolution.id === currentResolutionId
141 })
142 }
143
144 this.resolutions = resolutions
145 this.channel.notify({
146 method: 'resolutionUpdate',
147 params: this.resolutions
148 })
149 }
202e7223
C
150}
151
99941732 152class PeerTubeEmbed {
902aa3a0
C
153 videoElement: HTMLVideoElement
154 player: any
155 playerOptions: any
156 api: PeerTubeEmbedApi = null
157 autoplay = false
158 controls = true
159 muted = false
160 loop = false
161 enableApi = false
1f6824c9 162 startTime: number | string = 0
902aa3a0
C
163 scope = 'peertube'
164
165 static async main () {
c6352f2c 166 const videoContainerId = 'video-container'
99941732
WL
167 const embed = new PeerTubeEmbed(videoContainerId)
168 await embed.init()
169 }
902aa3a0
C
170
171 constructor (private videoContainerId: string) {
172 this.videoElement = document.getElementById(videoContainerId) as HTMLVideoElement
173 }
174
99941732
WL
175 getVideoUrl (id: string) {
176 return window.location.origin + '/api/v1/videos/' + id
177 }
d4f3fea6 178
99941732
WL
179 loadVideoInfo (videoId: string): Promise<Response> {
180 return fetch(this.getVideoUrl(videoId))
181 }
d4f3fea6 182
16f7022b
C
183 loadVideoCaptions (videoId: string): Promise<Response> {
184 return fetch(this.getVideoUrl(videoId) + '/captions')
185 }
186
99941732
WL
187 removeElement (element: HTMLElement) {
188 element.parentElement.removeChild(element)
189 }
d4f3fea6 190
6d88de72 191 displayError (text: string) {
99941732 192 // Remove video element
6d88de72 193 if (this.videoElement) this.removeElement(this.videoElement)
99941732
WL
194
195 document.title = 'Sorry - ' + text
196
197 const errorBlock = document.getElementById('error-block')
198 errorBlock.style.display = 'flex'
199
200 const errorText = document.getElementById('error-content')
201 errorText.innerHTML = text
202 }
203
6d88de72 204 videoNotFound () {
99941732 205 const text = 'This video does not exist.'
6d88de72 206 this.displayError(text)
99941732
WL
207 }
208
6d88de72 209 videoFetchError () {
99941732 210 const text = 'We cannot fetch the video. Please try again later.'
6d88de72 211 this.displayError(text)
99941732
WL
212 }
213
214 getParamToggle (params: URLSearchParams, name: string, defaultValue: boolean) {
215 return params.has(name) ? (params.get(name) === '1' || params.get(name) === 'true') : defaultValue
216 }
d4f3fea6 217
99941732
WL
218 getParamString (params: URLSearchParams, name: string, defaultValue: string) {
219 return params.has(name) ? params.get(name) : defaultValue
220 }
da99ccf2 221
902aa3a0 222 async init () {
99941732
WL
223 try {
224 await this.initCore()
225 } catch (e) {
226 console.error(e)
227 }
228 }
229
902aa3a0
C
230 private initializeApi () {
231 if (!this.enableApi) return
232
233 this.api = new PeerTubeEmbedApi(this)
234 this.api.initialize()
235 }
236
237 private loadParams () {
da99ccf2
C
238 try {
239 let params = new URL(window.location.toString()).searchParams
99941732
WL
240
241 this.autoplay = this.getParamToggle(params, 'autoplay', this.autoplay)
242 this.controls = this.getParamToggle(params, 'controls', this.controls)
243 this.muted = this.getParamToggle(params, 'muted', this.muted)
244 this.loop = this.getParamToggle(params, 'loop', this.loop)
245 this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
246 this.scope = this.getParamString(params, 'scope', this.scope)
f37bad63
C
247
248 const startTimeParamString = params.get('start')
1f6824c9 249 if (startTimeParamString) this.startTime = startTimeParamString
da99ccf2
C
250 } catch (err) {
251 console.error('Cannot get params from URL.', err)
252 }
99941732
WL
253 }
254
902aa3a0 255 private async initCore () {
99941732 256 const urlParts = window.location.href.split('/')
902aa3a0
C
257 const lastPart = urlParts[ urlParts.length - 1 ]
258 const videoId = lastPart.indexOf('?') === -1 ? lastPart : lastPart.split('?')[ 0 ]
99941732 259
3dfa8494
C
260 const [ , serverTranslations, videoResponse, captionsResponse ] = await Promise.all([
261 loadLocaleInVideoJS(window.location.origin, vjs, navigator.language),
262 getServerTranslations(window.location.origin, navigator.language),
16f7022b
C
263 this.loadVideoInfo(videoId),
264 this.loadVideoCaptions(videoId)
265 ])
99941732 266
16f7022b 267 if (!videoResponse.ok) {
6d88de72 268 if (videoResponse.status === 404) return this.videoNotFound()
99941732 269
6d88de72 270 return this.videoFetchError()
99941732
WL
271 }
272
16f7022b
C
273 const videoInfo: VideoDetails = await videoResponse.json()
274 let videoCaptions: VideoJSCaption[] = []
275 if (captionsResponse.ok) {
276 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
277 videoCaptions = data.map(c => ({
3dfa8494 278 label: peertubeTranslate(c.language.label, serverTranslations),
16f7022b
C
279 language: c.language.id,
280 src: window.location.origin + c.captionPath
281 }))
282 }
99941732
WL
283
284 this.loadParams()
da99ccf2 285
c6352f2c 286 const videojsOptions = getVideojsOptions({
99941732
WL
287 autoplay: this.autoplay,
288 controls: this.controls,
289 muted: this.muted,
290 loop: this.loop,
902aa3a0 291 startTime: this.startTime,
99941732 292
16f7022b 293 videoCaptions,
c6352f2c 294 inactivityTimeout: 1500,
99941732
WL
295 videoViewUrl: this.getVideoUrl(videoId) + '/views',
296 playerElement: this.videoElement,
c6352f2c
C
297 videoFiles: videoInfo.files,
298 videoDuration: videoInfo.duration,
299 enableHotkeys: true,
33d78552 300 peertubeLink: true,
f37bad63 301 poster: window.location.origin + videoInfo.previewPath,
054a103b 302 theaterMode: false
c6352f2c 303 })
202e7223 304
99941732
WL
305 this.playerOptions = videojsOptions
306 this.player = vjs(this.videoContainerId, videojsOptions, () => {
244b4ae3 307 this.player.on('customError', (data: any) => this.handleError(data.err))
e945b184 308
902aa3a0 309 window[ 'videojsPlayer' ] = this.player
99941732
WL
310
311 if (this.controls) {
902aa3a0 312 this.player.dock({
99941732
WL
313 title: videoInfo.name,
314 description: this.player.localize('Uses P2P, others may know your IP is downloading this video.')
315 })
316 }
902aa3a0 317
99941732 318 addContextMenu(this.player, window.location.origin + videoInfo.embedPath)
16f7022b 319
99941732 320 this.initializeApi()
202e7223 321 })
99941732 322 }
6d88de72
C
323
324 private handleError (err: Error) {
0f7fedc3 325 if (err.message.indexOf('from xs param') !== -1) {
6d88de72
C
326 this.player.dispose()
327 this.videoElement = null
328 this.displayError('This video is not available because the remote instance is not responding.')
329 return
330 }
331 }
99941732
WL
332}
333
334PeerTubeEmbed.main()
902aa3a0 335 .catch(err => console.error('Cannot init embed.', err))