mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-20 15:54:42 +01:00
Merge branch 'master' into wip/general-posters
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { BlurView } from "expo-blur";
|
||||
import React, { useEffect } from "react";
|
||||
import { Platform, View, ViewProps } from "react-native";
|
||||
import { Platform, TouchableOpacity, ViewProps } from "react-native";
|
||||
import GoogleCast, {
|
||||
CastButton,
|
||||
CastContext,
|
||||
useCastDevice,
|
||||
useDevices,
|
||||
useMediaStatus,
|
||||
useRemoteMediaClient,
|
||||
} from "react-native-google-cast";
|
||||
|
||||
@@ -25,6 +27,7 @@ export const Chromecast: React.FC<Props> = ({
|
||||
const devices = useDevices();
|
||||
const sessionManager = GoogleCast.getSessionManager();
|
||||
const discoveryManager = GoogleCast.getDiscoveryManager();
|
||||
const mediaStatus = useMediaStatus();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -38,31 +41,47 @@ export const Chromecast: React.FC<Props> = ({
|
||||
|
||||
if (background === "transparent")
|
||||
return (
|
||||
<View
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
|
||||
else CastContext.showCastDialog();
|
||||
}}
|
||||
className="rounded-full h-10 w-10 flex items-center justify-center b"
|
||||
{...props}
|
||||
>
|
||||
<CastButton style={{ tintColor: "white", height, width }} />
|
||||
</View>
|
||||
<Feather name="cast" size={22} color={"white"} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
if (Platform.OS === "android")
|
||||
return (
|
||||
<View
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
|
||||
else CastContext.showCastDialog();
|
||||
}}
|
||||
className="rounded-full h-10 w-10 flex items-center justify-center bg-neutral-800/80"
|
||||
{...props}
|
||||
>
|
||||
<CastButton style={{ tintColor: "white", height, width }} />
|
||||
</View>
|
||||
<Feather name="cast" size={22} color={"white"} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
return (
|
||||
<BlurView
|
||||
intensity={100}
|
||||
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (mediaStatus?.currentItemId) CastContext.showExpandedControls();
|
||||
else CastContext.showCastDialog();
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<CastButton style={{ tintColor: "white", height, width }} />
|
||||
</BlurView>
|
||||
<BlurView
|
||||
intensity={100}
|
||||
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
|
||||
{...props}
|
||||
>
|
||||
<Feather name="cast" size={22} color={"white"} />
|
||||
</BlurView>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import CastContext, {
|
||||
PlayServicesState,
|
||||
useMediaStatus,
|
||||
useRemoteMediaClient,
|
||||
} from "react-native-google-cast";
|
||||
import Animated, {
|
||||
@@ -22,6 +23,10 @@ import Animated, {
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import { Button } from "./Button";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl";
|
||||
|
||||
interface Props extends React.ComponentProps<typeof Button> {
|
||||
item?: BaseItemDto | null;
|
||||
@@ -33,11 +38,12 @@ const MIN_PLAYBACK_WIDTH = 15;
|
||||
|
||||
export const PlayButton: React.FC<Props> = ({ item, url, ...props }) => {
|
||||
const { showActionSheetWithOptions } = useActionSheet();
|
||||
const { setCurrentlyPlayingState } = usePlayback();
|
||||
|
||||
const client = useRemoteMediaClient();
|
||||
const { setCurrentlyPlayingState } = usePlayback();
|
||||
const mediaStatus = useMediaStatus();
|
||||
|
||||
const [colorAtom] = useAtom(itemThemeColorAtom);
|
||||
const [api] = useAtom(apiAtom);
|
||||
|
||||
const memoizedItem = useMemo(() => item, [item?.Id]); // Memoize the item
|
||||
const memoizedColor = useMemo(() => colorAtom, [colorAtom]); // Memoize the color
|
||||
@@ -63,24 +69,88 @@ export const PlayButton: React.FC<Props> = ({ item, url, ...props }) => {
|
||||
cancelButtonIndex,
|
||||
},
|
||||
async (selectedIndex: number | undefined) => {
|
||||
if (!api) return;
|
||||
const currentTitle = mediaStatus?.mediaInfo?.metadata?.title;
|
||||
const isOpeningCurrentlyPlayingMedia =
|
||||
currentTitle && currentTitle === item?.Name;
|
||||
|
||||
switch (selectedIndex) {
|
||||
case 0:
|
||||
await CastContext.getPlayServicesState().then((state) => {
|
||||
if (state && state !== PlayServicesState.SUCCESS)
|
||||
CastContext.showPlayServicesErrorDialog(state);
|
||||
else {
|
||||
client.loadMedia({
|
||||
mediaInfo: {
|
||||
contentUrl: url,
|
||||
contentType: "video/mp4",
|
||||
metadata: {
|
||||
type: item.Type === "Episode" ? "tvShow" : "movie",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
// If we're opening a currently playing item, don't restart the media.
|
||||
// Instead just open controls.
|
||||
if (isOpeningCurrentlyPlayingMedia) {
|
||||
CastContext.showExpandedControls();
|
||||
return;
|
||||
}
|
||||
client
|
||||
.loadMedia({
|
||||
mediaInfo: {
|
||||
contentUrl: url,
|
||||
contentType: "video/mp4",
|
||||
metadata:
|
||||
item.Type === "Episode"
|
||||
? {
|
||||
type: "tvShow",
|
||||
title: item.Name || "",
|
||||
episodeNumber: item.IndexNumber || 0,
|
||||
seasonNumber: item.ParentIndexNumber || 0,
|
||||
seriesTitle: item.SeriesName || "",
|
||||
images: [
|
||||
{
|
||||
url: getParentBackdropImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: item.Type === "Movie"
|
||||
? {
|
||||
type: "movie",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
}
|
||||
: {
|
||||
type: "generic",
|
||||
title: item.Name || "",
|
||||
subtitle: item.Overview || "",
|
||||
images: [
|
||||
{
|
||||
url: getPrimaryImageUrl({
|
||||
api,
|
||||
item,
|
||||
quality: 90,
|
||||
width: 2000,
|
||||
})!,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
startTime: 0,
|
||||
});
|
||||
startTime: 0,
|
||||
})
|
||||
.then(() => {
|
||||
// state is already set when reopening current media, so skip it here.
|
||||
if (isOpeningCurrentlyPlayingMedia) {
|
||||
return;
|
||||
}
|
||||
CastContext.showExpandedControls();
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const FilterButton = <T,>({
|
||||
queryFn,
|
||||
queryKey,
|
||||
set,
|
||||
values,
|
||||
values, // selected values
|
||||
title,
|
||||
renderItemLabel,
|
||||
searchFilter,
|
||||
|
||||
@@ -186,7 +186,7 @@ export const FilterSheet = <T,>({
|
||||
className=" bg-neutral-800 px-4 py-3 flex flex-row items-center justify-between"
|
||||
>
|
||||
<Text>{renderItemLabel(item)}</Text>
|
||||
{values.includes(item) ? (
|
||||
{values.some((i) => i === item) ? (
|
||||
<Ionicons name="radio-button-on" size={24} color="white" />
|
||||
) : (
|
||||
<Ionicons name="radio-button-off" size={24} color="white" />
|
||||
|
||||
Reference in New Issue
Block a user