diff --git a/package.json b/package.json index 7fb9ab6..27e5103 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@profullstack/player", - "version": "0.2.0", + "version": "0.3.0", "description": "One web player for every source a Profullstack site serves: MP4, HLS, MPEG-2 transport streams and audio, with one control bar, on desktop, mobile, PWA and television.", "keywords": [ "video", @@ -32,6 +32,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./codecs": { + "types": "./dist/codecs.d.ts", + "default": "./dist/codecs.js" + }, "./react": { "types": "./dist/react/index.d.ts", "default": "./dist/react/index.js" diff --git a/src/codecs.ts b/src/codecs.ts new file mode 100644 index 0000000..2904632 --- /dev/null +++ b/src/codecs.ts @@ -0,0 +1,16 @@ +/** + * The codec table on its own, with no player attached. + * + * genrewatch and tipoffwatch each carry a copy of this file. It is the most + * expensive 150 lines in either repo -- every rule in it was written in + * response to a channel that failed in production, and two of them were got + * wrong twice -- and three copies of it is exactly the thing this package + * exists to stop. + * + * A subpath rather than the root export, because those two bundle their client + * by hand and are careful about its weight: importing from the root would pull + * the player and its dynamic engine imports into a bundle that wants a lookup + * table and nothing else. + */ + +export { codecName, mseCandidates, unplayableReason, type MediaInfoLike } from './engines/codecs'; diff --git a/src/engines/codecs.ts b/src/engines/codecs.ts index 4c8532e..e43746d 100644 --- a/src/engines/codecs.ts +++ b/src/engines/codecs.ts @@ -119,11 +119,16 @@ export function mseCandidates(kind: 'video' | 'audio', codec: string | undefined * is testable without a browser. * @param advice appended when there is a problem — the host site's suggestion * for what to do instead, which this package cannot know. + * @param subject what the thing is called on the host site. genrewatch and + * tipoffwatch say "channel" because that is what their reader clicked; a + * recording is a "stream" at best. Naming it wrongly is a small thing that + * makes shared copy read as though it came from somewhere else. */ export function unplayableReason( info: MediaInfoLike | null | undefined, isTypeSupported: (type: string) => boolean, - advice = '' + advice = '', + subject = 'stream' ): string | null { if (!info || typeof isTypeSupported !== 'function') return null; @@ -158,5 +163,5 @@ export function unplayableReason( // Both halves unplayable is one sentence, not two: the reader is going // elsewhere either way, and listing two problems reads as two things to fix. const what = bad.length === 1 ? bad[0] : `${String(bad[0])} and ${String(bad[1])}`; - return `This stream is ${String(what)}, which this browser cannot decode.${advice ? ` ${advice}` : ''}`; + return `This ${subject} is ${String(what)}, which this browser cannot decode.${advice ? ` ${advice}` : ''}`; } diff --git a/test/codecs.test.ts b/test/codecs.test.ts index 09a3fd8..5715db2 100644 --- a/test/codecs.test.ts +++ b/test/codecs.test.ts @@ -84,6 +84,13 @@ describe('unplayableReason', () => { ); }); + it('lets the host name the thing its reader clicked', () => { + // genrewatch and tipoffwatch say "channel"; a recording is a "stream". + expect(unplayableReason({ videoCodec: 'hvc1.1' }, supports([]), '', 'channel')).toBe( + 'This channel is H.265, which this browser cannot decode.' + ); + }); + it('appends the host site’s advice when given some', () => { const reason = unplayableReason({ videoCodec: 'hvc1.1' }, supports([]), 'Try VLC.'); expect(reason).toBe('This stream is H.265, which this browser cannot decode. Try VLC.'); diff --git a/tsup.config.ts b/tsup.config.ts index 17bf121..2e7130b 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -14,7 +14,7 @@ import { defineConfig } from 'tsup'; * anyone who already has one. */ export default defineConfig({ - entry: ['src/index.ts', 'src/react/index.tsx'], + entry: ['src/index.ts', 'src/react/index.tsx', 'src/codecs.ts'], format: ['esm'], target: 'es2022', splitting: true,