Files
bitvid/package.json
T
thePR0M3TH3ANandClaude Opus 5 4404728410 [playback] Add HLS support and stop blaming stale MediaErrors
bitvid attached every hosted source with `videoEl.src = url`, which only plays
HLS on Safari-family browsers. Firefox 146 fails with MEDIA_ERR_SRC_NOT_SUPPORTED
(canPlayType returns ""), so every .m3u8 note -- e.g. anything published by
nostube/slidestr -- was unplayable there and the URL-health badge showed
"offline". Chrome/Brave 149 happen to play HLS natively, which is why this
looked browser-specific.

Vendor hls.js (npm run build:hls) and route HLS sources through it:

- js/services/hlsPlayback.js owns detection, engine choice and the hls.js
  lifecycle. Engine choice keys off MediaSource, NOT canPlayType: Chrome
  returns "maybe" for every HLS type, so a native-first branch would hang.
  Native is used only when MSE is genuinely unavailable.
- The bundle is the FULL build, not hls.js/light -- light drops the alternate
  audio stream controller and real ladders demux audio into an EXT-X-MEDIA
  group. It is lazy-imported only when a .m3u8 is encountered, so ordinary
  MP4/WebTorrent playback never pays the ~500KB.
- modalCoordinator tears the instance down first; clearing `src` alone leaves
  the transmuxing worker and segment fetches running after the modal closes.
- urlHealthController probes HLS by fetching the playlist for #EXTM3U. The old
  bare-<video> probe could not load it and on Chrome emitted no events at all,
  burning the full timeout on every HLS card.
- HLS_PLAYBACK_START_TIMEOUT widens the 3s start window, which fired
  mid-startup: the bundle, master playlist, variant playlist, init segment and
  first media segment are four-plus serial round trips.

Also fixes two bugs found while verifying this:

- A stale MediaError was reported as the current failure. Teardown clears `src`
  and calls load(), leaving MEDIA_ERR_SRC_NOT_SUPPORTED ("Empty src attribute")
  on the element; a later watchdog fallback rendered that as "Hosted playback
  failed: source not supported or blocked." over a stream that was playing
  fine. Only a MediaError produced by the current attempt is surfaced now.
  Sources are also bound before the watchdogs are armed, so hls.js's own
  attachMedia() src swap cannot read as a playback failure.
- `audio/x-mpegurl` is a legacy alias for an HLS playlist of any kind, not an
  audio-only assertion. isAudioOnlyVideoObject misfiled HLS video ladders
  published with it as podcasts and dropped them from every video feed.

Verified end-to-end in Chromium, Chrome 149, Brave 149 and Firefox 146 against
a live HLS stream: Firefox goes from "No playable source found." to playing.
Rollback: stop calling attachHlsSource() and the previous `src` assignment
returns; see docs/playback-fallback.md.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 13:58:55 -04:00

97 lines
5.4 KiB
JSON

{
"name": "bitvid",
"private": true,
"type": "module",
"version": "1.0.0",
"engines": {
"node": ">=22"
},
"scripts": {
"start": "npm run build && npx serve dist",
"prepare": "npm run build:css",
"serve": "npx serve",
"build": "node scripts/build-dist.mjs && npm run verify:dist:deploy-artifact",
"archive": "npm run build && rm -f bitvid-site.zip && (cd dist && zip -qr ../bitvid-site.zip .) && echo 'Wrote bitvid-site.zip (upload to your nsite deployer).'",
"build:css": "postcss css/tailwind.source.css -o css/tailwind.generated.css",
"build:beacon:bundle": "node scripts/build-beacon.mjs",
"build:beacon": "npm run build:beacon:bundle && npm run lint:inline-styles",
"build:bitcoin-connect": "node scripts/build-bitcoin-connect.mjs",
"build:blossom-sdk": "node scripts/build-blossom-sdk.mjs",
"build:hls": "node scripts/build-hls.mjs",
"ci:beacon": "npm run build:beacon",
"format": "prettier --cache --write css/tailwind.source.css css/tokens.css css/docs.css \"docs/**/*.html\" tailwind.config.cjs README.md CONTRIBUTING.md",
"audit": "node scripts/daily-design-system-audit.mjs",
"lint": "npm run lint:css && npm run lint:hex && npm run lint:inline-styles && npm run lint:tokens && npm run lint:tailwind-brackets && npm run lint:tailwind-colors && npm run lint:file-size && npm run lint:innerhtml && npm run lint:assets && npm run lint:sw-compat && npm run lint:pool-access",
"lint:file-size": "node scripts/check-file-size.mjs",
"lint:innerhtml": "node scripts/check-innerhtml.mjs",
"lint:css": "stylelint css/tailwind.source.css css/tokens.css css/docs.css",
"lint:hex": "node scripts/check-hex.js",
"lint:inline-styles": "node scripts/check-inline-styles.mjs",
"lint:tokens": "node scripts/check-design-tokens.mjs --check=tokens",
"lint:tailwind-brackets": "node scripts/check-design-tokens.mjs --check=brackets",
"lint:tailwind-colors": "node scripts/check-tailwind-colors.mjs",
"check:css": "npm run build:css",
"test:visual": "npm run build && npx playwright test --project=visual",
"test:visual:update": "npm run build && cross-env UPDATE_VISUAL_BASELINES=1 npx playwright test --project=visual",
"test:e2e": "npm run build && npx playwright test tests/e2e --project=e2e --project=e2e-firefox",
"test:e2e:chromium": "npm run build && npx playwright test tests/e2e --project=e2e",
"test:e2e:firefox": "npm run build && npx playwright test tests/e2e --project=e2e-firefox",
"test:e2e:deterministic": "npm run build && npx playwright test tests/e2e --project=e2e --project=e2e-firefox",
"test:e2e:coverage": "npm run build && cross-env PLAYWRIGHT_COVERAGE=1 PLAYWRIGHT_CAPTURE_CONSOLE=1 npx playwright test tests/e2e --project=e2e && node scripts/playwright/generate-function-coverage-report.mjs",
"test:e2e:coverage:report": "node scripts/playwright/generate-function-coverage-report.mjs",
"test:e2e:extension": "npm run build && node scripts/playwright/run-extension-persistent.mjs",
"test:load": "node scripts/agent/load-test.mjs",
"test:smoke": "node scripts/agent/smoke-test.mjs",
"test:dm:unit": "node scripts/run-targeted-tests.mjs tests/dm-db.test.mjs tests/dm-decryptor.test.mjs tests/dm-normalization.test.mjs tests/nostr-send-direct-message.test.mjs",
"test:dm:integration": "node scripts/run-targeted-tests.mjs tests/nostr/dm-direct-message-flow.test.mjs",
"test:unit": "node scripts/run-unit-tests.mjs",
"test:unit:shard1": "cross-env UNIT_TEST_SHARD=1/3 node scripts/run-unit-tests.mjs",
"test:unit:shard2": "cross-env UNIT_TEST_SHARD=2/3 node scripts/run-unit-tests.mjs",
"test:unit:shard3": "cross-env UNIT_TEST_SHARD=3/3 node scripts/run-unit-tests.mjs",
"telemetry:aggregate": "node scripts/agent/telemetry-aggregator.mjs",
"lint:assets": "node scripts/check-asset-references.mjs",
"lint:sw-compat": "node scripts/check-sw-compat.mjs",
"hash:dist": "node scripts/hash-dist.mjs --dist dist --output artifacts/dist-hashes-current.json",
"diff:dist-hashes": "node scripts/diff-dist-hashes.mjs",
"verify:dist:deploy-artifact": "node scripts/verify-dist-deploy-artifact.mjs",
"verify:deployed:index-html": "node scripts/verify-deployed-index-html.mjs",
"purge:cloudflare:changed": "node scripts/purge-cloudflare-changed.mjs",
"smoke:dist-assets": "node scripts/check-asset-references.mjs",
"test:e2e:cache-upgrade": "npx playwright test tests/e2e/cache-upgrade-smoke.spec.ts --project=e2e",
"lint:pool-access": "node scripts/check-direct-pool-access.mjs"
},
"devDependencies": {
"@getalby/bitcoin-connect": "3.12.3",
"@playwright/test": "^1.58.2",
"blossom-client-sdk": "5.0.0",
"cssnano": "^7.1.2",
"fake-indexeddb": "^6.2.5",
"hls.js": "^1.6.16",
"jsdom": "^28.1.0",
"nostr-wasm": "^0.1.0",
"pixelmatch": "^5.3.0",
"playwright": "^1.58.2",
"pngjs": "^7.0.0",
"postcss-cli": "^11.0.1",
"postcss-import": "^15.1.0",
"prettier": "^3.8.1",
"prettier-plugin-tailwindcss": "^0.6.5",
"serve": "^14.2.5",
"stylelint": "^16.26.1",
"stylelint-config-standard": "^36.0.1",
"vitest": "^4.0.18"
},
"license": "GPL-3.0-or-later",
"dependencies": {
"@floating-ui/dom": "^1.7.4",
"@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.24",
"cross-env": "^10.1.0",
"esbuild": "^0.27.3",
"nostr-tools": "^2.19.4",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",
"ws": "^8.19.0"
}
}