@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
package/dist/index.js CHANGED
@@ -12703,7 +12703,6 @@ class DashPlayback extends BasePlayback {
12703
12703
  _playbackType = Playback.VOD;
12704
12704
  // #EXT-X-PLAYLIST-TYPE
12705
12705
  _playlistType = null;
12706
- // #EXT-X-PROGRAM-DATE-TIME
12707
12706
  _programDateTime = 0;
12708
12707
  _dash = null;
12709
12708
  _extrapolatedWindowDuration = 0;
@@ -12891,9 +12890,9 @@ class DashPlayback extends BasePlayback {
12891
12890
  clearInterval(this._timeUpdateTimer);
12892
12891
  }
12893
12892
  }
12894
- getProgramDateTime() {
12895
- return this._programDateTime;
12896
- }
12893
+ // getProgramDateTime() {
12894
+ // return this._programDateTime
12895
+ // }
12897
12896
  // the duration on the video element itself should not be used
12898
12897
  // as this does not necesarily represent the duration of the stream
12899
12898
  // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
@@ -13012,7 +13011,7 @@ class DashPlayback extends BasePlayback {
13012
13011
  const update = {
13013
13012
  current: this.getCurrentTime(),
13014
13013
  total: this.getDuration(),
13015
- firstFragDateTime: this.getProgramDateTime(),
13014
+ // firstFragDateTime: this.getProgramDateTime(), // TODO figure out if needed
13016
13015
  };
13017
13016
  const isSame = this._lastTimeUpdate &&
13018
13017
  update.current === this._lastTimeUpdate.current &&
@@ -42359,7 +42358,6 @@ class HlsPlayback extends BasePlayback {
42359
42358
  const update = {
42360
42359
  current: this.getCurrentTime(),
42361
42360
  total: this.getDuration(),
42362
- firstFragDateTime: this.getProgramDateTime(),
42363
42361
  };
42364
42362
  const isSame = this._lastTimeUpdate &&
42365
42363
  update.current === this._lastTimeUpdate.current &&
@@ -43305,7 +43303,7 @@ class Player {
43305
43303
  }
43306
43304
  }
43307
43305
 
43308
- var version$1 = "2.22.17";
43306
+ var version$1 = "2.22.18";
43309
43307
 
43310
43308
  var packages = {
43311
43309
  "node_modules/@clappr/core": {
@@ -43963,7 +43961,6 @@ function newMetrics$1() {
43963
43961
  duration: 0,
43964
43962
  currentTime: 0,
43965
43963
  },
43966
- custom: {},
43967
43964
  };
43968
43965
  }
43969
43966
 
@@ -45657,63 +45654,72 @@ const timeScale = new humanFormat.Scale({
45657
45654
  ms: 1,
45658
45655
  sec: 1000,
45659
45656
  min: 60000,
45660
- hours: 3600000
45657
+ hours: 3600000,
45661
45658
  });
45662
45659
  const percentScale = new humanFormat.Scale({
45663
- '%': 1
45660
+ '%': 1,
45664
45661
  });
45662
+ const metricTemplates = {
45663
+ fps: {
45664
+ scale: 'SI',
45665
+ decimals: 0,
45666
+ },
45667
+ volume: {
45668
+ scale: percentScale,
45669
+ },
45670
+ };
45665
45671
  const formattingTemplate = {
45666
45672
  general: {
45667
45673
  volume: {
45668
- scale: percentScale
45669
- }
45674
+ scale: percentScale,
45675
+ },
45670
45676
  },
45671
45677
  timers: {
45672
45678
  startup: {
45673
- scale: timeScale
45679
+ scale: timeScale,
45674
45680
  },
45675
45681
  watch: {
45676
- scale: timeScale
45682
+ scale: timeScale,
45677
45683
  },
45678
45684
  pause: {
45679
- scale: timeScale
45685
+ scale: timeScale,
45680
45686
  },
45681
45687
  buffering: {
45682
- scale: timeScale
45688
+ scale: timeScale,
45683
45689
  },
45684
45690
  session: {
45685
- scale: timeScale
45691
+ scale: timeScale,
45686
45692
  },
45687
45693
  latency: {
45688
- scale: timeScale
45689
- }
45694
+ scale: timeScale,
45695
+ },
45690
45696
  },
45691
45697
  extra: {
45692
45698
  buffersize: {
45693
- scale: timeScale
45699
+ scale: timeScale,
45694
45700
  },
45695
45701
  duration: {
45696
- scale: timeScale
45702
+ scale: timeScale,
45697
45703
  },
45698
45704
  currentTime: {
45699
- scale: timeScale
45705
+ scale: timeScale,
45700
45706
  },
45701
45707
  bitrateWeightedMean: {
45702
- unit: 'bps'
45708
+ unit: 'bps',
45703
45709
  },
45704
45710
  bitrateMostUsed: {
45705
- unit: 'bps'
45711
+ unit: 'bps',
45706
45712
  },
45707
45713
  bandwidth: {
45708
- unit: 'bps'
45714
+ unit: 'bps',
45709
45715
  },
45710
45716
  watchedPercentage: {
45711
- scale: percentScale
45717
+ scale: percentScale,
45712
45718
  },
45713
45719
  bufferingPercentage: {
45714
- scale: percentScale
45715
- }
45716
- }
45720
+ scale: percentScale,
45721
+ },
45722
+ },
45717
45723
  };
45718
45724
  class Formatter {
45719
45725
  static format(metrics) {
@@ -45723,8 +45729,10 @@ class Formatter {
45723
45729
  formattedMetrics[type] = fmt;
45724
45730
  const typeTemplate = formattingTemplate[type];
45725
45731
  Object.entries(mm).forEach(([name, value]) => {
45726
- // const value = mm[name];
45727
- if (typeTemplate && typeTemplate[name] && (typeof value === 'number') && !isNaN(value)) {
45732
+ if (typeTemplate &&
45733
+ typeTemplate[name] &&
45734
+ typeof value === 'number' &&
45735
+ !isNaN(value)) {
45728
45736
  // @ts-ignore
45729
45737
  const templateScale = typeTemplate[name].scale || 'SI';
45730
45738
  // @ts-ignore
@@ -45732,7 +45740,7 @@ class Formatter {
45732
45740
  fmt[name] = humanFormat(value, {
45733
45741
  scale: templateScale,
45734
45742
  unit: templateUnit,
45735
- decimals: 2
45743
+ decimals: 2,
45736
45744
  });
45737
45745
  }
45738
45746
  else {
@@ -45742,6 +45750,27 @@ class Formatter {
45742
45750
  });
45743
45751
  return formattedMetrics;
45744
45752
  }
45753
+ static formatVolume(volume) {
45754
+ return humanFormat(volume, metricTemplates.volume);
45755
+ }
45756
+ static formatTime(time) {
45757
+ return humanFormat(time, {
45758
+ scale: timeScale,
45759
+ });
45760
+ }
45761
+ static formatFps(fps) {
45762
+ return humanFormat(fps, metricTemplates.fps);
45763
+ }
45764
+ static formatPercentage(percentage) {
45765
+ return humanFormat(percentage, {
45766
+ scale: percentScale,
45767
+ });
45768
+ }
45769
+ static formatBitrate(bitrate) {
45770
+ return humanFormat(bitrate, {
45771
+ unit: 'bps',
45772
+ });
45773
+ }
45745
45774
  }
45746
45775
 
45747
45776
  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";
@@ -46234,7 +46263,7 @@ function rankConnectionSpeed(dlSpeed) {
46234
46263
  return 0;
46235
46264
  }
46236
46265
 
46237
- 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";
46266
+ 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";
46238
46267
 
46239
46268
  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";
46240
46269
 
@@ -46307,6 +46336,12 @@ const drawSummary = (customMetrics, vodContainer, liveContainer) => {
46307
46336
  vodContainer.html(vodHtml);
46308
46337
  liveContainer.html(liveHtml);
46309
46338
  };
46339
+
46340
+ const PLAYBACK_NAMES = {
46341
+ dash: 'DASH.js',
46342
+ hls: 'HLS.js',
46343
+ html5_video: 'Native',
46344
+ };
46310
46345
  const T$f = 'plugins.nerd_stats';
46311
46346
  /**
46312
46347
  * `PLUGIN` that displays useful network-related statistics.
@@ -46326,13 +46361,13 @@ const T$f = 'plugins.nerd_stats';
46326
46361
  */
46327
46362
  class NerdStats extends UICorePlugin {
46328
46363
  container = null;
46329
- customMetrics = {
46364
+ speedtestMetrics = {
46330
46365
  connectionSpeed: 0,
46331
46366
  ping: 0,
46332
46367
  jitter: 0,
46333
46368
  };
46334
46369
  metrics = newMetrics();
46335
- showing = false;
46370
+ open = false;
46336
46371
  shortcut;
46337
46372
  iconPosition;
46338
46373
  static buttonTemplate = tmpl(buttonHtml$3);
@@ -46354,7 +46389,6 @@ class NerdStats extends UICorePlugin {
46354
46389
  */
46355
46390
  get attributes() {
46356
46391
  return {
46357
- 'data-clappr-nerd-stats': '',
46358
46392
  class: 'clappr-nerd-stats',
46359
46393
  };
46360
46394
  }
@@ -46363,13 +46397,17 @@ class NerdStats extends UICorePlugin {
46363
46397
  */
46364
46398
  get events() {
46365
46399
  return {
46366
- 'click [data-show-stats-button]': 'showOrHide',
46367
- 'click [data-close-button]': 'hide',
46368
- 'click [data-refresh-button]': 'refreshSpeedTest',
46400
+ click: 'clicked',
46401
+ 'click #nerd-stats-close': 'hide',
46402
+ 'click #nerd-stats-refresh': 'refreshSpeedTest',
46369
46403
  };
46370
46404
  }
46405
+ clicked(e) {
46406
+ e.stopPropagation();
46407
+ e.preventDefault();
46408
+ }
46371
46409
  get statsBoxElem() {
46372
- return '.clappr-nerd-stats[data-clappr-nerd-stats] .stats-box';
46410
+ return this.$el.find('#nerd-stats-box');
46373
46411
  }
46374
46412
  get statsBoxWidthThreshold() {
46375
46413
  return 720;
@@ -46388,7 +46426,7 @@ class NerdStats extends UICorePlugin {
46388
46426
  ];
46389
46427
  this.iconPosition =
46390
46428
  core.options.clapprNerdStats?.iconPosition ?? 'bottom-right';
46391
- this.customMetrics = {
46429
+ this.speedtestMetrics = {
46392
46430
  connectionSpeed: 0,
46393
46431
  ping: 0,
46394
46432
  jitter: 0,
@@ -46400,20 +46438,38 @@ class NerdStats extends UICorePlugin {
46400
46438
  */
46401
46439
  bindEvents() {
46402
46440
  this.listenToOnce(this.core, Events$1.CORE_READY, this.onCoreReady);
46441
+ this.listenTo(this.core, Events$1.CORE_RESIZE, this.onPlayerResize);
46442
+ this.listenTo(this.core, Events$1.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChanged);
46403
46443
  }
46404
46444
  onCoreReady() {
46405
46445
  const bottomGear = this.core.getPlugin('bottom_gear');
46406
46446
  assert(bottomGear, 'bottom_gear plugin is required');
46407
- this.listenTo(bottomGear, GearEvents.RENDERED, this.addToBottomGear);
46447
+ this.listenTo(bottomGear, GearEvents.RENDERED, this.attach);
46448
+ Mousetrap.bind(this.shortcut, this.toggle);
46449
+ this.updateResolution();
46450
+ }
46451
+ onActiveContainerChanged() {
46408
46452
  this.container = this.core.activeContainer;
46409
46453
  const clapprStats = this.container?.getPlugin('clappr_stats');
46410
46454
  assert(clapprStats, 'clappr-stats not available. Please, include it as a plugin of your Clappr instance.\n' +
46411
46455
  'For more info, visit: https://github.com/clappr/clappr-stats.');
46412
- Mousetrap.bind(this.shortcut, this.toggle);
46413
- this.listenTo(this.core, Events$1.CORE_RESIZE, this.onPlayerResize);
46414
46456
  this.listenTo(clapprStats, ClapprStatsEvents.REPORT, this.updateMetrics);
46457
+ this.listenTo(this.core.activeContainer, Events$1.CONTAINER_VOLUME, () => {
46458
+ this.metrics.general.volume = this.container?.volume ?? 0;
46459
+ this.$el
46460
+ .find('#nerd-stats-volume')
46461
+ .text(Formatter.formatVolume(this.metrics.general.volume));
46462
+ });
46463
+ this.listenTo(this.core.activePlayback, Events$1.PLAYBACK_LOADEDMETADATA, () => {
46464
+ this.$el
46465
+ .find('#nerd-stats-playback-type')
46466
+ .text(this.formatPlaybackName(this.core.activePlayback.getPlaybackType()));
46467
+ });
46415
46468
  this.updateMetrics(clapprStats.exportMetrics());
46416
- this.render();
46469
+ this.$el
46470
+ .find('#nerd-stats-playback-name')
46471
+ .text(PLAYBACK_NAMES[this.core.activePlayback.name] ?? '-');
46472
+ this.core.activeContainer.$el.append(this.$el);
46417
46473
  }
46418
46474
  /**
46419
46475
  * @internal
@@ -46423,7 +46479,7 @@ class NerdStats extends UICorePlugin {
46423
46479
  return super.destroy();
46424
46480
  }
46425
46481
  toggle = () => {
46426
- if (this.showing) {
46482
+ if (this.open) {
46427
46483
  this.hide();
46428
46484
  }
46429
46485
  else {
@@ -46431,10 +46487,11 @@ class NerdStats extends UICorePlugin {
46431
46487
  }
46432
46488
  };
46433
46489
  show() {
46434
- this.core.$el.find(this.statsBoxElem).show();
46435
- this.showing = true;
46490
+ this.$el.show();
46491
+ this.statsBoxElem.scrollTop(this.statsBoxElem.scrollTop());
46492
+ this.open = true;
46436
46493
  this.refreshSpeedTest();
46437
- initSpeedTest(this.customMetrics)
46494
+ initSpeedTest(this.speedtestMetrics)
46438
46495
  .then(() => {
46439
46496
  startSpeedtest();
46440
46497
  })
@@ -46444,21 +46501,28 @@ class NerdStats extends UICorePlugin {
46444
46501
  });
46445
46502
  }
46446
46503
  hide() {
46447
- this.core.$el.find(this.statsBoxElem).hide();
46448
- this.showing = false;
46504
+ this.$el.hide();
46505
+ this.open = false;
46449
46506
  stopSpeedtest();
46450
46507
  }
46451
46508
  onPlayerResize() {
46452
46509
  this.setStatsBoxSize();
46510
+ this.updateResolution();
46453
46511
  }
46454
- addGeneralMetrics() {
46455
- this.metrics.general = {
46456
- displayResolution: this.playerWidth + 'x' + this.playerHeight,
46457
- volume: this.container?.volume,
46512
+ updateResolution() {
46513
+ this.metrics.general.resolution = {
46514
+ width: this.playerWidth,
46515
+ height: this.playerHeight,
46458
46516
  };
46517
+ this.$el
46518
+ .find('#nerd-stats-resolution-width')
46519
+ .text(this.metrics.general.resolution.width);
46520
+ this.$el
46521
+ .find('#nerd-stats-resolution-height')
46522
+ .text(this.metrics.general.resolution.height);
46459
46523
  }
46460
- addCustomMetrics() {
46461
- this.metrics.custom = this.customMetrics;
46524
+ estimateQuality() {
46525
+ trace(`${T$f} estimateQuality`);
46462
46526
  const videoQualityNames = [
46463
46527
  'SD (480p)',
46464
46528
  'HD (720p)',
@@ -46466,9 +46530,9 @@ class NerdStats extends UICorePlugin {
46466
46530
  '2K (1440p)',
46467
46531
  '4K (2160p)',
46468
46532
  ];
46469
- const { connectionSpeed, ping } = this.customMetrics;
46533
+ const { connectionSpeed, ping } = this.speedtestMetrics;
46470
46534
  if (!connectionSpeed || !ping) {
46471
- const calculatingText = 'Calculating... Please wait.';
46535
+ const calculatingText = this.core.i18n.t('stats.calculating');
46472
46536
  this.metrics.custom.vodQuality = calculatingText;
46473
46537
  this.metrics.custom.liveQuality = calculatingText;
46474
46538
  return;
@@ -46483,44 +46547,109 @@ class NerdStats extends UICorePlugin {
46483
46547
  prefix + videoQualityNames[liveQuality - 1];
46484
46548
  }
46485
46549
  updateMetrics(metrics) {
46550
+ trace(`${T$f} updateMetrics`, { custom: this.speedtestMetrics });
46486
46551
  Object.assign(this.metrics, metrics);
46487
- this.addGeneralMetrics();
46488
- this.addCustomMetrics();
46489
- const scrollTop = this.core.$el.find(this.statsBoxElem).scrollTop();
46490
- this.$el.html(NerdStats.template({
46491
- metrics: Formatter.format(this.metrics),
46492
- iconPosition: this.iconPosition,
46493
- }));
46552
+ this.updateEstimatedQuality();
46553
+ this.$el
46554
+ .find('#nerd-stats-current-time')
46555
+ .text(Formatter.formatTime(this.metrics.extra.currentTime));
46556
+ this.$el
46557
+ .find('#nerd-stats-video-duration')
46558
+ .text(Formatter.formatTime(this.metrics.extra.duration));
46559
+ this.$el
46560
+ .find('#nerd-stats-buffer-size')
46561
+ .text(Formatter.formatTime(this.metrics.extra.buffersize));
46562
+ this.$el
46563
+ .find('#nerd-stats-bitrate-weighted-mean')
46564
+ .text(Formatter.formatBitrate(this.metrics.extra.bitrateWeightedMean));
46565
+ this.$el
46566
+ .find('#nerd-stats-bitrate-most-used')
46567
+ .text(Formatter.formatBitrate(this.metrics.extra.bitrateMostUsed));
46568
+ this.$el
46569
+ .find('#nerd-stats-watched-percentage')
46570
+ .text(Formatter.formatPercentage(this.metrics.extra.watchedPercentage));
46571
+ this.$el
46572
+ .find('#nerd-stats-buffering-percentage')
46573
+ .text(Formatter.formatPercentage(this.metrics.extra.bufferingPercentage));
46574
+ this.$el
46575
+ .find('#nerd-stats-startup-time')
46576
+ .text(Formatter.formatTime(this.metrics.chrono.startup));
46577
+ this.$el
46578
+ .find('#nerd-stats-watch-time')
46579
+ .text(Formatter.formatTime(this.metrics.chrono.watch));
46580
+ this.$el
46581
+ .find('#nerd-stats-pause-time')
46582
+ .text(Formatter.formatTime(this.metrics.chrono.pause));
46583
+ this.$el
46584
+ .find('#nerd-stats-buffering-time')
46585
+ .text(Formatter.formatTime(this.metrics.chrono.buffering));
46586
+ this.$el
46587
+ .find('#nerd-stats-session-time')
46588
+ .text(Formatter.formatTime(this.metrics.chrono.session));
46589
+ this.$el.find('#nerd-stats-plays').text(this.metrics.counters.play);
46590
+ this.$el.find('#nerd-stats-pauses').text(this.metrics.counters.pause);
46591
+ this.$el.find('#nerd-stats-errors').text(this.metrics.counters.error);
46592
+ this.$el
46593
+ .find('#nerd-stats-bufferings')
46594
+ .text(this.metrics.counters.buffering);
46595
+ this.$el
46596
+ .find('#nerd-stats-decoded-frames')
46597
+ .text(this.metrics.counters.decodedFrames);
46598
+ this.$el
46599
+ .find('#nerd-stats-dropped-frames')
46600
+ .text(this.metrics.counters.droppedFrames);
46601
+ this.$el
46602
+ .find('#nerd-stats-bitrate-changes')
46603
+ .text(this.metrics.counters.changeLevel);
46604
+ this.$el.find('#nerd-stats-seeks').text(this.metrics.counters.seek);
46605
+ this.$el
46606
+ .find('#nerd-stats-fullscreen')
46607
+ .text(this.metrics.counters.fullscreen);
46608
+ this.$el.find('#nerd-stats-dvr-usage').text(this.metrics.counters.dvrUsage);
46609
+ this.$el
46610
+ .find('#nerd-stats-fps')
46611
+ .text(Formatter.formatFps(this.metrics.counters.fps));
46494
46612
  this.setStatsBoxSize();
46495
46613
  drawSpeedTestResults();
46496
- drawSummary(this.metrics?.custom, this.$el.find('.speedtest-quality-content[data-streaming-type="vod"]'), this.$el.find('.speedtest-quality-content[data-streaming-type="live"]'));
46497
- this.core.$el.find(this.statsBoxElem).scrollTop(scrollTop);
46498
- if (!this.showing) {
46614
+ drawSummary(this.speedtestMetrics, this.$el.find('#nerd-stats-quality-vod'), this.$el.find('#nerd-stats-quality-live'));
46615
+ if (!this.open) {
46499
46616
  this.hide();
46500
46617
  }
46501
46618
  }
46619
+ updateEstimatedQuality() {
46620
+ this.estimateQuality();
46621
+ this.$el
46622
+ .find('#nerd-stats-quality-vod-text')
46623
+ .html(this.metrics.custom.vodQuality);
46624
+ this.$el
46625
+ .find('#nerd-stats-quality-live-text')
46626
+ .html(this.metrics.custom.liveQuality);
46627
+ }
46502
46628
  setStatsBoxSize() {
46503
46629
  if (this.playerWidth >= this.statsBoxWidthThreshold) {
46504
- this.$el.find(this.statsBoxElem).addClass('wide');
46505
- this.$el.find(this.statsBoxElem).removeClass('narrow');
46630
+ this.statsBoxElem.addClass('wide');
46631
+ this.statsBoxElem.removeClass('narrow');
46506
46632
  }
46507
46633
  else {
46508
- this.$el.find(this.statsBoxElem).removeClass('wide');
46509
- this.$el.find(this.statsBoxElem).addClass('narrow');
46634
+ this.statsBoxElem.removeClass('wide');
46635
+ this.statsBoxElem.addClass('narrow');
46510
46636
  }
46511
46637
  }
46512
46638
  /**
46513
46639
  * @internal
46514
46640
  */
46515
46641
  render() {
46516
- trace(`${T$f} render`);
46517
- // TODO append to the container
46518
- this.core.$el.append(this.$el[0]);
46519
- this.hide();
46642
+ this.$el
46643
+ .html(NerdStats.template({
46644
+ metrics: Formatter.format(this.metrics ?? newMetrics()),
46645
+ iconPosition: this.iconPosition,
46646
+ i18n: this.core.i18n,
46647
+ }))
46648
+ .hide();
46520
46649
  return this;
46521
46650
  }
46522
- addToBottomGear() {
46523
- trace(`${T$f} addToBottomGear`);
46651
+ attach() {
46652
+ trace(`${T$f} attach`);
46524
46653
  const gear = this.core.getPlugin('bottom_gear');
46525
46654
  gear
46526
46655
  .addItem('nerd_stats')
@@ -46533,11 +46662,11 @@ class NerdStats extends UICorePlugin {
46533
46662
  this.toggle();
46534
46663
  });
46535
46664
  }
46536
- clearCustomMetrics() {
46665
+ clearSpeedtestMetrics() {
46537
46666
  const clapprStats = this.container?.getPlugin('clappr_stats');
46538
- this.customMetrics.connectionSpeed = 0;
46539
- this.customMetrics.ping = 0;
46540
- this.customMetrics.jitter = 0;
46667
+ this.speedtestMetrics.connectionSpeed = 0;
46668
+ this.speedtestMetrics.ping = 0;
46669
+ this.speedtestMetrics.jitter = 0;
46541
46670
  if (clapprStats) {
46542
46671
  this.updateMetrics(clapprStats.exportMetrics());
46543
46672
  }
@@ -46545,7 +46674,7 @@ class NerdStats extends UICorePlugin {
46545
46674
  refreshSpeedTest() {
46546
46675
  stopSpeedtest();
46547
46676
  setTimeout(() => {
46548
- this.clearCustomMetrics();
46677
+ this.clearSpeedtestMetrics();
46549
46678
  clearSpeedTestResults();
46550
46679
  drawSpeedTestResults();
46551
46680
  }, 200);
@@ -46553,11 +46682,28 @@ class NerdStats extends UICorePlugin {
46553
46682
  startSpeedtest();
46554
46683
  }, 800);
46555
46684
  }
46685
+ formatPlaybackName(playbackType) {
46686
+ switch (playbackType) {
46687
+ case Playback.VOD:
46688
+ return this.core.i18n.t('vod');
46689
+ case Playback.LIVE:
46690
+ return this.core.i18n.t('live');
46691
+ default:
46692
+ return '-';
46693
+ }
46694
+ }
46556
46695
  }
46557
46696
  function newMetrics() {
46558
46697
  return {
46559
46698
  ...newMetrics$1(),
46560
- general: {},
46699
+ general: {
46700
+ displayResolution: '',
46701
+ resolution: {
46702
+ width: 0,
46703
+ height: 0,
46704
+ },
46705
+ volume: 0,
46706
+ },
46561
46707
  custom: {
46562
46708
  connectionSpeed: 0,
46563
46709
  ping: 0,
@@ -50416,7 +50562,7 @@ class SeekTime extends UICorePlugin {
50416
50562
  this.listenTo(this.mediaControl, Events$1.MEDIACONTROL_CONTAINERCHANGED, this.onContainerChanged);
50417
50563
  if (this.mediaControlContainer) {
50418
50564
  this.listenTo(this.mediaControlContainer, Events$1.CONTAINER_PLAYBACKDVRSTATECHANGED, this.update);
50419
- this.listenTo(this.mediaControlContainer, Events$1.CONTAINER_TIMEUPDATE, this.updateDuration);
50565
+ this.listenTo(this.mediaControlContainer, Events$1.CONTAINER_TIMEUPDATE, this.onTimeUpdate);
50420
50566
  }
50421
50567
  }
50422
50568
  onContainerChanged() {
@@ -50424,9 +50570,8 @@ class SeekTime extends UICorePlugin {
50424
50570
  this.stopListening();
50425
50571
  this.bindEvents();
50426
50572
  }
50427
- updateDuration(timeProgress) {
50428
- this.duration = timeProgress.total;
50429
- // this.firstFragDateTime = timeProgress.firstFragDateTime;
50573
+ onTimeUpdate({ total }) {
50574
+ this.duration = total;
50430
50575
  this.update();
50431
50576
  }
50432
50577
  showTime(event) {