/** * Playback unit utilities. * * The app's own media model speaks milliseconds (see the domain migration in * docs/specs/frontend-domain-model.md), so catalog code does NOT use the tick * helpers here. Ticks survive only at the **remote Jellyfin session boundary** — * `SessionInfo.playState.positionTicks` and `NowPlayingItem.runTimeTicks` arrive * straight from a live Jellyfin session and are converted here for display. * `formatTime`/`calculateProgress` are plain seconds-based presentation helpers. */ /** * Number of Jellyfin ticks per second (10 million). * * Only for the remote-session boundary (see module doc); catalog durations are * milliseconds and never touch this. */ export const TICKS_PER_SECOND = 10_000_000; /** * Convert seconds to Jellyfin ticks. Remote-session boundary only. */ export function secondsToTicks(seconds: number): number { return Math.floor(seconds * TICKS_PER_SECOND); } /** * Convert Jellyfin session ticks to seconds. Remote-session boundary only. */ export function ticksToSeconds(ticks: number | null | undefined): number { return (ticks ?? 0) / TICKS_PER_SECOND; } /** * Convert normalized volume (0-1) to percentage (0-100) * Used when sending volume to Jellyfin remote sessions * @param volume - Normalized volume (0.0 to 1.0) * @returns Volume as percentage (0 to 100) */ export function volumeToPercent(volume: number): number { return Math.floor(Math.max(0, Math.min(1, volume)) * 100); } /** * Convert percentage volume (0-100) to normalized (0-1) * Used when receiving volume from Jellyfin remote sessions * @param percent - Volume as percentage (0 to 100) * @returns Normalized volume (0.0 to 1.0) */ export function percentToVolume(percent: number): number { return Math.max(0, Math.min(100, percent)) / 100; } /** * Format time in seconds to MM:SS display string * @param seconds - Time in seconds * @returns Formatted string like "3:45" or "12:09" */ export function formatTime(seconds: number): string { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, "0")}`; } /** * Format time in seconds to HH:MM:SS display string (for longer content) * @param seconds - Time in seconds * @returns Formatted string like "1:23:45" or "0:03:45" */ export function formatTimeLong(seconds: number): string { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}:${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; } return `${mins}:${secs.toString().padStart(2, "0")}`; } /** * Calculate progress percentage * @param position - Current position in seconds * @param duration - Total duration in seconds * @returns Progress as percentage (0 to 100) */ export function calculateProgress(position: number, duration: number): number { if (duration <= 0) return 0; return Math.min(100, Math.max(0, (position / duration) * 100)); }