feat(fix-a3): rename TransferredVideoChunk → TransferredVideoSegment
Pure rename pass — zero behavioural change at any call site. The
prior "chunk" naming is a vestige of D-09..D-11's chunk-level
buffer; under D-13 the unit of transfer is a self-contained ~10 s
WebM segment, so the type name now matches the data shape.
Renames propagated atomically:
- src/shared/types.ts
* TransferredVideoChunk → TransferredVideoSegment
(also: the `isFirst?: boolean` field is dropped — D-13 segments
are all implicitly their own header, so the flag is meaningless
and only existed for the retired ring-buffer's pin semantics)
* VideoChunk → VideoSegment (drops `isFirst?` for the same reason)
* PortMessage.chunks? → PortMessage.segments?
* VideoBufferResponse.chunks → VideoBufferResponse.segments
- src/offscreen/recorder.ts
* import type rename
* encodeAndSendBuffer's per-element accumulator + filter type
* the port.postMessage payload field
- src/background/index.ts
* import type rename
* getVideoBufferFromOffscreen reads `(msg as {segments?}).segments`
(matching the new wire field name)
* empty-buffer returns `{ segments: [] }`
* mergeVideoSegments signature takes VideoSegment[]
* createArchive consumes videoBufferResponse.segments
* saveArchive log line says "segments"
Verification:
- npx tsc --noEmit clean.
- npx vitest run → 28 passed / 2 failed (the 2 fixture-empirical
ffmpeg dry-runs; unchanged — they're gated on ./smoke.sh regen).
- npm run build succeeds, all 60 modules transformed.
- grep predicates clean in src/:
* no addChunk / trimAged / firstChunkSaved / isFirst
* no TransferredVideoChunk / VideoChunk (old names)
* 21 occurrences of new names propagated correctly
- Pre-existing port-serialization.test.ts still GREEN: its `isFirst`
references are inside inline-test-scoped objects (not imports
from production types), and its tests assert JSON-roundtrip
behaviour rather than the production type shape.
This commit is contained in:
@@ -2,8 +2,8 @@ import { Logger } from '../shared/logger';
|
||||
import { base64ToBlob } from '../shared/binary';
|
||||
import type {
|
||||
Message,
|
||||
TransferredVideoChunk,
|
||||
VideoChunk,
|
||||
TransferredVideoSegment,
|
||||
VideoSegment,
|
||||
SessionMetadata,
|
||||
VideoBufferResponse
|
||||
} from '../shared/types';
|
||||
@@ -105,14 +105,14 @@ const BUFFER_FETCH_TIMEOUT_MS = 2_000;
|
||||
async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> {
|
||||
if (videoPort === null) {
|
||||
logger.warn('No offscreen port available; returning empty buffer');
|
||||
return { chunks: [] };
|
||||
return { segments: [] };
|
||||
}
|
||||
const port = videoPort;
|
||||
return new Promise<VideoBufferResponse>((resolve) => {
|
||||
const timer = setTimeout(() => {
|
||||
port.onMessage.removeListener(handler);
|
||||
logger.warn(`Buffer fetch timed out after ${BUFFER_FETCH_TIMEOUT_MS} ms`);
|
||||
resolve({ chunks: [] });
|
||||
resolve({ segments: [] });
|
||||
}, BUFFER_FETCH_TIMEOUT_MS);
|
||||
const handler = (msg: unknown) => {
|
||||
if (
|
||||
@@ -122,20 +122,19 @@ async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> {
|
||||
) {
|
||||
clearTimeout(timer);
|
||||
port.onMessage.removeListener(handler);
|
||||
// D-12 wire format: payload arrives as TransferredVideoChunk[]
|
||||
// — base64 string + MIME — because chrome.runtime.Port
|
||||
// JSON-serializes across extension contexts. Decode each entry
|
||||
// back into a VideoChunk. D-13 lifecycle: each entry is now a
|
||||
// self-contained WebM segment (~10 s, EBML header + seed
|
||||
// keyframe), not a partial chunk; concatenating them
|
||||
// sequentially produces a multi-EBML-header file Chrome plays
|
||||
// natively. See src/shared/binary.ts + RESEARCH.md Pattern 3.
|
||||
// D-12 wire format + D-13 segment lifecycle: payload arrives
|
||||
// as TransferredVideoSegment[] (base64 string + MIME). Decode
|
||||
// each entry back into a VideoSegment — each is a
|
||||
// self-contained ~10 s WebM (EBML header + seed keyframe).
|
||||
// Concatenating them sequentially produces a multi-EBML-header
|
||||
// file Chrome plays natively. See src/shared/binary.ts +
|
||||
// RESEARCH.md Pattern 3.
|
||||
const wireSegments =
|
||||
(msg as { chunks?: TransferredVideoChunk[] }).chunks ?? [];
|
||||
const chunks: VideoChunk[] = [];
|
||||
(msg as { segments?: TransferredVideoSegment[] }).segments ?? [];
|
||||
const segments: VideoSegment[] = [];
|
||||
for (const wire of wireSegments) {
|
||||
try {
|
||||
chunks.push({
|
||||
segments.push({
|
||||
data: base64ToBlob(wire.data, wire.type || VIDEO_MIME_FALLBACK),
|
||||
timestamp: wire.timestamp,
|
||||
});
|
||||
@@ -147,7 +146,7 @@ async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> {
|
||||
);
|
||||
}
|
||||
}
|
||||
resolve({ chunks });
|
||||
resolve({ segments });
|
||||
}
|
||||
};
|
||||
port.onMessage.addListener(handler);
|
||||
@@ -238,7 +237,7 @@ async function captureScreenshot(): Promise<Blob> {
|
||||
|
||||
// Склейка сегментов в один WebM-файл.
|
||||
//
|
||||
// Под D-13 каждый VideoChunk — это самодостаточный ~10-секундный WebM
|
||||
// Под D-13 каждый VideoSegment — это самодостаточный ~10-секундный WebM
|
||||
// (собственный EBML-заголовок + seed keyframe). Сортируем по timestamp
|
||||
// и склеиваем подряд: получаем multi-EBML-header файл, который Chrome
|
||||
// проигрывает последовательно (см. SPEC §10 #7 — требуется проигрывание
|
||||
@@ -246,7 +245,7 @@ async function captureScreenshot(): Promise<Blob> {
|
||||
//
|
||||
// Никакой логики «pin первого чанка» или header-retention больше нет
|
||||
// — это снято вместе с D-09..D-11 и активацией D-13 в recorder.ts.
|
||||
function mergeVideoSegments(segments: VideoChunk[]): Blob {
|
||||
function mergeVideoSegments(segments: VideoSegment[]): Blob {
|
||||
logger.log(`Merging ${segments.length} segments`);
|
||||
|
||||
// Сортируем по времени, чтобы сохранить правильный порядок (старшие
|
||||
@@ -286,9 +285,9 @@ async function createArchive(
|
||||
|
||||
const zip = new JSZip();
|
||||
|
||||
// Добавляем видео (D-13: чанки в ответе — это уже сегменты)
|
||||
if (videoBufferResponse.chunks.length > 0) {
|
||||
const videoBlob = mergeVideoSegments(videoBufferResponse.chunks);
|
||||
// Добавляем видео (D-13: каждая запись — самодостаточный WebM-сегмент)
|
||||
if (videoBufferResponse.segments.length > 0) {
|
||||
const videoBlob = mergeVideoSegments(videoBufferResponse.segments);
|
||||
zip.file('video/last_30sec.webm', videoBlob);
|
||||
logger.log(`✓ Added video: ${videoBlob.size} bytes`);
|
||||
} else {
|
||||
@@ -386,7 +385,7 @@ async function saveArchive() {
|
||||
|
||||
// Получаем видео буфер из offscreen через long-lived port (D-17)
|
||||
const videoBufferResp = await getVideoBufferFromOffscreen();
|
||||
logger.log(`Video buffer: ${videoBufferResp.chunks.length} chunks`);
|
||||
logger.log(`Video buffer: ${videoBufferResp.segments.length} segments`);
|
||||
|
||||
// Получаем rrweb события от content script
|
||||
logger.log(`Requesting rrweb events from content script on tab ${tab.id} (${tab.url})...`);
|
||||
|
||||
Reference in New Issue
Block a user