aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets')
-rw-r--r--client/src/assets/player/p2p-media-loader/hls-plugin.ts16
-rw-r--r--client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts36
-rw-r--r--client/src/assets/player/p2p-media-loader/segment-url-builder.ts2
-rw-r--r--client/src/assets/player/p2p-media-loader/segment-validator.ts2
-rw-r--r--client/src/assets/player/peertube-player-manager.ts88
-rw-r--r--client/src/assets/player/peertube-plugin.ts13
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts9
-rw-r--r--client/src/assets/player/playlist/playlist-menu-item.ts2
-rw-r--r--client/src/assets/player/stats/stats-card.ts3
-rw-r--r--client/src/assets/player/utils.ts136
-rw-r--r--client/src/assets/player/videojs-components/peertube-link-button.ts17
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts11
12 files changed, 128 insertions, 207 deletions
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
index 53969a5a5..3050110cd 100644
--- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
@@ -264,20 +264,16 @@ class Html5Hlsjs {
264 if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1 264 if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1
265 else this.errorCounts[ data.type ] = 1 265 else this.errorCounts[ data.type ] = 1
266 266
267 if (!data.fatal) { 267 if (data.fatal) console.warn(error.message)
268 console.warn(error.message) 268 else console.error(error.message, data)
269 return
270 }
271
272 console.error(error.message)
273 269
274 if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) { 270 if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
275 error.code = 2 271 error.code = 2
276 this._handleNetworkError(error) 272 this._handleNetworkError(error)
277 } else if (data.type === Hlsjs.ErrorTypes.MEDIA_ERROR && data.details !== 'manifestIncompatibleCodecsError') { 273 } else if (data.fatal && data.type === Hlsjs.ErrorTypes.MEDIA_ERROR && data.details !== 'manifestIncompatibleCodecsError') {
278 error.code = 3 274 error.code = 3
279 this._handleMediaError(error) 275 this._handleMediaError(error)
280 } else { 276 } else if (data.fatal) {
281 this.hls.destroy() 277 this.hls.destroy()
282 console.info('bubbling error up to VIDEOJS') 278 console.info('bubbling error up to VIDEOJS')
283 this.tech.error = () => error as any 279 this.tech.error = () => error as any
@@ -286,12 +282,12 @@ class Html5Hlsjs {
286 } 282 }
287 283
288 private switchQuality (qualityId: number) { 284 private switchQuality (qualityId: number) {
289 this.hls.nextLevel = qualityId 285 this.hls.currentLevel = qualityId
290 } 286 }
291 287
292 private _levelLabel (level: Hlsjs.Level) { 288 private _levelLabel (level: Hlsjs.Level) {
293 if (this.player.srOptions_.levelLabelHandler) { 289 if (this.player.srOptions_.levelLabelHandler) {
294 return this.player.srOptions_.levelLabelHandler(level) 290 return this.player.srOptions_.levelLabelHandler(level as any)
295 } 291 }
296 292
297 if (level.height) return level.height + 'p' 293 if (level.height) return level.height + 'p'
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
index f1bd9f0c4..093795e48 100644
--- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
@@ -1,9 +1,9 @@
1import * as Hlsjs from 'hls.js/dist/hls.light.js' 1import * as Hlsjs from 'hls.js/dist/hls.light.js'
2import { Events, Segment } from 'p2p-media-loader-core'
3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
4import videojs from 'video.js' 2import videojs from 'video.js'
3import { Events, Segment } from '@peertube/p2p-media-loader-core'
4import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs'
5import { timeToInt } from '@shared/core-utils'
5import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings' 6import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
6import { timeToInt } from '../utils'
7import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' 7import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
8 8
9registerConfigPlugin(videojs) 9registerConfigPlugin(videojs)
@@ -36,9 +36,6 @@ class P2pMediaLoaderPlugin extends Plugin {
36 36
37 private networkInfoInterval: any 37 private networkInfoInterval: any
38 38
39 private hlsjsCurrentLevel: number
40 private hlsjsLevels: Hlsjs.Level[]
41
42 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) { 39 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) {
43 super(player) 40 super(player)
44 41
@@ -88,13 +85,12 @@ class P2pMediaLoaderPlugin extends Plugin {
88 } 85 }
89 86
90 getCurrentLevel () { 87 getCurrentLevel () {
91 return this.hlsjsLevels.find(l => l.level === this.hlsjsCurrentLevel) 88 return this.hlsjs.levels[this.hlsjs.currentLevel]
92 } 89 }
93 90
94 getLiveLatency () { 91 getLiveLatency () {
95 return undefined as number 92 // FIXME: typings
96 // FIXME: Use latency when hls >= V1 93 return Math.round((this.hlsjs as any).latency)
97 // return this.hlsjs.latency
98 } 94 }
99 95
100 getHLSJS () { 96 getHLSJS () {
@@ -140,31 +136,23 @@ class P2pMediaLoaderPlugin extends Plugin {
140 } 136 }
141 137
142 private runStats () { 138 private runStats () {
143 this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, size: number) => { 139 this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, _segment, bytes: number) => {
144 const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes 140 const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes
145 141
146 elem.pendingDownload.push(size) 142 elem.pendingDownload.push(bytes)
147 elem.totalDownload += size 143 elem.totalDownload += bytes
148 }) 144 })
149 145
150 this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, size: number) => { 146 this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => {
151 const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes 147 const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes
152 148
153 elem.pendingUpload.push(size) 149 elem.pendingUpload.push(bytes)
154 elem.totalUpload += size 150 elem.totalUpload += bytes
155 }) 151 })
156 152
157 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) 153 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
158 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--) 154 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
159 155
160 this.hlsjs.on(Hlsjs.Events.MANIFEST_PARSED, (_e, manifest) => {
161 this.hlsjsCurrentLevel = manifest.firstLevel
162 this.hlsjsLevels = manifest.levels
163 })
164 this.hlsjs.on(Hlsjs.Events.LEVEL_LOADED, (_e, level) => {
165 this.hlsjsCurrentLevel = level.levelId || (level as any).id
166 })
167
168 this.networkInfoInterval = setInterval(() => { 156 this.networkInfoInterval = setInterval(() => {
169 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload) 157 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload)
170 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) 158 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
diff --git a/client/src/assets/player/p2p-media-loader/segment-url-builder.ts b/client/src/assets/player/p2p-media-loader/segment-url-builder.ts
index 039777cea..ad0e460ae 100644
--- a/client/src/assets/player/p2p-media-loader/segment-url-builder.ts
+++ b/client/src/assets/player/p2p-media-loader/segment-url-builder.ts
@@ -1,4 +1,4 @@
1import { Segment } from 'p2p-media-loader-core' 1import { Segment } from '@peertube/p2p-media-loader-core'
2import { RedundancyUrlManager } from './redundancy-url-manager' 2import { RedundancyUrlManager } from './redundancy-url-manager'
3 3
4function segmentUrlBuilderFactory (redundancyUrlManager: RedundancyUrlManager) { 4function segmentUrlBuilderFactory (redundancyUrlManager: RedundancyUrlManager) {
diff --git a/client/src/assets/player/p2p-media-loader/segment-validator.ts b/client/src/assets/player/p2p-media-loader/segment-validator.ts
index 59245ceb4..d0a4c4a3f 100644
--- a/client/src/assets/player/p2p-media-loader/segment-validator.ts
+++ b/client/src/assets/player/p2p-media-loader/segment-validator.ts
@@ -1,5 +1,5 @@
1import { wait } from '@root-helpers/utils' 1import { wait } from '@root-helpers/utils'
2import { Segment } from 'p2p-media-loader-core' 2import { Segment } from '@peertube/p2p-media-loader-core'
3import { basename } from 'path' 3import { basename } from 'path'
4 4
5type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } } 5type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } }
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index b071a0938..c45e8f53e 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -22,24 +22,28 @@ import './videojs-components/settings-panel-child'
22import './videojs-components/theater-button' 22import './videojs-components/theater-button'
23import './playlist/playlist-plugin' 23import './playlist/playlist-plugin'
24import videojs from 'video.js' 24import videojs from 'video.js'
25import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
25import { PluginsManager } from '@root-helpers/plugins-manager' 26import { PluginsManager } from '@root-helpers/plugins-manager'
27import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
26import { isDefaultLocale } from '@shared/core-utils/i18n' 28import { isDefaultLocale } from '@shared/core-utils/i18n'
27import { VideoFile } from '@shared/models' 29import { VideoFile } from '@shared/models'
28import { copyToClipboard } from '../../root-helpers/utils' 30import { copyToClipboard } from '../../root-helpers/utils'
29import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 31import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
30import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder' 32import { segmentUrlBuilderFactory } from './p2p-media-loader/segment-url-builder'
31import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' 33import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
32import { getStoredP2PEnabled } from './peertube-player-local-storage' 34import { getAverageBandwidthInStore, getStoredP2PEnabled, saveAverageBandwidth } from './peertube-player-local-storage'
33import { 35import {
34 NextPreviousVideoButtonOptions, 36 NextPreviousVideoButtonOptions,
35 P2PMediaLoaderPluginOptions, 37 P2PMediaLoaderPluginOptions,
38 PeerTubeLinkButtonOptions,
39 PlayerNetworkInfo,
36 PlaylistPluginOptions, 40 PlaylistPluginOptions,
37 UserWatching, 41 UserWatching,
38 VideoJSCaption, 42 VideoJSCaption,
39 VideoJSPluginOptions 43 VideoJSPluginOptions
40} from './peertube-videojs-typings' 44} from './peertube-videojs-typings'
41import { TranslationsManager } from './translations-manager' 45import { TranslationsManager } from './translations-manager'
42import { buildVideoLink, buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils' 46import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils'
43 47
44// Change 'Playback Rate' to 'Speed' (smaller for our settings menu) 48// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
45(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed' 49(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
@@ -110,6 +114,7 @@ export interface CommonOptions extends CustomizationOptions {
110 videoCaptions: VideoJSCaption[] 114 videoCaptions: VideoJSCaption[]
111 115
112 videoUUID: string 116 videoUUID: string
117 videoShortUUID: string
113 118
114 userWatching?: UserWatching 119 userWatching?: UserWatching
115 120
@@ -145,7 +150,7 @@ export class PeertubePlayerManager {
145 if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin') 150 if (mode === 'webtorrent') await import('./webtorrent/webtorrent-plugin')
146 if (mode === 'p2p-media-loader') { 151 if (mode === 'p2p-media-loader') {
147 [ p2pMediaLoader ] = await Promise.all([ 152 [ p2pMediaLoader ] = await Promise.all([
148 import('p2p-media-loader-hlsjs'), 153 import('@peertube/p2p-media-loader-hlsjs'),
149 import('./p2p-media-loader/p2p-media-loader-plugin') 154 import('./p2p-media-loader/p2p-media-loader-plugin')
150 ]) 155 ])
151 } 156 }
@@ -175,7 +180,13 @@ export class PeertubePlayerManager {
175 PeertubePlayerManager.alreadyPlayed = true 180 PeertubePlayerManager.alreadyPlayed = true
176 }) 181 })
177 182
178 self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) 183 self.addContextMenu({
184 mode,
185 player,
186 videoShortUUID: options.common.videoShortUUID,
187 videoEmbedUrl: options.common.embedUrl,
188 videoEmbedTitle: options.common.embedTitle
189 })
179 190
180 player.bezels() 191 player.bezels()
181 player.stats({ 192 player.stats({
@@ -184,6 +195,12 @@ export class PeertubePlayerManager {
184 mode 195 mode
185 }) 196 })
186 197
198 player.on('p2pInfo', (_, data: PlayerNetworkInfo) => {
199 if (data.source !== 'p2p-media-loader' || isNaN(data.bandwidthEstimate)) return
200
201 saveAverageBandwidth(data.bandwidthEstimate)
202 })
203
187 return res(player) 204 return res(player)
188 }) 205 })
189 }) 206 })
@@ -218,7 +235,13 @@ export class PeertubePlayerManager {
218 videojs(newVideoElement, videojsOptions, function (this: videojs.Player) { 235 videojs(newVideoElement, videojsOptions, function (this: videojs.Player) {
219 const player = this 236 const player = this
220 237
221 self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) 238 self.addContextMenu({
239 mode,
240 player,
241 videoShortUUID: options.common.videoShortUUID,
242 videoEmbedUrl: options.common.embedUrl,
243 videoEmbedTitle: options.common.embedTitle
244 })
222 245
223 PeertubePlayerManager.onPlayerChange(player) 246 PeertubePlayerManager.onPlayerChange(player)
224 }) 247 })
@@ -295,6 +318,8 @@ export class PeertubePlayerManager {
295 318
296 controlBar: { 319 controlBar: {
297 children: this.getControlBarChildren(mode, { 320 children: this.getControlBarChildren(mode, {
321 videoShortUUID: commonOptions.videoShortUUID,
322
298 captions: commonOptions.captions, 323 captions: commonOptions.captions,
299 peertubeLink: commonOptions.peertubeLink, 324 peertubeLink: commonOptions.peertubeLink,
300 theaterButton: commonOptions.theaterButton, 325 theaterButton: commonOptions.theaterButton,
@@ -342,12 +367,13 @@ export class PeertubePlayerManager {
342 consumeOnly = true 367 consumeOnly = true
343 } 368 }
344 369
345 const p2pMediaLoaderConfig = { 370 const p2pMediaLoaderConfig: HlsJsEngineSettings = {
346 loader: { 371 loader: {
347 trackerAnnounce, 372 trackerAnnounce,
348 segmentValidator: segmentValidatorFactory(options.p2pMediaLoader.segmentsSha256Url, options.common.isLive), 373 segmentValidator: segmentValidatorFactory(options.p2pMediaLoader.segmentsSha256Url, options.common.isLive),
349 rtcConfig: getRtcConfig(), 374 rtcConfig: getRtcConfig(),
350 requiredSegmentsPriority: 1, 375 requiredSegmentsPriority: 1,
376 simultaneousHttpDownloads: 1,
351 segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager), 377 segmentUrlBuilder: segmentUrlBuilderFactory(redundancyUrlManager),
352 useP2P: getStoredP2PEnabled(), 378 useP2P: getStoredP2PEnabled(),
353 consumeOnly 379 consumeOnly
@@ -356,6 +382,7 @@ export class PeertubePlayerManager {
356 swarmId: p2pMediaLoaderOptions.playlistUrl 382 swarmId: p2pMediaLoaderOptions.playlistUrl
357 } 383 }
358 } 384 }
385
359 const hlsjs = { 386 const hlsjs = {
360 levelLabelHandler: (level: { height: number, width: number }) => { 387 levelLabelHandler: (level: { height: number, width: number }) => {
361 const resolution = Math.min(level.height || 0, level.width || 0) 388 const resolution = Math.min(level.height || 0, level.width || 0)
@@ -370,12 +397,7 @@ export class PeertubePlayerManager {
370 return label 397 return label
371 }, 398 },
372 html5: { 399 html5: {
373 hlsjsConfig: { 400 hlsjsConfig: this.getHLSOptions(p2pMediaLoaderModule, p2pMediaLoaderConfig)
374 capLevelToPlayerSize: true,
375 autoStartLoad: false,
376 liveSyncDurationCount: 5,
377 loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
378 }
379 } 401 }
380 } 402 }
381 403
@@ -385,6 +407,28 @@ export class PeertubePlayerManager {
385 return toAssign 407 return toAssign
386 } 408 }
387 409
410 private static getHLSOptions (p2pMediaLoaderModule: any, p2pMediaLoaderConfig: HlsJsEngineSettings) {
411 const base = {
412 capLevelToPlayerSize: true,
413 autoStartLoad: false,
414 liveSyncDurationCount: 5,
415
416 loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass()
417 }
418
419 const averageBandwidth = getAverageBandwidthInStore()
420 if (!averageBandwidth) return base
421
422 return {
423 ...base,
424
425 abrEwmaDefaultEstimate: averageBandwidth * 8, // We want bit/s
426 startLevel: -1,
427 testBandwidth: false,
428 debug: false
429 }
430 }
431
388 private static addWebTorrentOptions (plugins: VideoJSPluginOptions, options: PeertubePlayerManagerOptions) { 432 private static addWebTorrentOptions (plugins: VideoJSPluginOptions, options: PeertubePlayerManagerOptions) {
389 const commonOptions = options.common 433 const commonOptions = options.common
390 const webtorrentOptions = options.webtorrent 434 const webtorrentOptions = options.webtorrent
@@ -409,6 +453,8 @@ export class PeertubePlayerManager {
409 } 453 }
410 454
411 private static getControlBarChildren (mode: PlayerMode, options: { 455 private static getControlBarChildren (mode: PlayerMode, options: {
456 videoShortUUID: string
457
412 peertubeLink: boolean 458 peertubeLink: boolean
413 theaterButton: boolean 459 theaterButton: boolean
414 captions: boolean 460 captions: boolean
@@ -497,7 +543,7 @@ export class PeertubePlayerManager {
497 543
498 if (options.peertubeLink === true) { 544 if (options.peertubeLink === true) {
499 Object.assign(children, { 545 Object.assign(children, {
500 'peerTubeLinkButton': {} 546 'peerTubeLinkButton': { shortUUID: options.videoShortUUID } as PeerTubeLinkButtonOptions
501 }) 547 })
502 } 548 }
503 549
@@ -514,7 +560,15 @@ export class PeertubePlayerManager {
514 return children 560 return children
515 } 561 }
516 562
517 private static addContextMenu (mode: PlayerMode, player: videojs.Player, videoEmbedUrl: string, videoEmbedTitle: string) { 563 private static addContextMenu (options: {
564 mode: PlayerMode
565 player: videojs.Player
566 videoShortUUID: string
567 videoEmbedUrl: string
568 videoEmbedTitle: string
569 }) {
570 const { mode, player, videoEmbedTitle, videoEmbedUrl, videoShortUUID } = options
571
518 const content = () => { 572 const content = () => {
519 const isLoopEnabled = player.options_['loop'] 573 const isLoopEnabled = player.options_['loop']
520 const items = [ 574 const items = [
@@ -528,13 +582,15 @@ export class PeertubePlayerManager {
528 { 582 {
529 label: player.localize('Copy the video URL'), 583 label: player.localize('Copy the video URL'),
530 listener: function () { 584 listener: function () {
531 copyToClipboard(buildVideoLink()) 585 copyToClipboard(buildVideoLink({ shortUUID: videoShortUUID }))
532 } 586 }
533 }, 587 },
534 { 588 {
535 label: player.localize('Copy the video URL at the current time'), 589 label: player.localize('Copy the video URL at the current time'),
536 listener: function (this: videojs.Player) { 590 listener: function (this: videojs.Player) {
537 copyToClipboard(buildVideoLink({ startTime: this.currentTime() })) 591 const url = buildVideoLink({ shortUUID: videoShortUUID })
592
593 copyToClipboard(decorateVideoLink({ url, startTime: this.currentTime() }))
538 } 594 }
539 }, 595 },
540 { 596 {
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts
index 07c7e33f6..ade8e2ee4 100644
--- a/client/src/assets/player/peertube-plugin.ts
+++ b/client/src/assets/player/peertube-plugin.ts
@@ -1,12 +1,6 @@
1import videojs from 'video.js'
2import './videojs-components/settings-menu-button' 1import './videojs-components/settings-menu-button'
3import { 2import videojs from 'video.js'
4 PeerTubePluginOptions, 3import { timeToInt } from '@shared/core-utils'
5 ResolutionUpdateData,
6 UserWatching,
7 VideoJSCaption
8} from './peertube-videojs-typings'
9import { isMobile, timeToInt } from './utils'
10import { 4import {
11 getStoredLastSubtitle, 5 getStoredLastSubtitle,
12 getStoredMute, 6 getStoredMute,
@@ -16,6 +10,8 @@ import {
16 saveVideoWatchHistory, 10 saveVideoWatchHistory,
17 saveVolumeInStore 11 saveVolumeInStore
18} from './peertube-player-local-storage' 12} from './peertube-player-local-storage'
13import { PeerTubePluginOptions, ResolutionUpdateData, UserWatching, VideoJSCaption } from './peertube-videojs-typings'
14import { isMobile } from './utils'
19 15
20const Plugin = videojs.getPlugin('plugin') 16const Plugin = videojs.getPlugin('plugin')
21 17
@@ -232,6 +228,7 @@ class PeerTubePlugin extends Plugin {
232 } 228 }
233 } 229 }
234 230
231 console.log('Resolution changed.', data)
235 this.trigger('resolutionChange', data) 232 this.trigger('resolutionChange', data)
236 } 233 }
237 234
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index 8afb424a7..f0eb129d4 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -1,4 +1,4 @@
1import { Config, Level } from 'hls.js' 1import { HlsConfig, Level } from 'hls.js'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models' 3import { VideoFile, VideoPlaylist, VideoPlaylistElement } from '@shared/models'
4import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin' 4import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
@@ -60,7 +60,7 @@ export interface VideoJSTechHLS extends videojs.Tech {
60} 60}
61 61
62export interface HlsjsConfigHandlerOptions { 62export interface HlsjsConfigHandlerOptions {
63 hlsjsConfig?: Config & { cueHandler: any }// FIXME: typings 63 hlsjsConfig?: HlsConfig & { cueHandler: any }// FIXME: typings
64 captionConfig?: any // FIXME: typings 64 captionConfig?: any // FIXME: typings
65 65
66 levelLabelHandler?: (level: Level) => string 66 levelLabelHandler?: (level: Level) => string
@@ -132,6 +132,10 @@ type NextPreviousVideoButtonOptions = {
132 isDisabled: () => boolean 132 isDisabled: () => boolean
133} 133}
134 134
135type PeerTubeLinkButtonOptions = {
136 shortUUID: string
137}
138
135type WebtorrentPluginOptions = { 139type WebtorrentPluginOptions = {
136 playerElement: HTMLVideoElement 140 playerElement: HTMLVideoElement
137 141
@@ -225,5 +229,6 @@ export {
225 VideoJSPluginOptions, 229 VideoJSPluginOptions,
226 LoadedQualityData, 230 LoadedQualityData,
227 QualityLevelRepresentation, 231 QualityLevelRepresentation,
232 PeerTubeLinkButtonOptions,
228 QualityLevels 233 QualityLevels
229} 234}
diff --git a/client/src/assets/player/playlist/playlist-menu-item.ts b/client/src/assets/player/playlist/playlist-menu-item.ts
index 87a72b6a3..2519a34c7 100644
--- a/client/src/assets/player/playlist/playlist-menu-item.ts
+++ b/client/src/assets/player/playlist/playlist-menu-item.ts
@@ -1,7 +1,7 @@
1import videojs from 'video.js' 1import videojs from 'video.js'
2import { secondsToTime } from '@shared/core-utils'
2import { VideoPlaylistElement } from '@shared/models' 3import { VideoPlaylistElement } from '@shared/models'
3import { PlaylistItemOptions } from '../peertube-videojs-typings' 4import { PlaylistItemOptions } from '../peertube-videojs-typings'
4import { secondsToTime } from '../utils'
5 5
6const Component = videojs.getComponent('Component') 6const Component = videojs.getComponent('Component')
7 7
diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts
index a93f59506..b271d0526 100644
--- a/client/src/assets/player/stats/stats-card.ts
+++ b/client/src/assets/player/stats/stats-card.ts
@@ -1,6 +1,7 @@
1import videojs from 'video.js' 1import videojs from 'video.js'
2import { secondsToTime } from '@shared/core-utils'
2import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings' 3import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings'
3import { bytes, secondsToTime } from '../utils' 4import { bytes } from '../utils'
4 5
5interface StatsCardOptions extends videojs.ComponentOptions { 6interface StatsCardOptions extends videojs.ComponentOptions {
6 videoUUID: string 7 videoUUID: string
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index f26176acc..f0a1b1aee 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -1,5 +1,5 @@
1import { VideoFile } from '@shared/models'
2import { escapeHTML } from '@shared/core-utils/renderer' 1import { escapeHTML } from '@shared/core-utils/renderer'
2import { VideoFile } from '@shared/models'
3 3
4function toTitleCase (str: string) { 4function toTitleCase (str: string) {
5 return str.charAt(0).toUpperCase() + str.slice(1) 5 return str.charAt(0).toUpperCase() + str.slice(1)
@@ -43,136 +43,9 @@ function isMobile () {
43 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) 43 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
44} 44}
45 45
46function buildVideoLink (options: {
47 baseUrl?: string
48
49 startTime?: number
50 stopTime?: number
51
52 subtitle?: string
53
54 loop?: boolean
55 autoplay?: boolean
56 muted?: boolean
57
58 // Embed options
59 title?: boolean
60 warningTitle?: boolean
61 controls?: boolean
62 peertubeLink?: boolean
63} = {}) {
64 const { baseUrl } = options
65
66 const url = baseUrl
67 ? baseUrl
68 : window.location.origin + window.location.pathname.replace('/videos/embed/', '/w/')
69
70 const params = generateParams(window.location.search)
71
72 if (options.startTime !== undefined && options.startTime !== null) {
73 const startTimeInt = Math.floor(options.startTime)
74 params.set('start', secondsToTime(startTimeInt))
75 }
76
77 if (options.stopTime) {
78 const stopTimeInt = Math.floor(options.stopTime)
79 params.set('stop', secondsToTime(stopTimeInt))
80 }
81
82 if (options.subtitle) params.set('subtitle', options.subtitle)
83
84 if (options.loop === true) params.set('loop', '1')
85 if (options.autoplay === true) params.set('autoplay', '1')
86 if (options.muted === true) params.set('muted', '1')
87 if (options.title === false) params.set('title', '0')
88 if (options.warningTitle === false) params.set('warningTitle', '0')
89 if (options.controls === false) params.set('controls', '0')
90 if (options.peertubeLink === false) params.set('peertubeLink', '0')
91
92 return buildUrl(url, params)
93}
94
95function buildPlaylistLink (options: {
96 baseUrl?: string
97
98 playlistPosition?: number
99}) {
100 const { baseUrl } = options
101
102 const url = baseUrl
103 ? baseUrl
104 : window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/w/p/')
105
106 const params = generateParams(window.location.search)
107
108 if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
109
110 return buildUrl(url, params)
111}
112
113function buildUrl (url: string, params: URLSearchParams) {
114 let hasParams = false
115 params.forEach(() => hasParams = true)
116
117 if (hasParams) return url + '?' + params.toString()
118
119 return url
120}
121
122function generateParams (url: string) {
123 const params = new URLSearchParams(window.location.search)
124 // Unused parameters in embed
125 params.delete('videoId')
126 params.delete('resume')
127
128 return params
129}
130
131function timeToInt (time: number | string) {
132 if (!time) return 0
133 if (typeof time === 'number') return time
134
135 const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/
136 const matches = time.match(reg)
137
138 if (!matches) return 0
139
140 const hours = parseInt(matches[2] || '0', 10)
141 const minutes = parseInt(matches[4] || '0', 10)
142 const seconds = parseInt(matches[6] || '0', 10)
143
144 return hours * 3600 + minutes * 60 + seconds
145}
146
147function secondsToTime (seconds: number, full = false, symbol?: string) {
148 let time = ''
149
150 if (seconds === 0 && !full) return '0s'
151
152 const hourSymbol = (symbol || 'h')
153 const minuteSymbol = (symbol || 'm')
154 const secondsSymbol = full ? '' : 's'
155
156 const hours = Math.floor(seconds / 3600)
157 if (hours >= 1) time = hours + hourSymbol
158 else if (full) time = '0' + hourSymbol
159
160 seconds %= 3600
161 const minutes = Math.floor(seconds / 60)
162 if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol
163 else if (minutes >= 1) time += minutes + minuteSymbol
164 else if (full) time += '00' + minuteSymbol
165
166 seconds %= 60
167 if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol
168 else if (seconds >= 1) time += seconds + secondsSymbol
169 else if (full) time += '00'
170
171 return time
172}
173
174function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) { 46function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) {
175 const title = escapeHTML(embedTitle) 47 const title = escapeHTML(embedTitle)
48
176 return '<iframe width="560" height="315" ' + 49 return '<iframe width="560" height="315" ' +
177 'sandbox="allow-same-origin allow-scripts allow-popups" ' + 50 'sandbox="allow-same-origin allow-scripts allow-popups" ' +
178 'title="' + title + '" ' + 51 'title="' + title + '" ' +
@@ -221,11 +94,8 @@ function getRtcConfig () {
221export { 94export {
222 getRtcConfig, 95 getRtcConfig,
223 toTitleCase, 96 toTitleCase,
224 timeToInt,
225 secondsToTime,
226 isWebRTCDisabled, 97 isWebRTCDisabled,
227 buildPlaylistLink, 98
228 buildVideoLink,
229 buildVideoOrPlaylistEmbed, 99 buildVideoOrPlaylistEmbed,
230 videoFileMaxByResolution, 100 videoFileMaxByResolution,
231 videoFileMinByResolution, 101 videoFileMinByResolution,
diff --git a/client/src/assets/player/videojs-components/peertube-link-button.ts b/client/src/assets/player/videojs-components/peertube-link-button.ts
index e73c95900..c49cee566 100644
--- a/client/src/assets/player/videojs-components/peertube-link-button.ts
+++ b/client/src/assets/player/videojs-components/peertube-link-button.ts
@@ -1,11 +1,12 @@
1import { buildVideoLink } from '../utils'
2import videojs from 'video.js' 1import videojs from 'video.js'
2import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
3import { PeerTubeLinkButtonOptions } from '../peertube-videojs-typings'
3 4
4const Button = videojs.getComponent('Button') 5const Button = videojs.getComponent('Button')
5class PeerTubeLinkButton extends Button { 6class PeerTubeLinkButton extends Button {
6 7
7 constructor (player: videojs.Player, options?: videojs.ComponentOptions) { 8 constructor (player: videojs.Player, options?: PeerTubeLinkButtonOptions) {
8 super(player, options) 9 super(player, options as any)
9 } 10 }
10 11
11 createEl () { 12 createEl () {
@@ -13,7 +14,7 @@ class PeerTubeLinkButton extends Button {
13 } 14 }
14 15
15 updateHref () { 16 updateHref () {
16 this.el().setAttribute('href', buildVideoLink({ startTime: this.player().currentTime() })) 17 this.el().setAttribute('href', this.buildLink())
17 } 18 }
18 19
19 handleClick () { 20 handleClick () {
@@ -22,7 +23,7 @@ class PeerTubeLinkButton extends Button {
22 23
23 private buildElement () { 24 private buildElement () {
24 const el = videojs.dom.createEl('a', { 25 const el = videojs.dom.createEl('a', {
25 href: buildVideoLink(), 26 href: this.buildLink(),
26 innerHTML: 'PeerTube', 27 innerHTML: 'PeerTube',
27 title: this.player().localize('Video page (new window)'), 28 title: this.player().localize('Video page (new window)'),
28 className: 'vjs-peertube-link', 29 className: 'vjs-peertube-link',
@@ -33,6 +34,12 @@ class PeerTubeLinkButton extends Button {
33 34
34 return el as HTMLButtonElement 35 return el as HTMLButtonElement
35 } 36 }
37
38 private buildLink () {
39 const url = buildVideoLink({ shortUUID: (this.options_ as PeerTubeLinkButtonOptions).shortUUID })
40
41 return decorateVideoLink({ url, startTime: this.player().currentTime() })
42 }
36} 43}
37 44
38videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton) 45videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index b648b29e8..17d369c10 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -1,9 +1,7 @@
1import videojs from 'video.js' 1import videojs from 'video.js'
2import * as WebTorrent from 'webtorrent' 2import * as WebTorrent from 'webtorrent'
3import { renderVideo } from './video-renderer' 3import { timeToInt } from '@shared/core-utils'
4import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings' 4import { VideoFile } from '@shared/models'
5import { getRtcConfig, timeToInt, videoFileMaxByResolution, videoFileMinByResolution, isIOS, isSafari } from '../utils'
6import { PeertubeChunkStore } from './peertube-chunk-store'
7import { 5import {
8 getAverageBandwidthInStore, 6 getAverageBandwidthInStore,
9 getStoredMute, 7 getStoredMute,
@@ -11,7 +9,10 @@ import {
11 getStoredVolume, 9 getStoredVolume,
12 saveAverageBandwidth 10 saveAverageBandwidth
13} from '../peertube-player-local-storage' 11} from '../peertube-player-local-storage'
14import { VideoFile } from '@shared/models' 12import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
13import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
14import { PeertubeChunkStore } from './peertube-chunk-store'
15import { renderVideo } from './video-renderer'
15 16
16const CacheChunkStore = require('cache-chunk-store') 17const CacheChunkStore = require('cache-chunk-store')
17 18