mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
fix(playback): share mpegts evidence across players
This commit is contained in:
1 parent
2e3cde484d
commit
dfd7ac84d4
5 files changed
+77
-25
No files matched your search
@@ -186,10 +186,11 @@ describe('ArtPlayerSourceSession', () => {
|
||||
details: 'stale callback',
|
||||
fatal: true,
|
||||
});
|
||||
const secret = 'art-mpegts-secret';
|
||||
mpegTsInstances.at(-1)?.handlers.get('error')?.(
|
||||
'mediaError',
|
||||
'unsupported codec',
|
||||
{}
|
||||
'MediaError',
|
||||
'CodecUnsupported',
|
||||
{ message: secret, headers: { Authorization: secret } }
|
||||
);
|
||||
|
||||
expect(emitted).toEqual([
|
||||
@@ -199,11 +200,18 @@ describe('ArtPlayerSourceSession', () => {
|
||||
player: 'artplayer',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: 'unsupported-codec',
|
||||
source: 'mpegts',
|
||||
sourceUrl: 'https://example.test/live.ts',
|
||||
player: 'artplayer',
|
||||
mpegTs: expect.objectContaining({
|
||||
engineType: 'MediaError',
|
||||
engineDetails: 'CodecUnsupported',
|
||||
failure: 'codec',
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
expect(JSON.stringify(emitted)).not.toContain(secret);
|
||||
});
|
||||
|
||||
it('reports only structured evidence for a fatal HLS manifest HTTP failure', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
classifyMpegTsPlaybackIssue,
|
||||
classifyUnsupportedHlsManifestCodecs,
|
||||
createHlsPlaybackEvidence,
|
||||
createMpegTsPlaybackEvidence,
|
||||
createPlaybackSourceMetadata,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
import type { WebVideoControlsAdapter } from '../player-controls';
|
||||
@@ -66,7 +67,7 @@ export class ArtPlayerSourceSession {
|
||||
| null = null;
|
||||
private mpegTsPlayer: mpegts.Player | null = null;
|
||||
private mpegTsErrorListener:
|
||||
| ((type: string, details: string, info: unknown) => void)
|
||||
| ((type: unknown, details: unknown, info: unknown) => void)
|
||||
| null = null;
|
||||
private shakaSession: ShakaVideoSession | null = null;
|
||||
private destroyed = false;
|
||||
@@ -192,7 +193,7 @@ export class ArtPlayerSourceSession {
|
||||
}
|
||||
this.config.emitPlaybackIssue(
|
||||
classifyMpegTsPlaybackIssue(
|
||||
{ type, details, info },
|
||||
createMpegTsPlaybackEvidence(type, details, info),
|
||||
this.createSourceMetadata(url, 'video/mp2t')
|
||||
)
|
||||
);
|
||||
|
||||
@@ -239,10 +239,11 @@ describe('ArtPlayerComponent', () => {
|
||||
artPlayerInstances[0].video,
|
||||
'https://example.com/live/channel.ts'
|
||||
);
|
||||
const secret = 'art-component-mpegts-secret';
|
||||
mpegTsInstances[0].handlers.get('error')?.(
|
||||
'mediaError',
|
||||
'unsupported codec',
|
||||
{}
|
||||
'MediaError',
|
||||
'CodecUnsupported',
|
||||
{ message: secret }
|
||||
);
|
||||
|
||||
expect(issues).toEqual([
|
||||
@@ -250,9 +251,15 @@ describe('ArtPlayerComponent', () => {
|
||||
code: 'unsupported-codec',
|
||||
source: 'mpegts',
|
||||
sourceUrl: 'https://example.com/live/channel.ts',
|
||||
mpegTs: expect.objectContaining({
|
||||
engineType: 'MediaError',
|
||||
engineDetails: 'CodecUnsupported',
|
||||
failure: 'codec',
|
||||
}),
|
||||
externalFallbackRecommended: true,
|
||||
}),
|
||||
]);
|
||||
expect(JSON.stringify(issues)).not.toContain(secret);
|
||||
});
|
||||
|
||||
it('emits playbackEnded exactly once for a native ended event and not during reload or destroy', () => {
|
||||
|
||||
@@ -111,28 +111,68 @@ describe('VjsMpegTsSession', () => {
|
||||
expect(duration).toHaveBeenCalledWith(200);
|
||||
});
|
||||
|
||||
it('classifies engine errors with Video.js source metadata', () => {
|
||||
it('emits structured HTTP evidence with Video.js source metadata', () => {
|
||||
const mpegTsPlayer = createMpegTsPlayer();
|
||||
createPlayerMock.mockReturnValue(mpegTsPlayer);
|
||||
const video = document.createElement('video');
|
||||
const { session, emitPlaybackIssue } = createSession();
|
||||
const secret = 'vjs-mpegts-secret';
|
||||
|
||||
session.start('https://example.test/live/stream.ts', video);
|
||||
mpegTsPlayer.emit(
|
||||
'error',
|
||||
'NetworkError',
|
||||
'HttpStatusCodeInvalid',
|
||||
{
|
||||
code: 503,
|
||||
msg: `Service Unavailable ${secret}`,
|
||||
headers: { Authorization: secret },
|
||||
}
|
||||
);
|
||||
|
||||
expect(emitPlaybackIssue).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: 'network-error',
|
||||
source: 'mpegts',
|
||||
sourceUrl: 'https://example.test/live/stream.ts',
|
||||
player: 'videojs',
|
||||
httpStatus: 503,
|
||||
mpegTs: expect.objectContaining({
|
||||
engineType: 'NetworkError',
|
||||
engineDetails: 'HttpStatusCodeInvalid',
|
||||
failure: 'http',
|
||||
httpStatus: 503,
|
||||
}),
|
||||
externalFallbackRecommended: false,
|
||||
})
|
||||
);
|
||||
expect(JSON.stringify(emitPlaybackIssue.mock.calls)).not.toContain(
|
||||
secret
|
||||
);
|
||||
});
|
||||
|
||||
it('does not guess browser access from a generic mpegts exception', () => {
|
||||
const mpegTsPlayer = createMpegTsPlayer();
|
||||
createPlayerMock.mockReturnValue(mpegTsPlayer);
|
||||
const video = document.createElement('video');
|
||||
const { session, emitPlaybackIssue } = createSession();
|
||||
|
||||
session.start('https://example.test/live/stream.ts', video);
|
||||
mpegTsPlayer.emit(
|
||||
'error',
|
||||
'NetworkError',
|
||||
'FetchError',
|
||||
new Error('CORS blocked')
|
||||
);
|
||||
mpegTsPlayer.emit('error', 'NetworkError', 'Exception', {
|
||||
code: -1,
|
||||
msg: 'blocked by CORS policy',
|
||||
});
|
||||
|
||||
expect(emitPlaybackIssue).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: 'browser-access-error',
|
||||
source: 'mpegts',
|
||||
sourceUrl: 'https://example.test/live/stream.ts',
|
||||
player: 'videojs',
|
||||
code: 'network-error',
|
||||
mpegTs: expect.objectContaining({ failure: 'network' }),
|
||||
externalFallbackRecommended: false,
|
||||
})
|
||||
);
|
||||
expect(JSON.stringify(emitPlaybackIssue.mock.calls)).not.toContain(
|
||||
'CORS'
|
||||
);
|
||||
});
|
||||
|
||||
it('replaces an active session and removes listeners from the old video', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
InlinePlaybackPlayer,
|
||||
type PlaybackDiagnostic,
|
||||
classifyMpegTsPlaybackIssue,
|
||||
createMpegTsPlaybackEvidence,
|
||||
createPlaybackSourceMetadata,
|
||||
getPlaybackMediaExtensionFromUrl,
|
||||
} from '../playback-diagnostics/playback-diagnostics.util';
|
||||
@@ -59,12 +60,7 @@ export class VjsMpegTsSession {
|
||||
this.syncDuration();
|
||||
this.config.emitPlaybackIssue(
|
||||
classifyMpegTsPlaybackIssue(
|
||||
{
|
||||
type: typeof type === 'string' ? type : undefined,
|
||||
details:
|
||||
typeof details === 'string' ? details : undefined,
|
||||
info,
|
||||
},
|
||||
createMpegTsPlaybackEvidence(type, details, info),
|
||||
createPlaybackSourceMetadata({
|
||||
url,
|
||||
mimeType: 'video/mp2t',
|
||||
|
||||
Reference in new issue
Block a user