@newrelic/video-videojs 4.0.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/video-videojs",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "New relic tracker for Videojs",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -30,16 +30,13 @@
30
30
  "@babel/core": "^7.24.5",
31
31
  "@babel/plugin-transform-modules-commonjs": "^7.24.1",
32
32
  "@babel/preset-env": "^7.24.5",
33
- "aws-sdk": "^2.920.0",
34
33
  "@newrelic/newrelic-oss-cli": "^0.1.2",
34
+ "aws-sdk": "^2.920.0",
35
35
  "babel-loader": "^9.1.3",
36
36
  "videojs-ima": "2.1.0",
37
37
  "webpack": "^5.91.0",
38
38
  "webpack-cli": "^4.9.2"
39
39
  },
40
- "dependencies": {
41
- "@newrelic/video-core": "^4.0.0"
42
- },
43
40
  "files": [
44
41
  "THIRD_PARTY_NOTICES.md",
45
42
  "dist",
@@ -50,5 +47,8 @@
50
47
  ],
51
48
  "publishConfig": {
52
49
  "access": "public"
50
+ },
51
+ "dependencies": {
52
+ "@newrelic/video-core": "^4.0.0"
53
53
  }
54
54
  }
@@ -1,45 +1,95 @@
1
1
  export default class ContribHlsTech {
2
- constructor (tech) {
3
- this.tech = tech.vhs
2
+ constructor(tech) {
3
+ this.tech = tech.vhs;
4
+ this.player = tech.el().player; // Store player reference for playback bitrate calculation
4
5
  }
5
6
 
6
- getRenditionName () {
7
+ getRenditionName() {
7
8
  try {
8
- var media = this.tech.playlists.media()
9
- if (media && media.attributes) return media.attributes.NAME
10
- } catch (err) { }
11
- return null
9
+ var media = this.tech.playlists.media();
10
+ if (media && media.attributes) return media.attributes.NAME;
11
+ } catch (err) {}
12
+ return null;
12
13
  }
13
14
 
14
- getRenditionBitrate () {
15
+ getRenditionBitrate() {
15
16
  try {
16
- var media = this.tech.playlists.media()
17
- if (media && media.attributes) return media.attributes.BANDWIDTH
18
- } catch (err) { }
19
- return null
17
+ var media = this.tech.playlists.media();
18
+ if (media && media.attributes) return media.attributes.BANDWIDTH;
19
+ } catch (err) {}
20
+ return null;
20
21
  }
21
22
 
22
- getRenditionWidth () {
23
+ getRenditionWidth() {
23
24
  try {
24
- var media = this.tech.playlists.media()
25
+ var media = this.tech.playlists.media();
25
26
  if (media && media.attributes && media.attributes.RESOLUTION) {
26
- return media.attributes.RESOLUTION.width
27
+ return media.attributes.RESOLUTION.width;
27
28
  }
28
- } catch (err) { }
29
- return null
29
+ } catch (err) {}
30
+ return null;
30
31
  }
31
32
 
32
- getRenditionHeight () {
33
+ getRenditionHeight() {
33
34
  try {
34
- var media = this.tech.playlists.media()
35
+ var media = this.tech.playlists.media();
35
36
  if (media && media.attributes && media.attributes.RESOLUTION) {
36
- return media.attributes.RESOLUTION.height
37
+ return media.attributes.RESOLUTION.height;
37
38
  }
38
- } catch (err) { }
39
- return null
39
+ } catch (err) {}
40
+ return null;
41
+ }
42
+
43
+ getBitrate() {
44
+ // Calculate playback bitrate (actual content consumption rate)
45
+ return this.getContentBitratePlayback();
46
+ }
47
+
48
+ getManifestBitrate() {
49
+ try {
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
+
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
+ }
68
+
69
+ getNetworkDownloadBitrate() {
70
+ if (this.tech.throughput !== undefined && this.tech.throughput > 0) {
71
+ return this.tech.throughput;
72
+ }
73
+ return null;
74
+ }
75
+
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
+ );
87
+ }
88
+ } catch (err) {}
89
+ return null;
40
90
  }
41
91
  }
42
92
 
43
93
  ContribHlsTech.isUsing = function (tech) {
44
- return !!tech.vhs
45
- }
94
+ return !!tech.vhs;
95
+ };
@@ -1,45 +1,88 @@
1
1
  export default class HlsJs {
2
- constructor (tech) {
3
- this.tech = tech.vhs_
2
+ constructor(tech) {
3
+ this.tech = tech.vhs_;
4
+ this.player = tech.el().player; // Store player reference for currentTime
4
5
  }
5
6
 
6
- getResource (tech) {
7
- return this.tech.url
7
+ getResource(tech) {
8
+ return this.tech.url;
8
9
  }
9
10
 
10
- getRenditionName (tech) {
11
+ getRenditionName(tech) {
11
12
  try {
12
- var level = this.tech.levels[this.tech.currentLevel]
13
- if (level && level.name) return level.name
14
- } catch (err) { }
15
- return null
13
+ var level = this.tech.levels[this.tech.currentLevel];
14
+ if (level && level.name) return level.name;
15
+ } catch (err) {}
16
+ return null;
16
17
  }
17
18
 
18
- getRenditionBitrate (tech) {
19
+ getRenditionBitrate(tech) {
19
20
  try {
20
- var level = this.tech.levels[this.tech.currentLevel]
21
- if (level && level.bitrate) return level.bitrate
22
- } catch (err) { }
23
- return null
21
+ var level = this.tech.levels[this.tech.currentLevel];
22
+ if (level && level.bitrate) return level.bitrate;
23
+ } catch (err) {}
24
+ return null;
24
25
  }
25
26
 
26
- getRenditionWidth (tech) {
27
+ getRenditionWidth(tech) {
27
28
  try {
28
- var level = this.tech.levels[this.tech.currentLevel]
29
- if (level && level.width) return level.width
30
- } catch (err) { }
31
- return null
29
+ var level = this.tech.levels[this.tech.currentLevel];
30
+ if (level && level.width) return level.width;
31
+ } catch (err) {}
32
+ return null;
32
33
  }
33
34
 
34
- getRenditionHeight (tech) {
35
+ getRenditionHeight(tech) {
35
36
  try {
36
- var level = this.tech.levels[this.tech.currentLevel]
37
- if (level && level.height) return level.height
38
- } catch (err) { }
39
- return null
37
+ var level = this.tech.levels[this.tech.currentLevel];
38
+ if (level && level.height) return level.height;
39
+ } catch (err) {}
40
+ return null;
41
+ }
42
+
43
+ getBitrate() {
44
+ // Default uses playback bitrate (Method 4)
45
+ return this.getContentBitratePlayback();
46
+ }
47
+
48
+ getContentBitratePlayback() {
49
+ try {
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
+ }
58
+
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));
64
+ }
65
+ } catch (err) {}
66
+ return null;
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;
40
83
  }
41
84
  }
42
85
 
43
86
  HlsJs.isUsing = function (tech) {
44
- return !!tech.vhs_
45
- }
87
+ return !!tech.vhs_;
88
+ };
@@ -1,49 +1,101 @@
1
1
  export default class ShakaTech {
2
- constructor (tech) {
3
- this.tech = tech.shakaPlayer
2
+ constructor(tech) {
3
+ this.tech = tech.shakaPlayer;
4
+ this.player = tech.el().player; // Store player reference for playback bitrate calculation
4
5
  }
5
6
 
6
- getSrc (tech) {
7
+ getSrc(tech) {
7
8
  try {
8
- return this.tech.getManifestUri()
9
+ return this.tech.getManifestUri();
9
10
  } catch (err) {}
10
- return null
11
+ return null;
11
12
  }
12
13
 
13
- getRenditionBitrate (tech) {
14
+ getRenditionBitrate(tech) {
14
15
  try {
15
- return this.tech.getStats().streamBandwidth
16
- } catch (err) { }
17
- return null
16
+ return this.tech.getStats().streamBandwidth;
17
+ } catch (err) {}
18
+ return null;
18
19
  }
19
20
 
20
- getRenditionWidth (tech) {
21
+ getManifestBitrate() {
21
22
  try {
22
- var tracks = this.tech.getVariantTracks()
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
+
34
+ getRenditionWidth(tech) {
35
+ try {
36
+ var tracks = this.tech.getVariantTracks();
23
37
  for (var i in tracks) {
24
- var track = tracks[i]
38
+ var track = tracks[i];
25
39
  if (track.active && track.type === 'video') {
26
- return track.width
40
+ return track.width;
27
41
  }
28
42
  }
29
- } catch (err) { }
30
- return null
43
+ } catch (err) {}
44
+ return null;
31
45
  }
32
46
 
33
- getRenditionHeight (tech) {
47
+ getRenditionHeight(tech) {
34
48
  try {
35
- var tracks = this.tech.getVariantTracks()
49
+ var tracks = this.tech.getVariantTracks();
36
50
  for (var i in tracks) {
37
- var track = tracks[i]
51
+ var track = tracks[i];
38
52
  if (track.active && track.type === 'video') {
39
- return track.height
53
+ return track.height;
40
54
  }
41
55
  }
42
- } catch (err) { }
43
- return null
56
+ } catch (err) {}
57
+ return null;
58
+ }
59
+
60
+ getBitrate() {
61
+ // Calculate playback bitrate (actual content consumption rate)
62
+ return this.getContentBitratePlayback();
63
+ }
64
+
65
+ getContentBitratePlayback() {
66
+ try {
67
+ // Get the current variant's bitrate from manifest (streamBandwidth)
68
+ var stats = this.tech.getStats();
69
+ if (stats && stats.streamBandwidth && stats.streamBandwidth > 0) {
70
+ return stats.streamBandwidth;
71
+ }
72
+ } catch (err) {}
73
+ return null;
74
+ }
75
+
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;
93
+ }
94
+ } catch (err) {}
95
+ return null;
44
96
  }
45
97
  }
46
98
 
47
99
  ShakaTech.isUsing = function (tech) {
48
- return !!tech.shakaPlayer
49
- }
100
+ return !!tech.shakaPlayer;
101
+ };
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;
@@ -112,176 +113,102 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
112
113
  }
113
114
 
114
115
  getBitrate() {
115
- let videoBitrate = 0;
116
- let audioBitrate = 0;
117
- let totalBitrate = 0;
116
+ return this.getContentBitratePlayback();
117
+ }
118
118
 
119
- if (this.player) {
119
+ // Measures: Actual content consumption rate during playback
120
+ getContentBitratePlayback() {
121
+ try {
120
122
  const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
121
123
 
122
- if (tech) {
123
- let playlists, currentMedia;
124
-
125
- // Method 1: Try VHS (Video HTTP Streaming) - most common for HLS/DASH
126
- if (tech.vhs && tech.vhs.playlists && tech.vhs.playlists.media) {
127
- playlists = tech.vhs.playlists;
128
- currentMedia = tech.vhs.playlists.media();
129
- } else if (tech.hls && tech.hls.playlists && tech.hls.playlists.media) {
130
- playlists = tech.hls.playlists;
131
- currentMedia = tech.hls.playlists.media();
132
- }
133
-
134
- if (currentMedia && currentMedia.attributes) {
135
- if (currentMedia.attributes.BANDWIDTH) {
136
- videoBitrate = currentMedia.attributes.BANDWIDTH;
137
- }
138
-
139
- // Get audio bitrate if available
140
- const audioTracks = this.player.audioTracks();
141
- let activeAudioTrack;
142
-
143
- if (audioTracks && audioTracks.length > 0) {
144
- for (let i = 0; i < audioTracks.length; i++) {
145
- if (audioTracks[i].enabled) {
146
- activeAudioTrack = audioTracks[i];
147
- break;
148
- }
149
- }
150
- }
151
-
152
- if (activeAudioTrack && currentMedia.attributes.AUDIO) {
153
- let masterPlaylist;
154
- if (playlists) {
155
- masterPlaylist = playlists.master || playlists.main;
156
- }
157
-
158
- if (
159
- masterPlaylist &&
160
- masterPlaylist.mediaGroups &&
161
- masterPlaylist.mediaGroups.AUDIO
162
- ) {
163
- const audioGroup =
164
- masterPlaylist.mediaGroups.AUDIO[currentMedia.attributes.AUDIO];
165
- const audioMediaInfo =
166
- audioGroup && audioGroup[activeAudioTrack.id];
167
-
168
- if (
169
- audioMediaInfo &&
170
- audioMediaInfo.playlists &&
171
- audioMediaInfo.playlists[0]
172
- ) {
173
- const audioPlaylist = audioMediaInfo.playlists[0].attributes;
174
- if (audioPlaylist && audioPlaylist.BANDWIDTH) {
175
- audioBitrate = audioPlaylist.BANDWIDTH;
176
- }
177
- }
178
- }
179
- }
180
-
181
- totalBitrate = videoBitrate + audioBitrate;
182
-
183
- return totalBitrate; // Return in bps
184
- }
185
-
186
- // Method 2: Try Shaka Player
187
- const shakaPlayer =
188
- tech.shakaPlayer_ || tech.shaka_ || tech.shakaPlayer;
189
- if (shakaPlayer && typeof shakaPlayer.getStats === 'function') {
190
- const stats = shakaPlayer.getStats();
191
- if (stats && stats.streamBandwidth) {
192
- return stats.streamBandwidth;
193
- }
194
- }
195
-
196
- // Method 3: Try HLS.js
197
- const hlsJs = tech.hls_;
198
- if (hlsJs && hlsJs.levels && hlsJs.currentLevel >= 0) {
199
- const currentLevel = hlsJs.levels[hlsJs.currentLevel];
200
- if (currentLevel && currentLevel.bitrate) {
201
- return currentLevel.bitrate;
202
- }
203
- }
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
+ );
204
133
  }
205
134
 
206
- // Method 4: Try DASH.js
207
- let dashPlayer;
208
- if (this.player.mediaPlayer) {
209
- dashPlayer = this.player.mediaPlayer;
210
- } else if (this.player.dash && this.player.dash.mediaPlayer) {
211
- dashPlayer = this.player.dash.mediaPlayer;
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();
212
139
  }
140
+ } catch (err) {
141
+ /* ignore */
142
+ }
143
+ return null;
144
+ }
213
145
 
214
- if (
215
- dashPlayer &&
216
- typeof dashPlayer.getQualityFor === 'function' &&
217
- typeof dashPlayer.getBitrateInfoListFor === 'function'
218
- ) {
219
- // Get audio bitrate
220
- const audioQuality = dashPlayer.getQualityFor('audio');
221
- const audioBitrateList = dashPlayer.getBitrateInfoListFor('audio');
222
- if (
223
- audioQuality !== undefined &&
224
- audioBitrateList &&
225
- audioBitrateList[audioQuality] &&
226
- audioBitrateList[audioQuality].bitrate
227
- ) {
228
- audioBitrate = audioBitrateList[audioQuality].bitrate;
229
- }
230
-
231
- // Get video bitrate
232
- const videoQuality = dashPlayer.getQualityFor('video');
233
- const videoBitrateList = dashPlayer.getBitrateInfoListFor('video');
234
- if (
235
- videoQuality !== undefined &&
236
- videoBitrateList &&
237
- videoBitrateList[videoQuality] &&
238
- videoBitrateList[videoQuality].bitrate
239
- ) {
240
- videoBitrate = videoBitrateList[videoQuality].bitrate;
241
- } else {
242
- videoBitrate = videoBitrate || 0;
243
- }
244
-
245
- totalBitrate = audioBitrate + videoBitrate;
246
- return totalBitrate;
247
- }
146
+ getRenditionName() {
147
+ let tech = this.getTech();
148
+ if (tech && tech.getRenditionName) {
149
+ return tech.getRenditionName();
248
150
  }
151
+ }
249
152
 
250
- // Fallback: Try tech-specific implementations from your wrappers
251
- const techWrapper = this.getTech();
252
- if (techWrapper) {
253
- if (
254
- techWrapper.getBitrate &&
255
- typeof techWrapper.getBitrate === 'function'
256
- ) {
257
- return techWrapper.getBitrate();
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;
258
165
  }
259
166
 
260
- if (
261
- techWrapper.tech &&
262
- techWrapper.tech.stats &&
263
- techWrapper.tech.stats.bandwidth
264
- ) {
265
- return techWrapper.tech.stats.bandwidth;
167
+ // Fallback to tech wrappers (Shaka/Hls.js)
168
+ const techWrapper = this.getTech();
169
+ if (techWrapper?.getManifestBitrate) {
170
+ return techWrapper.getManifestBitrate();
266
171
  }
172
+ } catch (e) {
173
+ /* ignore */
267
174
  }
268
-
269
175
  return null;
270
176
  }
271
177
 
272
- getRenditionName() {
273
- let tech = this.getTech();
274
- if (tech && tech.getRenditionName) {
275
- return tech.getRenditionName();
178
+ getSegmentDownloadBitrate() {
179
+ try {
180
+ const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
181
+
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 */
276
194
  }
195
+ return null;
277
196
  }
278
197
 
279
- getRenditionBitrate() {
280
- let tech = this.getTech();
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
+ }
281
204
 
282
- if (tech && tech.getRenditionBitrate) {
283
- return tech.getRenditionBitrate();
205
+ // Fallback to tech wrapper implementation
206
+ const techWrapper = this.getTech();
207
+ if (techWrapper?.getNetworkDownloadBitrate) {
208
+ return techWrapper.getNetworkDownloadBitrate();
284
209
  }
210
+
211
+ return null;
285
212
  }
286
213
 
287
214
  getRenditionHeight() {
@@ -364,7 +291,6 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
364
291
  this.player.on('waiting', this.onWaiting);
365
292
  this.player.on('timeupdate', this.onTimeupdate);
366
293
  this.player.on('ads-allpods-completed', this.OnAdsAllpodsCompleted);
367
-
368
294
  this.player.on('stream-manager', this.onStreamManager);
369
295
  }
370
296
 
@@ -387,7 +313,6 @@ export default class VideojsTracker extends nrvideo.VideoTracker {
387
313
  this.player.off('waiting', this.onWaiting);
388
314
  this.player.off('timeupdate', this.onTimeupdate);
389
315
  this.player.off('ads-allpods-completed', this.OnAdsAllpodsCompleted);
390
-
391
316
  this.player.off('stream-manager', this.onStreamManager);
392
317
  }
393
318