@gcorevideo/player 2.22.16 → 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.
- package/assets/clappr-nerd-stats/clappr-nerd-stats.ejs +76 -78
- package/assets/clappr-nerd-stats/clappr-nerd-stats.scss +10 -7
- package/dist/core.js +10 -14
- package/dist/index.css +1441 -1440
- package/dist/index.js +589 -522
- package/dist/player.d.ts +216 -159
- package/dist/plugins/index.css +1204 -1203
- package/dist/plugins/index.js +581 -506
- package/docs/api/player.clapprstats.exportmetrics.md +1 -1
- package/docs/api/player.clapprstats.md +5 -15
- package/docs/api/player.clapprstatssettings.md +13 -0
- package/docs/api/player.clips.destroy.md +18 -0
- package/docs/api/player.clips.disable.md +18 -0
- package/docs/api/player.clips.enable.md +18 -0
- package/docs/api/player.clips.md +170 -0
- package/docs/api/player.clips.render.md +18 -0
- package/docs/api/player.clips.supportedversion.md +16 -0
- package/docs/api/player.clips.version.md +14 -0
- package/docs/api/player.clipspluginsettings.md +2 -2
- package/docs/api/player.clipspluginsettings.text.md +1 -1
- package/docs/api/player.md +27 -18
- package/docs/api/player.mediacontrol.md +1 -1
- package/docs/api/{player.mediacontrol.getelement.md → player.mediacontrol.mount.md} +20 -7
- package/docs/api/player.mediacontrolleftelement.md +1 -1
- package/docs/api/{player.clapprnerdstats._constructor_.md → player.nerdstats._constructor_.md} +3 -3
- package/docs/api/{player.clapprnerdstats.md → player.nerdstats.md} +5 -5
- package/docs/api/player.qualitylevel.height.md +1 -1
- package/docs/api/player.qualitylevel.level.md +1 -1
- package/docs/api/player.qualitylevel.md +4 -4
- package/docs/api/player.qualitylevel.width.md +1 -1
- package/docs/api/player.timeposition.current.md +1 -1
- package/docs/api/player.timeposition.md +2 -2
- package/docs/api/player.timeposition.total.md +1 -1
- package/docs/api/player.timeprogress.md +6 -4
- package/docs/api/player.timevalue.md +1 -1
- package/lib/index.plugins.d.ts +2 -1
- package/lib/index.plugins.d.ts.map +1 -1
- package/lib/index.plugins.js +2 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts +0 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +9 -12
- package/lib/playback/hls-playback/HlsPlayback.d.ts +1 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +0 -1
- package/lib/playback.types.d.ts +24 -12
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts +4 -0
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.js +20 -23
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts +86 -0
- package/lib/plugins/clappr-nerd-stats/NerdStats.d.ts.map +1 -0
- package/lib/plugins/clappr-nerd-stats/NerdStats.js +390 -0
- package/lib/plugins/clappr-nerd-stats/formatter.d.ts +5 -0
- package/lib/plugins/clappr-nerd-stats/formatter.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/formatter.js +56 -24
- package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts +2 -2
- package/lib/plugins/clappr-nerd-stats/speedtest/index.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts +1 -1
- package/lib/plugins/clappr-nerd-stats/speedtest/types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/types.d.ts +3 -0
- package/lib/plugins/clappr-nerd-stats/types.d.ts.map +1 -1
- package/lib/plugins/clappr-nerd-stats/utils.d.ts +7 -0
- package/lib/plugins/clappr-nerd-stats/utils.d.ts.map +1 -0
- package/lib/plugins/clappr-nerd-stats/utils.js +67 -0
- package/lib/plugins/clappr-stats/ClapprStats.d.ts +27 -32
- package/lib/plugins/clappr-stats/ClapprStats.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/ClapprStats.js +94 -202
- package/lib/plugins/clappr-stats/types.d.ts +65 -25
- package/lib/plugins/clappr-stats/types.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/types.js +37 -2
- package/lib/plugins/clappr-stats/utils.d.ts +1 -1
- package/lib/plugins/clappr-stats/utils.d.ts.map +1 -1
- package/lib/plugins/clappr-stats/utils.js +1 -3
- package/lib/plugins/seek-time/SeekTime.d.ts +1 -1
- package/lib/plugins/seek-time/SeekTime.d.ts.map +1 -1
- package/lib/plugins/seek-time/SeekTime.js +3 -4
- package/lib/testUtils.d.ts +2 -1
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +3 -2
- package/package.json +1 -1
- package/src/index.plugins.ts +2 -1
- package/src/playback/dash-playback/DashPlayback.ts +10 -15
- package/src/playback/hls-playback/HlsPlayback.ts +2 -4
- package/src/playback.types.ts +25 -11
- package/src/plugins/clappr-nerd-stats/NerdStats.ts +503 -0
- package/src/plugins/clappr-nerd-stats/formatter.ts +91 -47
- package/src/plugins/clappr-nerd-stats/speedtest/index.ts +2 -2
- package/src/plugins/clappr-nerd-stats/speedtest/types.ts +1 -1
- package/src/plugins/clappr-nerd-stats/types.ts +43 -3
- package/src/plugins/clappr-nerd-stats/utils.ts +75 -0
- package/src/plugins/clappr-stats/ClapprStats.ts +242 -306
- package/src/plugins/clappr-stats/__tests__/ClapprStats.test.ts +133 -0
- package/src/plugins/clappr-stats/types.ts +93 -47
- package/src/plugins/clappr-stats/utils.ts +4 -6
- package/src/plugins/error-screen/__tests__/ErrorScreen.test.ts +3 -4
- package/src/plugins/seek-time/SeekTime.ts +4 -5
- package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -0
- package/src/testUtils.ts +3 -2
- package/temp/player.api.json +311 -159
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.clapprstats.setupdatemetrics.md +0 -56
- package/docs/api/player.clipsplugin.gettext.md +0 -58
- package/docs/api/player.clipsplugin.md +0 -59
- package/src/plugins/clappr-nerd-stats/ClapprNerdStats.ts +0 -435
package/dist/plugins/index.js
CHANGED
|
@@ -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$
|
|
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$
|
|
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$
|
|
10044
|
+
trace(`${T$e} render`, {
|
|
10045
10045
|
el: !!this.$el,
|
|
10046
10046
|
});
|
|
10047
10047
|
this.$el.html(BigMuteButton.template());
|
|
@@ -10310,6 +10310,338 @@ class BottomGear extends UICorePlugin {
|
|
|
10310
10310
|
}
|
|
10311
10311
|
}
|
|
10312
10312
|
|
|
10313
|
+
/**
|
|
10314
|
+
* @beta
|
|
10315
|
+
*/
|
|
10316
|
+
var Chronograph;
|
|
10317
|
+
(function (Chronograph) {
|
|
10318
|
+
Chronograph["Startup"] = "startup";
|
|
10319
|
+
Chronograph["Watch"] = "watch";
|
|
10320
|
+
Chronograph["Pause"] = "pause";
|
|
10321
|
+
Chronograph["Buffering"] = "buffering";
|
|
10322
|
+
Chronograph["Session"] = "session";
|
|
10323
|
+
// Latency = 'latency',
|
|
10324
|
+
})(Chronograph || (Chronograph = {}));
|
|
10325
|
+
/**
|
|
10326
|
+
* @beta
|
|
10327
|
+
*/
|
|
10328
|
+
var Counter;
|
|
10329
|
+
(function (Counter) {
|
|
10330
|
+
Counter["Play"] = "play";
|
|
10331
|
+
Counter["Pause"] = "pause";
|
|
10332
|
+
Counter["Error"] = "error";
|
|
10333
|
+
Counter["Buffering"] = "buffering";
|
|
10334
|
+
Counter["DecodedFrames"] = "decodedFrames";
|
|
10335
|
+
Counter["DroppedFrames"] = "droppedFrames";
|
|
10336
|
+
Counter["Fps"] = "fps";
|
|
10337
|
+
Counter["ChangeLevel"] = "changeLevel";
|
|
10338
|
+
Counter["Seek"] = "seek";
|
|
10339
|
+
Counter["Fullscreen"] = "fullscreen";
|
|
10340
|
+
Counter["DvrUsage"] = "dvrUsage";
|
|
10341
|
+
})(Counter || (Counter = {}));
|
|
10342
|
+
/**
|
|
10343
|
+
* @beta
|
|
10344
|
+
*/
|
|
10345
|
+
var ClapprStatsEvents;
|
|
10346
|
+
(function (ClapprStatsEvents) {
|
|
10347
|
+
/**
|
|
10348
|
+
* Emitted periodically with current measurements.
|
|
10349
|
+
*/
|
|
10350
|
+
ClapprStatsEvents["REPORT"] = "clappr:stats:report";
|
|
10351
|
+
/**
|
|
10352
|
+
* Emitted when the playback reaches a certain percentage of the total duration.
|
|
10353
|
+
*/
|
|
10354
|
+
// PERCENTAGE = 'clappr:stats:percentage',
|
|
10355
|
+
})(ClapprStatsEvents || (ClapprStatsEvents = {}));
|
|
10356
|
+
|
|
10357
|
+
function newMetrics$1() {
|
|
10358
|
+
return {
|
|
10359
|
+
counters: {
|
|
10360
|
+
play: 0,
|
|
10361
|
+
pause: 0,
|
|
10362
|
+
error: 0,
|
|
10363
|
+
buffering: 0,
|
|
10364
|
+
decodedFrames: 0,
|
|
10365
|
+
droppedFrames: 0,
|
|
10366
|
+
fps: 0,
|
|
10367
|
+
changeLevel: 0,
|
|
10368
|
+
seek: 0,
|
|
10369
|
+
fullscreen: 0,
|
|
10370
|
+
dvrUsage: 0,
|
|
10371
|
+
},
|
|
10372
|
+
chrono: {
|
|
10373
|
+
startup: 0,
|
|
10374
|
+
watch: 0,
|
|
10375
|
+
pause: 0,
|
|
10376
|
+
buffering: 0,
|
|
10377
|
+
session: 0,
|
|
10378
|
+
},
|
|
10379
|
+
extra: {
|
|
10380
|
+
playbackName: '',
|
|
10381
|
+
playbackType: '',
|
|
10382
|
+
bitratesHistory: [],
|
|
10383
|
+
bitrateWeightedMean: 0,
|
|
10384
|
+
bitrateMostUsed: 0,
|
|
10385
|
+
buffersize: 0,
|
|
10386
|
+
watchHistory: [],
|
|
10387
|
+
watchedPercentage: 0,
|
|
10388
|
+
bufferingPercentage: 0,
|
|
10389
|
+
bandwidth: 0,
|
|
10390
|
+
duration: 0,
|
|
10391
|
+
currentTime: 0,
|
|
10392
|
+
},
|
|
10393
|
+
};
|
|
10394
|
+
}
|
|
10395
|
+
|
|
10396
|
+
/**
|
|
10397
|
+
* `PLUGIN` that measures data about playback, which can be useful for analyzing performance and UX.
|
|
10398
|
+
* @beta
|
|
10399
|
+
* @remarks
|
|
10400
|
+
* This plugin does not render anything and is supposed to be extended or used together with other plugins that actually render something.
|
|
10401
|
+
*
|
|
10402
|
+
* Configuration options - {@link ClapprStatsSettings}
|
|
10403
|
+
*
|
|
10404
|
+
* Events - {@link ClapprStatsEvents}
|
|
10405
|
+
*/
|
|
10406
|
+
class ClapprStats extends ContainerPlugin {
|
|
10407
|
+
timerId = null;
|
|
10408
|
+
lastDecodedFramesCount = 0;
|
|
10409
|
+
metrics = newMetrics$1();
|
|
10410
|
+
timers = {
|
|
10411
|
+
[Chronograph.Startup]: 0,
|
|
10412
|
+
[Chronograph.Watch]: 0,
|
|
10413
|
+
[Chronograph.Pause]: 0,
|
|
10414
|
+
[Chronograph.Buffering]: 0,
|
|
10415
|
+
[Chronograph.Session]: 0,
|
|
10416
|
+
};
|
|
10417
|
+
runEach;
|
|
10418
|
+
/**
|
|
10419
|
+
* @internal
|
|
10420
|
+
*/
|
|
10421
|
+
get name() {
|
|
10422
|
+
return 'clappr_stats';
|
|
10423
|
+
}
|
|
10424
|
+
/**
|
|
10425
|
+
* @internal
|
|
10426
|
+
*/
|
|
10427
|
+
get supportedVersion() {
|
|
10428
|
+
return { min: CLAPPR_VERSION$1 };
|
|
10429
|
+
}
|
|
10430
|
+
get playbackName() {
|
|
10431
|
+
return String(this.container.playback.name || '');
|
|
10432
|
+
}
|
|
10433
|
+
get playbackType() {
|
|
10434
|
+
return this.container.getPlaybackType();
|
|
10435
|
+
}
|
|
10436
|
+
now() {
|
|
10437
|
+
const hasPerformanceSupport = window.performance && typeof window.performance.now === 'function';
|
|
10438
|
+
return hasPerformanceSupport
|
|
10439
|
+
? window.performance.now()
|
|
10440
|
+
: new Date().getTime();
|
|
10441
|
+
}
|
|
10442
|
+
inc(counter) {
|
|
10443
|
+
this.metrics.counters[counter] += 1;
|
|
10444
|
+
}
|
|
10445
|
+
// _timerHasStarted(timer) {
|
|
10446
|
+
// return this[`_start${timer}`] !== undefined;
|
|
10447
|
+
// }
|
|
10448
|
+
start(timer) {
|
|
10449
|
+
// this[`_start${timer}`] = this._now();
|
|
10450
|
+
this.timers[timer] = this.now();
|
|
10451
|
+
}
|
|
10452
|
+
stop(timer) {
|
|
10453
|
+
// this._metrics.timers[timer] += this._now() - this[`_start${timer}`];
|
|
10454
|
+
this.metrics.chrono[timer] += this.now() - this.timers[timer];
|
|
10455
|
+
}
|
|
10456
|
+
constructor(container) {
|
|
10457
|
+
super(container);
|
|
10458
|
+
this.runEach = container.options.clapprStats?.runEach ?? 5000;
|
|
10459
|
+
}
|
|
10460
|
+
/**
|
|
10461
|
+
* @internal
|
|
10462
|
+
*/
|
|
10463
|
+
bindEvents() {
|
|
10464
|
+
this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onBitrate);
|
|
10465
|
+
this.listenTo(this.container, Events.CONTAINER_STOP, this.stopReporting);
|
|
10466
|
+
this.listenTo(this.container, Events.CONTAINER_ENDED, this.stopReporting);
|
|
10467
|
+
this.listenToOnce(this.container.playback, Events.PLAYBACK_PLAY_INTENT, this.startTimers);
|
|
10468
|
+
this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.onFirstPlaying);
|
|
10469
|
+
this.listenTo(this.container, Events.CONTAINER_PLAY, this.onPlay);
|
|
10470
|
+
this.listenTo(this.container, Events.CONTAINER_PAUSE, this.onPause);
|
|
10471
|
+
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering);
|
|
10472
|
+
this.listenTo(this.container, Events.CONTAINER_SEEK, this.onSeek);
|
|
10473
|
+
this.listenTo(this.container, Events.CONTAINER_ERROR, () => this.inc(Counter.Error));
|
|
10474
|
+
this.listenTo(this.container, Events.CONTAINER_FULLSCREEN, () => this.inc(Counter.Fullscreen));
|
|
10475
|
+
this.listenTo(this.container, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, (dvrInUse) => {
|
|
10476
|
+
dvrInUse && this.inc(Counter.DvrUsage);
|
|
10477
|
+
});
|
|
10478
|
+
this.listenTo(this.container.playback, Events.PLAYBACK_PROGRESS, this.onProgress);
|
|
10479
|
+
this.listenTo(this.container.playback, Events.PLAYBACK_TIMEUPDATE, this.onTimeUpdate);
|
|
10480
|
+
}
|
|
10481
|
+
/**
|
|
10482
|
+
* @internal
|
|
10483
|
+
*/
|
|
10484
|
+
destroy() {
|
|
10485
|
+
this.stopReporting();
|
|
10486
|
+
super.destroy();
|
|
10487
|
+
}
|
|
10488
|
+
/**
|
|
10489
|
+
* Returns the collected metrics.
|
|
10490
|
+
* @returns Measurements collected so far
|
|
10491
|
+
*/
|
|
10492
|
+
exportMetrics() {
|
|
10493
|
+
return structuredClone(this.metrics);
|
|
10494
|
+
}
|
|
10495
|
+
onBitrate(newBitrate) {
|
|
10496
|
+
const bitrate = newBitrate.bitrate;
|
|
10497
|
+
const now = this.now();
|
|
10498
|
+
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
10499
|
+
const last = this.metrics.extra.bitratesHistory[this.metrics.extra.bitratesHistory.length - 1];
|
|
10500
|
+
last.end = now;
|
|
10501
|
+
last.time = now - last.start;
|
|
10502
|
+
}
|
|
10503
|
+
this.metrics.extra.bitratesHistory.push({ start: this.now(), bitrate });
|
|
10504
|
+
this.inc(Counter.ChangeLevel);
|
|
10505
|
+
}
|
|
10506
|
+
stopReporting() {
|
|
10507
|
+
this.buildReport();
|
|
10508
|
+
if (this.timerId !== null) {
|
|
10509
|
+
clearInterval(this.timerId);
|
|
10510
|
+
this.timerId = null;
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
startTimers() {
|
|
10514
|
+
this.timerId = setInterval(this.buildReport.bind(this), this.runEach);
|
|
10515
|
+
this.start(Chronograph.Session);
|
|
10516
|
+
this.start(Chronograph.Startup);
|
|
10517
|
+
}
|
|
10518
|
+
onFirstPlaying() {
|
|
10519
|
+
this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
10520
|
+
this.start(Chronograph.Watch);
|
|
10521
|
+
this.stop(Chronograph.Startup);
|
|
10522
|
+
}
|
|
10523
|
+
playAfterPause() {
|
|
10524
|
+
this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
10525
|
+
this.stop(Chronograph.Pause);
|
|
10526
|
+
this.start(Chronograph.Watch);
|
|
10527
|
+
}
|
|
10528
|
+
onPlay() {
|
|
10529
|
+
this.inc(Counter.Play);
|
|
10530
|
+
}
|
|
10531
|
+
onPause() {
|
|
10532
|
+
this.stop(Chronograph.Watch);
|
|
10533
|
+
this.start(Chronograph.Pause);
|
|
10534
|
+
this.inc(Counter.Pause);
|
|
10535
|
+
this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.playAfterPause);
|
|
10536
|
+
this.stopListening(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
10537
|
+
}
|
|
10538
|
+
onSeek(e) {
|
|
10539
|
+
this.inc(Counter.Seek);
|
|
10540
|
+
this.metrics.extra.watchHistory.push([e * 1000, e * 1000]);
|
|
10541
|
+
}
|
|
10542
|
+
onTimeUpdate(e) {
|
|
10543
|
+
const current = e.current * 1000, total = e.total * 1000, l = this.metrics.extra.watchHistory.length;
|
|
10544
|
+
this.metrics.extra.duration = total;
|
|
10545
|
+
this.metrics.extra.currentTime = current;
|
|
10546
|
+
// TODO what if it's a live stream?
|
|
10547
|
+
this.metrics.extra.watchedPercentage = (current / total) * 100;
|
|
10548
|
+
if (l === 0) {
|
|
10549
|
+
this.metrics.extra.watchHistory.push([current, current]);
|
|
10550
|
+
}
|
|
10551
|
+
else {
|
|
10552
|
+
this.metrics.extra.watchHistory[l - 1][1] = current;
|
|
10553
|
+
}
|
|
10554
|
+
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
10555
|
+
const lastBitrate = this.metrics.extra.bitratesHistory[this.metrics.extra.bitratesHistory.length - 1];
|
|
10556
|
+
if (!lastBitrate.end) {
|
|
10557
|
+
lastBitrate.time = this.now() - lastBitrate.start;
|
|
10558
|
+
}
|
|
10559
|
+
}
|
|
10560
|
+
this.onCompletion();
|
|
10561
|
+
}
|
|
10562
|
+
onContainerUpdateWhilePlaying() {
|
|
10563
|
+
if (this.container.playback.isPlaying()) {
|
|
10564
|
+
this.stop(Chronograph.Watch);
|
|
10565
|
+
this.start(Chronograph.Watch);
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
onBuffering() {
|
|
10569
|
+
this.inc(Counter.Buffering);
|
|
10570
|
+
this.start(Chronograph.Buffering);
|
|
10571
|
+
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferfull);
|
|
10572
|
+
}
|
|
10573
|
+
onBufferfull() {
|
|
10574
|
+
this.stop(Chronograph.Buffering);
|
|
10575
|
+
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering);
|
|
10576
|
+
}
|
|
10577
|
+
onProgress(progress) {
|
|
10578
|
+
this.metrics.extra.buffersize = progress.current * 1000;
|
|
10579
|
+
}
|
|
10580
|
+
onCompletion() {
|
|
10581
|
+
// Decide if this is needed
|
|
10582
|
+
// const currentPercentage = this.metrics.extra.watchedPercentage;
|
|
10583
|
+
// this.trigger(ClapprStatsEvents.PERCENTAGE, currentPercentage);
|
|
10584
|
+
}
|
|
10585
|
+
buildReport() {
|
|
10586
|
+
this.stop(Chronograph.Session);
|
|
10587
|
+
this.start(Chronograph.Session);
|
|
10588
|
+
this.metrics.extra.playbackName = this.playbackName;
|
|
10589
|
+
this.metrics.extra.playbackType = this.playbackType;
|
|
10590
|
+
this.calcBitrates();
|
|
10591
|
+
this.calcBufferingPercentage();
|
|
10592
|
+
// TODO calc FPS properly, e.g., on TIMEUPDATE event
|
|
10593
|
+
this.fetchFPS();
|
|
10594
|
+
this.trigger(ClapprStatsEvents.REPORT, structuredClone(this.metrics));
|
|
10595
|
+
}
|
|
10596
|
+
fetchFPS() {
|
|
10597
|
+
// TODO check if the playback and media sources support video, then use the common method
|
|
10598
|
+
// flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds)
|
|
10599
|
+
// hls ??? (use the same?)
|
|
10600
|
+
const fetchFPS = {
|
|
10601
|
+
html5_video: this.html5FetchFPS,
|
|
10602
|
+
hls: this.html5FetchFPS,
|
|
10603
|
+
dash: this.html5FetchFPS,
|
|
10604
|
+
};
|
|
10605
|
+
if (this.playbackName in fetchFPS) {
|
|
10606
|
+
fetchFPS[this.playbackName].call(this);
|
|
10607
|
+
}
|
|
10608
|
+
}
|
|
10609
|
+
// TODO sort out
|
|
10610
|
+
calcBitrates() {
|
|
10611
|
+
const { bitratesHistory } = this.metrics.extra;
|
|
10612
|
+
if (bitratesHistory.length === 0) {
|
|
10613
|
+
return;
|
|
10614
|
+
}
|
|
10615
|
+
let totalTime = 0;
|
|
10616
|
+
let weightedTotal = 0;
|
|
10617
|
+
for (const { bitrate, time = 0 } of bitratesHistory) {
|
|
10618
|
+
totalTime += time;
|
|
10619
|
+
weightedTotal += bitrate * time;
|
|
10620
|
+
}
|
|
10621
|
+
this.metrics.extra.bitrateWeightedMean = weightedTotal / totalTime;
|
|
10622
|
+
this.metrics.extra.bitrateMostUsed = bitratesHistory.reduce((mostUsed, current) => (current.time || 0) > (mostUsed.time || 0) ? current : mostUsed, { time: 0, bitrate: 0, start: 0, end: 0 }).bitrate;
|
|
10623
|
+
}
|
|
10624
|
+
calcBufferingPercentage() {
|
|
10625
|
+
if (this.metrics.extra.duration > 0) {
|
|
10626
|
+
this.metrics.extra.bufferingPercentage =
|
|
10627
|
+
(this.metrics.chrono.buffering / this.metrics.extra.duration) * 100;
|
|
10628
|
+
}
|
|
10629
|
+
}
|
|
10630
|
+
html5FetchFPS() {
|
|
10631
|
+
const videoTag = this.container.playback.el;
|
|
10632
|
+
const getFirstValidValue = (...args) => args.find((val) => val !== undefined);
|
|
10633
|
+
const decodedFrames = getFirstValidValue(videoTag.webkitDecodedFrameCount, videoTag.mozDecodedFrames, 0);
|
|
10634
|
+
const droppedFrames = getFirstValidValue(videoTag.webkitDroppedFrameCount, videoTag.mozParsedFrames && videoTag.mozDecodedFrames
|
|
10635
|
+
? videoTag.mozParsedFrames - videoTag.mozDecodedFrames
|
|
10636
|
+
: 0, 0);
|
|
10637
|
+
const delta = decodedFrames - (this.lastDecodedFramesCount || 0);
|
|
10638
|
+
this.metrics.counters.decodedFrames = decodedFrames;
|
|
10639
|
+
this.metrics.counters.droppedFrames = droppedFrames;
|
|
10640
|
+
this.metrics.counters.fps = delta / (this.runEach / 1000); // TODO use time delta instead of runEach
|
|
10641
|
+
this.lastDecodedFramesCount = decodedFrames;
|
|
10642
|
+
}
|
|
10643
|
+
}
|
|
10644
|
+
|
|
10313
10645
|
function getDefaultExportFromCjs (x) {
|
|
10314
10646
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
10315
10647
|
}
|
|
@@ -11381,57 +11713,7 @@ function requireMousetrap () {
|
|
|
11381
11713
|
var mousetrapExports = requireMousetrap();
|
|
11382
11714
|
const Mousetrap = /*@__PURE__*/getDefaultExportFromCjs(mousetrapExports);
|
|
11383
11715
|
|
|
11384
|
-
|
|
11385
|
-
* @beta
|
|
11386
|
-
*/
|
|
11387
|
-
var ClapprStatsEvents;
|
|
11388
|
-
(function (ClapprStatsEvents) {
|
|
11389
|
-
ClapprStatsEvents["REPORT_EVENT"] = "clappr:stats:report";
|
|
11390
|
-
ClapprStatsEvents["PERCENTAGE_EVENT"] = "clappr:stats:percentage";
|
|
11391
|
-
})(ClapprStatsEvents || (ClapprStatsEvents = {}));
|
|
11392
|
-
|
|
11393
|
-
function newMetrics$1() {
|
|
11394
|
-
return {
|
|
11395
|
-
counters: {
|
|
11396
|
-
play: 0,
|
|
11397
|
-
pause: 0,
|
|
11398
|
-
error: 0,
|
|
11399
|
-
buffering: 0,
|
|
11400
|
-
decodedFrames: 0,
|
|
11401
|
-
droppedFrames: 0,
|
|
11402
|
-
fps: 0,
|
|
11403
|
-
changeLevel: 0,
|
|
11404
|
-
seek: 0,
|
|
11405
|
-
fullscreen: 0,
|
|
11406
|
-
dvrUsage: 0,
|
|
11407
|
-
},
|
|
11408
|
-
timers: {
|
|
11409
|
-
startup: 0,
|
|
11410
|
-
watch: 0,
|
|
11411
|
-
pause: 0,
|
|
11412
|
-
buffering: 0,
|
|
11413
|
-
session: 0,
|
|
11414
|
-
latency: 0,
|
|
11415
|
-
},
|
|
11416
|
-
extra: {
|
|
11417
|
-
playbackName: '',
|
|
11418
|
-
playbackType: '',
|
|
11419
|
-
bitratesHistory: [],
|
|
11420
|
-
bitrateWeightedMean: 0,
|
|
11421
|
-
bitrateMostUsed: 0,
|
|
11422
|
-
buffersize: 0,
|
|
11423
|
-
watchHistory: [],
|
|
11424
|
-
watchedPercentage: 0,
|
|
11425
|
-
bufferingPercentage: 0,
|
|
11426
|
-
bandwidth: 0,
|
|
11427
|
-
duration: 0,
|
|
11428
|
-
currentTime: 0,
|
|
11429
|
-
},
|
|
11430
|
-
custom: {},
|
|
11431
|
-
};
|
|
11432
|
-
}
|
|
11433
|
-
|
|
11434
|
-
var humanFormat$2 = {exports: {}};
|
|
11716
|
+
var humanFormat$2 = {exports: {}};
|
|
11435
11717
|
|
|
11436
11718
|
var humanFormat$1 = humanFormat$2.exports;
|
|
11437
11719
|
|
|
@@ -11805,63 +12087,72 @@ const timeScale = new humanFormat.Scale({
|
|
|
11805
12087
|
ms: 1,
|
|
11806
12088
|
sec: 1000,
|
|
11807
12089
|
min: 60000,
|
|
11808
|
-
hours: 3600000
|
|
12090
|
+
hours: 3600000,
|
|
11809
12091
|
});
|
|
11810
12092
|
const percentScale = new humanFormat.Scale({
|
|
11811
|
-
'%': 1
|
|
12093
|
+
'%': 1,
|
|
11812
12094
|
});
|
|
12095
|
+
const metricTemplates = {
|
|
12096
|
+
fps: {
|
|
12097
|
+
scale: 'SI',
|
|
12098
|
+
decimals: 0,
|
|
12099
|
+
},
|
|
12100
|
+
volume: {
|
|
12101
|
+
scale: percentScale,
|
|
12102
|
+
},
|
|
12103
|
+
};
|
|
11813
12104
|
const formattingTemplate = {
|
|
11814
12105
|
general: {
|
|
11815
12106
|
volume: {
|
|
11816
|
-
scale: percentScale
|
|
11817
|
-
}
|
|
12107
|
+
scale: percentScale,
|
|
12108
|
+
},
|
|
11818
12109
|
},
|
|
11819
12110
|
timers: {
|
|
11820
12111
|
startup: {
|
|
11821
|
-
scale: timeScale
|
|
12112
|
+
scale: timeScale,
|
|
11822
12113
|
},
|
|
11823
12114
|
watch: {
|
|
11824
|
-
scale: timeScale
|
|
12115
|
+
scale: timeScale,
|
|
11825
12116
|
},
|
|
11826
12117
|
pause: {
|
|
11827
|
-
scale: timeScale
|
|
12118
|
+
scale: timeScale,
|
|
11828
12119
|
},
|
|
11829
12120
|
buffering: {
|
|
11830
|
-
scale: timeScale
|
|
12121
|
+
scale: timeScale,
|
|
11831
12122
|
},
|
|
11832
12123
|
session: {
|
|
11833
|
-
scale: timeScale
|
|
12124
|
+
scale: timeScale,
|
|
11834
12125
|
},
|
|
11835
12126
|
latency: {
|
|
11836
|
-
scale: timeScale
|
|
11837
|
-
}
|
|
12127
|
+
scale: timeScale,
|
|
12128
|
+
},
|
|
11838
12129
|
},
|
|
11839
12130
|
extra: {
|
|
11840
12131
|
buffersize: {
|
|
11841
|
-
scale: timeScale
|
|
12132
|
+
scale: timeScale,
|
|
11842
12133
|
},
|
|
11843
12134
|
duration: {
|
|
11844
|
-
scale: timeScale
|
|
12135
|
+
scale: timeScale,
|
|
11845
12136
|
},
|
|
11846
12137
|
currentTime: {
|
|
11847
|
-
scale: timeScale
|
|
12138
|
+
scale: timeScale,
|
|
11848
12139
|
},
|
|
11849
12140
|
bitrateWeightedMean: {
|
|
11850
|
-
unit: 'bps'
|
|
12141
|
+
unit: 'bps',
|
|
11851
12142
|
},
|
|
11852
12143
|
bitrateMostUsed: {
|
|
11853
|
-
unit: 'bps'
|
|
12144
|
+
unit: 'bps',
|
|
11854
12145
|
},
|
|
11855
12146
|
bandwidth: {
|
|
11856
|
-
unit: 'bps'
|
|
12147
|
+
unit: 'bps',
|
|
11857
12148
|
},
|
|
11858
12149
|
watchedPercentage: {
|
|
11859
|
-
scale: percentScale
|
|
12150
|
+
scale: percentScale,
|
|
11860
12151
|
},
|
|
11861
12152
|
bufferingPercentage: {
|
|
11862
|
-
scale: percentScale
|
|
11863
|
-
}
|
|
11864
|
-
}
|
|
12153
|
+
scale: percentScale,
|
|
12154
|
+
},
|
|
12155
|
+
},
|
|
11865
12156
|
};
|
|
11866
12157
|
class Formatter {
|
|
11867
12158
|
static format(metrics) {
|
|
@@ -11871,8 +12162,10 @@ class Formatter {
|
|
|
11871
12162
|
formattedMetrics[type] = fmt;
|
|
11872
12163
|
const typeTemplate = formattingTemplate[type];
|
|
11873
12164
|
Object.entries(mm).forEach(([name, value]) => {
|
|
11874
|
-
|
|
11875
|
-
|
|
12165
|
+
if (typeTemplate &&
|
|
12166
|
+
typeTemplate[name] &&
|
|
12167
|
+
typeof value === 'number' &&
|
|
12168
|
+
!isNaN(value)) {
|
|
11876
12169
|
// @ts-ignore
|
|
11877
12170
|
const templateScale = typeTemplate[name].scale || 'SI';
|
|
11878
12171
|
// @ts-ignore
|
|
@@ -11880,7 +12173,7 @@ class Formatter {
|
|
|
11880
12173
|
fmt[name] = humanFormat(value, {
|
|
11881
12174
|
scale: templateScale,
|
|
11882
12175
|
unit: templateUnit,
|
|
11883
|
-
decimals: 2
|
|
12176
|
+
decimals: 2,
|
|
11884
12177
|
});
|
|
11885
12178
|
}
|
|
11886
12179
|
else {
|
|
@@ -11890,6 +12183,27 @@ class Formatter {
|
|
|
11890
12183
|
});
|
|
11891
12184
|
return formattedMetrics;
|
|
11892
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
|
+
}
|
|
11893
12207
|
}
|
|
11894
12208
|
|
|
11895
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";
|
|
@@ -12372,7 +12686,7 @@ function rankConnectionSpeed(dlSpeed) {
|
|
|
12372
12686
|
return 0;
|
|
12373
12687
|
}
|
|
12374
12688
|
|
|
12375
|
-
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>×<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";
|
|
12376
12690
|
|
|
12377
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";
|
|
12378
12692
|
|
|
@@ -12445,6 +12759,13 @@ const drawSummary = (customMetrics, vodContainer, liveContainer) => {
|
|
|
12445
12759
|
vodContainer.html(vodHtml);
|
|
12446
12760
|
liveContainer.html(liveHtml);
|
|
12447
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';
|
|
12448
12769
|
/**
|
|
12449
12770
|
* `PLUGIN` that displays useful network-related statistics.
|
|
12450
12771
|
* @beta
|
|
@@ -12461,15 +12782,15 @@ const drawSummary = (customMetrics, vodContainer, liveContainer) => {
|
|
|
12461
12782
|
* When clicked, it shows an overlay window with the information about the network speed, latency, etc,
|
|
12462
12783
|
* and recommended quality level.
|
|
12463
12784
|
*/
|
|
12464
|
-
class
|
|
12785
|
+
class NerdStats extends UICorePlugin {
|
|
12465
12786
|
container = null;
|
|
12466
|
-
|
|
12787
|
+
speedtestMetrics = {
|
|
12467
12788
|
connectionSpeed: 0,
|
|
12468
12789
|
ping: 0,
|
|
12469
12790
|
jitter: 0,
|
|
12470
12791
|
};
|
|
12471
12792
|
metrics = newMetrics();
|
|
12472
|
-
|
|
12793
|
+
open = false;
|
|
12473
12794
|
shortcut;
|
|
12474
12795
|
iconPosition;
|
|
12475
12796
|
static buttonTemplate = tmpl(buttonHtml$3);
|
|
@@ -12491,7 +12812,6 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12491
12812
|
*/
|
|
12492
12813
|
get attributes() {
|
|
12493
12814
|
return {
|
|
12494
|
-
'data-clappr-nerd-stats': '',
|
|
12495
12815
|
class: 'clappr-nerd-stats',
|
|
12496
12816
|
};
|
|
12497
12817
|
}
|
|
@@ -12500,13 +12820,17 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12500
12820
|
*/
|
|
12501
12821
|
get events() {
|
|
12502
12822
|
return {
|
|
12503
|
-
|
|
12504
|
-
'click
|
|
12505
|
-
'click
|
|
12823
|
+
click: 'clicked',
|
|
12824
|
+
'click #nerd-stats-close': 'hide',
|
|
12825
|
+
'click #nerd-stats-refresh': 'refreshSpeedTest',
|
|
12506
12826
|
};
|
|
12507
12827
|
}
|
|
12828
|
+
clicked(e) {
|
|
12829
|
+
e.stopPropagation();
|
|
12830
|
+
e.preventDefault();
|
|
12831
|
+
}
|
|
12508
12832
|
get statsBoxElem() {
|
|
12509
|
-
return '
|
|
12833
|
+
return this.$el.find('#nerd-stats-box');
|
|
12510
12834
|
}
|
|
12511
12835
|
get statsBoxWidthThreshold() {
|
|
12512
12836
|
return 720;
|
|
@@ -12525,7 +12849,7 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12525
12849
|
];
|
|
12526
12850
|
this.iconPosition =
|
|
12527
12851
|
core.options.clapprNerdStats?.iconPosition ?? 'bottom-right';
|
|
12528
|
-
this.
|
|
12852
|
+
this.speedtestMetrics = {
|
|
12529
12853
|
connectionSpeed: 0,
|
|
12530
12854
|
ping: 0,
|
|
12531
12855
|
jitter: 0,
|
|
@@ -12537,41 +12861,60 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12537
12861
|
*/
|
|
12538
12862
|
bindEvents() {
|
|
12539
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);
|
|
12540
12866
|
}
|
|
12541
12867
|
onCoreReady() {
|
|
12542
12868
|
const bottomGear = this.core.getPlugin('bottom_gear');
|
|
12543
12869
|
assert(bottomGear, 'bottom_gear plugin is required');
|
|
12544
|
-
this.listenTo(bottomGear, GearEvents.RENDERED, this.
|
|
12870
|
+
this.listenTo(bottomGear, GearEvents.RENDERED, this.attach);
|
|
12871
|
+
Mousetrap.bind(this.shortcut, this.toggle);
|
|
12872
|
+
this.updateResolution();
|
|
12873
|
+
}
|
|
12874
|
+
onActiveContainerChanged() {
|
|
12545
12875
|
this.container = this.core.activeContainer;
|
|
12546
12876
|
const clapprStats = this.container?.getPlugin('clappr_stats');
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
}
|
|
12877
|
+
assert(clapprStats, 'clappr-stats not available. Please, include it as a plugin of your Clappr instance.\n' +
|
|
12878
|
+
'For more info, visit: https://github.com/clappr/clappr-stats.');
|
|
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
|
+
});
|
|
12891
|
+
this.updateMetrics(clapprStats.exportMetrics());
|
|
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);
|
|
12561
12896
|
}
|
|
12562
|
-
|
|
12563
|
-
|
|
12897
|
+
/**
|
|
12898
|
+
* @internal
|
|
12899
|
+
*/
|
|
12900
|
+
destroy() {
|
|
12901
|
+
Mousetrap.unbind(this.shortcut);
|
|
12902
|
+
return super.destroy();
|
|
12903
|
+
}
|
|
12904
|
+
toggle = () => {
|
|
12905
|
+
if (this.open) {
|
|
12564
12906
|
this.hide();
|
|
12565
12907
|
}
|
|
12566
12908
|
else {
|
|
12567
12909
|
this.show();
|
|
12568
12910
|
}
|
|
12569
|
-
}
|
|
12911
|
+
};
|
|
12570
12912
|
show() {
|
|
12571
|
-
this
|
|
12572
|
-
this.
|
|
12913
|
+
this.$el.show();
|
|
12914
|
+
this.statsBoxElem.scrollTop(this.statsBoxElem.scrollTop());
|
|
12915
|
+
this.open = true;
|
|
12573
12916
|
this.refreshSpeedTest();
|
|
12574
|
-
initSpeedTest(this.
|
|
12917
|
+
initSpeedTest(this.speedtestMetrics)
|
|
12575
12918
|
.then(() => {
|
|
12576
12919
|
startSpeedtest();
|
|
12577
12920
|
})
|
|
@@ -12580,21 +12923,27 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12580
12923
|
});
|
|
12581
12924
|
}
|
|
12582
12925
|
hide() {
|
|
12583
|
-
this
|
|
12584
|
-
this.
|
|
12926
|
+
this.$el.hide();
|
|
12927
|
+
this.open = false;
|
|
12585
12928
|
stopSpeedtest();
|
|
12586
12929
|
}
|
|
12587
12930
|
onPlayerResize() {
|
|
12588
12931
|
this.setStatsBoxSize();
|
|
12932
|
+
this.updateResolution();
|
|
12589
12933
|
}
|
|
12590
|
-
|
|
12591
|
-
this.metrics.general = {
|
|
12592
|
-
|
|
12593
|
-
|
|
12934
|
+
updateResolution() {
|
|
12935
|
+
this.metrics.general.resolution = {
|
|
12936
|
+
width: this.playerWidth,
|
|
12937
|
+
height: this.playerHeight,
|
|
12594
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);
|
|
12595
12945
|
}
|
|
12596
|
-
|
|
12597
|
-
this.metrics.custom = this.customMetrics;
|
|
12946
|
+
estimateQuality() {
|
|
12598
12947
|
const videoQualityNames = [
|
|
12599
12948
|
'SD (480p)',
|
|
12600
12949
|
'HD (720p)',
|
|
@@ -12602,9 +12951,9 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12602
12951
|
'2K (1440p)',
|
|
12603
12952
|
'4K (2160p)',
|
|
12604
12953
|
];
|
|
12605
|
-
const { connectionSpeed, ping } = this.
|
|
12954
|
+
const { connectionSpeed, ping } = this.speedtestMetrics;
|
|
12606
12955
|
if (!connectionSpeed || !ping) {
|
|
12607
|
-
const calculatingText = '
|
|
12956
|
+
const calculatingText = this.core.i18n.t('stats.calculating');
|
|
12608
12957
|
this.metrics.custom.vodQuality = calculatingText;
|
|
12609
12958
|
this.metrics.custom.liveQuality = calculatingText;
|
|
12610
12959
|
return;
|
|
@@ -12619,46 +12968,112 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12619
12968
|
prefix + videoQualityNames[liveQuality - 1];
|
|
12620
12969
|
}
|
|
12621
12970
|
updateMetrics(metrics) {
|
|
12971
|
+
trace(`${T$d} updateMetrics`, { custom: this.speedtestMetrics });
|
|
12622
12972
|
Object.assign(this.metrics, metrics);
|
|
12623
|
-
this.
|
|
12624
|
-
this
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
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));
|
|
12630
13033
|
this.setStatsBoxSize();
|
|
12631
13034
|
drawSpeedTestResults();
|
|
12632
|
-
drawSummary(this.
|
|
12633
|
-
|
|
12634
|
-
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) {
|
|
12635
13037
|
this.hide();
|
|
12636
13038
|
}
|
|
12637
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
|
+
}
|
|
12638
13049
|
setStatsBoxSize() {
|
|
12639
13050
|
if (this.playerWidth >= this.statsBoxWidthThreshold) {
|
|
12640
|
-
this
|
|
12641
|
-
this
|
|
13051
|
+
this.statsBoxElem.addClass('wide');
|
|
13052
|
+
this.statsBoxElem.removeClass('narrow');
|
|
12642
13053
|
}
|
|
12643
13054
|
else {
|
|
12644
|
-
this
|
|
12645
|
-
this
|
|
13055
|
+
this.statsBoxElem.removeClass('wide');
|
|
13056
|
+
this.statsBoxElem.addClass('narrow');
|
|
12646
13057
|
}
|
|
12647
13058
|
}
|
|
12648
13059
|
/**
|
|
12649
13060
|
* @internal
|
|
12650
13061
|
*/
|
|
12651
13062
|
render() {
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
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();
|
|
12655
13070
|
return this;
|
|
12656
13071
|
}
|
|
12657
|
-
|
|
13072
|
+
attach() {
|
|
12658
13073
|
const gear = this.core.getPlugin('bottom_gear');
|
|
12659
13074
|
gear
|
|
12660
|
-
.addItem('
|
|
12661
|
-
.html(
|
|
13075
|
+
.addItem('nerd_stats')
|
|
13076
|
+
.html(NerdStats.buttonTemplate({
|
|
12662
13077
|
icon: statsIcon,
|
|
12663
13078
|
i18n: this.core.i18n,
|
|
12664
13079
|
}))
|
|
@@ -12667,11 +13082,11 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12667
13082
|
this.toggle();
|
|
12668
13083
|
});
|
|
12669
13084
|
}
|
|
12670
|
-
|
|
13085
|
+
clearSpeedtestMetrics() {
|
|
12671
13086
|
const clapprStats = this.container?.getPlugin('clappr_stats');
|
|
12672
|
-
this.
|
|
12673
|
-
this.
|
|
12674
|
-
this.
|
|
13087
|
+
this.speedtestMetrics.connectionSpeed = 0;
|
|
13088
|
+
this.speedtestMetrics.ping = 0;
|
|
13089
|
+
this.speedtestMetrics.jitter = 0;
|
|
12675
13090
|
if (clapprStats) {
|
|
12676
13091
|
this.updateMetrics(clapprStats.exportMetrics());
|
|
12677
13092
|
}
|
|
@@ -12679,7 +13094,7 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12679
13094
|
refreshSpeedTest() {
|
|
12680
13095
|
stopSpeedtest();
|
|
12681
13096
|
setTimeout(() => {
|
|
12682
|
-
this.
|
|
13097
|
+
this.clearSpeedtestMetrics();
|
|
12683
13098
|
clearSpeedTestResults();
|
|
12684
13099
|
drawSpeedTestResults();
|
|
12685
13100
|
}, 200);
|
|
@@ -12687,11 +13102,28 @@ class ClapprNerdStats extends UICorePlugin {
|
|
|
12687
13102
|
startSpeedtest();
|
|
12688
13103
|
}, 800);
|
|
12689
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
|
+
}
|
|
12690
13115
|
}
|
|
12691
13116
|
function newMetrics() {
|
|
12692
13117
|
return {
|
|
12693
13118
|
...newMetrics$1(),
|
|
12694
|
-
general: {
|
|
13119
|
+
general: {
|
|
13120
|
+
displayResolution: '',
|
|
13121
|
+
resolution: {
|
|
13122
|
+
width: 0,
|
|
13123
|
+
height: 0,
|
|
13124
|
+
},
|
|
13125
|
+
volume: 0,
|
|
13126
|
+
},
|
|
12695
13127
|
custom: {
|
|
12696
13128
|
connectionSpeed: 0,
|
|
12697
13129
|
ping: 0,
|
|
@@ -12700,362 +13132,6 @@ function newMetrics() {
|
|
|
12700
13132
|
};
|
|
12701
13133
|
}
|
|
12702
13134
|
|
|
12703
|
-
// TODO: fix
|
|
12704
|
-
const updateMetrics = () => { };
|
|
12705
|
-
/**
|
|
12706
|
-
* `PLUGIN` that collects useful statistics about playback performance.
|
|
12707
|
-
* @beta
|
|
12708
|
-
* @remarks
|
|
12709
|
-
* This plugin does not render anything and is supposed to be extended or used together with other plugins that actually render something.
|
|
12710
|
-
*/
|
|
12711
|
-
class ClapprStats extends ContainerPlugin {
|
|
12712
|
-
bwMeasureCount = 0;
|
|
12713
|
-
intervalId = null;
|
|
12714
|
-
lastDecodedFramesCount = 0;
|
|
12715
|
-
metrics = newMetrics$1();
|
|
12716
|
-
completion;
|
|
12717
|
-
_onReport;
|
|
12718
|
-
runBandwidthTestEvery;
|
|
12719
|
-
runEach;
|
|
12720
|
-
timers = {
|
|
12721
|
-
startup: 0,
|
|
12722
|
-
watch: 0,
|
|
12723
|
-
pause: 0,
|
|
12724
|
-
buffering: 0,
|
|
12725
|
-
session: 0,
|
|
12726
|
-
latency: 0,
|
|
12727
|
-
};
|
|
12728
|
-
updateFn = updateMetrics;
|
|
12729
|
-
urisToMeasureBandwidth;
|
|
12730
|
-
uriToMeasureLatency;
|
|
12731
|
-
/**
|
|
12732
|
-
* @internal
|
|
12733
|
-
*/
|
|
12734
|
-
get name() {
|
|
12735
|
-
return 'clappr_stats';
|
|
12736
|
-
}
|
|
12737
|
-
/**
|
|
12738
|
-
* @internal
|
|
12739
|
-
*/
|
|
12740
|
-
get supportedVersion() {
|
|
12741
|
-
return { min: CLAPPR_VERSION$1 };
|
|
12742
|
-
}
|
|
12743
|
-
get _playbackName() {
|
|
12744
|
-
return String(this.container.playback.name || '');
|
|
12745
|
-
}
|
|
12746
|
-
get _playbackType() {
|
|
12747
|
-
return this.container.getPlaybackType();
|
|
12748
|
-
}
|
|
12749
|
-
_now() {
|
|
12750
|
-
const hasPerformanceSupport = window.performance && typeof (window.performance.now) === 'function';
|
|
12751
|
-
return (hasPerformanceSupport) ? window.performance.now() : new Date().getTime();
|
|
12752
|
-
}
|
|
12753
|
-
_inc(counter) {
|
|
12754
|
-
this.metrics.counters[counter] += 1;
|
|
12755
|
-
}
|
|
12756
|
-
// _timerHasStarted(timer) {
|
|
12757
|
-
// return this[`_start${timer}`] !== undefined;
|
|
12758
|
-
// }
|
|
12759
|
-
start(timer) {
|
|
12760
|
-
// this[`_start${timer}`] = this._now();
|
|
12761
|
-
this.timers[timer] = this._now();
|
|
12762
|
-
}
|
|
12763
|
-
_stop(timer) {
|
|
12764
|
-
// this._metrics.timers[timer] += this._now() - this[`_start${timer}`];
|
|
12765
|
-
this.metrics.timers[timer] += this._now() - this.timers[timer];
|
|
12766
|
-
}
|
|
12767
|
-
/**
|
|
12768
|
-
* Registers a callback to receive the metrics.
|
|
12769
|
-
* @param updateMetricsFn - The callback to receive the metrics
|
|
12770
|
-
*/
|
|
12771
|
-
setUpdateMetrics(updateMetricsFn) {
|
|
12772
|
-
// TODO use events instead
|
|
12773
|
-
this.updateFn = updateMetricsFn;
|
|
12774
|
-
}
|
|
12775
|
-
_defaultReport(metrics) {
|
|
12776
|
-
this.updateFn(metrics);
|
|
12777
|
-
}
|
|
12778
|
-
constructor(container) {
|
|
12779
|
-
super(container);
|
|
12780
|
-
this.runEach = container.options.clapprStats?.runEach ?? 5000;
|
|
12781
|
-
this._onReport = container.options.clapprStats?.onReport ?? this._defaultReport;
|
|
12782
|
-
this.uriToMeasureLatency = container.options.clapprStats?.uriToMeasureLatency;
|
|
12783
|
-
this.urisToMeasureBandwidth = container.options.clapprStats?.urisToMeasureBandwidth;
|
|
12784
|
-
this.runBandwidthTestEvery = container.options.clapprStats?.runBandwidthTestEvery ?? 10;
|
|
12785
|
-
this.completion = {
|
|
12786
|
-
watch: container.options.clapprStats?.onCompletion ?? [],
|
|
12787
|
-
calls: []
|
|
12788
|
-
};
|
|
12789
|
-
}
|
|
12790
|
-
/**
|
|
12791
|
-
* @internal
|
|
12792
|
-
*/
|
|
12793
|
-
bindEvents() {
|
|
12794
|
-
this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onBitrate);
|
|
12795
|
-
this.listenTo(this.container, Events.CONTAINER_STOP, this.stopReporting);
|
|
12796
|
-
this.listenTo(this.container, Events.CONTAINER_ENDED, this.stopReporting);
|
|
12797
|
-
this.listenToOnce(this.container.playback, Events.PLAYBACK_PLAY_INTENT, this.startTimers);
|
|
12798
|
-
this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.onFirstPlaying);
|
|
12799
|
-
this.listenTo(this.container, Events.CONTAINER_PLAY, this.onPlay);
|
|
12800
|
-
this.listenTo(this.container, Events.CONTAINER_PAUSE, this.onPause);
|
|
12801
|
-
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering);
|
|
12802
|
-
this.listenTo(this.container, Events.CONTAINER_SEEK, this.onSeek);
|
|
12803
|
-
this.listenTo(this.container, Events.CONTAINER_ERROR, () => this._inc('error'));
|
|
12804
|
-
this.listenTo(this.container, Events.CONTAINER_FULLSCREEN, () => this._inc('fullscreen'));
|
|
12805
|
-
this.listenTo(this.container, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, (dvrInUse) => {
|
|
12806
|
-
dvrInUse && this._inc('dvrUsage');
|
|
12807
|
-
});
|
|
12808
|
-
this.listenTo(this.container.playback, Events.PLAYBACK_PROGRESS, this.onProgress);
|
|
12809
|
-
this.listenTo(this.container.playback, Events.PLAYBACK_TIMEUPDATE, this.onTimeUpdate);
|
|
12810
|
-
}
|
|
12811
|
-
/**
|
|
12812
|
-
* @internal
|
|
12813
|
-
*/
|
|
12814
|
-
destroy() {
|
|
12815
|
-
this.stopReporting();
|
|
12816
|
-
super.destroy();
|
|
12817
|
-
}
|
|
12818
|
-
/**
|
|
12819
|
-
* Returns the collected metrics.
|
|
12820
|
-
* @returns Currently collected metrics
|
|
12821
|
-
*/
|
|
12822
|
-
exportMetrics() {
|
|
12823
|
-
return structuredClone(this.metrics);
|
|
12824
|
-
}
|
|
12825
|
-
onBitrate(newBitrate) {
|
|
12826
|
-
const bitrate = newBitrate.bitrate;
|
|
12827
|
-
const now = this._now();
|
|
12828
|
-
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
12829
|
-
const beforeLast = this.metrics.extra.bitratesHistory[this.metrics.extra.bitratesHistory.length - 1];
|
|
12830
|
-
beforeLast.end = now;
|
|
12831
|
-
beforeLast.time = now - beforeLast.start;
|
|
12832
|
-
}
|
|
12833
|
-
this.metrics.extra.bitratesHistory.push({ start: this._now(), bitrate: bitrate });
|
|
12834
|
-
this._inc('changeLevel');
|
|
12835
|
-
}
|
|
12836
|
-
stopReporting() {
|
|
12837
|
-
this._buildReport();
|
|
12838
|
-
if (this.intervalId !== null) {
|
|
12839
|
-
clearInterval(this.intervalId);
|
|
12840
|
-
this.intervalId = null;
|
|
12841
|
-
}
|
|
12842
|
-
this._newMetrics();
|
|
12843
|
-
// TODO
|
|
12844
|
-
// @ts-ignore
|
|
12845
|
-
this.stopListening();
|
|
12846
|
-
this.bindEvents();
|
|
12847
|
-
}
|
|
12848
|
-
startTimers() {
|
|
12849
|
-
this.intervalId = setInterval(this._buildReport.bind(this), this.runEach);
|
|
12850
|
-
this.start('session');
|
|
12851
|
-
this.start('startup');
|
|
12852
|
-
}
|
|
12853
|
-
onFirstPlaying() {
|
|
12854
|
-
this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
12855
|
-
this.start('watch');
|
|
12856
|
-
this._stop('startup');
|
|
12857
|
-
}
|
|
12858
|
-
playAfterPause() {
|
|
12859
|
-
this.listenTo(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
12860
|
-
this._stop('pause');
|
|
12861
|
-
this.start('watch');
|
|
12862
|
-
}
|
|
12863
|
-
onPlay() {
|
|
12864
|
-
this._inc('play');
|
|
12865
|
-
}
|
|
12866
|
-
onPause() {
|
|
12867
|
-
this._stop('watch');
|
|
12868
|
-
this.start('pause');
|
|
12869
|
-
this._inc('pause');
|
|
12870
|
-
this.listenToOnce(this.container, Events.CONTAINER_PLAY, this.playAfterPause);
|
|
12871
|
-
this.stopListening(this.container, Events.CONTAINER_TIMEUPDATE, this.onContainerUpdateWhilePlaying);
|
|
12872
|
-
}
|
|
12873
|
-
onSeek(e) {
|
|
12874
|
-
this._inc('seek');
|
|
12875
|
-
this.metrics.extra.watchHistory.push([e * 1000, e * 1000]);
|
|
12876
|
-
}
|
|
12877
|
-
onTimeUpdate(e) {
|
|
12878
|
-
const current = e.current * 1000, total = e.total * 1000, l = this.metrics.extra.watchHistory.length;
|
|
12879
|
-
this.metrics.extra.duration = total;
|
|
12880
|
-
this.metrics.extra.currentTime = current;
|
|
12881
|
-
this.metrics.extra.watchedPercentage = (current / total) * 100;
|
|
12882
|
-
if (l === 0) {
|
|
12883
|
-
this.metrics.extra.watchHistory.push([current, current]);
|
|
12884
|
-
}
|
|
12885
|
-
else {
|
|
12886
|
-
this.metrics.extra.watchHistory[l - 1][1] = current;
|
|
12887
|
-
}
|
|
12888
|
-
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
12889
|
-
const lastBitrate = this.metrics.extra.bitratesHistory[this.metrics.extra.bitratesHistory.length - 1];
|
|
12890
|
-
if (!lastBitrate.end) {
|
|
12891
|
-
lastBitrate.time = this._now() - lastBitrate.start;
|
|
12892
|
-
}
|
|
12893
|
-
}
|
|
12894
|
-
this._onCompletion();
|
|
12895
|
-
}
|
|
12896
|
-
onContainerUpdateWhilePlaying() {
|
|
12897
|
-
if (this.container.playback.isPlaying()) {
|
|
12898
|
-
this._stop('watch');
|
|
12899
|
-
this.start('watch');
|
|
12900
|
-
}
|
|
12901
|
-
}
|
|
12902
|
-
onBuffering() {
|
|
12903
|
-
this._inc('buffering');
|
|
12904
|
-
this.start('buffering');
|
|
12905
|
-
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferfull);
|
|
12906
|
-
}
|
|
12907
|
-
onBufferfull() {
|
|
12908
|
-
this._stop('buffering');
|
|
12909
|
-
this.listenToOnce(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering);
|
|
12910
|
-
}
|
|
12911
|
-
onProgress(progress) {
|
|
12912
|
-
this.metrics.extra.buffersize = progress.current * 1000;
|
|
12913
|
-
}
|
|
12914
|
-
_newMetrics() {
|
|
12915
|
-
this.metrics = newMetrics$1();
|
|
12916
|
-
}
|
|
12917
|
-
_onCompletion() {
|
|
12918
|
-
const currentPercentage = this.metrics.extra.watchedPercentage;
|
|
12919
|
-
const allPercentages = this.completion.watch;
|
|
12920
|
-
const isCalled = this.completion.calls.indexOf(currentPercentage) !== -1;
|
|
12921
|
-
if (allPercentages.indexOf(currentPercentage) !== -1 && !isCalled) {
|
|
12922
|
-
Log.info(this.name + ' PERCENTAGE_EVENT: ' + currentPercentage);
|
|
12923
|
-
this.completion.calls.push(currentPercentage);
|
|
12924
|
-
this.trigger(ClapprStatsEvents.PERCENTAGE_EVENT, currentPercentage);
|
|
12925
|
-
}
|
|
12926
|
-
}
|
|
12927
|
-
_buildReport() {
|
|
12928
|
-
this._stop('session');
|
|
12929
|
-
this.start('session');
|
|
12930
|
-
this.metrics.extra.playbackName = this._playbackName;
|
|
12931
|
-
this.metrics.extra.playbackType = this._playbackType;
|
|
12932
|
-
this._calculateBitrates();
|
|
12933
|
-
this._calculatePercentages();
|
|
12934
|
-
this._fetchFPS();
|
|
12935
|
-
this._measureLatency();
|
|
12936
|
-
this._measureBandwidth();
|
|
12937
|
-
this._onReport(this.metrics);
|
|
12938
|
-
this.trigger(ClapprStatsEvents.REPORT_EVENT, structuredClone(this.metrics));
|
|
12939
|
-
}
|
|
12940
|
-
_fetchFPS() {
|
|
12941
|
-
// flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds)
|
|
12942
|
-
// hls ??? (use the same?)
|
|
12943
|
-
const fetchFPS = {
|
|
12944
|
-
'html5_video': this._html5FetchFPS,
|
|
12945
|
-
'hls': this._html5FetchFPS,
|
|
12946
|
-
'dash_shaka_playback': this._html5FetchFPS
|
|
12947
|
-
};
|
|
12948
|
-
if (this._playbackName in fetchFPS) {
|
|
12949
|
-
fetchFPS[this._playbackName].call(this);
|
|
12950
|
-
}
|
|
12951
|
-
}
|
|
12952
|
-
_calculateBitrates() {
|
|
12953
|
-
const { bitratesHistory } = this.metrics.extra;
|
|
12954
|
-
if (bitratesHistory.length === 0) {
|
|
12955
|
-
return;
|
|
12956
|
-
}
|
|
12957
|
-
let totalTime = 0;
|
|
12958
|
-
let weightedTotal = 0;
|
|
12959
|
-
for (const { bitrate, time = 0 } of bitratesHistory) {
|
|
12960
|
-
totalTime += time;
|
|
12961
|
-
weightedTotal += bitrate * time;
|
|
12962
|
-
}
|
|
12963
|
-
this.metrics.extra.bitrateWeightedMean = weightedTotal / totalTime;
|
|
12964
|
-
this.metrics.extra.bitrateMostUsed = bitratesHistory.reduce((mostUsed, current) => (current.time || 0) > (mostUsed.time || 0) ? current : mostUsed, { time: 0, bitrate: 0, start: 0, end: 0 }).bitrate;
|
|
12965
|
-
}
|
|
12966
|
-
_calculatePercentages() {
|
|
12967
|
-
if (this.metrics.extra.duration > 0) {
|
|
12968
|
-
this.metrics.extra.bufferingPercentage = (this.metrics.timers.buffering / this.metrics.extra.duration) * 100;
|
|
12969
|
-
}
|
|
12970
|
-
}
|
|
12971
|
-
_html5FetchFPS() {
|
|
12972
|
-
const videoTag = this.container.playback.el;
|
|
12973
|
-
const getFirstValidValue = (...args) => args.find(val => val !== undefined);
|
|
12974
|
-
const decodedFrames = getFirstValidValue(videoTag.webkitDecodedFrameCount, videoTag.mozDecodedFrames, 0);
|
|
12975
|
-
const droppedFrames = getFirstValidValue(videoTag.webkitDroppedFrameCount, videoTag.mozParsedFrames && videoTag.mozDecodedFrames ? videoTag.mozParsedFrames - videoTag.mozDecodedFrames : 0, 0);
|
|
12976
|
-
const decodedFramesLastTime = decodedFrames - (this.lastDecodedFramesCount || 0);
|
|
12977
|
-
this.metrics.counters.decodedFrames = decodedFrames;
|
|
12978
|
-
this.metrics.counters.droppedFrames = droppedFrames;
|
|
12979
|
-
this.metrics.counters.fps = decodedFramesLastTime / (this.runEach / 1000);
|
|
12980
|
-
this.lastDecodedFramesCount = decodedFrames;
|
|
12981
|
-
}
|
|
12982
|
-
// originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/
|
|
12983
|
-
_measureLatency() {
|
|
12984
|
-
if (this.uriToMeasureLatency) {
|
|
12985
|
-
const t = [];
|
|
12986
|
-
const n = 2;
|
|
12987
|
-
let rtt;
|
|
12988
|
-
const ld = () => {
|
|
12989
|
-
t.push(this._now());
|
|
12990
|
-
if (t.length > n) {
|
|
12991
|
-
done();
|
|
12992
|
-
}
|
|
12993
|
-
else {
|
|
12994
|
-
const img = new Image;
|
|
12995
|
-
img.onload = ld;
|
|
12996
|
-
img.src = this.uriToMeasureLatency + '?' + Math.random()
|
|
12997
|
-
+ '=' + this._now();
|
|
12998
|
-
}
|
|
12999
|
-
};
|
|
13000
|
-
const done = () => {
|
|
13001
|
-
rtt = t[2] - t[1];
|
|
13002
|
-
this.metrics.timers.latency = rtt;
|
|
13003
|
-
};
|
|
13004
|
-
ld();
|
|
13005
|
-
}
|
|
13006
|
-
}
|
|
13007
|
-
// originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/
|
|
13008
|
-
_measureBandwidth() {
|
|
13009
|
-
if (this.urisToMeasureBandwidth && (this.bwMeasureCount % this.runBandwidthTestEvery === 0)) {
|
|
13010
|
-
let i = 0;
|
|
13011
|
-
const ld = (e) => {
|
|
13012
|
-
if (i > 0) {
|
|
13013
|
-
const prev = this.urisToMeasureBandwidth[i - 1];
|
|
13014
|
-
prev.end = this._now();
|
|
13015
|
-
if (prev.timer !== null) {
|
|
13016
|
-
clearTimeout(prev.timer);
|
|
13017
|
-
}
|
|
13018
|
-
}
|
|
13019
|
-
if (i >= this.urisToMeasureBandwidth.length || (i > 0 && this.urisToMeasureBandwidth[i - 1].expired)) {
|
|
13020
|
-
assert(e, 'incorrect invocation in _measureBandwidth');
|
|
13021
|
-
done(e);
|
|
13022
|
-
}
|
|
13023
|
-
else {
|
|
13024
|
-
const xhr = new XMLHttpRequest();
|
|
13025
|
-
xhr.open('GET', this.urisToMeasureBandwidth[i].url, true);
|
|
13026
|
-
xhr.responseType = 'arraybuffer';
|
|
13027
|
-
xhr.onload = xhr.onabort = ld;
|
|
13028
|
-
this.urisToMeasureBandwidth[i].start = this._now();
|
|
13029
|
-
this.urisToMeasureBandwidth[i].timer = setTimeout((j) => {
|
|
13030
|
-
this.urisToMeasureBandwidth[j].expired = true;
|
|
13031
|
-
xhr.abort();
|
|
13032
|
-
}, this.urisToMeasureBandwidth[i].timeout, i);
|
|
13033
|
-
xhr.send();
|
|
13034
|
-
}
|
|
13035
|
-
i++;
|
|
13036
|
-
};
|
|
13037
|
-
const done = (e) => {
|
|
13038
|
-
const timeSpent = (this.urisToMeasureBandwidth[i - 1].end - this.urisToMeasureBandwidth[i - 1].start) / 1000;
|
|
13039
|
-
const bandwidthBps = (e.loaded * 8) / timeSpent;
|
|
13040
|
-
this.metrics.extra.bandwidth = bandwidthBps;
|
|
13041
|
-
this.urisToMeasureBandwidth.forEach((x) => {
|
|
13042
|
-
x.start = 0;
|
|
13043
|
-
x.end = 0;
|
|
13044
|
-
x.expired = false;
|
|
13045
|
-
if (x.timer !== null) {
|
|
13046
|
-
clearTimeout(x.timer);
|
|
13047
|
-
x.timer = null;
|
|
13048
|
-
}
|
|
13049
|
-
});
|
|
13050
|
-
};
|
|
13051
|
-
ld();
|
|
13052
|
-
}
|
|
13053
|
-
this.bwMeasureCount++;
|
|
13054
|
-
}
|
|
13055
|
-
}
|
|
13056
|
-
// ClapprStats.REPORT_EVENT = 'clappr:stats:report';
|
|
13057
|
-
// ClapprStats.PERCENTAGE_EVENT = 'clappr:stats:percentage';
|
|
13058
|
-
|
|
13059
13135
|
// This work is based on the original work of the following authors:
|
|
13060
13136
|
// Copyright 2014 Globo.com Player authors. All rights reserved.
|
|
13061
13137
|
// Use of this source code is governed by a BSD-style
|
|
@@ -16865,7 +16941,7 @@ class SeekTime extends UICorePlugin {
|
|
|
16865
16941
|
this.listenTo(this.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.onContainerChanged);
|
|
16866
16942
|
if (this.mediaControlContainer) {
|
|
16867
16943
|
this.listenTo(this.mediaControlContainer, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, this.update);
|
|
16868
|
-
this.listenTo(this.mediaControlContainer, Events.CONTAINER_TIMEUPDATE, this.
|
|
16944
|
+
this.listenTo(this.mediaControlContainer, Events.CONTAINER_TIMEUPDATE, this.onTimeUpdate);
|
|
16869
16945
|
}
|
|
16870
16946
|
}
|
|
16871
16947
|
onContainerChanged() {
|
|
@@ -16873,9 +16949,8 @@ class SeekTime extends UICorePlugin {
|
|
|
16873
16949
|
this.stopListening();
|
|
16874
16950
|
this.bindEvents();
|
|
16875
16951
|
}
|
|
16876
|
-
|
|
16877
|
-
this.duration =
|
|
16878
|
-
// this.firstFragDateTime = timeProgress.firstFragDateTime;
|
|
16952
|
+
onTimeUpdate({ total }) {
|
|
16953
|
+
this.duration = total;
|
|
16879
16954
|
this.update();
|
|
16880
16955
|
}
|
|
16881
16956
|
showTime(event) {
|
|
@@ -18768,4 +18843,4 @@ class VolumeFade extends UICorePlugin {
|
|
|
18768
18843
|
}
|
|
18769
18844
|
}
|
|
18770
18845
|
|
|
18771
|
-
export { AudioTracks as AudioSelector, AudioTracks, BigMuteButton, BottomGear, ClapprNerdStats, ClapprStats, ClickToPause, Clips, ClosedCaptions, ContextMenu, DvrControls, ErrorScreen, Favicon, GearEvents, GoogleAnalytics, QualityLevels as LevelSelector, Logo, MediaControl, MultiCamera, PictureInPicture, PlaybackRate, Poster, QualityLevels, SeekTime, Share, SkipTime, SourceController, SpinnerThreeBounce as Spinner, SpinnerEvents, SpinnerThreeBounce, ClosedCaptions as Subtitles, Telemetry, TelemetryEvent, Thumbnails, VolumeFade, VolumeFadeEvents };
|
|
18846
|
+
export { AudioTracks as AudioSelector, AudioTracks, BigMuteButton, BottomGear, NerdStats as ClapprNerdStats, ClapprStats, ClickToPause, Clips, ClosedCaptions, ContextMenu, DvrControls, ErrorScreen, Favicon, GearEvents, GoogleAnalytics, QualityLevels as LevelSelector, Logo, MediaControl, MultiCamera, NerdStats, PictureInPicture, PlaybackRate, Poster, QualityLevels, SeekTime, Share, SkipTime, SourceController, SpinnerThreeBounce as Spinner, SpinnerEvents, SpinnerThreeBounce, ClosedCaptions as Subtitles, Telemetry, TelemetryEvent, Thumbnails, VolumeFade, VolumeFadeEvents };
|