Correctly implement p2p-media-loader
[oweals/peertube.git] / client / src / assets / player / p2p-media-loader-plugin.ts
1 // FIXME: something weird with our path definition in tsconfig and typings
2 // @ts-ignore
3 import * as videojs from 'video.js'
4 import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from './peertube-videojs-typings'
5
6 // videojs-hlsjs-plugin needs videojs in window
7 window['videojs'] = videojs
8 import '@streamroot/videojs-hlsjs-plugin'
9
10 import { Engine, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
11 import * as Hls from 'hls.js'
12 import { Events } from 'p2p-media-loader-core'
13
14 const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
15 class P2pMediaLoaderPlugin extends Plugin {
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
33   constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) {
34     super(player, options)
35
36     videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: Hls) => {
37       this.hlsjs = hlsjs
38
39       this.initialize()
40     })
41
42     initVideoJsContribHlsJsPlayer(player)
43
44     player.src({
45       type: options.type,
46       src: options.src
47     })
48   }
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   }
100 }
101
102 videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin)
103 export { P2pMediaLoaderPlugin }