diff options
author | Chocobozzz <me@florianbigard.com> | 2019-01-24 10:16:30 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-02-11 09:13:02 +0100 |
commit | 3b6f205c34bb931de0323581edf991ca33256e6b (patch) | |
tree | f6ba40b7c666e38ff9c321906f04cb2c2630163e | |
parent | 2adfc7ea9a1f858db874df9fe322e7ae833db77c (diff) | |
download | PeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.tar.gz PeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.tar.zst PeerTube-3b6f205c34bb931de0323581edf991ca33256e6b.zip |
Correctly implement p2p-media-loader
-rw-r--r-- | client/package.json | 2 | ||||
-rw-r--r-- | client/src/assets/player/p2p-media-loader-plugin.ts | 82 | ||||
-rw-r--r-- | client/src/assets/player/peertube-player-manager.ts | 12 | ||||
-rw-r--r-- | client/src/assets/player/peertube-plugin.ts | 41 | ||||
-rw-r--r-- | client/src/assets/player/peertube-videojs-typings.ts | 12 | ||||
-rw-r--r-- | client/src/assets/player/videojs-components/p2p-info-button.ts | 16 | ||||
-rw-r--r-- | client/src/assets/player/videojs-components/resolution-menu-button.ts | 33 | ||||
-rw-r--r-- | client/src/assets/player/videojs-components/resolution-menu-item.ts | 18 | ||||
-rw-r--r-- | client/src/assets/player/videojs-components/settings-menu-item.ts | 10 | ||||
-rw-r--r-- | client/src/assets/player/webtorrent-plugin.ts | 26 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 31 | ||||
-rw-r--r-- | client/yarn.lock | 17 |
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 |
3 | import * as videojs from 'video.js' | 3 | import * as videojs from 'video.js' |
4 | import { P2PMediaLoaderPluginOptions, VideoJSComponentInterface } from './peertube-videojs-typings' | 4 | import { 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 |
7 | window['videojs'] = videojs | 7 | window['videojs'] = videojs |
8 | import '@streamroot/videojs-hlsjs-plugin' | 8 | import '@streamroot/videojs-hlsjs-plugin' |
9 | 9 | ||
10 | import { initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' | 10 | import { Engine, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' |
11 | 11 | import * as Hls from 'hls.js' | |
12 | // import { Events } from '../p2p-media-loader/p2p-media-loader-core/lib' | 12 | import { Events } from 'p2p-media-loader-core' |
13 | 13 | ||
14 | const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') | 14 | const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') |
15 | class P2pMediaLoaderPlugin extends Plugin { | 15 | class 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 | ||
32 | videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin) | 102 | videojs.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) |
25 | videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' | 25 | videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' |
26 | 26 | ||
27 | type PlayerMode = 'webtorrent' | 'p2p-media-loader' | 27 | export type PlayerMode = 'webtorrent' | 'p2p-media-loader' |
28 | 28 | ||
29 | type WebtorrentOptions = { | 29 | export type WebtorrentOptions = { |
30 | videoFiles: VideoFile[] | 30 | videoFiles: VideoFile[] |
31 | } | 31 | } |
32 | 32 | ||
33 | type P2PMediaLoaderOptions = { | 33 | export type P2PMediaLoaderOptions = { |
34 | playlistUrl: string | 34 | playlistUrl: string |
35 | } | 35 | } |
36 | 36 | ||
37 | type CommonOptions = { | 37 | export 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 |
3 | import * as videojs from 'video.js' | 3 | import * as videojs from 'video.js' |
4 | import './videojs-components/settings-menu-button' | 4 | import './videojs-components/settings-menu-button' |
5 | import { PeerTubePluginOptions, UserWatching, VideoJSCaption, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' | 5 | import { |
6 | PeerTubePluginOptions, | ||
7 | ResolutionUpdateData, | ||
8 | UserWatching, | ||
9 | VideoJSCaption, | ||
10 | VideoJSComponentInterface, | ||
11 | videojsUntyped | ||
12 | } from './peertube-videojs-typings' | ||
6 | import { isMobile, timeToInt } from './utils' | 13 | import { isMobile, timeToInt } from './utils' |
7 | import { | 14 | import { |
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 = { | |||
83 | type ResolutionUpdateData = { | 83 | type ResolutionUpdateData = { |
84 | auto: boolean, | 84 | auto: boolean, |
85 | resolutionId: number | 85 | resolutionId: number |
86 | id?: number | ||
86 | } | 87 | } |
87 | 88 | ||
88 | type AutoResolutionUpdateData = { | 89 | type AutoResolutionUpdateData = { |
89 | possible: boolean | 90 | possible: boolean |
90 | } | 91 | } |
91 | 92 | ||
93 | type PlayerNetworkInfo = { | ||
94 | p2p: { | ||
95 | downloadSpeed: number | ||
96 | uploadSpeed: number | ||
97 | downloaded: number | ||
98 | uploaded: number | ||
99 | numPeers: number | ||
100 | } | ||
101 | } | ||
102 | |||
92 | export { | 103 | export { |
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 @@ | |||
1 | import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' | 1 | import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' |
2 | import { bytes } from '../utils' | 2 | import { bytes } from '../utils' |
3 | 3 | ||
4 | const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') | 4 | const 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 | } |
82 | ResolutionMenuButton.prototype.controlText_ = 'Quality' | 107 | ResolutionMenuButton.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' | |||
5 | import * as WebTorrent from 'webtorrent' | 5 | import * as WebTorrent from 'webtorrent' |
6 | import { VideoFile } from '../../../../shared/models/videos/video.model' | 6 | import { VideoFile } from '../../../../shared/models/videos/video.model' |
7 | import { renderVideo } from './webtorrent/video-renderer' | 7 | import { renderVideo } from './webtorrent/video-renderer' |
8 | import { LoadedQualityData, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings' | 8 | import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from './peertube-videojs-typings' |
9 | import { videoFileMaxByResolution, videoFileMinByResolution } from './utils' | 9 | import { videoFileMaxByResolution, videoFileMinByResolution } from './utils' |
10 | import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store' | 10 | import { PeertubeChunkStore } from './webtorrent/peertube-chunk-store' |
11 | import { | 11 | import { |
@@ -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' | |||
23 | import { PeerTubeResolution } from '../player/definitions' | 23 | import { PeerTubeResolution } from '../player/definitions' |
24 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' | 24 | import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' |
25 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 25 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' |
26 | import { PeertubePlayerManager, PeertubePlayerManagerOptions } from '../../assets/player/peertube-player-manager' | 26 | import { 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 | ||
3303 | eventemitter3@^3.0.0: | 3308 | eventemitter3@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 | ||
4244 | hls.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 | |||
4239 | hmac-drbg@^1.0.0: | 4252 | hmac-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 | ||
9979 | url-toolkit@^2.1.1, url-toolkit@^2.1.3: | 9992 | url-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== |