Skip to content

Commit c7774b5

Browse files
feat: capture request_url for HLS/DASH chunks, add browser env checks, fix destroyHlsMonitoring error, update UUID for web/mobile
1 parent 720a79e commit c7774b5

File tree

10 files changed

+99
-151
lines changed

10 files changed

+99
-151
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [1.0.4]
6+
- Added `request_url` to capture each video chunk URL in hls and dash players.
7+
- Implemented strict checks for `document` and `window` objects to ensure execution only in browser environments.
8+
- Fixed bug where `destroyHlsMonitoring` could throw a runtime error.
9+
- Updated UUID generation method to work seamlessly across both web and mobile applications.
10+
511
## [1.0.3]
612
- Updated `package.json` to include additional keywords related to video analytics, HLS, and DASH players.
713

package-lock.json

Lines changed: 2 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fastpix/video-data-core",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"author": "FastPix, Inc",
55
"license": "MIT",
66
"description": "FastPix Video Data SDK for real-time monitoring of HTML5 video players.",
@@ -41,8 +41,5 @@
4141
"esbuild": "^0.25.4",
4242
"prettier": "^3.3.3",
4343
"typescript": "^5.3.2"
44-
},
45-
"dependencies": {
46-
"uuid": "^11.0.5"
4744
}
4845
}

src/IdGenerationMethod/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { v4 as uuidv4 } from "uuid";
2-
31
const idCharacter = "000000";
42

53
// Returns 6 characters id
@@ -12,13 +10,23 @@ const generateIdToken: () => string = function () {
1210
return idCharacter.slice(idPart.length) + idPart;
1311
};
1412

13+
function generateFallbackUUID(): string {
14+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
15+
/[xy]/g,
16+
function (char) {
17+
const random = (Math.random() * 16) | 0;
18+
const value = char === "x" ? random : (random & 0x3) | 0x8;
19+
return value.toString(16);
20+
},
21+
);
22+
}
23+
1524
// Returns unique UUID
1625
const buildUUID: () => string = function () {
17-
return uuidv4();
26+
return generateFallbackUUID();
1827
};
1928

2029
const generateRandomIdentifier: () => string = function () {
21-
2230
return (
2331
idCharacter + ((Math.random() * Math.pow(36, 6)) << 0).toString(36)
2432
).slice(-6);

src/MonitorMetrics/PlaybackPulseHandler.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ interface Emitter {
1414
}
1515

1616
export class PlaybackPulseHandler {
17-
pulseInterval: boolean = false;
1817
playheadProgressing: boolean = false;
1918
pulse: Pulse;
2019
emitter: Emitter;
20+
pulseIntervalId: ReturnType<typeof setInterval> | null = null;
2121

2222
constructor(pulse: Pulse, emitter: Emitter) {
2323
this.pulse = pulse;
@@ -26,15 +26,21 @@ export class PlaybackPulseHandler {
2626
}
2727

2828
callPulseInterval() {
29-
if (this.pulse.worker) {
30-
this.pulse.worker.postMessage({ command: "initiatePulseInterval" });
29+
if (!this.pulseIntervalId) {
30+
this.pulse.dispatch("pulseStart");
31+
this.pulseIntervalId = setInterval(() => {
32+
this.pulse.dispatch("pulseStart");
33+
}, 25);
3134
}
3235
}
3336

3437
endPulseInterval() {
3538
this.pulse.playheadProgressing = false;
36-
if (this.pulse.worker) {
37-
this.pulse.worker.postMessage({ command: "demolishPulseInterval" });
39+
40+
if (this.pulseIntervalId) {
41+
clearInterval(this.pulseIntervalId);
42+
this.pulse.dispatch("pulseEnd");
43+
this.pulseIntervalId = null;
3844
}
3945
}
4046

@@ -56,7 +62,7 @@ export class PlaybackPulseHandler {
5662
};
5763

5864
handleTimeUpdate = () => {
59-
if (this.pulseInterval) {
65+
if (this.pulseIntervalId) {
6066
this.pulse.dispatch("pulseStart");
6167
}
6268
};

src/MonitorMetrics/PlayerEventHandler.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,13 @@ export class PlaybackEventHandler {
8787
this.previousBeaconData = null;
8888
this.sdkPageDetails = {
8989
viewer_connection_type: getNetworkConnection(),
90-
page_url: window?.location?.href ?? "",
90+
page_url: typeof window !== "undefined" ? window?.location?.href : "",
9191
};
92-
this.userData = this.disableCookies ? {} : getViewerCookie();
92+
93+
const canAccessDocument = typeof document !== "undefined";
94+
this.userData = (this.disableCookies || !canAccessDocument)
95+
? {}
96+
: getViewerCookie();
9397
}
9498

9599
sendData(event: string, obj: EventMetaData): void {
@@ -107,8 +111,13 @@ export class PlaybackEventHandler {
107111
);
108112
return;
109113
}
110-
const data = this.prepareEventData(event, obj);
111-
114+
let data = this.prepareEventData(event, obj);
115+
data = Object.fromEntries(
116+
Object.entries(data).filter(
117+
([_, value]) => value !== undefined && !Number.isNaN(value),
118+
),
119+
);
120+
112121
this.eventQueue.scheduleEvent(data);
113122

114123
if (event === "viewCompleted") {
@@ -143,11 +152,15 @@ export class PlaybackEventHandler {
143152
}
144153

145154
prepareEventData(event: string, obj: EventMetaData) {
146-
const cookieUpdater = this.disableCookies ? {} : this.updateCookies();
155+
const cookieSessionData =
156+
this.disableCookies || typeof document === "undefined"
157+
? {}
158+
: this.updateCookies();
159+
147160
const data = mergeObjects(
148161
this.sdkPageDetails,
149162
obj,
150-
cookieUpdater,
163+
cookieSessionData,
151164
this.userData,
152165
{
153166
event_name: event,
@@ -230,12 +243,16 @@ export class PlaybackEventHandler {
230243
}
231244
}
232245

233-
updateCookies(): {
234-
session_id: string;
235-
session_start: string;
236-
session_expiry_time: number;
237-
} {
238-
const data = getViewerData();
246+
updateCookies():
247+
| {
248+
session_id: string;
249+
session_start: string;
250+
session_expiry_time: number | string;
251+
}
252+
| {} {
253+
if (typeof document === "undefined") return {};
254+
255+
const data: any = getViewerData();
239256
const cookieTimer = Date.now();
240257

241258
if (

src/MonitorMetrics/index.ts

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
ListenerManager,
99
} from "../CommonMethods/index";
1010
import { buildUUID } from "../IdGenerationMethod/index";
11-
import { createTimerWorker, inlineWorkerText } from "../Worker/index";
1211
import { BufferMonitor, BufferProcessor } from "./VideoBufferMonitor";
1312
import { ErrorManager } from "./ErrorManager";
1413
import { PlaybackEventHandler } from "./PlayerEventHandler";
@@ -56,14 +55,15 @@ function nucleusState(
5655
disableCookies: actionableData.disableCookies ?? false,
5756
respectDoNotTrack: actionableData.respectDoNotTrack ?? false,
5857
allowRebufferTracking: false,
59-
disablePlayheadRebufferTracking: false,
58+
disablePlayheadRebufferTracking:
59+
actionableData.disablePlayheadRebufferTracking ?? false,
6060
errorConverter: function (errAttr: any) {
6161
return errAttr;
6262
},
6363
};
6464
actionableData = {
65-
actionableData,
6665
...defaultConfig,
66+
actionableData,
6767
};
6868
fileInstance.userConfigData = actionableData;
6969
fileInstance.fetchPlayheadTime =
@@ -90,34 +90,7 @@ function nucleusState(
9090
fileInstance.data.view_sequence_number = 1;
9191
fileInstance.data.player_sequence_number = 1;
9292
fileInstance.lastCheckedEventTime = void 0;
93-
94-
// Initiating web workers
95-
fileInstance.worker = createTimerWorker(inlineWorkerText);
96-
97-
// Message from web worker
98-
fileInstance.worker.onmessage = function (message: any) {
99-
let messageCommand: string = message.data.command;
100-
101-
switch (messageCommand) {
102-
case "pulseStart":
103-
fileInstance.dispatch(messageCommand);
104-
fileInstance.handlePulse.pulseInterval = true;
105-
break;
106-
107-
case "pulseEnd":
108-
fileInstance.playheadProgressing = false;
109-
fileInstance.dispatch(messageCommand);
110-
fileInstance.handlePulse.pulseInterval = false;
111-
break;
112-
113-
case "emitPulse":
114-
fileInstance.dispatch("pulse");
115-
break;
116-
117-
default:
118-
return;
119-
}
120-
};
93+
fileInstance.throbTimeoutId = undefined;
12194

12295
fileInstance.dispatch = function (
12396
name: string,
@@ -176,7 +149,10 @@ function nucleusState(
176149
fileInstance.demolishView();
177150
};
178151

179-
if (window && typeof window !== "undefined") {
152+
if (
153+
typeof window !== "undefined" &&
154+
typeof window.addEventListener !== "undefined"
155+
) {
180156
window.addEventListener(
181157
"pagehide",
182158
function (event) {
@@ -389,8 +365,6 @@ nucleusState.prototype.validateData = function () {
389365
};
390366

391367
nucleusState.prototype.filterData = function (str: string) {
392-
const workerInstance = this;
393-
394368
if (this.data.view_id) {
395369
if (
396370
this.data.player_source_duration > 0 ||
@@ -413,18 +387,29 @@ nucleusState.prototype.filterData = function (str: string) {
413387
this.eventsDispatcher.sendData(str, updatedata);
414388
this.data.view_sequence_number++;
415389
this.data.player_sequence_number++;
416-
workerInstance.worker.postMessage({
417-
command: "checkPulse",
418-
pausestate: workerInstance.data.player_is_paused,
419-
errortracker: workerInstance.warning.hasErrorOccurred,
420-
});
390+
391+
this.handlePulseEvent(this);
421392

422393
if (str === "viewCompleted") {
423394
delete this.data.view_id;
424395
}
425396
}
426397
};
427398

399+
nucleusState.prototype.handlePulseEvent = (instance: any) => {
400+
if (instance.throbTimeoutId) {
401+
clearTimeout(instance.throbTimeoutId);
402+
}
403+
404+
if (!instance.warning.hasErrorOccurred) {
405+
instance.throbTimeoutId = setTimeout(() => {
406+
if (!instance.data.player_is_paused) {
407+
instance.dispatch("pulse");
408+
}
409+
}, 10000);
410+
}
411+
};
412+
428413
nucleusState.prototype.refreshViewData = function () {
429414
const view = this;
430415
Object.keys(this.data).forEach(function (k) {

src/StreamMonitoring/HlsMonitoring.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const setupHlsMonitoring = (
2929
request_response_end: timerData.responseEnd,
3030
request_type: "manifest",
3131
request_hostname: getHostName(url),
32+
request_url: url ?? "",
3233
request_response_headers: headers,
3334
...details,
3435
};
@@ -135,7 +136,7 @@ export const setupHlsMonitoring = (
135136
) => {
136137
const switchLevel = hlstag.levels[lvl.level];
137138
if (!switchLevel?.attrs?.BANDWIDTH) {
138-
return;
139+
return;
139140
}
140141

141142
const levelSwitchEvent = {

0 commit comments

Comments
 (0)