if (typeof importScripts === "function") {
if (!globalThis.BitneedleBrowserFormatting) {
importScripts("./browser-formatting.js");
}
if (!globalThis.BitneedleEncodecBundleNames) {
importScripts("./encodec-bundle-names.js");
}
if (!globalThis.BitneedleOnnxRuntimeSession) {
importScripts("./onnx-runtime-session.js");
}
if (!globalThis.BitneedleOnnxWorkerTensors) {
importScripts("./onnx-worker-tensors.js");
}
if (!globalThis.BitneedleEcdcPcmLayout) {
importScripts("./ecdc-pcm-layout.js");
}
}
const WORKER_GENERATION_CACHE_VERSION =
new URL(self.location.href).searchParams.get("v") || "dev";
const WORKER_PERF_LOG_ENABLED =
new URL(self.location.href).searchParams.get("perf") === "1";
const WORKER_DECODE_DEBUG_ENABLED =
new URL(self.location.href).searchParams.get("decode_debug") === "1";
const MOSS_NANO_DECODE_ENABLED =
new URL(self.location.href).searchParams.get("mossnano") === "1";
const ONNX_RUNTIME_BASE_URL = "./wasm/onnxruntime-web/";
const ENCODEC_BUNDLE_BASE_URL = "./wasm/encodec-rs/onnx-bundles";
const MOSSNANO_WASM_BASE_URL = "/wasm/mossnano-rs";
const MOSS_NANO_CODEC = "moss-audio-tokenizer-nano-rvq16";
const MOSS_NANO_MODEL_ROOT = `${MOSSNANO_WASM_BASE_URL}/weights/MOSS-Audio-Tokenizer-Nano-ONNX`;
const MOSS_NANO_RECORD_PROFILE_CHUNK_SECONDS = Object.freeze({
single45: 1.333,
lp: 1.8,
});
let onnxRuntimeModulePromise = null;
let playerWasmModulePromise = null;
let playerAppWasmModule = null;
let mossNanoWasmModulePromise = null;
let mossNanoDecodeSessionPromise = null;
function versionedWorkerAssetUrl(path) {
const url = new URL(path, self.location.href);
url.searchParams.set("v", WORKER_GENERATION_CACHE_VERSION);
return url.toString();
}
function versionedWorkerManifestPartUrl(part, manifestUrl) {
const url = new URL(part, manifestUrl);
url.searchParams.set("v", WORKER_GENERATION_CACHE_VERSION);
return url.toString();
}
const {
formatDecimal,
workerErrorMessage,
} = globalThis.BitneedleBrowserFormatting;
const {
expectedFramePcmSamples,
floatToS16Sample,
frameStartSample,
triangleWeight,
} = globalThis.BitneedleEcdcPcmLayout || {};
if (
typeof expectedFramePcmSamples !== "function" ||
typeof floatToS16Sample !== "function" ||
typeof frameStartSample !== "function" ||
typeof triangleWeight !== "function"
) {
throw new Error("Bitneedle ECDC PCM layout helpers were not loaded before client-playback-worker.js.");
}
function postDecodeProgress(id, payload, transfer = []) {
self.postMessage({
id,
progress: {
status: "decoding",
...payload,
},
}, transfer);
}
function workerPerfStart(label, details = {}) {
if (!WORKER_PERF_LOG_ENABLED) {
return null;
}
return { label, details, startedAt: performance.now() };
}
function workerPerfEnd(id, mark, details = {}) {
if (!mark) {
return;
}
self.postMessage({
id,
perf: {
label: mark.label,
...mark.details,
...details,
ms: Math.round((performance.now() - mark.startedAt) * 10) / 10,
},
});
}
function createWorkerEcdcCacheProofContext(ecdc) {
const context = JSON.parse(
requirePlayerAppWasmFunction("createPlayerEcdcCacheProofContextJson")(
ecdc instanceof Uint8Array ? ecdc : new Uint8Array(ecdc || 0),
),
);
return context && typeof context === "object" ? context : null;
}
function reportSkippedDecodePacket(id, label, error) {
const message = `${label}: ${workerErrorMessage(error)}`;
console.error(`[bitneedle-player] ${message}`, error);
postDecodeProgress(id, {
status: "warning",
msg: message,
warning: message,
error: workerErrorMessage(error),
});
}
function workerDecodeDebug(...args) {
if (!WORKER_DECODE_DEBUG_ENABLED) {
return;
}
console.log(...args);
}
function workerDecodeInfo(...args) {
if (!WORKER_DECODE_DEBUG_ENABLED) {
return;
}
console.info(...args);
}
function workerDecodeWarn(...args) {
if (!WORKER_DECODE_DEBUG_ENABLED) {
return;
}
console.warn(...args);
}
async function ensureOnnxRuntimeModule() {
if (!onnxRuntimeModulePromise) {
onnxRuntimeModulePromise = (async () => {
const runtimeBaseUrl = ONNX_RUNTIME_BASE_URL.replace(/\/?$/, "/");
const ort = await import(versionedWorkerAssetUrl(`${runtimeBaseUrl}ort.wasm.min.mjs`));
ort.env.wasm.wasmPaths = new URL(runtimeBaseUrl, self.location.href).href;
ort.env.wasm.numThreads = 1;
return ort;
})();
}
return onnxRuntimeModulePromise;
}
async function ensurePlayerWasmModule() {
if (!playerWasmModulePromise) {
playerWasmModulePromise = (async () => {
const module = await import(versionedWorkerAssetUrl("./wasm/player-wasm/player_wasm.js"));
await module.default({
module_or_path: versionedWorkerAssetUrl("./wasm/player-wasm/player_wasm_bg.wasm"),
});
module.initPanicHook?.();
playerAppWasmModule = module;
return module;
})().catch((error) => {
playerWasmModulePromise = null;
playerAppWasmModule = null;
throw error;
});
}
return playerWasmModulePromise;
}
async function ensureRecordRenderWasmModule() {
return ensurePlayerWasmModule();
}
async function ensurePlayerAppWasmModule() {
return ensurePlayerWasmModule();
}
function requirePlayerAppWasmFunction(name) {
const fn = playerAppWasmModule?.[name];
if (typeof fn !== "function") {
throw new Error(`Bitneedle player app WASM is missing ${name}.`);
}
return fn;
}
function normalizeWorkerRecordProfileName(recordProfile) {
return requirePlayerAppWasmFunction("normalizeRecordProfileName")(String(recordProfile ?? ""));
}
async function ensureEncodecWasmModule() {
return ensurePlayerAppWasmModule();
}
function assertMossNanoDecodeEnabled() {
if (!MOSS_NANO_DECODE_ENABLED) {
throw new Error(
"MOSS Nano decode is disabled (PLAYER_MOSSNANO_DECODE_ENABLED is false in player-playback-config.js).",
);
}
}
async function ensureMossNanoWasmModule() {
assertMossNanoDecodeEnabled();
if (!mossNanoWasmModulePromise) {
mossNanoWasmModulePromise = (async () => {
const module = await import(versionedWorkerAssetUrl(`${MOSSNANO_WASM_BASE_URL}/pkg/mossnano_rs.js`));
await module.default({
module_or_path: versionedWorkerAssetUrl(`${MOSSNANO_WASM_BASE_URL}/pkg/mossnano_rs_bg.wasm`),
});
module.initPanicHook?.();
return module;
})().catch((error) => {
mossNanoWasmModulePromise = null;
throw error;
});
}
return mossNanoWasmModulePromise;
}
async function ensureMossNanoDecodeSession(runtime) {
assertMossNanoDecodeEnabled();
if (!mossNanoDecodeSessionPromise) {
mossNanoDecodeSessionPromise = (async () => {
const ort = await ensureOnnxRuntimeModule();
const model = await fetchMaybeSplitArrayBuffer(`${MOSS_NANO_MODEL_ROOT}/moss_audio_tokenizer_decode_full.onnx`);
const external = new Uint8Array(await fetchMaybeSplitArrayBuffer(
`${MOSS_NANO_MODEL_ROOT}/moss_audio_tokenizer_decode_shared.data`,
));
const executionProviders =
Array.isArray(runtime?.executionProviders) && runtime.executionProviders.length
? runtime.executionProviders
: ["wasm"];
const session = await ort.InferenceSession.create(model, {
executionProviders: [...executionProviders],
graphOptimizationLevel: "all",
externalData: [
{
path: "moss_audio_tokenizer_decode_shared.data",
data: external,
},
],
});
return { ort, session };
})().catch((error) => {
mossNanoDecodeSessionPromise = null;
throw error;
});
}
return mossNanoDecodeSessionPromise;
}
async function fetchArrayBuffer(url) {
const response = await fetch(url, { cache: "force-cache" });
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status}`);
}
return response.arrayBuffer();
}
function bundleAssetMetadata(meta, assetName) {
const assets = meta?.bitneedle_player_assets || meta?.bitneedlePlayerAssets || {};
return assets?.[assetName] || null;
}
async function fetchMaybeSplitArrayBuffer(assetPath, assetMetadata = null) {
if (assetMetadata?.split === true) {
return fetchSplitArrayBuffer(assetPath);
}
return fetchArrayBuffer(versionedWorkerAssetUrl(assetPath));
}
async function fetchSplitArrayBuffer(assetPath) {
const partsManifestUrl = versionedWorkerAssetUrl(`${assetPath}.parts.json`);
const manifestResponse = await fetch(partsManifestUrl, { cache: "force-cache" });
if (!manifestResponse.ok) {
throw new Error(`Failed to fetch ${partsManifestUrl}: ${manifestResponse.status}`);
}
const manifest = await manifestResponse.json();
if (!Array.isArray(manifest.parts) || !Number.isInteger(manifest.byteLength)) {
throw new Error(`Invalid asset parts manifest: ${partsManifestUrl}`);
}
const chunks = await Promise.all(
manifest.parts.map(async (part) => new Uint8Array(await fetchArrayBuffer(versionedWorkerManifestPartUrl(part, partsManifestUrl)))),
);
const bytes = concatenateUint8Chunks(chunks);
if (bytes.byteLength !== manifest.byteLength) {
throw new Error(`Asset parts for ${assetPath} produced ${bytes.byteLength} bytes, expected ${manifest.byteLength}`);
}
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
}
function concatenateUint8Chunks(chunks) {
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
const bytes = new Uint8Array(total);
let offset = 0;
chunks.forEach((chunk) => {
bytes.set(chunk, offset);
offset += chunk.byteLength;
});
return bytes;
}
async function loadModelForOrt(modelPath, assetMetadata = null) {
if (assetMetadata?.split !== true) {
return versionedWorkerAssetUrl(modelPath);
}
const partsManifestUrl = versionedWorkerAssetUrl(`${modelPath}.parts.json`);
const manifestResponse = await fetch(partsManifestUrl, { cache: "force-cache" });
if (!manifestResponse.ok) {
throw new Error(`Failed to fetch ${partsManifestUrl}: ${manifestResponse.status}`);
}
const manifest = await manifestResponse.json();
if (!Array.isArray(manifest.parts) || !Number.isInteger(manifest.byteLength)) {
throw new Error(`Invalid model parts manifest: ${partsManifestUrl}`);
}
const modelFilename = modelPath.split("/").pop();
if (manifest.parts.length === 1 && manifest.parts[0] === modelFilename) {
return versionedWorkerAssetUrl(modelPath);
}
const chunks = await Promise.all(
manifest.parts.map(async (part) => new Uint8Array(await fetchArrayBuffer(versionedWorkerManifestPartUrl(part, partsManifestUrl)))),
);
const model = concatenateUint8Chunks(chunks);
if (model.byteLength !== manifest.byteLength) {
throw new Error(`Model parts for ${modelPath} produced ${model.byteLength} bytes, expected ${manifest.byteLength}`);
}
return model;
}
async function loadQuantizedLmWeights(bundleRoot, meta) {
if (!meta?.lm_quant_weight_model) {
throw new Error("Client playback ECDC requires q8 LM weights.");
}
const weightsUrl = `${bundleRoot}/${meta.lm_quant_weight_model}`;
return new Uint8Array(await fetchMaybeSplitArrayBuffer(
weightsUrl,
bundleAssetMetadata(meta, meta.lm_quant_weight_model),
));
}
const bitneedleEncodecBundleNames = globalThis.BitneedleEncodecBundleNames;
if (!bitneedleEncodecBundleNames) {
throw new Error("Bitneedle EnCodec bundle helpers were not loaded before client-playback-worker.js.");
}
const {
encodeBundleNameForRecord: bitneedleEncodeBundleNameForRecord,
encodeBundleNameFromEcdcMetadata: bitneedleEncodeBundleNameFromEcdcMetadata,
getRecordMeta,
recordPayloadByteLength,
recordVisibleTurns,
} = bitneedleEncodecBundleNames;
function playerEncodeBundleNameForRecord(record) {
return bitneedleEncodeBundleNameForRecord(record);
}
function playerEncodeBundleNameFromEcdcMetadata(metadata, recordContext) {
return bitneedleEncodeBundleNameFromEcdcMetadata(metadata, recordContext);
}
function parseWorkerJson(value, fallback = null) {
if (!value) {
return fallback;
}
try {
return typeof value === "string" ? JSON.parse(value) : value;
} catch (_error) {
return fallback;
}
}
function normalizePayloadContainer(value, fallback = "") {
const normalized = String(value || fallback || "").trim().toUpperCase();
return normalized || String(fallback || "").trim().toUpperCase();
}
function payloadContainerIsEcdc(value) {
return normalizePayloadContainer(value) === "ECDC";
}
function payloadContainerIsMossNano(value) {
return normalizePayloadContainer(value) === "MOSSNANO";
}
function metadataFromRecordHeaderJson(header = {}) {
const metadata = JSON.parse(
requirePlayerAppWasmFunction("recordPlaybackMetadataFromHeaderJson")(
typeof header === "string" ? header : JSON.stringify(header || {}),
),
);
if (!metadata || typeof metadata !== "object") {
throw new Error("record header metadata parser returned an invalid value.");
}
return metadata;
}
function resolvePlaybackPayloadMetadata({
headerMetadata = {},
payloadMetadata = {},
lengthPrefixedEntries = false,
} = {}) {
const metadata = JSON.parse(
requirePlayerAppWasmFunction("resolvePlaybackPayloadMetadataJson")(
JSON.stringify(headerMetadata || {}),
JSON.stringify(payloadMetadata || {}),
Boolean(lengthPrefixedEntries),
),
);
if (!metadata || typeof metadata !== "object") {
throw new Error("playback payload metadata resolver returned an invalid value.");
}
return metadata;
}
function metadataFromRecordHeader(module, pngBytes) {
if (typeof module?.decodeRecordMetadataJson !== "function") {
throw new Error("Bitneedle record renderer WASM is missing decodeRecordMetadataJson.");
}
return metadataFromRecordHeaderJson(module.decodeRecordMetadataJson(pngBytes));
}
function programmeMapFromRecordHeader(module, pngBytes) {
if (typeof module?.decodeRecordProgrammeMapJson !== "function") {
return null;
}
try {
return parseWorkerJson(module.decodeRecordProgrammeMapJson(pngBytes), null);
} catch (_error) {
return null;
}
}
function readPayloadDecodeResult(result) {
const payloadBytes = result?.payloadBytes?.() || result?.payload_bytes?.() || result?.payloadBytes || result?.payload || null;
const chunkStreamBytes = result?.chunkStreamBytes?.() || result?.chunk_stream_bytes?.() || result?.chunkStreamBytes || result?.chunkStream || null;
const metadataJson = result?.metadataJson?.() || result?.metadata_json?.() || result?.metadataJson || "{}";
const silenceMapJson = result?.silenceMapJson?.() || result?.silence_map_json?.() || result?.silenceMapJson || "[]";
const payload = payloadBytes instanceof Uint8Array ? payloadBytes : new Uint8Array(payloadBytes || 0);
const chunkStream = chunkStreamBytes instanceof Uint8Array ? chunkStreamBytes : new Uint8Array(chunkStreamBytes || 0);
const metadata = parseWorkerJson(metadataJson, {}) || {};
const silenceMap = parseWorkerJson(silenceMapJson, []) || [];
result?.free?.();
return { payload, chunkStream, metadata, silenceMap };
}
function decodeRecordPayloadWithModule(module, {
bytes,
profile,
explicitProfile,
byteLength,
visibleTurns,
}) {
if (!explicitProfile && !byteLength && !visibleTurns && typeof module.decodeRecordPngToPayload === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayload(bytes));
}
if (visibleTurns && byteLength && typeof module.decodeRecordPngToPayloadForProfileWithTurnsAndLength === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayloadForProfileWithTurnsAndLength(bytes, profile, visibleTurns, byteLength));
}
if (visibleTurns && typeof module.decodeRecordPngToPayloadForProfileWithTurns === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayloadForProfileWithTurns(bytes, profile, visibleTurns));
}
if (byteLength && typeof module.decodeRecordPngToPayloadForProfileWithLength === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayloadForProfileWithLength(bytes, profile, byteLength));
}
if (typeof module.decodeRecordPngToPayloadForProfile === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayloadForProfile(bytes, profile));
}
if (byteLength && typeof module.decodeRecordPngToPayloadWithLength === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayloadWithLength(bytes, byteLength));
}
if (typeof module.decodeRecordPngToPayload === "function") {
return readPayloadDecodeResult(module.decodeRecordPngToPayload(bytes));
}
return null;
}
async function fetchEncodeBundle(bundleName) {
const bundleRoot = `${ENCODEC_BUNDLE_BASE_URL.replace(/\/+$/, "")}/${bundleName}`;
const response = await fetch(versionedWorkerAssetUrl(`${bundleRoot}/bundle.json`), { cache: "force-cache" });
if (!response.ok) {
throw new Error(`Failed to load EnCodec bundle ${bundleName}: ${response.status}`);
}
const bundleJson = await response.text();
return {
bundleName,
bundleRoot,
bundleJson,
meta: JSON.parse(bundleJson),
};
}
async function extractPayloadFromRecordPng({ id, pngBytes, record = {}, recordProfile }) {
workerDecodeDebug("[play:client-playback-worker.js] extractPayloadFromRecordPng", { id, pngBytes: pngBytes?.byteLength || 0, recordProfile });
const perfMark = workerPerfStart("extract record PNG", {
pngBytes: pngBytes?.byteLength || 0,
});
postDecodeProgress(id, {
msg: "Extracting spiral payload...",
progressPercent: 0,
});
try {
const module = await ensureRecordRenderWasmModule();
await ensurePlayerAppWasmModule();
const bytes = pngBytes instanceof Uint8Array ? pngBytes : new Uint8Array(pngBytes || 0);
let releaseId = "";
if (typeof module.decodeRecordMetadataJson === "function") {
try {
const recordMetadata = JSON.parse(module.decodeRecordMetadataJson(bytes));
releaseId = String(recordMetadata?.descriptor?.releaseId || "").trim();
} catch (_error) {
}
}
const explicitProfile =
record?.recordProfile ||
getRecordMeta(record).recordProfile ||
"";
let inferredProfile = "";
if (!explicitProfile && typeof module.inferRecordProfileFromPng === "function") {
inferredProfile = module.inferRecordProfileFromPng(bytes);
}
const profile = normalizeWorkerRecordProfileName(explicitProfile || inferredProfile || recordProfile);
const byteLength = recordPayloadByteLength(record);
const visibleTurns = recordVisibleTurns(record);
let decoded = decodeRecordPayloadWithModule(module, {
bytes,
profile,
explicitProfile,
byteLength,
visibleTurns,
});
if (!decoded?.payload?.byteLength) {
throw new Error("Bitneedle Player WASM does not expose PNG audio extraction.");
}
const payloadBytes = decoded.payload instanceof Uint8Array ? decoded.payload : new Uint8Array(decoded.payload);
const headerMetadata = metadataFromRecordHeader(module, bytes);
const programmeMap = programmeMapFromRecordHeader(module, bytes);
const lengthPrefixedEntries = false;
const playbackMetadata = resolvePlaybackPayloadMetadata({
headerMetadata,
payloadMetadata: decoded.metadata || {},
lengthPrefixedEntries,
});
const {
payloadContainer,
payloadCodec,
entryContainer,
trackListing,
dummySpiralRegions,
} = playbackMetadata;
if (!payloadContainer) {
throw new Error("record payload metadata must declare payloadContainer.");
}
let bundleName = "";
let recordHeaderProof = null;
if (payloadContainerIsEcdc(payloadContainer)) {
const ecdcForMetadata = lengthPrefixedEntries
? parseLengthPrefixedPayloadEntries(payloadBytes)[0]
: payloadBytes;
const metadata = (await ensureEncodecWasmModule()).ecdcMetadata(ecdcForMetadata);
bundleName = playerEncodeBundleNameFromEcdcMetadata(metadata, record);
recordHeaderProof = lengthPrefixedEntries
? null
: createWorkerEcdcCacheProofContext(payloadBytes);
workerDecodeDebug("[bitneedle-cache-dbg] worker recordHeaderProof", {
lengthPrefixedEntries,
hasProof: Boolean(recordHeaderProof),
proofFormat: recordHeaderProof?.format || null,
proofChunks: recordHeaderProof?.chunks?.length ?? null,
});
} else if (payloadContainerIsMossNano(payloadContainer)) {
bundleName = payloadCodec || MOSS_NANO_CODEC;
}
workerPerfEnd(id, perfMark, {
payloadBytes: payloadBytes.byteLength,
payloadContainer,
profile,
bundleName,
});
return {
payload: payloadBytes,
chunkStream: decoded.chunkStream instanceof Uint8Array ? decoded.chunkStream : new Uint8Array(decoded.chunkStream || 0),
ecdc: payloadContainerIsEcdc(payloadContainer) ? payloadBytes : null,
payloadContainer,
payloadCodec,
entryContainer,
payloadMetadata: decoded.metadata,
trackListing,
dummySpiralRegions,
silenceMap: Array.isArray(decoded.silenceMap) ? decoded.silenceMap : [],
programmeMap,
bundleName,
recordProfile: profile,
recordHeaderProof,
releaseId,
};
} catch (error) {
workerPerfEnd(id, perfMark, {
error: workerErrorMessage(error),
});
throw error;
}
}
const { createOnnxRuntimeSession } = globalThis.BitneedleOnnxRuntimeSession || {};
if (typeof createOnnxRuntimeSession !== "function") {
throw new Error("Bitneedle ONNX runtime session helpers were not loaded before client-playback-worker.js.");
}
const {
buildDecodeInputs,
disposeOrtTensorMap,
findDecodeOutput,
} = globalThis.BitneedleOnnxWorkerTensors || {};
if (
typeof buildDecodeInputs !== "function" ||
typeof disposeOrtTensorMap !== "function" ||
typeof findDecodeOutput !== "function"
) {
throw new Error("Bitneedle ONNX worker tensor helpers were not loaded before client-playback-worker.js.");
}
async function createDecodeSession(modelPath, runtime, assetMetadata = null) {
return createOnnxRuntimeSession({
modelPath,
runtime,
assetMetadata,
ensureOnnxRuntimeModule,
loadModelForOrt,
enableCpuMemArena: false,
enableMemPattern: false,
});
}
async function decodeLmChunk(encodecModule, lmWeights, bundleJson, chunk, meta) {
workerDecodeDebug("[play:client-playback-worker.js] decodeLmChunk", { offset: chunk?.offset, samples: chunk?.samples, frameLength: chunk?.frameLength, numCodebooks: meta?.num_codebooks });
const payload = chunk.payload instanceof Uint8Array ? chunk.payload : Uint8Array.from(chunk.payload || []);
const decoder = new encodecModule.QuantizedLmChunkDecoder(bundleJson, lmWeights, payload);
const encodedFrameLength = Math.max(0, Number(chunk.frameLength) || 0);
const frameLength = Math.max(1, encodedFrameLength || Number(meta.frame_length) || 1);
const codes = new Uint16Array(meta.num_codebooks * frameLength);
try {
for (let step = 0; step < Math.min(encodedFrameLength || frameLength, frameLength); step += 1) {
const symbols = decoder.pull();
for (let codebook = 0; codebook < meta.num_codebooks; codebook += 1) {
codes[codebook * frameLength + step] = symbols[codebook];
}
}
return {
offset: chunk.offset,
samples: chunk.samples,
frameLength,
scale: decoder.scale(),
codes,
};
} finally {
decoder.free?.();
}
}
function normalizeEncodedFrame(frame) {
return {
offset: frame.offset,
samples: frame.samples,
frameLength: frame.frameLength,
scale: Number(frame.scale ?? 1),
codes: frame.codes instanceof Uint16Array ? frame.codes : new Uint16Array(frame.codes || []),
silentPcm: frame.silentPcm === true,
decodeFailed: frame.decodeFailed === true,
};
}
function createSilentEncodedFrame(chunk, meta) {
const frameLength = Math.max(1, Number(chunk?.frameLength) || Number(meta?.frame_length) || 1);
const codebooks = Math.max(1, Number(meta?.num_codebooks) || 1);
return {
offset: chunk?.offset,
samples: chunk?.samples,
frameLength,
scale: 1,
codes: new Uint16Array(codebooks * frameLength),
silentPcm: true,
decodeFailed: true,
};
}
async function prepareEcdcChunkDecode({ ecdcBuffer, bundleJson, bundleRoot, bundleName = "", meta }) {
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode", { ecdcBytes: ecdcBuffer?.byteLength || 0, bundleName });
const encodecModule = await ensureEncodecWasmModule();
const ecdc = ecdcBuffer instanceof Uint8Array ? ecdcBuffer : new Uint8Array(ecdcBuffer);
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode wasm ready", { ecdcBytes: ecdc.byteLength, firstBytes: Array.from(ecdc.subarray(0, 16)) });
let metadata;
try {
metadata = encodecModule.ecdcMetadata(ecdc);
} catch (error) {
console.error("[play:client-playback-worker.js] prepareEcdcChunkDecode ecdcMetadata threw", { ecdcBytes: ecdc.byteLength, error: workerErrorMessage(error) });
throw error;
}
const bitstreamVersion = Number(metadata.acv ?? metadata.bitstream_version ?? 0);
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode metadata", { bitstreamVersion, metadata });
if (bitstreamVersion !== 2) {
throw new Error(`Client playback ECDC requires q8 LM acv=2, got acv=${bitstreamVersion}`);
}
let parsed;
try {
parsed = encodecModule.lmEcdcDecodeChunks(bundleJson, ecdc);
} catch (error) {
console.error("[play:client-playback-worker.js] prepareEcdcChunkDecode lmEcdcDecodeChunks threw", { ecdcBytes: ecdc.byteLength, error: workerErrorMessage(error) });
throw error;
}
const chunks = parsed.chunks || [];
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode chunks decoded", { chunkCount: chunks.length });
const expectedLmHash = metadata.lmh ?? metadata.lm_hash;
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode loading LM weights", { lmQuantWeightModel: meta?.lm_quant_weight_model || "", bundleRoot });
const lmWeights = await loadQuantizedLmWeights(bundleRoot, meta);
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode LM weights loaded", { lmWeightBytes: lmWeights.byteLength });
const actualLmHash = encodecModule.stableHashHex(lmWeights);
if (expectedLmHash && expectedLmHash !== actualLmHash) {
console.error("[bitneedle-player-worker] ECDC LM hash mismatch", {
expectedLmHash,
actualLmHash,
requiredBrowserLmHash: expectedLmHash,
bundleName,
});
throw new Error(`Client playback ECDC requires LM hash ${expectedLmHash}, but loaded ${actualLmHash}`);
}
const aggregateMetadata = parsed.metadata || metadata;
const audioLength = Number(aggregateMetadata.al ?? aggregateMetadata.audio_length) || 0;
workerDecodeDebug("[play:client-playback-worker.js] prepareEcdcChunkDecode parsed", { bundleName, chunkCount: chunks.length, audioLength, bitstreamVersion });
return {
encodecModule,
chunks,
lmWeights,
metadata: aggregateMetadata,
audioLength,
};
}
function frameDescriptorFromEcdcChunk(chunk, meta) {
return {
offset: chunk?.offset,
samples: chunk?.samples,
frameLength: Math.max(1, Number(chunk?.frameLength) || Number(meta?.frame_length) || 1),
};
}
function createS16OverlapAddWriter({ meta, frames, audioLength, layout }) {
const channels = Math.max(1, Number(meta.channels) || 1);
const safeAudioLength = Math.max(0, Math.floor(Number(audioLength) || 0));
const weight = triangleWeight(layout.samples);
let ringCapacity = nextRingPow2(Math.max(1, layout.samples * 2));
let ringMask = ringCapacity - 1;
let sums = new Float32Array(channels * ringCapacity);
let weights = new Float32Array(ringCapacity);
const channelData = Array.from({ length: channels }, () => new Int16Array(safeAudioLength));
let emittedSample = 0;
let maxWrittenSample = 0;
let emittedSegmentIndex = 0;
const cachedRanges = [];
let cachedRangePointer = 0;
function nextRingPow2(value) {
let capacity = 1;
while (capacity < value) {
capacity *= 2;
}
return capacity;
}
function growRing(requiredSpan) {
const newCapacity = nextRingPow2(requiredSpan);
const newMask = newCapacity - 1;
const newSums = new Float32Array(channels * newCapacity);
const newWeights = new Float32Array(newCapacity);
for (let globalSample = emittedSample; globalSample < maxWrittenSample; globalSample += 1) {
const oldIndex = globalSample & ringMask;
const newIndex = globalSample & newMask;
newWeights[newIndex] = weights[oldIndex];
for (let channel = 0; channel < channels; channel += 1) {
newSums[(channel * newCapacity) + newIndex] = sums[(channel * ringCapacity) + oldIndex];
}
}
sums = newSums;
weights = newWeights;
ringCapacity = newCapacity;
ringMask = newMask;
}
function ensureRingSpan(endSample) {
if (endSample - emittedSample > ringCapacity) {
growRing(endSample - emittedSample);
}
}
function addDecodedBatch(start, end, decodedFrameChunk) {
const batchSize = end - start;
if (!(batchSize > 0)) {
return;
}
const decodedSamples = Math.floor(decodedFrameChunk.length / Math.max(1, batchSize * channels));
if (!(decodedSamples > 0)) {
throw new Error("Decoded PCM batch was empty.");
}
for (let frameIndex = start; frameIndex < end; frameIndex += 1) {
const frame = frames[frameIndex];
const localIndex = frameIndex - start;
const frameStart = frameStartSample(frame, frameIndex, layout);
const frameSamples = Math.max(
0,
Math.min(
Number(frame?.samples) || layout.samples,
layout.samples,
safeAudioLength - frameStart,
),
);
if (decodedSamples < frameSamples) {
throw new Error(`Decoded frame ${frameIndex + 1} has ${decodedSamples} samples, expected at least ${frameSamples}.`);
}
const sourceBase = localIndex * channels * decodedSamples;
ensureRingSpan(Math.min(safeAudioLength, frameStart + frameSamples));
for (let sample = 0; sample < frameSamples; sample += 1) {
const globalSample = frameStart + sample;
if (globalSample < emittedSample || globalSample >= safeAudioLength) {
continue;
}
const w = weight[sample] || 0;
const ringIndex = globalSample & ringMask;
weights[ringIndex] += w;
for (let channel = 0; channel < channels; channel += 1) {
sums[(channel * ringCapacity) + ringIndex] +=
decodedFrameChunk[sourceBase + (channel * decodedSamples) + sample] * w;
}
}
maxWrittenSample = Math.max(
maxWrittenSample,
Math.min(safeAudioLength, frameStart + frameSamples),
);
}
}
function addCachedRange(startFrame, endFrame, interleavedInt16) {
const start = Math.max(0, Math.min(safeAudioLength, Math.floor(Number(startFrame) || 0)));
const end = Math.max(start, Math.min(safeAudioLength, Math.floor(Number(endFrame) || 0)));
if (!(end > start) || !(interleavedInt16?.length >= (end - start) * channels)) {
return false;
}
for (let globalSample = start; globalSample < end; globalSample += 1) {
const localBase = (globalSample - start) * channels;
for (let channel = 0; channel < channels; channel += 1) {
channelData[channel][globalSample] = interleavedInt16[localBase + channel];
}
}
cachedRanges.push([start, end]);
maxWrittenSample = Math.max(maxWrittenSample, end);
return true;
}
function addSilentFrame(frameIndex) {
const frame = frames[frameIndex];
const frameStart = frameStartSample(frame, frameIndex, layout);
const frameSamples = Math.max(
0,
Math.min(
Number(frame?.samples) || layout.samples,
layout.samples,
safeAudioLength - frameStart,
),
);
ensureRingSpan(Math.min(safeAudioLength, frameStart + frameSamples));
for (let sample = 0; sample < frameSamples; sample += 1) {
const globalSample = frameStart + sample;
if (globalSample >= emittedSample && globalSample < safeAudioLength) {
weights[globalSample & ringMask] += weight[sample] || 0;
}
}
maxWrittenSample = Math.max(
maxWrittenSample,
Math.min(safeAudioLength, frameStart + frameSamples),
);
}
function addSilentBatch(start, end) {
for (let frameIndex = start; frameIndex < end; frameIndex += 1) {
addSilentFrame(frameIndex);
}
}
function emitUntil(sampleEnd) {
const safeEnd = Math.max(emittedSample, Math.min(safeAudioLength, Math.floor(Number(sampleEnd) || 0)));
for (let globalSample = emittedSample; globalSample < safeEnd; globalSample += 1) {
const ringIndex = globalSample & ringMask;
while (cachedRangePointer < cachedRanges.length && globalSample >= cachedRanges[cachedRangePointer][1]) {
cachedRangePointer += 1;
}
const isCachedSample = cachedRangePointer < cachedRanges.length
&& globalSample >= cachedRanges[cachedRangePointer][0];
const denom = weights[ringIndex];
for (let channel = 0; channel < channels; channel += 1) {
const sourceIndex = (channel * ringCapacity) + ringIndex;
if (!isCachedSample) {
channelData[channel][globalSample] = floatToS16Sample(denom > 0 ? sums[sourceIndex] / denom : 0);
}
sums[sourceIndex] = 0;
}
weights[ringIndex] = 0;
}
emittedSample = safeEnd;
maxWrittenSample = Math.max(maxWrittenSample, emittedSample);
}
function safeEmitEnd(nextFrameIndex) {
if (nextFrameIndex >= frames.length) {
return safeAudioLength;
}
return frameStartSample(frames[nextFrameIndex], nextFrameIndex, layout);
}
function collectEmittedSegments() {
const segments = [];
while (emittedSegmentIndex < frames.length) {
const segmentStart = Math.max(
0,
Math.min(
safeAudioLength,
frameStartSample(frames[emittedSegmentIndex], emittedSegmentIndex, layout),
),
);
const segmentEnd = Math.max(
segmentStart,
Math.min(safeAudioLength, safeEmitEnd(emittedSegmentIndex + 1)),
);
if (segmentEnd > emittedSample) {
break;
}
if (segmentEnd > segmentStart) {
segments.push({
chunkIndex: emittedSegmentIndex,
startFrame: segmentStart,
endFrame: segmentEnd,
channelData: channelData.map((channel) => channel.slice(segmentStart, segmentEnd)),
});
}
emittedSegmentIndex += 1;
}
return segments;
}
return {
addCachedRange,
addDecodedBatch,
addSilentBatch,
addSilentFrame,
emitAfterBatch(nextFrameIndex) {
emitUntil(safeEmitEnd(nextFrameIndex));
return collectEmittedSegments();
},
flush() {
emitUntil(safeAudioLength);
return collectEmittedSegments();
},
result() {
this.flush();
return { channelData };
},
};
}
function normalizedProgressSegments(segments, { sampleRate, channels, audioLength }, transfer) {
return segments.map((segment) => {
const channelBuffers = segment.channelData.map((channel) => {
const buffer = channel.buffer.slice(channel.byteOffset, channel.byteOffset + channel.byteLength);
transfer.push(buffer);
return buffer;
});
return {
chunkIndex: segment.chunkIndex,
startFrame: segment.startFrame,
endFrame: segment.endFrame,
sampleRate,
channels,
audioLength,
channelBuffers,
};
});
}
function postDecodedPcmSegments(id, segments, { sampleRate, channels, audioLength }) {
if (!Array.isArray(segments) || !segments.length) {
return;
}
const transfer = [];
const decodedPcmSegments = normalizedProgressSegments(segments, { sampleRate, channels, audioLength }, transfer);
postDecodeProgress(id, { decodedPcmSegments }, transfer);
}
function postRawDecodedPcmSegments(id, segments, { sampleRate, channels, audioLength }) {
if (!Array.isArray(segments) || !segments.length) {
return;
}
const transfer = [];
const rawDecodedPcmSegments = normalizedProgressSegments(segments, { sampleRate, channels, audioLength }, transfer);
postDecodeProgress(id, { rawDecodedPcmSegments }, transfer);
}
function seamRepairProfileFromMeta(meta) {
const repair = String(meta?.seam_repair || meta?.seamRepair || "").trim();
if (!repair) {
return null;
}
if (repair !== "cubic-hermite-v1") {
throw new Error(`Unsupported ECDC seam repair profile: ${repair}`);
}
const ownedSamples = Math.max(0, Math.floor(
Number(meta?.owned_samples ?? meta?.ownedSamples) || 0,
));
const repairSamples = Math.max(0, Math.floor(
Number(meta?.seam_repair_samples ?? meta?.seamRepairSamples) || 0,
));
if (!(ownedSamples > 0) || !(repairSamples > 0) || (repairSamples % 2) !== 0) {
throw new Error(`Invalid cubic-hermite-v1 profile: ownedSamples=${ownedSamples} repairSamples=${repairSamples}`);
}
return {
repair,
ownedSamples,
repairSamples,
};
}
function clampS16(value) {
const rounded = Math.round(Number(value) || 0);
if (rounded < -32768) return -32768;
if (rounded > 32767) return 32767;
return rounded;
}
function applyHermiteSeamRepairToS16(channelData, audioLength, profile) {
if (!Array.isArray(channelData) || !channelData.length) {
return;
}
const ownedSamples = Math.max(1, profile.ownedSamples);
const eachSide = Math.max(1, Math.floor(profile.repairSamples / 2));
for (let seamFrame = ownedSamples; seamFrame < audioLength; seamFrame += ownedSamples) {
const start = Math.max(1, seamFrame - eachSide);
const end = Math.min(audioLength - 1, seamFrame + eachSide);
const span = end - start;
if (!(span > 0)) {
continue;
}
for (const channel of channelData) {
const y0 = channel[start - 1];
const y1 = channel[end];
const m0 = channel[start] - channel[start - 1];
const m1 = channel[end] - channel[end - 1];
for (let index = 0; index < span; index += 1) {
const t = (index + 1) / (span + 1);
const h00 = (2 * t ** 3) - (3 * t ** 2) + 1;
const h10 = (t ** 3) - (2 * t ** 2) + t;
const h01 = (-2 * t ** 3) + (3 * t ** 2);
const h11 = (t ** 3) - (t ** 2);
channel[start + index] = clampS16(
(h00 * y0) + (h10 * span * m0) + (h01 * y1) + (h11 * span * m1),
);
}
}
}
}
function deinterleaveS16Segment(interleavedInt16, channels, frameCount) {
const channelData = Array.from({ length: channels }, () => new Int16Array(frameCount));
for (let frame = 0; frame < frameCount; frame += 1) {
for (let channel = 0; channel < channels; channel += 1) {
channelData[channel][frame] = interleavedInt16[(frame * channels) + channel] || 0;
}
}
return channelData;
}
function cropDecodedOwnedSegmentToS16(encodecModule, bundleJson, decodedFloat32, meta, chunkIndex, audioLength) {
const cropped = encodecModule.ecdcCropDecodedOwnedAudio(bundleJson, decodedFloat32);
const planar = cropped instanceof Float32Array ? cropped : new Float32Array(cropped);
const channels = Math.max(1, Number(meta?.channels) || 2);
const ownedSamples = Math.max(1, Number(meta?.owned_samples ?? meta?.ownedSamples) || Math.floor(planar.length / channels));
const startFrame = chunkIndex * ownedSamples;
const endFrame = Math.max(startFrame, Math.min(audioLength, startFrame + ownedSamples));
const frameCount = endFrame - startFrame;
const channelData = Array.from({ length: channels }, () => new Int16Array(frameCount));
for (let channel = 0; channel < channels; channel += 1) {
const sourceOffset = channel * ownedSamples;
const target = channelData[channel];
for (let frame = 0; frame < frameCount; frame += 1) {
target[frame] = floatToS16Sample(planar[sourceOffset + frame] || 0);
}
}
return {
chunkIndex,
startFrame,
endFrame,
channelData,
};
}
function applyHermiteBetweenChunkSegments(leftSegment, rightSegment, repairSamples) {
const eachSide = Math.max(1, Math.floor(repairSamples / 2));
const channels = Math.min(leftSegment.channelData.length, rightSegment.channelData.length);
for (let channel = 0; channel < channels; channel += 1) {
const left = leftSegment.channelData[channel];
const right = rightSegment.channelData[channel];
const leftFrames = left.length;
const rightFrames = right.length;
if (leftFrames < 2 || rightFrames < 2) {
continue;
}
const leftReplace = Math.min(eachSide, leftFrames - 1);
const rightReplace = Math.min(eachSide, rightFrames - 1);
const span = leftReplace + rightReplace;
if (!(span > 0)) {
continue;
}
const leftStart = leftFrames - leftReplace;
const y0 = left[leftStart - 1];
const y1 = right[rightReplace];
const m0 = left[leftStart] - left[leftStart - 1];
const m1 = right[rightReplace] - right[rightReplace - 1];
for (let index = 0; index < span; index += 1) {
const t = (index + 1) / (span + 1);
const h00 = (2 * t ** 3) - (3 * t ** 2) + 1;
const h10 = (t ** 3) - (2 * t ** 2) + t;
const h01 = (-2 * t ** 3) + (3 * t ** 2);
const h11 = (t ** 3) - (t ** 2);
const value = clampS16(
(h00 * y0) + (h10 * span * m0) + (h01 * y1) + (h11 * span * m1),
);
if (index < leftReplace) {
left[leftStart + index] = value;
} else {
right[index - leftReplace] = value;
}
}
}
}
function createHermiteChunkAssembler({ channels, audioLength, ownedSamples, repairSamples }) {
const finalChannelData = Array.from({ length: channels }, () => new Int16Array(audioLength));
let pendingSegment = null;
function copyToOutput(segment) {
const start = Math.max(0, Math.floor(Number(segment.startFrame) || 0));
const end = Math.max(start, Math.min(audioLength, Math.floor(Number(segment.endFrame) || start)));
const frameCount = end - start;
for (let channel = 0; channel < channels; channel += 1) {
const source = segment.channelData[Math.min(channel, segment.channelData.length - 1)] || segment.channelData[0];
if (!source) continue;
finalChannelData[channel].set(source.subarray(0, frameCount), start);
}
}
return {
addRawSegment(segment) {
const emitted = [];
if (pendingSegment) {
applyHermiteBetweenChunkSegments(pendingSegment, segment, repairSamples);
copyToOutput(pendingSegment);
emitted.push(pendingSegment);
}
pendingSegment = segment;
return emitted;
},
flush() {
if (!pendingSegment) {
return [];
}
copyToOutput(pendingSegment);
const final = pendingSegment;
pendingSegment = null;
return [final];
},
result() {
const flushed = this.flush();
return { flushed, channelData: finalChannelData };
},
};
}
function asInt32Array(values) {
return values instanceof Int32Array ? values : Int32Array.from(values || []);
}
function firstMossFloatOutput(outputs) {
for (const value of Object.values(outputs || {})) {
if (value?.type === "float32") {
return value;
}
}
throw new Error(`MOSS Nano decoder returned no float32 output: ${Object.keys(outputs || {}).join(", ")}`);
}
function mossOutputTensor(outputs, name) {
const tensor = outputs?.[name];
if (!tensor) {
throw new Error(`MOSS Nano decoder missing output tensor ${name}.`);
}
return tensor;
}
function trimMossPlanar(data, channels, frames, originalSamples) {
const outFrames = Math.min(frames, Math.max(0, Math.floor(Number(originalSamples) || 0)));
const out = new Float32Array(channels * outFrames);
for (let channel = 0; channel < channels; channel += 1) {
const srcOffset = channel * frames;
const dstOffset = channel * outFrames;
out.set(data.subarray(srcOffset, srcOffset + outFrames), dstOffset);
}
return out;
}
function mossPlanarAudio(tensor, metadata) {
const dims = Array.from(tensor?.dims || []).map(Number);
const channels = Math.max(1, Number(metadata?.channels) || 2);
const originalSamples = Math.max(0, Math.floor(Number(metadata?.originalSamples) || 0));
if (dims.length === 3 && dims[0] === 1 && dims[1] === channels) {
return {
channels,
frames: Math.min(dims[2], originalSamples),
planar: trimMossPlanar(tensor.data, channels, dims[2], originalSamples),
};
}
if (dims.length === 2 && dims[0] === channels) {
return {
channels,
frames: Math.min(dims[1], originalSamples),
planar: trimMossPlanar(tensor.data, channels, dims[1], originalSamples),
};
}
throw new Error(`unsupported MOSS Nano audio output shape ${JSON.stringify(dims)}`);
}
function copyMossPlanarChunkToS16(channelData, planar, channels, startFrame, frames, audioLength) {
const safeFrames = Math.max(0, Math.min(
Math.floor(Number(frames) || 0),
Math.max(0, Math.floor(Number(audioLength) || 0) - Math.max(0, startFrame)),
));
if (!(safeFrames > 0)) {
return null;
}
for (let channel = 0; channel < channels; channel += 1) {
const sourceOffset = channel * frames;
const target = channelData[channel];
for (let sample = 0; sample < safeFrames; sample += 1) {
target[startFrame + sample] = floatToS16Sample(planar[sourceOffset + sample] || 0);
}
}
return {
chunkIndex: 0,
startFrame,
endFrame: startFrame + safeFrames,
channelData: channelData.map((channel) => channel.slice(startFrame, startFrame + safeFrames)),
};
}
async function decodeMossNanoPlayback({
id,
payloadBuffer,
recordProfile,
runtime,
cacheDecodedSegments = false,
}) {
assertMossNanoDecodeEnabled();
const perfMark = workerPerfStart("decode MOSS Nano playback", {
payloadBytes: payloadBuffer?.byteLength || 0,
});
let stream = null;
try {
const payloadBytes = payloadBuffer instanceof Uint8Array ? payloadBuffer : new Uint8Array(payloadBuffer || 0);
const mossNanoModule = await ensureMossNanoWasmModule();
const metadata = JSON.parse(mossNanoModule.mossnanoMetadataJson(payloadBytes));
const sampleRate = Math.max(1, Number(metadata.sampleRate) || 48000);
const channels = Math.max(1, Number(metadata.channels) || 2);
const audioLength = Math.max(1, Math.floor(Number(metadata.originalSamples) || 0));
const profile = normalizeWorkerRecordProfileName(recordProfile);
const chunkSeconds = MOSS_NANO_RECORD_PROFILE_CHUNK_SECONDS[profile];
if (!(chunkSeconds > 0)) {
throw new Error(`MOSS Nano decode does not support record profile ${profile}.`);
}
const chunkFrames = Math.max(1, Number(mossNanoModule.mossnanoChunkFramesForSeconds(sampleRate, chunkSeconds)) || 1);
const actualChunkSeconds = Number(mossNanoModule.mossnanoChunkSecondsForFrames(sampleRate, chunkFrames)) || chunkSeconds;
stream = new mossNanoModule.MossNanoDecodeStream(payloadBytes, chunkFrames);
const { ort, session } = await ensureMossNanoDecodeSession(runtime);
const channelData = Array.from({ length: channels }, () => new Int16Array(audioLength));
const chunkCount = Math.max(1, Number(stream.chunkCount()) || 1);
let writeFrame = 0;
let chunkIndex = 0;
while (stream.hasNext()) {
const tokenStart = Number(stream.nextStartFrame()) || 0;
const tokenFrames = Math.max(1, Number(stream.nextTokenFrames()) || 1);
postDecodeProgress(id, {
msg: `Reading MOSS Nano audio ${chunkIndex + 1}/${chunkCount}`,
progressPercent: Math.round((chunkIndex / chunkCount) * 100),
processedSeconds: formatDecimal(Math.min(audioLength / sampleRate, writeFrame / sampleRate), 2),
duration: formatDecimal(audioLength / sampleRate, 2),
});
const tensorCodes = stream.nextCodesTqI32();
let feeds = null;
let outputs = null;
try {
const audioCodeInput = session.inputNames.includes("audio_codes") ? "audio_codes" : session.inputNames[0];
const audioLengthInput = session.inputNames.includes("audio_code_lengths")
? "audio_code_lengths"
: session.inputNames[1];
feeds = {
[audioCodeInput]: new ort.Tensor("int32", asInt32Array(tensorCodes), [1, tokenFrames, Number(metadata.quantizers) || 16]),
[audioLengthInput]: new ort.Tensor("int32", Int32Array.of(tokenFrames), [1]),
};
outputs = await session.run(feeds);
const audioTensor = firstMossFloatOutput(outputs);
const audioLengths = outputs.audio_lengths ? mossOutputTensor(outputs, "audio_lengths") : null;
const requestedFrames = audioLengths ? Number(audioLengths.data[0]) : tokenFrames * 3840;
const chunk = mossPlanarAudio(audioTensor, {
...metadata,
channels,
originalSamples: Math.min(requestedFrames, audioLength - writeFrame),
});
const segment = copyMossPlanarChunkToS16(
channelData,
chunk.planar,
channels,
writeFrame,
chunk.frames,
audioLength,
);
stream.pushDecodedPlanar(chunk.planar, channels, chunk.frames);
if (segment) {
segment.chunkIndex = chunkIndex;
if (cacheDecodedSegments) {
postDecodedPcmSegments(id, [segment], {
sampleRate,
channels,
audioLength,
});
}
writeFrame = segment.endFrame;
}
} catch (error) {
reportSkippedDecodePacket(
id,
`Skipped failed MOSS Nano packet ${chunkIndex + 1}/${chunkCount}; inserted silence`,
error,
);
writeFrame = Math.min(audioLength, writeFrame + Math.max(1, Math.round(tokenFrames * 3840)));
} finally {
disposeOrtTensorMap(feeds);
disposeOrtTensorMap(outputs);
}
chunkIndex += 1;
workerPerfEnd(id, null, { tokenStart, tokenFrames, actualChunkSeconds });
}
postDecodeProgress(id, {
msg: "Preparing playback",
progressPercent: 100,
});
workerPerfEnd(id, perfMark, {
chunks: chunkCount,
duration: audioLength / sampleRate,
payloadCodec: MOSS_NANO_CODEC,
});
return {
s16ChannelData: channelData,
channels,
sampleRate,
audioLength,
bundleName: MOSS_NANO_CODEC,
audioFormat: "record_png_mossnano",
payloadContainer: "MOSSNANO",
payloadCodec: MOSS_NANO_CODEC,
};
} catch (error) {
workerPerfEnd(id, perfMark, {
error: workerErrorMessage(error),
});
throw error;
} finally {
stream?.free?.();
}
}
function spliceSilenceIntoChannelData(channelData, audioLength, samplesPerChunk, silenceMap) {
const spans = (Array.isArray(silenceMap) ? silenceMap : [])
.map((span) => ({
afterEntryIndex: Math.max(0, Math.floor(Number(span?.afterEntryIndex) || 0)),
sampleCount: Math.max(0, Math.floor(Number(span?.sampleCount) || 0)),
}))
.filter((span) => span.sampleCount > 0);
if (!spans.length) {
return { channelData, audioLength };
}
const totalSilenceSamples = spans.reduce((sum, span) => sum + span.sampleCount, 0);
const splicedAudioLength = audioLength + totalSilenceSamples;
const splicedChannelData = channelData.map((channel) => {
const spliced = new Int16Array(splicedAudioLength); let sourceOffset = 0;
let destOffset = 0;
for (const span of spans) {
const sampleOffset = Math.min(audioLength, span.afterEntryIndex * samplesPerChunk);
const copyLength = Math.max(0, sampleOffset - sourceOffset);
if (copyLength > 0) {
spliced.set(channel.subarray(sourceOffset, sourceOffset + copyLength), destOffset);
sourceOffset += copyLength;
destOffset += copyLength;
}
destOffset += span.sampleCount;
}
if (sourceOffset < channel.length) {
spliced.set(channel.subarray(sourceOffset), destOffset);
}
return spliced;
});
return { channelData: splicedChannelData, audioLength: splicedAudioLength };
}
async function decodePlayback({ id, frames, ecdcBuffer, bundleJson, bundleRoot, bundleName = "", meta, runtime, audioLength, record = null, cacheDecodedSegments = false, cachedPcmSegments = [], silenceMap = [] }) {
workerDecodeDebug("[play:client-playback-worker.js] decodePlayback", { id, ecdcBytes: ecdcBuffer?.byteLength || 0, bundleName, cacheDecodedSegments, silenceSpans: Array.isArray(silenceMap) ? silenceMap.length : 0 });
if (!ecdcBuffer) {
throw new Error("Client playback worker requires the ECDC round-trip payload.");
}
const perfMark = workerPerfStart("decode playback", {
ecdcBytes: ecdcBuffer?.byteLength || 0,
bundleName,
});
const ecdcBytes = ecdcBuffer instanceof Uint8Array ? ecdcBuffer : new Uint8Array(ecdcBuffer);
let selectedBundleName = bundleName;
let selectedBundleRoot = bundleRoot;
let selectedBundleJson = bundleJson;
let selectedMeta = meta;
let session = null;
try {
if (!selectedBundleJson || !selectedBundleRoot || !selectedMeta) {
const bundleMark = workerPerfStart("select encodec bundle");
const metadata = (await ensureEncodecWasmModule()).ecdcMetadata(ecdcBytes);
selectedBundleName = selectedBundleName || playerEncodeBundleNameFromEcdcMetadata(metadata, record);
const bundle = await fetchEncodeBundle(selectedBundleName);
selectedBundleRoot = bundle.bundleRoot;
selectedBundleJson = bundle.bundleJson;
selectedMeta = bundle.meta;
workerPerfEnd(id, bundleMark, { bundleName: selectedBundleName });
}
const entropyMark = workerPerfStart("prepare interleaved ECDC decode", {
bundleName: selectedBundleName,
});
const ecdcDecode = await prepareEcdcChunkDecode({
ecdcBuffer: ecdcBytes,
bundleJson: selectedBundleJson,
bundleRoot: selectedBundleRoot,
bundleName: selectedBundleName,
meta: selectedMeta,
});
workerPerfEnd(id, entropyMark, {
chunks: ecdcDecode.chunks?.length || 0,
});
const chunks = ecdcDecode.chunks || [];
const encodedFrames = chunks.map((chunk) => frameDescriptorFromEcdcChunk(chunk, selectedMeta));
if (!encodedFrames.length) {
throw new Error("Client playback ECDC did not contain EnCodec frames.");
}
const safeAudioLength = Math.floor(Number(ecdcDecode.audioLength) || 0);
if (!(safeAudioLength > 0)) {
throw new Error("Client playback ECDC is missing PCM audio length.");
}
const sampleRate = Number(selectedMeta.sample_rate) || 48000;
const seamRepairProfile = seamRepairProfileFromMeta(selectedMeta);
const durationSeconds = safeAudioLength / sampleRate;
const decodeModelPath = `${selectedBundleRoot}/${selectedMeta.decode_model}`;
const ort = await ensureOnnxRuntimeModule();
const sessionMark = workerPerfStart("create ONNX decode session", {
bundleName: selectedBundleName,
});
({ session } = await createDecodeSession(
decodeModelPath,
runtime,
bundleAssetMetadata(selectedMeta, selectedMeta.decode_model),
));
workerPerfEnd(id, sessionMark);
const layout = ecdcDecode.encodecModule.ecdcChunkLayoutFromMetadata(
selectedBundleJson,
ecdcBytes,
encodedFrames.length,
);
if (seamRepairProfile) {
const channels = Number(selectedMeta.channels) || 2;
const ownedSamples = Math.max(1, seamRepairProfile.ownedSamples);
const assembler = createHermiteChunkAssembler({
channels,
audioLength: safeAudioLength,
ownedSamples,
repairSamples: seamRepairProfile.repairSamples,
});
const cachedSegmentsByIndex = new Map();
for (const segment of Array.isArray(cachedPcmSegments) ? cachedPcmSegments : []) {
const chunkIndex = Math.max(0, Math.floor(Number(segment?.chunkIndex) || 0));
if (segment?.pcmBuffer?.byteLength) {
cachedSegmentsByIndex.set(chunkIndex, segment);
}
}
const pcmMark = workerPerfStart("run guarded ECDC PCM decode", {
frames: encodedFrames.length,
bundleName: selectedBundleName,
cachedChunks: cachedSegmentsByIndex.size,
});
for (let index = 0; index < chunks.length; index += 1) {
const end = index + 1;
const processedSeconds = Math.min(durationSeconds, durationSeconds * (end / Math.max(1, chunks.length)));
postDecodeProgress(id, {
msg: `Reading groove audio ${end}/${chunks.length}`,
progressPercent: Math.round((end / chunks.length) * 100),
processedSeconds: formatDecimal(processedSeconds, 2),
duration: formatDecimal(durationSeconds, 2),
});
const cachedSegment = cachedSegmentsByIndex.get(index);
let rawSegment = null;
if (cachedSegment) {
const startFrame = Math.max(0, Math.floor(Number(cachedSegment.startFrame) || (index * ownedSamples)));
const endFrame = Math.max(startFrame, Math.min(
safeAudioLength,
Math.floor(Number(cachedSegment.endFrame) || (startFrame + ownedSamples)),
));
const frameCount = endFrame - startFrame;
rawSegment = {
chunkIndex: index,
startFrame,
endFrame,
channelData: deinterleaveS16Segment(new Int16Array(cachedSegment.pcmBuffer), channels, frameCount),
};
} else {
let frame = null;
try {
frame = await decodeLmChunk(
ecdcDecode.encodecModule,
ecdcDecode.lmWeights,
selectedBundleJson,
chunks[index],
selectedMeta,
);
} catch (error) {
reportSkippedDecodePacket(
id,
`Skipped damaged ECDC packet ${end}/${chunks.length}; inserted ${expectedFramePcmSamples(chunks[index], selectedMeta)} silent samples`,
error,
);
frame = createSilentEncodedFrame(chunks[index], selectedMeta);
}
encodedFrames[index] = normalizeEncodedFrame(frame);
if (chunks[index] && typeof chunks[index] === "object") {
chunks[index].payload = null;
}
let feeds = null;
let outputs = null;
try {
if (encodedFrames[index]?.silentPcm) {
const startFrame = index * ownedSamples;
const endFrame = Math.max(startFrame, Math.min(safeAudioLength, startFrame + ownedSamples));
rawSegment = {
chunkIndex: index,
startFrame,
endFrame,
channelData: Array.from({ length: channels }, () => new Int16Array(endFrame - startFrame)),
};
} else {
const decoderInputs = buildDecodeInputs(encodedFrames, selectedMeta, index, end);
feeds = {
[session.inputNames[0]]: new ort.Tensor("int64", decoderInputs.codes, [
decoderInputs.batchSize,
selectedMeta.num_codebooks,
decoderInputs.frameLength,
]),
[session.inputNames[1]]: new ort.Tensor("float32", decoderInputs.scales, [decoderInputs.batchSize, 1]),
};
outputs = await session.run(feeds);
const decodedTensor = findDecodeOutput(outputs);
const decodedFloat = decodedTensor.data instanceof Float32Array
? decodedTensor.data
: new Float32Array(decodedTensor.data);
const decodedSamples = Math.floor(decodedFloat.length / Math.max(1, channels));
if (decodedSamples < Math.max(1, Number(selectedMeta.segment_samples) || 0)) {
throw new Error(`Guarded decoder returned ${decodedSamples} samples, expected at least ${selectedMeta.segment_samples}`);
}
rawSegment = cropDecodedOwnedSegmentToS16(
ecdcDecode.encodecModule,
selectedBundleJson,
decodedFloat,
selectedMeta,
index,
safeAudioLength,
);
}
if (cacheDecodedSegments && rawSegment) {
postRawDecodedPcmSegments(id, [rawSegment], {
sampleRate,
channels,
audioLength: safeAudioLength,
});
}
} catch (error) {
reportSkippedDecodePacket(
id,
`Skipped failed EnCodec PCM packet ${end}/${encodedFrames.length}; inserted silence`,
error,
);
const startFrame = index * ownedSamples;
const endFrame = Math.max(startFrame, Math.min(safeAudioLength, startFrame + ownedSamples));
rawSegment = {
chunkIndex: index,
startFrame,
endFrame,
channelData: Array.from({ length: channels }, () => new Int16Array(endFrame - startFrame)),
};
} finally {
disposeOrtTensorMap(feeds);
disposeOrtTensorMap(outputs);
if (encodedFrames[index]) {
encodedFrames[index].codes = null;
}
}
}
const emittedSegments = assembler.addRawSegment(rawSegment);
if (emittedSegments.length) {
postDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels,
audioLength: safeAudioLength,
});
}
}
workerPerfEnd(id, pcmMark, {
decodedSamples: safeAudioLength,
});
postDecodeProgress(id, {
msg: "Preparing playback",
progressPercent: 100,
});
const s16Result = assembler.result();
if (s16Result.flushed.length) {
postDecodedPcmSegments(id, s16Result.flushed, {
sampleRate,
channels,
audioLength: safeAudioLength,
});
}
workerPerfEnd(id, perfMark, {
bundleName: selectedBundleName,
duration: durationSeconds,
});
return {
s16ChannelData: s16Result.channelData,
channels,
sampleRate,
audioLength: safeAudioLength,
bundleName: selectedBundleName,
};
}
const s16Writer = createS16OverlapAddWriter({
meta: selectedMeta,
frames: encodedFrames,
audioLength: safeAudioLength,
layout,
});
const cachedSegmentsByIndex = new Map();
for (const segment of Array.isArray(cachedPcmSegments) ? cachedPcmSegments : []) {
const chunkIndex = Math.max(0, Math.floor(Number(segment?.chunkIndex) || 0));
if (segment?.pcmBuffer?.byteLength) {
cachedSegmentsByIndex.set(chunkIndex, segment);
}
}
if (cachedSegmentsByIndex.size) {
workerDecodeInfo("[play:client-playback-worker.js] decodePlayback cached segments", {
cachedChunks: cachedSegmentsByIndex.size,
totalChunks: chunks.length,
decodingChunks: Math.max(0, chunks.length - cachedSegmentsByIndex.size),
});
}
const pcmMark = workerPerfStart("run interleaved ECDC PCM decode", {
frames: encodedFrames.length,
bundleName: selectedBundleName,
cachedChunks: cachedSegmentsByIndex.size,
});
for (let index = 0; index < chunks.length; index += 1) {
const end = index + 1;
const cachedSegment = cachedSegmentsByIndex.get(index);
if (cachedSegment) {
const spliced = s16Writer.addCachedRange(
cachedSegment.startFrame,
cachedSegment.endFrame,
new Int16Array(cachedSegment.pcmBuffer),
);
if (spliced) {
if (chunks[index] && typeof chunks[index] === "object") {
chunks[index].payload = null;
}
postDecodeProgress(id, {
msg: `Using cached groove audio ${end}/${chunks.length}`,
progressPercent: Math.round((end / chunks.length) * 100),
processedSeconds: formatDecimal(Math.min(durationSeconds, durationSeconds * (end / Math.max(1, chunks.length))), 2),
duration: formatDecimal(durationSeconds, 2),
});
const emittedSegments = s16Writer.emitAfterBatch(end);
if (cacheDecodedSegments) {
postRawDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
postDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
}
if (encodedFrames[index]) {
encodedFrames[index].codes = null;
}
continue;
}
workerDecodeWarn("[play:client-playback-worker.js] cached segment splice rejected; decoding chunk", { index });
}
workerDecodeDebug("[play:client-playback-worker.js] decodePlayback chunk", { index, totalChunks: chunks.length, offset: chunks[index]?.offset, frameLength: chunks[index]?.frameLength, bundleName: selectedBundleName });
const processedSeconds = Math.min(durationSeconds, durationSeconds * (end / Math.max(1, chunks.length)));
postDecodeProgress(id, {
msg: `Reading groove audio ${end}/${chunks.length}`,
progressPercent: Math.round((end / chunks.length) * 100),
processedSeconds: formatDecimal(processedSeconds, 2),
duration: formatDecimal(durationSeconds, 2),
});
let frame = null;
try {
frame = await decodeLmChunk(
ecdcDecode.encodecModule,
ecdcDecode.lmWeights,
selectedBundleJson,
chunks[index],
selectedMeta,
);
} catch (error) {
reportSkippedDecodePacket(
id,
`Skipped damaged ECDC packet ${end}/${chunks.length}; inserted ${expectedFramePcmSamples(chunks[index], selectedMeta)} silent samples`,
error,
);
frame = createSilentEncodedFrame(chunks[index], selectedMeta);
}
encodedFrames[index] = normalizeEncodedFrame(frame);
if (chunks[index] && typeof chunks[index] === "object") {
chunks[index].payload = null;
}
let feeds = null;
let outputs = null;
try {
if (encodedFrames[index]?.silentPcm) {
s16Writer.addSilentFrame(index);
} else {
const decoderInputs = buildDecodeInputs(encodedFrames, selectedMeta, index, end);
feeds = {
[session.inputNames[0]]: new ort.Tensor("int64", decoderInputs.codes, [
decoderInputs.batchSize,
selectedMeta.num_codebooks,
decoderInputs.frameLength,
]),
[session.inputNames[1]]: new ort.Tensor("float32", decoderInputs.scales, [decoderInputs.batchSize, 1]),
};
outputs = await session.run(feeds);
const decodedTensor = findDecodeOutput(outputs);
s16Writer.addDecodedBatch(index, end, decodedTensor.data);
}
const emittedSegments = s16Writer.emitAfterBatch(end);
if (cacheDecodedSegments) {
postRawDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
postDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
}
} catch (error) {
reportSkippedDecodePacket(
id,
`Skipped failed EnCodec PCM packet ${end}/${encodedFrames.length}; inserted silence`,
error,
);
s16Writer.addSilentFrame(index);
const emittedSegments = s16Writer.emitAfterBatch(end);
if (cacheDecodedSegments) {
postRawDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
postDecodedPcmSegments(id, emittedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
}
} finally {
disposeOrtTensorMap(feeds);
disposeOrtTensorMap(outputs);
if (encodedFrames[index]) {
encodedFrames[index].codes = null;
}
}
}
workerPerfEnd(id, pcmMark, {
decodedSamples: safeAudioLength,
});
postDecodeProgress(id, {
msg: "Preparing playback",
progressPercent: 100,
});
if (cacheDecodedSegments) {
const flushedSegments = s16Writer.flush();
postRawDecodedPcmSegments(id, flushedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
postDecodedPcmSegments(id, flushedSegments, {
sampleRate,
channels: Number(selectedMeta.channels) || 2,
audioLength: safeAudioLength,
});
}
const s16Result = s16Writer.result();
const samplesPerChunk = Math.max(
1,
Math.floor(Number(selectedMeta?.owned_samples ?? selectedMeta?.ownedSamples) || 0)
|| Math.round(safeAudioLength / Math.max(1, encodedFrames.length)),
);
const { channelData: splicedChannelData, audioLength: splicedAudioLength } =
spliceSilenceIntoChannelData(s16Result.channelData, safeAudioLength, samplesPerChunk, silenceMap);
workerPerfEnd(id, perfMark, {
bundleName: selectedBundleName,
duration: durationSeconds,
});
return {
s16ChannelData: splicedChannelData,
channels: Number(selectedMeta.channels) || 2,
sampleRate,
audioLength: splicedAudioLength,
bundleName: selectedBundleName,
};
} catch (error) {
workerPerfEnd(id, perfMark, {
error: workerErrorMessage(error),
});
throw error;
} finally {
await session?.release?.();
}
}
async function decodeRecordPngPlayback({ id, pngBytes, record = {}, recordProfile, runtime }) {
workerDecodeDebug("[play:client-playback-worker.js] decodeRecordPngPlayback", { id, pngBytes: pngBytes?.byteLength || 0, recordProfile });
const perfMark = workerPerfStart("decode record PNG end-to-end", {
pngBytes: pngBytes?.byteLength || 0,
});
try {
const extracted = await extractPayloadFromRecordPng({ id, pngBytes, record, recordProfile });
const decoded = await decodePayloadPlayback({
id,
payloadBuffer: extracted.payload,
payloadContainer: extracted.payloadContainer,
payloadCodec: extracted.payloadCodec,
entryContainer: extracted.entryContainer,
bundleName: extracted.bundleName,
record,
recordProfile: extracted.recordProfile,
runtime,
silenceMap: extracted.silenceMap,
});
workerPerfEnd(id, perfMark, {
bundleName: decoded.bundleName,
recordProfile: extracted.recordProfile,
});
return {
...decoded,
recordProfile: extracted.recordProfile,
recordHeaderProof: extracted.recordHeaderProof || null,
releaseId: extracted.releaseId || "",
};
} catch (error) {
workerPerfEnd(id, perfMark, {
error: workerErrorMessage(error),
});
throw error;
}
}
function decodeTrackMetadataFromRecord(record = {}) {
const meta = record?.meta && typeof record.meta === "object" ? record.meta : {};
const payloadMetadata = record?.payloadMetadata && typeof record.payloadMetadata === "object"
? record.payloadMetadata
: {};
return resolvePlaybackPayloadMetadata({
headerMetadata: {
payloadContainer: record?.payloadContainer || meta.payloadContainer || "",
entryContainer: record?.entryContainer || meta.entryContainer || "",
trackListing: Array.isArray(record?.trackListing)
? record.trackListing
: (Array.isArray(meta.trackListing) ? meta.trackListing : []),
dummySpiralRegions: Array.isArray(record?.dummySpiralRegions)
? record.dummySpiralRegions
: (Array.isArray(meta.dummySpiralRegions) ? meta.dummySpiralRegions : []),
},
payloadMetadata,
});
}
async function decodePayloadPlayback({ payloadBuffer, payloadContainer = "", payloadCodec = "", entryContainer = "", recordProfile, ...rest }) {
workerDecodeDebug("[play:client-playback-worker.js] decodePayloadPlayback", { payloadBytes: payloadBuffer?.byteLength || 0, payloadContainer, payloadCodec, entryContainer, recordProfile });
await ensurePlayerAppWasmModule();
payloadContainer = normalizePayloadContainer(payloadContainer);
if (!payloadContainer) {
throw new Error("payloadContainer is required.");
}
const decorate = (result) => {
const metadata = decodeTrackMetadataFromRecord({
...(rest.record || {}),
payloadContainer,
entryContainer,
payloadMetadata: rest.payloadMetadata || rest.record?.payloadMetadata || {},
trackListing: Array.isArray(rest.trackListing) ? rest.trackListing : rest.record?.trackListing,
dummySpiralRegions: Array.isArray(rest.dummySpiralRegions)
? rest.dummySpiralRegions
: rest.record?.dummySpiralRegions,
});
return {
...result,
payloadContainer: result.payloadContainer || metadata.payloadContainer,
entryContainer: result.entryContainer || metadata.entryContainer,
trackListing: metadata.trackListing,
dummySpiralRegions: metadata.dummySpiralRegions,
};
};
if (payloadContainerIsMossNano(payloadContainer)) {
return decorate(await decodeMossNanoPlayback({
...rest,
payloadBuffer,
recordProfile,
}));
}
return decorate(await decodePlayback({
...rest,
ecdcBuffer: payloadBuffer,
}));
}
function postWorkerDecodeResult(id, result) {
const s16ChannelBuffers = (result.s16ChannelData || []).map((channel) => channel.buffer);
const transfer = s16ChannelBuffers;
self.postMessage(
{
id,
ok: true,
result: {
s16ChannelBuffers,
channels: result.channels,
sampleRate: result.sampleRate,
audioLength: result.audioLength,
bundleName: result.bundleName,
audioFormat: result.audioFormat || "record_png_payload",
payloadContainer: result.payloadContainer || "",
payloadCodec: result.payloadCodec || "",
entryContainer: result.entryContainer || "",
recordProfile: result.recordProfile,
recordHeaderProof: result.recordHeaderProof || null,
releaseId: result.releaseId || "",
trackListing: Array.isArray(result.trackListing) ? result.trackListing : [],
dummySpiralRegions: Array.isArray(result.dummySpiralRegions) ? result.dummySpiralRegions : [],
},
},
transfer,
);
}
self.onmessage = async (event) => {
const { id, type } = event.data || {};
if (type === "init") {
self.postMessage({ id, ok: true, result: { crossOriginIsolated: self.crossOriginIsolated === true } });
return;
}
try {
if (type === "inspect-record-png") {
workerDecodeDebug("[play:client-playback-worker.js] onmessage inspect-record-png", { id, pngBytes: event.data?.pngBytes?.byteLength || 0, recordProfile: event.data?.recordProfile });
const result = await extractPayloadFromRecordPng(event.data);
const payloadBuffer = result.payload.buffer.slice(result.payload.byteOffset, result.payload.byteOffset + result.payload.byteLength);
const chunkStreamBuffer = result.chunkStream?.byteLength
? result.chunkStream.buffer.slice(result.chunkStream.byteOffset, result.chunkStream.byteOffset + result.chunkStream.byteLength)
: null;
const ecdcBuffer = result.ecdc
? result.ecdc.buffer.slice(result.ecdc.byteOffset, result.ecdc.byteOffset + result.ecdc.byteLength)
: null;
const transfer = [payloadBuffer];
if (chunkStreamBuffer) transfer.push(chunkStreamBuffer);
if (ecdcBuffer) transfer.push(ecdcBuffer);
self.postMessage({
id,
ok: true,
result: {
payloadBuffer,
chunkStreamBuffer,
payloadContainer: result.payloadContainer,
payloadCodec: result.payloadCodec,
entryContainer: result.entryContainer || "",
payloadMetadataJson: JSON.stringify(result.payloadMetadata || {}),
trackListingJson: JSON.stringify(result.trackListing || []),
dummySpiralRegionsJson: JSON.stringify(result.dummySpiralRegions || []),
silenceMapJson: JSON.stringify(result.silenceMap || []),
programmeMapJson: result.programmeMap ? JSON.stringify(result.programmeMap) : "",
ecdcBuffer,
bundleName: result.bundleName,
recordProfile: result.recordProfile,
recordHeaderProof: result.recordHeaderProof || null,
releaseId: result.releaseId || "",
},
}, transfer);
return;
}
if (type === "decode-record-png") {
workerDecodeDebug("[play:client-playback-worker.js] onmessage decode-record-png", { id, pngBytes: event.data?.pngBytes?.byteLength || 0, recordProfile: event.data?.recordProfile });
const result = await decodeRecordPngPlayback(event.data);
postWorkerDecodeResult(id, result);
return;
}
if (type === "decode-payload") {
workerDecodeDebug("[play:client-playback-worker.js] onmessage decode-payload", { id, payloadBytes: event.data?.payloadBuffer?.byteLength || 0, payloadContainer: event.data?.payloadContainer, entryContainer: event.data?.entryContainer });
const result = await decodePayloadPlayback(event.data);
postWorkerDecodeResult(id, result);
return;
}
if (type === "decode" || type === "decode-ecdc") {
workerDecodeDebug("[play:client-playback-worker.js] onmessage decode-ecdc", { id, ecdcBytes: event.data?.ecdcBuffer?.byteLength || 0, bundleName: event.data?.bundleName || "" });
const result = await decodePlayback(event.data);
postWorkerDecodeResult(id, result);
return;
}
throw new Error(`Unknown playback worker action: ${type || "missing"}`);
} catch (error) {
self.postMessage({
id,
ok: false,
error: error?.message || String(error),
});
}
};