mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-13 18:36:35 +01:00
Performance improvments for android playback
Ensured the correct hardware encoding is used for android TV versions Fixed scaling of the hero layout Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
@@ -134,7 +134,7 @@ export default function TabLayout() {
|
||||
tabBarItemHidden: !Platform.isTV,
|
||||
tabBarIcon:
|
||||
Platform.OS === "android"
|
||||
? (_e) => require("@/assets/icons/list.png")
|
||||
? (_e) => require("@/assets/icons/gear.png") //Should maybe use other libraries to have it uniform
|
||||
: (_e) => ({ sfSymbol: "gearshape.fill" }),
|
||||
}}
|
||||
/>
|
||||
|
||||
BIN
assets/icons/gear.png
Normal file
BIN
assets/icons/gear.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -329,18 +329,22 @@ export const InfiniteScrollingCollectionList: React.FC<Props> = ({
|
||||
windowSize={5}
|
||||
removeClippedSubviews={false}
|
||||
maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
|
||||
ListHeaderComponent={
|
||||
<View style={{ width: sizes.padding.horizontal }} />
|
||||
}
|
||||
style={{ overflow: "visible" }}
|
||||
contentInset={{
|
||||
left: sizes.padding.horizontal,
|
||||
right: sizes.padding.horizontal,
|
||||
}}
|
||||
contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
||||
contentContainerStyle={{
|
||||
paddingVertical: SCALE_PADDING,
|
||||
paddingVertical: sizes.gaps.small,
|
||||
paddingLeft: sizes.padding.horizontal,
|
||||
paddingRight: sizes.padding.horizontal,
|
||||
}}
|
||||
// Below is a work around with the contentInset, same in TVHeroCarousel, if okay on apple remove
|
||||
// ListHeaderComponent={
|
||||
// <View style={{ width: sizes.padding.horizontal }} />
|
||||
// }
|
||||
// contentInset={{
|
||||
// left: sizes.padding.horizontal,
|
||||
// right: sizes.padding.horizontal,
|
||||
// }}
|
||||
// contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
||||
// contentContainerStyle={{ paddingVertical: SCALE_PADDING }}
|
||||
ListFooterComponent={
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||
|
||||
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
||||
@@ -129,7 +130,7 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
||||
<GlassPosterView
|
||||
imageUrl={posterUrl}
|
||||
aspectRatio={16 / 9}
|
||||
cornerRadius={24}
|
||||
cornerRadius={scaleSize(24)}
|
||||
progress={progress}
|
||||
showWatchedIndicator={false}
|
||||
isFocused={focused}
|
||||
@@ -154,15 +155,15 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
||||
style={{
|
||||
width: sizes.posters.episode,
|
||||
aspectRatio: 16 / 9,
|
||||
borderRadius: 24,
|
||||
borderRadius: scaleSize(24),
|
||||
overflow: "hidden",
|
||||
transform: [{ scale }],
|
||||
borderWidth: 2,
|
||||
borderWidth: scaleSize(2),
|
||||
borderColor: focused ? "#FFFFFF" : "transparent",
|
||||
shadowColor: "#FFFFFF",
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: focused ? 0.6 : 0,
|
||||
shadowRadius: focused ? 20 : 0,
|
||||
shadowRadius: focused ? scaleSize(20) : 0,
|
||||
}}
|
||||
>
|
||||
{posterUrl ? (
|
||||
@@ -183,7 +184,7 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
||||
>
|
||||
<Ionicons
|
||||
name='film-outline'
|
||||
size={48}
|
||||
size={scaleSize(48)}
|
||||
color='rgba(255,255,255,0.3)'
|
||||
/>
|
||||
</View>
|
||||
@@ -472,7 +473,10 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
left: sizes.padding.horizontal,
|
||||
right: sizes.padding.horizontal,
|
||||
bottom:
|
||||
40 + sizes.posters.episode * (9 / 16) + sizes.gaps.small * 2 + 20,
|
||||
scaleSize(40) +
|
||||
sizes.posters.episode * (9 / 16) +
|
||||
sizes.gaps.small * 2 +
|
||||
scaleSize(20),
|
||||
}}
|
||||
>
|
||||
{/* Logo or Title */}
|
||||
@@ -480,9 +484,9 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
<Image
|
||||
source={{ uri: logoUrl }}
|
||||
style={{
|
||||
height: 100,
|
||||
height: scaleSize(100),
|
||||
width: SCREEN_WIDTH * 0.35,
|
||||
marginBottom: 16,
|
||||
marginBottom: scaleSize(16),
|
||||
}}
|
||||
contentFit='contain'
|
||||
contentPosition='left'
|
||||
@@ -493,7 +497,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
fontSize: typography.display,
|
||||
fontWeight: "bold",
|
||||
color: "#FFFFFF",
|
||||
marginBottom: 12,
|
||||
marginBottom: scaleSize(12),
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -507,7 +511,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
style={{
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.9)",
|
||||
marginBottom: 12,
|
||||
marginBottom: scaleSize(12),
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -521,7 +525,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
style={{
|
||||
fontSize: typography.body,
|
||||
color: "rgba(255,255,255,0.8)",
|
||||
marginBottom: 16,
|
||||
marginBottom: scaleSize(16),
|
||||
maxWidth: SCREEN_WIDTH * 0.5,
|
||||
lineHeight: typography.body * 1.4,
|
||||
}}
|
||||
@@ -536,7 +540,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
gap: scaleSize(16),
|
||||
}}
|
||||
>
|
||||
{year && (
|
||||
@@ -562,10 +566,10 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
{activeItem?.OfficialRating && (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
paddingHorizontal: scaleSize(8),
|
||||
paddingVertical: scaleSize(2),
|
||||
borderRadius: scaleSize(4),
|
||||
borderWidth: scaleSize(1),
|
||||
borderColor: "rgba(255,255,255,0.5)",
|
||||
}}
|
||||
>
|
||||
@@ -584,15 +588,15 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
gap: scaleSize(6),
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: 60,
|
||||
height: 4,
|
||||
width: scaleSize(60),
|
||||
height: scaleSize(4),
|
||||
backgroundColor: "rgba(255,255,255,0.3)",
|
||||
borderRadius: 2,
|
||||
borderRadius: scaleSize(2),
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
@@ -624,7 +628,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
bottom: scaleSize(40),
|
||||
}}
|
||||
>
|
||||
<FlatList
|
||||
@@ -633,12 +637,21 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
||||
keyExtractor={keyExtractor}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={{ overflow: "visible" }}
|
||||
contentInset={{
|
||||
left: sizes.padding.horizontal,
|
||||
right: sizes.padding.horizontal,
|
||||
contentContainerStyle={{
|
||||
paddingVertical: sizes.gaps.small,
|
||||
paddingLeft: sizes.padding.horizontal,
|
||||
paddingRight: sizes.padding.horizontal,
|
||||
}}
|
||||
contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
||||
contentContainerStyle={{ paddingVertical: sizes.gaps.small }}
|
||||
// Below is a work around with the contentInset, same in infiniteScrollingCollectionList, if okay on apple remove
|
||||
// ListHeaderComponent={
|
||||
// <View style={{ width: sizes.padding.horizontal }} />
|
||||
// }
|
||||
// contentInset={{
|
||||
// left: sizes.padding.horizontal,
|
||||
// right: sizes.padding.horizontal,
|
||||
// }}
|
||||
// contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
||||
// contentContainerStyle={{ paddingVertical: sizes.gaps.small }}
|
||||
renderItem={renderHeroCard}
|
||||
removeClippedSubviews={false}
|
||||
initialNumToRender={8}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package expo.modules.mpvplayer
|
||||
|
||||
import android.app.UiModeManager
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.AssetManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -27,7 +29,12 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
const val MPV_FORMAT_DOUBLE = 5
|
||||
const val MPV_FORMAT_NODE = 6
|
||||
}
|
||||
|
||||
|
||||
private fun isTvDevice(): Boolean {
|
||||
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
|
||||
return uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
|
||||
}
|
||||
|
||||
interface Delegate {
|
||||
fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double)
|
||||
fun onPauseChanged(isPaused: Boolean)
|
||||
@@ -157,7 +164,15 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
||||
MPVLib.setOptionString("opengl-es", "yes")
|
||||
|
||||
// Hardware video decoding
|
||||
MPVLib.setOptionString("hwdec", "mediacodec-copy")
|
||||
// TV: zero-copy (mediacodec) for better performance on low-power devices
|
||||
// Mobile: copy mode (mediacodec-copy) for better compatibility
|
||||
val isTV = isTvDevice()
|
||||
if (isTV) {
|
||||
MPVLib.setOptionString("hwdec", "mediacodec")
|
||||
MPVLib.setOptionString("profile", "fast")
|
||||
} else {
|
||||
MPVLib.setOptionString("hwdec", "mediacodec-copy")
|
||||
}
|
||||
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
|
||||
|
||||
// Cache settings for better network streaming
|
||||
|
||||
Reference in New Issue
Block a user