diff options
author | Chocobozzz <me@florianbigard.com> | 2022-08-12 16:41:29 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-08-16 10:33:27 +0200 |
commit | fd3c2e87051f5029cdec39d877b576a62f48e219 (patch) | |
tree | a3c657f178702a3363af680ed8ffb7cd038243b8 /client/src/assets | |
parent | 0e6cd1c00f71554fe7375a96db693a6983951ba6 (diff) | |
download | PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.tar.gz PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.tar.zst PeerTube-fd3c2e87051f5029cdec39d877b576a62f48e219.zip |
Add playback metric endpoint sent to OTEL
Diffstat (limited to 'client/src/assets')
11 files changed, 170 insertions, 21 deletions
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' | |||
22 | import './shared/mobile/peertube-mobile-plugin' | 22 | import './shared/mobile/peertube-mobile-plugin' |
23 | import './shared/mobile/peertube-mobile-buttons' | 23 | import './shared/mobile/peertube-mobile-buttons' |
24 | import './shared/hotkeys/peertube-hotkeys-plugin' | 24 | import './shared/hotkeys/peertube-hotkeys-plugin' |
25 | import './shared/metrics/metrics-plugin' | ||
25 | import videojs from 'video.js' | 26 | import videojs from 'video.js' |
26 | import { logger } from '@root-helpers/logger' | 27 | import { logger } from '@root-helpers/logger' |
27 | import { PluginsManager } from '@root-helpers/plugins-manager' | 28 | import { 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 @@ | |||
1 | import videojs from 'video.js' | ||
2 | import { PlaybackMetricCreate } from '../../../../../../shared/models' | ||
3 | import { MetricsPluginOptions, PlayerMode, PlayerNetworkInfo } from '../../types' | ||
4 | |||
5 | const Plugin = videojs.getPlugin('plugin') | ||
6 | |||
7 | class 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 | |||
127 | videojs.registerPlugin('metrics', MetricsPlugin) | ||
128 | export { 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' | |||
2 | import videojs from 'video.js' | 2 | import videojs from 'video.js' |
3 | import { Events, Segment } from '@peertube/p2p-media-loader-core' | 3 | import { Events, Segment } from '@peertube/p2p-media-loader-core' |
4 | import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' | 4 | import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertube/p2p-media-loader-hlsjs' |
5 | import { logger } from '@root-helpers/logger' | ||
5 | import { timeToInt } from '@shared/core-utils' | 6 | import { timeToInt } from '@shared/core-utils' |
6 | import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' | 7 | import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' |
7 | import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' | 8 | import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' |
8 | import { logger } from '@root-helpers/logger' | ||
9 | 9 | ||
10 | registerConfigPlugin(videojs) | 10 | registerConfigPlugin(videojs) |
11 | registerSourceHandler(videojs) | 11 | registerSourceHandler(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 | ||
112 | type MetricsPluginOptions = { | ||
113 | mode: PlayerMode | ||
114 | metricsUrl: string | ||
115 | videoUUID: string | ||
116 | } | ||
117 | |||
112 | type PlaylistPluginOptions = { | 118 | type 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, |