84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
/**
|
|
* Shared mutable state and pure utility functions.
|
|
* All modules import from here - no circular dependencies.
|
|
*/
|
|
import type { LibraryInfo, VideoItem } from './types';
|
|
|
|
// ---- Shared mutable state ----
|
|
export let library: LibraryInfo | null = null;
|
|
export let currentIndex = 0;
|
|
export let prefs: Record<string, any> | null = null;
|
|
export let suppressTick = false;
|
|
export let lastTick = 0;
|
|
export let seeking = false;
|
|
|
|
export function setLibrary(lib: LibraryInfo | null) { library = lib; }
|
|
export function setCurrentIndex(idx: number) { currentIndex = idx; }
|
|
export function setPrefs(p: Record<string, any> | null) { prefs = p; }
|
|
export function setSuppressTick(v: boolean) { suppressTick = v; }
|
|
export function setLastTick(v: number) { lastTick = v; }
|
|
export function setSeeking(v: boolean) { seeking = v; }
|
|
|
|
// ---- Cross-module callbacks (set by main.ts) ----
|
|
export const cb = {
|
|
loadIndex: null as ((idx: number, tc?: number, pause?: boolean, autoplay?: boolean) => Promise<void>) | null,
|
|
renderList: null as (() => void) | null,
|
|
updateInfoPanel: null as (() => void) | null,
|
|
updateOverall: null as (() => void) | null,
|
|
notify: null as ((msg: string) => void) | null,
|
|
refreshCurrentVideoMeta: null as (() => Promise<void>) | null,
|
|
onLibraryLoaded: null as ((info: LibraryInfo, startScan: boolean) => Promise<void>) | null,
|
|
buildSpeedMenu: null as ((rate: number) => void) | null,
|
|
};
|
|
|
|
// ---- Pure utility functions ----
|
|
|
|
export function clamp(n: number, a: number, b: number): number {
|
|
return Math.max(a, Math.min(b, n));
|
|
}
|
|
|
|
export function fmtTime(sec: number): string {
|
|
sec = Math.max(0, Math.floor(sec || 0));
|
|
const h = Math.floor(sec / 3600);
|
|
const m = Math.floor((sec % 3600) / 60);
|
|
const s = sec % 60;
|
|
if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
return `${m}:${String(s).padStart(2, '0')}`;
|
|
}
|
|
|
|
export function fmtBytes(n: number): string {
|
|
n = Number(n || 0);
|
|
if (!isFinite(n) || n <= 0) return '-';
|
|
const u = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
let i = 0;
|
|
while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; }
|
|
return `${n.toFixed(i === 0 ? 0 : 1)} ${u[i]}`;
|
|
}
|
|
|
|
export function fmtDate(ts: number): string {
|
|
if (!ts) return '-';
|
|
try { return new Date(ts * 1000).toLocaleString(); } catch { return '-'; }
|
|
}
|
|
|
|
export function fmtBitrate(bps: number): string | null {
|
|
const n = Number(bps || 0);
|
|
if (!isFinite(n) || n <= 0) return null;
|
|
const kb = n / 1000.0;
|
|
if (kb < 1000) return `${kb.toFixed(0)} kbps`;
|
|
return `${(kb / 1000).toFixed(2)} Mbps`;
|
|
}
|
|
|
|
export function currentItem(): VideoItem | null {
|
|
if (!library || !library.items) return null;
|
|
return library.items[currentIndex] || null;
|
|
}
|
|
|
|
export function computeResumeTime(item: VideoItem | null): number {
|
|
if (!item) return 0.0;
|
|
if (item.finished) return 0.0;
|
|
const pos = Number(item.pos || 0.0);
|
|
const dur = Number(item.duration || 0.0);
|
|
if (dur > 0) return clamp(pos, 0.0, Math.max(0.0, dur - 0.25));
|
|
return Math.max(0.0, pos);
|
|
}
|