mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
ksplayer sub rendering fix
This commit is contained in:
parent
9f461f7091
commit
985d01d5a9
10 changed files with 102 additions and 99 deletions
|
|
@ -132,7 +132,7 @@ class MPVView @JvmOverloads constructor(
|
|||
MPVLib.setOptionString("sub-auto", "fuzzy")
|
||||
MPVLib.setOptionString("sub-visibility", "yes")
|
||||
MPVLib.setOptionString("sub-font-size", "48")
|
||||
MPVLib.setOptionString("sub-pos", "95")
|
||||
MPVLib.setOptionString("sub-pos", "100")
|
||||
MPVLib.setOptionString("sub-color", "#FFFFFFFF")
|
||||
MPVLib.setOptionString("sub-border-size", "3")
|
||||
MPVLib.setOptionString("sub-border-color", "#FF000000")
|
||||
|
|
@ -146,15 +146,15 @@ class MPVView @JvmOverloads constructor(
|
|||
|
||||
MPVLib.setOptionString("sub-codepage", "auto")
|
||||
|
||||
MPVLib.setOptionString("osc", "no")
|
||||
MPVLib.setOptionString("osd-level", "1")
|
||||
|
||||
MPVLib.setOptionString("blend-subtitles", "no")
|
||||
MPVLib.setOptionString("sub-use-margins", "no")
|
||||
MPVLib.setOptionString("sub-ass-override", "scale")
|
||||
MPVLib.setOptionString("sub-use-margins", "yes")
|
||||
MPVLib.setOptionString("sub-ass-override", "force")
|
||||
MPVLib.setOptionString("sub-scale", "1.0")
|
||||
MPVLib.setOptionString("sub-fix-timing", "yes")
|
||||
|
||||
MPVLib.setOptionString("osc", "no")
|
||||
MPVLib.setOptionString("osd-level", "1")
|
||||
|
||||
MPVLib.setOptionString("sid", "auto")
|
||||
|
||||
MPVLib.setOptionString("terminal", "no")
|
||||
|
|
|
|||
|
|
@ -246,20 +246,15 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
|
||||
private func setupPlayerCallbacks() {
|
||||
// Configure KSOptions (use static defaults where required)
|
||||
// Configure KSOptions
|
||||
KSOptions.isAutoPlay = false
|
||||
#if targetEnvironment(simulator)
|
||||
// Simulator: disable hardware decode and MEPlayer to avoid VT/Vulkan issues
|
||||
KSOptions.hardwareDecode = false
|
||||
KSOptions.asynchronousDecompression = false
|
||||
KSOptions.secondPlayerType = nil
|
||||
#else
|
||||
// PERFORMANCE OPTIMIZATION: Enable asynchronous decompression globally
|
||||
// This ensures the global default is correct for all player instances
|
||||
KSOptions.asynchronousDecompression = true
|
||||
// Ensure hardware decode is enabled globally
|
||||
KSOptions.hardwareDecode = true
|
||||
#endif
|
||||
|
||||
// Set default subtitle font size - use standard size for readability
|
||||
SubtitleModel.textFontSize = 20.0 // Moderate size that works on most devices
|
||||
SubtitleModel.textBold = false
|
||||
|
||||
print("KSPlayerView: [PERF] Global settings: asyncDecomp=\(KSOptions.asynchronousDecompression), hwDecode=\(KSOptions.hardwareDecode)")
|
||||
}
|
||||
|
||||
|
|
@ -298,21 +293,14 @@ class KSPlayerView: UIView {
|
|||
|
||||
// Choose player pipeline based on format
|
||||
let isMKV = uri.lowercased().contains(".mkv")
|
||||
#if targetEnvironment(simulator)
|
||||
if isMKV {
|
||||
// MKV not supported on AVPlayer in Simulator and MEPlayer is disabled
|
||||
sendEvent("onError", ["error": "MKV playback is not supported in the iOS Simulator. Test on a real device."])
|
||||
}
|
||||
#else
|
||||
if isMKV {
|
||||
// Prefer MEPlayer (FFmpeg) for MKV on device
|
||||
// Prefer MEPlayer (FFmpeg) for MKV
|
||||
KSOptions.firstPlayerType = KSMEPlayer.self
|
||||
KSOptions.secondPlayerType = nil
|
||||
} else {
|
||||
KSOptions.firstPlayerType = KSAVPlayer.self
|
||||
KSOptions.secondPlayerType = KSMEPlayer.self
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create KSPlayerResource with validated URL
|
||||
let resource = KSPlayerResource(url: url, options: createOptions(with: headers), name: "Video")
|
||||
|
|
@ -376,15 +364,9 @@ class KSPlayerView: UIView {
|
|||
|
||||
// PERFORMANCE OPTIMIZATION: Hardware decode explicitly enabled
|
||||
// Ensure VideoToolbox hardware acceleration is always preferred for non-simulator
|
||||
#if targetEnvironment(simulator)
|
||||
options.hardwareDecode = false
|
||||
options.asynchronousDecompression = false
|
||||
#else
|
||||
options.hardwareDecode = true // Explicitly enable hardware decode
|
||||
// PERFORMANCE OPTIMIZATION: Asynchronous decompression (CRITICAL)
|
||||
// Offloads VideoToolbox decompression to background threads, preventing main thread stalls
|
||||
// Hardware decode and async decompression
|
||||
options.hardwareDecode = true
|
||||
options.asynchronousDecompression = true
|
||||
#endif
|
||||
|
||||
// HDR handling: Let KSPlayer automatically detect content's native dynamic range
|
||||
// Setting destinationDynamicRange to nil allows KSPlayer to use the content's actual HDR/SDR mode
|
||||
|
|
@ -557,22 +539,21 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
|
||||
func setTextTrack(_ trackId: Int) {
|
||||
print("KSPlayerView: [SET TEXT TRACK] Starting setTextTrack with trackId: \(trackId)")
|
||||
NSLog("KSPlayerView: [SET TEXT TRACK] Starting setTextTrack with trackId: %d", trackId)
|
||||
|
||||
// Wait slightly longer than the 1-second delay for subtitle data source connection
|
||||
// This ensures srtControl.addSubtitle(dataSouce:) has been called in VideoPlayerView
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { [weak self] in
|
||||
// Small delay to ensure player is ready
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
guard let self = self else {
|
||||
print("KSPlayerView: [SET TEXT TRACK] self is nil, aborting")
|
||||
NSLog("KSPlayerView: [SET TEXT TRACK] self is nil, aborting")
|
||||
return
|
||||
}
|
||||
|
||||
print("KSPlayerView: [SET TEXT TRACK] Executing delayed track selection")
|
||||
NSLog("KSPlayerView: [SET TEXT TRACK] Executing track selection")
|
||||
|
||||
if let player = self.playerView.playerLayer?.player {
|
||||
let textTracks = player.tracks(mediaType: .subtitle)
|
||||
print("KSPlayerView: Available text tracks count: \(textTracks.count)")
|
||||
print("KSPlayerView: Requested text track ID: \(trackId)")
|
||||
NSLog("KSPlayerView: Available text tracks count: %d", textTracks.count)
|
||||
NSLog("KSPlayerView: Requested text track ID: %d", trackId)
|
||||
|
||||
// First try to find track by trackID (proper way)
|
||||
var selectedTrack: MediaPlayerTrack? = nil
|
||||
|
|
@ -582,84 +563,57 @@ class KSPlayerView: UIView {
|
|||
if let track = textTracks.first(where: { Int($0.trackID) == trackId }) {
|
||||
selectedTrack = track
|
||||
trackIndex = textTracks.firstIndex(where: { $0.trackID == track.trackID }) ?? -1
|
||||
print("KSPlayerView: Found text track by trackID \(trackId) at index \(trackIndex)")
|
||||
NSLog("KSPlayerView: Found text track by trackID %d at index %d", trackId, trackIndex)
|
||||
}
|
||||
// Fallback: treat trackId as array index
|
||||
else if trackId >= 0 && trackId < textTracks.count {
|
||||
selectedTrack = textTracks[trackId]
|
||||
trackIndex = trackId
|
||||
print("KSPlayerView: Found text track by array index \(trackId) (fallback)")
|
||||
NSLog("KSPlayerView: Found text track by array index %d (fallback)", trackId)
|
||||
}
|
||||
|
||||
if let track = selectedTrack {
|
||||
print("KSPlayerView: Selecting text track \(trackId) (index: \(trackIndex)): '\(track.name)' (ID: \(track.trackID))")
|
||||
NSLog("KSPlayerView: Selecting text track %d (index: %d): '%@' (ID: %d)", trackId, trackIndex, track.name, track.trackID)
|
||||
|
||||
// First disable all tracks to ensure only one is active
|
||||
// Disable all tracks first
|
||||
for t in textTracks {
|
||||
t.isEnabled = false
|
||||
}
|
||||
|
||||
// Use KSPlayer's select method which properly handles track selection
|
||||
// Enable the selected track
|
||||
track.isEnabled = true
|
||||
|
||||
// Use KSPlayer's select method to update player state
|
||||
player.select(track: track)
|
||||
|
||||
// Sync srtControl with player track selection
|
||||
// Find the corresponding SubtitleInfo in srtControl and select it
|
||||
if let matchingSubtitleInfo = self.playerView.srtControl.subtitleInfos.first(where: { subtitleInfo in
|
||||
// Try to match by name or track ID
|
||||
subtitleInfo.name.lowercased() == track.name.lowercased() ||
|
||||
subtitleInfo.subtitleID == String(track.trackID)
|
||||
}) {
|
||||
print("KSPlayerView: Found matching SubtitleInfo: \(matchingSubtitleInfo.name) (ID: \(matchingSubtitleInfo.subtitleID))")
|
||||
self.playerView.srtControl.selectedSubtitleInfo = matchingSubtitleInfo
|
||||
print("KSPlayerView: Set srtControl.selectedSubtitleInfo to: \(matchingSubtitleInfo.name)")
|
||||
// CRITICAL: Cast MediaPlayerTrack to SubtitleInfo and set on srtControl
|
||||
// FFmpegAssetTrack conforms to SubtitleInfo via extension
|
||||
if let subtitleInfo = track as? SubtitleInfo {
|
||||
self.playerView.srtControl.selectedSubtitleInfo = subtitleInfo
|
||||
NSLog("KSPlayerView: Set srtControl.selectedSubtitleInfo to track '%@'", track.name)
|
||||
} else {
|
||||
print("KSPlayerView: No matching SubtitleInfo found for track '\(track.name)' (ID: \(track.trackID))")
|
||||
print("KSPlayerView: Available SubtitleInfos:")
|
||||
for (index, info) in self.playerView.srtControl.subtitleInfos.enumerated() {
|
||||
print("KSPlayerView: [\(index)] name='\(info.name)', subtitleID='\(info.subtitleID)'")
|
||||
}
|
||||
NSLog("KSPlayerView: Warning - track could not be cast to SubtitleInfo")
|
||||
}
|
||||
|
||||
// Ensure subtitle views are visible after selection
|
||||
// Ensure subtitle views are visible
|
||||
self.playerView.subtitleLabel.isHidden = false
|
||||
self.playerView.subtitleBackView.isHidden = false
|
||||
|
||||
// Debug: Check the enabled state of all tracks after selection
|
||||
print("KSPlayerView: Track states after selection:")
|
||||
for (index, t) in textTracks.enumerated() {
|
||||
print("KSPlayerView: Track \(index): ID=\(t.trackID), Name='\(t.name)', Enabled=\(t.isEnabled)")
|
||||
}
|
||||
|
||||
// Verify the selection worked after a delay
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let tracksAfter = player.tracks(mediaType: .subtitle)
|
||||
print("KSPlayerView: Verification after subtitle selection:")
|
||||
for (index, track) in tracksAfter.enumerated() {
|
||||
print("KSPlayerView: Track \(index) (ID: \(track.trackID)) isEnabled: \(track.isEnabled)")
|
||||
}
|
||||
|
||||
// Also verify srtControl selection
|
||||
if let selectedInfo = self.playerView.srtControl.selectedSubtitleInfo {
|
||||
print("KSPlayerView: srtControl.selectedSubtitleInfo: \(selectedInfo.name) (ID: \(selectedInfo.subtitleID))")
|
||||
} else {
|
||||
print("KSPlayerView: srtControl.selectedSubtitleInfo is nil")
|
||||
}
|
||||
}
|
||||
|
||||
print("KSPlayerView: Successfully selected text track \(trackId)")
|
||||
NSLog("KSPlayerView: Successfully selected and enabled text track %d", trackId)
|
||||
} else if trackId == -1 {
|
||||
// Disable all subtitles
|
||||
for track in textTracks { track.isEnabled = false }
|
||||
// Clear srtControl selection and hide subtitle views
|
||||
for track in textTracks {
|
||||
track.isEnabled = false
|
||||
}
|
||||
self.playerView.srtControl.selectedSubtitleInfo = nil
|
||||
self.playerView.subtitleLabel.isHidden = true
|
||||
self.playerView.subtitleBackView.isHidden = true
|
||||
print("KSPlayerView: Disabled all text tracks and cleared srtControl selection")
|
||||
NSLog("KSPlayerView: Disabled all text tracks")
|
||||
} else {
|
||||
print("KSPlayerView: Text track \(trackId) not found. Available track IDs: \(textTracks.map { Int($0.trackID) }), array indices: 0..\(textTracks.count - 1)")
|
||||
NSLog("KSPlayerView: Text track %d not found. Available count: %d", trackId, textTracks.count)
|
||||
}
|
||||
} else {
|
||||
print("KSPlayerView: No player available for text track selection")
|
||||
NSLog("KSPlayerView: No player available for text track selection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1015,7 +969,18 @@ extension KSPlayerView: KSPlayerLayerDelegate {
|
|||
if let part = playerView.srtControl.parts.first {
|
||||
print("KSPlayerView: [SUBTITLE RENDER] time=\(currentTime), text='\(part.text?.string ?? "nil")', hasImage=\(part.image != nil)")
|
||||
playerView.subtitleBackView.image = part.image
|
||||
playerView.subtitleLabel.attributedText = part.text
|
||||
|
||||
// Normalize font size for all subtitles to ensure consistent display
|
||||
if let originalText = part.text {
|
||||
let mutableText = NSMutableAttributedString(attributedString: originalText)
|
||||
// Apply consistent font across the entire text
|
||||
let font = UIFont.systemFont(ofSize: 20.0)
|
||||
mutableText.addAttributes([.font: font], range: NSRange(location: 0, length: mutableText.length))
|
||||
playerView.subtitleLabel.attributedText = mutableText
|
||||
} else {
|
||||
playerView.subtitleLabel.attributedText = nil
|
||||
}
|
||||
|
||||
playerView.subtitleBackView.isHidden = false
|
||||
playerView.subtitleLabel.isHidden = false
|
||||
print("KSPlayerView: [SUBTITLE RENDER] Set subtitle text and made views visible")
|
||||
|
|
|
|||
|
|
@ -87,9 +87,14 @@ class KSPlayerViewManager: RCTViewManager {
|
|||
}
|
||||
|
||||
@objc func setTextTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
NSLog("[KSPlayerViewManager] setTextTrack called - node: %@, trackId: %@", node, trackId)
|
||||
DispatchQueue.main.async {
|
||||
NSLog("[KSPlayerViewManager] setTextTrack on main queue - looking for view with tag: %@", node)
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
NSLog("[KSPlayerViewManager] Found view, calling setTextTrack(%d)", Int(truncating: trackId))
|
||||
view.setTextTrack(Int(truncating: trackId))
|
||||
} else {
|
||||
NSLog("[KSPlayerViewManager] ERROR - Could not find KSPlayerView for tag: %@", node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -476,6 +476,7 @@
|
|||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -508,6 +509,7 @@
|
|||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3.1</string>
|
||||
<string>1.2.10</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -44,7 +44,7 @@
|
|||
"expo-brightness": "~14.0.7",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-crypto": "~15.0.7",
|
||||
"expo-dev-client": "~6.0.15",
|
||||
"expo-dev-client": "~6.0.20",
|
||||
"expo-device": "~8.0.9",
|
||||
"expo-document-picker": "~14.0.7",
|
||||
"expo-file-system": "~19.0.17",
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
"expo-brightness": "~14.0.7",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-crypto": "~15.0.7",
|
||||
"expo-dev-client": "~6.0.15",
|
||||
"expo-dev-client": "~6.0.20",
|
||||
"expo-device": "~8.0.9",
|
||||
"expo-document-picker": "~14.0.7",
|
||||
"expo-file-system": "~19.0.17",
|
||||
|
|
|
|||
|
|
@ -120,11 +120,16 @@ const KSPlayer = forwardRef<KSPlayerRef, KSPlayerProps>((props, ref) => {
|
|||
}
|
||||
},
|
||||
setTextTrack: (trackId: number) => {
|
||||
console.log('[KSPlayerComponent] setTextTrack called with trackId:', trackId);
|
||||
if (nativeRef.current) {
|
||||
const node = findNodeHandle(nativeRef.current);
|
||||
console.log('[KSPlayerComponent] setTextTrack dispatching command to node:', node);
|
||||
// @ts-ignore legacy UIManager commands path for Paper
|
||||
const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setTextTrack;
|
||||
console.log('[KSPlayerComponent] setTextTrack commandId:', commandId);
|
||||
UIManager.dispatchViewManagerCommand(node, commandId, [trackId]);
|
||||
} else {
|
||||
console.warn('[KSPlayerComponent] setTextTrack: nativeRef.current is null');
|
||||
}
|
||||
},
|
||||
getTracks: async () => {
|
||||
|
|
|
|||
|
|
@ -389,6 +389,27 @@ const KSPlayerCore: React.FC = () => {
|
|||
navigation.goBack();
|
||||
}, [navigation, currentTime, duration, traktAutosync]);
|
||||
|
||||
// Track selection handlers - update state, prop change triggers native update
|
||||
const handleSelectTextTrack = useCallback((trackId: number) => {
|
||||
console.log('[KSPlayerCore] handleSelectTextTrack called with trackId:', trackId);
|
||||
|
||||
// Disable custom subtitles when selecting a built-in track
|
||||
// This ensures the textTrack prop is actually passed to the native player
|
||||
if (trackId !== -1) {
|
||||
customSubs.setUseCustomSubtitles(false);
|
||||
}
|
||||
|
||||
// Just update state - the textTrack prop change will trigger native update
|
||||
tracks.selectTextTrack(trackId);
|
||||
}, [tracks, customSubs]);
|
||||
|
||||
const handleSelectAudioTrack = useCallback((trackId: number) => {
|
||||
tracks.selectAudioTrack(trackId);
|
||||
if (ksPlayerRef.current) {
|
||||
ksPlayerRef.current.setAudioTrack(trackId);
|
||||
}
|
||||
}, [tracks, ksPlayerRef]);
|
||||
|
||||
// Stream selection handler
|
||||
const handleSelectStream = async (newStream: any) => {
|
||||
if (newStream.url === uri) {
|
||||
|
|
@ -498,8 +519,8 @@ const KSPlayerCore: React.FC = () => {
|
|||
setZoomScale={setZoomScale}
|
||||
lastZoomScale={lastZoomScale}
|
||||
setLastZoomScale={setLastZoomScale}
|
||||
audioTrack={tracks.selectedAudioTrack !== null ? tracks.selectedAudioTrack : undefined}
|
||||
textTrack={customSubs.useCustomSubtitles ? undefined : (tracks.selectedTextTrack !== -1 ? tracks.selectedTextTrack : undefined)}
|
||||
audioTrack={tracks.selectedAudioTrack ?? undefined}
|
||||
textTrack={customSubs.useCustomSubtitles ? -1 : tracks.selectedTextTrack}
|
||||
onAudioTracks={(d) => tracks.setKsAudioTracks(d.audioTracks || [])}
|
||||
onTextTracks={(d) => tracks.setKsTextTracks(d.textTracks || [])}
|
||||
onLoad={onLoad}
|
||||
|
|
@ -682,7 +703,7 @@ const KSPlayerCore: React.FC = () => {
|
|||
setShowAudioModal={modals.setShowAudioModal}
|
||||
ksAudioTracks={tracks.ksAudioTracks}
|
||||
selectedAudioTrack={tracks.selectedAudioTrack}
|
||||
selectAudioTrack={tracks.selectAudioTrack}
|
||||
selectAudioTrack={handleSelectAudioTrack}
|
||||
/>
|
||||
|
||||
<ErrorModal
|
||||
|
|
@ -744,10 +765,10 @@ const KSPlayerCore: React.FC = () => {
|
|||
ksTextTracks={tracks.ksTextTracks}
|
||||
selectedTextTrack={tracks.selectedTextTrack !== null ? tracks.selectedTextTrack : -1}
|
||||
useCustomSubtitles={customSubs.useCustomSubtitles}
|
||||
selectTextTrack={tracks.selectTextTrack}
|
||||
selectTextTrack={handleSelectTextTrack}
|
||||
disableCustomSubtitles={() => {
|
||||
customSubs.setUseCustomSubtitles(false);
|
||||
tracks.selectTextTrack(-1);
|
||||
handleSelectTextTrack(-1);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@ export const KSPlayerSurface: React.FC<KSPlayerSurfaceProps> = ({
|
|||
headers
|
||||
};
|
||||
|
||||
// Debug: log textTrack prop changes
|
||||
React.useEffect(() => {
|
||||
console.log('[KSPlayerSurface] textTrack prop changed to:', textTrack);
|
||||
}, [textTrack]);
|
||||
|
||||
// Handle buffering - KSPlayerComponent uses onBuffering callback
|
||||
const handleBuffering = (data: any) => {
|
||||
onBuffer(data?.isBuffering ?? false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue