diff --git a/app/(auth)/(tabs)/_layout.tsx b/app/(auth)/(tabs)/_layout.tsx index 1ed67eda..b92f3b13 100644 --- a/app/(auth)/(tabs)/_layout.tsx +++ b/app/(auth)/(tabs)/_layout.tsx @@ -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" }), }} /> diff --git a/assets/icons/gear.png b/assets/icons/gear.png new file mode 100644 index 00000000..f5b98cf0 Binary files /dev/null and b/assets/icons/gear.png differ diff --git a/components/home/InfiniteScrollingCollectionList.tv.tsx b/components/home/InfiniteScrollingCollectionList.tv.tsx index 16aac0b0..64869ec1 100644 --- a/components/home/InfiniteScrollingCollectionList.tv.tsx +++ b/components/home/InfiniteScrollingCollectionList.tv.tsx @@ -329,18 +329,22 @@ export const InfiniteScrollingCollectionList: React.FC = ({ windowSize={5} removeClippedSubviews={false} maintainVisibleContentPosition={{ minIndexForVisible: 0 }} - ListHeaderComponent={ - - } 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={ + // + // } + // contentInset={{ + // left: sizes.padding.horizontal, + // right: sizes.padding.horizontal, + // }} + // contentOffset={{ x: -sizes.padding.horizontal, y: 0 }} + // contentContainerStyle={{ paddingVertical: SCALE_PADDING }} ListFooterComponent={ = React.memo( = 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 = React.memo( > @@ -472,7 +473,10 @@ export const TVHeroCarousel: React.FC = ({ 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 = ({ = ({ fontSize: typography.display, fontWeight: "bold", color: "#FFFFFF", - marginBottom: 12, + marginBottom: scaleSize(12), }} numberOfLines={1} > @@ -507,7 +511,7 @@ export const TVHeroCarousel: React.FC = ({ 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 = ({ 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 = ({ style={{ flexDirection: "row", alignItems: "center", - gap: 16, + gap: scaleSize(16), }} > {year && ( @@ -562,10 +566,10 @@ export const TVHeroCarousel: React.FC = ({ {activeItem?.OfficialRating && ( @@ -584,15 +588,15 @@ export const TVHeroCarousel: React.FC = ({ style={{ flexDirection: "row", alignItems: "center", - gap: 6, + gap: scaleSize(6), }} > @@ -624,7 +628,7 @@ export const TVHeroCarousel: React.FC = ({ position: "absolute", left: 0, right: 0, - bottom: 40, + bottom: scaleSize(40), }} > = ({ 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={ + // + // } + // 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} diff --git a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt index 38c55625..8860932e 100644 --- a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt +++ b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt @@ -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