aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-01-24 10:16:30 +0100
committerChocobozzz <chocobozzz@cpy.re>2019-02-11 09:13:02 +0100
commit3b6f205c34bb931de0323581edf991ca33256e6b (patch)
treef6ba40b7c666e38ff9c321906f04cb2c2630163e
parent2adfc7ea9a1f858db874df9fe322e7ae833db77c (diff)
downloadPeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.tar.gz
PeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.tar.zst
PeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.zip
Correctly implement p2p-media-loader
-rw-r--r--client/package.json2
-rw-r--r--client/src/assets/player/p2p-media-loader-plugin.ts82
-rw-r--r--client/src/assets/player/peertube-player-manager.ts12
-rw-r--r--client/src/assets/player/peertube-plugin.ts41
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts12
-rw-r--r--client/src/assets/player/videojs-components/p2p-info-button.ts16
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-button.ts33
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-item.ts18
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-item.ts10
-rw-r--r--client/src/assets/player/webtorrent-plugin.ts26
-rw-r--r--client/src/standalone/videos/embed.ts31
-rw-r--r--client/yarn.lock17
12 files changed, 240 insertions, 60 deletions
diff --git a/client/package.json b/client/package.json
index 9da7c1025..a455653fe 100644
--- a/client/package.json
+++ b/client/package.json
@@ -87,6 +87,7 @@
87 "@ngx-translate/i18n-polyfill": "^1.0.0", 87 "@ngx-translate/i18n-polyfill": "^1.0.0",
88 "@streamroot/videojs-hlsjs-plugin": "^1.0.7", 88 "@streamroot/videojs-hlsjs-plugin": "^1.0.7",
89 "@types/core-js": "^2.5.0", 89 "@types/core-js": "^2.5.0",
90 "@types/hls.js": "^0.12.0",
90 "@types/jasmine": "^2.8.7", 91 "@types/jasmine": "^2.8.7",
91 "@types/jasminewd2": "^2.0.3", 92 "@types/jasminewd2": "^2.0.3",
92 "@types/jest": "^23.3.1", 93 "@types/jest": "^23.3.1",
@@ -110,6 +111,7 @@
110 "extract-text-webpack-plugin": "4.0.0-beta.0", 111 "extract-text-webpack-plugin": "4.0.0-beta.0",
111 "file-loader": "^2.0.0", 112 "file-loader": "^2.0.0",
112 "focus-visible": "^4.1.5", 113 "focus-visible": "^4.1.5",
114 "hls.js": "^0.12.2",
113 "html-loader": "^0.5.5", 115 "html-loader": "^0.5.5",
114 "html-webpack-plugin": "^3.2.0", 116 "html-webpack-plugin": "^3.2.0",
115 "https-browserify": "^1.0.0", 117 "https-browserify": "^1.0.0",
diff --git a/client/src/assets/player/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader-plugin.ts
index 6d07a2c9c..25117e51e 100644
--- a/client/src/assets/player/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader-plugin.ts
@@ -1,25 +1,45 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1// FIXME: something weird with our path definition in tsconfig and typings
2// @ts-ignore 2// @ts-ignore
3import * as videojs from 'video.js' 3import * as videojs from 'video.js'
4import { P2PMediaLoaderPluginOptions, VideoJSComponentInterface } from './peertube-videojs-typings' 4import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from './peertube-videojs-typings'
5 5
6// videojs-hlsjs-plugin needs videojs in window 6// videojs-hlsjs-plugin needs videojs in window
7window['videojs'] = videojs 7window['videojs'] = videojs
8import '@streamroot/videojs-hlsjs-plugin' 8import '@streamroot/videojs-hlsjs-plugin'
9 9
10import { initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' 10import { Engine, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
11 11import * as Hls from 'hls.js'
12// import { Events } from '../p2p-media-loader/p2p-media-loader-core/lib' 12import { Events } from 'p2p-media-loader-core'
13 13
14const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') 14const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
15class P2pMediaLoaderPlugin extends Plugin { 15class P2pMediaLoaderPlugin extends Plugin {
16 16
17 private readonly CONSTANTS = {
18 INFO_SCHEDULER: 1000 // Don't change this
19 }
20
21 private hlsjs: Hls
22 private p2pEngine: Engine
23 private statsP2PBytes = {
24 pendingDownload: [] as number[],
25 pendingUpload: [] as number[],
26 numPeers: 0,
27 totalDownload: 0,
28 totalUpload: 0
29 }
30
31 private networkInfoInterval: any
32
17 constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) { 33 constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) {
18 super(player, options) 34 super(player, options)
19 35
20 initVideoJsContribHlsJsPlayer(player) 36 videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: Hls) => {
37 this.hlsjs = hlsjs
21 38
22 console.log(options) 39 this.initialize()
40 })
41
42 initVideoJsContribHlsJsPlayer(player)
23 43
24 player.src({ 44 player.src({
25 type: options.type, 45 type: options.type,
@@ -27,6 +47,56 @@ class P2pMediaLoaderPlugin extends Plugin {
27 }) 47 })
28 } 48 }
29 49
50 dispose () {
51 clearInterval(this.networkInfoInterval)
52 }
53
54 private initialize () {
55 this.p2pEngine = this.player.tech_.options_.hlsjsConfig.loader.getEngine()
56
57 this.hlsjs.on(Hls.Events.LEVEL_SWITCHING, (_, data: Hls.levelSwitchingData) => {
58 this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height })
59 })
60
61 this.runStats()
62 }
63
64 private runStats () {
65 this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, size: number) => {
66 if (method === 'p2p') {
67 this.statsP2PBytes.pendingDownload.push(size)
68 this.statsP2PBytes.totalDownload += size
69 }
70 })
71
72 this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, size: number) => {
73 if (method === 'p2p') {
74 this.statsP2PBytes.pendingUpload.push(size)
75 this.statsP2PBytes.totalUpload += size
76 }
77 })
78
79 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
80 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
81
82 this.networkInfoInterval = setInterval(() => {
83 let downloadSpeed = this.statsP2PBytes.pendingDownload.reduce((a: number, b: number) => a + b, 0)
84 let uploadSpeed = this.statsP2PBytes.pendingUpload.reduce((a: number, b: number) => a + b, 0)
85
86 this.statsP2PBytes.pendingDownload = []
87 this.statsP2PBytes.pendingUpload = []
88
89 return this.player.trigger('p2pInfo', {
90 p2p: {
91 downloadSpeed,
92 uploadSpeed,
93 numPeers: this.statsP2PBytes.numPeers,
94 downloaded: this.statsP2PBytes.totalDownload,
95 uploaded: this.statsP2PBytes.totalUpload
96 }
97 } as PlayerNetworkInfo)
98 }, this.CONSTANTS.INFO_SCHEDULER)
99 }
30} 100}
31 101
32videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin) 102videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin)
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 9155c0698..2e090847c 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -24,17 +24,17 @@ videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitle
24// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know) 24// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
25videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' 25videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
26 26
27type PlayerMode = 'webtorrent' | 'p2p-media-loader' 27export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
28 28
29type WebtorrentOptions = { 29export type WebtorrentOptions = {
30 videoFiles: VideoFile[] 30 videoFiles: VideoFile[]
31} 31}
32 32
33type P2PMediaLoaderOptions = { 33export type P2PMediaLoaderOptions = {
34 playlistUrl: string 34 playlistUrl: string
35} 35}
36 36
37type CommonOptions = { 37export type CommonOptions = {
38 playerElement: HTMLVideoElement 38 playerElement: HTMLVideoElement
39 39
40 autoplay: boolean 40 autoplay: boolean
@@ -137,6 +137,7 @@ export class PeertubePlayerManager {
137 const commonOptions = options.common 137 const commonOptions = options.common
138 const webtorrentOptions = options.webtorrent 138 const webtorrentOptions = options.webtorrent
139 const p2pMediaLoaderOptions = options.p2pMediaLoader 139 const p2pMediaLoaderOptions = options.p2pMediaLoader
140 let html5 = {}
140 141
141 const plugins: VideoJSPluginOptions = { 142 const plugins: VideoJSPluginOptions = {
142 peertube: { 143 peertube: {
@@ -171,6 +172,7 @@ export class PeertubePlayerManager {
171 } 172 }
172 173
173 Object.assign(plugins, { p2pMediaLoader, streamrootHls }) 174 Object.assign(plugins, { p2pMediaLoader, streamrootHls })
175 html5 = streamrootHls.html5
174 } 176 }
175 177
176 if (webtorrentOptions) { 178 if (webtorrentOptions) {
@@ -184,6 +186,8 @@ export class PeertubePlayerManager {
184 } 186 }
185 187
186 const videojsOptions = { 188 const videojsOptions = {
189 html5,
190
187 // We don't use text track settings for now 191 // We don't use text track settings for now
188 textTrackSettings: false, 192 textTrackSettings: false,
189 controls: commonOptions.controls !== undefined ? commonOptions.controls : true, 193 controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts
index 0bd607697..f83d9094a 100644
--- a/client/src/assets/player/peertube-plugin.ts
+++ b/client/src/assets/player/peertube-plugin.ts
@@ -2,7 +2,14 @@
2// @ts-ignore 2// @ts-ignore
3import * as videojs from 'video.js' 3import * as videojs from 'video.js'
4import './videojs-components/settings-menu-button' 4import './videojs-components/settings-menu-button'
5import { PeerTubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' 5import {
6 PeerTubePluginOptions,
7 ResolutionUpdateData,
8 UserWatching,
9 VideoJSCaption,
10 VideoJSComponentInterface,
11 videojsUntyped
12} from './peertube-videojs-typings'
6import { isMobile, timeToInt } from './utils' 13import { isMobile, timeToInt } from './utils'
7import { 14import {
8 getStoredLastSubtitle, 15 getStoredLastSubtitle,
@@ -30,6 +37,7 @@ class PeerTubePlugin extends Plugin {
30 private videoViewInterval: any 37 private videoViewInterval: any
31 private userWatchingVideoInterval: any 38 private userWatchingVideoInterval: any
32 private qualityObservationTimer: any 39 private qualityObservationTimer: any
40 private lastResolutionChange: ResolutionUpdateData
33 41
34 constructor (player: videojs.Player, options: PeerTubePluginOptions) { 42 constructor (player: videojs.Player, options: PeerTubePluginOptions) {
35 super(player, options) 43 super(player, options)
@@ -44,6 +52,22 @@ class PeerTubePlugin extends Plugin {
44 this.player.ready(() => { 52 this.player.ready(() => {
45 const playerOptions = this.player.options_ 53 const playerOptions = this.player.options_
46 54
55 if (this.player.webtorrent) {
56 this.player.webtorrent().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
57 this.player.webtorrent().on('autoResolutionChange', (_: any, d: any) => this.trigger('autoResolutionChange', d))
58 }
59
60 if (this.player.p2pMediaLoader) {
61 this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
62 }
63
64 this.player.tech_.on('loadedqualitydata', () => {
65 setTimeout(() => {
66 // Replay a resolution change, now we loaded all quality data
67 if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange)
68 }, 0)
69 })
70
47 const volume = getStoredVolume() 71 const volume = getStoredVolume()
48 if (volume !== undefined) this.player.volume(volume) 72 if (volume !== undefined) this.player.volume(volume)
49 73
@@ -158,6 +182,21 @@ class PeerTubePlugin extends Plugin {
158 return fetch(url, { method: 'PUT', body, headers }) 182 return fetch(url, { method: 'PUT', body, headers })
159 } 183 }
160 184
185 private handleResolutionChange (data: ResolutionUpdateData) {
186 this.lastResolutionChange = data
187
188 const qualityLevels = this.player.qualityLevels()
189
190 for (let i = 0; i < qualityLevels.length; i++) {
191 if (qualityLevels[i].height === data.resolutionId) {
192 data.id = qualityLevels[i].id
193 break
194 }
195 }
196
197 this.trigger('resolutionChange', data)
198 }
199
161 private alterInactivity () { 200 private alterInactivity () {
162 let saveInactivityTimeout: number 201 let saveInactivityTimeout: number
163 202
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index 060ea4dce..fff992a6f 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -83,13 +83,25 @@ type LoadedQualityData = {
83type ResolutionUpdateData = { 83type ResolutionUpdateData = {
84 auto: boolean, 84 auto: boolean,
85 resolutionId: number 85 resolutionId: number
86 id?: number
86} 87}
87 88
88type AutoResolutionUpdateData = { 89type AutoResolutionUpdateData = {
89 possible: boolean 90 possible: boolean
90} 91}
91 92
93type PlayerNetworkInfo = {
94 p2p: {
95 downloadSpeed: number
96 uploadSpeed: number
97 downloaded: number
98 uploaded: number
99 numPeers: number
100 }
101}
102
92export { 103export {
104 PlayerNetworkInfo,
93 ResolutionUpdateData, 105 ResolutionUpdateData,
94 AutoResolutionUpdateData, 106 AutoResolutionUpdateData,
95 VideoJSComponentInterface, 107 VideoJSComponentInterface,
diff --git a/client/src/assets/player/videojs-components/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts
index 03a5d29f0..2fc4c4562 100644
--- a/client/src/assets/player/videojs-components/p2p-info-button.ts
+++ b/client/src/assets/player/videojs-components/p2p-info-button.ts
@@ -1,4 +1,4 @@
1import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 1import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
2import { bytes } from '../utils' 2import { bytes } from '../utils'
3 3
4const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 4const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
@@ -65,7 +65,7 @@ class P2pInfoButton extends Button {
65 subDivHttp.appendChild(subDivHttpText) 65 subDivHttp.appendChild(subDivHttpText)
66 div.appendChild(subDivHttp) 66 div.appendChild(subDivHttp)
67 67
68 this.player_.on('p2pInfo', (event: any, data: any) => { 68 this.player_.on('p2pInfo', (event: any, data: PlayerNetworkInfo) => {
69 // We are in HTTP fallback 69 // We are in HTTP fallback
70 if (!data) { 70 if (!data) {
71 subDivHttp.className = 'vjs-peertube-displayed' 71 subDivHttp.className = 'vjs-peertube-displayed'
@@ -74,11 +74,13 @@ class P2pInfoButton extends Button {
74 return 74 return
75 } 75 }
76 76
77 const downloadSpeed = bytes(data.downloadSpeed) 77 const p2pStats = data.p2p
78 const uploadSpeed = bytes(data.uploadSpeed) 78
79 const totalDownloaded = bytes(data.downloaded) 79 const downloadSpeed = bytes(p2pStats.downloadSpeed)
80 const totalUploaded = bytes(data.uploaded) 80 const uploadSpeed = bytes(p2pStats.uploadSpeed)
81 const numPeers = data.numPeers 81 const totalDownloaded = bytes(p2pStats.downloaded)
82 const totalUploaded = bytes(p2pStats.uploaded)
83 const numPeers = p2pStats.numPeers
82 84
83 subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' + 85 subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
84 this.player_.localize('Total uploaded: ' + totalUploaded.join(' ')) 86 this.player_.localize('Total uploaded: ' + totalUploaded.join(' '))
diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts
index 2847de470..abcc16411 100644
--- a/client/src/assets/player/videojs-components/resolution-menu-button.ts
+++ b/client/src/assets/player/videojs-components/resolution-menu-button.ts
@@ -14,11 +14,9 @@ class ResolutionMenuButton extends MenuButton {
14 super(player, options) 14 super(player, options)
15 this.player = player 15 this.player = player
16 16
17 player.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) 17 player.tech_.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
18 18
19 if (player.webtorrent) { 19 player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0))
20 player.webtorrent().on('videoFileUpdate', () => setTimeout(() => this.trigger('updateLabel'), 0))
21 }
22 } 20 }
23 21
24 createEl () { 22 createEl () {
@@ -49,11 +47,32 @@ class ResolutionMenuButton extends MenuButton {
49 return 'vjs-resolution-control ' + super.buildWrapperCSSClass() 47 return 'vjs-resolution-control ' + super.buildWrapperCSSClass()
50 } 48 }
51 49
50 private addClickListener (component: any) {
51 component.on('click', () => {
52 let children = this.menu.children()
53
54 for (const child of children) {
55 if (component !== child) {
56 child.selected(false)
57 }
58 }
59 })
60 }
61
52 private buildQualities (data: LoadedQualityData) { 62 private buildQualities (data: LoadedQualityData) {
53 // The automatic resolution item will need other labels 63 // The automatic resolution item will need other labels
54 const labels: { [ id: number ]: string } = {} 64 const labels: { [ id: number ]: string } = {}
55 65
66 data.qualityData.video.sort((a, b) => {
67 if (a.id > b.id) return -1
68 if (a.id === b.id) return 0
69 return 1
70 })
71
56 for (const d of data.qualityData.video) { 72 for (const d of data.qualityData.video) {
73 // Skip auto resolution, we'll add it ourselves
74 if (d.id === -1) continue
75
57 this.menu.addChild(new ResolutionMenuItem( 76 this.menu.addChild(new ResolutionMenuItem(
58 this.player_, 77 this.player_,
59 { 78 {
@@ -77,6 +96,12 @@ class ResolutionMenuButton extends MenuButton {
77 selected: true // By default, in auto mode 96 selected: true // By default, in auto mode
78 } 97 }
79 )) 98 ))
99
100 for (const m of this.menu.children()) {
101 this.addClickListener(m)
102 }
103
104 this.trigger('menuChanged')
80 } 105 }
81} 106}
82ResolutionMenuButton.prototype.controlText_ = 'Quality' 107ResolutionMenuButton.prototype.controlText_ = 'Quality'
diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts
index cc1c79739..6c42fefd2 100644
--- a/client/src/assets/player/videojs-components/resolution-menu-item.ts
+++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts
@@ -28,16 +28,12 @@ class ResolutionMenuItem extends MenuItem {
28 this.id = options.id 28 this.id = options.id
29 this.callback = options.callback 29 this.callback = options.callback
30 30
31 if (player.webtorrent) { 31 player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
32 player.webtorrent().on('videoFileUpdate', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
33 32
34 // We only want to disable the "Auto" item 33 // We only want to disable the "Auto" item
35 if (this.id === -1) { 34 if (this.id === -1) {
36 player.webtorrent().on('autoResolutionUpdate', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) 35 player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data))
37 }
38 } 36 }
39
40 // TODO: update on HLS change
41 } 37 }
42 38
43 handleClick (event: any) { 39 handleClick (event: any) {
@@ -46,12 +42,12 @@ class ResolutionMenuItem extends MenuItem {
46 42
47 super.handleClick(event) 43 super.handleClick(event)
48 44
49 this.callback(this.id) 45 this.callback(this.id, 'video')
50 } 46 }
51 47
52 updateSelection (data: ResolutionUpdateData) { 48 updateSelection (data: ResolutionUpdateData) {
53 if (this.id === -1) { 49 if (this.id === -1) {
54 this.currentResolutionLabel = this.labels[data.resolutionId] 50 this.currentResolutionLabel = this.labels[data.id]
55 } 51 }
56 52
57 // Automatic resolution only 53 // Automatic resolution only
@@ -60,7 +56,7 @@ class ResolutionMenuItem extends MenuItem {
60 return 56 return
61 } 57 }
62 58
63 this.selected(this.id === data.resolutionId) 59 this.selected(this.id === data.id)
64 } 60 }
65 61
66 updateAutoResolution (data: AutoResolutionUpdateData) { 62 updateAutoResolution (data: AutoResolutionUpdateData) {
diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts
index b9a430290..f14959f9c 100644
--- a/client/src/assets/player/videojs-components/settings-menu-item.ts
+++ b/client/src/assets/player/videojs-components/settings-menu-item.ts
@@ -223,6 +223,11 @@ class SettingsMenuItem extends MenuItem {
223 this.subMenu.on('updateLabel', () => { 223 this.subMenu.on('updateLabel', () => {
224 this.update() 224 this.update()
225 }) 225 })
226 this.subMenu.on('menuChanged', () => {
227 this.bindClickEvents()
228 this.setSize()
229 this.update()
230 })
226 231
227 this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_) 232 this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
228 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_) 233 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
@@ -230,7 +235,7 @@ class SettingsMenuItem extends MenuItem {
230 this.update() 235 this.update()
231 236
232 this.createBackButton() 237 this.createBackButton()
233 this.getSize() 238 this.setSize()
234 this.bindClickEvents() 239 this.bindClickEvents()
235 240
236 // prefixed event listeners for CSS TransitionEnd 241 // prefixed event listeners for CSS TransitionEnd
@@ -292,8 +297,9 @@ class SettingsMenuItem extends MenuItem {
292 297
293 // save size of submenus on first init 298 // save size of submenus on first init
294 // if number of submenu items change dynamically more logic will be needed 299 // if number of submenu items change dynamically more logic will be needed
295 getSize () { 300 setSize () {
296 this.dialog.removeClass('vjs-hidden') 301 this.dialog.removeClass('vjs-hidden')
302 videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
297 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_) 303 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
298 this.setMargin() 304 this.setMargin()
299 this.dialog.addClass('vjs-hidden') 305 this.dialog.addClass('vjs-hidden')
diff --git a/client/src/assets/player/webtorrent-plugin.ts b/client/src/assets/player/webtorrent-plugin.ts
index c3d990aed..47f169e24 100644
--- a/client/src/assets/player/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent-plugin.ts
@@ -5,7 +5,7 @@ import * as videojs from 'video.js'
5import * as WebTorrent from 'webtorrent' 5import * as WebTorrent from 'webtorrent'
6import { VideoFile } from '../../../../shared/models/videos/video.model' 6import { VideoFile } from '../../../../shared/models/videos/video.model'
7import { renderVideo } from './webtorrent/video-renderer' 7import { renderVideo } from './webtorrent/video-renderer'
8import { LoadedQualityData, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings' 8import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings'
9import { videoFileMaxByResolution, videoFileMinByResolution } from './utils' 9import { videoFileMaxByResolution, videoFileMinByResolution } from './utils'
10import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store' 10import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store'
11import { 11import {
@@ -180,7 +180,7 @@ class WebTorrentPlugin extends Plugin {
180 }) 180 })
181 181
182 this.changeQuality() 182 this.changeQuality()
183 this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id }) 183 this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.currentVideoFile.resolution.id })
184 } 184 }
185 185
186 updateResolution (resolutionId: number, delay = 0) { 186 updateResolution (resolutionId: number, delay = 0) {
@@ -216,15 +216,15 @@ class WebTorrentPlugin extends Plugin {
216 216
217 enableAutoResolution () { 217 enableAutoResolution () {
218 this.autoResolution = true 218 this.autoResolution = true
219 this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) 219 this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
220 } 220 }
221 221
222 disableAutoResolution (forbid = false) { 222 disableAutoResolution (forbid = false) {
223 if (forbid === true) this.autoResolutionPossible = false 223 if (forbid === true) this.autoResolutionPossible = false
224 224
225 this.autoResolution = false 225 this.autoResolution = false
226 this.trigger('autoResolutionUpdate', { possible: this.autoResolutionPossible }) 226 this.trigger('autoResolutionChange', { possible: this.autoResolutionPossible })
227 this.trigger('videoFileUpdate', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() }) 227 this.trigger('resolutionChange', { auto: this.autoResolution, resolutionId: this.getCurrentResolutionId() })
228 } 228 }
229 229
230 getTorrent () { 230 getTorrent () {
@@ -472,12 +472,14 @@ class WebTorrentPlugin extends Plugin {
472 if (this.webtorrent.downloadSpeed !== 0) this.downloadSpeeds.push(this.webtorrent.downloadSpeed) 472 if (this.webtorrent.downloadSpeed !== 0) this.downloadSpeeds.push(this.webtorrent.downloadSpeed)
473 473
474 return this.player.trigger('p2pInfo', { 474 return this.player.trigger('p2pInfo', {
475 downloadSpeed: this.torrent.downloadSpeed, 475 p2p: {
476 numPeers: this.torrent.numPeers, 476 downloadSpeed: this.torrent.downloadSpeed,
477 uploadSpeed: this.torrent.uploadSpeed, 477 numPeers: this.torrent.numPeers,
478 downloaded: this.torrent.downloaded, 478 uploadSpeed: this.torrent.uploadSpeed,
479 uploaded: this.torrent.uploaded 479 downloaded: this.torrent.downloaded,
480 }) 480 uploaded: this.torrent.uploaded
481 }
482 } as PlayerNetworkInfo)
481 }, this.CONSTANTS.INFO_SCHEDULER) 483 }, this.CONSTANTS.INFO_SCHEDULER)
482 } 484 }
483 485
@@ -597,7 +599,7 @@ class WebTorrentPlugin extends Plugin {
597 video: qualityLevelsPayload 599 video: qualityLevelsPayload
598 } 600 }
599 } 601 }
600 this.player.trigger('loadedqualitydata', payload) 602 this.player.tech_.trigger('loadedqualitydata', payload)
601 } 603 }
602 604
603 private buildQualityLabel (file: VideoFile) { 605 private buildQualityLabel (file: VideoFile) {
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index b1261c4a2..0d165ea7b 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -23,7 +23,7 @@ import { peertubeTranslate, ResultList, VideoDetails } from '../../../../shared'
23import { PeerTubeResolution } from '../player/definitions' 23import { PeerTubeResolution } from '../player/definitions'
24import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 24import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
25import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 25import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
26import { PeertubePlayerManager, PeertubePlayerManagerOptions } from '../../assets/player/peertube-player-manager' 26import { PeertubePlayerManager, PeertubePlayerManagerOptions, PlayerMode } from '../../assets/player/peertube-player-manager'
27 27
28/** 28/**
29 * Embed API exposes control of the embed player to the outside world via 29 * Embed API exposes control of the embed player to the outside world via
@@ -162,6 +162,7 @@ class PeerTubeEmbed {
162 subtitle: string 162 subtitle: string
163 enableApi = false 163 enableApi = false
164 startTime: number | string = 0 164 startTime: number | string = 0
165 mode: PlayerMode
165 scope = 'peertube' 166 scope = 'peertube'
166 167
167 static async main () { 168 static async main () {
@@ -255,6 +256,8 @@ class PeerTubeEmbed {
255 this.scope = this.getParamString(params, 'scope', this.scope) 256 this.scope = this.getParamString(params, 'scope', this.scope)
256 this.subtitle = this.getParamString(params, 'subtitle') 257 this.subtitle = this.getParamString(params, 'subtitle')
257 this.startTime = this.getParamString(params, 'start') 258 this.startTime = this.getParamString(params, 'start')
259
260 this.mode = this.getParamToggle(params, 'p2p-media-loader') ? 'p2p-media-loader' : 'webtorrent'
258 } catch (err) { 261 } catch (err) {
259 console.error('Cannot get params from URL.', err) 262 console.error('Cannot get params from URL.', err)
260 } 263 }
@@ -312,20 +315,26 @@ class PeerTubeEmbed {
312 serverUrl: window.location.origin, 315 serverUrl: window.location.origin,
313 language: navigator.language, 316 language: navigator.language,
314 embedUrl: window.location.origin + videoInfo.embedPath 317 embedUrl: window.location.origin + videoInfo.embedPath
315 },
316
317 webtorrent: {
318 videoFiles: videoInfo.files
319 } 318 }
319 }
320 320
321 // p2pMediaLoader: { 321 if (this.mode === 'p2p-media-loader') {
322 // // playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8' 322 Object.assign(options, {
323 // // playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8' 323 p2pMediaLoader: {
324 // playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8' 324 // playlistUrl: 'https://akamai-axtest.akamaized.net/routes/lapd-v1-acceptance/www_c4/Manifest.m3u8'
325 // } 325 // playlistUrl: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8'
326 playlistUrl: 'https://cdn.theoplayer.com/video/elephants-dream/playlist.m3u8'
327 }
328 })
329 } else {
330 Object.assign(options, {
331 webtorrent: {
332 videoFiles: videoInfo.files
333 }
334 })
326 } 335 }
327 336
328 this.player = await PeertubePlayerManager.initialize('webtorrent', options) 337 this.player = await PeertubePlayerManager.initialize(this.mode, options)
329 338
330 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) 339 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
331 340
diff --git a/client/yarn.lock b/client/yarn.lock
index 0698ca501..ced35688f 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -411,6 +411,11 @@
411 resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47" 411 resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47"
412 integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ== 412 integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ==
413 413
414"@types/hls.js@^0.12.0":
415 version "0.12.0"
416 resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-0.12.0.tgz#33f73e542201a766fa56792cb81fe9f97d7097ed"
417 integrity sha512-hJ7eJAQVEazAANK4Ay0YbXlZF36SDy9c8kcHTF7//77ylgV6hV/JrlwhVmobsSacr5aZcbw5MbZ2bSHbS36eOQ==
418
414"@types/jasmine@*": 419"@types/jasmine@*":
415 version "3.3.1" 420 version "3.3.1"
416 resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc" 421 resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc"
@@ -3300,7 +3305,7 @@ etag@~1.8.1:
3300 resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 3305 resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
3301 integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 3306 integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
3302 3307
3303eventemitter3@^3.0.0: 3308eventemitter3@3.1.0, eventemitter3@^3.0.0:
3304 version "3.1.0" 3309 version "3.1.0"
3305 resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" 3310 resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
3306 integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA== 3311 integrity sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==
@@ -4236,6 +4241,14 @@ he@1.2.x:
4236 resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 4241 resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
4237 integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 4242 integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
4238 4243
4244hls.js@^0.12.2:
4245 version "0.12.2"
4246 resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.12.2.tgz#64a969a78cc25991ed5de19357b1dc3f178ac23b"
4247 integrity sha512-lQBSXggw9OzEuaUllUBoSxPcf7neFgnEiDRfCdCNdIPtUeV7vXZ0OeASx6EWtZTBiqSSPigoOX1Y+AR5dA1Feg==
4248 dependencies:
4249 eventemitter3 "3.1.0"
4250 url-toolkit "^2.1.6"
4251
4239hmac-drbg@^1.0.0: 4252hmac-drbg@^1.0.0:
4240 version "1.0.1" 4253 version "1.0.1"
4241 resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" 4254 resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@@ -9976,7 +9989,7 @@ url-parse@^1.4.3:
9976 querystringify "^2.0.0" 9989 querystringify "^2.0.0"
9977 requires-port "^1.0.0" 9990 requires-port "^1.0.0"
9978 9991
9979url-toolkit@^2.1.1, url-toolkit@^2.1.3: 9992url-toolkit@^2.1.1, url-toolkit@^2.1.3, url-toolkit@^2.1.6:
9980 version "2.1.6" 9993 version "2.1.6"
9981 resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2" 9994 resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
9982 integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw== 9995 integrity sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==