mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-23 11:38:03 +00:00
feat(player): enhance technical info overlay with codec details
This commit is contained in:
@@ -481,6 +481,7 @@ export const Controls: FC<Props> = ({
|
||||
getTechnicalInfo={getTechnicalInfo}
|
||||
playMethod={playMethod}
|
||||
transcodeReasons={transcodeReasons}
|
||||
mediaSource={mediaSource}
|
||||
/>
|
||||
)}
|
||||
<Animated.View
|
||||
|
||||
@@ -788,6 +788,9 @@ export const Controls: FC<Props> = ({
|
||||
getTechnicalInfo={getTechnicalInfo}
|
||||
playMethod={playMethod}
|
||||
transcodeReasons={transcodeReasons}
|
||||
mediaSource={mediaSource}
|
||||
currentAudioIndex={audioIndex}
|
||||
currentSubtitleIndex={subtitleIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { type FC, memo, useCallback, useEffect, useState } from "react";
|
||||
import type { MediaSourceInfo } from "@jellyfin/sdk/lib/generated-client";
|
||||
import {
|
||||
type FC,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Platform, StyleSheet, Text, View } from "react-native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
@@ -20,6 +28,9 @@ interface TechnicalInfoOverlayProps {
|
||||
getTechnicalInfo: () => Promise<TechnicalInfo>;
|
||||
playMethod?: PlayMethod;
|
||||
transcodeReasons?: string[];
|
||||
mediaSource?: MediaSourceInfo | null;
|
||||
currentSubtitleIndex?: number;
|
||||
currentAudioIndex?: number;
|
||||
}
|
||||
|
||||
const formatBitrate = (bitsPerSecond: number): string => {
|
||||
@@ -48,10 +59,51 @@ const formatCodec = (codec: string): string => {
|
||||
flac: "FLAC",
|
||||
opus: "Opus",
|
||||
mp3: "MP3",
|
||||
// Subtitle codecs
|
||||
srt: "SRT",
|
||||
subrip: "SRT",
|
||||
ass: "ASS",
|
||||
ssa: "SSA",
|
||||
webvtt: "WebVTT",
|
||||
vtt: "WebVTT",
|
||||
pgs: "PGS",
|
||||
hdmv_pgs_subtitle: "PGS",
|
||||
dvd_subtitle: "VobSub",
|
||||
dvdsub: "VobSub",
|
||||
mov_text: "MOV Text",
|
||||
cc_dec: "CC",
|
||||
eia_608: "CC",
|
||||
};
|
||||
return codecMap[codec.toLowerCase()] || codec.toUpperCase();
|
||||
};
|
||||
|
||||
const formatAudioChannels = (channels: number): string => {
|
||||
switch (channels) {
|
||||
case 1:
|
||||
return "Mono";
|
||||
case 2:
|
||||
return "Stereo";
|
||||
case 6:
|
||||
return "5.1";
|
||||
case 8:
|
||||
return "7.1";
|
||||
default:
|
||||
return `${channels}ch`;
|
||||
}
|
||||
};
|
||||
|
||||
const formatVideoRange = (range?: string | null): string | null => {
|
||||
if (!range || range === "SDR") return null;
|
||||
const rangeMap: Record<string, string> = {
|
||||
HDR10: "HDR10",
|
||||
HDR10Plus: "HDR10+",
|
||||
HLG: "HLG",
|
||||
"Dolby Vision": "Dolby Vision",
|
||||
DolbyVision: "Dolby Vision",
|
||||
};
|
||||
return rangeMap[range] || range;
|
||||
};
|
||||
|
||||
const formatFps = (fps: number): string => {
|
||||
// Common frame rates
|
||||
if (Math.abs(fps - 23.976) < 0.01) return "23.976";
|
||||
@@ -127,6 +179,9 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
getTechnicalInfo,
|
||||
playMethod,
|
||||
transcodeReasons,
|
||||
mediaSource,
|
||||
currentSubtitleIndex,
|
||||
currentAudioIndex,
|
||||
}) => {
|
||||
const { settings } = useSettings();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -134,6 +189,39 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
|
||||
const opacity = useSharedValue(0);
|
||||
|
||||
// Extract stream info from media source
|
||||
const streamInfo = useMemo(() => {
|
||||
if (!mediaSource?.MediaStreams) return null;
|
||||
|
||||
const videoStream = mediaSource.MediaStreams.find(
|
||||
(s) => s.Type === "Video",
|
||||
);
|
||||
const audioStream = mediaSource.MediaStreams.find(
|
||||
(s) =>
|
||||
s.Type === "Audio" &&
|
||||
(currentAudioIndex !== undefined
|
||||
? s.Index === currentAudioIndex
|
||||
: s.IsDefault),
|
||||
);
|
||||
const subtitleStream = mediaSource.MediaStreams.find(
|
||||
(s) =>
|
||||
s.Type === "Subtitle" &&
|
||||
currentSubtitleIndex !== undefined &&
|
||||
currentSubtitleIndex >= 0 &&
|
||||
s.Index === currentSubtitleIndex,
|
||||
);
|
||||
|
||||
return {
|
||||
container: mediaSource.Container,
|
||||
videoRange: videoStream?.VideoRangeType,
|
||||
bitDepth: videoStream?.BitDepth,
|
||||
audioChannels: audioStream?.Channels,
|
||||
audioCodecFromSource: audioStream?.Codec,
|
||||
subtitleCodec: subtitleStream?.Codec,
|
||||
subtitleTitle: subtitleStream?.DisplayTitle,
|
||||
};
|
||||
}, [mediaSource, currentAudioIndex, currentSubtitleIndex]);
|
||||
|
||||
// Animate visibility based on visible prop only (stays visible regardless of controls)
|
||||
useEffect(() => {
|
||||
opacity.value = withTiming(visible ? 1 : 0, {
|
||||
@@ -214,6 +302,10 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
{info?.videoWidth && info?.videoHeight && (
|
||||
<Text style={textStyle}>
|
||||
{info.videoWidth}x{info.videoHeight}
|
||||
{streamInfo?.bitDepth ? ` ${streamInfo.bitDepth}bit` : ""}
|
||||
{formatVideoRange(streamInfo?.videoRange)
|
||||
? ` ${formatVideoRange(streamInfo?.videoRange)}`
|
||||
: ""}
|
||||
</Text>
|
||||
)}
|
||||
{info?.videoCodec && (
|
||||
@@ -223,7 +315,17 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
||||
</Text>
|
||||
)}
|
||||
{info?.audioCodec && (
|
||||
<Text style={textStyle}>Audio: {formatCodec(info.audioCodec)}</Text>
|
||||
<Text style={textStyle}>
|
||||
Audio: {formatCodec(info.audioCodec)}
|
||||
{streamInfo?.audioChannels
|
||||
? ` ${formatAudioChannels(streamInfo.audioChannels)}`
|
||||
: ""}
|
||||
</Text>
|
||||
)}
|
||||
{streamInfo?.subtitleCodec && (
|
||||
<Text style={textStyle}>
|
||||
Subtitle: {formatCodec(streamInfo.subtitleCodec)}
|
||||
</Text>
|
||||
)}
|
||||
{(info?.videoBitrate || info?.audioBitrate) && (
|
||||
<Text style={textStyle}>
|
||||
|
||||
Reference in New Issue
Block a user