@gcorevideo/player 2.22.17 → 2.22.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/assets/clappr-nerd-stats/clappr-nerd-stats.ejs +76 -78
  2. package/assets/clappr-nerd-stats/clappr-nerd-stats.scss +10 -7
  3. package/dist/core.js +5 -7
  4. package/dist/index.css +1350 -1349
  5. package/dist/index.js +234 -89
  6. package/dist/plugins/index.css +666 -665
  7. package/dist/plugins/index.js +231 -83
  8. package/lib/playback/dash-playback/DashPlayback.d.ts +0 -1
  9. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  10. package/lib/playback/dash-playback/DashPlayback.js +4 -5
  11. package/lib/playback/hls-playback/HlsPlayback.d.ts +1 -1
  12. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  13. package/lib/playback/hls-playback/HlsPlayback.js +0 -1
  14. package/lib/playback.types.d.ts +2 -3
  15. package/lib/playback.types.d.ts.map +1 -1
  16. package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts +13 -10
  17. package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts.map +1 -1
  18. package/lib/plugins/clappr-nerd-stats/NerdStats.js +171 -120
  19. package/lib/plugins/clappr-nerd-stats/formatter.d.ts +5 -0
  20. package/lib/plugins/clappr-nerd-stats/formatter.d.ts.map +1 -1
  21. package/lib/plugins/clappr-nerd-stats/formatter.js +56 -24
  22. package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts +2 -2
  23. package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts.map +1 -1
  24. package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts +1 -1
  25. package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts.map +1 -1
  26. package/lib/plugins/clappr-nerd-stats/types.d.ts +3 -0
  27. package/lib/plugins/clappr-nerd-stats/types.d.ts.map +1 -1
  28. package/lib/plugins/clappr-nerd-stats/utils.d.ts +7 -0
  29. package/lib/plugins/clappr-nerd-stats/utils.d.ts.map +1 -0
  30. package/lib/plugins/clappr-nerd-stats/utils.js +67 -0
  31. package/lib/plugins/clappr-stats/types.d.ts +0 -1
  32. package/lib/plugins/clappr-stats/types.d.ts.map +1 -1
  33. package/lib/plugins/clappr-stats/utils.d.ts +1 -1
  34. package/lib/plugins/clappr-stats/utils.d.ts.map +1 -1
  35. package/lib/plugins/clappr-stats/utils.js +0 -1
  36. package/lib/plugins/seek-time/SeekTime.d.ts +1 -1
  37. package/lib/plugins/seek-time/SeekTime.d.ts.map +1 -1
  38. package/lib/plugins/seek-time/SeekTime.js +3 -4
  39. package/package.json +1 -1
  40. package/src/playback/dash-playback/DashPlayback.ts +5 -7
  41. package/src/playback/hls-playback/HlsPlayback.ts +2 -4
  42. package/src/playback.types.ts +2 -3
  43. package/src/plugins/clappr-nerd-stats/NerdStats.ts +212 -139
  44. package/src/plugins/clappr-nerd-stats/formatter.ts +91 -47
  45. package/src/plugins/clappr-nerd-stats/speedtest/index.ts +2 -2
  46. package/src/plugins/clappr-nerd-stats/speedtest/types.ts +1 -1
  47. package/src/plugins/clappr-nerd-stats/types.ts +43 -3
  48. package/src/plugins/clappr-nerd-stats/utils.ts +75 -0
  49. package/src/plugins/clappr-stats/types.ts +39 -40
  50. package/src/plugins/clappr-stats/utils.ts +3 -4
  51. package/src/plugins/seek-time/SeekTime.ts +4 -5
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -9943,7 +9943,7 @@ const volumeOffIcon = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fil
9943
9943
 
9944
9944
  const pluginHtml$6 = "<div class=\"big-mute-icon-wrapper\" data-big-mute>\n <div class=\"big-mute-icon gcore-skin-border-color\" data-big-mute-icon></div>\n</div>\n";
9945
9945
 
9946
- const T$d = 'plugins.big_mute_button';
9946
+ const T$e = 'plugins.big_mute_button';
9947
9947
  // TODO rewrite as a container plugin
9948
9948
  /**
9949
9949
  * `PLUGIN` that displays a big mute button over the video when it's muted.
@@ -9984,7 +9984,7 @@ class BigMuteButton extends UICorePlugin {
9984
9984
  this.listenTo(this.core, Events.CORE_READY, this.onCoreReady);
9985
9985
  this.listenTo(this.core, 'core:advertisement:start', this.onStartAd);
9986
9986
  this.listenTo(this.core, 'core:advertisement:finish', this.onFinishAd);
9987
- trace(`${T$d} bindEvents`, {
9987
+ trace(`${T$e} bindEvents`, {
9988
9988
  mediacontrol: !!this.core.mediaControl,
9989
9989
  });
9990
9990
  this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this.mediaControlRendered);
@@ -10041,7 +10041,7 @@ class BigMuteButton extends UICorePlugin {
10041
10041
  */
10042
10042
  render() {
10043
10043
  if (this.shouldRender()) {
10044
- trace(`${T$d} render`, {
10044
+ trace(`${T$e} render`, {
10045
10045
  el: !!this.$el,
10046
10046
  });
10047
10047
  this.$el.html(BigMuteButton.template());
@@ -10390,7 +10390,6 @@ function newMetrics$1() {
10390
10390
  duration: 0,
10391
10391
  currentTime: 0,
10392
10392
  },
10393
- custom: {},
10394
10393
  };
10395
10394
  }
10396
10395
 
@@ -12088,63 +12087,72 @@ const timeScale = new humanFormat.Scale({
12088
12087
  ms: 1,
12089
12088
  sec: 1000,
12090
12089
  min: 60000,
12091
- hours: 3600000
12090
+ hours: 3600000,
12092
12091
  });
12093
12092
  const percentScale = new humanFormat.Scale({
12094
- '%': 1
12093
+ '%': 1,
12095
12094
  });
12095
+ const metricTemplates = {
12096
+ fps: {
12097
+ scale: 'SI',
12098
+ decimals: 0,
12099
+ },
12100
+ volume: {
12101
+ scale: percentScale,
12102
+ },
12103
+ };
12096
12104
  const formattingTemplate = {
12097
12105
  general: {
12098
12106
  volume: {
12099
- scale: percentScale
12100
- }
12107
+ scale: percentScale,
12108
+ },
12101
12109
  },
12102
12110
  timers: {
12103
12111
  startup: {
12104
- scale: timeScale
12112
+ scale: timeScale,
12105
12113
  },
12106
12114
  watch: {
12107
- scale: timeScale
12115
+ scale: timeScale,
12108
12116
  },
12109
12117
  pause: {
12110
- scale: timeScale
12118
+ scale: timeScale,
12111
12119
  },
12112
12120
  buffering: {
12113
- scale: timeScale
12121
+ scale: timeScale,
12114
12122
  },
12115
12123
  session: {
12116
- scale: timeScale
12124
+ scale: timeScale,
12117
12125
  },
12118
12126
  latency: {
12119
- scale: timeScale
12120
- }
12127
+ scale: timeScale,
12128
+ },
12121
12129
  },
12122
12130
  extra: {
12123
12131
  buffersize: {
12124
- scale: timeScale
12132
+ scale: timeScale,
12125
12133
  },
12126
12134
  duration: {
12127
- scale: timeScale
12135
+ scale: timeScale,
12128
12136
  },
12129
12137
  currentTime: {
12130
- scale: timeScale
12138
+ scale: timeScale,
12131
12139
  },
12132
12140
  bitrateWeightedMean: {
12133
- unit: 'bps'
12141
+ unit: 'bps',
12134
12142
  },
12135
12143
  bitrateMostUsed: {
12136
- unit: 'bps'
12144
+ unit: 'bps',
12137
12145
  },
12138
12146
  bandwidth: {
12139
- unit: 'bps'
12147
+ unit: 'bps',
12140
12148
  },
12141
12149
  watchedPercentage: {
12142
- scale: percentScale
12150
+ scale: percentScale,
12143
12151
  },
12144
12152
  bufferingPercentage: {
12145
- scale: percentScale
12146
- }
12147
- }
12153
+ scale: percentScale,
12154
+ },
12155
+ },
12148
12156
  };
12149
12157
  class Formatter {
12150
12158
  static format(metrics) {
@@ -12154,8 +12162,10 @@ class Formatter {
12154
12162
  formattedMetrics[type] = fmt;
12155
12163
  const typeTemplate = formattingTemplate[type];
12156
12164
  Object.entries(mm).forEach(([name, value]) => {
12157
- // const value = mm[name];
12158
- if (typeTemplate && typeTemplate[name] && (typeof value === 'number') && !isNaN(value)) {
12165
+ if (typeTemplate &&
12166
+ typeTemplate[name] &&
12167
+ typeof value === 'number' &&
12168
+ !isNaN(value)) {
12159
12169
  // @ts-ignore
12160
12170
  const templateScale = typeTemplate[name].scale || 'SI';
12161
12171
  // @ts-ignore
@@ -12163,7 +12173,7 @@ class Formatter {
12163
12173
  fmt[name] = humanFormat(value, {
12164
12174
  scale: templateScale,
12165
12175
  unit: templateUnit,
12166
- decimals: 2
12176
+ decimals: 2,
12167
12177
  });
12168
12178
  }
12169
12179
  else {
@@ -12173,6 +12183,27 @@ class Formatter {
12173
12183
  });
12174
12184
  return formattedMetrics;
12175
12185
  }
12186
+ static formatVolume(volume) {
12187
+ return humanFormat(volume, metricTemplates.volume);
12188
+ }
12189
+ static formatTime(time) {
12190
+ return humanFormat(time, {
12191
+ scale: timeScale,
12192
+ });
12193
+ }
12194
+ static formatFps(fps) {
12195
+ return humanFormat(fps, metricTemplates.fps);
12196
+ }
12197
+ static formatPercentage(percentage) {
12198
+ return humanFormat(percentage, {
12199
+ scale: percentScale,
12200
+ });
12201
+ }
12202
+ static formatBitrate(bitrate) {
12203
+ return humanFormat(bitrate, {
12204
+ unit: 'bps',
12205
+ });
12206
+ }
12176
12207
  }
12177
12208
 
12178
12209
  const SpeedtestWorkerModule = "// data reported to main thread\n\n// -1=not started, 0=starting, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort\nlet testState = -1;\n// download speed in megabit/s with 2 decimal digits\nlet dlStatus = 0;\n// upload speed in megabit/s with 2 decimal digits\nlet ulStatus = '';\n// ping in milliseconds with 2 decimal digits\nlet pingStatus = '';\n// jitter in milliseconds with 2 decimal digits\nlet jitterStatus = '';\n// client's IP address as reported by getIP.php\nlet clientIp = '';\nlet serverHostName = '';\n//progress of download test 0-1\nlet dlProgress = 0;\n//progress of upload test 0-1\nlet ulProgress = 0;\n//progress of ping+jitter test 0-1\nlet pingProgress = 0;\n//test ID (sent back by telemetry if used, null otherwise)\nlet testId = null;\n\nlet log = ''; //telemetry log\n\nfunction tlog(s) {\n if (settings.telemetry_level >= 2) {\n log += Date.now() + ': ' + s + '\\n';\n }\n}\n\nfunction tverb(s) {\n if (settings.telemetry_level >= 3) {\n log += Date.now() + ': ' + s + '\\n';\n }\n}\n\nfunction twarn(s) {\n if (settings.telemetry_level >= 2) {\n log += Date.now() + ' WARN: ' + s + '\\n';\n }\n\n console.warn(s);\n}\n\n// test settings. can be overridden by sending specific values with the start command\nconst settings = {\n //set to true when in MPOT mode\n mpot: false,\n //order in which tests will be performed as a string. D=Download, U=Upload, P=Ping+Jitter, I=IP, _=1 second delay\n test_order: 'P_D',\n // max duration of upload test in seconds\n time_ul_max: 0,\n // max duration of download test in seconds\n time_dl_max: 15,\n // if set to true, tests will take less time on faster connections\n time_auto: true,\n //time to wait in seconds before actually measuring ul speed (wait for buffers to fill)\n time_ulGraceTime: 3,\n //time to wait in seconds before actually measuring dl speed (wait for TCP window to increase)\n time_dlGraceTime: 1.5,\n // number of pings to perform in ping test\n count_ping: 10,\n // path to a large file or garbage.php, used for download test. must be relative to this js file\n url_dl: 'backend/garbage.php',\n // path to an empty file, used for upload test. must be relative to this js file\n url_ul: 'backend/empty.php',\n // path to an empty file, used for ping test. must be relative to this js file\n url_ping: 'backend/empty.php',\n // path to getIP.php relative to this js file, or a similar thing that outputs the client's ip\n url_getIp: 'backend/getIP.php',\n // if set to true, the server will include ISP info with the IP address\n getIp_ispInfo: true,\n // km or mi=estimate distance from server in km/mi; set to false to disable distance estimation.\n // getIp_ispInfo must be enabled in order for this to work\n getIp_ispInfo_distance: false,\n // number of download streams to use (can be different if enable_quirks is active)\n xhr_dlMultistream: 6,\n // number of upload streams to use (can be different if enable_quirks is active)\n xhr_ulMultistream: 3,\n // how much concurrent requests should be delayed\n xhr_multistreamDelay: 300,\n // 0=fail on errors, 1=attempt to restart a stream if it fails, 2=ignore all errors\n xhr_ignoreErrors: 1,\n // if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize\n // and/or high xhr_dlMultistream)\n xhr_dlUseBlob: false,\n // size in megabytes of the upload blobs sent in the upload test (forced to 4 on chrome mobile)\n xhr_ul_blob_megabytes: 20,\n // size of chunks sent by garbage.php (can be different if enable_quirks is active)\n garbagePhp_chunkSize: 100,\n // enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers,\n // unless they are already being overridden with the start command\n enable_quirks: true,\n // if enabled, the ping test will attempt to calculate the ping more precisely using the Performance API.\n // Currently works perfectly in Chrome, badly in Edge, and not at all in Firefox.\n // If Performance API is not supported or the result is obviously wrong, a fallback is provided.\n ping_allowPerformanceApi: true,\n // can be changed to compensatie for transport overhead. (see doc.md for some other values)\n overheadCompensationFactor: 1.06,\n //if set to true, speed will be reported in mebibits/s instead of megabits/s\n useMebibits: false,\n // 0=disabled, 1=basic (results only), 2=full (results and timing) 3=debug (results+log)\n telemetry_level: 0,\n // path to the script that adds telemetry data to the database\n url_telemetry: 'results/telemetry.php',\n //extra data that can be passed to the telemetry through the settings\n telemetry_extra: ''\n};\n\nlet xhr = null; // array of currently active xhr requests\nlet interval = null; // timer used in tests\nlet test_pointer = 0; //pointer to the next test to run inside settings.test_order\n\n/*\n this function is used on URLs passed in the settings to determine whether we need a ? or an & as a separator\n*/\nfunction url_sep(url) {\n return url.match(/\\?/) ? '&' : '?';\n}\n\n/*\n listener for commands from main thread to this worker.\n commands:\n -status: returns the current status as a JSON string containing testState,\n dlStatus, ulStatus, pingStatus, clientIp, jitterStatus, dlProgress, ulProgress, pingProgress\n -abort: aborts the current test\n -start: starts the test. optionally, settings can be passed as JSON.\n example: start {\"time_ul_max\":\"10\", \"time_dl_max\":\"10\", \"count_ping\":\"50\"}\n*/\nself.addEventListener('message', function (e) {\n const params = e.data.split(' ');\n\n if (params[0] === 'status') {\n // return status\n postMessage(\n {\n testState: testState,\n dlStatus: dlStatus,\n ulStatus: ulStatus,\n pingStatus: pingStatus,\n clientIp: clientIp,\n serverHostName: serverHostName,\n jitterStatus: jitterStatus,\n dlProgress: dlProgress,\n ulProgress: ulProgress,\n pingProgress: pingProgress,\n testId: testId\n }\n );\n }\n if (params[0] === 'start' && testState === -1) {\n const ua = navigator.userAgent;\n\n // start new test\n testState = 0;\n try {\n // parse settings, if present\n let s = {};\n\n try {\n const ss = e.data.substring(5);\n\n if (ss) {\n s = JSON.parse(ss);\n }\n } catch (e) {\n twarn('Error parsing custom settings JSON. Please check your syntax');\n }\n //copy custom settings\n for (const key in s) {\n if (typeof settings[key] !== 'undefined') {\n settings[key] = s[key];\n } else {\n twarn('Unknown setting ignored: ' + key);\n }\n }\n // quirks for specific browsers. apply only if not overridden. more may be added in future releases\n if (settings.enable_quirks || (typeof s.enable_quirks !== 'undefined' && s.enable_quirks)) {\n if (/Firefox.(\\d+\\.\\d+)/i.test(ua)) {\n if (typeof s.ping_allowPerformanceApi === 'undefined') {\n // ff performance API sucks\n settings.ping_allowPerformanceApi = false;\n }\n }\n if (/Edge.(\\d+\\.\\d+)/i.test(ua)) {\n if (typeof s.xhr_dlMultistream === 'undefined') {\n // edge more precise with 3 download streams\n settings.xhr_dlMultistream = 3;\n }\n }\n if (/Chrome.(\\d+)/i.test(ua) && !!self.fetch) {\n if (typeof s.xhr_dlMultistream === 'undefined') {\n // chrome more precise with 5 streams\n settings.xhr_dlMultistream = 5;\n }\n }\n }\n if (/Edge.(\\d+\\.\\d+)/i.test(ua)) {\n //Edge 15 introduced a bug that causes onprogress events to not get fired,\n // we have to use the \"small chunks\" workaround that reduces accuracy\n settings.forceIE11Workaround = true;\n }\n if (/PlayStation 4.(\\d+\\.\\d+)/i.test(ua)) {\n //PS4 browser has the same bug as IE11/Edge\n settings.forceIE11Workaround = true;\n }\n if (/Chrome.(\\d+)/i.test(ua) && /Android|iPhone|iPad|iPod|Windows Phone/i.test(ua)) {\n // cheap af\n // Chrome mobile introduced a limitation somewhere around version 65,\n // we have to limit XHR upload size to 4 megabytes\n settings.xhr_ul_blob_megabytes = 4;\n }\n if (/^((?!chrome|android|crios|fxios).)*safari/i.test(ua)) {\n //Safari also needs the IE11 workaround but only for the MPOT version\n settings.forceIE11Workaround = true;\n }\n // telemetry_level has to be parsed and not just copied\n if (typeof s.telemetry_level !== 'undefined') {\n const telemetryLevels = {\n 'basic': 1,\n 'full': 2,\n 'debug': 3\n };\n\n settings.telemetry_level = telemetryLevels[s.telemetry_level] || 0;\n } // telemetry level\n // transform test_order to uppercase, just in case\n settings.test_order = settings.test_order.toUpperCase();\n } catch (e) {\n twarn('Possible error in custom test settings. Some settings might not have been applied. Exception: ' + e);\n }\n // run the tests\n tverb(JSON.stringify(settings));\n test_pointer = 0;\n let iRun = false,\n dRun = false,\n // uRun = false,\n pRun = false;\n // eslint-disable-next-line no-var\n var runNextTest = function () {\n if (testState === 5) {\n return;\n }\n if (test_pointer >= settings.test_order.length) {\n //test is finished\n if (settings.telemetry_level > 0) {\n sendTelemetry(function (id) {\n testState = 4;\n if (id !== null || id !== undefined) {\n testId = id;\n }\n });\n } else {\n testState = 4;\n }\n\n return;\n }\n switch (settings.test_order.charAt(test_pointer)) {\n case 'I': {\n test_pointer++;\n if (iRun) {\n runNextTest();\n\n return;\n } else {\n iRun = true;\n }\n getIp(runNextTest);\n }\n break;\n case 'D': {\n test_pointer++;\n if (dRun) {\n runNextTest();\n\n return;\n } else {\n dRun = true;\n }\n testState = 1;\n dlTest(runNextTest);\n }\n break;\n case 'U': {\n // test_pointer++;\n // if (uRun) {\n // runNextTest();\n // return;\n // } else uRun = true;\n // testState = 3;\n // ulTest(runNextTest);\n }\n break;\n case 'P': {\n test_pointer++;\n if (pRun) {\n runNextTest();\n\n return;\n } else {\n pRun = true;\n }\n testState = 2;\n pingTest(runNextTest);\n }\n break;\n case '_': {\n test_pointer++;\n setTimeout(runNextTest, 1000);\n }\n break;\n default:\n test_pointer++;\n }\n };\n\n runNextTest();\n }\n if (params[0] === 'abort') {\n // abort command\n if (testState >= 4) {\n return;\n }\n tlog('manually aborted');\n clearRequests(); // stop all xhr activity\n runNextTest = null;\n if (interval) {\n clearInterval(interval);\n } // clear timer if present\n if (settings.telemetry_level > 1) {\n sendTelemetry(function () {\n });\n }\n testState = 5; //set test as aborted\n dlStatus = 0;\n ulStatus = '';\n pingStatus = '';\n jitterStatus = '';\n clientIp = '';\n serverHostName = '';\n dlProgress = 0;\n ulProgress = 0;\n pingProgress = 0;\n }\n});\n\n// stops all XHR activity, aggressively\nfunction clearRequests() {\n tverb('stopping pending XHRs');\n if (xhr) {\n for (let i = 0; i < xhr.length; i++) {\n try {\n xhr[i].onprogress = null;\n xhr[i].onload = null;\n xhr[i].onerror = null;\n } catch (e) {\n console.warn(e);\n }\n try {\n xhr[i].upload.onprogress = null;\n xhr[i].upload.onload = null;\n xhr[i].upload.onerror = null;\n } catch (e) {\n console.warn(e);\n }\n try {\n xhr[i].abort();\n } catch (e) {\n console.warn(e);\n }\n try {\n delete xhr[i];\n } catch (e) {\n console.warn(e);\n }\n }\n xhr = null;\n }\n}\n\n// gets client's IP using url_getIp, then calls the done function\nlet ipCalled = false; // used to prevent multiple accidental calls to getIp\nlet ispInfo = ''; //used for telemetry\n\nfunction getIp(done) {\n tverb('getIp');\n if (ipCalled) {\n return;\n } else {\n ipCalled = true;\n } // getIp already called?\n const startT = new Date().getTime();\n\n xhr = new XMLHttpRequest();\n xhr.onload = function () {\n tlog('IP: ' + xhr.responseText + ', took ' + (new Date().getTime() - startT) + 'ms');\n try {\n const data = JSON.parse(xhr.responseText);\n\n clientIp = data.processedString;\n serverHostName = data.serverHostName;\n ispInfo = data.rawIspInfo;\n } catch (e) {\n clientIp = xhr.responseText;\n ispInfo = '';\n }\n done();\n };\n xhr.onerror = function () {\n tlog('getIp failed, took ' + (new Date().getTime() - startT) + 'ms');\n done();\n };\n const queryParams = [\n settings.mpot ? 'cors=true' : '',\n settings.getIp_ispInfo ?\n `isp=true${settings.getIp_ispInfo_distance ? '&distance=' + settings.getIp_ispInfo_distance : ''}` :\n '',\n 'r=' + Math.random()\n ].filter(Boolean).join('&');\n\n const url = `${settings.url_getIp}${url_sep(settings.url_getIp)}${queryParams}`;\n\n xhr.open(\n 'GET',\n url,\n true\n );\n xhr.send();\n}\n\n// download test, calls done function when it's over\nlet dlCalled = false; // used to prevent multiple accidental calls to dlTest\n\nfunction dlTest(done) {\n tverb('dlTest');\n if (dlCalled) {\n return;\n } else {\n dlCalled = true;\n } // dlTest already called?\n let totLoaded = 0.0, // total number of loaded bytes\n startT = new Date().getTime(), // timestamp when test was started\n bonusT = 0, //how many milliseconds the test has been shortened by (higher on faster connections)\n graceTimeDone = false, //set to true after the grace time is past\n failed = false; // set to true if a stream fails\n\n xhr = [];\n // function to create a download stream. streams are slightly delayed so that they will not end at the same time\n const testStream = function (i, delay) {\n setTimeout(\n function () {\n if (testState !== 1) {\n return;\n } // delayed stream ended up starting after the end of the download test\n tverb('dl test stream started ' + i + ' ' + delay);\n let prevLoaded = 0; // number of bytes loaded last time onprogress was called\n const x = new XMLHttpRequest();\n\n xhr[i] = x;\n xhr[i].onprogress = function (event) {\n tverb('dl stream progress event ' + i + ' ' + event.loaded);\n if (testState !== 1) {\n try {\n x.abort();\n } catch (e) {\n console.warn(e);\n }\n } // just in case this XHR is still running after the download test\n // progress event, add number of new loaded bytes to totLoaded\n const loadDiff = event.loaded <= 0 ? 0 : event.loaded - prevLoaded;\n\n if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) {\n return;\n } // just in case\n totLoaded += loadDiff;\n prevLoaded = event.loaded;\n }.bind(this);\n xhr[i].onload = function () {\n // the large file has been loaded entirely, start again\n tverb('dl stream finished ' + i);\n try {\n xhr[i].abort();\n } catch (e) {\n console.warn(e);\n } // reset the stream data to empty ram\n testStream(i, 0);\n }.bind(this);\n xhr[i].onerror = function () {\n // error\n tverb('dl stream failed ' + i);\n if (settings.xhr_ignoreErrors === 0) {\n failed = true;\n } //abort\n try {\n xhr[i].abort();\n } catch (e) {\n console.warn(e);\n }\n delete xhr[i];\n if (settings.xhr_ignoreErrors === 1) {\n testStream(i, 0);\n } //restart stream\n }.bind(this);\n // send xhr\n try {\n if (settings.xhr_dlUseBlob) {\n xhr[i].responseType = 'blob';\n } else {\n xhr[i].responseType = 'arraybuffer';\n }\n } catch (e) {\n console.warn(e);\n }\n\n const queryParams = [\n settings.mpot ? 'cors=true' : '',\n 'r=' + Math.random(),\n 'ckSize=' + settings.garbagePhp_chunkSize\n ].join('&');\n\n const url = `${settings.url_dl}${url_sep(settings.url_dl)}${queryParams}`;\n\n // random string to prevent caching\n xhr[i].open('GET', url, true);\n xhr[i].send();\n }.bind(this),\n 1 + delay\n );\n }.bind(this);\n\n // open streams\n for (let i = 0; i < settings.xhr_dlMultistream; i++) {\n testStream(i, settings.xhr_multistreamDelay * i);\n }\n // every 200ms, update dlStatus\n interval = setInterval(\n function () {\n tverb('DL: ' + dlStatus + (graceTimeDone ? '' : ' (in grace time)'));\n const t = new Date().getTime() - startT;\n\n if (graceTimeDone) {\n dlProgress = (t + bonusT) / (settings.time_dl_max * 1000);\n }\n if (t < 200) {\n return;\n }\n if (!graceTimeDone) {\n if (t > 1000 * settings.time_dlGraceTime) {\n if (totLoaded > 0) {\n // if the connection is so slow that we didn't get a single chunk yet, do not reset\n startT = new Date().getTime();\n bonusT = 0;\n totLoaded = 0.0;\n }\n graceTimeDone = true;\n }\n } else {\n const speed = totLoaded / (t / 1000.0);\n\n if (settings.time_auto) {\n //decide how much to shorten the test. Every 200ms, the test is shortened by the bonusT calculated here\n const bonus = (6.4 * speed) / 100000;\n\n bonusT += bonus > 800 ? 800 : bonus;\n }\n // update status\n // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied,\n // then everything is divided by 1048576 or 1000000 to go to megabits/mebibits\n dlStatus = ((speed * 8 * settings.overheadCompensationFactor) / (settings.useMebibits ? 1048576 : 1000000));\n if ((t + bonusT) / 1000.0 > settings.time_dl_max || failed) {\n // test is over, stop streams and timer\n if (failed || isNaN(dlStatus)) {\n dlStatus = 'Fail';\n }\n clearRequests();\n clearInterval(interval);\n dlProgress = 1;\n tlog('dlTest: ' + dlStatus + ', took ' + (new Date().getTime() - startT) + 'ms');\n done();\n }\n }\n }.bind(this),\n 200\n );\n}\n\n// ping+jitter test, function done is called when it's over\nlet ptCalled = false; // used to prevent multiple accidental calls to pingTest\n\nfunction pingTest(done) {\n tverb('pingTest');\n if (ptCalled) {\n return;\n } else {\n ptCalled = true;\n } // pingTest already called?\n const startT = new Date().getTime(); //when the test was started\n let prevT = null; // last time a pong was received\n let ping = 0.0; // current ping value\n let jitter = 0.0; // current jitter value\n let i = 0; // counter of pongs received\n let prevInstspd = 0; // last ping time, used for jitter calculation\n\n xhr = [];\n // ping function\n const doPing = function () {\n tverb('ping');\n pingProgress = i / settings.count_ping;\n prevT = new Date().getTime();\n xhr[0] = new XMLHttpRequest();\n xhr[0].onload = function () {\n // pong\n tverb('pong');\n if (i === 0) {\n prevT = new Date().getTime(); // first pong\n } else {\n let instspd = new Date().getTime() - prevT;\n\n if (settings.ping_allowPerformanceApi) {\n try {\n //try to get accurate performance timing using performance api\n let p = performance.getEntries();\n\n p = p[p.length - 1];\n let d = p.responseStart - p.requestStart;\n\n if (d <= 0) {\n d = p.duration;\n }\n if (d > 0 && d < instspd) {\n instspd = d;\n }\n } catch (e) {\n //if not possible, keep the estimate\n tverb('Performance API not supported, using estimate');\n }\n }\n //noticed that some browsers randomly have 0ms ping\n if (instspd < 1) {\n instspd = prevInstspd;\n }\n if (instspd < 1) {\n instspd = 1;\n }\n const instjitter = Math.abs(instspd - prevInstspd);\n\n if (i === 1) {\n ping = instspd;\n }/* first ping, can't tell jitter yet*/ else {\n if (instspd < ping) {\n ping = instspd;\n } // update ping, if the instant ping is lower\n if (i === 2) {\n jitter = instjitter;\n } else {\n //discard the first jitter measurement because it might be much higher than it should be\n jitter = instjitter > jitter ? jitter * 0.3 + instjitter * 0.7 : jitter * 0.8 + instjitter * 0.2;\n } // update jitter, weighted average. spikes in ping values are given more weight.\n }\n prevInstspd = instspd;\n }\n pingStatus = ping.toFixed(2);\n jitterStatus = jitter.toFixed(2);\n i++;\n tverb('ping: ' + pingStatus + ' jitter: ' + jitterStatus);\n if (i < settings.count_ping) {\n doPing();\n } else {\n // more pings to do?\n pingProgress = 1;\n tlog('ping: ' + pingStatus + ' jitter: ' + jitterStatus + ', took ' + (new Date().getTime() - startT) + 'ms');\n done();\n }\n }.bind(this);\n xhr[0].onerror = function () {\n // a ping failed, cancel test\n tverb('ping failed');\n if (settings.xhr_ignoreErrors === 0) {\n //abort\n pingStatus = 'Fail';\n jitterStatus = 'Fail';\n clearRequests();\n tlog('ping test failed, took ' + (new Date().getTime() - startT) + 'ms');\n pingProgress = 1;\n done();\n }\n if (settings.xhr_ignoreErrors === 1) {\n doPing();\n } //retry ping\n if (settings.xhr_ignoreErrors === 2) {\n //ignore failed ping\n i++;\n if (i < settings.count_ping) {\n doPing();\n } else {\n // more pings to do?\n pingProgress = 1;\n tlog('ping: ' + pingStatus + ' jitter: ' + jitterStatus + ', took ' + (new Date().getTime() - startT) + 'ms');\n done();\n }\n }\n }.bind(this);\n // send xhr\n const queryString = [\n settings.mpot ? 'cors=true' : '',\n `r=${Math.random()}`\n ].filter(part => part !== '').join('&');\n\n const url = `${settings.url_ping}${url_sep(settings.url_ping)}${queryString}`;\n\n // random string to prevent caching\n xhr[0].open('GET', url, true);\n xhr[0].send();\n }.bind(this);\n\n doPing(); // start first ping\n}\n\n// telemetry\nfunction sendTelemetry(done) {\n if (settings.telemetry_level < 1) {\n return;\n }\n xhr = new XMLHttpRequest();\n xhr.onload = function () {\n try {\n const parts = xhr.responseText.split(' ');\n\n if (parts[0] === 'id') {\n try {\n const id = parts[1];\n\n done(id);\n } catch (e) {\n done(null);\n }\n } else {\n done(null);\n }\n } catch (e) {\n done(null);\n }\n };\n xhr.onerror = function () {\n console.warn('TELEMETRY ERROR ' + xhr.status);\n done(null);\n };\n xhr.open('POST', settings.url_telemetry + url_sep(settings.url_telemetry) + (settings.mpot ? 'cors=true&' : '') + 'r=' + Math.random(), true);\n const telemetryIspInfo = {\n processedString: clientIp,\n serverHostName: serverHostName,\n rawIspInfo: typeof ispInfo === 'object' ? ispInfo : ''\n };\n\n try {\n const fd = new FormData();\n\n fd.append('ispinfo', JSON.stringify(telemetryIspInfo));\n fd.append('dl', dlStatus);\n fd.append('ul', ulStatus);\n fd.append('ping', pingStatus);\n fd.append('jitter', jitterStatus);\n fd.append('log', settings.telemetry_level > 1 ? log : '');\n fd.append('extra', settings.telemetry_extra);\n xhr.send(fd);\n } catch (ex) {\n const postData = 'extra=' + encodeURIComponent(settings.telemetry_extra) + '&ispinfo=' + encodeURIComponent(JSON.stringify(telemetryIspInfo)) + '&dl=' + encodeURIComponent(dlStatus) + '&ul=' + encodeURIComponent(ulStatus) + '&ping=' + encodeURIComponent(pingStatus) + '&jitter=' + encodeURIComponent(jitterStatus) + '&log=' + encodeURIComponent(settings.telemetry_level > 1 ? log : '');\n\n xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n xhr.send(postData);\n }\n}\n";
@@ -12655,7 +12686,7 @@ function rankConnectionSpeed(dlSpeed) {
12655
12686
  return 0;
12656
12687
  }
12657
12688
 
12658
- const pluginHtml$4 = "<% general = metrics.general %>\n<% counters = metrics.counters %>\n<% timers = metrics.timers %>\n<% extra = metrics.extra %>\n<% custom = metrics.custom %>\n\n<div class=\"stats-box\">\n <div class=\"stats-box-top\">\n <a class=\"close-button gplayer-lite-btn\" data-close-button>\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <g clip-path=\"url(#clip0_184_1489)\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M7.41376 6.00013L13.7068 -0.292872C14.0978 -0.683872 14.0978 -1.31587 13.7068 -1.70687C13.3158 -2.09787 12.6838 -2.09787 12.2928 -1.70687L5.99976 4.58613L-0.293238 -1.70687C-0.684238 -2.09787 -1.31624 -2.09787 -1.70724 -1.70687C-2.09824 -1.31587 -2.09824 -0.683872 -1.70724 -0.292872L4.58576 6.00013L-1.70724 12.2931C-2.09824 12.6841 -2.09824 13.3161 -1.70724 13.7071C-1.51224 13.9021 -1.25624 14.0001 -1.00024 14.0001C-0.744238 14.0001 -0.488238 13.9021 -0.293238 13.7071L5.99976 7.41413L12.2928 13.7071C12.4878 13.9021 12.7438 14.0001 12.9998 14.0001C13.2558 14.0001 13.5118 13.9021 13.7068 13.7071C14.0978 13.3161 14.0978 12.6841 13.7068 12.2931L7.41376 6.00013Z\"\n fill=\"white\"/>\n </g>\n <defs>\n <clipPath id=\"clip0_184_1489\">\n <rect width=\"12\" height=\"12\" fill=\"white\"/>\n </clipPath>\n </defs>\n </svg>\n </a>\n </div>\n <div class=\"stats-box-main\">\n <ul>\n <li class=\"title\"><span>General</span></li>\n <li>\n Display resolution:\n <div><span><%= general.displayResolution %></span></div>\n </li>\n <li>\n Volume:\n <div><span><%= general.volume %></span></div>\n </li>\n <li>\n Connection speed:\n <div><span id=\"dlText\"><%= custom.connectionSpeed %></span> Mbps</div>\n </li>\n <li class=\"canvas-wrapper\">\n <canvas id=\"speedTestCanvas\" width=\"190\" height=\"20\"></canvas>\n </li>\n <li>\n Ping:\n <div><span id=\"pingText\"><%= custom.ping %></span> ms</div>\n </li>\n <li>\n Jitter:\n <div><span id=\"jitterText\"><%= custom.jitter %></span> ms</div>\n </li>\n </ul>\n\n <ul>\n <li class=\"title\"><span>Counters</span></li>\n <li>\n Plays:\n <div><span><%= counters.play %></span></div>\n </li>\n <li>\n Pauses:\n <div><span><%= counters.pause %></span></div>\n </li>\n <li>\n Errors:\n <div><span><%= counters.error %></span></div>\n </li>\n <li>\n Bufferings:\n <div><span><%= counters.buffering %></span></div>\n </li>\n <li>\n Decoded frames:\n <div><span><%= counters.decodedFrames %></span></div>\n </li>\n <li>\n Dropped frames:\n <div><span><%= counters.droppedFrames %></span></div>\n </li>\n <li>\n Frames per second:\n <div><span><%= counters.fps %></span></div>\n </li>\n <li>\n Bitrate changes:\n <div><span><%= counters.changeLevel %></span></div>\n </li>\n <li>\n Seeks:\n <div><span><%= counters.seek %></span></div>\n </li>\n <li>\n Fullscreen:\n <div><span><%= counters.fullscreen %></span></div>\n </li>\n <li>\n DVR seeks:\n <div><span><%= counters.dvrUsage %></span></div>\n </li>\n </ul>\n\n <ul>\n <li class=\"title\"><span>Timers</span></li>\n <li>\n Startup time:\n <div><span><%= timers.startup %></span></div>\n </li>\n <li>\n Watching time:\n <div><span><%= timers.watch %></span></div>\n </li>\n <li>\n Pause time:\n <div><span><%= timers.pause %></span></div>\n </li>\n <li>\n Buffering time:\n <div><span><%= timers.buffering %></span></div>\n </li>\n <li>\n Session time:\n <div><span><%= timers.session %></span></div>\n </li>\n <!-- <li>-->\n <!-- Latency:-->\n <!-- <div><span><%= timers.latency %></span></div>-->\n <!-- </li>-->\n </ul>\n\n <ul>\n <li class=\"title\"><span>Extra</span></li>\n <li>\n Playback:\n <div><span><%= extra.playbackName %></span></div>\n </li>\n <li>\n Playback type:\n <div><span><%= extra.playbackType %></span></div>\n </li>\n <li>\n Buffer size:\n <div><span><%= extra.buffersize %></span></div>\n </li>\n <li>\n Video duration:\n <div><span><%= extra.duration %></span></div>\n </li>\n <li>\n Current time:\n <div><span><%= extra.currentTime %></span></div>\n </li>\n <li>\n Bitrate weighted mean:\n <div><span><%= extra.bitrateWeightedMean %></span></div>\n </li>\n <li>\n Bitrate most used:\n <div><span><%= extra.bitrateMostUsed %></span></div>\n </li>\n <li>\n % Watched:\n <div><span><%= extra.watchedPercentage %></span></div>\n </li>\n <li>\n % Buffering:\n <div><span><%= extra.bufferingPercentage %></span></div>\n </li>\n </ul>\n </div>\n <div class=\"speedtest-summary\">\n <div class=\"speedtest-summary-header\">Your internet quality summary</div>\n <div class=\"speedtest-summary-block\">\n <div class=\"speedtest-summary-subblock\">\n <div class=\"speedtest-summary-subblock-content\">\n <div class=\"speedtest-quality\">\n <div class=\"speedtest-quality-header\">VOD: <%= custom.vodQuality %></div>\n <div class=\"speedtest-quality-content\" data-streaming-type=\"vod\">\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"speedtest-summary-subblock\">\n <div class=\"speedtest-summary-subblock-content\">\n <div class=\"speedtest-quality\">\n <div class=\"speedtest-quality-header\">Live: <%= custom.liveQuality %></div>\n <div class=\"speedtest-quality-content\" data-streaming-type=\"live\">\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"speedtest-footer\">\n <!-- <a class=\"speedtest-footer-about-link\" href=\"\" target=\"_blank\">I am not a nerd, what's this all about?</a>-->\n <button class=\"gplayer-lite-btn speedtest-btn speedtest-footer-refresh\" data-refresh-button type=\"button\">\n <svg width=\"12\" height=\"10\" viewBox=\"0 0 12 10\" fill=\"none\">\n <path\n d=\"M6.03968 0.124998C3.64268 0.124998 1.67268 1.9565 1.48068 4.2915H1.00018C0.925833 4.29146 0.853156 4.31353 0.791378 4.35489C0.729601 4.39625 0.681511 4.45503 0.653218 4.52378C0.624925 4.59253 0.617705 4.66814 0.632476 4.74101C0.647248 4.81387 0.683343 4.88069 0.736177 4.933L1.57618 5.766C1.64641 5.83561 1.74129 5.87467 1.84018 5.87467C1.93906 5.87467 2.03395 5.83561 2.10418 5.766L2.94418 4.933C2.99701 4.88069 3.03311 4.81387 3.04788 4.74101C3.06265 4.66814 3.05543 4.59253 3.02714 4.52378C2.99884 4.45503 2.95075 4.39625 2.88898 4.35489C2.8272 4.31353 2.75452 4.29146 2.68018 4.2915H2.23368C2.42368 2.376 4.05268 0.874998 6.03968 0.874998C6.6948 0.873639 7.33932 1.04039 7.91158 1.35931C8.48384 1.67822 8.9647 2.13863 9.30818 2.6965C9.33331 2.73978 9.36686 2.7776 9.40684 2.80771C9.44682 2.83783 9.49243 2.85963 9.54097 2.87184C9.58951 2.88405 9.64001 2.88643 9.68948 2.87881C9.73895 2.8712 9.7864 2.85377 9.82902 2.82753C9.87165 2.80129 9.90859 2.76679 9.93767 2.72605C9.96675 2.68531 9.98739 2.63916 9.99835 2.59032C10.0093 2.54148 10.0104 2.49095 10.0015 2.44168C9.99264 2.39242 9.974 2.34544 9.94668 2.3035C9.53615 1.63664 8.96146 1.08621 8.27752 0.704805C7.59359 0.323402 6.82277 0.123774 6.03968 0.124998ZM10.4207 4.2335C10.3505 4.16419 10.2558 4.12532 10.1572 4.12532C10.0585 4.12532 9.96386 4.16419 9.89368 4.2335L9.05018 5.0665C8.9972 5.11874 8.96096 5.18557 8.94608 5.25847C8.93119 5.33137 8.93833 5.40705 8.96658 5.47588C8.99483 5.54472 9.04292 5.60359 9.10473 5.64501C9.16654 5.68644 9.23927 5.70853 9.31368 5.7085H9.76318C9.57218 7.6235 7.93768 9.125 5.94118 9.125C5.28399 9.12683 4.63729 8.96035 4.06269 8.64141C3.48808 8.32247 3.00473 7.86169 2.65868 7.303C2.63281 7.26107 2.59893 7.22465 2.55899 7.19582C2.51904 7.16699 2.47381 7.14631 2.42587 7.13495C2.37793 7.1236 2.32823 7.1218 2.27959 7.12966C2.23096 7.13752 2.18435 7.15488 2.14243 7.18075C2.05776 7.233 1.99731 7.31674 1.97438 7.41355C1.95146 7.51037 1.96793 7.61233 2.02018 7.697C2.43345 8.36457 3.01076 8.91521 3.69713 9.29647C4.38349 9.67772 5.15604 9.87689 5.94118 9.875C8.34518 9.875 10.3237 8.045 10.5162 5.7085H11.0002C11.0746 5.70853 11.1473 5.68644 11.2091 5.64501C11.2709 5.60359 11.319 5.54472 11.3473 5.47588C11.3755 5.40705 11.3827 5.33137 11.3678 5.25847C11.3529 5.18557 11.3167 5.11874 11.2637 5.0665L10.4207 4.2335Z\"\n fill=\"white\"/>\n </svg>\n Refresh\n </button>\n </div>\n</div>\n";
12689
+ const pluginHtml$4 = "<% general = metrics.general %>\n<% counters = metrics.counters %>\n<% timers = metrics.chrono %>\n<% extra = metrics.extra %>\n<% custom = metrics.custom %>\n\n<div class=\"stats-box\" id=\"nerd-stats-box\">\n <div class=\"stats-box-top\">\n <a class=\"close-button gplayer-lite-btn\" id=\"nerd-stats-close\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <g clip-path=\"url(#clip0_184_1489)\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n d=\"M7.41376 6.00013L13.7068 -0.292872C14.0978 -0.683872 14.0978 -1.31587 13.7068 -1.70687C13.3158 -2.09787 12.6838 -2.09787 12.2928 -1.70687L5.99976 4.58613L-0.293238 -1.70687C-0.684238 -2.09787 -1.31624 -2.09787 -1.70724 -1.70687C-2.09824 -1.31587 -2.09824 -0.683872 -1.70724 -0.292872L4.58576 6.00013L-1.70724 12.2931C-2.09824 12.6841 -2.09824 13.3161 -1.70724 13.7071C-1.51224 13.9021 -1.25624 14.0001 -1.00024 14.0001C-0.744238 14.0001 -0.488238 13.9021 -0.293238 13.7071L5.99976 7.41413L12.2928 13.7071C12.4878 13.9021 12.7438 14.0001 12.9998 14.0001C13.2558 14.0001 13.5118 13.9021 13.7068 13.7071C14.0978 13.3161 14.0978 12.6841 13.7068 12.2931L7.41376 6.00013Z\"\n fill=\"white\"/>\n </g>\n <defs>\n <clipPath id=\"clip0_184_1489\">\n <rect width=\"12\" height=\"12\" fill=\"white\"/>\n </clipPath>\n </defs>\n </svg>\n </a>\n </div>\n <div class=\"stats-box-main\">\n <ul>\n <li class=\"title\"><span><%= i18n.t('stats.general') %></span></li>\n <li>\n <%= i18n.t('stats.display_resolution') %>\n <div><span><span id=\"nerd-stats-resolution-width\"><%= general.resolution.width %></span>&times;<span id=\"nerd-stats-resolution-height\"><%= general.resolution.height %></span></span></div>\n </li>\n <li>\n <%= i18n.t('stats.volume') %>\n <div id=\"nerd-stats-volume\"><span><%= general.volume %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.connection_speed') %>\n <div><span id=\"dlText\"><%= custom.connectionSpeed %></span> <%= i18n.t('mbps') %></div>\n </li>\n <li class=\"canvas-wrapper\">\n <canvas id=\"speedTestCanvas\" width=\"190\" height=\"20\"></canvas>\n </li>\n <li>\n <%= i18n.t('stats.ping') %>\n <div><span id=\"pingText\"><%= custom.ping %></span> <%= i18n.t('ms') %></div>\n </li>\n <li>\n <%= i18n.t('stats.jitter') %>\n <div><span id=\"jitterText\"><%= custom.jitter %></span> <%= i18n.t('ms') %></div>\n </li>\n </ul>\n\n <ul>\n <li class=\"title\"><span><%= i18n.t('stats.counters') %></span></li>\n <li>\n <%= i18n.t('stats.plays') %>\n <div><span id=\"nerd-stats-plays\"><%= counters.play %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.pauses') %>\n <div><span id=\"nerd-stats-pauses\"><%= counters.pause %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.errors') %>\n <div><span id=\"nerd-stats-errors\"><%= counters.error %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.bufferings') %>\n <div><span id=\"nerd-stats-bufferings\"><%= counters.buffering %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.decoded_frames') %>\n <div><span id=\"nerd-stats-decoded-frames\"><%= counters.decodedFrames %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.dropped_frames') %>\n <div><span id=\"nerd-stats-dropped-frames\"><%= counters.droppedFrames %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.fps') %>\n <div><span id=\"nerd-stats-fps\"><%= counters.fps %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.bitrate_changes') %>\n <div><span id=\"nerd-stats-bitrate-changes\"><%= counters.changeLevel %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.seeks') %>\n <div><span id=\"nerd-stats-seeks\"><%= counters.seek %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.fullscreen') %>\n <div><span id=\"nerd-stats-fullscreen\"><%= counters.fullscreen %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.dvr_seeks') %>\n <div><span id=\"nerd-stats-dvr-usage\"><%= counters.dvrUsage %></span></div>\n </li>\n </ul>\n\n <ul>\n <li class=\"title\"><span><%= i18n.t('stats.duration') %></span></li>\n <li>\n <%= i18n.t('stats.startup') %>\n <div><span id=\"nerd-stats-startup-time\"><%= timers.startup %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.watching') %>\n <div><span id=\"nerd-stats-watch-time\"><%= timers.watch %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.paused') %>\n <div><span id=\"nerd-stats-pause-time\"><%= timers.pause %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.buffering') %>\n <div><span id=\"nerd-stats-buffering-time\"><%= timers.buffering %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.session') %>\n <div><span id=\"nerd-stats-session-time\"><%= timers.session %></span></div>\n </li>\n </ul>\n\n <ul>\n <li class=\"title\"><span><%= i18n.t('stats.extra') %></span></li>\n <li>\n <%= i18n.t('stats.playback') %>\n <div><span id=\"nerd-stats-playback-name\"><%= extra.playbackName %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.playback_type') %>\n <div><span id=\"nerd-stats-playback-type\"><%= extra.playbackType %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.buffer_size') %>\n <div><span id=\"nerd-stats-buffer-size\"><%= extra.buffersize %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.video_duration') %>\n <div><span id=\"nerd-stats-video-duration\"><%= extra.duration %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.current_time') %>\n <div><span id=\"nerd-stats-current-time\"><%= extra.currentTime %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.bitrate_weighted_mean') %>\n <div><span id=\"nerd-stats-bitrate-weighted-mean\"><%= extra.bitrateWeightedMean %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.bitrate_most_used') %>\n <div><span id=\"nerd-stats-bitrate-most-used\"><%= extra.bitrateMostUsed %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.watched_percentage') %>\n <div><span id=\"nerd-stats-watched-percentage\"><%= extra.watchedPercentage %></span></div>\n </li>\n <li>\n <%= i18n.t('stats.buffering_percentage') %>\n <div><span id=\"nerd-stats-buffering-percentage\"><%= extra.bufferingPercentage %></span></div>\n </li>\n </ul>\n </div>\n <div class=\"speedtest-summary\">\n <div class=\"speedtest-summary-header\"><%= i18n.t('stats.your_internet_quality_summary') %>:</div>\n <div class=\"speedtest-summary-block\">\n <div class=\"speedtest-summary-subblock\">\n <div class=\"speedtest-summary-subblock-content\">\n <div class=\"speedtest-quality\">\n <div class=\"speedtest-quality-header\"><%= i18n.t('vod') %>: \n <span id=\"nerd-stats-quality-vod-text\"><%= custom.vodQuality %></span></div>\n <div class=\"speedtest-quality-content\" data-streaming-type=\"vod\" id=\"nerd-stats-quality-vod\">\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"speedtest-summary-subblock\">\n <div class=\"speedtest-summary-subblock-content\">\n <div class=\"speedtest-quality\">\n <div class=\"speedtest-quality-header\"><%= i18n.t('live') %>: \n <span id=\"nerd-stats-quality-live-text\"><%= custom.liveQuality %></span></div>\n <div class=\"speedtest-quality-content\" data-streaming-type=\"live\" id=\"nerd-stats-quality-live\">\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n <div class=\"speedtest-quality-content-item\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"speedtest-footer\">\n <!-- <a class=\"speedtest-footer-about-link\" href=\"\" target=\"_blank\">I am not a nerd, what's this all about?</a>-->\n <button class=\"gplayer-lite-btn speedtest-btn speedtest-footer-refresh\" type=\"button\" id=\"nerd-stats-refresh\">\n <svg width=\"12\" height=\"10\" viewBox=\"0 0 12 10\" fill=\"none\">\n <path\n d=\"M6.03968 0.124998C3.64268 0.124998 1.67268 1.9565 1.48068 4.2915H1.00018C0.925833 4.29146 0.853156 4.31353 0.791378 4.35489C0.729601 4.39625 0.681511 4.45503 0.653218 4.52378C0.624925 4.59253 0.617705 4.66814 0.632476 4.74101C0.647248 4.81387 0.683343 4.88069 0.736177 4.933L1.57618 5.766C1.64641 5.83561 1.74129 5.87467 1.84018 5.87467C1.93906 5.87467 2.03395 5.83561 2.10418 5.766L2.94418 4.933C2.99701 4.88069 3.03311 4.81387 3.04788 4.74101C3.06265 4.66814 3.05543 4.59253 3.02714 4.52378C2.99884 4.45503 2.95075 4.39625 2.88898 4.35489C2.8272 4.31353 2.75452 4.29146 2.68018 4.2915H2.23368C2.42368 2.376 4.05268 0.874998 6.03968 0.874998C6.6948 0.873639 7.33932 1.04039 7.91158 1.35931C8.48384 1.67822 8.9647 2.13863 9.30818 2.6965C9.33331 2.73978 9.36686 2.7776 9.40684 2.80771C9.44682 2.83783 9.49243 2.85963 9.54097 2.87184C9.58951 2.88405 9.64001 2.88643 9.68948 2.87881C9.73895 2.8712 9.7864 2.85377 9.82902 2.82753C9.87165 2.80129 9.90859 2.76679 9.93767 2.72605C9.96675 2.68531 9.98739 2.63916 9.99835 2.59032C10.0093 2.54148 10.0104 2.49095 10.0015 2.44168C9.99264 2.39242 9.974 2.34544 9.94668 2.3035C9.53615 1.63664 8.96146 1.08621 8.27752 0.704805C7.59359 0.323402 6.82277 0.123774 6.03968 0.124998ZM10.4207 4.2335C10.3505 4.16419 10.2558 4.12532 10.1572 4.12532C10.0585 4.12532 9.96386 4.16419 9.89368 4.2335L9.05018 5.0665C8.9972 5.11874 8.96096 5.18557 8.94608 5.25847C8.93119 5.33137 8.93833 5.40705 8.96658 5.47588C8.99483 5.54472 9.04292 5.60359 9.10473 5.64501C9.16654 5.68644 9.23927 5.70853 9.31368 5.7085H9.76318C9.57218 7.6235 7.93768 9.125 5.94118 9.125C5.28399 9.12683 4.63729 8.96035 4.06269 8.64141C3.48808 8.32247 3.00473 7.86169 2.65868 7.303C2.63281 7.26107 2.59893 7.22465 2.55899 7.19582C2.51904 7.16699 2.47381 7.14631 2.42587 7.13495C2.37793 7.1236 2.32823 7.1218 2.27959 7.12966C2.23096 7.13752 2.18435 7.15488 2.14243 7.18075C2.05776 7.233 1.99731 7.31674 1.97438 7.41355C1.95146 7.51037 1.96793 7.61233 2.02018 7.697C2.43345 8.36457 3.01076 8.91521 3.69713 9.29647C4.38349 9.67772 5.15604 9.87689 5.94118 9.875C8.34518 9.875 10.3237 8.045 10.5162 5.7085H11.0002C11.0746 5.70853 11.1473 5.68644 11.2091 5.64501C11.2709 5.60359 11.319 5.54472 11.3473 5.47588C11.3755 5.40705 11.3827 5.33137 11.3678 5.25847C11.3529 5.18557 11.3167 5.11874 11.2637 5.0665L10.4207 4.2335Z\"\n fill=\"white\"/>\n </svg>\n <%= i18n.t('stats.refresh') %>\n </button>\n </div>\n</div>\n";
12659
12690
 
12660
12691
  const buttonHtml$3 = "<button class='nerd-button gplayer-lite-btn gcore-skin-text-color gear-option' id=\"nerd-stats-button\">\n <span class=\"gear-option_icon\"><%= icon %></span>\n <span class=\"gear-option_label\"><%= i18n.t('statistics') %></span>\n</button>\n";
12661
12692
 
@@ -12728,6 +12759,13 @@ const drawSummary = (customMetrics, vodContainer, liveContainer) => {
12728
12759
  vodContainer.html(vodHtml);
12729
12760
  liveContainer.html(liveHtml);
12730
12761
  };
12762
+
12763
+ const PLAYBACK_NAMES = {
12764
+ dash: 'DASH.js',
12765
+ hls: 'HLS.js',
12766
+ html5_video: 'Native',
12767
+ };
12768
+ const T$d = 'plugins.nerd_stats';
12731
12769
  /**
12732
12770
  * `PLUGIN` that displays useful network-related statistics.
12733
12771
  * @beta
@@ -12746,13 +12784,13 @@ const drawSummary = (customMetrics, vodContainer, liveContainer) => {
12746
12784
  */
12747
12785
  class NerdStats extends UICorePlugin {
12748
12786
  container = null;
12749
- customMetrics = {
12787
+ speedtestMetrics = {
12750
12788
  connectionSpeed: 0,
12751
12789
  ping: 0,
12752
12790
  jitter: 0,
12753
12791
  };
12754
12792
  metrics = newMetrics();
12755
- showing = false;
12793
+ open = false;
12756
12794
  shortcut;
12757
12795
  iconPosition;
12758
12796
  static buttonTemplate = tmpl(buttonHtml$3);
@@ -12774,7 +12812,6 @@ class NerdStats extends UICorePlugin {
12774
12812
  */
12775
12813
  get attributes() {
12776
12814
  return {
12777
- 'data-clappr-nerd-stats': '',
12778
12815
  class: 'clappr-nerd-stats',
12779
12816
  };
12780
12817
  }
@@ -12783,13 +12820,17 @@ class NerdStats extends UICorePlugin {
12783
12820
  */
12784
12821
  get events() {
12785
12822
  return {
12786
- 'click [data-show-stats-button]': 'showOrHide',
12787
- 'click [data-close-button]': 'hide',
12788
- 'click [data-refresh-button]': 'refreshSpeedTest',
12823
+ click: 'clicked',
12824
+ 'click #nerd-stats-close': 'hide',
12825
+ 'click #nerd-stats-refresh': 'refreshSpeedTest',
12789
12826
  };
12790
12827
  }
12828
+ clicked(e) {
12829
+ e.stopPropagation();
12830
+ e.preventDefault();
12831
+ }
12791
12832
  get statsBoxElem() {
12792
- return '.clappr-nerd-stats[data-clappr-nerd-stats] .stats-box';
12833
+ return this.$el.find('#nerd-stats-box');
12793
12834
  }
12794
12835
  get statsBoxWidthThreshold() {
12795
12836
  return 720;
@@ -12808,7 +12849,7 @@ class NerdStats extends UICorePlugin {
12808
12849
  ];
12809
12850
  this.iconPosition =
12810
12851
  core.options.clapprNerdStats?.iconPosition ?? 'bottom-right';
12811
- this.customMetrics = {
12852
+ this.speedtestMetrics = {
12812
12853
  connectionSpeed: 0,
12813
12854
  ping: 0,
12814
12855
  jitter: 0,
@@ -12820,20 +12861,38 @@ class NerdStats extends UICorePlugin {
12820
12861
  */
12821
12862
  bindEvents() {
12822
12863
  this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady);
12864
+ this.listenTo(this.core, Events.CORE_RESIZE, this.onPlayerResize);
12865
+ this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChanged);
12823
12866
  }
12824
12867
  onCoreReady() {
12825
12868
  const bottomGear = this.core.getPlugin('bottom_gear');
12826
12869
  assert(bottomGear, 'bottom_gear plugin is required');
12827
- this.listenTo(bottomGear, GearEvents.RENDERED, this.addToBottomGear);
12870
+ this.listenTo(bottomGear, GearEvents.RENDERED, this.attach);
12871
+ Mousetrap.bind(this.shortcut, this.toggle);
12872
+ this.updateResolution();
12873
+ }
12874
+ onActiveContainerChanged() {
12828
12875
  this.container = this.core.activeContainer;
12829
12876
  const clapprStats = this.container?.getPlugin('clappr_stats');
12830
12877
  assert(clapprStats, 'clappr-stats not available. Please, include it as a plugin of your Clappr instance.\n' +
12831
12878
  'For more info, visit: https://github.com/clappr/clappr-stats.');
12832
- Mousetrap.bind(this.shortcut, this.toggle);
12833
- this.listenTo(this.core, Events.CORE_RESIZE, this.onPlayerResize);
12834
12879
  this.listenTo(clapprStats, ClapprStatsEvents.REPORT, this.updateMetrics);
12880
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_VOLUME, () => {
12881
+ this.metrics.general.volume = this.container?.volume ?? 0;
12882
+ this.$el
12883
+ .find('#nerd-stats-volume')
12884
+ .text(Formatter.formatVolume(this.metrics.general.volume));
12885
+ });
12886
+ this.listenTo(this.core.activePlayback, Events.PLAYBACK_LOADEDMETADATA, () => {
12887
+ this.$el
12888
+ .find('#nerd-stats-playback-type')
12889
+ .text(this.formatPlaybackName(this.core.activePlayback.getPlaybackType()));
12890
+ });
12835
12891
  this.updateMetrics(clapprStats.exportMetrics());
12836
- this.render();
12892
+ this.$el
12893
+ .find('#nerd-stats-playback-name')
12894
+ .text(PLAYBACK_NAMES[this.core.activePlayback.name] ?? '-');
12895
+ this.core.activeContainer.$el.append(this.$el);
12837
12896
  }
12838
12897
  /**
12839
12898
  * @internal
@@ -12843,7 +12902,7 @@ class NerdStats extends UICorePlugin {
12843
12902
  return super.destroy();
12844
12903
  }
12845
12904
  toggle = () => {
12846
- if (this.showing) {
12905
+ if (this.open) {
12847
12906
  this.hide();
12848
12907
  }
12849
12908
  else {
@@ -12851,10 +12910,11 @@ class NerdStats extends UICorePlugin {
12851
12910
  }
12852
12911
  };
12853
12912
  show() {
12854
- this.core.$el.find(this.statsBoxElem).show();
12855
- this.showing = true;
12913
+ this.$el.show();
12914
+ this.statsBoxElem.scrollTop(this.statsBoxElem.scrollTop());
12915
+ this.open = true;
12856
12916
  this.refreshSpeedTest();
12857
- initSpeedTest(this.customMetrics)
12917
+ initSpeedTest(this.speedtestMetrics)
12858
12918
  .then(() => {
12859
12919
  startSpeedtest();
12860
12920
  })
@@ -12863,21 +12923,27 @@ class NerdStats extends UICorePlugin {
12863
12923
  });
12864
12924
  }
12865
12925
  hide() {
12866
- this.core.$el.find(this.statsBoxElem).hide();
12867
- this.showing = false;
12926
+ this.$el.hide();
12927
+ this.open = false;
12868
12928
  stopSpeedtest();
12869
12929
  }
12870
12930
  onPlayerResize() {
12871
12931
  this.setStatsBoxSize();
12932
+ this.updateResolution();
12872
12933
  }
12873
- addGeneralMetrics() {
12874
- this.metrics.general = {
12875
- displayResolution: this.playerWidth + 'x' + this.playerHeight,
12876
- volume: this.container?.volume,
12934
+ updateResolution() {
12935
+ this.metrics.general.resolution = {
12936
+ width: this.playerWidth,
12937
+ height: this.playerHeight,
12877
12938
  };
12939
+ this.$el
12940
+ .find('#nerd-stats-resolution-width')
12941
+ .text(this.metrics.general.resolution.width);
12942
+ this.$el
12943
+ .find('#nerd-stats-resolution-height')
12944
+ .text(this.metrics.general.resolution.height);
12878
12945
  }
12879
- addCustomMetrics() {
12880
- this.metrics.custom = this.customMetrics;
12946
+ estimateQuality() {
12881
12947
  const videoQualityNames = [
12882
12948
  'SD (480p)',
12883
12949
  'HD (720p)',
@@ -12885,9 +12951,9 @@ class NerdStats extends UICorePlugin {
12885
12951
  '2K (1440p)',
12886
12952
  '4K (2160p)',
12887
12953
  ];
12888
- const { connectionSpeed, ping } = this.customMetrics;
12954
+ const { connectionSpeed, ping } = this.speedtestMetrics;
12889
12955
  if (!connectionSpeed || !ping) {
12890
- const calculatingText = 'Calculating... Please wait.';
12956
+ const calculatingText = this.core.i18n.t('stats.calculating');
12891
12957
  this.metrics.custom.vodQuality = calculatingText;
12892
12958
  this.metrics.custom.liveQuality = calculatingText;
12893
12959
  return;
@@ -12902,42 +12968,108 @@ class NerdStats extends UICorePlugin {
12902
12968
  prefix + videoQualityNames[liveQuality - 1];
12903
12969
  }
12904
12970
  updateMetrics(metrics) {
12971
+ trace(`${T$d} updateMetrics`, { custom: this.speedtestMetrics });
12905
12972
  Object.assign(this.metrics, metrics);
12906
- this.addGeneralMetrics();
12907
- this.addCustomMetrics();
12908
- const scrollTop = this.core.$el.find(this.statsBoxElem).scrollTop();
12909
- this.$el.html(NerdStats.template({
12910
- metrics: Formatter.format(this.metrics),
12911
- iconPosition: this.iconPosition,
12912
- }));
12973
+ this.updateEstimatedQuality();
12974
+ this.$el
12975
+ .find('#nerd-stats-current-time')
12976
+ .text(Formatter.formatTime(this.metrics.extra.currentTime));
12977
+ this.$el
12978
+ .find('#nerd-stats-video-duration')
12979
+ .text(Formatter.formatTime(this.metrics.extra.duration));
12980
+ this.$el
12981
+ .find('#nerd-stats-buffer-size')
12982
+ .text(Formatter.formatTime(this.metrics.extra.buffersize));
12983
+ this.$el
12984
+ .find('#nerd-stats-bitrate-weighted-mean')
12985
+ .text(Formatter.formatBitrate(this.metrics.extra.bitrateWeightedMean));
12986
+ this.$el
12987
+ .find('#nerd-stats-bitrate-most-used')
12988
+ .text(Formatter.formatBitrate(this.metrics.extra.bitrateMostUsed));
12989
+ this.$el
12990
+ .find('#nerd-stats-watched-percentage')
12991
+ .text(Formatter.formatPercentage(this.metrics.extra.watchedPercentage));
12992
+ this.$el
12993
+ .find('#nerd-stats-buffering-percentage')
12994
+ .text(Formatter.formatPercentage(this.metrics.extra.bufferingPercentage));
12995
+ this.$el
12996
+ .find('#nerd-stats-startup-time')
12997
+ .text(Formatter.formatTime(this.metrics.chrono.startup));
12998
+ this.$el
12999
+ .find('#nerd-stats-watch-time')
13000
+ .text(Formatter.formatTime(this.metrics.chrono.watch));
13001
+ this.$el
13002
+ .find('#nerd-stats-pause-time')
13003
+ .text(Formatter.formatTime(this.metrics.chrono.pause));
13004
+ this.$el
13005
+ .find('#nerd-stats-buffering-time')
13006
+ .text(Formatter.formatTime(this.metrics.chrono.buffering));
13007
+ this.$el
13008
+ .find('#nerd-stats-session-time')
13009
+ .text(Formatter.formatTime(this.metrics.chrono.session));
13010
+ this.$el.find('#nerd-stats-plays').text(this.metrics.counters.play);
13011
+ this.$el.find('#nerd-stats-pauses').text(this.metrics.counters.pause);
13012
+ this.$el.find('#nerd-stats-errors').text(this.metrics.counters.error);
13013
+ this.$el
13014
+ .find('#nerd-stats-bufferings')
13015
+ .text(this.metrics.counters.buffering);
13016
+ this.$el
13017
+ .find('#nerd-stats-decoded-frames')
13018
+ .text(this.metrics.counters.decodedFrames);
13019
+ this.$el
13020
+ .find('#nerd-stats-dropped-frames')
13021
+ .text(this.metrics.counters.droppedFrames);
13022
+ this.$el
13023
+ .find('#nerd-stats-bitrate-changes')
13024
+ .text(this.metrics.counters.changeLevel);
13025
+ this.$el.find('#nerd-stats-seeks').text(this.metrics.counters.seek);
13026
+ this.$el
13027
+ .find('#nerd-stats-fullscreen')
13028
+ .text(this.metrics.counters.fullscreen);
13029
+ this.$el.find('#nerd-stats-dvr-usage').text(this.metrics.counters.dvrUsage);
13030
+ this.$el
13031
+ .find('#nerd-stats-fps')
13032
+ .text(Formatter.formatFps(this.metrics.counters.fps));
12913
13033
  this.setStatsBoxSize();
12914
13034
  drawSpeedTestResults();
12915
- drawSummary(this.metrics?.custom, this.$el.find('.speedtest-quality-content[data-streaming-type="vod"]'), this.$el.find('.speedtest-quality-content[data-streaming-type="live"]'));
12916
- this.core.$el.find(this.statsBoxElem).scrollTop(scrollTop);
12917
- if (!this.showing) {
13035
+ drawSummary(this.speedtestMetrics, this.$el.find('#nerd-stats-quality-vod'), this.$el.find('#nerd-stats-quality-live'));
13036
+ if (!this.open) {
12918
13037
  this.hide();
12919
13038
  }
12920
13039
  }
13040
+ updateEstimatedQuality() {
13041
+ this.estimateQuality();
13042
+ this.$el
13043
+ .find('#nerd-stats-quality-vod-text')
13044
+ .html(this.metrics.custom.vodQuality);
13045
+ this.$el
13046
+ .find('#nerd-stats-quality-live-text')
13047
+ .html(this.metrics.custom.liveQuality);
13048
+ }
12921
13049
  setStatsBoxSize() {
12922
13050
  if (this.playerWidth >= this.statsBoxWidthThreshold) {
12923
- this.$el.find(this.statsBoxElem).addClass('wide');
12924
- this.$el.find(this.statsBoxElem).removeClass('narrow');
13051
+ this.statsBoxElem.addClass('wide');
13052
+ this.statsBoxElem.removeClass('narrow');
12925
13053
  }
12926
13054
  else {
12927
- this.$el.find(this.statsBoxElem).removeClass('wide');
12928
- this.$el.find(this.statsBoxElem).addClass('narrow');
13055
+ this.statsBoxElem.removeClass('wide');
13056
+ this.statsBoxElem.addClass('narrow');
12929
13057
  }
12930
13058
  }
12931
13059
  /**
12932
13060
  * @internal
12933
13061
  */
12934
13062
  render() {
12935
- // TODO append to the container
12936
- this.core.$el.append(this.$el[0]);
12937
- this.hide();
13063
+ this.$el
13064
+ .html(NerdStats.template({
13065
+ metrics: Formatter.format(this.metrics ?? newMetrics()),
13066
+ iconPosition: this.iconPosition,
13067
+ i18n: this.core.i18n,
13068
+ }))
13069
+ .hide();
12938
13070
  return this;
12939
13071
  }
12940
- addToBottomGear() {
13072
+ attach() {
12941
13073
  const gear = this.core.getPlugin('bottom_gear');
12942
13074
  gear
12943
13075
  .addItem('nerd_stats')
@@ -12950,11 +13082,11 @@ class NerdStats extends UICorePlugin {
12950
13082
  this.toggle();
12951
13083
  });
12952
13084
  }
12953
- clearCustomMetrics() {
13085
+ clearSpeedtestMetrics() {
12954
13086
  const clapprStats = this.container?.getPlugin('clappr_stats');
12955
- this.customMetrics.connectionSpeed = 0;
12956
- this.customMetrics.ping = 0;
12957
- this.customMetrics.jitter = 0;
13087
+ this.speedtestMetrics.connectionSpeed = 0;
13088
+ this.speedtestMetrics.ping = 0;
13089
+ this.speedtestMetrics.jitter = 0;
12958
13090
  if (clapprStats) {
12959
13091
  this.updateMetrics(clapprStats.exportMetrics());
12960
13092
  }
@@ -12962,7 +13094,7 @@ class NerdStats extends UICorePlugin {
12962
13094
  refreshSpeedTest() {
12963
13095
  stopSpeedtest();
12964
13096
  setTimeout(() => {
12965
- this.clearCustomMetrics();
13097
+ this.clearSpeedtestMetrics();
12966
13098
  clearSpeedTestResults();
12967
13099
  drawSpeedTestResults();
12968
13100
  }, 200);
@@ -12970,11 +13102,28 @@ class NerdStats extends UICorePlugin {
12970
13102
  startSpeedtest();
12971
13103
  }, 800);
12972
13104
  }
13105
+ formatPlaybackName(playbackType) {
13106
+ switch (playbackType) {
13107
+ case Playback.VOD:
13108
+ return this.core.i18n.t('vod');
13109
+ case Playback.LIVE:
13110
+ return this.core.i18n.t('live');
13111
+ default:
13112
+ return '-';
13113
+ }
13114
+ }
12973
13115
  }
12974
13116
  function newMetrics() {
12975
13117
  return {
12976
13118
  ...newMetrics$1(),
12977
- general: {},
13119
+ general: {
13120
+ displayResolution: '',
13121
+ resolution: {
13122
+ width: 0,
13123
+ height: 0,
13124
+ },
13125
+ volume: 0,
13126
+ },
12978
13127
  custom: {
12979
13128
  connectionSpeed: 0,
12980
13129
  ping: 0,
@@ -16792,7 +16941,7 @@ class SeekTime extends UICorePlugin {
16792
16941
  this.listenTo(this.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.onContainerChanged);
16793
16942
  if (this.mediaControlContainer) {
16794
16943
  this.listenTo(this.mediaControlContainer, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, this.update);
16795
- this.listenTo(this.mediaControlContainer, Events.CONTAINER_TIMEUPDATE, this.updateDuration);
16944
+ this.listenTo(this.mediaControlContainer, Events.CONTAINER_TIMEUPDATE, this.onTimeUpdate);
16796
16945
  }
16797
16946
  }
16798
16947
  onContainerChanged() {
@@ -16800,9 +16949,8 @@ class SeekTime extends UICorePlugin {
16800
16949
  this.stopListening();
16801
16950
  this.bindEvents();
16802
16951
  }
16803
- updateDuration(timeProgress) {
16804
- this.duration = timeProgress.total;
16805
- // this.firstFragDateTime = timeProgress.firstFragDateTime;
16952
+ onTimeUpdate({ total }) {
16953
+ this.duration = total;
16806
16954
  this.update();
16807
16955
  }
16808
16956
  showTime(event) {