mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-23 15:26:42 +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,
|
tabBarItemHidden: !Platform.isTV,
|
||||||
tabBarIcon:
|
tabBarIcon:
|
||||||
Platform.OS === "android"
|
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" }),
|
: (_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}
|
windowSize={5}
|
||||||
removeClippedSubviews={false}
|
removeClippedSubviews={false}
|
||||||
maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
|
maintainVisibleContentPosition={{ minIndexForVisible: 0 }}
|
||||||
ListHeaderComponent={
|
|
||||||
<View style={{ width: sizes.padding.horizontal }} />
|
|
||||||
}
|
|
||||||
style={{ overflow: "visible" }}
|
style={{ overflow: "visible" }}
|
||||||
contentInset={{
|
|
||||||
left: sizes.padding.horizontal,
|
|
||||||
right: sizes.padding.horizontal,
|
|
||||||
}}
|
|
||||||
contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
|
||||||
contentContainerStyle={{
|
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={
|
ListFooterComponent={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||||
|
import { scaleSize } from "@/utils/scaleSize";
|
||||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("window");
|
||||||
@@ -129,7 +130,7 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
|||||||
<GlassPosterView
|
<GlassPosterView
|
||||||
imageUrl={posterUrl}
|
imageUrl={posterUrl}
|
||||||
aspectRatio={16 / 9}
|
aspectRatio={16 / 9}
|
||||||
cornerRadius={24}
|
cornerRadius={scaleSize(24)}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
showWatchedIndicator={false}
|
showWatchedIndicator={false}
|
||||||
isFocused={focused}
|
isFocused={focused}
|
||||||
@@ -154,15 +155,15 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
|||||||
style={{
|
style={{
|
||||||
width: sizes.posters.episode,
|
width: sizes.posters.episode,
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
borderRadius: 24,
|
borderRadius: scaleSize(24),
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
transform: [{ scale }],
|
transform: [{ scale }],
|
||||||
borderWidth: 2,
|
borderWidth: scaleSize(2),
|
||||||
borderColor: focused ? "#FFFFFF" : "transparent",
|
borderColor: focused ? "#FFFFFF" : "transparent",
|
||||||
shadowColor: "#FFFFFF",
|
shadowColor: "#FFFFFF",
|
||||||
shadowOffset: { width: 0, height: 0 },
|
shadowOffset: { width: 0, height: 0 },
|
||||||
shadowOpacity: focused ? 0.6 : 0,
|
shadowOpacity: focused ? 0.6 : 0,
|
||||||
shadowRadius: focused ? 20 : 0,
|
shadowRadius: focused ? scaleSize(20) : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{posterUrl ? (
|
{posterUrl ? (
|
||||||
@@ -183,7 +184,7 @@ const HeroCard: React.FC<HeroCardProps> = React.memo(
|
|||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='film-outline'
|
name='film-outline'
|
||||||
size={48}
|
size={scaleSize(48)}
|
||||||
color='rgba(255,255,255,0.3)'
|
color='rgba(255,255,255,0.3)'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -472,7 +473,10 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
left: sizes.padding.horizontal,
|
left: sizes.padding.horizontal,
|
||||||
right: sizes.padding.horizontal,
|
right: sizes.padding.horizontal,
|
||||||
bottom:
|
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 */}
|
{/* Logo or Title */}
|
||||||
@@ -480,9 +484,9 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
<Image
|
<Image
|
||||||
source={{ uri: logoUrl }}
|
source={{ uri: logoUrl }}
|
||||||
style={{
|
style={{
|
||||||
height: 100,
|
height: scaleSize(100),
|
||||||
width: SCREEN_WIDTH * 0.35,
|
width: SCREEN_WIDTH * 0.35,
|
||||||
marginBottom: 16,
|
marginBottom: scaleSize(16),
|
||||||
}}
|
}}
|
||||||
contentFit='contain'
|
contentFit='contain'
|
||||||
contentPosition='left'
|
contentPosition='left'
|
||||||
@@ -493,7 +497,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
fontSize: typography.display,
|
fontSize: typography.display,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: "#FFFFFF",
|
color: "#FFFFFF",
|
||||||
marginBottom: 12,
|
marginBottom: scaleSize(12),
|
||||||
}}
|
}}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
@@ -507,7 +511,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
fontSize: typography.body,
|
fontSize: typography.body,
|
||||||
color: "rgba(255,255,255,0.9)",
|
color: "rgba(255,255,255,0.9)",
|
||||||
marginBottom: 12,
|
marginBottom: scaleSize(12),
|
||||||
}}
|
}}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
@@ -521,7 +525,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
fontSize: typography.body,
|
fontSize: typography.body,
|
||||||
color: "rgba(255,255,255,0.8)",
|
color: "rgba(255,255,255,0.8)",
|
||||||
marginBottom: 16,
|
marginBottom: scaleSize(16),
|
||||||
maxWidth: SCREEN_WIDTH * 0.5,
|
maxWidth: SCREEN_WIDTH * 0.5,
|
||||||
lineHeight: typography.body * 1.4,
|
lineHeight: typography.body * 1.4,
|
||||||
}}
|
}}
|
||||||
@@ -536,7 +540,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 16,
|
gap: scaleSize(16),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{year && (
|
{year && (
|
||||||
@@ -562,10 +566,10 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
{activeItem?.OfficialRating && (
|
{activeItem?.OfficialRating && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: scaleSize(8),
|
||||||
paddingVertical: 2,
|
paddingVertical: scaleSize(2),
|
||||||
borderRadius: 4,
|
borderRadius: scaleSize(4),
|
||||||
borderWidth: 1,
|
borderWidth: scaleSize(1),
|
||||||
borderColor: "rgba(255,255,255,0.5)",
|
borderColor: "rgba(255,255,255,0.5)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -584,15 +588,15 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 6,
|
gap: scaleSize(6),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: 60,
|
width: scaleSize(60),
|
||||||
height: 4,
|
height: scaleSize(4),
|
||||||
backgroundColor: "rgba(255,255,255,0.3)",
|
backgroundColor: "rgba(255,255,255,0.3)",
|
||||||
borderRadius: 2,
|
borderRadius: scaleSize(2),
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -624,7 +628,7 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 40,
|
bottom: scaleSize(40),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FlatList
|
<FlatList
|
||||||
@@ -633,12 +637,21 @@ export const TVHeroCarousel: React.FC<TVHeroCarouselProps> = ({
|
|||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
style={{ overflow: "visible" }}
|
style={{ overflow: "visible" }}
|
||||||
contentInset={{
|
contentContainerStyle={{
|
||||||
left: sizes.padding.horizontal,
|
paddingVertical: sizes.gaps.small,
|
||||||
right: sizes.padding.horizontal,
|
paddingLeft: sizes.padding.horizontal,
|
||||||
|
paddingRight: sizes.padding.horizontal,
|
||||||
}}
|
}}
|
||||||
contentOffset={{ x: -sizes.padding.horizontal, y: 0 }}
|
// Below is a work around with the contentInset, same in infiniteScrollingCollectionList, if okay on apple remove
|
||||||
contentContainerStyle={{ paddingVertical: sizes.gaps.small }}
|
// 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}
|
renderItem={renderHeroCard}
|
||||||
removeClippedSubviews={false}
|
removeClippedSubviews={false}
|
||||||
initialNumToRender={8}
|
initialNumToRender={8}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package expo.modules.mpvplayer
|
package expo.modules.mpvplayer
|
||||||
|
|
||||||
|
import android.app.UiModeManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
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_DOUBLE = 5
|
||||||
const val MPV_FORMAT_NODE = 6
|
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 {
|
interface Delegate {
|
||||||
fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double)
|
fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double)
|
||||||
fun onPauseChanged(isPaused: Boolean)
|
fun onPauseChanged(isPaused: Boolean)
|
||||||
@@ -157,7 +164,15 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
MPVLib.setOptionString("opengl-es", "yes")
|
MPVLib.setOptionString("opengl-es", "yes")
|
||||||
|
|
||||||
// Hardware video decoding
|
// 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")
|
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
|
||||||
|
|
||||||
// Cache settings for better network streaming
|
// Cache settings for better network streaming
|
||||||
|
|||||||
Reference in New Issue
Block a user