mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(playback): tighten browser access diagnostics
Entire-Checkpoint: f957cd9849e0
This commit is contained in:
1 parent
d71d21f4d1
commit
48f16c7148
5 files changed
+129
-15
No files matched your search
@@ -279,6 +279,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
details: data.details,
|
||||
fatal: data.fatal,
|
||||
message: data.error?.message,
|
||||
error: data.error,
|
||||
},
|
||||
this.createSourceMetadata(url, 'application/x-mpegURL')
|
||||
)
|
||||
|
||||
@@ -109,4 +109,36 @@ describe('HtmlVideoPlayerComponent', () => {
|
||||
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps raw HLS error object context in emitted playback issue details', () => {
|
||||
const issues: Array<{ details?: string }> = [];
|
||||
component.playbackIssue.subscribe((issue) => {
|
||||
if (issue) issues.push(issue);
|
||||
});
|
||||
|
||||
(
|
||||
component as unknown as {
|
||||
handleHlsError: (
|
||||
url: string,
|
||||
data: {
|
||||
type: string;
|
||||
details: string;
|
||||
fatal: boolean;
|
||||
error?: unknown;
|
||||
}
|
||||
) => void;
|
||||
}
|
||||
).handleHlsError('https://example.com/live/playlist.m3u8', {
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError',
|
||||
fatal: true,
|
||||
error: {
|
||||
context: 'xhr setup failed',
|
||||
status: 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(issues[0].details).toContain('xhr setup failed');
|
||||
expect(issues[0].details).toContain('"status":0');
|
||||
});
|
||||
});
|
||||
@@ -288,6 +288,7 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
details: data.details,
|
||||
fatal: data.fatal,
|
||||
message: data.error?.message,
|
||||
error: data.error,
|
||||
},
|
||||
this.createSourceMetadata(url, 'application/x-mpegURL')
|
||||
)
|
||||
|
||||
@@ -74,6 +74,20 @@ describe('playback diagnostics', () => {
|
||||
expect(unsupportedSourceIssue.externalFallbackRecommended).toBe(true);
|
||||
});
|
||||
|
||||
it('classifies MIME-only unsupported containers as container diagnostics', () => {
|
||||
const issue = classifyNativePlaybackIssue(
|
||||
{ code: 4, message: 'source not supported' },
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://example.com/archive/stream',
|
||||
mimeType: 'video/x-msvideo',
|
||||
player: 'html5',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.UnsupportedContainer);
|
||||
expect(issue.container).toBe('x-msvideo');
|
||||
});
|
||||
|
||||
it('classifies HLS network errors without claiming codec incompatibility', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
@@ -91,6 +105,44 @@ describe('playback diagnostics', () => {
|
||||
expect(issue.externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('does not treat provider-side blocked messages as browser access errors', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError Request blocked by rate limiter',
|
||||
fatal: true,
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live/index.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.code).toBe(PlaybackDiagnosticCode.NetworkError);
|
||||
expect(issue.externalFallbackRecommended).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps raw HLS error object context in diagnostic details', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
type: 'networkError',
|
||||
details: 'manifestLoadError',
|
||||
fatal: true,
|
||||
error: {
|
||||
context: 'xhr setup failed',
|
||||
status: 0,
|
||||
},
|
||||
},
|
||||
createPlaybackSourceMetadata({
|
||||
url: 'https://provider.example/live/index.m3u8',
|
||||
player: 'videojs',
|
||||
})
|
||||
);
|
||||
|
||||
expect(issue.details).toContain('xhr setup failed');
|
||||
expect(issue.details).toContain('"status":0');
|
||||
});
|
||||
|
||||
it('classifies HLS browser access blocks separately from provider network failures', () => {
|
||||
const issue = classifyHlsPlaybackIssue(
|
||||
{
|
||||
|
||||
@@ -115,6 +115,15 @@ const UNSUPPORTED_CONTAINER_EXTENSIONS = new Set([
|
||||
'wmv',
|
||||
]);
|
||||
|
||||
const UNSUPPORTED_CONTAINER_NAMES = new Set([
|
||||
...UNSUPPORTED_CONTAINER_EXTENSIONS,
|
||||
'matroska',
|
||||
'mp2t',
|
||||
'quicktime',
|
||||
'x-matroska',
|
||||
'x-msvideo',
|
||||
]);
|
||||
|
||||
const DECLARED_MEDIA_EXTENSION_QUERY_KEYS = [
|
||||
'extension',
|
||||
'ext',
|
||||
@@ -525,7 +534,8 @@ function inferContainerFromMimeType(mimeType: string | undefined): string {
|
||||
|
||||
function isLikelyContainerIssue(metadata: PlaybackSourceMetadata): boolean {
|
||||
return (
|
||||
UNSUPPORTED_CONTAINER_EXTENSIONS.has(metadata.extension) ||
|
||||
UNSUPPORTED_CONTAINER_NAMES.has(metadata.extension) ||
|
||||
UNSUPPORTED_CONTAINER_NAMES.has(metadata.container) ||
|
||||
metadata.mimeType === 'video/matroska'
|
||||
);
|
||||
}
|
||||
@@ -535,24 +545,41 @@ function normalizeErrorDetails(
|
||||
): string {
|
||||
const hlsError = 'error' in error ? error.error : undefined;
|
||||
const mpegTsInfo = 'info' in error ? error.info : undefined;
|
||||
const errorMessage =
|
||||
hlsError instanceof Error
|
||||
? hlsError.message
|
||||
: typeof hlsError === 'string'
|
||||
? hlsError
|
||||
: '';
|
||||
const info =
|
||||
typeof mpegTsInfo === 'string'
|
||||
? mpegTsInfo
|
||||
: mpegTsInfo
|
||||
? JSON.stringify(mpegTsInfo)
|
||||
: '';
|
||||
const errorMessage = normalizeErrorPayload(hlsError);
|
||||
const info = normalizeErrorPayload(mpegTsInfo);
|
||||
|
||||
return [error.details, error.message, errorMessage, info]
|
||||
.filter((part): part is string => Boolean(part))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function normalizeErrorPayload(payload: unknown): string {
|
||||
if (!payload) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof payload === 'string') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (payload instanceof Error) {
|
||||
const extraDetails = stringifyUnknown(payload);
|
||||
return [payload.message, extraDetails === '{}' ? '' : extraDetails]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
return stringifyUnknown(payload);
|
||||
}
|
||||
|
||||
function stringifyUnknown(value: unknown): string {
|
||||
try {
|
||||
return JSON.stringify(value) || '';
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function isNetworkFailure(type: string, details: string): boolean {
|
||||
return (
|
||||
type.includes('network') ||
|
||||
@@ -573,8 +600,9 @@ function isBrowserAccessFailure(details: string): boolean {
|
||||
details.includes('content security policy') ||
|
||||
details.includes('mixed content') ||
|
||||
details.includes('private network access') ||
|
||||
details.includes('blocked by') ||
|
||||
details.includes('has been blocked') ||
|
||||
details.includes('blocked by content security') ||
|
||||
details.includes('blocked by cors') ||
|
||||
details.includes('blocked by mixed content') ||
|
||||
details.includes('not allowed to load local resource') ||
|
||||
details.includes('err_blocked') ||
|
||||
details.includes('err_cleartext')
|
||||
|
||||
Reference in new issue
Block a user