diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe5ee54..2f5232b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/build.gradle b/app/build.gradle index 7a25b21b..da5d5759 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/assets/build_fake_node/README.md b/app/src/main/assets/build_fake_node/README.md deleted file mode 100644 index 8579cf1f..00000000 --- a/app/src/main/assets/build_fake_node/README.md +++ /dev/null @@ -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 diff --git a/app/src/main/assets/build_fake_node/fake-node.c b/app/src/main/assets/build_fake_node/fake-node.c deleted file mode 100644 index fe63ea12..00000000 --- a/app/src/main/assets/build_fake_node/fake-node.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#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; -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/App.kt b/app/src/main/java/com/deniscerri/ytdl/App.kt index ae83d087..3cacafe3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/App.kt +++ b/app/src/main/java/com/deniscerri/ytdl/App.kt @@ -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){ diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt deleted file mode 100644 index 4e2ae9ab..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/ytdlp/webview/YTDLPWebview.kt +++ /dev/null @@ -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 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("", "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()).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()).put("error", e.message)) - } - } - } - - fun shutdown() { - executor.shutdownNow() - try { executor.awaitTermination(1, TimeUnit.SECONDS) } catch (_: Exception) {} - mainHandler.post { webView.destroy() } - } -} \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/108070000.txt b/fastlane/metadata/android/en-US/changelogs/108070000.txt new file mode 100644 index 00000000..7bc4df06 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/108070000.txt @@ -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