mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
fix(portals): twenty-fourth review round on the cinematic details
- A forced Xtream episode launch returns its promise to the menu, which reports a rejection; the bookkeeping consumes a settled copy. - Launch bookkeeping is guarded by a page token (series + visit), checked again after the asynchronous save, so neither a return to the same series nor a navigation during the save contaminates another page. - A Stalker launch re-anchors to the host's request id after the host's own inline teardown: only a later start supersedes it, and that still silences the old launch's failure. - The shared movie page disables Play and Start Over while a start is pending, so nothing starts beside an in-flight launch. - Episode sublines honour duration_secs before the formatted duration. - The external session → episode ids derivation moves into the launch helper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
d71a92cec8
commit
d087cf4d1c
15 files changed
+209
-71
No files matched your search
@@ -51,3 +51,13 @@ export function formatEpisodePositionText(
|
||||
.join(':');
|
||||
return `${formatted}${suffix}`;
|
||||
}
|
||||
|
||||
/** The episode runtime: the provider's `duration_secs` first, then its formatted `duration`. */
|
||||
export function episodeRuntimeSeconds(
|
||||
info: { duration_secs?: number; duration?: string } | null | undefined
|
||||
): number {
|
||||
const seconds = Number(info?.duration_secs);
|
||||
return Number.isFinite(seconds) && seconds > 0
|
||||
? Math.floor(seconds)
|
||||
: parseDuration(info?.duration);
|
||||
}
|
||||
@@ -1,8 +1,23 @@
|
||||
import {
|
||||
episodeRuntimeSeconds,
|
||||
formatEpisodePositionText,
|
||||
parseDuration,
|
||||
} from './episode-progress.util';
|
||||
|
||||
describe('episodeRuntimeSeconds', () => {
|
||||
it('prefers the provider seconds over the formatted string', () => {
|
||||
expect(episodeRuntimeSeconds({ duration_secs: 2520 })).toBe(2520);
|
||||
expect(
|
||||
episodeRuntimeSeconds({ duration_secs: 2520, duration: '00:10:00' })
|
||||
).toBe(2520);
|
||||
expect(episodeRuntimeSeconds({ duration: '00:42:00' })).toBe(2520);
|
||||
expect(
|
||||
episodeRuntimeSeconds({ duration_secs: 0, duration: '00:42:00' })
|
||||
).toBe(2520);
|
||||
expect(episodeRuntimeSeconds(undefined)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('episode-progress.util', () => {
|
||||
it('parses duration strings', () => {
|
||||
expect(parseDuration('01:00:30')).toBe(3630);
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
getPortalPlaybackProgressPercent,
|
||||
isPortalPlaybackInProgress,
|
||||
isPortalPlaybackWatched,
|
||||
parseDurationSeconds,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
import {
|
||||
PlaybackPositionData,
|
||||
@@ -39,7 +38,10 @@ import {
|
||||
EpisodeInfoDialogComponent,
|
||||
buildEpisodeInfoDialogData,
|
||||
} from './episode-info-dialog.component';
|
||||
import { formatEpisodePositionText } from './episode-progress.util';
|
||||
import {
|
||||
formatEpisodePositionText,
|
||||
episodeRuntimeSeconds,
|
||||
} from './episode-progress.util';
|
||||
import { resolveAutoSelectedSeason } from './season-auto-select.util';
|
||||
import { SeasonDownloadPresenter } from './season-download-presenter';
|
||||
import { SeasonTabsComponent } from './season-tabs.component';
|
||||
@@ -433,8 +435,7 @@ export class SeasonContainerComponent implements OnInit {
|
||||
/** "42 min · 18m left", "42 min · watched", "42 min" — or null. */
|
||||
getEpisodeSubline(episode: XtreamSerieEpisode): string | null {
|
||||
const info = this.getEpisodeInfo(episode);
|
||||
const seconds = parseDurationSeconds(info?.duration);
|
||||
const duration = formatDurationLabel(seconds);
|
||||
const duration = formatDurationLabel(episodeRuntimeSeconds(info));
|
||||
const position = this.playbackPositions().get(Number(episode.id));
|
||||
const remaining = formatRemainingLabel(position);
|
||||
const parts = [
|
||||
|
||||
Reference in new issue
Block a user