mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
added internal sub customization - mpv
This commit is contained in:
parent
579b0a77b3
commit
2921b3eb1f
6 changed files with 224 additions and 3 deletions
|
|
@ -342,6 +342,119 @@ class MPVView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Subtitle Styling Methods
|
||||
|
||||
fun setSubtitleSize(size: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle size: $size")
|
||||
MPVLib.setPropertyInt("sub-font-size", size)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleColor(color: String) {
|
||||
if (isMpvInitialized) {
|
||||
// MPV expects color in #AARRGGBB format, but we receive #RRGGBB
|
||||
// Convert to MPV format with full opacity
|
||||
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||
Log.d(TAG, "Setting subtitle color: $mpvColor")
|
||||
MPVLib.setPropertyString("sub-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBackgroundColor(color: String, opacity: Float) {
|
||||
if (isMpvInitialized) {
|
||||
// Convert opacity (0-1) to hex (00-FF)
|
||||
val alphaHex = (opacity * 255).toInt().coerceIn(0, 255).let {
|
||||
String.format("%02X", it)
|
||||
}
|
||||
// MPV format: #AARRGGBB
|
||||
val baseColor = if (color.startsWith("#")) color.substring(1) else color
|
||||
val mpvColor = "#${alphaHex}${baseColor.takeLast(6)}"
|
||||
Log.d(TAG, "Setting subtitle background: $mpvColor (opacity: $opacity)")
|
||||
MPVLib.setPropertyString("sub-back-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBorderSize(size: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle border size: $size")
|
||||
MPVLib.setPropertyInt("sub-border-size", size)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBorderColor(color: String) {
|
||||
if (isMpvInitialized) {
|
||||
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||
Log.d(TAG, "Setting subtitle border color: $mpvColor")
|
||||
MPVLib.setPropertyString("sub-border-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleShadow(enabled: Boolean, offset: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle shadow: enabled=$enabled, offset=$offset")
|
||||
if (enabled) {
|
||||
MPVLib.setPropertyInt("sub-shadow-offset", offset)
|
||||
MPVLib.setPropertyString("sub-shadow-color", "#80000000")
|
||||
} else {
|
||||
MPVLib.setPropertyInt("sub-shadow-offset", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitlePosition(pos: Int) {
|
||||
if (isMpvInitialized) {
|
||||
// sub-pos: 0=top, 100=bottom, can go beyond 100 for more offset
|
||||
// UI sends bottomOffset (0=at bottom, higher=more up from bottom)
|
||||
// Convert: MPV pos = 100 - (bottomOffset / screenHeightFactor)
|
||||
// Simplified: just pass pos directly, UI should convert
|
||||
Log.d(TAG, "Setting subtitle position: $pos")
|
||||
MPVLib.setPropertyInt("sub-pos", pos)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleDelay(delaySec: Double) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle delay: $delaySec seconds")
|
||||
MPVLib.setPropertyDouble("sub-delay", delaySec)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleScale(scale: Double) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle scale: $scale")
|
||||
MPVLib.setPropertyDouble("sub-scale", scale)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleAlignment(align: String) {
|
||||
if (isMpvInitialized) {
|
||||
// MPV sub-justify values: left, center, right, auto
|
||||
val mpvAlign = when (align) {
|
||||
"left" -> "left"
|
||||
"right" -> "right"
|
||||
"center" -> "center"
|
||||
else -> "center"
|
||||
}
|
||||
Log.d(TAG, "Setting subtitle alignment: $mpvAlign")
|
||||
MPVLib.setPropertyString("sub-justify", mpvAlign)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBold(bold: Boolean) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle bold: $bold")
|
||||
MPVLib.setPropertyString("sub-bold", if (bold) "yes" else "no")
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleItalic(italic: Boolean) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle italic: $italic")
|
||||
MPVLib.setPropertyString("sub-italic", if (italic) "yes" else "no")
|
||||
}
|
||||
}
|
||||
|
||||
// MPVLib.EventObserver implementation
|
||||
|
||||
override fun eventProperty(property: String) {
|
||||
|
|
|
|||
|
|
@ -190,4 +190,52 @@ class MpvPlayerViewManager(
|
|||
fun setGpuMode(view: MPVView, gpuMode: String?) {
|
||||
view.gpuMode = gpuMode ?: "gpu"
|
||||
}
|
||||
|
||||
// Subtitle Styling Props
|
||||
|
||||
@ReactProp(name = "subtitleSize", defaultInt = 48)
|
||||
fun setSubtitleSize(view: MPVView, size: Int) {
|
||||
view.setSubtitleSize(size)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleColor")
|
||||
fun setSubtitleColor(view: MPVView, color: String?) {
|
||||
view.setSubtitleColor(color ?: "#FFFFFF")
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBackgroundOpacity", defaultFloat = 0.0f)
|
||||
fun setSubtitleBackgroundOpacity(view: MPVView, opacity: Float) {
|
||||
// Black background with user-specified opacity
|
||||
view.setSubtitleBackgroundColor("#000000", opacity)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBorderSize", defaultInt = 3)
|
||||
fun setSubtitleBorderSize(view: MPVView, size: Int) {
|
||||
view.setSubtitleBorderSize(size)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBorderColor")
|
||||
fun setSubtitleBorderColor(view: MPVView, color: String?) {
|
||||
view.setSubtitleBorderColor(color ?: "#000000")
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleShadowEnabled", defaultBoolean = true)
|
||||
fun setSubtitleShadowEnabled(view: MPVView, enabled: Boolean) {
|
||||
view.setSubtitleShadow(enabled, if (enabled) 2 else 0)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitlePosition", defaultInt = 100)
|
||||
fun setSubtitlePosition(view: MPVView, pos: Int) {
|
||||
view.setSubtitlePosition(pos)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleDelay", defaultFloat = 0.0f)
|
||||
fun setSubtitleDelay(view: MPVView, delay: Float) {
|
||||
view.setSubtitleDelay(delay.toDouble())
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleAlignment")
|
||||
fun setSubtitleAlignment(view: MPVView, align: String?) {
|
||||
view.setSubtitleAlignment(align ?: "center")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -570,6 +570,16 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
screenDimensions={playerState.screenDimensions}
|
||||
decoderMode={settings.decoderMode}
|
||||
gpuMode={settings.gpuMode}
|
||||
// Subtitle Styling - pass to MPV for built-in subtitle customization
|
||||
subtitleSize={subtitleSize}
|
||||
subtitleColor={subtitleTextColor}
|
||||
subtitleBackgroundOpacity={subtitleBackground ? subtitleBgOpacity : 0}
|
||||
subtitleBorderSize={subtitleOutline ? subtitleOutlineWidth : 0}
|
||||
subtitleBorderColor={subtitleOutlineColor}
|
||||
subtitleShadowEnabled={subtitleTextShadow}
|
||||
subtitlePosition={100 - Math.floor(subtitleBottomOffset / 2)} // Convert bottomOffset to MPV position
|
||||
subtitleDelay={subtitleOffsetSec}
|
||||
subtitleAlignment={subtitleAlign}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,16 @@ export interface MpvPlayerProps {
|
|||
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
|
||||
decoderMode?: 'auto' | 'sw' | 'hw' | 'hw+';
|
||||
gpuMode?: 'gpu' | 'gpu-next';
|
||||
// Subtitle Styling
|
||||
subtitleSize?: number;
|
||||
subtitleColor?: string;
|
||||
subtitleBackgroundOpacity?: number;
|
||||
subtitleBorderSize?: number;
|
||||
subtitleBorderColor?: string;
|
||||
subtitleShadowEnabled?: boolean;
|
||||
subtitlePosition?: number;
|
||||
subtitleDelay?: number;
|
||||
subtitleAlignment?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
||||
|
|
@ -107,6 +117,16 @@ const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
|||
onTracksChanged={handleTracksChanged}
|
||||
decoderMode={props.decoderMode ?? 'auto'}
|
||||
gpuMode={props.gpuMode ?? 'gpu'}
|
||||
// Subtitle Styling
|
||||
subtitleSize={props.subtitleSize ?? 48}
|
||||
subtitleColor={props.subtitleColor ?? '#FFFFFF'}
|
||||
subtitleBackgroundOpacity={props.subtitleBackgroundOpacity ?? 0}
|
||||
subtitleBorderSize={props.subtitleBorderSize ?? 3}
|
||||
subtitleBorderColor={props.subtitleBorderColor ?? '#000000'}
|
||||
subtitleShadowEnabled={props.subtitleShadowEnabled ?? true}
|
||||
subtitlePosition={props.subtitlePosition ?? 100}
|
||||
subtitleDelay={props.subtitleDelay ?? 0}
|
||||
subtitleAlignment={props.subtitleAlignment ?? 'center'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,6 +34,16 @@ interface VideoSurfaceProps {
|
|||
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
|
||||
decoderMode?: 'auto' | 'sw' | 'hw' | 'hw+';
|
||||
gpuMode?: 'gpu' | 'gpu-next';
|
||||
// Subtitle Styling
|
||||
subtitleSize?: number;
|
||||
subtitleColor?: string;
|
||||
subtitleBackgroundOpacity?: number;
|
||||
subtitleBorderSize?: number;
|
||||
subtitleBorderColor?: string;
|
||||
subtitleShadowEnabled?: boolean;
|
||||
subtitlePosition?: number;
|
||||
subtitleDelay?: number;
|
||||
subtitleAlignment?: 'left' | 'center' | 'right';
|
||||
}
|
||||
|
||||
export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
||||
|
|
@ -59,6 +69,16 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
|||
onTracksChanged,
|
||||
decoderMode,
|
||||
gpuMode,
|
||||
// Subtitle Styling
|
||||
subtitleSize,
|
||||
subtitleColor,
|
||||
subtitleBackgroundOpacity,
|
||||
subtitleBorderSize,
|
||||
subtitleBorderColor,
|
||||
subtitleShadowEnabled,
|
||||
subtitlePosition,
|
||||
subtitleDelay,
|
||||
subtitleAlignment,
|
||||
}) => {
|
||||
// Use the actual stream URL
|
||||
const streamUrl = currentStreamUrl || processedStreamUrl;
|
||||
|
|
@ -119,6 +139,16 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
|||
onTracksChanged={onTracksChanged}
|
||||
decoderMode={decoderMode}
|
||||
gpuMode={gpuMode}
|
||||
// Subtitle Styling
|
||||
subtitleSize={subtitleSize}
|
||||
subtitleColor={subtitleColor}
|
||||
subtitleBackgroundOpacity={subtitleBackgroundOpacity}
|
||||
subtitleBorderSize={subtitleBorderSize}
|
||||
subtitleBorderColor={subtitleBorderColor}
|
||||
subtitleShadowEnabled={subtitleShadowEnabled}
|
||||
subtitlePosition={subtitlePosition}
|
||||
subtitleDelay={subtitleDelay}
|
||||
subtitleAlignment={subtitleAlignment}
|
||||
/>
|
||||
|
||||
{/* Gesture overlay - transparent, on top of the player */}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ interface SubtitleModalsProps {
|
|||
isLoadingSubtitles: boolean;
|
||||
customSubtitles: SubtitleCue[];
|
||||
availableSubtitles: WyzieSubtitle[];
|
||||
ksTextTracks: Array<{id: number, name: string, language?: string}>;
|
||||
ksTextTracks: Array<{ id: number, name: string, language?: string }>;
|
||||
selectedTextTrack: number;
|
||||
useCustomSubtitles: boolean;
|
||||
isKsPlayerActive?: boolean;
|
||||
|
|
@ -180,7 +180,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<TouchableOpacity
|
||||
key={sub.id}
|
||||
onPress={() => { setSelectedOnlineSubtitleId(sub.id); loadWyzieSubtitle(sub); }}
|
||||
style={{ padding: 5,paddingLeft: 8, paddingRight: 10, borderRadius: 12, backgroundColor: selectedOnlineSubtitleId === sub.id ? 'white' : 'rgba(255,255,255,0.05)', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', textAlignVertical: 'center' }}
|
||||
style={{ padding: 5, paddingLeft: 8, paddingRight: 10, borderRadius: 12, backgroundColor: selectedOnlineSubtitleId === sub.id ? 'white' : 'rgba(255,255,255,0.05)', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', textAlignVertical: 'center' }}
|
||||
>
|
||||
<View>
|
||||
<Text style={{ marginLeft: 5, color: selectedOnlineSubtitleId === sub.id ? 'black' : 'white', fontWeight: '600' }}>{sub.display}</Text>
|
||||
|
|
@ -328,7 +328,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Align</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
{([ { key: 'left', icon: 'format-align-left' }, { key: 'center', icon: 'format-align-center' }, { key: 'right', icon: 'format-align-right' } ] as const).map(a => (
|
||||
{([{ key: 'left', icon: 'format-align-left' }, { key: 'center', icon: 'format-align-center' }, { key: 'right', icon: 'format-align-right' }] as const).map(a => (
|
||||
<TouchableOpacity key={a.key} onPress={() => setSubtitleAlign(a.key)} style={{ paddingHorizontal: isCompact ? 8 : 10, paddingVertical: isCompact ? 4 : 6, borderRadius: 8, backgroundColor: subtitleAlign === a.key ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)' }}>
|
||||
<MaterialIcons name={a.icon as any} size={18} color="#FFFFFF" />
|
||||
</TouchableOpacity>
|
||||
|
|
|
|||
Loading…
Reference in a new issue