mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(playback): preserve headers in diagnostics wrapper
This commit is contained in:
1 parent
d7f914bc57
commit
2ae62f5a12
4 files changed
+150
-14
No files matched your search
@@ -37,6 +37,7 @@ type VideoPlayerSource = {
|
||||
type VideoPlayerOptions = Record<string, unknown> & {
|
||||
autoplay?: boolean;
|
||||
isLive?: boolean;
|
||||
reloadToken?: number;
|
||||
sources?: VideoPlayerSource[];
|
||||
};
|
||||
|
||||
@@ -235,7 +236,14 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
const previousSource =
|
||||
changes['options'].previousValue.sources?.[0];
|
||||
const newSource = changes['options'].currentValue.sources?.[0];
|
||||
if (this.hasSourceChanged(previousSource, newSource)) {
|
||||
if (
|
||||
this.hasPlaybackInputChanged(
|
||||
changes['options'].previousValue,
|
||||
changes['options'].currentValue,
|
||||
previousSource,
|
||||
newSource
|
||||
)
|
||||
) {
|
||||
this.playbackIssue.emit(null);
|
||||
this.destroyMpegTs();
|
||||
if (!newSource) {
|
||||
@@ -274,11 +282,14 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return (extension === 'ts' || !extension) && mpegts.isSupported();
|
||||
}
|
||||
|
||||
private hasSourceChanged(
|
||||
private hasPlaybackInputChanged(
|
||||
previousOptions: VideoPlayerOptions,
|
||||
newOptions: VideoPlayerOptions,
|
||||
previousSource: VideoPlayerSource | undefined,
|
||||
newSource: VideoPlayerSource | undefined
|
||||
): boolean {
|
||||
return (
|
||||
previousOptions.reloadToken !== newOptions.reloadToken ||
|
||||
previousSource?.src !== newSource?.src ||
|
||||
previousSource?.type !== newSource?.type
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
|
||||
<h2 class="web-player-diagnostic__headline">
|
||||
{{ getDiagnosticHeadlineKey() | translate }}
|
||||
{{ diagnosticHeadlineKey() | translate }}
|
||||
</h2>
|
||||
|
||||
<div class="web-player-diagnostic__description">
|
||||
|
||||
@@ -234,6 +234,69 @@ describe('WebPlayerViewComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves playback HTTP metadata for channel-based players', () => {
|
||||
const streamUrl = 'https://example.com/live/channel.m3u8';
|
||||
fixture.componentRef.setInput('playerOverride', VideoPlayer.Html5Player);
|
||||
fixture.componentRef.setInput('playback', {
|
||||
streamUrl,
|
||||
title: 'Header Locked Channel',
|
||||
userAgent: 'ProviderAgent/1.0',
|
||||
referer: 'https://provider.example/ref',
|
||||
origin: 'https://provider.example',
|
||||
headers: {
|
||||
'User-Agent': 'IgnoredFallbackAgent/1.0',
|
||||
Referer: 'https://ignored.example/ref',
|
||||
Origin: 'https://ignored.example',
|
||||
},
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const player = fixture.debugElement.query(
|
||||
By.directive(StubHtmlVideoPlayerComponent)
|
||||
).componentInstance as StubHtmlVideoPlayerComponent;
|
||||
expect(player.channel()).toEqual(
|
||||
expect.objectContaining({
|
||||
url: streamUrl,
|
||||
name: 'Header Locked Channel',
|
||||
http: {
|
||||
'user-agent': 'ProviderAgent/1.0',
|
||||
referrer: 'https://provider.example/ref',
|
||||
origin: 'https://provider.example',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to playback headers when explicit HTTP metadata is absent', () => {
|
||||
const streamUrl = 'https://example.com/live/channel.m3u8';
|
||||
fixture.componentRef.setInput('playerOverride', VideoPlayer.Html5Player);
|
||||
fixture.componentRef.setInput('playback', {
|
||||
streamUrl,
|
||||
title: 'Header Fallback Channel',
|
||||
headers: {
|
||||
'user-agent': 'HeaderAgent/1.0',
|
||||
referer: 'https://headers.example/ref',
|
||||
origin: 'https://headers.example',
|
||||
},
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const player = fixture.debugElement.query(
|
||||
By.directive(StubHtmlVideoPlayerComponent)
|
||||
).componentInstance as StubHtmlVideoPlayerComponent;
|
||||
expect(player.channel()).toEqual(
|
||||
expect.objectContaining({
|
||||
http: {
|
||||
'user-agent': 'HeaderAgent/1.0',
|
||||
referrer: 'https://headers.example/ref',
|
||||
origin: 'https://headers.example',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('uses browser access diagnostic translation keys', () => {
|
||||
const issue = createBrowserAccessDiagnostic();
|
||||
|
||||
@@ -311,6 +374,13 @@ describe('WebPlayerViewComponent', () => {
|
||||
|
||||
it('clears playback diagnostics when retrying inline playback', () => {
|
||||
fixture.detectChanges();
|
||||
const player = fixture.debugElement.query(
|
||||
By.directive(StubVjsPlayerComponent)
|
||||
).componentInstance as StubVjsPlayerComponent;
|
||||
expect(player.options()).toEqual(
|
||||
expect.objectContaining({ reloadToken: 0 })
|
||||
);
|
||||
|
||||
component.handlePlaybackIssue(createUnsupportedCodecDiagnostic());
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -327,6 +397,9 @@ describe('WebPlayerViewComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.playbackDiagnostic()).toBeNull();
|
||||
expect(player.options()).toEqual(
|
||||
expect.objectContaining({ reloadToken: 1 })
|
||||
);
|
||||
expect(
|
||||
fixture.debugElement.query(
|
||||
By.css('[data-test-id="playback-diagnostic-banner"]')
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
@@ -17,6 +18,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
Channel,
|
||||
ResolvedPortalPlayback,
|
||||
Settings,
|
||||
STORE_KEY,
|
||||
@@ -80,19 +82,26 @@ export class WebPlayerViewComponent {
|
||||
this.storage.get(STORE_KEY.Settings)
|
||||
) as Signal<Settings>;
|
||||
|
||||
channel!: { url: string };
|
||||
channel!: Channel;
|
||||
player!: VideoPlayer;
|
||||
vjsOptions!: {
|
||||
isLive: boolean;
|
||||
reloadToken: number;
|
||||
sources: { src: string; type: string }[];
|
||||
};
|
||||
readonly isDesktop = signal(this.detectDesktop());
|
||||
readonly reloadToken = signal(0);
|
||||
readonly playbackDiagnostic = signal<PlaybackDiagnostic | null>(null);
|
||||
readonly canShowExternalFallbackActions = computed(
|
||||
() =>
|
||||
this.isDesktop() &&
|
||||
!!this.playbackDiagnostic()?.externalFallbackRecommended
|
||||
);
|
||||
readonly diagnosticHeadlineKey = computed(() =>
|
||||
this.canShowExternalFallbackActions()
|
||||
? 'PLAYBACK_DIAGNOSTICS.NATIVE_FALLBACK_TITLE'
|
||||
: 'PLAYBACK_DIAGNOSTICS.INLINE_FAILURE_TITLE'
|
||||
);
|
||||
|
||||
readonly resolvedPlayback = computed<ResolvedPortalPlayback>(() => {
|
||||
const playback = this.playback();
|
||||
@@ -119,7 +128,7 @@ export class WebPlayerViewComponent {
|
||||
|
||||
const playback = this.resolvedPlayback();
|
||||
this.playbackDiagnostic.set(null);
|
||||
this.setChannel(playback.streamUrl);
|
||||
this.setChannel(playback);
|
||||
this.setVjsOptions(
|
||||
playback.streamUrl,
|
||||
this.isLivePlayback(playback)
|
||||
@@ -138,13 +147,47 @@ export class WebPlayerViewComponent {
|
||||
|
||||
this.vjsOptions = {
|
||||
isLive,
|
||||
reloadToken: untracked(() => this.reloadToken()),
|
||||
sources: [{ src: streamUrl, type: mimeType }],
|
||||
};
|
||||
}
|
||||
|
||||
setChannel(streamUrl: string) {
|
||||
setChannel(playbackOrUrl: ResolvedPortalPlayback | string) {
|
||||
const playback =
|
||||
typeof playbackOrUrl === 'string'
|
||||
? {
|
||||
streamUrl: playbackOrUrl,
|
||||
title: playbackOrUrl,
|
||||
}
|
||||
: playbackOrUrl;
|
||||
|
||||
this.channel = {
|
||||
url: streamUrl,
|
||||
id: playback.streamUrl,
|
||||
url: playback.streamUrl,
|
||||
name: playback.title || playback.streamUrl,
|
||||
group: { title: '' },
|
||||
tvg: {
|
||||
id: '',
|
||||
name: playback.title || playback.streamUrl,
|
||||
url: '',
|
||||
logo: playback.thumbnail ?? '',
|
||||
rec: '',
|
||||
},
|
||||
http: {
|
||||
referrer:
|
||||
playback.referer ??
|
||||
this.getHeaderValue(playback.headers, 'Referer') ??
|
||||
'',
|
||||
'user-agent':
|
||||
playback.userAgent ??
|
||||
this.getHeaderValue(playback.headers, 'User-Agent') ??
|
||||
'',
|
||||
origin:
|
||||
playback.origin ??
|
||||
this.getHeaderValue(playback.headers, 'Origin') ??
|
||||
'',
|
||||
},
|
||||
radio: 'false',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,7 +212,8 @@ export class WebPlayerViewComponent {
|
||||
const playback = this.resolvedPlayback();
|
||||
|
||||
this.playbackDiagnostic.set(null);
|
||||
this.setChannel(playback.streamUrl);
|
||||
this.reloadToken.update((value) => value + 1);
|
||||
this.setChannel(playback);
|
||||
this.setVjsOptions(playback.streamUrl, this.isLivePlayback(playback));
|
||||
}
|
||||
|
||||
@@ -181,12 +225,6 @@ export class WebPlayerViewComponent {
|
||||
return `${this.getDiagnosticTranslationBase(issue)}.DESCRIPTION`;
|
||||
}
|
||||
|
||||
getDiagnosticHeadlineKey(): string {
|
||||
return this.canShowExternalFallbackActions()
|
||||
? 'PLAYBACK_DIAGNOSTICS.NATIVE_FALLBACK_TITLE'
|
||||
: 'PLAYBACK_DIAGNOSTICS.INLINE_FAILURE_TITLE';
|
||||
}
|
||||
|
||||
getDiagnosticMeta(issue: PlaybackDiagnostic): string {
|
||||
const codecs = [...issue.videoCodecs, ...issue.audioCodecs].join(', ');
|
||||
if (codecs) {
|
||||
@@ -279,6 +317,20 @@ export class WebPlayerViewComponent {
|
||||
return !playback.contentInfo;
|
||||
}
|
||||
|
||||
private getHeaderValue(
|
||||
headers: ResolvedPortalPlayback['headers'] | undefined,
|
||||
name: string
|
||||
): string | undefined {
|
||||
if (!headers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const matchingKey = Object.keys(headers).find(
|
||||
(key) => key.toLowerCase() === name.toLowerCase()
|
||||
);
|
||||
return matchingKey ? headers[matchingKey] : undefined;
|
||||
}
|
||||
|
||||
private formatPlayer(player: PlaybackDiagnostic['player']): string {
|
||||
switch (player) {
|
||||
case 'videojs':
|
||||
|
||||
Reference in new issue
Block a user