fix: Refactors casting player and components

Refactors the casting player screen and related components for improved code clarity, performance, and maintainability.

- Removes unused code and simplifies logic, especially around audio track selection and recommended stereo track handling.
- Improves the formatting of trickplay time displays for consistency.
- Streamlines UI elements and removes unnecessary conditional checks.
- Updates the Chromecast component to use hooks for side effects, ensuring the Chromecast session remains active.
- Improves the display of the language in the audio track display.
This commit is contained in:
Uruk
2026-02-09 22:03:48 +01:00
parent 2c27186e22
commit f34997a024
7 changed files with 50 additions and 290 deletions

View File

@@ -3,8 +3,6 @@
* Common utilities for casting protocols
*/
import type { CastProtocol, ConnectionQuality } from "./types";
/**
* Format milliseconds to HH:MM:SS or MM:SS
*/
@@ -46,19 +44,6 @@ export const calculateEndingTime = (
}
};
/**
* Determine connection quality based on bitrate
*/
export const getConnectionQuality = (bitrate?: number): ConnectionQuality => {
if (bitrate == null) 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
*/
@@ -103,78 +88,15 @@ export const isWithinSegment = (
};
/**
* Format bitrate to human-readable string
* Format trickplay time from {hours, minutes, seconds} to display string.
* Produces "H:MM:SS" when hours > 0, otherwise "MM:SS".
*/
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";
default: {
const _exhaustive: never = protocol;
return String(_exhaustive);
}
}
};
/**
* Get protocol icon name
*/
export const getProtocolIcon = (
protocol: CastProtocol,
): "tv" | "logo-apple" => {
switch (protocol) {
case "chromecast":
return "tv";
default: {
const _exhaustive: never = protocol;
return "tv";
}
}
};
/**
* Format episode info (e.g., "S1 E1" or "Episode 1")
* @param seasonNumber - Season number
* @param episodeNumber - Episode number
* @param episodeLabel - Optional label for standalone episode (e.g. translated "Episode")
*/
export const formatEpisodeInfo = (
seasonNumber?: number | null,
episodeNumber?: number | null,
episodeLabel = "Episode",
): string => {
if (
seasonNumber !== undefined &&
seasonNumber !== null &&
episodeNumber !== undefined &&
episodeNumber !== null
) {
return `S${seasonNumber} E${episodeNumber}`;
}
if (episodeNumber !== undefined && episodeNumber !== null) {
return `${episodeLabel} ${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;
export const formatTrickplayTime = (time: {
hours: number;
minutes: number;
seconds: number;
}): string => {
const mm = String(time.minutes).padStart(2, "0");
const ss = String(time.seconds).padStart(2, "0");
return time.hours > 0 ? `${time.hours}:${mm}:${ss}` : `${mm}:${ss}`;
};

View File

@@ -24,18 +24,9 @@ export interface CastPlayerState {
progress: number;
duration: number;
volume: number;
showControls: boolean;
isBuffering: boolean;
}
export interface CastSegmentData {
intro: { start: number; end: number } | null;
credits: { start: number; end: number } | null;
recap: { start: number; end: number } | null;
commercial: Array<{ start: number; end: number }>;
preview: Array<{ start: number; end: number }>;
}
export interface AudioTrack {
index: number;
language: string;
@@ -77,8 +68,5 @@ export const DEFAULT_CAST_STATE: CastPlayerState = {
progress: 0,
duration: 0,
volume: 0.5,
showControls: true,
isBuffering: false,
};
export type ConnectionQuality = "excellent" | "good" | "fair" | "poor";

View File

@@ -1,50 +1,7 @@
/**
* Chromecast player configuration and constants
* Chromecast player configuration and types
*/
export const CHROMECAST_CONSTANTS = {
// Timing (all milliseconds for consistency)
PROGRESS_REPORT_INTERVAL_MS: 10_000,
CONTROLS_TIMEOUT_MS: 5_000,
BUFFERING_THRESHOLD_MS: 10_000,
NEXT_EPISODE_COUNTDOWN_MS: 30_000,
CONNECTION_CHECK_INTERVAL_MS: 5_000,
// UI
POSTER_WIDTH: 300,
POSTER_HEIGHT: 450,
MINI_PLAYER_HEIGHT: 80,
SKIP_FORWARD_SECS: 15, // overridden by settings
SKIP_BACKWARD_SECS: 15, // overridden by settings
// Animation
ANIMATION_DURATION_MS: 300,
BLUR_RADIUS: 10,
} as const;
export const CONNECTION_QUALITY = {
EXCELLENT: { min: 50, label: "Excellent", icon: "wifi" }, // min Mbps
GOOD: { min: 30, label: "Good", icon: "signal" }, // min Mbps
FAIR: { min: 15, label: "Fair", icon: "cellular" }, // min Mbps
POOR: { min: 0, label: "Poor", icon: "warning" }, // min Mbps
} as const;
export type ConnectionQuality = keyof typeof CONNECTION_QUALITY;
export type PlaybackState = "playing" | "paused" | "stopped" | "buffering";
export interface ChromecastPlayerState {
isConnected: boolean;
deviceName: string | null;
playbackState: PlaybackState;
progress: number; // milliseconds
duration: number; // milliseconds
volume: number; // 0-1
isMuted: boolean;
currentItemId: string | null;
connectionQuality: ConnectionQuality;
}
export interface ChromecastSegmentData {
intro: { start: number; end: number } | null;
credits: { start: number; end: number } | null;
@@ -52,15 +9,3 @@ export interface ChromecastSegmentData {
commercial: { start: number; end: number }[];
preview: { start: number; end: number }[];
}
export const DEFAULT_CHROMECAST_STATE: ChromecastPlayerState = {
isConnected: false,
deviceName: null,
playbackState: "stopped",
progress: 0,
duration: 0,
volume: 1,
isMuted: false,
currentItemId: null,
connectionQuality: "GOOD",
};