pull/999/head v1.8.7
deniscerri 8 months ago
parent c2b3dee644
commit 4c2306206b
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -1,5 +1,19 @@
# YTDLnis Changelog
> # 1.8.7 (2025-11)
# What's Changed
## Add QuickJS runtime
Implements yt-dlp's --js-runtimes command to help with many website challenges while fetching data and downloading.
Hopefully with this many of the current issues are resolved
- Upgrade to ffmpeg 7.1.1
- Fix hardcoded user id in path so it supports multiple users
- fix --cache-dir not properly formatted when used as command template
- Add toggle to always update yt-dlp before downloading
> # 1.8.6 (2025-10)
# What's Changed

@ -11,7 +11,7 @@ plugins {
def properties = new Properties()
def versionMajor = 1
def versionMinor = 8
def versionPatch = 6
def versionPatch = 7
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def isBeta = false

@ -1,14 +0,0 @@
# Set NDK path
NDK=/path/to/android-ndk
# Target: arm64-v8a, min API 24
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -O2 -static -o android-js-arm64 fake-node.c
# armeabi-v7a
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi14-clang -O2 -static -o android-js-arm fake-node.c
# x86
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android24-clang -O2 -static -o android-js-x86 fake-node.c
# x86_64
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android24-clang -O2 -static -o android-js-x86_64 fake-node.c

@ -1,70 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 65953
#define BUFSIZE 65536
int main(int argc, char *argv[]) {
char buffer[BUFSIZE];
int sock;
struct sockaddr_in server_addr;
// Collect JS code
char *code = NULL;
size_t code_len = 0;
if (argc > 1) {
// Join argv[1..] as code (like "node -e 'code'")
size_t total = 0;
for (int i = 1; i < argc; i++) total += strlen(argv[i]) + 1;
code = malloc(total);
code[0] = '\0';
for (int i = 1; i < argc; i++) {
strcat(code, argv[i]);
if (i < argc - 1) strcat(code, " ");
}
} else {
// Read from stdin
ssize_t n = read(STDIN_FILENO, buffer, BUFSIZE - 1);
if (n <= 0) return 1;
buffer[n] = '\0';
code = strdup(buffer);
}
// Open socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(sock);
return 1;
}
// Send code
write(sock, code, strlen(code));
write(sock, "\0", 1);
// Receive result
ssize_t n = read(sock, buffer, BUFSIZE - 1);
if (n > 0) {
buffer[n] = '\0';
printf("%s\n", buffer);
}
close(sock);
free(code);
return 0;
}

@ -7,7 +7,6 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.extractors.ytdlp.webview.YTDLPWebview
import com.yausername.aria2c.Aria2c
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
@ -31,8 +30,6 @@ class App : Application() {
try {
createNotificationChannels()
initLibraries()
//init js interp server
//val jsServer = YTDLPWebview(this@App, 65953)
val appVer = sharedPreferences.getString("version", "")!!
if(appVer.isEmpty() || appVer != BuildConfig.VERSION_NAME){

@ -1,176 +0,0 @@
package com.deniscerri.ytdl.util.extractors.ytdlp.webview
import android.annotation.SuppressLint
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.InetAddress
import java.net.ServerSocket
import java.net.Socket
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@SuppressLint("SetJavaScriptEnabled")
class YTDLPWebview(private val context: Context, private val port: Int, private val evalTimeoutMs: Long = 10_000L) {
private val mainHandler = Handler(Looper.getMainLooper())
private val executor = Executors.newCachedThreadPool()
private lateinit var webView: WebView
// map callId -> callback
private val callbacks = ConcurrentHashMap<String, (JSONObject) -> Unit>()
init {
initWebView()
// start server off-main thread
executor.submit { startServer() }
}
private fun initWebView() {
mainHandler.post {
webView = WebView(context)
val s = webView.settings
s.javaScriptEnabled = true
s.allowFileAccess = false
s.allowContentAccess = false
// disable access to file:// and other risky things
webView.settings.domStorageEnabled = false
webView.webViewClient = WebViewClient()
webView.addJavascriptInterface(JsBridge(), "AndroidBridge")
// Load blank page (we will eval into this context)
webView.loadData("<html><body></body></html>", "text/html", "utf-8")
}
}
inner class JsBridge {
@JavascriptInterface
fun postResponse(id: String, json: String) {
val obj = try { JSONObject(json) } catch (_: Exception) {
JSONObject().put("result", JSONObject.NULL).put("error", "invalid_json_from_js")
}
callbacks.remove(id)?.invoke(obj)
}
}
private fun startServer() {
// Bind only to localhost
val server = ServerSocket(port, 50, InetAddress.getByName("127.0.0.1"))
while (!server.isClosed) {
val client = server.accept()
executor.submit { handleClient(client) }
}
}
private fun handleClient(client: Socket) {
client.soTimeout = (evalTimeoutMs + 2000).toInt()
val reader = BufferedReader(InputStreamReader(client.getInputStream()))
// read until NUL terminator (wrapper should send code+'\0')
val sb = StringBuilder()
var ch = reader.read()
while (ch >= 0 && ch != 0) {
sb.append(ch.toChar())
ch = reader.read()
}
val code = sb.toString()
val callId = System.nanoTime().toString()
// register callback with timeout
val done = Object()
var responded = false
callbacks[callId] = { jsonObj ->
try {
val bytes = (jsonObj.toString() + "\u0000").toByteArray()
client.getOutputStream().write(bytes)
client.getOutputStream().flush()
} catch (_: Exception) { }
finally {
try { client.close() } catch (_: Exception) {}
synchronized(done) { responded = true; done.notifyAll() }
}
}
// schedule timeout
executor.submit {
try {
Thread.sleep(evalTimeoutMs)
} catch (_: InterruptedException) {}
if (callbacks.remove(callId) != null) {
val timeoutJson = JSONObject().put("result", JSONObject.NULL).put("logs", arrayOf<String>()).put("error", "timeout")
try {
client.getOutputStream().write((timeoutJson.toString() + "\u0000").toByteArray())
client.close()
} catch (_: Exception) {}
}
}
// eval
evalJsWithConsoleCapture(code, callId)
// wait briefly for response (not strictly necessary)
synchronized(done) {
if (!responded) {
try { done.wait(evalTimeoutMs + 1000) } catch (_: InterruptedException) {}
}
}
}
private fun evalJsWithConsoleCapture(code: String, id: String) {
// wrap code so we capture console logs and async results
val escaped = code.replace("\\", "\\\\").replace("\n", "\\n").replace("\"", "\\\"")
val wrapped = """
(function(){
(function(){
var logs = [];
var origLog = console.log;
console.log = function(){
try {
var a = Array.prototype.slice.call(arguments).map(function(x){
try { return typeof x === 'object' ? JSON.stringify(x) : String(x); } catch (e) { return String(x); }
});
logs.push(a.join(' '));
} catch(e){}
try{ origLog.apply(console, arguments); }catch(e){}
};
function send(obj){
try{
AndroidBridge.postResponse("$id", JSON.stringify(obj));
}catch(e){}
}
try {
var res = (function(){ return eval("$escaped"); })();
// if Promise-like, wait for it
if (res && typeof res.then === 'function') {
res.then(function(v){ send({result: v==undefined? null: v, logs: logs, error: null}); })
.catch(function(err){ send({result: null, logs: logs, error: String(err)}); });
} else {
send({result: res==undefined? null: res, logs: logs, error: null});
}
} catch(e) {
send({result: null, logs: logs, error: String(e)});
}
})();
})();
""".trimIndent()
mainHandler.post {
try {
webView.evaluateJavascript(wrapped, null)
} catch (e: Exception) {
// immediate failure: return error
callbacks.remove(id)?.invoke(JSONObject().put("result", JSONObject.NULL).put("logs", arrayOf<String>()).put("error", e.message))
}
}
}
fun shutdown() {
executor.shutdownNow()
try { executor.awaitTermination(1, TimeUnit.SECONDS) } catch (_: Exception) {}
mainHandler.post { webView.destroy() }
}
}

@ -0,0 +1,11 @@
# What's Changed
## Add QuickJS runtime
Implements yt-dlp's --js-runtimes command to help with many website challenges while fetching data and downloading.
Hopefully with this many of the current issues are resolved
- Upgrade to ffmpeg 7.1.1
- Fix hardcoded user id in path so it supports multiple users
- fix --cache-dir not properly formatted when used as command template
- Add toggle to always update yt-dlp before downloading
Loading…
Cancel
Save