fix(playback): structure HTML5 mpegts errors

This commit is contained in:
4gray committed 2026-08-01 16:20:18 +02:00
1 parent fc8fc4bdae
commit 2e3cde484d
3 files changed
+72 -3

No files matched your search

@@ -7,6 +7,7 @@ import {
classifyMpegTsPlaybackIssue,
classifyUnsupportedHlsManifestCodecs,
createHlsPlaybackEvidence,
createMpegTsPlaybackEvidence,
createPlaybackSourceMetadata,
} from '../playback-diagnostics/playback-diagnostics.util';
@@ -69,12 +70,16 @@ export function emitFatalHlsPlaybackError(
export function emitMpegTsPlaybackError(
url: string,
error: { type: string; details: string; info: unknown },
error: { type: unknown; details: unknown; info: unknown },
emitPlaybackIssue: (issue: PlaybackDiagnostic) => void
): void {
emitPlaybackIssue(
classifyMpegTsPlaybackIssue(
error,
createMpegTsPlaybackEvidence(
error.type,
error.details,
error.info
),
createHtml5SourceMetadata(url, 'video/mp2t')
)
);
@@ -114,6 +114,52 @@ describe('HtmlVideoPlayerComponent shared controls sources', () => {
}
);
it('emits structured HTTP evidence for mpegts.js failures', () => {
mpegTsIsSupported.mockReturnValue(true);
const { component } = renderSharedControls(
HtmlVideoPlayerComponent,
fixtures,
{
channel: {
...TEST_CHANNEL,
url: 'https://example.test/missing.ts',
},
}
);
const issues: unknown[] = [];
component.playbackIssue.subscribe((issue) => issues.push(issue));
const secret = 'html-mpegts-secret';
mpegTsInstances[0].emit(
'error',
'NetworkError',
'HttpStatusCodeInvalid',
{
code: 404,
msg: `Not Found ${secret}`,
url: `https://provider.example/error?token=${secret}`,
}
);
expect(issues).toEqual([
expect.objectContaining({
code: 'network-error',
source: 'mpegts',
sourceUrl: 'https://example.test/missing.ts',
player: 'html5',
httpStatus: 404,
mpegTs: expect.objectContaining({
engineType: 'NetworkError',
engineDetails: 'HttpStatusCodeInvalid',
failure: 'http',
httpStatus: 404,
}),
externalFallbackRecommended: false,
}),
]);
expect(JSON.stringify(issues)).not.toContain(secret);
});
it('owns one MPEG-TS source between media attachment and loading', () => {
mpegTsIsSupported.mockReturnValue(true);
const { component } = renderSharedControls(
@@ -93,10 +93,22 @@ export class MockHls {
}
export class MockMpegTsPlayer {
private readonly listeners = new Map<
string,
Set<(...args: unknown[]) => void>
>();
readonly attachMediaElement = jest.fn(() =>
lifecycle.push('mpegts:attachMedia')
);
readonly on = jest.fn();
readonly on = jest.fn(
(event: string, listener: (...args: unknown[]) => void) => {
const listeners =
this.listeners.get(event) ??
new Set<(...args: unknown[]) => void>();
listeners.add(listener);
this.listeners.set(event, listeners);
}
);
readonly load = jest.fn(() => lifecycle.push('mpegts:load'));
readonly pause = jest.fn();
readonly unload = jest.fn();
@@ -106,6 +118,12 @@ export class MockMpegTsPlayer {
constructor() {
mpegTsInstances.push(this);
}
emit(event: string, ...args: unknown[]): void {
for (const listener of this.listeners.get(event) ?? []) {
listener(...args);
}
}
}
export const mpegTsCreatePlayer = jest.fn(() => new MockMpegTsPlayer());