feat: add home page with resume functionality and recently added VOD

This commit is contained in:
Trevor Mears
2026-01-18 03:08:08 -08:00
parent 052178e395
commit bf111e1fca
14 changed files with 731 additions and 25 deletions
+86 -5
View File
@@ -82,6 +82,9 @@ class WatchPage {
this.nextEpisodeCountdown = 10;
this.nextEpisodeInterval = null;
// Watch history
this.historyInterval = null;
this.init();
}
@@ -198,6 +201,8 @@ class WatchPage {
this.seriesInfo = content.seriesInfo || null;
this.currentSeason = content.currentSeason || null;
this.currentEpisode = content.currentEpisode || null;
this.resumeTime = content.resumeTime || 0;
this.containerExtension = content.containerExtension || 'mp4';
this.returnPage = content.type === 'movie' ? 'movies' : 'series';
// Stop any Live TV playback before starting movie/series
@@ -240,6 +245,9 @@ class WatchPage {
await this.checkFavorite();
// Show overlay initially
this.showOverlay();
// Start watch history tracking
this.startHistoryTracking();
}
/**
@@ -273,7 +281,11 @@ class WatchPage {
const res = await fetch('/api/transcode/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, ...options })
body: JSON.stringify({
url,
seekOffset: this.resumeTime, // Pass resume point to backend
...options
})
});
if (!res.ok) throw new Error('Failed to start session');
const session = await res.json();
@@ -346,7 +358,8 @@ class WatchPage {
if (settings.autoTranscode) {
console.log('[WatchPage] Auto Transcode enabled. Probing stream...');
try {
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}`);
const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset;
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`);
const info = await probeRes.json();
console.log(`[WatchPage] Probe result: video=${info.video}, audio=${info.audio}, compatible=${info.compatible}`);
@@ -360,6 +373,7 @@ class WatchPage {
this.updateTranscodeStatus('transcoding', statusText);
const playlistUrl = await this.startTranscodeSession(url, {
videoMode,
seekOffset: this.resumeTime, // Ensure seekOffset is passed
videoCodec: info.video,
audioCodec: info.audio,
audioChannels: info.audioChannels
@@ -392,7 +406,10 @@ class WatchPage {
if (settings.forceVideoTranscode) {
console.log('[WatchPage] Force Video Transcode enabled. Starting session (encode)...');
this.updateTranscodeStatus('transcoding', 'Transcoding (Video)');
const playlistUrl = await this.startTranscodeSession(url, { videoMode: 'encode' });
const playlistUrl = await this.startTranscodeSession(url, {
videoMode: 'encode',
seekOffset: this.resumeTime
});
this.playHls(playlistUrl);
this.setVolumeFromStorage();
return;
@@ -405,12 +422,17 @@ class WatchPage {
// Probe to get video codec for HEVC tag handling
let videoCodec = 'unknown';
try {
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}`);
const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset;
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`);
const info = await probeRes.json();
videoCodec = info.video;
} catch (e) { console.warn('Probe failed for force audio, assuming h264'); }
const playlistUrl = await this.startTranscodeSession(url, { videoMode: 'copy', videoCodec });
const playlistUrl = await this.startTranscodeSession(url, {
videoMode: 'copy',
videoCodec,
seekOffset: this.resumeTime
});
this.playHls(playlistUrl);
this.setVolumeFromStorage();
return;
@@ -509,6 +531,10 @@ class WatchPage {
}
stop() {
// Stop history tracking and save final progress
this.stopHistoryTracking();
this.saveProgress();
// Cleanup transcode session if exists
this.stopTranscodeSession();
this.updateTranscodeStatus('hidden');
@@ -631,6 +657,17 @@ class WatchPage {
onMetadataLoaded() {
// Duration is unreliable for transcoded streams, so we don't display it
// Handle resumption
if (this.resumeTime > 0 && this.video) {
const duration = this.video.duration;
// Only resume if not near the end (95%)
if (!duration || this.resumeTime < duration * 0.95) {
console.log(`[WatchPage] Resuming at ${this.resumeTime}s`);
this.video.currentTime = this.resumeTime;
}
this.resumeTime = 0; // Reset after use
}
}
onPlay() {
@@ -1191,6 +1228,50 @@ class WatchPage {
// Don't stop playback here - allow background playback
this.cancelNextEpisode();
}
// ============================================================
// Watch History Tracking
// ============================================================
startHistoryTracking() {
this.stopHistoryTracking(); // Clear existing if any
this.historyInterval = setInterval(() => this.saveProgress(), 10000); // 10s
}
stopHistoryTracking() {
if (this.historyInterval) {
clearInterval(this.historyInterval);
this.historyInterval = null;
}
}
async saveProgress() {
if (!this.content || !this.video || this.video.paused) return;
const progress = Math.floor(this.video.currentTime);
const duration = Math.floor(this.video.duration);
if (isNaN(progress) || isNaN(duration) || duration <= 0) return;
try {
const data = {
title: this.content.title || 'Unknown Title',
subtitle: this.content.subtitle || (this.content.type === 'movie' ? 'Movie' : 'Series'),
poster: this.content.poster,
sourceId: this.content.sourceId,
containerExtension: this.containerExtension
};
await window.API.request('POST', '/history', {
id: this.content.id,
type: this.content.type === 'movie' ? 'movie' : 'episode',
progress,
duration,
data
});
} catch (err) {
console.warn('[History] Failed to save progress:', err);
}
}
}
window.WatchPage = WatchPage;