mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-23 11:38:03 +00:00
- Remove AirPlay from CastProtocol type union (Chromecast only for now) - Replace AirPlay TODOs with generic 'Future: Add X for other protocols' comments - Remove PROTOCOL_COLORS export, use hardcoded Chromecast color (#F9AB00) - Update all component headers to be protocol-agnostic - Keep switch statements extensible for future protocol additions - Maintain clean architecture for easy integration of new casting protocols Architecture remains flexible for future protocols (AirPlay, DLNA, etc.)
161 lines
3.9 KiB
TypeScript
161 lines
3.9 KiB
TypeScript
/**
|
|
* Unified Casting Helper Functions
|
|
* Common utilities for casting protocols
|
|
*/
|
|
|
|
import type { CastProtocol, ConnectionQuality } from "./types";
|
|
|
|
/**
|
|
* Format milliseconds to HH:MM:SS or MM:SS
|
|
*/
|
|
export const formatTime = (ms: number): string => {
|
|
const totalSeconds = Math.floor(ms / 1000);
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
|
|
if (hours > 0) {
|
|
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
}
|
|
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
};
|
|
|
|
/**
|
|
* Calculate ending time based on current progress and duration
|
|
*/
|
|
export const calculateEndingTime = (
|
|
currentMs: number,
|
|
durationMs: number,
|
|
): string => {
|
|
const remainingMs = durationMs - currentMs;
|
|
const endTime = new Date(Date.now() + remainingMs);
|
|
const hours = endTime.getHours();
|
|
const minutes = endTime.getMinutes();
|
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
const displayHours = hours % 12 || 12;
|
|
|
|
return `${displayHours}:${minutes.toString().padStart(2, "0")} ${ampm}`;
|
|
};
|
|
|
|
/**
|
|
* Determine connection quality based on bitrate
|
|
*/
|
|
export const getConnectionQuality = (bitrate?: number): ConnectionQuality => {
|
|
if (!bitrate) return "good";
|
|
const mbps = bitrate / 1000000;
|
|
|
|
if (mbps >= 15) return "excellent";
|
|
if (mbps >= 8) return "good";
|
|
if (mbps >= 4) return "fair";
|
|
return "poor";
|
|
};
|
|
|
|
/**
|
|
* Get poster URL for item with specified dimensions
|
|
*/
|
|
export const getPosterUrl = (
|
|
baseUrl: string | undefined,
|
|
itemId: string | undefined,
|
|
tag: string | undefined,
|
|
width: number,
|
|
height: number,
|
|
): string | null => {
|
|
if (!baseUrl || !itemId) return null;
|
|
|
|
const params = new URLSearchParams({
|
|
maxWidth: width.toString(),
|
|
maxHeight: height.toString(),
|
|
quality: "90",
|
|
...(tag && { tag }),
|
|
});
|
|
|
|
return `${baseUrl}/Items/${itemId}/Images/Primary?${params.toString()}`;
|
|
};
|
|
|
|
/**
|
|
* Truncate title to max length with ellipsis
|
|
*/
|
|
export const truncateTitle = (title: string, maxLength: number): string => {
|
|
if (title.length <= maxLength) return title;
|
|
return `${title.substring(0, maxLength - 3)}...`;
|
|
};
|
|
|
|
/**
|
|
* Check if current time is within a segment
|
|
*/
|
|
export const isWithinSegment = (
|
|
currentMs: number,
|
|
segment: { start: number; end: number } | null,
|
|
): boolean => {
|
|
if (!segment) return false;
|
|
const currentSeconds = currentMs / 1000;
|
|
return currentSeconds >= segment.start && currentSeconds <= segment.end;
|
|
};
|
|
|
|
/**
|
|
* Format bitrate to human-readable string
|
|
*/
|
|
export const formatBitrate = (bitrate: number): string => {
|
|
const mbps = bitrate / 1000000;
|
|
if (mbps >= 1) {
|
|
return `${mbps.toFixed(1)} Mbps`;
|
|
}
|
|
return `${(bitrate / 1000).toFixed(0)} Kbps`;
|
|
};
|
|
|
|
/**
|
|
* Get protocol display name
|
|
*/
|
|
export const getProtocolName = (protocol: CastProtocol): string => {
|
|
switch (protocol) {
|
|
case "chromecast":
|
|
return "Chromecast";
|
|
// Future: Add cases for other protocols
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get protocol icon name
|
|
*/
|
|
export const getProtocolIcon = (
|
|
protocol: CastProtocol,
|
|
): "tv" | "logo-apple" => {
|
|
switch (protocol) {
|
|
case "chromecast":
|
|
return "tv";
|
|
// Future: Add icons for other protocols
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Format episode info (e.g., "S1 E1" or "Episode 1")
|
|
*/
|
|
export const formatEpisodeInfo = (
|
|
seasonNumber?: number | null,
|
|
episodeNumber?: number | null,
|
|
): string => {
|
|
if (
|
|
seasonNumber !== undefined &&
|
|
seasonNumber !== null &&
|
|
episodeNumber !== undefined &&
|
|
episodeNumber !== null
|
|
) {
|
|
return `S${seasonNumber} E${episodeNumber}`;
|
|
}
|
|
if (episodeNumber !== undefined && episodeNumber !== null) {
|
|
return `Episode ${episodeNumber}`;
|
|
}
|
|
return "";
|
|
};
|
|
|
|
/**
|
|
* Check if we should show next episode countdown
|
|
*/
|
|
export const shouldShowNextEpisodeCountdown = (
|
|
remainingMs: number,
|
|
hasNextEpisode: boolean,
|
|
countdownStartSeconds: number,
|
|
): boolean => {
|
|
return hasNextEpisode && remainingMs <= countdownStartSeconds * 1000;
|
|
};
|