mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-16 10:50:28 +01:00
feat(tv): add background theme music playback
This commit is contained in:
@@ -47,6 +47,7 @@ import { useImageColorsReturn } from "@/hooks/useImageColorsReturn";
|
||||
import { useTVItemActionModal } from "@/hooks/useTVItemActionModal";
|
||||
import { useTVOptionModal } from "@/hooks/useTVOptionModal";
|
||||
import { useTVSubtitleModal } from "@/hooks/useTVSubtitleModal";
|
||||
import { useTVThemeMusic } from "@/hooks/useTVThemeMusic";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
@@ -86,6 +87,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
|
||||
const _itemColors = useImageColorsReturn({ item });
|
||||
|
||||
// Auto-play theme music (handles fade in/out and cleanup)
|
||||
useTVThemeMusic(item?.Id);
|
||||
|
||||
// State for first episode card ref (used for focus guide)
|
||||
const [_firstEpisodeRef, setFirstEpisodeRef] = useState<View | null>(null);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||
import useRouter from "@/hooks/useAppRouter";
|
||||
import { useTVItemActionModal } from "@/hooks/useTVItemActionModal";
|
||||
import { useTVSeriesSeasonModal } from "@/hooks/useTVSeriesSeasonModal";
|
||||
import { useTVThemeMusic } from "@/hooks/useTVThemeMusic";
|
||||
import { useDownload } from "@/providers/DownloadProvider";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||
@@ -230,6 +231,9 @@ export const TVSeriesPage: React.FC<TVSeriesPageProps> = ({
|
||||
const seasonModalState = useAtomValue(tvSeriesSeasonModalAtom);
|
||||
const isSeasonModalVisible = seasonModalState !== null;
|
||||
|
||||
// Auto-play theme music (handles fade in/out and cleanup)
|
||||
useTVThemeMusic(item.Id);
|
||||
|
||||
// Season state
|
||||
const [seasonIndexState, setSeasonIndexState] = useAtom(seasonIndexAtom);
|
||||
const selectedSeasonIndex = useMemo(
|
||||
|
||||
78
components/tv/TVThemeMusicIndicator.tsx
Normal file
78
components/tv/TVThemeMusicIndicator.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Animated, Easing, Pressable, View } from "react-native";
|
||||
import { AnimatedEqualizer } from "@/components/music/AnimatedEqualizer";
|
||||
|
||||
interface TVThemeMusicIndicatorProps {
|
||||
isPlaying: boolean;
|
||||
isMuted: boolean;
|
||||
hasThemeMusic: boolean;
|
||||
onToggleMute: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const TVThemeMusicIndicator: React.FC<TVThemeMusicIndicatorProps> = ({
|
||||
isPlaying,
|
||||
isMuted,
|
||||
hasThemeMusic,
|
||||
onToggleMute,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const scale = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const animateTo = (v: number) =>
|
||||
Animated.timing(scale, {
|
||||
toValue: v,
|
||||
duration: 150,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
|
||||
if (!hasThemeMusic || !isPlaying) return null;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onToggleMute}
|
||||
onFocus={() => {
|
||||
setFocused(true);
|
||||
animateTo(1.15);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setFocused(false);
|
||||
animateTo(1);
|
||||
}}
|
||||
disabled={disabled}
|
||||
focusable={!disabled}
|
||||
>
|
||||
<Animated.View
|
||||
style={{
|
||||
transform: [{ scale }],
|
||||
backgroundColor: focused
|
||||
? "rgba(255,255,255,0.25)"
|
||||
: "rgba(255,255,255,0.1)",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 48,
|
||||
height: 48,
|
||||
}}
|
||||
>
|
||||
{isMuted ? (
|
||||
<Ionicons name='volume-mute' size={22} color='#FFFFFF' />
|
||||
) : (
|
||||
<View style={{ marginRight: 0 }}>
|
||||
<AnimatedEqualizer
|
||||
color='#FFFFFF'
|
||||
barWidth={3}
|
||||
barCount={3}
|
||||
height={18}
|
||||
gap={2}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -59,6 +59,7 @@ export type { TVTabButtonProps } from "./TVTabButton";
|
||||
export { TVTabButton } from "./TVTabButton";
|
||||
export type { TVTechnicalDetailsProps } from "./TVTechnicalDetails";
|
||||
export { TVTechnicalDetails } from "./TVTechnicalDetails";
|
||||
export { TVThemeMusicIndicator } from "./TVThemeMusicIndicator";
|
||||
// Subtitle sheet components
|
||||
export type { TVTrackCardProps } from "./TVTrackCard";
|
||||
export { TVTrackCard } from "./TVTrackCard";
|
||||
|
||||
Reference in New Issue
Block a user