allow limiting video-comments rss feeds to an account or video channel
[oweals/peertube.git] / client / src / assets / player / p2p-media-loader / p2p-media-loader-plugin.ts
1 import videojs from 'video.js'
2 import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
3 import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
4 import { Events, Segment } from 'p2p-media-loader-core'
5 import { timeToInt } from '../utils'
6 import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
7 import * as Hlsjs from 'hls.js/dist/hls.light.js'
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       const message = 'HLS.js does not seem to be supported.'
47       console.warn(message)
48
49       player.ready(() => player.trigger('error', new Error(message)))
50       return
51     }
52
53     // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
54     (videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => {
55       this.hlsjs = hlsjs
56     })
57
58     initVideoJsContribHlsJsPlayer(player)
59
60     this.startTime = timeToInt(options.startTime)
61
62     player.src({
63       type: options.type,
64       src: options.src
65     })
66
67     player.one('play', () => {
68       player.addClass('vjs-has-big-play-button-clicked')
69     })
70
71     player.ready(() => this.initialize())
72   }
73
74   dispose () {
75     if (this.hlsjs) this.hlsjs.destroy()
76     if (this.p2pEngine) this.p2pEngine.destroy()
77
78     clearInterval(this.networkInfoInterval)
79   }
80
81   getHLSJS () {
82     return this.hlsjs
83   }
84
85   private initialize () {
86     initHlsJsPlayer(this.hlsjs)
87
88     // FIXME: typings
89     const options = this.player.tech(true).options_ as any
90     this.p2pEngine = options.hlsjsConfig.loader.getEngine()
91
92     this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHING, (_: any, data: any) => {
93       this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height })
94     })
95
96     this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
97       console.error('Segment error.', segment, err)
98
99       this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
100     })
101
102     this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls()
103
104     this.runStats()
105
106     this.player.one('canplay', () => {
107       if (this.startTime) {
108         this.player.currentTime(this.startTime)
109       }
110     })
111   }
112
113   private runStats () {
114     this.p2pEngine.on(Events.PieceBytesDownloaded, (method: string, size: number) => {
115       const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes
116
117       elem.pendingDownload.push(size)
118       elem.totalDownload += size
119     })
120
121     this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, size: number) => {
122       const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes
123
124       elem.pendingUpload.push(size)
125       elem.totalUpload += size
126     })
127
128     this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
129     this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
130
131     this.networkInfoInterval = setInterval(() => {
132       const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload)
133       const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
134
135       const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload)
136       const httpUploadSpeed = this.arraySum(this.statsHTTPBytes.pendingUpload)
137
138       this.statsP2PBytes.pendingDownload = []
139       this.statsP2PBytes.pendingUpload = []
140       this.statsHTTPBytes.pendingDownload = []
141       this.statsHTTPBytes.pendingUpload = []
142
143       return this.player.trigger('p2pInfo', {
144         http: {
145           downloadSpeed: httpDownloadSpeed,
146           uploadSpeed: httpUploadSpeed,
147           downloaded: this.statsHTTPBytes.totalDownload,
148           uploaded: this.statsHTTPBytes.totalUpload
149         },
150         p2p: {
151           downloadSpeed: p2pDownloadSpeed,
152           uploadSpeed: p2pUploadSpeed,
153           numPeers: this.statsP2PBytes.numPeers,
154           downloaded: this.statsP2PBytes.totalDownload,
155           uploaded: this.statsP2PBytes.totalUpload
156         }
157       } as PlayerNetworkInfo)
158     }, this.CONSTANTS.INFO_SCHEDULER)
159   }
160
161   private arraySum (data: number[]) {
162     return data.reduce((a: number, b: number) => a + b, 0)
163   }
164 }
165
166 videojs.registerPlugin('p2pMediaLoader', P2pMediaLoaderPlugin)
167 export { P2pMediaLoaderPlugin }