aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts2
-rw-r--r--client/src/assets/player/peertube-player-manager.ts1
-rw-r--r--client/src/assets/player/shared/control-bar/p2p-info-button.ts4
-rw-r--r--client/src/assets/player/shared/manager-options/manager-options-builder.ts8
-rw-r--r--client/src/assets/player/shared/metrics/index.ts1
-rw-r--r--client/src/assets/player/shared/metrics/metrics-plugin.ts128
-rw-r--r--client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts23
-rw-r--r--client/src/assets/player/shared/peertube/peertube-plugin.ts4
-rw-r--r--client/src/assets/player/shared/stats/stats-card.ts4
-rw-r--r--client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts6
-rw-r--r--client/src/assets/player/types/manager-options.ts2
-rw-r--r--client/src/assets/player/types/peertube-videojs-typings.ts10
-rw-r--r--client/src/standalone/videos/shared/player-manager-options.ts1
13 files changed, 173 insertions, 21 deletions
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index 8d9c08ab3..9ae6f9f12 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -628,6 +628,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
628 : null, 628 : null,
629 authorizationHeader: this.authService.getRequestHeaderValue(), 629 authorizationHeader: this.authService.getRequestHeaderValue(),
630 630
631 metricsUrl: environment.apiUrl + '/api/v1/metrics/playback',
632
631 embedUrl: video.embedUrl, 633 embedUrl: video.embedUrl,
632 embedTitle: video.name, 634 embedTitle: video.name,
633 instanceName: this.serverConfig.instance.name, 635 instanceName: this.serverConfig.instance.name,
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index b9077dcae..0d4acc3d9 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -22,6 +22,7 @@ import './shared/playlist/playlist-plugin'
22import './shared/mobile/peertube-mobile-plugin' 22import './shared/mobile/peertube-mobile-plugin'
23import './shared/mobile/peertube-mobile-buttons' 23import './shared/mobile/peertube-mobile-buttons'
24import './shared/hotkeys/peertube-hotkeys-plugin' 24import './shared/hotkeys/peertube-hotkeys-plugin'
25import './shared/metrics/metrics-plugin'
25import videojs from 'video.js' 26import videojs from 'video.js'
26import { logger } from '@root-helpers/logger' 27import { logger } from '@root-helpers/logger'
27import { PluginsManager } from '@root-helpers/plugins-manager' 28import { PluginsManager } from '@root-helpers/plugins-manager'
diff --git a/client/src/assets/player/shared/control-bar/p2p-info-button.ts b/client/src/assets/player/shared/control-bar/p2p-info-button.ts
index 36517e125..1979654ad 100644
--- a/client/src/assets/player/shared/control-bar/p2p-info-button.ts
+++ b/client/src/assets/player/shared/control-bar/p2p-info-button.ts
@@ -87,9 +87,9 @@ class P2pInfoButton extends Button {
87 const httpStats = data.http 87 const httpStats = data.http
88 88
89 const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed) 89 const downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed)
90 const uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed) 90 const uploadSpeed = bytes(p2pStats.uploadSpeed)
91 const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded) 91 const totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded)
92 const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded) 92 const totalUploaded = bytes(p2pStats.uploaded)
93 const numPeers = p2pStats.numPeers 93 const numPeers = p2pStats.numPeers
94 94
95 subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' 95 subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n'
diff --git a/client/src/assets/player/shared/manager-options/manager-options-builder.ts b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
index bc70bb12f..07678493d 100644
--- a/client/src/assets/player/shared/manager-options/manager-options-builder.ts
+++ b/client/src/assets/player/shared/manager-options/manager-options-builder.ts
@@ -44,6 +44,14 @@ export class ManagerOptionsBuilder {
44 'isLive', 44 'isLive',
45 'videoUUID' 45 'videoUUID'
46 ]) 46 ])
47 },
48 metrics: {
49 mode: this.mode,
50
51 ...pick(commonOptions, [
52 'metricsUrl',
53 'videoUUID'
54 ])
47 } 55 }
48 } 56 }
49 57
diff --git a/client/src/assets/player/shared/metrics/index.ts b/client/src/assets/player/shared/metrics/index.ts
new file mode 100644
index 000000000..85d75cdc7
--- /dev/null
+++ b/client/src/assets/player/shared/metrics/index.ts
@@ -0,0 +1 @@
export * from './metrics-plugin'
diff --git a/client/src/assets/player/shared/metrics/metrics-plugin.ts b/client/src/assets/player/shared/metrics/metrics-plugin.ts
new file mode 100644
index 000000000..1b2349eba
--- /dev/null
+++ b/client/src/assets/player/shared/metrics/metrics-plugin.ts
@@ -0,0 +1,128 @@
1import videojs from 'video.js'
2import { PlaybackMetricCreate } from '../../../../../../shared/models'
3import { MetricsPluginOptions, PlayerMode, PlayerNetworkInfo } from '../../types'
4
5const Plugin = videojs.getPlugin('plugin')
6
7class MetricsPlugin extends Plugin {
8 private readonly metricsUrl: string
9 private readonly videoUUID: string
10 private readonly mode: PlayerMode
11
12 private downloadedBytesP2P = 0
13 private downloadedBytesHTTP = 0
14 private uploadedBytesP2P = 0
15
16 private resolutionChanges = 0
17 private errors = 0
18
19 private lastPlayerNetworkInfo: PlayerNetworkInfo
20
21 private metricsInterval: any
22
23 private readonly CONSTANTS = {
24 METRICS_INTERVAL: 15000
25 }
26
27 constructor (player: videojs.Player, options: MetricsPluginOptions) {
28 super(player)
29
30 this.metricsUrl = options.metricsUrl
31 this.videoUUID = options.videoUUID
32 this.mode = options.mode
33
34 this.player.one('play', () => {
35 this.runMetricsInterval()
36
37 this.trackBytes()
38 this.trackResolutionChange()
39 this.trackErrors()
40 })
41 }
42
43 dispose () {
44 if (this.metricsInterval) clearInterval(this.metricsInterval)
45 }
46
47 private runMetricsInterval () {
48 this.metricsInterval = setInterval(() => {
49 let resolution: number
50 let fps: number
51
52 if (this.mode === 'p2p-media-loader') {
53 const level = this.player.p2pMediaLoader().getCurrentLevel()
54 if (!level) return
55
56 resolution = Math.min(level.height || 0, level.width || 0)
57
58 const framerate = level?.attrs['FRAME-RATE']
59 fps = framerate
60 ? parseInt(framerate, 10)
61 : undefined
62 } else { // webtorrent
63 const videoFile = this.player.webtorrent().getCurrentVideoFile()
64 if (!videoFile) return
65
66 resolution = videoFile.resolution.id
67 fps = videoFile.fps
68 }
69
70 const body: PlaybackMetricCreate = {
71 resolution,
72 fps,
73
74 playerMode: this.mode,
75
76 resolutionChanges: this.resolutionChanges,
77
78 errors: this.errors,
79
80 downloadedBytesP2P: this.downloadedBytesP2P,
81 downloadedBytesHTTP: this.downloadedBytesHTTP,
82
83 uploadedBytesP2P: this.uploadedBytesP2P,
84
85 videoId: this.videoUUID
86 }
87
88 this.resolutionChanges = 0
89
90 this.downloadedBytesP2P = 0
91 this.downloadedBytesHTTP = 0
92
93 this.uploadedBytesP2P = 0
94
95 this.errors = 0
96
97 const headers = new Headers({ 'Content-type': 'application/json; charset=UTF-8' })
98
99 return fetch(this.metricsUrl, { method: 'POST', body: JSON.stringify(body), headers })
100 }, this.CONSTANTS.METRICS_INTERVAL)
101 }
102
103 private trackBytes () {
104 this.player.on('p2pInfo', (_event, data: PlayerNetworkInfo) => {
105 this.downloadedBytesHTTP += data.http.downloaded - (this.lastPlayerNetworkInfo?.http.downloaded || 0)
106 this.downloadedBytesP2P += data.p2p.downloaded - (this.lastPlayerNetworkInfo?.p2p.downloaded || 0)
107
108 this.uploadedBytesP2P += data.p2p.uploaded - (this.lastPlayerNetworkInfo?.p2p.uploaded || 0)
109
110 this.lastPlayerNetworkInfo = data
111 })
112 }
113
114 private trackResolutionChange () {
115 this.player.on('engineResolutionChange', () => {
116 this.resolutionChanges++
117 })
118 }
119
120 private trackErrors () {
121 this.player.on('error', () => {
122 this.errors++
123 })
124 }
125}
126
127videojs.registerPlugin('metrics', MetricsPlugin)
128export { MetricsPlugin }
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
index e5f099dea..54d87aea5 100644
--- 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
@@ -2,10 +2,10 @@ import Hlsjs from 'hls.js'
2import videojs from 'video.js' 2import videojs from 'video.js'
3import { Events, Segment } from '@peertube/p2p-media-loader-core' 3import { Events, Segment } from '@peertube/p2p-media-loader-core'
4import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' 4import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs'
5import { logger } from '@root-helpers/logger'
5import { timeToInt } from '@shared/core-utils' 6import { timeToInt } from '@shared/core-utils'
6import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' 7import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types'
7import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' 8import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
8import { logger } from '@root-helpers/logger'
9 9
10registerConfigPlugin(videojs) 10registerConfigPlugin(videojs)
11registerSourceHandler(videojs) 11registerSourceHandler(videojs)
@@ -29,9 +29,7 @@ class P2pMediaLoaderPlugin extends Plugin {
29 } 29 }
30 private statsHTTPBytes = { 30 private statsHTTPBytes = {
31 pendingDownload: [] as number[], 31 pendingDownload: [] as number[],
32 pendingUpload: [] as number[], 32 totalDownload: 0
33 totalDownload: 0,
34 totalUpload: 0
35 } 33 }
36 private startTime: number 34 private startTime: number
37 35
@@ -123,6 +121,8 @@ class P2pMediaLoaderPlugin extends Plugin {
123 this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls() 121 this.statsP2PBytes.numPeers = 1 + this.options.redundancyUrlManager.countBaseUrls()
124 122
125 this.runStats() 123 this.runStats()
124
125 this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHED, () => this.player.trigger('engineResolutionChange'))
126 } 126 }
127 127
128 private runStats () { 128 private runStats () {
@@ -134,10 +134,13 @@ class P2pMediaLoaderPlugin extends Plugin {
134 }) 134 })
135 135
136 this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => { 136 this.p2pEngine.on(Events.PieceBytesUploaded, (method: string, _segment, bytes: number) => {
137 const elem = method === 'p2p' ? this.statsP2PBytes : this.statsHTTPBytes 137 if (method !== 'p2p') {
138 logger.error(`Received upload from unknown method ${method}`)
139 return
140 }
138 141
139 elem.pendingUpload.push(bytes) 142 this.statsP2PBytes.pendingUpload.push(bytes)
140 elem.totalUpload += bytes 143 this.statsP2PBytes.totalUpload += bytes
141 }) 144 })
142 145
143 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) 146 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
@@ -148,20 +151,16 @@ class P2pMediaLoaderPlugin extends Plugin {
148 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) 151 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
149 152
150 const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload) 153 const httpDownloadSpeed = this.arraySum(this.statsHTTPBytes.pendingDownload)
151 const httpUploadSpeed = this.arraySum(this.statsHTTPBytes.pendingUpload)
152 154
153 this.statsP2PBytes.pendingDownload = [] 155 this.statsP2PBytes.pendingDownload = []
154 this.statsP2PBytes.pendingUpload = [] 156 this.statsP2PBytes.pendingUpload = []
155 this.statsHTTPBytes.pendingDownload = [] 157 this.statsHTTPBytes.pendingDownload = []
156 this.statsHTTPBytes.pendingUpload = []
157 158
158 return this.player.trigger('p2pInfo', { 159 return this.player.trigger('p2pInfo', {
159 source: 'p2p-media-loader', 160 source: 'p2p-media-loader',
160 http: { 161 http: {
161 downloadSpeed: httpDownloadSpeed, 162 downloadSpeed: httpDownloadSpeed,
162 uploadSpeed: httpUploadSpeed, 163 downloaded: this.statsHTTPBytes.totalDownload
163 downloaded: this.statsHTTPBytes.totalDownload,
164 uploaded: this.statsHTTPBytes.totalUpload
165 }, 164 },
166 p2p: { 165 p2p: {
167 downloadSpeed: p2pDownloadSpeed, 166 downloadSpeed: p2pDownloadSpeed,
diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts
index 69a7b2d65..83c32415e 100644
--- a/client/src/assets/player/shared/peertube/peertube-plugin.ts
+++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts
@@ -144,6 +144,8 @@ class PeerTubePlugin extends Plugin {
144 this.listenFullScreenChange() 144 this.listenFullScreenChange()
145 } 145 }
146 146
147 // ---------------------------------------------------------------------------
148
147 private runUserViewing () { 149 private runUserViewing () {
148 let lastCurrentTime = this.startTime 150 let lastCurrentTime = this.startTime
149 let lastViewEvent: VideoViewEvent 151 let lastViewEvent: VideoViewEvent
@@ -205,6 +207,8 @@ class PeerTubePlugin extends Plugin {
205 return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers }) 207 return fetch(this.videoViewUrl, { method: 'POST', body: JSON.stringify(body), headers })
206 } 208 }
207 209
210 // ---------------------------------------------------------------------------
211
208 private listenFullScreenChange () { 212 private listenFullScreenChange () {
209 this.player.on('fullscreenchange', () => { 213 this.player.on('fullscreenchange', () => {
210 if (this.player.isFullscreen()) this.player.focus() 214 if (this.player.isFullscreen()) this.player.focus()
diff --git a/client/src/assets/player/shared/stats/stats-card.ts b/client/src/assets/player/shared/stats/stats-card.ts
index b65adcfca..1199d3285 100644
--- a/client/src/assets/player/shared/stats/stats-card.ts
+++ b/client/src/assets/player/shared/stats/stats-card.ts
@@ -95,9 +95,9 @@ class StatsCard extends Component {
95 const httpStats = data.http 95 const httpStats = data.http
96 96
97 this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ') 97 this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ')
98 this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ') 98 this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed).join(' ')
99 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ') 99 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
100 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ') 100 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded).join(' ')
101 this.playerNetworkInfo.numPeers = p2pStats.numPeers 101 this.playerNetworkInfo.numPeers = p2pStats.numPeers
102 this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s' 102 this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
103 103
diff --git a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts
index 9fd5f593e..fa3f48a9a 100644
--- a/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts
@@ -204,6 +204,8 @@ class WebTorrentPlugin extends Plugin {
204 } 204 }
205 205
206 this.updateVideoFile(newVideoFile, options) 206 this.updateVideoFile(newVideoFile, options)
207
208 this.player.trigger('engineResolutionChange')
207 } 209 }
208 210
209 flushVideoFile (videoFile: VideoFile, destroyRenderer = true) { 211 flushVideoFile (videoFile: VideoFile, destroyRenderer = true) {
@@ -506,9 +508,7 @@ class WebTorrentPlugin extends Plugin {
506 source: 'webtorrent', 508 source: 'webtorrent',
507 http: { 509 http: {
508 downloadSpeed: 0, 510 downloadSpeed: 0,
509 uploadSpeed: 0, 511 downloaded: 0
510 downloaded: 0,
511 uploaded: 0
512 }, 512 },
513 p2p: { 513 p2p: {
514 downloadSpeed: this.torrent.downloadSpeed, 514 downloadSpeed: this.torrent.downloadSpeed,
diff --git a/client/src/assets/player/types/manager-options.ts b/client/src/assets/player/types/manager-options.ts
index cadce739d..b4d9374c3 100644
--- a/client/src/assets/player/types/manager-options.ts
+++ b/client/src/assets/player/types/manager-options.ts
@@ -59,6 +59,8 @@ export interface CommonOptions extends CustomizationOptions {
59 videoViewUrl: string 59 videoViewUrl: string
60 authorizationHeader?: string 60 authorizationHeader?: string
61 61
62 metricsUrl: string
63
62 embedUrl: string 64 embedUrl: string
63 embedTitle: string 65 embedTitle: string
64 66
diff --git a/client/src/assets/player/types/peertube-videojs-typings.ts b/client/src/assets/player/types/peertube-videojs-typings.ts
index 115afb614..6df94992c 100644
--- a/client/src/assets/player/types/peertube-videojs-typings.ts
+++ b/client/src/assets/player/types/peertube-videojs-typings.ts
@@ -109,6 +109,12 @@ type PeerTubePluginOptions = {
109 videoUUID: string 109 videoUUID: string
110} 110}
111 111
112type MetricsPluginOptions = {
113 mode: PlayerMode
114 metricsUrl: string
115 videoUUID: string
116}
117
112type PlaylistPluginOptions = { 118type PlaylistPluginOptions = {
113 elements: VideoPlaylistElement[] 119 elements: VideoPlaylistElement[]
114 120
@@ -165,6 +171,7 @@ type VideoJSPluginOptions = {
165 playlist?: PlaylistPluginOptions 171 playlist?: PlaylistPluginOptions
166 172
167 peertube: PeerTubePluginOptions 173 peertube: PeerTubePluginOptions
174 metrics: MetricsPluginOptions
168 175
169 webtorrent?: WebtorrentPluginOptions 176 webtorrent?: WebtorrentPluginOptions
170 177
@@ -197,9 +204,7 @@ type PlayerNetworkInfo = {
197 204
198 http: { 205 http: {
199 downloadSpeed: number 206 downloadSpeed: number
200 uploadSpeed: number
201 downloaded: number 207 downloaded: number
202 uploaded: number
203 } 208 }
204 209
205 p2p: { 210 p2p: {
@@ -227,6 +232,7 @@ export {
227 ResolutionUpdateData, 232 ResolutionUpdateData,
228 AutoResolutionUpdateData, 233 AutoResolutionUpdateData,
229 PlaylistPluginOptions, 234 PlaylistPluginOptions,
235 MetricsPluginOptions,
230 VideoJSCaption, 236 VideoJSCaption,
231 PeerTubePluginOptions, 237 PeerTubePluginOptions,
232 WebtorrentPluginOptions, 238 WebtorrentPluginOptions,
diff --git a/client/src/standalone/videos/shared/player-manager-options.ts b/client/src/standalone/videos/shared/player-manager-options.ts
index 9cebdcd10..eed821994 100644
--- a/client/src/standalone/videos/shared/player-manager-options.ts
+++ b/client/src/standalone/videos/shared/player-manager-options.ts
@@ -203,6 +203,7 @@ export class PlayerManagerOptions {
203 videoCaptions, 203 videoCaptions,
204 inactivityTimeout: 2500, 204 inactivityTimeout: 2500,
205 videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid), 205 videoViewUrl: this.videoFetcher.getVideoViewsUrl(video.uuid),
206 metricsUrl: window.location.origin + '/api/v1/metrics/playback',
206 207
207 videoShortUUID: video.shortUUID, 208 videoShortUUID: video.shortUUID,
208 videoUUID: video.uuid, 209 videoUUID: video.uuid,