diff options
Diffstat (limited to 'client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts')
-rw-r--r-- | client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts new file mode 100644 index 000000000..5c0f0021f --- /dev/null +++ b/client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts | |||
@@ -0,0 +1,183 @@ | |||
1 | import Hlsjs from 'hls.js' | ||
2 | import videojs from 'video.js' | ||
3 | import { Events, Segment } from '@peertube/p2p-media-loader-core' | ||
4 | import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' | ||
5 | import { timeToInt } from '@shared/core-utils' | ||
6 | import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' | ||
7 | import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' | ||
8 | |||
9 | registerConfigPlugin(videojs) | ||
10 | registerSourceHandler(videojs) | ||
11 | |||
12 | const Plugin = videojs.getPlugin('plugin') | ||
13 | class P2pMediaLoaderPlugin extends Plugin { | ||
14 | |||
15 | private readonly CONSTANTS = { | ||
16 | INFO_SCHEDULER: 1000 // Don't change this | ||
17 | } | ||
18 | private readonly options: P2PMediaLoaderPluginOptions | ||
19 | |||
20 | private hlsjs: Hlsjs | ||
21 | private p2pEngine: Engine | ||
22 | private statsP2PBytes = { | ||
23 | pendingDownload: [] as number[], | ||
24 | pendingUpload: [] as number[], | ||
25 | numPeers: 0, | ||
26 | totalDownload: 0, | ||
27 | totalUpload: 0 | ||
28 | } | ||
29 | private statsHTTPBytes = { | ||
30 | pendingDownload: [] as number[], | ||
31 | pendingUpload: [] as number[], | ||
32 | totalDownload: 0, | ||
33 | totalUpload: 0 | ||
34 | } | ||
35 | private startTime: number | ||
36 | |||
37 | private networkInfoInterval: any | ||
38 | |||
39 | constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) { | ||
40 | super(player) | ||
41 | |||
42 | this.options = options | ||
43 | |||
44 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 | ||
45 | if (!(videojs as any).Html5Hlsjs) { | ||
46 | console.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.') | ||
47 | |||
48 | if (!player.canPlayType('application/vnd.apple.mpegurl')) { | ||
49 | const message = 'Cannot fallback to built-in HLS' | ||
50 | console.warn(message) | ||
51 | |||
52 | player.ready(() => player.trigger('error', new Error(message))) | ||
53 | return | ||
54 | } | ||
55 | } else { | ||
56 | // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 | ||
57 | (videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => { | ||
58 | this.hlsjs = hlsjs | ||
59 | }) | ||
60 | |||
61 | initVideoJsContribHlsJsPlayer(player) | ||
62 | } | ||
63 | |||
64 | this.startTime = timeToInt(options.startTime) | ||
65 | |||
66 | player.src({ | ||
67 | type: options.type, | ||
68 | src: options.src | ||
69 | }) | ||
70 | |||
71 | player.ready(() => { | ||
72 | this.initializeCore() | ||
73 | |||
74 | if ((videojs as any).Html5Hlsjs) { | ||
75 | this.initializePlugin() | ||
76 | } | ||
77 | }) | ||
78 | } | ||
79 | |||
80 | dispose () { | ||
81 | if (this.hlsjs) this.hlsjs.destroy() | ||
82 | if (this.p2pEngine) this.p2pEngine.destroy() | ||
83 | |||
84 | clearInterval(this.networkInfoInterval) | ||
85 | } | ||
86 | |||
87 | getCurrentLevel () { | ||
88 | return this.hlsjs.levels[this.hlsjs.currentLevel] | ||
89 | } | ||
90 | |||
91 | getLiveLatency () { | ||
92 | return Math.round(this.hlsjs.latency) | ||
93 | } | ||
94 | |||
95 | getHLSJS () { | ||
96 | return this.hlsjs | ||
97 | } | ||
98 | |||
99 | private initializeCore () { | ||
100 | this.player.one('play', () => { | ||
101 | this.player.addClass('vjs-has-big-play-button-clicked') | ||
102 | }) | ||
103 | |||
104 | this.player.one('canplay', () => { | ||
105 | if (this.startTime) { | ||
106 | this.player.currentTime(this.startTime) | ||
107 | } | ||
108 | }) | ||
109 | } | ||
110 | |||
111 | private initializePlugin () { | ||
112 | initHlsJsPlayer(this.hlsjs) | ||
113 | |||
114 | this.p2pEngine = this.options.loader.getEngine() | ||
115 | |||
116 | this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { | ||
117 | console.error('Segment error.', segment, err) | ||
118 | |||
119 | this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl) | ||
120 | }) | ||
121 | |||
122 | this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() | ||
123 | |||
124 | this.runStats() | ||
125 | } | ||
126 | |||
127 | private runStats () { | ||
128 | this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, _segment, bytes: number) => { | ||
129 | const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes | ||
130 | |||
131 | elem.pendingDownload.push(bytes) | ||
132 | elem.totalDownload += bytes | ||
133 | }) | ||
134 | |||
135 | this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => { | ||
136 | const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes | ||
137 | |||
138 | elem.pendingUpload.push(bytes) | ||
139 | elem.totalUpload += bytes | ||
140 | }) | ||
141 | |||
142 | this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) | ||
143 | this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--) | ||
144 | |||
145 | this.networkInfoInterval = setInterval(() => { | ||
146 | const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload) | ||
147 | const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) | ||
148 | |||
149 | const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload) | ||
150 | const httpUploadSpeed = this.arraySum(this.statsHTTPBytes.pendingUpload) | ||
151 | |||
152 | this.statsP2PBytes.pendingDownload = [] | ||
153 | this.statsP2PBytes.pendingUpload = [] | ||
154 | this.statsHTTPBytes.pendingDownload = [] | ||
155 | this.statsHTTPBytes.pendingUpload = [] | ||
156 | |||
157 | return this.player.trigger('p2pInfo', { | ||
158 | source: 'p2p-media-loader', | ||
159 | http: { | ||
160 | downloadSpeed: httpDownloadSpeed, | ||
161 | uploadSpeed: httpUploadSpeed, | ||
162 | downloaded: this.statsHTTPBytes.totalDownload, | ||
163 | uploaded: this.statsHTTPBytes.totalUpload | ||
164 | }, | ||
165 | p2p: { | ||
166 | downloadSpeed: p2pDownloadSpeed, | ||
167 | uploadSpeed: p2pUploadSpeed, | ||
168 | numPeers: this.statsP2PBytes.numPeers, | ||
169 | downloaded: this.statsP2PBytes.totalDownload, | ||
170 | uploaded: this.statsP2PBytes.totalUpload | ||
171 | }, | ||
172 | bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8 | ||
173 | } as PlayerNetworkInfo) | ||
174 | }, this.CONSTANTS.INFO_SCHEDULER) | ||
175 | } | ||
176 | |||
177 | private arraySum (data: number[]) { | ||
178 | return data.reduce((a: number, b: number) => a + b, 0) | ||
179 | } | ||
180 | } | ||
181 | |||
182 | videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin) | ||
183 | export { P2pMediaLoaderPlugin } | ||