improved preloading

This commit is contained in:
Dmitri 2026-04-09 07:22:18 +04:00
parent d98ab24f6e
commit 42cc3456eb
3 changed files with 31 additions and 20 deletions

View File

@ -20,10 +20,10 @@ export const PRELOAD_CONFIG = {
currentPage: 1000, currentPage: 1000,
neighborBase: 500, neighborBase: 500,
assetType: { assetType: {
transition: 150, // Transitions preloaded for faster start
image: 100, // Backgrounds load first image: 100, // Backgrounds load first
audio: 50, audio: 50,
video: 30, video: 30,
// Note: transitions are cached on first playback, not preloaded
} as Record<string, number>, } as Record<string, number>,
variant: { variant: {
thumbnail: 50, thumbnail: 50,
@ -68,11 +68,11 @@ export const PRELOAD_CONFIG = {
// Partial preload settings (online mode only) // Partial preload settings (online mode only)
// Download only first N bytes of videos/audio for faster Phase 1 completion // Download only first N bytes of videos/audio for faster Phase 1 completion
// Playback uses presigned URL directly (browser handles remaining buffering) // Playback uses presigned URL directly (browser handles remaining buffering)
// Note: Transitions are cached on first playback, not preloaded
partialPreload: { partialPreload: {
enabled: true, enabled: true,
videoMaxBytes: 5 * 1024 * 1024, // 5MB (~5 seconds of video) videoMaxBytes: 5 * 1024 * 1024, // 5MB (~5 seconds of video)
audioMaxBytes: 512 * 1024, // 512KB (~5 seconds of audio) audioMaxBytes: 512 * 1024, // 512KB (~5 seconds of audio)
transitionMaxBytes: 3 * 1024 * 1024, // 3MB (~3 seconds of transition video)
}, },
// Asset URL field names in element content_json (camelCase) // Asset URL field names in element content_json (camelCase)

View File

@ -74,14 +74,12 @@ function extractAssetsFromContent(
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string' && value && urlFields.includes(key)) { if (typeof value === 'string' && value && urlFields.includes(key)) {
// Classify asset type based on field name // Classify asset type based on field name
// Skip transition fields - transitions are cached on first playback
const lowerKey = key.toLowerCase(); const lowerKey = key.toLowerCase();
if (lowerKey.includes('transition')) {
continue; // Skip transitions
}
let assetType: 'video' | 'audio' | 'image'; let assetType: 'video' | 'audio' | 'image' | 'transition';
if (lowerKey.includes('video')) { if (lowerKey.includes('transition')) {
assetType = 'transition';
} else if (lowerKey.includes('video')) {
assetType = 'video'; assetType = 'video';
} else if (lowerKey.includes('audio')) { } else if (lowerKey.includes('audio')) {
assetType = 'audio'; assetType = 'audio';
@ -239,14 +237,25 @@ export function useNeighborGraph(
}); });
}); });
// Add transition videos (transition is eagerly loaded in page_links) // Extract transition videos from page_links for preloading
const matchingLinks = pageLinks.filter( const matchingLinks = pageLinks.filter(
(link) => (link) =>
link.is_active !== false && pageIds.includes(link.from_pageId || ''), link.is_active !== false && pageIds.includes(link.from_pageId || ''),
); );
// Note: Transition videos are NOT extracted for preloading. matchingLinks.forEach((link) => {
// They are cached on first playback via useTransitionPlayback.cacheBlob() // Extract transition video URL from link.transition
const transition = link.transition as { video_url?: string } | undefined;
if (transition?.video_url && !seenUrls.has(transition.video_url)) {
seenUrls.add(transition.video_url);
assets.push({
url: transition.video_url,
pageId: link.from_pageId || '',
assetType: 'transition',
priority: 0, // Will be calculated later with transition priority
});
}
});
return assets; return assets;
}; };

View File

@ -529,12 +529,18 @@ export function usePreloadOrchestrator(
) => { ) => {
// Helper to determine max bytes for partial preload (online mode only) // Helper to determine max bytes for partial preload (online mode only)
// IMPORTANT: Only applies to NEIGHBOR pages, not the current page // IMPORTANT: Only applies to NEIGHBOR pages, not the current page
// Transitions always use partial preload (regardless of page)
const getMaxBytesForAsset = ( const getMaxBytesForAsset = (
assetType: 'image' | 'video' | 'audio' | 'transition' | 'other', assetType: 'image' | 'video' | 'audio' | 'transition' | 'other',
isNeighborPage: boolean, isNeighborPage: boolean,
): number | undefined => { ): number | undefined => {
if (!PRELOAD_CONFIG.partialPreload.enabled) return undefined; if (!PRELOAD_CONFIG.partialPreload.enabled) return undefined;
// Transitions always use partial preload - they need just enough to start quickly
if (assetType === 'transition') {
return PRELOAD_CONFIG.partialPreload.transitionMaxBytes;
}
// Current page assets should be fully downloaded for best UX // Current page assets should be fully downloaded for best UX
if (!isNeighborPage) return undefined; if (!isNeighborPage) return undefined;
@ -557,11 +563,6 @@ export function usePreloadOrchestrator(
assetType: 'image' | 'video' | 'audio' | 'transition' | 'other', assetType: 'image' | 'video' | 'audio' | 'transition' | 'other',
pageId: string, pageId: string,
): Promise<void> | null => { ): Promise<void> | null => {
// Skip transitions - they're cached on first playback via useTransitionPlayback
if (assetType === 'transition') {
return null;
}
const resolvedUrl = resolveUrl(storageKey, presignedUrls); const resolvedUrl = resolveUrl(storageKey, presignedUrls);
if (!resolvedUrl) return null; if (!resolvedUrl) return null;
@ -577,8 +578,9 @@ export function usePreloadOrchestrator(
// Determine if partial preload applies (neighbor pages only, media files only) // Determine if partial preload applies (neighbor pages only, media files only)
const isNeighborPage = pageId !== currentPageId; const isNeighborPage = pageId !== currentPageId;
const maxBytes = getMaxBytesForAsset(assetType, isNeighborPage); const maxBytes = getMaxBytesForAsset(assetType, isNeighborPage);
// For partial downloads, don't create blob URL - playback uses presigned URL // Create blob URL for images (instant navigation) and full downloads
const createBlobUrl = maxBytes === undefined; // Partial downloads (video/audio/transition) use presigned URL directly for playback
const createBlobUrl = assetType === 'image' || maxBytes === undefined;
return downloadManager return downloadManager
.addJob({ .addJob({
@ -690,11 +692,11 @@ export function usePreloadOrchestrator(
// ============================================ // ============================================
// PHASE 2: Preload everything else (don't wait) // PHASE 2: Preload everything else (don't wait)
// - Current page element assets (full downloads) // - Current page element assets (full downloads)
// - Transition videos (partial preload - 3MB)
// - Neighbor page backgrounds (partial preload for video/audio) // - Neighbor page backgrounds (partial preload for video/audio)
// - Neighbor page element assets (partial preload for video/audio) // - Neighbor page element assets (partial preload for video/audio)
// - Transition videos from page links (partial preload - 3MB)
// ============================================ // ============================================
logger.info('[PRELOAD] Phase 2: Preloading transitions and neighbors'); logger.info('[PRELOAD] Phase 2: Preloading neighbors and transitions');
// Current page element assets (moved from Phase 1 for faster startup) // Current page element assets (moved from Phase 1 for faster startup)
const currentPageAssets = assets.filter( const currentPageAssets = assets.filter(