aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/shared/metrics/metrics-plugin.ts
blob: 0e296bef6f967da8d190665d06c3b4210e08c672 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import videojs from 'video.js'
import { PlaybackMetricCreate } from '../../../../../../shared/models'
import { MetricsPluginOptions, PlayerMode, PlayerNetworkInfo } from '../../types'

const Plugin = videojs.getPlugin('plugin')

class MetricsPlugin extends Plugin {
  private readonly metricsUrl: string
  private readonly videoUUID: string
  private readonly mode: PlayerMode

  private downloadedBytesP2P = 0
  private downloadedBytesHTTP = 0
  private uploadedBytesP2P = 0

  private resolutionChanges = 0
  private errors = 0

  private lastPlayerNetworkInfo: PlayerNetworkInfo

  private metricsInterval: any

  private readonly CONSTANTS = {
    METRICS_INTERVAL: 15000
  }

  constructor (player: videojs.Player, options: MetricsPluginOptions) {
    super(player)

    this.metricsUrl = options.metricsUrl
    this.videoUUID = options.videoUUID
    this.mode = options.mode

    this.player.one('play', () => {
      this.runMetricsInterval()

      this.trackBytes()
      this.trackResolutionChange()
      this.trackErrors()
    })
  }

  dispose () {
    if (this.metricsInterval) clearInterval(this.metricsInterval)
  }

  private runMetricsInterval () {
    this.metricsInterval = setInterval(() => {
      let resolution: number
      let fps: number

      if (this.mode === 'p2p-media-loader') {
        const level = this.player.p2pMediaLoader().getCurrentLevel()
        if (!level) return

        resolution = Math.min(level.height || 0, level.width || 0)

        const framerate = level?.attrs['FRAME-RATE']
        fps = framerate
          ? parseInt(framerate, 10)
          : undefined
      } else { // webtorrent
        const videoFile = this.player.webtorrent().getCurrentVideoFile()
        if (!videoFile) return

        resolution = videoFile.resolution.id
        fps = videoFile.fps && videoFile.fps !== -1
          ? videoFile.fps
          : undefined
      }

      const body: PlaybackMetricCreate = {
        resolution,
        fps,

        playerMode: this.mode,

        resolutionChanges: this.resolutionChanges,

        errors: this.errors,

        downloadedBytesP2P: this.downloadedBytesP2P,
        downloadedBytesHTTP: this.downloadedBytesHTTP,

        uploadedBytesP2P: this.uploadedBytesP2P,

        videoId: this.videoUUID
      }

      this.resolutionChanges = 0

      this.downloadedBytesP2P = 0
      this.downloadedBytesHTTP = 0

      this.uploadedBytesP2P = 0

      this.errors = 0

      const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })

      return fetch(this.metricsUrl, { method: 'POST', body: JSON.stringify(body), headers })
    }, this.CONSTANTS.METRICS_INTERVAL)
  }

  private trackBytes () {
    this.player.on('p2pInfo', (_event, data: PlayerNetworkInfo) => {
      if (!data) return

      this.downloadedBytesHTTP += data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0)
      this.downloadedBytesP2P += data.p2p.downloaded - (this.lastPlayerNetworkInfo?.p2p.downloaded || 0)

      this.uploadedBytesP2P += data.p2p.uploaded - (this.lastPlayerNetworkInfo?.p2p.uploaded || 0)

      this.lastPlayerNetworkInfo = data
    })
  }

  private trackResolutionChange () {
    this.player.on('engineResolutionChange', () => {
      this.resolutionChanges++
    })
  }

  private trackErrors () {
    this.player.on('error', () => {
      this.errors++
    })
  }
}

videojs.registerPlugin('metrics', MetricsPlugin)
export { MetricsPlugin }