@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
|
@@ -1,469 +1,405 @@
|
|
|
1
|
-
import { Container, ContainerPlugin, Events as CoreEvents
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { ClapprStatsEvents } from './types.js';
|
|
9
|
-
import { newMetrics } from './utils.js';
|
|
10
|
-
|
|
11
|
-
type StatsTimer = keyof Metrics['timers'];
|
|
12
|
-
|
|
13
|
-
type UriToMeasureBandwidth = {
|
|
14
|
-
url: string;
|
|
15
|
-
start: number;
|
|
16
|
-
end: number;
|
|
17
|
-
expired: boolean;
|
|
18
|
-
timeout: number;
|
|
19
|
-
timer: TimerId | null;
|
|
20
|
-
}
|
|
1
|
+
import { Container, ContainerPlugin, Events as CoreEvents } from '@clappr/core'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
QualityLevel,
|
|
5
|
+
TimePosition,
|
|
6
|
+
TimeProgress,
|
|
7
|
+
} from '../../playback.types.js'
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
import { CLAPPR_VERSION } from '../../build.js'
|
|
10
|
+
import { TimerId } from '../../utils/types.js'
|
|
11
|
+
import type { Metrics } from './types.js'
|
|
12
|
+
import { ClapprStatsEvents, Chronograph, Counter } from './types.js'
|
|
13
|
+
import { newMetrics } from './utils.js'
|
|
14
|
+
|
|
15
|
+
export type ClapprStatsSettings = {
|
|
16
|
+
/**
|
|
17
|
+
* The interval in milliseconds of periodic measurements.
|
|
18
|
+
* The plugin will emit a {@link ClapprStatsEvents.REPORT} event with the collected metrics at the specified interval.
|
|
19
|
+
*/
|
|
20
|
+
runEach?: number
|
|
21
|
+
}
|
|
24
22
|
|
|
25
23
|
/**
|
|
26
|
-
* `PLUGIN` that
|
|
24
|
+
* `PLUGIN` that measures data about playback, which can be useful for analyzing performance and UX.
|
|
27
25
|
* @beta
|
|
28
26
|
* @remarks
|
|
29
27
|
* This plugin does not render anything and is supposed to be extended or used together with other plugins that actually render something.
|
|
28
|
+
*
|
|
29
|
+
* Configuration options - {@link ClapprStatsSettings}
|
|
30
|
+
*
|
|
31
|
+
* Events - {@link ClapprStatsEvents}
|
|
30
32
|
*/
|
|
31
33
|
export class ClapprStats extends ContainerPlugin {
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
private intervalId: TimerId | null = null;
|
|
35
|
-
|
|
36
|
-
private lastDecodedFramesCount = 0;
|
|
37
|
-
|
|
38
|
-
private metrics: Metrics = newMetrics();
|
|
39
|
-
|
|
40
|
-
private completion: {
|
|
41
|
-
watch: number[];
|
|
42
|
-
calls: number[];
|
|
43
|
-
};
|
|
34
|
+
private timerId: TimerId | null = null
|
|
44
35
|
|
|
45
|
-
private
|
|
36
|
+
private lastDecodedFramesCount = 0
|
|
46
37
|
|
|
47
|
-
private
|
|
38
|
+
private metrics: Metrics = newMetrics()
|
|
48
39
|
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
session: 0,
|
|
57
|
-
latency: 0,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
private updateFn: MetricsUpdateFn = updateMetrics;
|
|
61
|
-
|
|
62
|
-
private urisToMeasureBandwidth: UriToMeasureBandwidth[];
|
|
40
|
+
private timers: Record<Chronograph, number> = {
|
|
41
|
+
[Chronograph.Startup]: 0,
|
|
42
|
+
[Chronograph.Watch]: 0,
|
|
43
|
+
[Chronograph.Pause]: 0,
|
|
44
|
+
[Chronograph.Buffering]: 0,
|
|
45
|
+
[Chronograph.Session]: 0,
|
|
46
|
+
}
|
|
63
47
|
|
|
64
|
-
private
|
|
48
|
+
private runEach: number
|
|
65
49
|
|
|
66
50
|
/**
|
|
67
51
|
* @internal
|
|
68
52
|
*/
|
|
69
53
|
get name() {
|
|
70
|
-
return 'clappr_stats'
|
|
54
|
+
return 'clappr_stats'
|
|
71
55
|
}
|
|
72
56
|
|
|
73
57
|
/**
|
|
74
58
|
* @internal
|
|
75
59
|
*/
|
|
76
60
|
get supportedVersion() {
|
|
77
|
-
return { min: CLAPPR_VERSION }
|
|
61
|
+
return { min: CLAPPR_VERSION }
|
|
78
62
|
}
|
|
79
63
|
|
|
80
|
-
private get
|
|
81
|
-
return String(this.container.playback.name || '')
|
|
64
|
+
private get playbackName() {
|
|
65
|
+
return String(this.container.playback.name || '')
|
|
82
66
|
}
|
|
83
67
|
|
|
84
|
-
private get
|
|
85
|
-
return this.container.getPlaybackType()
|
|
68
|
+
private get playbackType() {
|
|
69
|
+
return this.container.getPlaybackType()
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
private
|
|
89
|
-
const hasPerformanceSupport =
|
|
72
|
+
private now() {
|
|
73
|
+
const hasPerformanceSupport =
|
|
74
|
+
window.performance && typeof window.performance.now === 'function'
|
|
90
75
|
|
|
91
|
-
return
|
|
76
|
+
return hasPerformanceSupport
|
|
77
|
+
? window.performance.now()
|
|
78
|
+
: new Date().getTime()
|
|
92
79
|
}
|
|
93
80
|
|
|
94
|
-
private
|
|
95
|
-
this.metrics.counters[counter] += 1
|
|
81
|
+
private inc(counter: Counter) {
|
|
82
|
+
this.metrics.counters[counter] += 1
|
|
96
83
|
}
|
|
97
84
|
|
|
98
85
|
// _timerHasStarted(timer) {
|
|
99
86
|
// return this[`_start${timer}`] !== undefined;
|
|
100
87
|
// }
|
|
101
88
|
|
|
102
|
-
private start(timer:
|
|
89
|
+
private start(timer: Chronograph) {
|
|
103
90
|
// this[`_start${timer}`] = this._now();
|
|
104
|
-
this.timers[timer] = this.
|
|
91
|
+
this.timers[timer] = this.now()
|
|
105
92
|
}
|
|
106
93
|
|
|
107
|
-
private
|
|
94
|
+
private stop(timer: Chronograph) {
|
|
108
95
|
// this._metrics.timers[timer] += this._now() - this[`_start${timer}`];
|
|
109
|
-
this.metrics.
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Registers a callback to receive the metrics.
|
|
114
|
-
* @param updateMetricsFn - The callback to receive the metrics
|
|
115
|
-
*/
|
|
116
|
-
setUpdateMetrics(updateMetricsFn: MetricsUpdateFn) {
|
|
117
|
-
// TODO use events instead
|
|
118
|
-
this.updateFn = updateMetricsFn;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private _defaultReport(metrics: Metrics) {
|
|
122
|
-
this.updateFn(metrics);
|
|
96
|
+
this.metrics.chrono[timer] += this.now() - this.timers[timer]
|
|
123
97
|
}
|
|
124
98
|
|
|
125
99
|
constructor(container: Container) {
|
|
126
|
-
super(container)
|
|
127
|
-
this.runEach = container.options.clapprStats?.runEach ?? 5000
|
|
128
|
-
this._onReport = container.options.clapprStats?.onReport ?? this._defaultReport;
|
|
129
|
-
this.uriToMeasureLatency = container.options.clapprStats?.uriToMeasureLatency;
|
|
130
|
-
this.urisToMeasureBandwidth = container.options.clapprStats?.urisToMeasureBandwidth;
|
|
131
|
-
this.runBandwidthTestEvery = container.options.clapprStats?.runBandwidthTestEvery ?? 10;
|
|
132
|
-
|
|
133
|
-
this.completion = {
|
|
134
|
-
watch: container.options.clapprStats?.onCompletion ?? [],
|
|
135
|
-
calls: []
|
|
136
|
-
};
|
|
100
|
+
super(container)
|
|
101
|
+
this.runEach = container.options.clapprStats?.runEach ?? 5000
|
|
137
102
|
}
|
|
138
103
|
|
|
139
104
|
/**
|
|
140
105
|
* @internal
|
|
141
106
|
*/
|
|
142
107
|
override bindEvents() {
|
|
143
|
-
this.listenTo(this.container, CoreEvents.CONTAINER_BITRATE, this.onBitrate)
|
|
144
|
-
this.listenTo(this.container, CoreEvents.CONTAINER_STOP, this.stopReporting)
|
|
145
|
-
this.listenTo(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.listenToOnce(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
108
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_BITRATE, this.onBitrate)
|
|
109
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_STOP, this.stopReporting)
|
|
110
|
+
this.listenTo(
|
|
111
|
+
this.container,
|
|
112
|
+
CoreEvents.CONTAINER_ENDED,
|
|
113
|
+
this.stopReporting,
|
|
114
|
+
)
|
|
115
|
+
this.listenToOnce(
|
|
116
|
+
this.container.playback,
|
|
117
|
+
CoreEvents.PLAYBACK_PLAY_INTENT,
|
|
118
|
+
this.startTimers,
|
|
119
|
+
)
|
|
120
|
+
this.listenToOnce(
|
|
121
|
+
this.container,
|
|
122
|
+
CoreEvents.CONTAINER_PLAY,
|
|
123
|
+
this.onFirstPlaying,
|
|
124
|
+
)
|
|
125
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_PLAY, this.onPlay)
|
|
126
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_PAUSE, this.onPause)
|
|
127
|
+
this.listenToOnce(
|
|
128
|
+
this.container,
|
|
129
|
+
CoreEvents.CONTAINER_STATE_BUFFERING,
|
|
130
|
+
this.onBuffering,
|
|
131
|
+
)
|
|
132
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_SEEK, this.onSeek)
|
|
133
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_ERROR, () =>
|
|
134
|
+
this.inc(Counter.Error),
|
|
135
|
+
)
|
|
136
|
+
this.listenTo(this.container, CoreEvents.CONTAINER_FULLSCREEN, () =>
|
|
137
|
+
this.inc(Counter.Fullscreen),
|
|
138
|
+
)
|
|
139
|
+
this.listenTo(
|
|
140
|
+
this.container,
|
|
141
|
+
CoreEvents.CONTAINER_PLAYBACKDVRSTATECHANGED,
|
|
142
|
+
(dvrInUse: boolean) => {
|
|
143
|
+
dvrInUse && this.inc(Counter.DvrUsage)
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
this.listenTo(
|
|
147
|
+
this.container.playback,
|
|
148
|
+
CoreEvents.PLAYBACK_PROGRESS,
|
|
149
|
+
this.onProgress,
|
|
150
|
+
)
|
|
151
|
+
this.listenTo(
|
|
152
|
+
this.container.playback,
|
|
153
|
+
CoreEvents.PLAYBACK_TIMEUPDATE,
|
|
154
|
+
this.onTimeUpdate,
|
|
155
|
+
)
|
|
159
156
|
}
|
|
160
157
|
|
|
161
158
|
/**
|
|
162
159
|
* @internal
|
|
163
160
|
*/
|
|
164
161
|
override destroy() {
|
|
165
|
-
this.stopReporting()
|
|
166
|
-
super.destroy()
|
|
162
|
+
this.stopReporting()
|
|
163
|
+
super.destroy()
|
|
167
164
|
}
|
|
168
165
|
|
|
169
166
|
/**
|
|
170
167
|
* Returns the collected metrics.
|
|
171
|
-
* @returns
|
|
168
|
+
* @returns Measurements collected so far
|
|
172
169
|
*/
|
|
173
170
|
exportMetrics() {
|
|
174
|
-
return structuredClone(this.metrics)
|
|
171
|
+
return structuredClone(this.metrics)
|
|
175
172
|
}
|
|
176
173
|
|
|
177
174
|
private onBitrate(newBitrate: QualityLevel) {
|
|
178
|
-
const bitrate = newBitrate.bitrate
|
|
179
|
-
const now = this.
|
|
175
|
+
const bitrate = newBitrate.bitrate
|
|
176
|
+
const now = this.now()
|
|
180
177
|
|
|
181
178
|
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
const last =
|
|
180
|
+
this.metrics.extra.bitratesHistory[
|
|
181
|
+
this.metrics.extra.bitratesHistory.length - 1
|
|
182
|
+
]
|
|
183
|
+
last.end = now
|
|
184
|
+
last.time = now - last.start
|
|
186
185
|
}
|
|
187
186
|
|
|
188
|
-
this.metrics.extra.bitratesHistory.push({ start: this.
|
|
187
|
+
this.metrics.extra.bitratesHistory.push({ start: this.now(), bitrate })
|
|
189
188
|
|
|
190
|
-
this.
|
|
189
|
+
this.inc(Counter.ChangeLevel)
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
private stopReporting() {
|
|
194
|
-
this.
|
|
193
|
+
this.buildReport()
|
|
195
194
|
|
|
196
|
-
if (this.
|
|
197
|
-
clearInterval(this.
|
|
198
|
-
this.
|
|
195
|
+
if (this.timerId !== null) {
|
|
196
|
+
clearInterval(this.timerId)
|
|
197
|
+
this.timerId = null
|
|
199
198
|
}
|
|
200
|
-
this._newMetrics();
|
|
201
|
-
|
|
202
|
-
// TODO
|
|
203
|
-
// @ts-ignore
|
|
204
|
-
this.stopListening();
|
|
205
|
-
this.bindEvents();
|
|
206
199
|
}
|
|
207
200
|
|
|
208
201
|
private startTimers() {
|
|
209
|
-
this.
|
|
210
|
-
this.start(
|
|
211
|
-
this.start(
|
|
202
|
+
this.timerId = setInterval(this.buildReport.bind(this), this.runEach)
|
|
203
|
+
this.start(Chronograph.Session)
|
|
204
|
+
this.start(Chronograph.Startup)
|
|
212
205
|
}
|
|
213
206
|
|
|
214
207
|
private onFirstPlaying() {
|
|
215
|
-
this.listenTo(
|
|
208
|
+
this.listenTo(
|
|
209
|
+
this.container,
|
|
210
|
+
CoreEvents.CONTAINER_TIMEUPDATE,
|
|
211
|
+
this.onContainerUpdateWhilePlaying,
|
|
212
|
+
)
|
|
216
213
|
|
|
217
|
-
this.start(
|
|
218
|
-
this.
|
|
214
|
+
this.start(Chronograph.Watch)
|
|
215
|
+
this.stop(Chronograph.Startup)
|
|
219
216
|
}
|
|
220
217
|
|
|
221
218
|
private playAfterPause() {
|
|
222
|
-
this.listenTo(
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
this.listenTo(
|
|
220
|
+
this.container,
|
|
221
|
+
CoreEvents.CONTAINER_TIMEUPDATE,
|
|
222
|
+
this.onContainerUpdateWhilePlaying,
|
|
223
|
+
)
|
|
224
|
+
this.stop(Chronograph.Pause)
|
|
225
|
+
this.start(Chronograph.Watch)
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
private onPlay() {
|
|
228
|
-
this.
|
|
229
|
+
this.inc(Counter.Play)
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
private onPause() {
|
|
232
|
-
this.
|
|
233
|
-
this.start(
|
|
234
|
-
this.
|
|
235
|
-
this.listenToOnce(
|
|
236
|
-
|
|
233
|
+
this.stop(Chronograph.Watch)
|
|
234
|
+
this.start(Chronograph.Pause)
|
|
235
|
+
this.inc(Counter.Pause)
|
|
236
|
+
this.listenToOnce(
|
|
237
|
+
this.container,
|
|
238
|
+
CoreEvents.CONTAINER_PLAY,
|
|
239
|
+
this.playAfterPause,
|
|
240
|
+
)
|
|
241
|
+
this.stopListening(
|
|
242
|
+
this.container,
|
|
243
|
+
CoreEvents.CONTAINER_TIMEUPDATE,
|
|
244
|
+
this.onContainerUpdateWhilePlaying,
|
|
245
|
+
)
|
|
237
246
|
}
|
|
238
247
|
|
|
239
248
|
private onSeek(e: number) {
|
|
240
|
-
this.
|
|
241
|
-
this.metrics.extra.watchHistory.push([e * 1000, e * 1000])
|
|
249
|
+
this.inc(Counter.Seek)
|
|
250
|
+
this.metrics.extra.watchHistory.push([e * 1000, e * 1000])
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
private onTimeUpdate(e: TimePosition) {
|
|
245
254
|
const current = e.current * 1000,
|
|
246
255
|
total = e.total * 1000,
|
|
247
|
-
l = this.metrics.extra.watchHistory.length
|
|
256
|
+
l = this.metrics.extra.watchHistory.length
|
|
248
257
|
|
|
249
|
-
this.metrics.extra.duration = total
|
|
250
|
-
this.metrics.extra.currentTime = current
|
|
251
|
-
|
|
258
|
+
this.metrics.extra.duration = total
|
|
259
|
+
this.metrics.extra.currentTime = current
|
|
260
|
+
// TODO what if it's a live stream?
|
|
261
|
+
this.metrics.extra.watchedPercentage = (current / total) * 100
|
|
252
262
|
|
|
253
263
|
if (l === 0) {
|
|
254
|
-
this.metrics.extra.watchHistory.push([current, current])
|
|
264
|
+
this.metrics.extra.watchHistory.push([current, current])
|
|
255
265
|
} else {
|
|
256
|
-
this.metrics.extra.watchHistory[l - 1][1] = current
|
|
266
|
+
this.metrics.extra.watchHistory[l - 1][1] = current
|
|
257
267
|
}
|
|
258
268
|
|
|
259
269
|
if (this.metrics.extra.bitratesHistory.length > 0) {
|
|
260
|
-
const lastBitrate =
|
|
270
|
+
const lastBitrate =
|
|
271
|
+
this.metrics.extra.bitratesHistory[
|
|
272
|
+
this.metrics.extra.bitratesHistory.length - 1
|
|
273
|
+
]
|
|
261
274
|
|
|
262
275
|
if (!lastBitrate.end) {
|
|
263
|
-
lastBitrate.time = this.
|
|
276
|
+
lastBitrate.time = this.now() - lastBitrate.start
|
|
264
277
|
}
|
|
265
278
|
}
|
|
266
279
|
|
|
267
|
-
this.
|
|
280
|
+
this.onCompletion()
|
|
268
281
|
}
|
|
269
282
|
|
|
270
283
|
private onContainerUpdateWhilePlaying() {
|
|
271
284
|
if (this.container.playback.isPlaying()) {
|
|
272
|
-
this.
|
|
273
|
-
this.start(
|
|
285
|
+
this.stop(Chronograph.Watch)
|
|
286
|
+
this.start(Chronograph.Watch)
|
|
274
287
|
}
|
|
275
288
|
}
|
|
276
289
|
|
|
277
290
|
private onBuffering() {
|
|
278
|
-
this.
|
|
279
|
-
this.start(
|
|
280
|
-
this.listenToOnce(
|
|
291
|
+
this.inc(Counter.Buffering)
|
|
292
|
+
this.start(Chronograph.Buffering)
|
|
293
|
+
this.listenToOnce(
|
|
294
|
+
this.container,
|
|
295
|
+
CoreEvents.CONTAINER_STATE_BUFFERFULL,
|
|
296
|
+
this.onBufferfull,
|
|
297
|
+
)
|
|
281
298
|
}
|
|
282
299
|
|
|
283
300
|
private onBufferfull() {
|
|
284
|
-
this.
|
|
285
|
-
this.listenToOnce(
|
|
301
|
+
this.stop(Chronograph.Buffering)
|
|
302
|
+
this.listenToOnce(
|
|
303
|
+
this.container,
|
|
304
|
+
CoreEvents.CONTAINER_STATE_BUFFERING,
|
|
305
|
+
this.onBuffering,
|
|
306
|
+
)
|
|
286
307
|
}
|
|
287
308
|
|
|
288
309
|
private onProgress(progress: TimeProgress) {
|
|
289
|
-
this.metrics.extra.buffersize = progress.current * 1000
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private _newMetrics() {
|
|
293
|
-
this.metrics = newMetrics();
|
|
310
|
+
this.metrics.extra.buffersize = progress.current * 1000
|
|
294
311
|
}
|
|
295
312
|
|
|
296
|
-
private
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (allPercentages.indexOf(currentPercentage) !== -1 && !isCalled) {
|
|
302
|
-
Log.info(this.name + ' PERCENTAGE_EVENT: ' + currentPercentage);
|
|
303
|
-
this.completion.calls.push(currentPercentage);
|
|
304
|
-
this.trigger(ClapprStatsEvents.PERCENTAGE_EVENT, currentPercentage);
|
|
305
|
-
}
|
|
313
|
+
private onCompletion() {
|
|
314
|
+
// Decide if this is needed
|
|
315
|
+
// const currentPercentage = this.metrics.extra.watchedPercentage;
|
|
316
|
+
// this.trigger(ClapprStatsEvents.PERCENTAGE, currentPercentage);
|
|
306
317
|
}
|
|
307
318
|
|
|
308
|
-
private
|
|
309
|
-
this.
|
|
310
|
-
this.start(
|
|
311
|
-
|
|
312
|
-
this.metrics.extra.playbackName = this._playbackName;
|
|
313
|
-
this.metrics.extra.playbackType = this._playbackType;
|
|
319
|
+
private buildReport() {
|
|
320
|
+
this.stop(Chronograph.Session)
|
|
321
|
+
this.start(Chronograph.Session)
|
|
314
322
|
|
|
315
|
-
this.
|
|
316
|
-
this.
|
|
317
|
-
this._fetchFPS();
|
|
318
|
-
this._measureLatency();
|
|
319
|
-
this._measureBandwidth();
|
|
323
|
+
this.metrics.extra.playbackName = this.playbackName
|
|
324
|
+
this.metrics.extra.playbackType = this.playbackType
|
|
320
325
|
|
|
321
|
-
this.
|
|
322
|
-
this.
|
|
326
|
+
this.calcBitrates()
|
|
327
|
+
this.calcBufferingPercentage()
|
|
328
|
+
// TODO calc FPS properly, e.g., on TIMEUPDATE event
|
|
329
|
+
this.fetchFPS()
|
|
330
|
+
this.trigger(ClapprStatsEvents.REPORT, structuredClone(this.metrics))
|
|
323
331
|
}
|
|
324
332
|
|
|
325
|
-
private
|
|
333
|
+
private fetchFPS() {
|
|
334
|
+
// TODO check if the playback and media sources support video, then use the common method
|
|
326
335
|
// flashls ??? - hls.droppedFramesl hls.stream.bufferLength (seconds)
|
|
327
336
|
// hls ??? (use the same?)
|
|
328
337
|
const fetchFPS = {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
338
|
+
html5_video: this.html5FetchFPS,
|
|
339
|
+
hls: this.html5FetchFPS,
|
|
340
|
+
dash: this.html5FetchFPS,
|
|
341
|
+
}
|
|
333
342
|
|
|
334
|
-
if (this.
|
|
335
|
-
fetchFPS[this.
|
|
343
|
+
if (this.playbackName in fetchFPS) {
|
|
344
|
+
fetchFPS[this.playbackName as keyof typeof fetchFPS].call(this)
|
|
336
345
|
}
|
|
337
346
|
}
|
|
338
347
|
|
|
339
|
-
|
|
340
|
-
|
|
348
|
+
// TODO sort out
|
|
349
|
+
private calcBitrates() {
|
|
350
|
+
const { bitratesHistory } = this.metrics.extra
|
|
341
351
|
|
|
342
352
|
if (bitratesHistory.length === 0) {
|
|
343
|
-
return
|
|
353
|
+
return
|
|
344
354
|
}
|
|
345
355
|
|
|
346
|
-
let totalTime = 0
|
|
347
|
-
let weightedTotal = 0
|
|
356
|
+
let totalTime = 0
|
|
357
|
+
let weightedTotal = 0
|
|
348
358
|
|
|
349
359
|
for (const { bitrate, time = 0 } of bitratesHistory) {
|
|
350
|
-
totalTime += time
|
|
351
|
-
weightedTotal += bitrate * time
|
|
360
|
+
totalTime += time
|
|
361
|
+
weightedTotal += bitrate * time
|
|
352
362
|
}
|
|
353
|
-
this.metrics.extra.bitrateWeightedMean = weightedTotal / totalTime
|
|
363
|
+
this.metrics.extra.bitrateWeightedMean = weightedTotal / totalTime
|
|
354
364
|
|
|
355
|
-
this.metrics.extra.bitrateMostUsed = bitratesHistory.reduce(
|
|
356
|
-
(
|
|
365
|
+
this.metrics.extra.bitrateMostUsed = bitratesHistory.reduce(
|
|
366
|
+
(mostUsed, current) =>
|
|
367
|
+
(current.time || 0) > (mostUsed.time || 0) ? current : mostUsed,
|
|
357
368
|
{ time: 0, bitrate: 0, start: 0, end: 0 },
|
|
358
|
-
).bitrate
|
|
369
|
+
).bitrate
|
|
359
370
|
}
|
|
360
371
|
|
|
361
|
-
private
|
|
372
|
+
private calcBufferingPercentage() {
|
|
362
373
|
if (this.metrics.extra.duration > 0) {
|
|
363
|
-
this.metrics.extra.bufferingPercentage =
|
|
374
|
+
this.metrics.extra.bufferingPercentage =
|
|
375
|
+
(this.metrics.chrono.buffering / this.metrics.extra.duration) * 100
|
|
364
376
|
}
|
|
365
377
|
}
|
|
366
378
|
|
|
367
|
-
private
|
|
368
|
-
const videoTag = this.container.playback.el
|
|
379
|
+
private html5FetchFPS() {
|
|
380
|
+
const videoTag = this.container.playback.el
|
|
369
381
|
|
|
370
|
-
const getFirstValidValue = (...args: any[]) =>
|
|
382
|
+
const getFirstValidValue = (...args: any[]) =>
|
|
383
|
+
args.find((val) => val !== undefined)
|
|
371
384
|
|
|
372
|
-
const decodedFrames = getFirstValidValue(
|
|
385
|
+
const decodedFrames = getFirstValidValue(
|
|
386
|
+
videoTag.webkitDecodedFrameCount,
|
|
387
|
+
videoTag.mozDecodedFrames,
|
|
388
|
+
0,
|
|
389
|
+
)
|
|
373
390
|
const droppedFrames = getFirstValidValue(
|
|
374
391
|
videoTag.webkitDroppedFrameCount,
|
|
375
|
-
videoTag.mozParsedFrames && videoTag.mozDecodedFrames
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
this.
|
|
381
|
-
this.metrics.counters.droppedFrames = droppedFrames;
|
|
382
|
-
this.metrics.counters.fps = decodedFramesLastTime / (this.runEach / 1000);
|
|
383
|
-
|
|
384
|
-
this.lastDecodedFramesCount = decodedFrames;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// originally from https://www.smashingmagazine.com/2011/11/analyzing-network-characteristics-using-javascript-and-the-dom-part-1/
|
|
388
|
-
private _measureLatency() {
|
|
389
|
-
if (this.uriToMeasureLatency) {
|
|
390
|
-
const t: number[] = [];
|
|
391
|
-
const n = 2;
|
|
392
|
-
let rtt;
|
|
393
|
-
const ld = () => {
|
|
394
|
-
t.push(this._now());
|
|
395
|
-
if (t.length > n) {
|
|
396
|
-
done();
|
|
397
|
-
} else {
|
|
398
|
-
const img = new Image;
|
|
399
|
-
|
|
400
|
-
img.onload = ld;
|
|
401
|
-
img.src = this.uriToMeasureLatency + '?' + Math.random()
|
|
402
|
-
+ '=' + this._now();
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
const done = () => {
|
|
406
|
-
rtt = t[2] - t[1];
|
|
407
|
-
this.metrics.timers.latency = rtt;
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
ld();
|
|
411
|
-
}
|
|
412
|
-
}
|
|
392
|
+
videoTag.mozParsedFrames && videoTag.mozDecodedFrames
|
|
393
|
+
? videoTag.mozParsedFrames - videoTag.mozDecodedFrames
|
|
394
|
+
: 0,
|
|
395
|
+
0,
|
|
396
|
+
)
|
|
397
|
+
const delta = decodedFrames - (this.lastDecodedFramesCount || 0)
|
|
413
398
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const ld = (e?: ProgressEvent) => {
|
|
420
|
-
if (i > 0) {
|
|
421
|
-
const prev = this.urisToMeasureBandwidth[i - 1];
|
|
422
|
-
prev.end = this._now();
|
|
423
|
-
if (prev.timer !== null) {
|
|
424
|
-
clearTimeout(prev.timer);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (i >= this.urisToMeasureBandwidth.length || (i > 0 && this.urisToMeasureBandwidth[i - 1].expired)) {
|
|
428
|
-
assert(e, 'incorrect invocation in _measureBandwidth');
|
|
429
|
-
done(e);
|
|
430
|
-
} else {
|
|
431
|
-
const xhr = new XMLHttpRequest();
|
|
432
|
-
|
|
433
|
-
xhr.open('GET', this.urisToMeasureBandwidth[i].url, true);
|
|
434
|
-
xhr.responseType = 'arraybuffer';
|
|
435
|
-
xhr.onload = xhr.onabort = ld;
|
|
436
|
-
this.urisToMeasureBandwidth[i].start = this._now();
|
|
437
|
-
this.urisToMeasureBandwidth[i].timer = setTimeout((j) => {
|
|
438
|
-
this.urisToMeasureBandwidth[j].expired = true;
|
|
439
|
-
xhr.abort();
|
|
440
|
-
}, this.urisToMeasureBandwidth[i].timeout, i);
|
|
441
|
-
xhr.send();
|
|
442
|
-
}
|
|
443
|
-
i++;
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const done = (e: ProgressEvent) => {
|
|
447
|
-
const timeSpent = (this.urisToMeasureBandwidth[i - 1].end - this.urisToMeasureBandwidth[i - 1].start) / 1000;
|
|
448
|
-
const bandwidthBps = (e.loaded * 8) / timeSpent;
|
|
449
|
-
|
|
450
|
-
this.metrics.extra.bandwidth = bandwidthBps;
|
|
451
|
-
this.urisToMeasureBandwidth.forEach((x) => {
|
|
452
|
-
x.start = 0;
|
|
453
|
-
x.end = 0;
|
|
454
|
-
x.expired = false;
|
|
455
|
-
if (x.timer !== null) {
|
|
456
|
-
clearTimeout(x.timer);
|
|
457
|
-
x.timer = null;
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
ld();
|
|
463
|
-
}
|
|
464
|
-
this.bwMeasureCount++;
|
|
399
|
+
this.metrics.counters.decodedFrames = decodedFrames
|
|
400
|
+
this.metrics.counters.droppedFrames = droppedFrames
|
|
401
|
+
this.metrics.counters.fps = delta / (this.runEach / 1000) // TODO use time delta instead of runEach
|
|
402
|
+
|
|
403
|
+
this.lastDecodedFramesCount = decodedFrames
|
|
465
404
|
}
|
|
466
405
|
}
|
|
467
|
-
|
|
468
|
-
// ClapprStats.REPORT_EVENT = 'clappr:stats:report';
|
|
469
|
-
// ClapprStats.PERCENTAGE_EVENT = 'clappr:stats:percentage';
|