@newrelic/video-videojs 4.0.3 → 4.1.0
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/CHANGELOG.md +36 -3
- package/README.md +287 -67
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.LICENSE.txt +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/umd/newrelic-video-videojs.min.js +1 -1
- package/dist/umd/newrelic-video-videojs.min.js.LICENSE.txt +1 -1
- package/dist/umd/newrelic-video-videojs.min.js.map +1 -1
- package/package.json +1 -1
- package/src/techs/contrib-hls.js +64 -52
- package/src/techs/hls-js.js +29 -12
- package/src/techs/shaka.js +37 -10
- package/src/tracker.js +77 -36
package/package.json
CHANGED
package/src/techs/contrib-hls.js
CHANGED
|
@@ -1,83 +1,95 @@
|
|
|
1
1
|
export default class ContribHlsTech {
|
|
2
|
-
constructor
|
|
3
|
-
this.tech = tech.vhs
|
|
4
|
-
this.player = tech.el().player // Store player reference for playback bitrate calculation
|
|
2
|
+
constructor(tech) {
|
|
3
|
+
this.tech = tech.vhs;
|
|
4
|
+
this.player = tech.el().player; // Store player reference for playback bitrate calculation
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
getRenditionName
|
|
7
|
+
getRenditionName() {
|
|
8
8
|
try {
|
|
9
|
-
var media = this.tech.playlists.media()
|
|
10
|
-
if (media && media.attributes) return media.attributes.NAME
|
|
11
|
-
} catch (err) {
|
|
12
|
-
return null
|
|
9
|
+
var media = this.tech.playlists.media();
|
|
10
|
+
if (media && media.attributes) return media.attributes.NAME;
|
|
11
|
+
} catch (err) {}
|
|
12
|
+
return null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
getRenditionBitrate
|
|
15
|
+
getRenditionBitrate() {
|
|
16
16
|
try {
|
|
17
|
-
var media = this.tech.playlists.media()
|
|
18
|
-
if (media && media.attributes) return media.attributes.BANDWIDTH
|
|
19
|
-
} catch (err) {
|
|
20
|
-
return null
|
|
17
|
+
var media = this.tech.playlists.media();
|
|
18
|
+
if (media && media.attributes) return media.attributes.BANDWIDTH;
|
|
19
|
+
} catch (err) {}
|
|
20
|
+
return null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
getRenditionWidth
|
|
23
|
+
getRenditionWidth() {
|
|
24
24
|
try {
|
|
25
|
-
var media = this.tech.playlists.media()
|
|
25
|
+
var media = this.tech.playlists.media();
|
|
26
26
|
if (media && media.attributes && media.attributes.RESOLUTION) {
|
|
27
|
-
return media.attributes.RESOLUTION.width
|
|
27
|
+
return media.attributes.RESOLUTION.width;
|
|
28
28
|
}
|
|
29
|
-
} catch (err) {
|
|
30
|
-
return null
|
|
29
|
+
} catch (err) {}
|
|
30
|
+
return null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
getRenditionHeight
|
|
33
|
+
getRenditionHeight() {
|
|
34
34
|
try {
|
|
35
|
-
var media = this.tech.playlists.media()
|
|
35
|
+
var media = this.tech.playlists.media();
|
|
36
36
|
if (media && media.attributes && media.attributes.RESOLUTION) {
|
|
37
|
-
return media.attributes.RESOLUTION.height
|
|
37
|
+
return media.attributes.RESOLUTION.height;
|
|
38
38
|
}
|
|
39
|
-
} catch (err) {
|
|
40
|
-
return null
|
|
39
|
+
} catch (err) {}
|
|
40
|
+
return null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
getBitrate
|
|
43
|
+
getBitrate() {
|
|
44
44
|
// Calculate playback bitrate (actual content consumption rate)
|
|
45
|
-
return this.getContentBitratePlayback()
|
|
45
|
+
return this.getContentBitratePlayback();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
getManifestBitrate() {
|
|
49
49
|
try {
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
// Return highest available bitrate from all renditions
|
|
51
|
+
const playlists = this.tech.playlists.master.playlists;
|
|
52
|
+
if (playlists && playlists.length > 0) {
|
|
53
|
+
return Math.max(...playlists.map((p) => p.attributes.BANDWIDTH));
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
getSegmentDownloadBitrate() {
|
|
60
|
+
if (
|
|
61
|
+
this.tech.stats?.bandwidth !== undefined &&
|
|
62
|
+
this.tech.stats.bandwidth > 0
|
|
63
|
+
) {
|
|
64
|
+
return this.tech.stats.bandwidth;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
getNetworkDownloadBitrate() {
|
|
70
|
+
if (this.tech.throughput !== undefined && this.tech.throughput > 0) {
|
|
71
|
+
return this.tech.throughput;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
76
|
+
getContentBitratePlayback() {
|
|
77
|
+
try {
|
|
78
|
+
// Get the current active rendition's bitrate from manifest
|
|
79
|
+
const media = this.tech.playlists.media();
|
|
80
|
+
if (media && media.attributes) {
|
|
81
|
+
// Use AVERAGE-BANDWIDTH if available, fallback to BANDWIDTH
|
|
82
|
+
return (
|
|
83
|
+
media.attributes['AVERAGE-BANDWIDTH'] ||
|
|
84
|
+
media.attributes.BANDWIDTH ||
|
|
85
|
+
null
|
|
86
|
+
);
|
|
75
87
|
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
return null
|
|
88
|
+
} catch (err) {}
|
|
89
|
+
return null;
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
ContribHlsTech.isUsing = function (tech) {
|
|
82
|
-
return !!tech.vhs
|
|
83
|
-
}
|
|
94
|
+
return !!tech.vhs;
|
|
95
|
+
};
|
package/src/techs/hls-js.js
CHANGED
|
@@ -47,23 +47,40 @@ export default class HlsJs {
|
|
|
47
47
|
|
|
48
48
|
getContentBitratePlayback() {
|
|
49
49
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
// Get the current active level's bitrate from manifest
|
|
51
|
+
const level = this.tech.levels[this.tech.currentLevel];
|
|
52
|
+
if (level && level.bitrate) {
|
|
53
|
+
return level.bitrate;
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
getManifestBitrate() {
|
|
60
|
+
try {
|
|
61
|
+
// Return highest available bitrate from all renditions
|
|
62
|
+
if (this.tech.levels && this.tech.levels.length > 0) {
|
|
63
|
+
return Math.max(...this.tech.levels.map(l => l.bitrate));
|
|
63
64
|
}
|
|
64
65
|
} catch (err) {}
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
68
|
+
|
|
69
|
+
getSegmentDownloadBitrate() {
|
|
70
|
+
try {
|
|
71
|
+
// VHS stats.bandwidth
|
|
72
|
+
if (this.tech.stats && this.tech.stats.bandwidth > 0)
|
|
73
|
+
return this.tech.stats.bandwidth;
|
|
74
|
+
} catch (err) {}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getNetworkDownloadBitrate() {
|
|
79
|
+
if (this.tech.throughput && this.tech.throughput > 0) {
|
|
80
|
+
return this.tech.throughput;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
HlsJs.isUsing = function (tech) {
|
package/src/techs/shaka.js
CHANGED
|
@@ -18,6 +18,19 @@ export default class ShakaTech {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
getManifestBitrate() {
|
|
22
|
+
try {
|
|
23
|
+
// Return highest available bitrate from all variants
|
|
24
|
+
const tracks = this.tech.getVariantTracks();
|
|
25
|
+
if (tracks && tracks.length > 0) {
|
|
26
|
+
return Math.max(
|
|
27
|
+
...tracks.map((t) => t.videoBandwidth + (t.audioBandwidth || 0)),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
getRenditionWidth(tech) {
|
|
22
35
|
try {
|
|
23
36
|
var tracks = this.tech.getVariantTracks();
|
|
@@ -51,18 +64,32 @@ export default class ShakaTech {
|
|
|
51
64
|
|
|
52
65
|
getContentBitratePlayback() {
|
|
53
66
|
try {
|
|
67
|
+
// Get the current variant's bitrate from manifest (streamBandwidth)
|
|
54
68
|
var stats = this.tech.getStats();
|
|
55
|
-
if (stats) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
if (stats && stats.streamBandwidth && stats.streamBandwidth > 0) {
|
|
70
|
+
return stats.streamBandwidth;
|
|
71
|
+
}
|
|
72
|
+
} catch (err) {}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
getSegmentDownloadBitrate() {
|
|
77
|
+
try {
|
|
78
|
+
// Use estimatedBandwidth for measured bitrate
|
|
79
|
+
var stats = this.tech.getStats();
|
|
80
|
+
if (stats && stats.estimatedBandwidth > 0) {
|
|
81
|
+
return stats.estimatedBandwidth;
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getNetworkDownloadBitrate() {
|
|
88
|
+
try {
|
|
89
|
+
// Shaka: use estimatedBandwidth for download bitrate (no separate property)
|
|
90
|
+
var stats = this.tech.getStats();
|
|
91
|
+
if (stats && stats.estimatedBandwidth > 0) {
|
|
92
|
+
return stats.estimatedBandwidth;
|
|
66
93
|
}
|
|
67
94
|
} catch (err) {}
|
|
68
95
|
return null;
|
package/src/tracker.js
CHANGED
|
@@ -12,6 +12,7 @@ import DaiAdsTracker from './ads/dai';
|
|
|
12
12
|
export default class VideojsTracker extends nrvideo.VideoTracker {
|
|
13
13
|
constructor(player, options) {
|
|
14
14
|
super(player, options);
|
|
15
|
+
|
|
15
16
|
this.isContentEnd = false;
|
|
16
17
|
this.imaAdCuePoints = '';
|
|
17
18
|
this.daiInitialized = false;
|
|
@@ -117,37 +118,28 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
|
|
|
117
118
|
|
|
118
119
|
// Measures: Actual content consumption rate during playback
|
|
119
120
|
getContentBitratePlayback() {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Adjust playback time by playback rate
|
|
134
|
-
// If playing at 2x speed, you consume 2x the bitrate
|
|
135
|
-
const effectivePlaybackTime = currentTime / playbackRate;
|
|
136
|
-
|
|
137
|
-
if (effectivePlaybackTime > 0) {
|
|
138
|
-
const bitsTransferred = stats.mediaBytesTransferred * 8;
|
|
139
|
-
const playbackBitrate = bitsTransferred / effectivePlaybackTime;
|
|
140
|
-
return playbackBitrate;
|
|
141
|
-
}
|
|
121
|
+
try {
|
|
122
|
+
const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
|
|
123
|
+
|
|
124
|
+
// 1. Get the current active rendition (The most accurate "Playback Bitrate")
|
|
125
|
+
if (tech?.vhs?.playlists?.media()) {
|
|
126
|
+
const activePlaylist = tech.vhs.playlists.media();
|
|
127
|
+
// Use AVERAGE-BANDWIDTH if available, fallback to BANDWIDTH
|
|
128
|
+
return (
|
|
129
|
+
activePlaylist.attributes['AVERAGE-BANDWIDTH'] ||
|
|
130
|
+
activePlaylist.attributes.BANDWIDTH ||
|
|
131
|
+
null
|
|
132
|
+
);
|
|
142
133
|
}
|
|
143
|
-
}
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
// 2. Fallback to tech wrappers (Shaka/Hls.js) if they have a getBitrate method
|
|
136
|
+
const techWrapper = this.getTech();
|
|
137
|
+
if (techWrapper?.getBitrate) {
|
|
138
|
+
return techWrapper.getBitrate();
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
/* ignore */
|
|
149
142
|
}
|
|
150
|
-
|
|
151
143
|
return null;
|
|
152
144
|
}
|
|
153
145
|
|
|
@@ -158,14 +150,65 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
|
|
|
158
150
|
}
|
|
159
151
|
}
|
|
160
152
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
getManifestBitrate() {
|
|
154
|
+
try {
|
|
155
|
+
const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
|
|
156
|
+
// tech.vhs.playlists.master.playlists contains the array of all renditions
|
|
157
|
+
const allRenditions = tech?.vhs?.playlists?.master?.playlists;
|
|
158
|
+
|
|
159
|
+
if (allRenditions && allRenditions.length > 0) {
|
|
160
|
+
// Find the highest BANDWIDTH value in the list
|
|
161
|
+
const maxBitrate = Math.max(
|
|
162
|
+
...allRenditions.map((p) => p.attributes.BANDWIDTH || 0),
|
|
163
|
+
);
|
|
164
|
+
return maxBitrate > 0 ? maxBitrate : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fallback to tech wrappers (Shaka/Hls.js)
|
|
168
|
+
const techWrapper = this.getTech();
|
|
169
|
+
if (techWrapper?.getManifestBitrate) {
|
|
170
|
+
return techWrapper.getManifestBitrate();
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {
|
|
173
|
+
/* ignore */
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getSegmentDownloadBitrate() {
|
|
179
|
+
try {
|
|
180
|
+
const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
|
|
165
181
|
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
// VHS stats.bandwidth
|
|
183
|
+
if (tech?.vhs?.stats?.bandwidth && tech.vhs.stats.bandwidth > 0) {
|
|
184
|
+
return tech.vhs.stats.bandwidth;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Fallback to tech wrappers (Shaka/Hls.js)
|
|
188
|
+
const techWrapper = this.getTech();
|
|
189
|
+
if (techWrapper?.getSegmentDownloadBitrate) {
|
|
190
|
+
return techWrapper.getSegmentDownloadBitrate();
|
|
191
|
+
}
|
|
192
|
+
} catch (err) {
|
|
193
|
+
/* ignore */
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getNetworkDownloadBitrate() {
|
|
199
|
+
const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
|
|
200
|
+
|
|
201
|
+
if (tech?.vhs?.throughput && tech.vhs.throughput > 0) {
|
|
202
|
+
return tech.vhs.throughput;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Fallback to tech wrapper implementation
|
|
206
|
+
const techWrapper = this.getTech();
|
|
207
|
+
if (techWrapper?.getNetworkDownloadBitrate) {
|
|
208
|
+
return techWrapper.getNetworkDownloadBitrate();
|
|
168
209
|
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
169
212
|
}
|
|
170
213
|
|
|
171
214
|
getRenditionHeight() {
|
|
@@ -248,7 +291,6 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
|
|
|
248
291
|
this.player.on('waiting', this.onWaiting);
|
|
249
292
|
this.player.on('timeupdate', this.onTimeupdate);
|
|
250
293
|
this.player.on('ads-allpods-completed', this.OnAdsAllpodsCompleted);
|
|
251
|
-
|
|
252
294
|
this.player.on('stream-manager', this.onStreamManager);
|
|
253
295
|
}
|
|
254
296
|
|
|
@@ -271,7 +313,6 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
|
|
|
271
313
|
this.player.off('waiting', this.onWaiting);
|
|
272
314
|
this.player.off('timeupdate', this.onTimeupdate);
|
|
273
315
|
this.player.off('ads-allpods-completed', this.OnAdsAllpodsCompleted);
|
|
274
|
-
|
|
275
316
|
this.player.off('stream-manager', this.onStreamManager);
|
|
276
317
|
}
|
|
277
318
|
|