mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
feat(xtream): add catch-up playback to favorites and recent tabs (#1166)
Enables Xtream catch-up/timeshift from the Favorites and Recent surfaces (per-playlist and global), not just Live TV, and adds start-over replay of the currently-airing programme. Carries tv_archive/tv_archive_duration through the favorites and recently-viewed DB projections and maps them onto UnifiedCollectionItem; tv_archive_duration is interpreted as days, matching live-stream-layout.controlledArchiveDays. Closes #1138. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
c266eaa680
commit
2f8aee72df
25 files changed
+459
-48
No files matched your search
@@ -64,10 +64,19 @@ describe('epg-archive.util', () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('denies when the programme is not in the past', () => {
|
||||
it('allows start-over on the currently-airing programme', () => {
|
||||
expect(
|
||||
canCatchUpProgramme('now', inWindowStart, true, 7, NOW)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('denies start-over when archive playback is unavailable', () => {
|
||||
expect(
|
||||
canCatchUpProgramme('now', inWindowStart, false, 7, NOW)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('denies future programmes', () => {
|
||||
expect(
|
||||
canCatchUpProgramme('future', NOW + 60_000, true, 7, NOW)
|
||||
).toBe(false);
|
||||
@@ -91,8 +100,8 @@ describe('epg-archive.util', () => {
|
||||
expect(epgDialogActionFor('now', false)).toBe('live');
|
||||
});
|
||||
|
||||
it('prefers live over timeshift for an on-air programme', () => {
|
||||
expect(epgDialogActionFor('now', true)).toBe('live');
|
||||
it('prefers start-over timeshift for a catch-up-able on-air programme', () => {
|
||||
expect(epgDialogActionFor('now', true)).toBe('timeshift');
|
||||
});
|
||||
|
||||
it('maps a catch-up-able past programme to timeshift', () => {
|
||||
|
||||
@@ -21,7 +21,8 @@ export function isWithinArchiveWindow(
|
||||
return startMs >= nowMs - archiveDays * DAY_MS;
|
||||
}
|
||||
|
||||
/** Whether a past programme is playable from the catch-up archive. */
|
||||
/** Whether a programme is playable from the catch-up archive —
|
||||
* both past programmes and the currently-airing programme (start-over). */
|
||||
export function canCatchUpProgramme(
|
||||
when: TimelineWhen,
|
||||
startMs: number,
|
||||
@@ -31,7 +32,7 @@ export function canCatchUpProgramme(
|
||||
): boolean {
|
||||
return (
|
||||
archivePlaybackAvailable &&
|
||||
when === 'past' &&
|
||||
(when === 'past' || when === 'now') &&
|
||||
isWithinArchiveWindow(startMs, archiveDays, nowMs)
|
||||
);
|
||||
}
|
||||
@@ -41,7 +42,7 @@ export function epgDialogActionFor(
|
||||
when: TimelineWhen,
|
||||
canCatchUp: boolean
|
||||
): EpgItemDialogAction | null {
|
||||
if (when === 'now') {
|
||||
if (when === 'now' && !canCatchUp) {
|
||||
return 'live';
|
||||
}
|
||||
if (canCatchUp) {
|
||||
|
||||
@@ -109,7 +109,7 @@ function toRenderBlock(
|
||||
nowFillPercent: nowFillFor(block, nowMs),
|
||||
canCatchUp:
|
||||
archivePlaybackAvailable &&
|
||||
block.when === 'past' &&
|
||||
(block.when === 'past' || block.when === 'now') &&
|
||||
block.startMs >= archiveWindowStartMs,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,6 +80,15 @@
|
||||
<span class="epg-timeline__tag is-now">{{
|
||||
'EPG.TIMELINE.ON_NOW' | translate
|
||||
}}</span>
|
||||
@if (item.canCatchUp) {
|
||||
<button
|
||||
type="button"
|
||||
class="epg-timeline__watch"
|
||||
(click)="watchClick.emit(item.block); $event.stopPropagation()"
|
||||
>
|
||||
<mat-icon>replay</mat-icon>
|
||||
</button>
|
||||
}
|
||||
} @else if (isPlaying(item)) {
|
||||
<span class="epg-timeline__tag is-arch">{{
|
||||
'EPG.TIMELINE.FROM_ARCHIVE' | translate
|
||||
|
||||
@@ -167,6 +167,9 @@ $font-mono: var(--font-mono, ui-monospace, 'SF Mono', Menlo, monospace);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.epg-timeline__block-time {
|
||||
|
||||
@@ -316,15 +316,15 @@ export class EpgTimelineComponent {
|
||||
|
||||
onBlockClick(block: TimelineBlock): void {
|
||||
this.selectedKey.set(block.key);
|
||||
if (block.when === 'now') {
|
||||
this.returnToLive.emit();
|
||||
return;
|
||||
}
|
||||
if (this.canCatchUp(block)) {
|
||||
this.programActivated.emit({
|
||||
program: block.program,
|
||||
type: 'timeshift',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (block.when === 'now') {
|
||||
this.returnToLive.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user