added internal sub customization - mpv

This commit is contained in:
tapframe 2025-12-27 18:37:03 +05:30
parent 579b0a77b3
commit 2921b3eb1f
6 changed files with 224 additions and 3 deletions

View file

@ -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) {

View file

@ -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")
}
}

View file

@ -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}
/>
)}

View file

@ -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'}
/>
);
});

View file

@ -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 */}

View file

@ -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>