mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-13 13:30:46 +00:00
fixed subtitle rendering, added aspect ratio support
This commit is contained in:
parent
9504d48607
commit
3cea291901
5 changed files with 256 additions and 7 deletions
|
|
@ -28,6 +28,7 @@ class MPVView @JvmOverloads constructor(
|
||||||
var onProgressCallback: ((position: Double, duration: Double) -> Unit)? = null
|
var onProgressCallback: ((position: Double, duration: Double) -> Unit)? = null
|
||||||
var onEndCallback: (() -> Unit)? = null
|
var onEndCallback: (() -> Unit)? = null
|
||||||
var onErrorCallback: ((message: String) -> Unit)? = null
|
var onErrorCallback: ((message: String) -> Unit)? = null
|
||||||
|
var onTracksChangedCallback: ((audioTracks: List<Map<String, Any>>, subtitleTracks: List<Map<String, Any>>) -> Unit)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
surfaceTextureListener = this
|
surfaceTextureListener = this
|
||||||
|
|
@ -89,8 +90,13 @@ class MPVView @JvmOverloads constructor(
|
||||||
MPVLib.setOptionString("vo", "gpu")
|
MPVLib.setOptionString("vo", "gpu")
|
||||||
MPVLib.setOptionString("gpu-context", "android")
|
MPVLib.setOptionString("gpu-context", "android")
|
||||||
MPVLib.setOptionString("opengl-es", "yes")
|
MPVLib.setOptionString("opengl-es", "yes")
|
||||||
MPVLib.setOptionString("hwdec", "mediacodec,mediacodec-copy")
|
|
||||||
MPVLib.setOptionString("hwdec-codecs", "h264,hevc,mpeg4,mpeg2video,vp8,vp9,av1")
|
// Hardware decoding - use mediacodec-copy to allow subtitle overlay
|
||||||
|
// 'mediacodec-copy' copies frames to CPU memory which enables subtitle blending
|
||||||
|
MPVLib.setOptionString("hwdec", "mediacodec-copy")
|
||||||
|
MPVLib.setOptionString("hwdec-codecs", "all")
|
||||||
|
|
||||||
|
// Audio output
|
||||||
MPVLib.setOptionString("ao", "audiotrack,opensles")
|
MPVLib.setOptionString("ao", "audiotrack,opensles")
|
||||||
|
|
||||||
// Network caching for streaming
|
// Network caching for streaming
|
||||||
|
|
@ -99,6 +105,43 @@ class MPVView @JvmOverloads constructor(
|
||||||
MPVLib.setOptionString("cache", "yes")
|
MPVLib.setOptionString("cache", "yes")
|
||||||
MPVLib.setOptionString("cache-secs", "30")
|
MPVLib.setOptionString("cache-secs", "30")
|
||||||
|
|
||||||
|
// Subtitle configuration - CRITICAL for Android
|
||||||
|
MPVLib.setOptionString("sub-auto", "fuzzy") // Auto-load subtitles
|
||||||
|
MPVLib.setOptionString("sub-visibility", "yes") // Make subtitles visible by default
|
||||||
|
MPVLib.setOptionString("sub-font-size", "48") // Larger font size for mobile readability
|
||||||
|
MPVLib.setOptionString("sub-pos", "95") // Position at bottom (0-100, 100 = very bottom)
|
||||||
|
MPVLib.setOptionString("sub-color", "#FFFFFFFF") // White color
|
||||||
|
MPVLib.setOptionString("sub-border-size", "3") // Thicker border for readability
|
||||||
|
MPVLib.setOptionString("sub-border-color", "#FF000000") // Black border
|
||||||
|
MPVLib.setOptionString("sub-shadow-offset", "2") // Add shadow for better visibility
|
||||||
|
MPVLib.setOptionString("sub-shadow-color", "#80000000") // Semi-transparent black shadow
|
||||||
|
|
||||||
|
// Font configuration - point to Android system fonts for all language support
|
||||||
|
MPVLib.setOptionString("osd-fonts-dir", "/system/fonts")
|
||||||
|
MPVLib.setOptionString("sub-fonts-dir", "/system/fonts")
|
||||||
|
MPVLib.setOptionString("sub-font", "Roboto") // Default fallback font
|
||||||
|
// Allow embedded fonts in ASS/SSA but fallback to system fonts
|
||||||
|
MPVLib.setOptionString("embeddedfonts", "yes")
|
||||||
|
|
||||||
|
// Language/encoding support for various subtitle formats
|
||||||
|
MPVLib.setOptionString("sub-codepage", "auto") // Auto-detect encoding (supports UTF-8, Latin, CJK, etc.)
|
||||||
|
|
||||||
|
MPVLib.setOptionString("osc", "no") // Disable on screen controller
|
||||||
|
MPVLib.setOptionString("osd-level", "1")
|
||||||
|
|
||||||
|
// Critical for subtitle rendering on Android GPU
|
||||||
|
// blend-subtitles=no lets the GPU renderer handle subtitle overlay properly
|
||||||
|
MPVLib.setOptionString("blend-subtitles", "no")
|
||||||
|
MPVLib.setOptionString("sub-use-margins", "no")
|
||||||
|
// Use 'scale' to allow ASS styling but with our scale and font overrides
|
||||||
|
// This preserves styled subtitles while having font fallbacks
|
||||||
|
MPVLib.setOptionString("sub-ass-override", "scale")
|
||||||
|
MPVLib.setOptionString("sub-scale", "1.0")
|
||||||
|
MPVLib.setOptionString("sub-fix-timing", "yes") // Fix timing for SRT subtitles
|
||||||
|
|
||||||
|
// Force subtitle rendering
|
||||||
|
MPVLib.setOptionString("sid", "auto") // Auto-select subtitle track
|
||||||
|
|
||||||
// Disable terminal/input
|
// Disable terminal/input
|
||||||
MPVLib.setOptionString("terminal", "no")
|
MPVLib.setOptionString("terminal", "no")
|
||||||
MPVLib.setOptionString("input-default-bindings", "no")
|
MPVLib.setOptionString("input-default-bindings", "no")
|
||||||
|
|
@ -120,6 +163,11 @@ class MPVView @JvmOverloads constructor(
|
||||||
MPVLib.observeProperty("width", MPV_FORMAT_INT64)
|
MPVLib.observeProperty("width", MPV_FORMAT_INT64)
|
||||||
MPVLib.observeProperty("height", MPV_FORMAT_INT64)
|
MPVLib.observeProperty("height", MPV_FORMAT_INT64)
|
||||||
MPVLib.observeProperty("track-list", MPV_FORMAT_NONE)
|
MPVLib.observeProperty("track-list", MPV_FORMAT_NONE)
|
||||||
|
|
||||||
|
// Observe subtitle properties for debugging
|
||||||
|
MPVLib.observeProperty("sid", MPV_FORMAT_INT64)
|
||||||
|
MPVLib.observeProperty("sub-visibility", MPV_FORMAT_FLAG)
|
||||||
|
MPVLib.observeProperty("sub-text", MPV_FORMAT_NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFile(url: String) {
|
private fun loadFile(url: String) {
|
||||||
|
|
@ -176,11 +224,52 @@ class MPVView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSubtitleTrack(trackId: Int) {
|
fun setSubtitleTrack(trackId: Int) {
|
||||||
|
Log.d(TAG, "setSubtitleTrack called: trackId=$trackId, isMpvInitialized=$isMpvInitialized")
|
||||||
if (isMpvInitialized) {
|
if (isMpvInitialized) {
|
||||||
if (trackId == -1) {
|
if (trackId == -1) {
|
||||||
|
Log.d(TAG, "Disabling subtitles (sid=no)")
|
||||||
MPVLib.setPropertyString("sid", "no")
|
MPVLib.setPropertyString("sid", "no")
|
||||||
|
MPVLib.setPropertyString("sub-visibility", "no")
|
||||||
} else {
|
} else {
|
||||||
|
Log.d(TAG, "Setting subtitle track to: $trackId")
|
||||||
MPVLib.setPropertyInt("sid", trackId)
|
MPVLib.setPropertyInt("sid", trackId)
|
||||||
|
// Ensure subtitles are visible
|
||||||
|
MPVLib.setPropertyString("sub-visibility", "yes")
|
||||||
|
|
||||||
|
// Debug: Verify the subtitle was set correctly
|
||||||
|
val currentSid = MPVLib.getPropertyInt("sid")
|
||||||
|
val subVisibility = MPVLib.getPropertyString("sub-visibility")
|
||||||
|
val subDelay = MPVLib.getPropertyDouble("sub-delay")
|
||||||
|
val subScale = MPVLib.getPropertyDouble("sub-scale")
|
||||||
|
Log.d(TAG, "After setting - sid=$currentSid, sub-visibility=$subVisibility, sub-delay=$subDelay, sub-scale=$subScale")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setResizeMode(mode: String) {
|
||||||
|
Log.d(TAG, "setResizeMode called: mode=$mode, isMpvInitialized=$isMpvInitialized")
|
||||||
|
if (isMpvInitialized) {
|
||||||
|
when (mode) {
|
||||||
|
"contain" -> {
|
||||||
|
// Letterbox - show entire video with black bars
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
|
"cover" -> {
|
||||||
|
// Fill/crop - zoom to fill, cropping edges
|
||||||
|
MPVLib.setPropertyDouble("panscan", 1.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
|
"stretch" -> {
|
||||||
|
// Stretch - disable aspect ratio
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "no")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Default to contain
|
||||||
|
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||||
|
MPVLib.setPropertyString("keepaspect", "yes")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,11 +280,59 @@ class MPVView @JvmOverloads constructor(
|
||||||
Log.d(TAG, "Property changed: $property")
|
Log.d(TAG, "Property changed: $property")
|
||||||
when (property) {
|
when (property) {
|
||||||
"track-list" -> {
|
"track-list" -> {
|
||||||
// Track list updated, could notify JS about available tracks
|
// Parse track list and notify React Native
|
||||||
|
parseAndSendTracks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseAndSendTracks() {
|
||||||
|
try {
|
||||||
|
val trackCount = MPVLib.getPropertyInt("track-list/count") ?: 0
|
||||||
|
Log.d(TAG, "Track count: $trackCount")
|
||||||
|
|
||||||
|
val audioTracks = mutableListOf<Map<String, Any>>()
|
||||||
|
val subtitleTracks = mutableListOf<Map<String, Any>>()
|
||||||
|
|
||||||
|
for (i in 0 until trackCount) {
|
||||||
|
val type = MPVLib.getPropertyString("track-list/$i/type") ?: continue
|
||||||
|
val id = MPVLib.getPropertyInt("track-list/$i/id") ?: continue
|
||||||
|
val title = MPVLib.getPropertyString("track-list/$i/title") ?: ""
|
||||||
|
val lang = MPVLib.getPropertyString("track-list/$i/lang") ?: ""
|
||||||
|
val codec = MPVLib.getPropertyString("track-list/$i/codec") ?: ""
|
||||||
|
|
||||||
|
val trackName = when {
|
||||||
|
title.isNotEmpty() -> title
|
||||||
|
lang.isNotEmpty() -> lang.uppercase()
|
||||||
|
else -> "Track $id"
|
||||||
|
}
|
||||||
|
|
||||||
|
val track = mapOf(
|
||||||
|
"id" to id,
|
||||||
|
"name" to trackName,
|
||||||
|
"language" to lang,
|
||||||
|
"codec" to codec
|
||||||
|
)
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
"audio" -> {
|
||||||
|
Log.d(TAG, "Found audio track: $track")
|
||||||
|
audioTracks.add(track)
|
||||||
|
}
|
||||||
|
"sub" -> {
|
||||||
|
Log.d(TAG, "Found subtitle track: $track")
|
||||||
|
subtitleTracks.add(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending tracks - Audio: ${audioTracks.size}, Subtitles: ${subtitleTracks.size}")
|
||||||
|
onTracksChangedCallback?.invoke(audioTracks, subtitleTracks)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error parsing tracks", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun eventProperty(property: String, value: Long) {
|
override fun eventProperty(property: String, value: Long) {
|
||||||
Log.d(TAG, "Property $property = $value (Long)")
|
Log.d(TAG, "Property $property = $value (Long)")
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +381,22 @@ class MPVView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MPV_EVENT_END_FILE -> {
|
MPV_EVENT_END_FILE -> {
|
||||||
onEndCallback?.invoke()
|
Log.d(TAG, "MPV_EVENT_END_FILE")
|
||||||
|
|
||||||
|
// Heuristic: If duration is effectively 0 at end of file, it's a load error
|
||||||
|
val duration = MPVLib.getPropertyDouble("duration") ?: 0.0
|
||||||
|
val timePos = MPVLib.getPropertyDouble("time-pos") ?: 0.0
|
||||||
|
val eofReached = MPVLib.getPropertyBoolean("eof-reached") ?: false
|
||||||
|
|
||||||
|
Log.d(TAG, "End stats - Duration: $duration, Time: $timePos, EOF: $eofReached")
|
||||||
|
|
||||||
|
if (duration < 1.0 && !eofReached) {
|
||||||
|
val customError = "Unable to play media. Source may be unreachable."
|
||||||
|
Log.e(TAG, "Playback error detected (heuristic): $customError")
|
||||||
|
onErrorCallback?.invoke(customError)
|
||||||
|
} else {
|
||||||
|
onEndCallback?.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,35 @@ class MpvPlayerViewManager(
|
||||||
sendEvent(context, view.id, "onError", event)
|
sendEvent(context, view.id, "onError", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view.onTracksChangedCallback = { audioTracks, subtitleTracks ->
|
||||||
|
val event = Arguments.createMap().apply {
|
||||||
|
val audioArray = Arguments.createArray()
|
||||||
|
audioTracks.forEach { track ->
|
||||||
|
val trackMap = Arguments.createMap().apply {
|
||||||
|
putInt("id", track["id"] as Int)
|
||||||
|
putString("name", track["name"] as String)
|
||||||
|
putString("language", track["language"] as String)
|
||||||
|
putString("codec", track["codec"] as String)
|
||||||
|
}
|
||||||
|
audioArray.pushMap(trackMap)
|
||||||
|
}
|
||||||
|
putArray("audioTracks", audioArray)
|
||||||
|
|
||||||
|
val subtitleArray = Arguments.createArray()
|
||||||
|
subtitleTracks.forEach { track ->
|
||||||
|
val trackMap = Arguments.createMap().apply {
|
||||||
|
putInt("id", track["id"] as Int)
|
||||||
|
putString("name", track["name"] as String)
|
||||||
|
putString("language", track["language"] as String)
|
||||||
|
putString("codec", track["codec"] as String)
|
||||||
|
}
|
||||||
|
subtitleArray.pushMap(trackMap)
|
||||||
|
}
|
||||||
|
putArray("subtitleTracks", subtitleArray)
|
||||||
|
}
|
||||||
|
sendEvent(context, view.id, "onTracksChanged", event)
|
||||||
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,6 +101,7 @@ class MpvPlayerViewManager(
|
||||||
.put("onProgress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onProgress")))
|
.put("onProgress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onProgress")))
|
||||||
.put("onEnd", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onEnd")))
|
.put("onEnd", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onEnd")))
|
||||||
.put("onError", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onError")))
|
.put("onError", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onError")))
|
||||||
|
.put("onTracksChanged", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onTracksChanged")))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,4 +158,9 @@ class MpvPlayerViewManager(
|
||||||
// Intentionally ignoring - background color would block the TextureView content
|
// Intentionally ignoring - background color would block the TextureView content
|
||||||
// Leave the view transparent
|
// Leave the view transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "resizeMode")
|
||||||
|
fun setResizeMode(view: MPVView, resizeMode: String?) {
|
||||||
|
view.setResizeMode(resizeMode ?: "contain")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -396,11 +396,45 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
modals.setErrorDetails(JSON.stringify(err));
|
// Determine the actual error message
|
||||||
|
let displayError = 'An unknown error occurred';
|
||||||
|
|
||||||
|
if (typeof err?.error === 'string') {
|
||||||
|
displayError = err.error;
|
||||||
|
} else if (err?.error?.errorString) {
|
||||||
|
displayError = err.error.errorString;
|
||||||
|
} else if (err?.errorString) {
|
||||||
|
displayError = err.errorString;
|
||||||
|
} else if (typeof err === 'string') {
|
||||||
|
displayError = err;
|
||||||
|
} else {
|
||||||
|
displayError = JSON.stringify(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
modals.setErrorDetails(displayError);
|
||||||
modals.setShowErrorModal(true);
|
modals.setShowErrorModal(true);
|
||||||
}}
|
}}
|
||||||
onBuffer={(buf) => playerState.setIsBuffering(buf.isBuffering)}
|
onBuffer={(buf) => playerState.setIsBuffering(buf.isBuffering)}
|
||||||
onTracksUpdate={vlcHook.handleVlcTracksUpdate}
|
onTracksUpdate={vlcHook.handleVlcTracksUpdate}
|
||||||
|
onTracksChanged={(data) => {
|
||||||
|
console.log('[AndroidVideoPlayer] onTracksChanged:', data);
|
||||||
|
if (data?.audioTracks) {
|
||||||
|
const formatted = data.audioTracks.map((t: any) => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.name || `Track ${t.id}`,
|
||||||
|
language: t.language
|
||||||
|
}));
|
||||||
|
tracksHook.setRnVideoAudioTracks(formatted);
|
||||||
|
}
|
||||||
|
if (data?.subtitleTracks) {
|
||||||
|
const formatted = data.subtitleTracks.map((t: any) => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.name || `Track ${t.id}`,
|
||||||
|
language: t.language
|
||||||
|
}));
|
||||||
|
tracksHook.setRnVideoTextTracks(formatted);
|
||||||
|
}
|
||||||
|
}}
|
||||||
vlcPlayerRef={vlcHook.vlcPlayerRef}
|
vlcPlayerRef={vlcHook.vlcPlayerRef}
|
||||||
mpvPlayerRef={mpvPlayerRef}
|
mpvPlayerRef={mpvPlayerRef}
|
||||||
videoRef={videoRef}
|
videoRef={videoRef}
|
||||||
|
|
@ -504,8 +538,15 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
ksAudioTracks={tracksHook.ksAudioTracks}
|
ksAudioTracks={tracksHook.ksAudioTracks}
|
||||||
selectedAudioTrack={tracksHook.computedSelectedAudioTrack}
|
selectedAudioTrack={tracksHook.computedSelectedAudioTrack}
|
||||||
selectAudioTrack={(trackId) => {
|
selectAudioTrack={(trackId) => {
|
||||||
useVLC ? vlcHook.selectVlcAudioTrack(trackId) :
|
if (useVLC) {
|
||||||
|
vlcHook.selectVlcAudioTrack(trackId);
|
||||||
|
} else {
|
||||||
tracksHook.setSelectedAudioTrack(trackId === null ? null : { type: 'index', value: trackId });
|
tracksHook.setSelectedAudioTrack(trackId === null ? null : { type: 'index', value: trackId });
|
||||||
|
// Actually tell MPV to switch the audio track
|
||||||
|
if (trackId !== null && mpvPlayerRef.current) {
|
||||||
|
mpvPlayerRef.current.setAudioTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -527,7 +568,15 @@ const AndroidVideoPlayer: React.FC = () => {
|
||||||
fetchAvailableSubtitles={() => { }} // Placeholder
|
fetchAvailableSubtitles={() => { }} // Placeholder
|
||||||
loadWyzieSubtitle={() => { }} // Placeholder
|
loadWyzieSubtitle={() => { }} // Placeholder
|
||||||
selectTextTrack={(trackId) => {
|
selectTextTrack={(trackId) => {
|
||||||
useVLC ? vlcHook.selectVlcSubtitleTrack(trackId) : tracksHook.setSelectedTextTrack(trackId);
|
if (useVLC) {
|
||||||
|
vlcHook.selectVlcSubtitleTrack(trackId);
|
||||||
|
} else {
|
||||||
|
tracksHook.setSelectedTextTrack(trackId);
|
||||||
|
// Actually tell MPV to switch the subtitle track
|
||||||
|
if (mpvPlayerRef.current) {
|
||||||
|
mpvPlayerRef.current.setSubtitleTrack(trackId);
|
||||||
|
}
|
||||||
|
}
|
||||||
modals.setShowSubtitleModal(false);
|
modals.setShowSubtitleModal(false);
|
||||||
}}
|
}}
|
||||||
disableCustomSubtitles={() => { }} // Placeholder
|
disableCustomSubtitles={() => { }} // Placeholder
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ export interface MpvPlayerProps {
|
||||||
paused?: boolean;
|
paused?: boolean;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
|
resizeMode?: 'contain' | 'cover' | 'stretch';
|
||||||
style?: any;
|
style?: any;
|
||||||
onLoad?: (data: { duration: number; width: number; height: number }) => void;
|
onLoad?: (data: { duration: number; width: number; height: number }) => void;
|
||||||
onProgress?: (data: { currentTime: number; duration: number }) => void;
|
onProgress?: (data: { currentTime: number; duration: number }) => void;
|
||||||
onEnd?: () => void;
|
onEnd?: () => void;
|
||||||
onError?: (error: { error: string }) => void;
|
onError?: (error: { error: string }) => void;
|
||||||
|
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
||||||
|
|
@ -80,6 +82,11 @@ const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
||||||
props.onError?.(event?.nativeEvent);
|
props.onError?.(event?.nativeEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTracksChanged = (event: any) => {
|
||||||
|
console.log('[MpvPlayer] Native onTracksChanged event:', event?.nativeEvent);
|
||||||
|
props.onTracksChanged?.(event?.nativeEvent);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MpvPlayerNative
|
<MpvPlayerNative
|
||||||
ref={nativeRef}
|
ref={nativeRef}
|
||||||
|
|
@ -88,10 +95,12 @@ const MpvPlayer = forwardRef<MpvPlayerRef, MpvPlayerProps>((props, ref) => {
|
||||||
paused={props.paused ?? true}
|
paused={props.paused ?? true}
|
||||||
volume={props.volume ?? 1.0}
|
volume={props.volume ?? 1.0}
|
||||||
rate={props.rate ?? 1.0}
|
rate={props.rate ?? 1.0}
|
||||||
|
resizeMode={props.resizeMode ?? 'contain'}
|
||||||
onLoad={handleLoad}
|
onLoad={handleLoad}
|
||||||
onProgress={handleProgress}
|
onProgress={handleProgress}
|
||||||
onEnd={handleEnd}
|
onEnd={handleEnd}
|
||||||
onError={handleError}
|
onError={handleError}
|
||||||
|
onTracksChanged={handleTracksChanged}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ interface VideoSurfaceProps {
|
||||||
loadStartAtRef?: any;
|
loadStartAtRef?: any;
|
||||||
firstFrameAtRef?: any;
|
firstFrameAtRef?: any;
|
||||||
zoomScale?: number;
|
zoomScale?: number;
|
||||||
|
onTracksChanged?: (data: { audioTracks: any[]; subtitleTracks: any[] }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
||||||
|
|
@ -72,6 +73,7 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
||||||
onPinchGestureEvent,
|
onPinchGestureEvent,
|
||||||
onPinchHandlerStateChange,
|
onPinchHandlerStateChange,
|
||||||
screenDimensions,
|
screenDimensions,
|
||||||
|
onTracksChanged,
|
||||||
}) => {
|
}) => {
|
||||||
// Use the actual stream URL
|
// Use the actual stream URL
|
||||||
const streamUrl = currentStreamUrl || processedStreamUrl;
|
const streamUrl = currentStreamUrl || processedStreamUrl;
|
||||||
|
|
@ -122,11 +124,13 @@ export const VideoSurface: React.FC<VideoSurfaceProps> = ({
|
||||||
paused={paused}
|
paused={paused}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
rate={playbackSpeed}
|
rate={playbackSpeed}
|
||||||
|
resizeMode={resizeMode === 'none' ? 'contain' : resizeMode}
|
||||||
style={localStyles.player}
|
style={localStyles.player}
|
||||||
onLoad={handleLoad}
|
onLoad={handleLoad}
|
||||||
onProgress={handleProgress}
|
onProgress={handleProgress}
|
||||||
onEnd={handleEnd}
|
onEnd={handleEnd}
|
||||||
onError={handleError}
|
onError={handleError}
|
||||||
|
onTracksChanged={onTracksChanged}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Gesture overlay - transparent, on top of the player */}
|
{/* Gesture overlay - transparent, on top of the player */}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue