@meframe/core 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"video-decoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/video-decoder.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAgB5B;AAuKD;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAgBvB"}
1
+ {"version":3,"file":"video-decoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/video-decoder.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAsB5B;AAuKD;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAsBvB"}
@@ -5,11 +5,17 @@ function isDecodeError(error) {
5
5
  async function decodeChunksForScrub(chunks, config, targetTimeUs, options = {}) {
6
6
  const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();
7
7
  const platformInfo = detectPlatform();
8
- console.log("decodeChunksForScrub hwAccel:", hwAccel, "platform:", platformInfo.platform);
9
8
  try {
10
9
  return await _decodeForScrub(chunks, config, targetTimeUs, options, hwAccel);
11
10
  } catch (error) {
12
- console.warn("[decodeChunksForScrub] decode error:", error, " hwAccel:", hwAccel);
11
+ console.warn(
12
+ "[decodeChunksForScrub] decode error:",
13
+ error,
14
+ "hwAccel:",
15
+ hwAccel,
16
+ "platform:",
17
+ platformInfo.platform
18
+ );
13
19
  if (hwAccel !== "prefer-software" && isDecodeError(error)) {
14
20
  console.warn("[VideoDecoder] Hardware decode failed, falling back to software decode");
15
21
  return _decodeForScrub(chunks, config, targetTimeUs, options, "prefer-software");
@@ -146,11 +152,17 @@ async function _decodeForScrub(chunks, config, targetTimeUs, options, hwAccel) {
146
152
  async function decodeChunksWithoutFlush(chunks, config, options = {}) {
147
153
  const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();
148
154
  const platformInfo = detectPlatform();
149
- console.log("decodeChunksWithoutFlush hwAccel:", hwAccel, "platform:", platformInfo.platform);
150
155
  try {
151
156
  return await _decodeWithoutFlush(chunks, config, options, hwAccel);
152
157
  } catch (error) {
153
- console.warn("[decodeChunksWithoutFlush]decode error:", error, " hwAccel:", hwAccel);
158
+ console.warn(
159
+ "[decodeChunksWithoutFlush]decode error:",
160
+ error,
161
+ "hwAccel:",
162
+ hwAccel,
163
+ "platform:",
164
+ platformInfo.platform
165
+ );
154
166
  if (hwAccel !== "prefer-software" && isDecodeError(error)) {
155
167
  console.warn("[VideoDecoder] Hardware decode failed, falling back to software decode");
156
168
  return _decodeWithoutFlush(chunks, config, options, "prefer-software");
@@ -1 +1 @@
1
- {"version":3,"file":"video-decoder.js","sources":["../../../src/stages/decode/video-decoder.ts"],"sourcesContent":["import { detectPlatform, getRecommendedHardwareAcceleration } from '../../utils/platform-utils';\n\nfunction isDecodeError(error: unknown): boolean {\n return error instanceof Error && error.message === 'Decoding error.';\n}\n\nexport interface DecodeResult {\n frames: VideoFrame[];\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface VideoDecoderConfig {\n codec: string;\n width: number;\n height: number;\n description?: ArrayBuffer;\n hardwareAcceleration?: HardwareAcceleration;\n}\n\nexport interface DecodeOptions {\n timeoutMs?: number;\n pollIntervalMs?: number;\n}\n\nexport interface DecodeScrubResult {\n before: VideoFrame | null;\n after: VideoFrame | null;\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface DecodeScrubOptions {\n timeoutMs?: number;\n /**\n * Backpressure: keep decodeQueueSize small so we don't decode far past target.\n */\n maxQueueSize?: number;\n /**\n * Optional cooperative abort hook (e.g., session cancelled by a newer seek).\n */\n shouldAbort?: () => boolean;\n}\n\n/**\n * Scrub decode: feed chunks in order, stop as soon as output crosses targetTimeUs.\n *\n * Why this lives in helpers:\n * - Centralize VideoDecoder quirks (no-await flush, HW accel defaults)\n * - Keep on-demand session focused on IO/demux/cache, not decode control flow\n */\nexport async function decodeChunksForScrub(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n targetTimeUs: number,\n options: DecodeScrubOptions = {}\n): Promise<DecodeScrubResult> {\n const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();\n\n const platformInfo = detectPlatform();\n console.log('decodeChunksForScrub hwAccel:', hwAccel, 'platform:', platformInfo.platform);\n\n try {\n return await _decodeForScrub(chunks, config, targetTimeUs, options, hwAccel);\n } catch (error) {\n console.warn('[decodeChunksForScrub] decode error:', error, ' hwAccel:', hwAccel);\n if (hwAccel !== 'prefer-software' && isDecodeError(error)) {\n console.warn('[VideoDecoder] Hardware decode failed, falling back to software decode');\n return _decodeForScrub(chunks, config, targetTimeUs, options, 'prefer-software');\n }\n throw error;\n }\n}\n\nasync function _decodeForScrub(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n targetTimeUs: number,\n options: DecodeScrubOptions,\n hwAccel: HardwareAcceleration\n): Promise<DecodeScrubResult> {\n const { timeoutMs = 2000, maxQueueSize = 2, shouldAbort } = options;\n const startTime = performance.now();\n let outputFrames = 0;\n\n // Keep at most two frames: last <= target and first > target.\n let before: VideoFrame | null = null;\n let after: VideoFrame | null = null;\n\n return await new Promise<DecodeScrubResult>((resolve, reject) => {\n let settled = false;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n\n const settle = (ok: boolean, err?: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames,\n durationMs: performance.now() - startTime,\n };\n\n if (!ok) {\n if (before) before.close();\n if (after) after.close();\n reject(err instanceof Error ? err : new Error(String(err ?? 'Scrub decode failed')));\n return;\n }\n\n resolve({ before, after, stats });\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n outputFrames += 1;\n\n if (settled) {\n frame.close();\n return;\n }\n\n const ts = frame.timestamp ?? 0;\n if (ts <= targetTimeUs) {\n if (before) before.close();\n before = frame;\n return;\n }\n\n if (!after) {\n after = frame;\n // We have both sides (or at least the first after); stop as soon as possible.\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n\n frame.close();\n },\n error: (e) => {\n try {\n decoder.close();\n } catch {\n /* noop */\n }\n settle(false, e);\n },\n });\n\n try {\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: hwAccel,\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n } catch (e) {\n settle(false, e);\n return;\n }\n\n timeoutId = setTimeout(() => {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n // Best-effort: if we got at least one frame, still treat as success.\n if (before || after) {\n settle(true);\n } else {\n settle(false, new Error(`Scrub decode timeout after ${timeoutMs}ms`));\n }\n }, timeoutMs);\n\n const feed = async () => {\n try {\n for (const chunk of chunks) {\n if (settled) break;\n if (shouldAbort?.()) break;\n\n // Backpressure: keep queue small so we don't decode far past target.\n while (!settled && !shouldAbort?.() && decoder.decodeQueueSize > maxQueueSize) {\n await new Promise((r) => setTimeout(r, 0));\n }\n\n if (settled) break;\n if (shouldAbort?.()) break;\n decoder.decode(chunk);\n }\n\n // If we already settled (found target), we're done.\n if (settled) return;\n if (shouldAbort?.()) {\n // If cancelled, resolve best-effort if any output exists; otherwise fail.\n if (before || after) {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(false, new Error('Scrub decode aborted'));\n return;\n }\n\n // We may have hit end-of-input without seeing a frame after target (e.g., target at end).\n // Trigger flush (do not await) and give output a chance before timeout.\n decoder.flush().catch(() => {});\n } catch (e) {\n settle(false, e);\n }\n };\n\n void feed();\n });\n}\n\n/**\n * Decode chunks using native VideoDecoder without awaiting flush\n * This avoids Windows hardware acceleration flush hang bug\n *\n * Strategy:\n * - Feed all chunks to decoder\n * - Call flush() but don't await (may hang on Windows HW)\n * - Poll decodeQueueSize to detect completion\n * - Timeout fallback ensures no infinite hang\n */\nexport async function decodeChunksWithoutFlush(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n options: DecodeOptions = {}\n): Promise<DecodeResult> {\n const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();\n\n const platformInfo = detectPlatform();\n console.log('decodeChunksWithoutFlush hwAccel:', hwAccel, 'platform:', platformInfo.platform);\n\n try {\n return await _decodeWithoutFlush(chunks, config, options, hwAccel);\n } catch (error) {\n console.warn('[decodeChunksWithoutFlush]decode error:', error, ' hwAccel:', hwAccel);\n if (hwAccel !== 'prefer-software' && isDecodeError(error)) {\n console.warn('[VideoDecoder] Hardware decode failed, falling back to software decode');\n return _decodeWithoutFlush(chunks, config, options, 'prefer-software');\n }\n throw error;\n }\n}\n\nfunction _decodeWithoutFlush(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n options: DecodeOptions,\n hwAccel: HardwareAcceleration\n): Promise<DecodeResult> {\n const { timeoutMs = 2000, pollIntervalMs = 10 } = options;\n const startTime = performance.now();\n const frames: VideoFrame[] = [];\n\n return new Promise((resolve, reject) => {\n let isSettled = false;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (pollTimer !== null) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n if (timeoutTimer !== null) {\n clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const settle = (success: boolean, errorMsg?: string) => {\n if (isSettled) return;\n isSettled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames: frames.length,\n durationMs: performance.now() - startTime,\n };\n\n if (success && frames.length > 0) {\n resolve({ frames, stats });\n } else {\n reject(new Error(errorMsg || 'Decode failed'));\n }\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n frames.push(frame);\n },\n error: (error) => {\n for (const f of frames) {\n try {\n f.close();\n } catch {\n /* noop */\n }\n }\n try {\n decoder.close();\n } catch {\n /* noop */\n }\n settle(false, error.message);\n },\n });\n\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: hwAccel,\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n\n // Feed all chunks\n for (const chunk of chunks) {\n decoder.decode(chunk);\n }\n\n // Call flush but don't await (may hang on Windows HW acceleration)\n decoder.flush().catch(() => {\n // Flush errors are non-critical\n });\n\n // Poll decode queue to detect completion\n pollTimer = setInterval(() => {\n if (decoder.decodeQueueSize === 0 && frames.length > 0) {\n decoder.close();\n settle(true);\n }\n }, pollIntervalMs);\n\n // Timeout fallback\n timeoutTimer = setTimeout(() => {\n decoder.close();\n settle(frames.length > 0, `Decode timeout after ${timeoutMs}ms`);\n }, timeoutMs);\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"video-decoder.js","sources":["../../../src/stages/decode/video-decoder.ts"],"sourcesContent":["import { detectPlatform, getRecommendedHardwareAcceleration } from '../../utils/platform-utils';\n\nfunction isDecodeError(error: unknown): boolean {\n return error instanceof Error && error.message === 'Decoding error.';\n}\n\nexport interface DecodeResult {\n frames: VideoFrame[];\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface VideoDecoderConfig {\n codec: string;\n width: number;\n height: number;\n description?: ArrayBuffer;\n hardwareAcceleration?: HardwareAcceleration;\n}\n\nexport interface DecodeOptions {\n timeoutMs?: number;\n pollIntervalMs?: number;\n}\n\nexport interface DecodeScrubResult {\n before: VideoFrame | null;\n after: VideoFrame | null;\n stats: {\n inputChunks: number;\n outputFrames: number;\n durationMs: number;\n };\n}\n\nexport interface DecodeScrubOptions {\n timeoutMs?: number;\n /**\n * Backpressure: keep decodeQueueSize small so we don't decode far past target.\n */\n maxQueueSize?: number;\n /**\n * Optional cooperative abort hook (e.g., session cancelled by a newer seek).\n */\n shouldAbort?: () => boolean;\n}\n\n/**\n * Scrub decode: feed chunks in order, stop as soon as output crosses targetTimeUs.\n *\n * Why this lives in helpers:\n * - Centralize VideoDecoder quirks (no-await flush, HW accel defaults)\n * - Keep on-demand session focused on IO/demux/cache, not decode control flow\n */\nexport async function decodeChunksForScrub(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n targetTimeUs: number,\n options: DecodeScrubOptions = {}\n): Promise<DecodeScrubResult> {\n const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();\n\n const platformInfo = detectPlatform();\n\n try {\n return await _decodeForScrub(chunks, config, targetTimeUs, options, hwAccel);\n } catch (error) {\n console.warn(\n '[decodeChunksForScrub] decode error:',\n error,\n 'hwAccel:',\n hwAccel,\n 'platform:',\n platformInfo.platform\n );\n if (hwAccel !== 'prefer-software' && isDecodeError(error)) {\n console.warn('[VideoDecoder] Hardware decode failed, falling back to software decode');\n return _decodeForScrub(chunks, config, targetTimeUs, options, 'prefer-software');\n }\n throw error;\n }\n}\n\nasync function _decodeForScrub(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n targetTimeUs: number,\n options: DecodeScrubOptions,\n hwAccel: HardwareAcceleration\n): Promise<DecodeScrubResult> {\n const { timeoutMs = 2000, maxQueueSize = 2, shouldAbort } = options;\n const startTime = performance.now();\n let outputFrames = 0;\n\n // Keep at most two frames: last <= target and first > target.\n let before: VideoFrame | null = null;\n let after: VideoFrame | null = null;\n\n return await new Promise<DecodeScrubResult>((resolve, reject) => {\n let settled = false;\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n\n const settle = (ok: boolean, err?: unknown) => {\n if (settled) return;\n settled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames,\n durationMs: performance.now() - startTime,\n };\n\n if (!ok) {\n if (before) before.close();\n if (after) after.close();\n reject(err instanceof Error ? err : new Error(String(err ?? 'Scrub decode failed')));\n return;\n }\n\n resolve({ before, after, stats });\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n outputFrames += 1;\n\n if (settled) {\n frame.close();\n return;\n }\n\n const ts = frame.timestamp ?? 0;\n if (ts <= targetTimeUs) {\n if (before) before.close();\n before = frame;\n return;\n }\n\n if (!after) {\n after = frame;\n // We have both sides (or at least the first after); stop as soon as possible.\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n\n frame.close();\n },\n error: (e) => {\n try {\n decoder.close();\n } catch {\n /* noop */\n }\n settle(false, e);\n },\n });\n\n try {\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: hwAccel,\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n } catch (e) {\n settle(false, e);\n return;\n }\n\n timeoutId = setTimeout(() => {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n // Best-effort: if we got at least one frame, still treat as success.\n if (before || after) {\n settle(true);\n } else {\n settle(false, new Error(`Scrub decode timeout after ${timeoutMs}ms`));\n }\n }, timeoutMs);\n\n const feed = async () => {\n try {\n for (const chunk of chunks) {\n if (settled) break;\n if (shouldAbort?.()) break;\n\n // Backpressure: keep queue small so we don't decode far past target.\n while (!settled && !shouldAbort?.() && decoder.decodeQueueSize > maxQueueSize) {\n await new Promise((r) => setTimeout(r, 0));\n }\n\n if (settled) break;\n if (shouldAbort?.()) break;\n decoder.decode(chunk);\n }\n\n // If we already settled (found target), we're done.\n if (settled) return;\n if (shouldAbort?.()) {\n // If cancelled, resolve best-effort if any output exists; otherwise fail.\n if (before || after) {\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(true);\n return;\n }\n try {\n decoder.close();\n } catch {\n // ignore\n }\n settle(false, new Error('Scrub decode aborted'));\n return;\n }\n\n // We may have hit end-of-input without seeing a frame after target (e.g., target at end).\n // Trigger flush (do not await) and give output a chance before timeout.\n decoder.flush().catch(() => {});\n } catch (e) {\n settle(false, e);\n }\n };\n\n void feed();\n });\n}\n\n/**\n * Decode chunks using native VideoDecoder without awaiting flush\n * This avoids Windows hardware acceleration flush hang bug\n *\n * Strategy:\n * - Feed all chunks to decoder\n * - Call flush() but don't await (may hang on Windows HW)\n * - Poll decodeQueueSize to detect completion\n * - Timeout fallback ensures no infinite hang\n */\nexport async function decodeChunksWithoutFlush(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n options: DecodeOptions = {}\n): Promise<DecodeResult> {\n const hwAccel = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();\n\n const platformInfo = detectPlatform();\n\n try {\n return await _decodeWithoutFlush(chunks, config, options, hwAccel);\n } catch (error) {\n console.warn(\n '[decodeChunksWithoutFlush]decode error:',\n error,\n 'hwAccel:',\n hwAccel,\n 'platform:',\n platformInfo.platform\n );\n if (hwAccel !== 'prefer-software' && isDecodeError(error)) {\n console.warn('[VideoDecoder] Hardware decode failed, falling back to software decode');\n return _decodeWithoutFlush(chunks, config, options, 'prefer-software');\n }\n throw error;\n }\n}\n\nfunction _decodeWithoutFlush(\n chunks: EncodedVideoChunk[],\n config: VideoDecoderConfig,\n options: DecodeOptions,\n hwAccel: HardwareAcceleration\n): Promise<DecodeResult> {\n const { timeoutMs = 2000, pollIntervalMs = 10 } = options;\n const startTime = performance.now();\n const frames: VideoFrame[] = [];\n\n return new Promise((resolve, reject) => {\n let isSettled = false;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (pollTimer !== null) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n if (timeoutTimer !== null) {\n clearTimeout(timeoutTimer);\n timeoutTimer = null;\n }\n };\n\n const settle = (success: boolean, errorMsg?: string) => {\n if (isSettled) return;\n isSettled = true;\n cleanup();\n\n const stats = {\n inputChunks: chunks.length,\n outputFrames: frames.length,\n durationMs: performance.now() - startTime,\n };\n\n if (success && frames.length > 0) {\n resolve({ frames, stats });\n } else {\n reject(new Error(errorMsg || 'Decode failed'));\n }\n };\n\n const decoder = new VideoDecoder({\n output: (frame) => {\n frames.push(frame);\n },\n error: (error) => {\n for (const f of frames) {\n try {\n f.close();\n } catch {\n /* noop */\n }\n }\n try {\n decoder.close();\n } catch {\n /* noop */\n }\n settle(false, error.message);\n },\n });\n\n decoder.configure({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: hwAccel,\n optimizeForLatency: true,\n ...(config.description && { description: config.description }),\n });\n\n // Feed all chunks\n for (const chunk of chunks) {\n decoder.decode(chunk);\n }\n\n // Call flush but don't await (may hang on Windows HW acceleration)\n decoder.flush().catch(() => {\n // Flush errors are non-critical\n });\n\n // Poll decode queue to detect completion\n pollTimer = setInterval(() => {\n if (decoder.decodeQueueSize === 0 && frames.length > 0) {\n decoder.close();\n settle(true);\n }\n }, pollIntervalMs);\n\n // Timeout fallback\n timeoutTimer = setTimeout(() => {\n decoder.close();\n settle(frames.length > 0, `Decode timeout after ${timeoutMs}ms`);\n }, timeoutMs);\n });\n}\n"],"names":[],"mappings":"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"}
@@ -19,7 +19,7 @@ function isWindows() {
19
19
  }
20
20
  function getRecommendedHardwareAcceleration() {
21
21
  if (isWindows()) {
22
- return "prefer-hardware";
22
+ return "prefer-software";
23
23
  }
24
24
  return "no-preference";
25
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"platform-utils.js","sources":["../../src/utils/platform-utils.ts"],"sourcesContent":["/**\n * Platform detection utilities with caching for performance\n */\n\nexport interface PlatformInfo {\n isWindows: boolean;\n isMacOS: boolean;\n isLinux: boolean;\n platform: string;\n userAgent: string;\n}\n\n// Cache platform detection result (only detect once)\nlet cachedPlatformInfo: PlatformInfo | null = null;\n\n/**\n * Detect current platform (cached after first call)\n */\nexport function detectPlatform(): PlatformInfo {\n if (cachedPlatformInfo) {\n return cachedPlatformInfo;\n }\n\n const platform = typeof navigator !== 'undefined' ? navigator.platform : '';\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n\n cachedPlatformInfo = {\n isWindows: /Win/i.test(platform) || /Win/i.test(userAgent),\n isMacOS: /Mac/i.test(platform),\n isLinux: /Linux/i.test(platform),\n platform,\n userAgent,\n };\n\n return cachedPlatformInfo;\n}\n\n/**\n * Check if current platform is Windows (cached)\n */\nexport function isWindows(): boolean {\n return detectPlatform().isWindows;\n}\n\n/**\n * Check if current platform is macOS (cached)\n */\nexport function isMacOS(): boolean {\n return detectPlatform().isMacOS;\n}\n\n/**\n * Check if current platform is Linux (cached)\n */\nexport function isLinux(): boolean {\n return detectPlatform().isLinux;\n}\n\n/**\n * Get platform-recommended hardware acceleration setting for video decoding\n *\n * Background:\n * Windows hardware video decoders (especially with DXVA2/D3D11) may hang indefinitely\n * when calling VideoDecoder.flush() in certain scenarios (frequent seeks, large GOPs).\n * This appears to be a driver/platform-specific issue affecting Chromium's WebCodecs.\n *\n * Related discussions:\n * - https://github.com/w3c/webcodecs/issues\n * - Observed in production on Windows 10/11 with various GPU vendors\n *\n * Workaround:\n * Use software decoding on Windows to avoid flush() hangs, with ~4x slower decode\n * but reliable operation. Other platforms use hardware acceleration by default.\n *\n * @returns Platform-recommended hardware acceleration setting\n */\nexport function getRecommendedHardwareAcceleration(): HardwareAcceleration {\n // Windows: prefer software to avoid flush hang in hardware decoders\n if (isWindows()) {\n return 'prefer-hardware';\n }\n\n // Other platforms: no preference (let browser choose)\n return 'no-preference';\n}\n\nexport interface BrowserCompatibility {\n webCodecsAvailable: boolean;\n audioWebCodecsAvailable: boolean;\n opfsAvailable: boolean;\n missingFeatures: string[];\n browserInfo: {\n name: string;\n version: string;\n recommended: string;\n };\n}\n\nlet cachedCompatibility: BrowserCompatibility | null = null;\n\n/**\n * Check browser compatibility for WebCodecs and OPFS (sync, < 1ms, cached)\n * Only checks API existence, no file operations\n */\nexport function checkBrowserCompatibility(): BrowserCompatibility {\n if (cachedCompatibility) return cachedCompatibility;\n\n const missingFeatures: string[] = [];\n let webCodecsAvailable = true;\n let audioWebCodecsAvailable = true;\n let opfsAvailable = false;\n\n // Check WebCodecs APIs (sync, < 1ms)\n if (typeof VideoDecoder === 'undefined') {\n missingFeatures.push('VideoDecoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoEncoder === 'undefined') {\n missingFeatures.push('VideoEncoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoFrame === 'undefined') {\n missingFeatures.push('VideoFrame');\n webCodecsAvailable = false;\n }\n if (typeof EncodedVideoChunk === 'undefined') {\n missingFeatures.push('EncodedVideoChunk');\n webCodecsAvailable = false;\n }\n\n // Check Audio WebCodecs APIs (sync, < 1ms)\n if (typeof AudioDecoder === 'undefined') {\n missingFeatures.push('AudioDecoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioEncoder === 'undefined') {\n missingFeatures.push('AudioEncoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioData === 'undefined') {\n missingFeatures.push('AudioData');\n audioWebCodecsAvailable = false;\n }\n if (typeof EncodedAudioChunk === 'undefined') {\n missingFeatures.push('EncodedAudioChunk');\n audioWebCodecsAvailable = false;\n }\n\n // Detect browser (sync, < 1ms)\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n let browserName = 'Unknown';\n let browserVersion = 'Unknown';\n let recommended = 'Chrome 94+ / Edge 94+ / Safari 26+';\n\n if (userAgent.includes('Edg/')) {\n browserName = 'Edge';\n const match = userAgent.match(/Edg\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Edge 94+';\n } else if (userAgent.includes('Chrome/')) {\n browserName = 'Chrome';\n const match = userAgent.match(/Chrome\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Chrome 94+';\n } else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome')) {\n browserName = 'Safari';\n const match = userAgent.match(/Version\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Safari 26+';\n } else if (userAgent.includes('Firefox/')) {\n browserName = 'Firefox';\n const match = userAgent.match(/Firefox\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Not supported';\n }\n\n // Check OPFS API (sync, no file operations)\n if (webCodecsAvailable) {\n if (\n typeof navigator !== 'undefined' &&\n navigator.storage &&\n typeof navigator.storage.getDirectory === 'function'\n ) {\n // Assume support if the API exists (no file operations performed here).\n opfsAvailable = true;\n } else {\n missingFeatures.push('OPFS');\n }\n }\n\n cachedCompatibility = {\n webCodecsAvailable,\n audioWebCodecsAvailable,\n opfsAvailable,\n missingFeatures,\n browserInfo: { name: browserName, version: browserVersion, recommended },\n };\n\n return cachedCompatibility;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-utils.js","sources":["../../src/utils/platform-utils.ts"],"sourcesContent":["/**\n * Platform detection utilities with caching for performance\n */\n\nexport interface PlatformInfo {\n isWindows: boolean;\n isMacOS: boolean;\n isLinux: boolean;\n platform: string;\n userAgent: string;\n}\n\n// Cache platform detection result (only detect once)\nlet cachedPlatformInfo: PlatformInfo | null = null;\n\n/**\n * Detect current platform (cached after first call)\n */\nexport function detectPlatform(): PlatformInfo {\n if (cachedPlatformInfo) {\n return cachedPlatformInfo;\n }\n\n const platform = typeof navigator !== 'undefined' ? navigator.platform : '';\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n\n cachedPlatformInfo = {\n isWindows: /Win/i.test(platform) || /Win/i.test(userAgent),\n isMacOS: /Mac/i.test(platform),\n isLinux: /Linux/i.test(platform),\n platform,\n userAgent,\n };\n\n return cachedPlatformInfo;\n}\n\n/**\n * Check if current platform is Windows (cached)\n */\nexport function isWindows(): boolean {\n return detectPlatform().isWindows;\n}\n\n/**\n * Check if current platform is macOS (cached)\n */\nexport function isMacOS(): boolean {\n return detectPlatform().isMacOS;\n}\n\n/**\n * Check if current platform is Linux (cached)\n */\nexport function isLinux(): boolean {\n return detectPlatform().isLinux;\n}\n\n/**\n * Get platform-recommended hardware acceleration setting for video decoding\n *\n * Background:\n * Windows hardware video decoders (especially with DXVA2/D3D11) may hang indefinitely\n * when calling VideoDecoder.flush() in certain scenarios (frequent seeks, large GOPs).\n * This appears to be a driver/platform-specific issue affecting Chromium's WebCodecs.\n *\n * Related discussions:\n * - https://github.com/w3c/webcodecs/issues\n * - Observed in production on Windows 10/11 with various GPU vendors\n *\n * Workaround:\n * Use software decoding on Windows to avoid flush() hangs, with ~4x slower decode\n * but reliable operation. Other platforms use hardware acceleration by default.\n *\n * @returns Platform-recommended hardware acceleration setting\n */\nexport function getRecommendedHardwareAcceleration(): HardwareAcceleration {\n // Windows: prefer software to avoid flush hang in hardware decoders\n if (isWindows()) {\n return 'prefer-software';\n }\n\n // Other platforms: no preference (let browser choose)\n return 'no-preference';\n}\n\nexport interface BrowserCompatibility {\n webCodecsAvailable: boolean;\n audioWebCodecsAvailable: boolean;\n opfsAvailable: boolean;\n missingFeatures: string[];\n browserInfo: {\n name: string;\n version: string;\n recommended: string;\n };\n}\n\nlet cachedCompatibility: BrowserCompatibility | null = null;\n\n/**\n * Check browser compatibility for WebCodecs and OPFS (sync, < 1ms, cached)\n * Only checks API existence, no file operations\n */\nexport function checkBrowserCompatibility(): BrowserCompatibility {\n if (cachedCompatibility) return cachedCompatibility;\n\n const missingFeatures: string[] = [];\n let webCodecsAvailable = true;\n let audioWebCodecsAvailable = true;\n let opfsAvailable = false;\n\n // Check WebCodecs APIs (sync, < 1ms)\n if (typeof VideoDecoder === 'undefined') {\n missingFeatures.push('VideoDecoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoEncoder === 'undefined') {\n missingFeatures.push('VideoEncoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoFrame === 'undefined') {\n missingFeatures.push('VideoFrame');\n webCodecsAvailable = false;\n }\n if (typeof EncodedVideoChunk === 'undefined') {\n missingFeatures.push('EncodedVideoChunk');\n webCodecsAvailable = false;\n }\n\n // Check Audio WebCodecs APIs (sync, < 1ms)\n if (typeof AudioDecoder === 'undefined') {\n missingFeatures.push('AudioDecoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioEncoder === 'undefined') {\n missingFeatures.push('AudioEncoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioData === 'undefined') {\n missingFeatures.push('AudioData');\n audioWebCodecsAvailable = false;\n }\n if (typeof EncodedAudioChunk === 'undefined') {\n missingFeatures.push('EncodedAudioChunk');\n audioWebCodecsAvailable = false;\n }\n\n // Detect browser (sync, < 1ms)\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n let browserName = 'Unknown';\n let browserVersion = 'Unknown';\n let recommended = 'Chrome 94+ / Edge 94+ / Safari 26+';\n\n if (userAgent.includes('Edg/')) {\n browserName = 'Edge';\n const match = userAgent.match(/Edg\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Edge 94+';\n } else if (userAgent.includes('Chrome/')) {\n browserName = 'Chrome';\n const match = userAgent.match(/Chrome\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Chrome 94+';\n } else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome')) {\n browserName = 'Safari';\n const match = userAgent.match(/Version\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Safari 26+';\n } else if (userAgent.includes('Firefox/')) {\n browserName = 'Firefox';\n const match = userAgent.match(/Firefox\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Not supported';\n }\n\n // Check OPFS API (sync, no file operations)\n if (webCodecsAvailable) {\n if (\n typeof navigator !== 'undefined' &&\n navigator.storage &&\n typeof navigator.storage.getDirectory === 'function'\n ) {\n // Assume support if the API exists (no file operations performed here).\n opfsAvailable = true;\n } else {\n missingFeatures.push('OPFS');\n }\n }\n\n cachedCompatibility = {\n webCodecsAvailable,\n audioWebCodecsAvailable,\n opfsAvailable,\n missingFeatures,\n browserInfo: { name: browserName, version: browserVersion, recommended },\n };\n\n return cachedCompatibility;\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meframe/core",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Next generation media processing framework based on WebCodecs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -89,7 +89,7 @@
89
89
  "streaming",
90
90
  "composition"
91
91
  ],
92
- "author": "Sheepy",
92
+ "author": "haibo",
93
93
  "license": "MIT",
94
94
  "publishConfig": {
95
95
  "access": "public",