eye-analysis
Version:
Eye Analysis - Browser-based eye tracking and screen recording library for research and experiments
1,533 lines (1,524 loc) • 98.4 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {
get: all[name],
enumerable: true,
configurable: true,
set: (newValue) => all[name] = () => newValue
});
};
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
// interaction.ts
var _interactionState, interactionState;
var init_interaction = __esm(() => {
_interactionState = {};
if (typeof globalThis !== "undefined") {
if (!globalThis.__eyeAnalysisInteractionState) {
globalThis.__eyeAnalysisInteractionState = _interactionState;
}
}
interactionState = new Proxy(_interactionState, {
get(target, prop) {
const globalState = globalThis.__eyeAnalysisInteractionState || target;
return globalState[prop];
},
set(target, prop, value) {
const globalState = globalThis.__eyeAnalysisInteractionState || target;
globalState[prop] = value;
return true;
}
});
});
// recorder/state.ts
var exports_state = {};
__export(exports_state, {
subscribe: () => subscribe,
resetState: () => resetState,
getSubscriberCount: () => getSubscriberCount,
getState: () => getState,
dispatch: () => dispatch
});
var getInitialState = () => ({
status: "idle",
currentSession: null,
isRecording: false,
recordingDuration: 0,
gazeDataCount: 0,
eventsCount: 0,
videoChunksCount: 0,
error: null,
lastUpdate: Date.now(),
recordingConfig: undefined,
startBrowserTime: undefined,
recordingStream: null
}), ensureGlobalState = () => {
if (!globalThis.__eyeAnalysisRecorderState) {
globalThis.__eyeAnalysisRecorderState = getInitialState();
}
if (!globalThis.__eyeAnalysisStateSubscribers) {
globalThis.__eyeAnalysisStateSubscribers = new Set;
}
}, getCurrentState = () => {
ensureGlobalState();
return globalThis.__eyeAnalysisRecorderState;
}, getSubscribers = () => {
ensureGlobalState();
return globalThis.__eyeAnalysisStateSubscribers;
}, stateReducer = (state, action) => {
switch (action.type) {
case "INITIALIZE":
return {
...state,
status: "initialized",
error: null,
lastUpdate: Date.now()
};
case "CREATE_SESSION":
return {
...state,
currentSession: action.payload,
error: null,
lastUpdate: Date.now()
};
case "UPDATE_SESSION":
return {
...state,
currentSession: action.payload,
error: null,
lastUpdate: Date.now()
};
case "START_RECORDING":
return {
...state,
status: "recording",
isRecording: true,
recordingDuration: 0,
error: null,
lastUpdate: Date.now(),
startBrowserTime: performance.now()
};
case "STOP_RECORDING":
return {
...state,
status: "stopped",
isRecording: false,
error: null,
lastUpdate: Date.now(),
startBrowserTime: undefined,
recordingStream: null
};
case "ADD_GAZE_DATA":
return {
...state,
gazeDataCount: state.gazeDataCount + 1,
lastUpdate: Date.now()
};
case "ADD_EVENT":
return {
...state,
eventsCount: state.eventsCount + 1,
lastUpdate: Date.now()
};
case "UPDATE_DURATION":
return {
...state,
recordingDuration: action.payload,
lastUpdate: Date.now()
};
case "SET_ERROR":
return {
...state,
status: "error",
error: action.payload,
lastUpdate: Date.now()
};
case "CLEAR_ERROR":
return {
...state,
error: null,
lastUpdate: Date.now()
};
case "CLEAR_SESSION":
return {
...state,
status: "initialized",
currentSession: null,
gazeDataCount: 0,
eventsCount: 0,
videoChunksCount: 0,
recordingDuration: 0,
startBrowserTime: undefined,
recordingStream: null,
lastUpdate: Date.now()
};
case "SET_RECORDING_CONFIG":
return {
...state,
recordingConfig: action.payload,
lastUpdate: Date.now()
};
case "SET_RECORDING_STREAM":
return {
...state,
recordingStream: action.payload,
lastUpdate: Date.now()
};
case "RESET":
return {
...getInitialState(),
lastUpdate: Date.now()
};
default:
return state;
}
}, getState = () => getCurrentState(), dispatch = (action) => {
const currentState = getCurrentState();
const newState = stateReducer(currentState, action);
if (newState !== currentState) {
globalThis.__eyeAnalysisRecorderState = newState;
const subscribers = getSubscribers();
subscribers.forEach((subscriber) => {
try {
subscriber(newState);
} catch (error) {
console.error("State subscriber error:", error);
}
});
}
}, subscribe = (subscriber) => {
const subscribers = getSubscribers();
subscribers.add(subscriber);
return () => {
subscribers.delete(subscriber);
};
}, getSubscriberCount = () => getSubscribers().size, resetState = () => {
dispatch({ type: "RESET" });
};
var init_state = __esm(() => {
ensureGlobalState();
});
// recorder/storage.ts
var DB_NAME = "EyeAnalysisDB", DB_VERSION = 4, getDb = () => {
return globalThis.__eyeAnalysisDB || null;
}, setDb = (database) => {
globalThis.__eyeAnalysisDB = database;
}, initializeStorage = () => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (currentDb) {
resolve();
return;
}
const request = indexedDB.open(DB_NAME, DB_VERSION);
setupDatabase(request, resolve, reject);
});
}, setupDatabase = (request, resolve, reject) => {
request.onerror = () => {
reject(new Error("Failed to open database"));
};
request.onsuccess = () => {
setDb(request.result);
resolve();
};
request.onupgradeneeded = () => {
const database = request.result;
if (!database.objectStoreNames.contains("sessions")) {
const sessionStore = database.createObjectStore("sessions", {
keyPath: "sessionId"
});
sessionStore.createIndex("participantId", "participantId", {
unique: false
});
sessionStore.createIndex("startTime", "startTime", { unique: false });
}
if (!database.objectStoreNames.contains("events")) {
const eventStore = database.createObjectStore("events", {
keyPath: "id"
});
eventStore.createIndex("sessionId", "sessionId", { unique: false });
eventStore.createIndex("timestamp", "timestamp", { unique: false });
}
if (!database.objectStoreNames.contains("gazeData")) {
const gazeStore = database.createObjectStore("gazeData", {
autoIncrement: true
});
gazeStore.createIndex("sessionId", "sessionId", { unique: false });
gazeStore.createIndex("systemTimestamp", "systemTimestamp", {
unique: false
});
}
if (!database.objectStoreNames.contains("videoChunks")) {
const videoStore = database.createObjectStore("videoChunks", {
keyPath: "id"
});
videoStore.createIndex("sessionId", "sessionId", { unique: false });
videoStore.createIndex("timestamp", "timestamp", { unique: false });
}
};
}, resetDatabase = () => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (currentDb) {
currentDb.close();
setDb(null);
}
const deleteRequest = indexedDB.deleteDatabase(DB_NAME);
deleteRequest.onsuccess = () => {
resolve();
};
deleteRequest.onerror = () => {
reject(new Error("Failed to reset database"));
};
});
}, saveSession = async (session) => {
await ensureDatabaseReady();
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["sessions"], "readwrite");
const store = transaction.objectStore("sessions");
const request = store.put(session);
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error("Failed to save session"));
});
}, getSession = async (sessionId) => {
await ensureDatabaseReady();
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["sessions"], "readonly");
const store = transaction.objectStore("sessions");
const request = store.get(sessionId);
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(new Error("Failed to get session"));
});
}, saveEvent = (event) => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["events"], "readwrite");
const store = transaction.objectStore("events");
const request = store.add(event);
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error("Failed to save event"));
});
}, saveGazeData = (sessionId, gazePoint) => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const dataWithSession = {
...gazePoint,
storageId: `${sessionId}-${gazePoint.systemTimestamp}-${Math.random()}`
};
const transaction = currentDb.transaction(["gazeData"], "readwrite");
const store = transaction.objectStore("gazeData");
const request = store.add(dataWithSession);
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error("Failed to save gaze data"));
});
}, saveVideoChunk = (chunk) => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["videoChunks"], "readwrite");
const store = transaction.objectStore("videoChunks");
const request = store.add(chunk);
request.onsuccess = () => resolve();
request.onerror = () => reject(new Error("Failed to save video chunk"));
});
}, ensureDatabaseReady = (retries = 10, delay = 500) => {
return new Promise((resolve, reject) => {
const check = (attemptsLeft) => {
const currentDb = getDb();
if (currentDb && currentDb.version === DB_VERSION) {
resolve();
return;
}
if (attemptsLeft === 0) {
reject(new Error(`Database not initialized or ready after ${retries} attempts. Current db: ${currentDb ? `version ${currentDb.version}` : "null"}`));
return;
}
setTimeout(() => check(attemptsLeft - 1), delay);
};
check(retries);
});
}, getSessionData = async (sessionId, options) => {
await ensureDatabaseReady();
const currentDb = getDb();
if (!currentDb) {
throw new Error("Database not initialized");
}
const session = await getSession(sessionId);
if (!session) {
throw new Error("Session not found");
}
const events = await new Promise((resolveEvents, rejectEvents) => {
const currentDb2 = getDb();
if (!currentDb2) {
rejectEvents(new Error("Database not initialized"));
return;
}
const transaction = currentDb2.transaction(["events"], "readonly");
const store = transaction.objectStore("events");
const index = store.index("sessionId");
const request = index.getAll(sessionId);
request.onsuccess = () => resolveEvents(request.result);
request.onerror = () => rejectEvents(new Error("Failed to get events"));
});
const gazeData = await new Promise((resolveGaze, rejectGaze) => {
const currentDb2 = getDb();
if (!currentDb2) {
rejectGaze(new Error("Database not initialized"));
return;
}
const transaction = currentDb2.transaction(["gazeData"], "readonly");
const store = transaction.objectStore("gazeData");
const index = store.index("sessionId");
const request = index.getAll(sessionId);
request.onsuccess = () => {
const data = request.result.map((item) => {
const { sessionId: _, ...gazePoint } = item;
return gazePoint;
});
resolveGaze(data);
};
request.onerror = () => rejectGaze(new Error("Failed to get gaze data"));
});
const videoChunks = await new Promise((resolveVideo, rejectVideo) => {
const currentDb2 = getDb();
if (!currentDb2) {
rejectVideo(new Error("Database not initialized"));
return;
}
const transaction = currentDb2.transaction(["videoChunks"], "readonly");
const store = transaction.objectStore("videoChunks");
const index = store.index("sessionId");
const request = index.getAll(sessionId);
request.onsuccess = () => {
const chunks = request.result.map((chunk) => ({
id: chunk.id,
sessionId: chunk.sessionId,
timestamp: chunk.timestamp,
chunkIndex: chunk.chunkIndex,
duration: chunk.duration,
size: chunk.data?.size || 0
}));
resolveVideo(chunks);
};
request.onerror = () => rejectVideo(new Error("Failed to get video chunks"));
});
let startBrowserTime = options?.startBrowserTime ?? session.metadata?.startBrowserTime;
let endBrowserTime = options?.endBrowserTime ?? session.metadata?.endBrowserTime;
if (!startBrowserTime || !endBrowserTime) {
const recordingStartEvent = events.find((e) => e.type === "recording_start");
const recordingStopEvent = events.find((e) => e.type === "recording_stop");
if (recordingStartEvent) {
startBrowserTime = startBrowserTime ?? recordingStartEvent.browserTimestamp;
}
if (recordingStopEvent) {
endBrowserTime = endBrowserTime ?? recordingStopEvent.browserTimestamp;
}
}
let filteredGazeData = gazeData;
let filteredEvents = events;
if (startBrowserTime !== undefined || endBrowserTime !== undefined) {
filteredGazeData = gazeData.filter((gaze) => {
if (startBrowserTime !== undefined && gaze.browserTimestamp < startBrowserTime) {
return false;
}
if (endBrowserTime !== undefined && gaze.browserTimestamp > endBrowserTime) {
return false;
}
return true;
});
filteredEvents = events.filter((event) => {
if ([
"session_start",
"session_stop",
"recording_start",
"recording_stop"
].includes(event.type)) {
return true;
}
if (startBrowserTime !== undefined && event.browserTimestamp < startBrowserTime) {
return false;
}
if (endBrowserTime !== undefined && event.browserTimestamp > endBrowserTime) {
return false;
}
return true;
});
}
const sessionData = {
session,
events: filteredEvents,
gazeData: filteredGazeData,
videoChunks,
metadata: {
totalDuration: session.endTime ? session.endTime - session.startTime : 0,
gazeDataPoints: filteredGazeData.length,
eventsCount: filteredEvents.length,
chunksCount: videoChunks.length,
exportedAt: new Date().toISOString(),
startBrowserTime,
endBrowserTime
}
};
return sessionData;
}, getVideoChunkData = async (chunkId) => {
await ensureDatabaseReady();
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["videoChunks"], "readonly");
const store = transaction.objectStore("videoChunks");
const request = store.get(chunkId);
request.onsuccess = () => {
const chunk = request.result;
resolve(chunk?.data || null);
};
request.onerror = () => reject(new Error("Failed to get video chunk data"));
});
}, getStorageUsage = async () => {
if ("storage" in navigator && "estimate" in navigator.storage) {
const estimate = await navigator.storage.estimate();
const used = estimate.usage || 0;
const available = estimate.quota || 0;
const percentage = available > 0 ? used / available * 100 : 0;
return { used, available, percentage };
}
return { used: 0, available: 0, percentage: 0 };
}, cleanupOldVideoChunks = async (keepRecentHours = 24) => {
const currentDb = getDb();
if (!currentDb) {
throw new Error("Database not initialized");
}
const cutoffTime = Date.now() - keepRecentHours * 60 * 60 * 1000;
let deletedCount = 0;
return new Promise((resolve, reject) => {
const currentDb2 = getDb();
if (!currentDb2) {
reject(new Error("Database not available"));
return;
}
const transaction = currentDb2.transaction(["videoChunks"], "readwrite");
const store = transaction.objectStore("videoChunks");
const index = store.index("timestamp");
const request = index.openCursor(IDBKeyRange.upperBound(cutoffTime));
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
cursor.delete();
deletedCount++;
cursor.continue();
}
};
transaction.oncomplete = () => resolve(deletedCount);
transaction.onerror = () => reject(new Error("Failed to cleanup video chunks"));
});
}, autoCleanupStorage = async (triggerPercentage = 80) => {
const usage = await getStorageUsage();
if (usage.percentage >= triggerPercentage) {
console.warn(`Storage usage at ${usage.percentage.toFixed(1)}%, starting cleanup...`);
await cleanupOldVideoChunks(12);
const newUsage = await getStorageUsage();
if (newUsage.percentage >= triggerPercentage) {
await cleanupOldVideoChunks(6);
}
}
}, getAllSessions = () => {
return new Promise((resolve, reject) => {
const currentDb = getDb();
if (!currentDb) {
reject(new Error("Database not initialized"));
return;
}
const transaction = currentDb.transaction(["sessions"], "readonly");
const store = transaction.objectStore("sessions");
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(new Error("Failed to get sessions"));
});
}, deleteSession = async (sessionId) => {
const currentDb = getDb();
if (!currentDb) {
throw new Error("Database not initialized");
}
const transaction = currentDb.transaction(["sessions", "events", "gazeData", "videoChunks"], "readwrite");
const sessionStore = transaction.objectStore("sessions");
sessionStore.delete(sessionId);
const eventStore = transaction.objectStore("events");
const eventIndex = eventStore.index("sessionId");
const eventRequest = eventIndex.openCursor(IDBKeyRange.only(sessionId));
eventRequest.onsuccess = () => {
const cursor = eventRequest.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
const gazeStore = transaction.objectStore("gazeData");
const gazeIndex = gazeStore.index("sessionId");
const gazeRequest = gazeIndex.openCursor(IDBKeyRange.only(sessionId));
gazeRequest.onsuccess = () => {
const cursor = gazeRequest.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
const videoStore = transaction.objectStore("videoChunks");
const videoIndex = videoStore.index("sessionId");
const videoRequest = videoIndex.openCursor(IDBKeyRange.only(sessionId));
videoRequest.onsuccess = () => {
const cursor = videoRequest.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
};
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject(new Error("Failed to delete session"));
});
};
// ../../node_modules/fflate/esm/browser.js
function deflateSync(data, opts) {
return dopt(data, opts || {}, 0, 0);
}
function strToU8(str, latin1) {
if (latin1) {
var ar_1 = new u8(str.length);
for (var i2 = 0;i2 < str.length; ++i2)
ar_1[i2] = str.charCodeAt(i2);
return ar_1;
}
if (te)
return te.encode(str);
var l = str.length;
var ar = new u8(str.length + (str.length >> 1));
var ai = 0;
var w = function(v) {
ar[ai++] = v;
};
for (var i2 = 0;i2 < l; ++i2) {
if (ai + 5 > ar.length) {
var n = new u8(ai + 8 + (l - i2 << 1));
n.set(ar);
ar = n;
}
var c = str.charCodeAt(i2);
if (c < 128 || latin1)
w(c);
else if (c < 2048)
w(192 | c >> 6), w(128 | c & 63);
else if (c > 55295 && c < 57344)
c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i2) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
else
w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
}
return slc(ar, 0, ai);
}
function zipSync(data, opts) {
if (!opts)
opts = {};
var r = {};
var files = [];
fltn(data, "", r, opts);
var o = 0;
var tot = 0;
for (var fn in r) {
var _a2 = r[fn], file = _a2[0], p = _a2[1];
var compression = p.level == 0 ? 0 : 8;
var f = strToU8(fn), s = f.length;
var com = p.comment, m = com && strToU8(com), ms = m && m.length;
var exl = exfl(p.extra);
if (s > 65535)
err(11);
var d = compression ? deflateSync(file, p) : file, l = d.length;
var c = crc();
c.p(file);
files.push(mrg(p, {
size: file.length,
crc: c.d(),
c: d,
f,
m,
u: s != fn.length || m && com.length != ms,
o,
compression
}));
o += 30 + s + exl + l;
tot += 76 + 2 * (s + exl) + (ms || 0) + l;
}
var out = new u8(tot + 22), oe = o, cdl = tot - o;
for (var i2 = 0;i2 < files.length; ++i2) {
var f = files[i2];
wzh(out, f.o, f, f.f, f.u, f.c.length);
var badd = 30 + f.f.length + exfl(f.extra);
out.set(f.c, f.o + badd);
wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
}
wzf(out, o, files.length, cdl, oe);
return out;
}
var u8, u16, i32, fleb, fdeb, clim, freb = function(eb, start) {
var b = new u16(31);
for (var i = 0;i < 31; ++i) {
b[i] = start += 1 << eb[i - 1];
}
var r = new i32(b[30]);
for (var i = 1;i < 30; ++i) {
for (var j = b[i];j < b[i + 1]; ++j) {
r[j] = j - b[i] << 5 | i;
}
}
return { b, r };
}, _a, fl, revfl, _b, fd, revfd, rev, x, i, hMap = function(cd, mb, r) {
var s = cd.length;
var i2 = 0;
var l = new u16(mb);
for (;i2 < s; ++i2) {
if (cd[i2])
++l[cd[i2] - 1];
}
var le = new u16(mb);
for (i2 = 1;i2 < mb; ++i2) {
le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
}
var co;
if (r) {
co = new u16(1 << mb);
var rvb = 15 - mb;
for (i2 = 0;i2 < s; ++i2) {
if (cd[i2]) {
var sv = i2 << 4 | cd[i2];
var r_1 = mb - cd[i2];
var v = le[cd[i2] - 1]++ << r_1;
for (var m = v | (1 << r_1) - 1;v <= m; ++v) {
co[rev[v] >> rvb] = sv;
}
}
}
} else {
co = new u16(s);
for (i2 = 0;i2 < s; ++i2) {
if (cd[i2]) {
co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
}
}
}
return co;
}, flt, i, i, i, i, fdt, i, flm, fdm, shft = function(p) {
return (p + 7) / 8 | 0;
}, slc = function(v, s, e) {
if (s == null || s < 0)
s = 0;
if (e == null || e > v.length)
e = v.length;
return new u8(v.subarray(s, e));
}, ec, err = function(ind, msg, nt) {
var e = new Error(msg || ec[ind]);
e.code = ind;
if (Error.captureStackTrace)
Error.captureStackTrace(e, err);
if (!nt)
throw e;
return e;
}, wbits = function(d, p, v) {
v <<= p & 7;
var o = p / 8 | 0;
d[o] |= v;
d[o + 1] |= v >> 8;
}, wbits16 = function(d, p, v) {
v <<= p & 7;
var o = p / 8 | 0;
d[o] |= v;
d[o + 1] |= v >> 8;
d[o + 2] |= v >> 16;
}, hTree = function(d, mb) {
var t = [];
for (var i2 = 0;i2 < d.length; ++i2) {
if (d[i2])
t.push({ s: i2, f: d[i2] });
}
var s = t.length;
var t2 = t.slice();
if (!s)
return { t: et, l: 0 };
if (s == 1) {
var v = new u8(t[0].s + 1);
v[t[0].s] = 1;
return { t: v, l: 1 };
}
t.sort(function(a, b) {
return a.f - b.f;
});
t.push({ s: -1, f: 25001 });
var l = t[0], r = t[1], i0 = 0, i1 = 1, i22 = 2;
t[0] = { s: -1, f: l.f + r.f, l, r };
while (i1 != s - 1) {
l = t[t[i0].f < t[i22].f ? i0++ : i22++];
r = t[i0 != i1 && t[i0].f < t[i22].f ? i0++ : i22++];
t[i1++] = { s: -1, f: l.f + r.f, l, r };
}
var maxSym = t2[0].s;
for (var i2 = 1;i2 < s; ++i2) {
if (t2[i2].s > maxSym)
maxSym = t2[i2].s;
}
var tr = new u16(maxSym + 1);
var mbt = ln(t[i1 - 1], tr, 0);
if (mbt > mb) {
var i2 = 0, dt = 0;
var lft = mbt - mb, cst = 1 << lft;
t2.sort(function(a, b) {
return tr[b.s] - tr[a.s] || a.f - b.f;
});
for (;i2 < s; ++i2) {
var i2_1 = t2[i2].s;
if (tr[i2_1] > mb) {
dt += cst - (1 << mbt - tr[i2_1]);
tr[i2_1] = mb;
} else
break;
}
dt >>= lft;
while (dt > 0) {
var i2_2 = t2[i2].s;
if (tr[i2_2] < mb)
dt -= 1 << mb - tr[i2_2]++ - 1;
else
++i2;
}
for (;i2 >= 0 && dt; --i2) {
var i2_3 = t2[i2].s;
if (tr[i2_3] == mb) {
--tr[i2_3];
++dt;
}
}
mbt = mb;
}
return { t: new u8(tr), l: mbt };
}, ln = function(n, l, d) {
return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d;
}, lc = function(c) {
var s = c.length;
while (s && !c[--s])
;
var cl = new u16(++s);
var cli = 0, cln = c[0], cls = 1;
var w = function(v) {
cl[cli++] = v;
};
for (var i2 = 1;i2 <= s; ++i2) {
if (c[i2] == cln && i2 != s)
++cls;
else {
if (!cln && cls > 2) {
for (;cls > 138; cls -= 138)
w(32754);
if (cls > 2) {
w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305);
cls = 0;
}
} else if (cls > 3) {
w(cln), --cls;
for (;cls > 6; cls -= 6)
w(8304);
if (cls > 2)
w(cls - 3 << 5 | 8208), cls = 0;
}
while (cls--)
w(cln);
cls = 1;
cln = c[i2];
}
}
return { c: cl.subarray(0, cli), n: s };
}, clen = function(cf, cl) {
var l = 0;
for (var i2 = 0;i2 < cl.length; ++i2)
l += cf[i2] * cl[i2];
return l;
}, wfblk = function(out, pos, dat) {
var s = dat.length;
var o = shft(pos + 2);
out[o] = s & 255;
out[o + 1] = s >> 8;
out[o + 2] = out[o] ^ 255;
out[o + 3] = out[o + 1] ^ 255;
for (var i2 = 0;i2 < s; ++i2)
out[o + i2 + 4] = dat[i2];
return (o + 4 + s) * 8;
}, wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
wbits(out, p++, final);
++lf[256];
var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l;
var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l;
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
var lcfreq = new u16(19);
for (var i2 = 0;i2 < lclt.length; ++i2)
++lcfreq[lclt[i2] & 31];
for (var i2 = 0;i2 < lcdt.length; ++i2)
++lcfreq[lcdt[i2] & 31];
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
var nlcc = 19;
for (;nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
;
var flen = bl + 5 << 3;
var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
if (bs >= 0 && flen <= ftlen && flen <= dtlen)
return wfblk(out, p, dat.subarray(bs, bs + bl));
var lm, ll, dm, dl;
wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
if (dtlen < ftlen) {
lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
var llm = hMap(lct, mlcb, 0);
wbits(out, p, nlc - 257);
wbits(out, p + 5, ndc - 1);
wbits(out, p + 10, nlcc - 4);
p += 14;
for (var i2 = 0;i2 < nlcc; ++i2)
wbits(out, p + 3 * i2, lct[clim[i2]]);
p += 3 * nlcc;
var lcts = [lclt, lcdt];
for (var it = 0;it < 2; ++it) {
var clct = lcts[it];
for (var i2 = 0;i2 < clct.length; ++i2) {
var len = clct[i2] & 31;
wbits(out, p, llm[len]), p += lct[len];
if (len > 15)
wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
}
}
} else {
lm = flm, ll = flt, dm = fdm, dl = fdt;
}
for (var i2 = 0;i2 < li; ++i2) {
var sym = syms[i2];
if (sym > 255) {
var len = sym >> 18 & 31;
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
if (len > 7)
wbits(out, p, sym >> 23 & 31), p += fleb[len];
var dst = sym & 31;
wbits16(out, p, dm[dst]), p += dl[dst];
if (dst > 3)
wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst];
} else {
wbits16(out, p, lm[sym]), p += ll[sym];
}
}
wbits16(out, p, lm[256]);
return p + ll[256];
}, deo, et, dflt = function(dat, lvl, plvl, pre, post, st) {
var s = st.z || dat.length;
var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
var w = o.subarray(pre, o.length - post);
var lst = st.l;
var pos = (st.r || 0) & 7;
if (lvl) {
if (pos)
w[0] = st.r >> 3;
var opt = deo[lvl - 1];
var n = opt >> 13, c = opt & 8191;
var msk_1 = (1 << plvl) - 1;
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
var hsh = function(i3) {
return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1;
};
var syms = new i32(25000);
var lf = new u16(288), df = new u16(32);
var lc_1 = 0, eb = 0, i2 = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
for (;i2 + 2 < s; ++i2) {
var hv = hsh(i2);
var imod = i2 & 32767, pimod = head[hv];
prev[imod] = pimod;
head[hv] = imod;
if (wi <= i2) {
var rem = s - i2;
if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i2 - bs, pos);
li = lc_1 = eb = 0, bs = i2;
for (var j = 0;j < 286; ++j)
lf[j] = 0;
for (var j = 0;j < 30; ++j)
df[j] = 0;
}
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
if (rem > 2 && hv == hsh(i2 - dif)) {
var maxn = Math.min(n, rem) - 1;
var maxd = Math.min(32767, i2);
var ml = Math.min(258, rem);
while (dif <= maxd && --ch_1 && imod != pimod) {
if (dat[i2 + l] == dat[i2 + l - dif]) {
var nl = 0;
for (;nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
;
if (nl > l) {
l = nl, d = dif;
if (nl > maxn)
break;
var mmd = Math.min(dif, nl - 2);
var md = 0;
for (var j = 0;j < mmd; ++j) {
var ti = i2 - dif + j & 32767;
var pti = prev[ti];
var cd = ti - pti & 32767;
if (cd > md)
md = cd, pimod = ti;
}
}
}
imod = pimod, pimod = prev[imod];
dif += imod - pimod & 32767;
}
}
if (d) {
syms[li++] = 268435456 | revfl[l] << 18 | revfd[d];
var lin = revfl[l] & 31, din = revfd[d] & 31;
eb += fleb[lin] + fdeb[din];
++lf[257 + lin];
++df[din];
wi = i2 + l;
++lc_1;
} else {
syms[li++] = dat[i2];
++lf[dat[i2]];
}
}
}
for (i2 = Math.max(i2, wi);i2 < s; ++i2) {
syms[li++] = dat[i2];
++lf[dat[i2]];
}
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i2 - bs, pos);
if (!lst) {
st.r = pos & 7 | w[pos / 8 | 0] << 3;
pos -= 7;
st.h = head, st.p = prev, st.i = i2, st.w = wi;
}
} else {
for (var i2 = st.w || 0;i2 < s + lst; i2 += 65535) {
var e = i2 + 65535;
if (e >= s) {
w[pos / 8 | 0] = lst;
e = s;
}
pos = wfblk(w, pos + 1, dat.subarray(i2, e));
}
st.i = s;
}
return slc(o, 0, pre + shft(pos) + post);
}, crct, crc = function() {
var c = -1;
return {
p: function(d) {
var cr = c;
for (var i2 = 0;i2 < d.length; ++i2)
cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8;
c = cr;
},
d: function() {
return ~c;
}
};
}, dopt = function(dat, opt, pre, post, st) {
if (!st) {
st = { l: 1 };
if (opt.dictionary) {
var dict = opt.dictionary.subarray(-32768);
var newDat = new u8(dict.length + dat.length);
newDat.set(dict);
newDat.set(dat, dict.length);
dat = newDat;
st.w = dict.length;
}
}
return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st);
}, mrg = function(a, b) {
var o = {};
for (var k in a)
o[k] = a[k];
for (var k in b)
o[k] = b[k];
return o;
}, wbytes = function(d, b, v) {
for (;v; ++b)
d[b] = v, v >>>= 8;
}, fltn = function(d, p, t, o) {
for (var k in d) {
var val = d[k], n = p + k, op = o;
if (Array.isArray(val))
op = mrg(o, val[1]), val = val[0];
if (val instanceof u8)
t[n] = [val, op];
else {
t[n += "/"] = [new u8(0), op];
fltn(val, n, t, o);
}
}
}, te, td, tds = 0, exfl = function(ex) {
var le = 0;
if (ex) {
for (var k in ex) {
var l = ex[k].length;
if (l > 65535)
err(9);
le += l + 4;
}
}
return le;
}, wzh = function(d, b, f, fn, u, c, ce, co) {
var fl2 = fn.length, ex = f.extra, col = co && co.length;
var exl = exfl(ex);
wbytes(d, b, ce != null ? 33639248 : 67324752), b += 4;
if (ce != null)
d[b++] = 20, d[b++] = f.os;
d[b] = 20, b += 2;
d[b++] = f.flag << 1 | (c < 0 && 8), d[b++] = u && 8;
d[b++] = f.compression & 255, d[b++] = f.compression >> 8;
var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;
if (y < 0 || y > 119)
err(10);
wbytes(d, b, y << 25 | dt.getMonth() + 1 << 21 | dt.getDate() << 16 | dt.getHours() << 11 | dt.getMinutes() << 5 | dt.getSeconds() >> 1), b += 4;
if (c != -1) {
wbytes(d, b, f.crc);
wbytes(d, b + 4, c < 0 ? -c - 2 : c);
wbytes(d, b + 8, f.size);
}
wbytes(d, b + 12, fl2);
wbytes(d, b + 14, exl), b += 16;
if (ce != null) {
wbytes(d, b, col);
wbytes(d, b + 6, f.attrs);
wbytes(d, b + 10, ce), b += 14;
}
d.set(fn, b);
b += fl2;
if (exl) {
for (var k in ex) {
var exf = ex[k], l = exf.length;
wbytes(d, b, +k);
wbytes(d, b + 2, l);
d.set(exf, b + 4), b += 4 + l;
}
}
if (col)
d.set(co, b), b += col;
return b;
}, wzf = function(o, b, c, d, e) {
wbytes(o, b, 101010256);
wbytes(o, b + 8, c);
wbytes(o, b + 10, c);
wbytes(o, b + 12, d);
wbytes(o, b + 16, e);
};
var init_browser = __esm(() => {
u8 = Uint8Array;
u16 = Uint16Array;
i32 = Int32Array;
fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0]);
fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0]);
clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
_a = freb(fleb, 2);
fl = _a.b;
revfl = _a.r;
fl[28] = 258, revfl[258] = 28;
_b = freb(fdeb, 0);
fd = _b.b;
revfd = _b.r;
rev = new u16(32768);
for (i = 0;i < 32768; ++i) {
x = (i & 43690) >> 1 | (i & 21845) << 1;
x = (x & 52428) >> 2 | (x & 13107) << 2;
x = (x & 61680) >> 4 | (x & 3855) << 4;
rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
}
flt = new u8(288);
for (i = 0;i < 144; ++i)
flt[i] = 8;
for (i = 144;i < 256; ++i)
flt[i] = 9;
for (i = 256;i < 280; ++i)
flt[i] = 7;
for (i = 280;i < 288; ++i)
flt[i] = 8;
fdt = new u8(32);
for (i = 0;i < 32; ++i)
fdt[i] = 5;
flm = /* @__PURE__ */ hMap(flt, 9, 0);
fdm = /* @__PURE__ */ hMap(fdt, 5, 0);
ec = [
"unexpected EOF",
"invalid block type",
"invalid length/literal",
"invalid distance",
"stream finished",
"no stream handler",
,
"no callback",
"invalid UTF-8 data",
"extra field too long",
"date not in range 1980-2099",
"filename too long",
"stream finishing",
"invalid zip data"
];
deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
et = /* @__PURE__ */ new u8(0);
crct = /* @__PURE__ */ function() {
var t = new Int32Array(256);
for (var i2 = 0;i2 < 256; ++i2) {
var c = i2, k = 9;
while (--k)
c = (c & 1 && -306674912) ^ c >>> 1;
t[i2] = c;
}
return t;
}();
te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder;
td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder;
try {
td.decode(et, { stream: true });
tds = 1;
} catch (e) {}
});
// recorder/export.ts
var exports_export = {};
__export(exports_export, {
gazeDataToCSV: () => gazeDataToCSV,
exportExperimentDataset: () => exportExperimentDataset,
eventsToCSV: () => eventsToCSV,
downloadSession: () => downloadSession,
downloadFile: () => downloadFile,
downloadCompleteSessionData: () => downloadCompleteSessionData,
createSessionSummaryText: () => createSessionSummaryText,
createMetadataJSON: () => createMetadataJSON
});
var fieldExtractors, createFieldExtractors = (startBrowserTime) => [
{ header: "sessionId", getValue: (p) => p.sessionId },
{
header: "elapsedTime",
getValue: (p) => p.browserTimestamp !== undefined ? p.browserTimestamp - startBrowserTime : null
},
...fieldExtractors.slice(1)
], gazeDataToCSV = (gazeData, startBrowserTime) => {
if (startBrowserTime === undefined) {
throw new Error("startBrowserTime is required for elapsedTime calculation");
}
if (gazeData.length === 0) {
return "";
}
const extractors = createFieldExtractors(startBrowserTime);
const activeExtractors = extractors.filter((extractor) => gazeData.some((point) => {
const value = extractor.getValue(point);
return value !== null && value !== undefined;
}));
const headers = activeExtractors.map((extractor) => extractor.header);
const csvRows = [headers.join(",")];
for (const point of gazeData) {
const row = activeExtractors.map((extractor) => {
const value = extractor.getValue(point);
return value ?? "";
});
csvRows.push(row.join(","));
}
return csvRows.join(`
`);
}, eventsToCSV = (events, startBrowserTime) => {
if (startBrowserTime === undefined) {
throw new Error("startBrowserTime is required for elapsedTime calculation");
}
const headers = [
"id",
"sessionId",
"type",
"timestamp",
"elapsedTime",
"data"
];
const csvRows = [headers.join(",")];
for (const event of events) {
const elapsedTime = event.browserTimestamp !== undefined ? event.browserTimestamp - startBrowserTime : null;
const row = [
event.id,
event.sessionId,
event.type,
event.timestamp,
elapsedTime !== null ? elapsedTime : "",
event.data ? JSON.stringify(event.data).replace(/"/g, '""') : ""
];
csvRows.push(row.map((field) => `"${field}"`).join(","));
}
return csvRows.join(`
`);
}, createMetadataJSON = (sessionData) => {
return {
sessionInfo: sessionData.session,
metadata: sessionData.metadata,
videoChunks: sessionData.videoChunks.map((chunk) => ({
...chunk,
note: `Video chunk ${chunk.chunkIndex} - ${chunk.size} bytes`
})),
summary: {
totalGazePoints: sessionData.gazeData.length,
totalEvents: sessionData.events.length,
totalVideoChunks: sessionData.videoChunks.length,
sessionDuration: sessionData.metadata.totalDuration,
recordingStartTime: new Date(sessionData.session.startTime).toISOString(),
recordingEndTime: sessionData.session.endTime ? new Date(sessionData.session.endTime).toISOString() : null,
startBrowserTime: sessionData.metadata.startBrowserTime,
endBrowserTime: sessionData.metadata.endBrowserTime,
elapsedTimeNote: sessionData.metadata.startBrowserTime !== undefined ? "elapsedTime = browserTimestamp - startBrowserTime (in milliseconds)" : "elapsedTime not available - recording was never started"
}
};
}, downloadFile = (content, filename, mimeType = "text/plain") => {
const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, exportExperimentDataset = async (sessionIds, options = {}) => {
const allFiles = {};
const includeOptions = {
metadata: true,
gaze: true,
events: true,
video: true,
...options.include
};
for (const sessionId of sessionIds) {
try {
const sessionFolder = `session_${sessionId}`;
const filesToDownload = await collectSessionFiles(sessionId, {
includeOptions,
prefix: sessionFolder
});
for (const file of filesToDownload) {
if (file.content instanceof Blob) {
allFiles[file.filename] = new Uint8Array(await file.content.arrayBuffer());
} else {
allFiles[file.filename] = strToU8(file.content);
}
}
} catch (error) {
console.error(`Failed to process session ${sessionId}:`, error);
}
}
const datasetSummary = {
exportedAt: new Date().toISOString(),
totalSessions: sessionIds.length,
sessionIds,
description: "Combined dataset from Eye Analysis",
includeOptions
};
allFiles["dataset-summary.json"] = strToU8(JSON.stringify(datasetSummary, null, 2));
const zipped = zipSync(allFiles);
const zipBlob = new Blob([zipped], { type: "application/zip" });
const filename = `experiment-dataset-${new Date().toISOString().split("T")[0]}.zip`;
downloadFile(zipBlob, filename, "application/zip");
}, getSessionName = (sessionId) => {
return `session-${sessionId}-${new Date().toISOString().split("T")[0]}`;
}, collectSessionFiles = async (sessionId, options) => {
await initializeStorage();
const sessionData = await getSessionData(sessionId);
if ((options.includeOptions.gaze || options.includeOptions.events) && sessionData.metadata.startBrowserTime === undefined) {
throw new Error("Cannot export CSV data: startBrowserTime is required for elapsedTime calculation but is not available. This usually means recording was never started.");
}
const sessionName = getSessionName(sessionId);
const filesToDownload = [];
const getFilename = (baseName) => {
if (options.prefix) {
return `${options.prefix}/${baseName}`;
}
return `${sessionName}-${baseName}`;
};
if (options.includeOptions.metadata) {
const metadata = createMetadataJSON(sessionData);
filesToDownload.push({
content: JSON.stringify(metadata, null, 2),
filename: getFilename("metadata.json"),
mimeType: "application/json"
});
}
if (options.includeOptions.gaze && sessionData.gazeData.length > 0) {
const gazeDataCSV = gazeDataToCSV(sessionData.gazeData, sessionData.metadata.startBrowserTime);
filesToDownload.push({
content: gazeDataCSV,
filename: getFilename("gaze.csv"),
mimeType: "text/csv"
});
}
if (options.includeOptions.events && sessionData.events.length > 0) {
const eventsCSV = eventsToCSV(sessionData.events, sessionData.metadata.startBrowserTime);
filesToDownload.push({
content: eventsCSV,
filename: getFilename("events.csv"),
mimeType: "text/csv"
});
}
if (options.includeOptions.video && sessionData.videoChunks.length > 0) {
try {
const videoBlobs = [];
let videoFormat = "webm";
for (const chunk of sessionData.videoChunks) {
const chunkData = await getVideoChunkData(chunk.id);
if (chunkData) {
videoBlobs.push(chunkData);
if (chunkData.type.includes("mp4")) {
videoFormat = "mp4";
} else if (chunkData.type.includes("webm")) {
videoFormat = "webm";
}
}
}
if (videoBlobs.length > 0) {
const sessionVideoFormat = sessionData.session.config.videoFormat || videoFormat;
const mimeType = sessionVideoFormat === "mp4" ? "video/mp4" : "video/webm";
const videoBlob = new Blob(videoBlobs, { type: mimeType });
filesToDownload.push({
content: videoBlob,
filename: getFilename(`recording.${sessionVideoFormat}`),
mimeType
});
}
} catch (error) {
console.error("Failed to prepare video data:", error);
}
}
return filesToDownload;
}, downloadSession = async (sessionId, options = {}) => {
const includeOptions = {
metadata: true,
gaze: true,
events: true,
video: true,
...options.include
};
const asZip = options.asZip ?? false;
const filesToDownload = await collectSessionFiles(sessionId, {
includeOptions,
prefix: options.prefix
});
if (filesToDownload.length === 0) {
console.warn("No files to download");
return;
}
if (asZip) {
const files = {};
for (const file of filesToDownload) {
const filename = options.prefix ? file.filename.split("/").pop() || file.filename : file.filename.split("-").pop() || file.filename;
const content = file.content instanceof Blob ? new Uint8Array(await file.content.arrayBuffer()) : strToU8(file.content);
files[filename] = content;
}
const sessionName = getSessionName(sessionId);
const zipped = zipSync(files);
const zipBlob = new Blob([zipped], { type: "application/zip" });
downloadFile(zipBlob, `${sessionName}.zip`, "application/zip");
} else {
for (const file of filesToDownload) {
downloadFile(file.content, file.filename, file.mimeType);
}
}
}, downloadCompleteSessionData = async (sessionId) => {
const sessionData = await getSessionData(sessionId);
if ((sessionData.gazeData.length > 0 || sessionData.events.length > 0) && sessionData.metadata.startBrowserTime === undefined) {
throw new Error("Cannot export CSV data: startBrowserTime is required for elapsedTime calculation but is not available. This usually means recording was never started.");
}
const sessionName = `session-${sessionId}-${new Date().toISOString().split("T")[0]}`;
const metadata = createMetadataJSON(sessionData);
downloadFile(JSON.stringify(metadata, null, 2), `${sessionName}-metadata.json`, "application/json");
if (sessionData.gazeData.length > 0) {
const gazeCSV = gazeDataToCSV(sessionData.gazeData, sessionData.metadata.startBrowserTime);
downloadFile(gazeCSV, `${sessionName}-gaze.csv`, "text/csv");
}
if (sessionData.events.length > 0) {
const eventsCSV = eventsToCSV(sessionData.events, sessionData.metadata.startBrowserTime);
downloadFile(eventsCSV, `${sessionName}-events.csv`, "text/csv");
}
if (sessionData.videoChunks.length > 0) {
try {
const videoBlobs = [];
for (const chunk of sessionData.videoChunks) {
const chunkData = await getVideoChunkData(chunk.id);
if (chunkData) {
videoBlobs.push(chunkData);
}
}
if (videoBlobs.length > 0) {
const combinedVideo = new Blob(videoBlobs, { type: "video/webm" });
downloadFile(combinedVideo, `${sessionName}-recording.webm`, "video/webm");
}
} catch (error) {
console.error("Failed to download video data:", error);
}
}
}, createSessionSummaryText = (sessionData) => {
const duration = Math.round(sessionData.metadata.totalDuration / 1000);
const minutes = Math.floor(duration / 60);
const seconds = duration % 60;
return `Web Eye Tracking Recorder - Session Summary
==========================================
Session ID: ${sessionData.session.sessionId}
Participant: ${sessionData.session.participantId}
Experiment: ${sessionData.session.experimentType}
Start Time: ${new Date(sessionData.session.startTime).toLocaleString()}
End Time: ${sessionData.session.endTime ? new Date(sessionData.session.endTime).toLocaleString() : "N/A"}
Duration: ${minutes}m ${seconds}s
Data Summary:
- Gaze Data Points: ${sessionData.gazeData.length}
- Events Recorded: ${sessionData.events.length}
- Video Chunks: ${sessionData.videoChunks.length}
Recording Settings:
- Frame Rate: ${sessionData.session.config.frameRate} fps
- Quality: ${sessionData.session.config.quality}
- Chunk Duration: ${sessionData.session.config.chunkDuration}s
Export Date: ${new Date().toLocaleString()}
Generated by Web Eye Tracking Recorder
`;
};
var init_export = __esm(() => {
init_browser();
fieldExtractors = [
{ header: "sessionId", getValue: (p) => p.sessionId },
{ header: "deviceTimeStamp", getValue: (p) => p.deviceTimeStamp },
{ header: "systemTimestamp", getValue: (p) => p.systemTimestamp },
{ header: "screenX", getValue: (p) => p.screenX },
{ header: "screenY", getValue: (p) => p.screenY },
{ header: "screenWidth", getValue: (p) => p.screenWidth },
{ header: "screenHeight", getValue: (p) => p.screenHeight },
{ header: "normalized", getValue: (p) => p.normalized },
{ header: "contentX", getValue: (p) => p.contentX },
{ header: "contentY", getValue: (p) => p.contentY },
{ header: "confidence", getValue: (p) => p.confidence },
{ header: "leftEye - screenX", getValue: (p) => p.leftEye?.screenX },
{ header: "leftEye - screenY", getValue: (p) =>