This commit is contained in:
tapframe 2025-09-20 09:35:48 +05:30
parent eb3615acc6
commit 0011079199
2 changed files with 84 additions and 27 deletions

View file

@ -109,6 +109,14 @@ class KSPlayerView: UIView {
guard let uri = source["uri"] as? String else {
print("KSPlayerView: No URI provided")
sendEvent("onError", ["error": "No URI provided in source"])
return
}
// Validate URL before proceeding
guard let url = URL(string: uri), url.scheme != nil else {
print("KSPlayerView: Invalid URL format: \(uri)")
sendEvent("onError", ["error": "Invalid URL format: \(uri)"])
return
}
@ -135,11 +143,24 @@ class KSPlayerView: UIView {
}
#endif
// Create KSPlayerResource
let url = URL(string: uri)!
// Create KSPlayerResource with validated URL
let resource = KSPlayerResource(url: url, options: createOptions(with: headers), name: "Video")
print("KSPlayerView: Setting source: \(uri)")
print("KSPlayerView: URL scheme: \(url.scheme ?? "unknown"), host: \(url.host ?? "unknown")")
// Add timeout for source loading
loadTimeoutWorkItem?.cancel()
let work = DispatchWorkItem { [weak self] in
guard let self = self else { return }
let dur = self.playerView.playerLayer?.player.duration ?? 0
if dur <= 0 {
self.sendEvent("onError", ["error": "Stream timeout: unable to open input after 8 seconds"])
}
}
loadTimeoutWorkItem = work
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: work)
playerView.set(resource: resource)
// Set up delegate after setting the resource
@ -153,18 +174,6 @@ class KSPlayerView: UIView {
}
setVolume(currentVolume)
// Start a safety timeout to surface errors if never ready
loadTimeoutWorkItem?.cancel()
let work = DispatchWorkItem { [weak self] in
guard let self = self else { return }
let dur = self.playerView.playerLayer?.player.duration ?? 0
if dur <= 0 {
self.sendEvent("onError", ["error": "Playback timeout: stream did not become ready."])
}
}
loadTimeoutWorkItem = work
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: work)
}
private func createOptions(with headers: [String: String]) -> KSOptions {
@ -192,9 +201,23 @@ class KSPlayerView: UIView {
options.hardwareDecode = KSOptions.hardwareDecode
#endif
if !headers.isEmpty {
options.appendHeader(headers)
if let referer = headers["Referer"] ?? headers["referer"] {
options.referer = referer
// Clean and validate headers before adding
var cleanHeaders: [String: String] = [:]
for (key, value) in headers {
// Remove any null or empty values
if !value.isEmpty && value != "null" {
cleanHeaders[key] = value
}
}
if !cleanHeaders.isEmpty {
options.appendHeader(cleanHeaders)
print("KSPlayerView: Added headers: \(cleanHeaders.keys.joined(separator: ", "))")
if let referer = cleanHeaders["Referer"] ?? cleanHeaders["referer"] {
options.referer = referer
print("KSPlayerView: Set referer: \(referer)")
}
}
}
return options
@ -447,7 +470,22 @@ extension KSPlayerView: KSPlayerLayerDelegate {
func player(layer: KSPlayerLayer, finish error: Error?) {
if let error = error {
sendEvent("onError", ["error": error.localizedDescription])
let errorMessage = error.localizedDescription
print("KSPlayerView: Player finished with error: \(errorMessage)")
// Provide more specific error messages for common issues
var detailedError = errorMessage
if errorMessage.contains("avformat can't open input") {
detailedError = "Unable to open video stream. This could be due to:\n• Invalid or malformed URL\n• Network connectivity issues\n• Server blocking the request\n• Unsupported video format\n• Missing required headers"
} else if errorMessage.contains("timeout") {
detailedError = "Stream connection timed out. The server may be slow or unreachable."
} else if errorMessage.contains("404") || errorMessage.contains("Not Found") {
detailedError = "Video stream not found. The URL may be expired or incorrect."
} else if errorMessage.contains("403") || errorMessage.contains("Forbidden") {
detailedError = "Access denied. The server may be blocking requests or require authentication."
}
sendEvent("onError", ["error": detailedError])
}
}

View file

@ -212,19 +212,38 @@ const VideoPlayerCore: React.FC = () => {
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(false);
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: any[]; addonName: string } }>(passedAvailableStreams || {});
// Decode URLs for KSPlayer compatibility - KSPlayer handles encoded URLs better
const decodeUrlForKsPlayer = (url: string): string => {
// Smart URL processing for KSPlayer compatibility
const processUrlForKsPlayer = (url: string): string => {
try {
// KSPlayer handles encoded URLs well, but decode for consistency
const decoded = decodeURIComponent(url);
return decoded;
// Validate URL first
const urlObj = new URL(url);
// Only decode if the URL appears to be double-encoded
// Check if URL contains encoded characters that shouldn't be there
const hasDoubleEncoding = url.includes('%25') ||
(url.includes('%2F') && url.includes('//')) ||
(url.includes('%3A') && url.includes('://'));
if (hasDoubleEncoding) {
logger.log('[VideoPlayer] Detected double-encoded URL, decoding once');
return decodeURIComponent(url);
}
// For URLs with special characters in query params, ensure proper encoding
if (urlObj.search) {
const searchParams = new URLSearchParams(urlObj.search);
urlObj.search = searchParams.toString();
return urlObj.toString();
}
return url;
} catch (e) {
logger.warn('[VideoPlayer] URL decoding failed, using original:', e);
logger.warn('[VideoPlayer] URL processing failed, using original:', e);
return url;
}
};
const [currentStreamUrl, setCurrentStreamUrl] = useState<string>(decodeUrlForKsPlayer(uri));
const [currentStreamUrl, setCurrentStreamUrl] = useState<string>(processUrlForKsPlayer(uri));
const [isChangingSource, setIsChangingSource] = useState<boolean>(false);
const [showErrorModal, setShowErrorModal] = useState(false);
const [errorDetails, setErrorDetails] = useState<string>('');
@ -2365,8 +2384,8 @@ const VideoPlayerCore: React.FC = () => {
// Set pending seek state
setPendingSeek({ position: savedPosition, shouldPlay: wasPlaying });
// Update the stream URL and details immediately (decode URL for KSPlayer)
setCurrentStreamUrl(decodeUrlForKsPlayer(newStream.url));
// Update the stream URL and details immediately (process URL for KSPlayer)
setCurrentStreamUrl(processUrlForKsPlayer(newStream.url));
setCurrentQuality(newQuality);
setCurrentStreamProvider(newProvider);
setCurrentStreamName(newStreamName);