jellyfinFieldMapping.ts (SORT_FIELD_MAP friendly->Jellyfin sort names) had
zero consumers — sort code passes raw Jellyfin field names directly — so it
and its test are deleted.
playbackUnits.ts can't be removed: its tick<->seconds helpers are still the
correct converters for the remote Jellyfin *session* boundary
(SessionInfo.playState.positionTicks, NowPlayingItem.runTimeTicks), which
legitimately arrives in ticks. Documented that narrowed role; formatTime/
calculateProgress remain neutral seconds-based presentation helpers.
Note (out of scope): sortBy still passes raw Jellyfin field names
("SortName", "CommunityRating") — a separate sort-taxonomy leak that would
need its own Rust SortKey, like the search-scope work.
Frontend 626 tests (jellyfinFieldMapping's 18 removed with it), check clean.
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
/**
|
|
* 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));
|
|
}
|