mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-06 20:42:55 +01:00
fix: a few playback and home recommendation issues
Fixed the home recommendation display to use proper images. Fixed an issue for the subtitles background rendering Fixed playback resume from position Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
@@ -543,11 +543,6 @@ export default function page() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Gets the initial playback position in seconds. */
|
|
||||||
const _startPosition = useMemo(() => {
|
|
||||||
return ticksToSeconds(getInitialPlaybackTicks());
|
|
||||||
}, [getInitialPlaybackTicks]);
|
|
||||||
|
|
||||||
/** Build video source config for MPV */
|
/** Build video source config for MPV */
|
||||||
const videoSource = useMemo<MpvVideoSource | undefined>(() => {
|
const videoSource = useMemo<MpvVideoSource | undefined>(() => {
|
||||||
if (!stream?.url) return undefined;
|
if (!stream?.url) return undefined;
|
||||||
@@ -1104,6 +1099,13 @@ export default function page() {
|
|||||||
applySubtitleSettings();
|
applySubtitleSettings();
|
||||||
}, [isVideoLoaded, settings]);
|
}, [isVideoLoaded, settings]);
|
||||||
|
|
||||||
|
// Seek to resume position after file is loaded (MPV_EVENT_FILE_LOADED)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tracksReady || !videoRef.current) return;
|
||||||
|
const ticks = getInitialPlaybackTicks();
|
||||||
|
videoRef.current?.seekTo?.(ticksToSeconds(ticks));
|
||||||
|
}, [tracksReady, getInitialPlaybackTicks]);
|
||||||
|
|
||||||
// Apply initial playback speed when video loads
|
// Apply initial playback speed when video loads
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVideoLoaded || !videoRef.current) return;
|
if (!isVideoLoaded || !videoRef.current) return;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
subtitleIndex: selectedOptions.subtitleIndex?.toString() ?? "",
|
subtitleIndex: selectedOptions.subtitleIndex?.toString() ?? "",
|
||||||
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
mediaSourceId: selectedOptions.mediaSource?.Id ?? "",
|
||||||
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
bitrateValue: selectedOptions.bitrate?.value?.toString() ?? "",
|
||||||
|
playbackPosition: item.UserData?.PlaybackPositionTicks?.toString() ?? "0",
|
||||||
});
|
});
|
||||||
|
|
||||||
const queryString = queryParams.toString();
|
const queryString = queryParams.toString();
|
||||||
|
|||||||
@@ -463,6 +463,18 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
MPVLib.setPropertyInt("sub-font-size", size)
|
MPVLib.setPropertyInt("sub-font-size", size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBorderStyle(style: String) {
|
||||||
|
MPVLib.setPropertyString("sub-border-style", style)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBackgroundColor(color: String) {
|
||||||
|
MPVLib.setPropertyString("sub-back-color", color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleAssOverride(mode: String) {
|
||||||
|
MPVLib.setPropertyString("sub-ass-override", mode)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Audio Track Controls
|
// MARK: - Audio Track Controls
|
||||||
|
|
||||||
fun getAudioTracks(): List<Map<String, Any>> {
|
fun getAudioTracks(): List<Map<String, Any>> {
|
||||||
|
|||||||
@@ -151,6 +151,18 @@ class MpvPlayerModule : Module() {
|
|||||||
view.setSubtitleFontSize(size)
|
view.setSubtitleFontSize(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncFunction("setSubtitleBorderStyle") { view: MpvPlayerView, style: String ->
|
||||||
|
view.setSubtitleBorderStyle(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFunction("setSubtitleBackgroundColor") { view: MpvPlayerView, color: String ->
|
||||||
|
view.setSubtitleBackgroundColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFunction("setSubtitleAssOverride") { view: MpvPlayerView, mode: String ->
|
||||||
|
view.setSubtitleAssOverride(mode)
|
||||||
|
}
|
||||||
|
|
||||||
// Audio track functions
|
// Audio track functions
|
||||||
AsyncFunction("getAudioTracks") { view: MpvPlayerView ->
|
AsyncFunction("getAudioTracks") { view: MpvPlayerView ->
|
||||||
view.getAudioTracks()
|
view.getAudioTracks()
|
||||||
|
|||||||
@@ -272,6 +272,18 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
|
|||||||
renderer?.setSubtitleFontSize(size)
|
renderer?.setSubtitleFontSize(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBorderStyle(style: String) {
|
||||||
|
renderer?.setSubtitleBorderStyle(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleBackgroundColor(color: String) {
|
||||||
|
renderer?.setSubtitleBackgroundColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSubtitleAssOverride(mode: String) {
|
||||||
|
renderer?.setSubtitleAssOverride(mode)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Audio Track Controls
|
// MARK: - Audio Track Controls
|
||||||
|
|
||||||
fun getAudioTracks(): List<Map<String, Any>> {
|
fun getAudioTracks(): List<Map<String, Any>> {
|
||||||
|
|||||||
@@ -243,6 +243,7 @@ internal object TvRecommendationsPublisher {
|
|||||||
.setContentId(providerId)
|
.setContentId(providerId)
|
||||||
.setIntentUri(buildIntentUri(context, item.optString("playRoute").ifBlank { item.optString("route") }))
|
.setIntentUri(buildIntentUri(context, item.optString("playRoute").ifBlank { item.optString("route") }))
|
||||||
.setWeight(weight)
|
.setWeight(weight)
|
||||||
|
.setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_16_9)
|
||||||
|
|
||||||
item.optString("subtitle").takeIf { it.isNotBlank() }?.let {
|
item.optString("subtitle").takeIf { it.isNotBlank() }?.let {
|
||||||
builder.setDescription(it)
|
builder.setDescription(it)
|
||||||
@@ -250,7 +251,6 @@ internal object TvRecommendationsPublisher {
|
|||||||
|
|
||||||
imageUrl.takeIf { it.isNotBlank() }?.let {
|
imageUrl.takeIf { it.isNotBlank() }?.let {
|
||||||
val imageUri = Uri.parse(it)
|
val imageUri = Uri.parse(it)
|
||||||
builder.setPosterArtUri(imageUri)
|
|
||||||
builder.setThumbnailUri(imageUri)
|
builder.setThumbnailUri(imageUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,30 +25,60 @@ export interface TVDiscoveryPayload {
|
|||||||
sections: TVDiscoverySection[];
|
sections: TVDiscoverySection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTVDiscoveryImageUrl(
|
function getTVDiscoveryImage(
|
||||||
item: BaseItemDto,
|
item: BaseItemDto,
|
||||||
api: Api,
|
api: Api,
|
||||||
): string | undefined {
|
): { url: string } | undefined {
|
||||||
const baseUrl = api.basePath;
|
const baseUrl = api.basePath;
|
||||||
|
|
||||||
if (item.Type === "Episode") {
|
// 1. Episode backdrop
|
||||||
if (item.SeriesId && item.SeriesPrimaryImageTag) {
|
const episodeBackdrop = item.BackdropImageTags?.[0];
|
||||||
return `${baseUrl}/Items/${item.SeriesId}/Images/Primary?quality=90&tag=${encodeURIComponent(item.SeriesPrimaryImageTag)}&width=500`;
|
if (item.Id && episodeBackdrop) {
|
||||||
}
|
return {
|
||||||
|
url:
|
||||||
if (item.ParentPrimaryImageItemId && item.ParentPrimaryImageTag) {
|
`${baseUrl}/Items/${item.Id}/Images/Backdrop/0` +
|
||||||
return `${baseUrl}/Items/${item.ParentPrimaryImageItemId}/Images/Primary?quality=90&tag=${encodeURIComponent(item.ParentPrimaryImageTag)}&width=500`;
|
`?fillWidth=1920` +
|
||||||
}
|
`&fillHeight=1080` +
|
||||||
|
`&quality=90` +
|
||||||
|
`&tag=${encodeURIComponent(episodeBackdrop)}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Series backdrop
|
||||||
|
if (item.SeriesId) {
|
||||||
|
return {
|
||||||
|
url:
|
||||||
|
`${baseUrl}/Items/${item.SeriesId}/Images/Backdrop` +
|
||||||
|
`?fillWidth=1920` +
|
||||||
|
`&fillHeight=1080` +
|
||||||
|
`&quality=90`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Generic item backdrop
|
||||||
|
const backdrop = item.BackdropImageTags?.[0];
|
||||||
|
if (item.Id && backdrop) {
|
||||||
|
return {
|
||||||
|
url:
|
||||||
|
`${baseUrl}/Items/${item.Id}/Images/Backdrop/0` +
|
||||||
|
`?fillWidth=1920` +
|
||||||
|
`&fillHeight=1080` +
|
||||||
|
`&quality=90` +
|
||||||
|
`&tag=${encodeURIComponent(backdrop)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Last resort: crop poster into landscape
|
||||||
const primaryTag = item.ImageTags?.Primary;
|
const primaryTag = item.ImageTags?.Primary;
|
||||||
if (item.Id && primaryTag) {
|
if (item.Id && primaryTag) {
|
||||||
return `${baseUrl}/Items/${item.Id}/Images/Primary?quality=90&tag=${encodeURIComponent(primaryTag)}&width=500`;
|
return {
|
||||||
}
|
url:
|
||||||
|
`${baseUrl}/Items/${item.Id}/Images/Primary` +
|
||||||
const backdropTag = item.BackdropImageTags?.[0];
|
`?fillWidth=1920` +
|
||||||
if (item.Id && backdropTag) {
|
`&fillHeight=1080` +
|
||||||
return `${baseUrl}/Items/${item.Id}/Images/Backdrop/0?quality=90&tag=${encodeURIComponent(backdropTag)}&width=800`;
|
`&quality=90` +
|
||||||
|
`&tag=${encodeURIComponent(primaryTag)}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -98,15 +128,18 @@ function sectionFromItems(
|
|||||||
const payloadItems = (items || [])
|
const payloadItems = (items || [])
|
||||||
.filter((item) => item.Id && item.Name)
|
.filter((item) => item.Id && item.Name)
|
||||||
.slice(0, TV_DISCOVERY_ITEM_LIMIT)
|
.slice(0, TV_DISCOVERY_ITEM_LIMIT)
|
||||||
.map((item) => ({
|
.map((item) => {
|
||||||
id: item.Id!,
|
const image = getTVDiscoveryImage(item, api);
|
||||||
itemType: item.Type || undefined,
|
return {
|
||||||
title: getTVDiscoveryTitle(item),
|
id: item.Id!,
|
||||||
subtitle: getTVDiscoverySubtitle(item),
|
itemType: item.Type || undefined,
|
||||||
imageUrl: getTVDiscoveryImageUrl(item, api),
|
title: getTVDiscoveryTitle(item),
|
||||||
route: `streamyfin://topshelf/item?id=${encodeURIComponent(item.Id!)}&type=${encodeURIComponent(item.Type || "")}`,
|
subtitle: getTVDiscoverySubtitle(item),
|
||||||
playRoute: `streamyfin://topshelf/play?id=${encodeURIComponent(item.Id!)}`,
|
imageUrl: image?.url,
|
||||||
}));
|
route: `streamyfin://topshelf/item?id=${encodeURIComponent(item.Id!)}&type=${encodeURIComponent(item.Type || "")}`,
|
||||||
|
playRoute: `streamyfin://topshelf/play?id=${encodeURIComponent(item.Id!)}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (payloadItems.length === 0) return null;
|
if (payloadItems.length === 0) return null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user