mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-25 01:54:42 +01:00
fix: resolve 13 review issues across casting components
- casting-player: remove redundant self-navigation useEffect - casting-player: derive Type from metadata instead of hardcoding 'Movie' - casting-player: pass null to useTrickplay instead of empty BaseItemDto - casting-player: use != null for skip time labels (allow 0 to render) - Chromecast: case-insensitive m3u8 detection via regex - Chromecast: fix UUID hyphen indices to 4,6,8,10 for proper v4 format - CastingMiniPlayer: use SeriesPrimaryImageTag for series poster URL - ChromecastConnectionMenu: send rounded volume to castSession.setVolume - ChromecastConnectionMenu: use isMutedRef in onValueChange to avoid stale closure - ChromecastDeviceSheet: skip volume sync during active sliding - ChromecastDeviceSheet: move unmute logic from onValueChange to onSlidingStart - useCasting: detect playback start via isPlaying/playerState, not just progress>0 - useCasting: derive isChromecastAvailable from castState instead of hardcoding true - useTrickplay: accept BaseItemDto|null with null guards on Id access
This commit is contained in:
@@ -121,7 +121,7 @@ export function Chromecast({
|
||||
randomBytes[8] = (randomBytes[8] & 0x3f) | 0x80; // Variant 10
|
||||
const uuid = Array.from(randomBytes, (b, i) => {
|
||||
const hex = b.toString(16).padStart(2, "0");
|
||||
return [3, 5, 7, 9].includes(i) ? `-${hex}` : hex;
|
||||
return [4, 6, 8, 10].includes(i) ? `-${hex}` : hex;
|
||||
}).join("");
|
||||
playSessionIdRef.current = uuid;
|
||||
lastContentIdRef.current = contentId;
|
||||
@@ -130,7 +130,7 @@ export function Chromecast({
|
||||
const positionTicks = Math.floor(streamPosition * 10000000);
|
||||
const isPaused = mediaStatus.playerState === "paused";
|
||||
const streamUrl = mediaStatus.mediaInfo.contentUrl || "";
|
||||
const isTranscoding = streamUrl.includes("m3u8");
|
||||
const isTranscoding = /m3u8/i.test(streamUrl);
|
||||
|
||||
const progressInfo: PlaybackProgressInfo = {
|
||||
ItemId: contentId,
|
||||
|
||||
@@ -45,9 +45,9 @@ export const CastingMiniPlayer: React.FC = () => {
|
||||
return mediaStatus?.mediaInfo?.customData as BaseItemDto | undefined;
|
||||
}, [mediaStatus?.mediaInfo?.customData]);
|
||||
|
||||
// Trickplay support - pass currentItem as BaseItemDto or empty object
|
||||
// Trickplay support - pass currentItem as BaseItemDto or null
|
||||
const { trickPlayUrl, calculateTrickplayUrl, trickplayInfo } = useTrickplay(
|
||||
currentItem || ({} as BaseItemDto),
|
||||
currentItem || null,
|
||||
);
|
||||
const [trickplayTime, setTrickplayTime] = useState({
|
||||
hours: 0,
|
||||
@@ -121,7 +121,7 @@ export const CastingMiniPlayer: React.FC = () => {
|
||||
}
|
||||
}, [progress, sliderProgress]);
|
||||
|
||||
// For episodes, use season poster; for other content, use item poster
|
||||
// For episodes, use series poster; for other content, use item poster
|
||||
const posterUrl = useMemo(() => {
|
||||
if (!api?.basePath || !currentItem) return null;
|
||||
|
||||
@@ -131,8 +131,8 @@ export const CastingMiniPlayer: React.FC = () => {
|
||||
currentItem.ParentIndexNumber !== undefined &&
|
||||
currentItem.SeasonId
|
||||
) {
|
||||
// Build season poster URL using SeriesId and image tag for cache validation
|
||||
const imageTag = currentItem.ImageTags?.Primary || "";
|
||||
// Build series poster URL using SeriesId and series-level image tag
|
||||
const imageTag = currentItem.SeriesPrimaryImageTag || "";
|
||||
const tagParam = imageTag ? `&tag=${imageTag}` : "";
|
||||
return `${api.basePath}/Items/${currentItem.SeriesId}/Images/Primary?fillHeight=120&fillWidth=80&quality=96${tagParam}`;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export const ChromecastConnectionMenu: React.FC<
|
||||
|
||||
try {
|
||||
if (castSession) {
|
||||
await castSession.setVolume(value / 100);
|
||||
await castSession.setVolume(rounded / 100);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Connection Menu] Volume error:", error);
|
||||
@@ -262,7 +262,9 @@ export const ChromecastConnectionMenu: React.FC<
|
||||
onValueChange={async (value) => {
|
||||
volumeValue.value = value;
|
||||
handleVolumeChange(value);
|
||||
if (isMuted) {
|
||||
// Unmute when adjusting volume - use ref to avoid
|
||||
// stale closure and prevent repeated async calls
|
||||
if (isMutedRef.current) {
|
||||
isMutedRef.current = false;
|
||||
setIsMuted(false);
|
||||
try {
|
||||
|
||||
@@ -45,7 +45,9 @@ export const ChromecastDeviceSheet: React.FC<ChromecastDeviceSheetProps> = ({
|
||||
const lastSetVolume = useRef(Math.round(volume * 100));
|
||||
|
||||
// Sync volume slider with prop changes (updates from physical buttons)
|
||||
// Skip updates while user is actively sliding to avoid overwriting drag
|
||||
useEffect(() => {
|
||||
if (isSliding.current) return;
|
||||
volumeValue.value = volume * 100;
|
||||
setDisplayVolume(Math.round(volume * 100));
|
||||
}, [volume, volumeValue]);
|
||||
@@ -275,13 +277,9 @@ export const ChromecastDeviceSheet: React.FC<ChromecastDeviceSheetProps> = ({
|
||||
minimumTrackTintColor: isMuted ? "#666" : "#a855f7",
|
||||
bubbleBackgroundColor: "#a855f7",
|
||||
}}
|
||||
onSlidingStart={() => {
|
||||
onSlidingStart={async () => {
|
||||
isSliding.current = true;
|
||||
}}
|
||||
onValueChange={async (value) => {
|
||||
volumeValue.value = value;
|
||||
handleVolumeChange(value);
|
||||
// Unmute when adjusting volume
|
||||
// Auto-unmute when user starts adjusting volume
|
||||
if (isMuted && castSession) {
|
||||
setIsMuted(false);
|
||||
try {
|
||||
@@ -292,6 +290,10 @@ export const ChromecastDeviceSheet: React.FC<ChromecastDeviceSheetProps> = ({
|
||||
}
|
||||
}
|
||||
}}
|
||||
onValueChange={(value) => {
|
||||
volumeValue.value = value;
|
||||
handleVolumeChange(value);
|
||||
}}
|
||||
onSlidingComplete={(value) => {
|
||||
isSliding.current = false;
|
||||
lastSetVolume.current = Math.round(value);
|
||||
|
||||
Reference in New Issue
Block a user