├── libmpv ├── proguard-rules.pro ├── src │ └── main │ │ ├── cpp │ │ ├── event.h │ │ ├── log.cpp │ │ ├── globals.h │ │ ├── log.h │ │ ├── jni_utils.h │ │ ├── CMakeLists.txt │ │ ├── render.cpp │ │ ├── jni_utils.cpp │ │ ├── main.cpp │ │ ├── event.cpp │ │ └── property.cpp │ │ └── java │ │ └── dev │ │ └── jdtech │ │ └── mpv │ │ └── MPVLib.kt └── build.gradle.kts ├── gradle.properties ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── settings.gradle.kts ├── README.md ├── LICENSE ├── .github └── workflows │ ├── build.yaml │ └── publish.yaml └── gradlew /libmpv/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class dev.jdtech.mpv.MPVLib { *; } 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void *event_thread(void *arg); 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jarnedemeulemeester/libmpv-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gdb_history 2 | bin 3 | gen 4 | jniLibs 5 | obj 6 | .gradle 7 | .idea 8 | .cxx 9 | **/*.iml 10 | build 11 | local.properties 12 | *~ 13 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | 5 | void die(const char *msg) 6 | { 7 | ALOGE("%s", msg); 8 | exit(1); 9 | } 10 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":libmpv") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | google() 8 | } 9 | } -------------------------------------------------------------------------------- /libmpv/src/main/cpp/globals.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern JavaVM *g_vm; 6 | extern mpv_handle *g_mpv; 7 | extern std::atomic g_event_thread_request_exit; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libmpv for Android 2 | 3 | Based on [mpv-android](https://github.com/mpv-android/mpv-android) 4 | 5 | ## Building from source 6 | 7 | Take a look at [README.md](buildscripts/README.md) inside the `buildscripts` directory. 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | android-plugin = "8.13.0" 3 | kotlin = "2.2.20" 4 | maven-publish = "0.34.0" 5 | 6 | [plugins] 7 | android-library = { id = "com.android.library", version.ref = "android-plugin" } 8 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 9 | maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } 10 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DEBUG 1 6 | 7 | #define LOG_TAG "mpv" 8 | #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 9 | #if DEBUG 10 | #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 11 | #else 12 | #define ALOGV(...) (void)0 13 | #endif 14 | 15 | __attribute__((noreturn)) void die(const char *msg); 16 | 17 | #define CHECK_MPV_INIT() do { \ 18 | if (__builtin_expect(!g_mpv, 0)) \ 19 | die("libmpv is not initialized"); \ 20 | } while (0) 21 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/jni_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define jni_func_name(name) Java_dev_jdtech_mpv_MPVLib_##name 6 | #define jni_func(return_type, name, ...) JNIEXPORT return_type JNICALL jni_func_name(name) (JNIEnv *env, jobject obj, ##__VA_ARGS__) 7 | 8 | bool acquire_jni_env(JavaVM *vm, JNIEnv **env); 9 | void init_methods_cache(JNIEnv *env); 10 | 11 | #ifndef UTIL_EXTERN 12 | #define UTIL_EXTERN extern 13 | #endif 14 | 15 | UTIL_EXTERN jclass java_Integer, java_Double, java_Boolean; 16 | UTIL_EXTERN jmethodID java_Integer_init, java_Double_init, java_Boolean_init; 17 | 18 | UTIL_EXTERN jclass mpv_MPVLib; 19 | UTIL_EXTERN jmethodID mpv_MPVLib_eventProperty_S, 20 | mpv_MPVLib_eventProperty_Sb, 21 | mpv_MPVLib_eventProperty_Sl, 22 | mpv_MPVLib_eventProperty_Sd, 23 | mpv_MPVLib_eventProperty_SS, 24 | mpv_MPVLib_event, 25 | mpv_MPVLib_logMessage_SiS; 26 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | 3 | project("libmpv") 4 | 5 | add_library( 6 | player 7 | SHARED 8 | main.cpp render.cpp log.cpp jni_utils.cpp property.cpp event.cpp 9 | ) 10 | 11 | add_library( 12 | mpv 13 | SHARED 14 | IMPORTED 15 | ) 16 | 17 | set_target_properties( 18 | mpv 19 | PROPERTIES IMPORTED_LOCATION 20 | ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libmpv.so 21 | ) 22 | 23 | add_library( 24 | avcodec 25 | SHARED 26 | IMPORTED 27 | ) 28 | 29 | set_target_properties( 30 | avcodec 31 | PROPERTIES IMPORTED_LOCATION 32 | ${CMAKE_CURRENT_SOURCE_DIR}/../../../../buildscripts/prefix/${ANDROID_ABI}/lib/libavcodec.so 33 | ) 34 | 35 | include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../../../buildscripts/prefix/${ANDROID_ABI}/include ) 36 | 37 | target_link_libraries( player mpv avcodec log ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ilya Zhuravlev 2 | Copyright (c) 2016 sfan5 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/render.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "jni_utils.h" 6 | #include "log.h" 7 | #include "globals.h" 8 | 9 | extern "C" { 10 | jni_func(void, attachSurface, jobject surface_); 11 | jni_func(void, detachSurface); 12 | }; 13 | 14 | static jobject surface; 15 | 16 | jni_func(void, attachSurface, jobject surface_) { 17 | CHECK_MPV_INIT(); 18 | 19 | surface = env->NewGlobalRef(surface_); 20 | if (!surface) 21 | die("invalid surface provided"); 22 | int64_t wid = reinterpret_cast(surface); 23 | int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid); 24 | if (result < 0) 25 | ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result)); 26 | } 27 | 28 | jni_func(void, detachSurface) { 29 | CHECK_MPV_INIT(); 30 | 31 | int64_t wid = 0; 32 | int result = mpv_set_option(g_mpv, "wid", MPV_FORMAT_INT64, &wid); 33 | if (result < 0) 34 | ALOGE("mpv_set_option(wid) returned error %s", mpv_error_string(result)); 35 | 36 | env->DeleteGlobalRef(surface); 37 | surface = NULL; 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build libmpv-android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v5 16 | - name: Setup Java JDK 17 | uses: actions/setup-java@v5 18 | with: 19 | distribution: temurin 20 | java-version: 17 21 | - name: Setup Gradle 22 | uses: gradle/actions/setup-gradle@v5 23 | - name: Symlink SDK 24 | working-directory: ./buildscripts 25 | run: | 26 | mkdir sdk 27 | ln -s ${ANDROID_HOME} ./sdk/android-sdk-linux 28 | - name: Download dependencies 29 | working-directory: ./buildscripts 30 | run: ./download.sh 31 | - name: Apply patches 32 | working-directory: ./buildscripts 33 | run: ./patch.sh 34 | - name: Build 35 | working-directory: ./buildscripts 36 | run: ./build.sh 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: libmpv-release.aar 40 | path: ./libmpv/build/outputs/aar/libmpv-release.aar 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | environment: release 13 | if: ${{ contains(github.repository_owner, 'jarnedemeulemeester') || startsWith(github.ref, 'refs/tags/v') }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v5 17 | - name: Setup Java JDK 18 | uses: actions/setup-java@v5 19 | with: 20 | distribution: temurin 21 | java-version: 17 22 | - name: Setup Gradle 23 | uses: gradle/actions/setup-gradle@v5 24 | - name: Symlink SDK 25 | working-directory: ./buildscripts 26 | run: | 27 | mkdir sdk 28 | ln -s ${ANDROID_HOME} ./sdk/android-sdk-linux 29 | - name: Download dependencies 30 | working-directory: ./buildscripts 31 | run: ./download.sh 32 | - name: Apply patches 33 | working-directory: ./buildscripts 34 | run: ./patch.sh 35 | - name: Build 36 | working-directory: ./buildscripts 37 | run: ./build.sh 38 | - name: Publish 39 | run: ./gradlew publishToMavenCentral --no-configuration-cache 40 | env: 41 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }} 42 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }} 43 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} 44 | ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }} 45 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} 46 | - name: Create release 47 | uses: softprops/action-gh-release@v2 48 | with: 49 | draft: true 50 | files: | 51 | ./libmpv/build/outputs/aar/libmpv-release.aar 52 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/jni_utils.cpp: -------------------------------------------------------------------------------- 1 | #define UTIL_EXTERN 2 | #include "jni_utils.h" 3 | 4 | #include 5 | #include 6 | 7 | bool acquire_jni_env(JavaVM *vm, JNIEnv **env) 8 | { 9 | int ret = vm->GetEnv((void**) env, JNI_VERSION_1_6); 10 | if (ret == JNI_EDETACHED) 11 | return vm->AttachCurrentThread(env, NULL) == 0; 12 | else 13 | return ret == JNI_OK; 14 | } 15 | 16 | // Apparently it's considered slow to FindClass and GetMethodID every time we need them, 17 | // so let's have a nice cache here 18 | void init_methods_cache(JNIEnv *env) { 19 | static bool methods_initialized = false; 20 | if (methods_initialized) 21 | return; 22 | 23 | #define FIND_CLASS(name) reinterpret_cast(env->NewGlobalRef(env->FindClass(name))) 24 | java_Integer = FIND_CLASS("java/lang/Integer"); 25 | java_Integer_init = env->GetMethodID(java_Integer, "", "(I)V"); 26 | java_Double = FIND_CLASS("java/lang/Double"); 27 | java_Double_init = env->GetMethodID(java_Double, "", "(D)V"); 28 | java_Boolean = FIND_CLASS("java/lang/Boolean"); 29 | java_Boolean_init = env->GetMethodID(java_Boolean, "", "(Z)V"); 30 | 31 | mpv_MPVLib = FIND_CLASS("dev/jdtech/mpv/MPVLib"); 32 | mpv_MPVLib_eventProperty_S = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;)V"); // eventProperty(String) 33 | mpv_MPVLib_eventProperty_Sb = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Z)V"); // eventProperty(String, boolean) 34 | mpv_MPVLib_eventProperty_Sl = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;J)V"); // eventProperty(String, long) 35 | mpv_MPVLib_eventProperty_Sd = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;D)V"); // eventProperty(String, double) 36 | mpv_MPVLib_eventProperty_SS = env->GetStaticMethodID(mpv_MPVLib, "eventProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); // eventProperty(String, String) 37 | mpv_MPVLib_event = env->GetStaticMethodID(mpv_MPVLib, "event", "(I)V"); // event(int) 38 | mpv_MPVLib_logMessage_SiS = env->GetStaticMethodID(mpv_MPVLib, "logMessage", "(Ljava/lang/String;ILjava/lang/String;)V"); // logMessage(String, int, String) 39 | #undef FIND_CLASS 40 | 41 | methods_initialized = true; 42 | } 43 | -------------------------------------------------------------------------------- /libmpv/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.AndroidSingleVariantLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.kotlin.android) 6 | alias(libs.plugins.maven.publish) 7 | } 8 | 9 | android { 10 | namespace = "dev.jdtech.mpv" 11 | compileSdk = 36 12 | buildToolsVersion = "36.1.0" 13 | ndkVersion = "29.0.14206865" 14 | 15 | defaultConfig { 16 | minSdk = 26 17 | consumerProguardFiles("proguard-rules.pro") 18 | externalNativeBuild { 19 | cmake { 20 | arguments += listOf( 21 | "-DANDROID_STL=c++_shared", 22 | ) 23 | cFlags += "-Werror" 24 | cppFlags += "-std=c++11" 25 | } 26 | } 27 | } 28 | 29 | externalNativeBuild { 30 | cmake { 31 | path = file("src/main/cpp/CMakeLists.txt") 32 | version = "4.1.2" 33 | } 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_17 38 | targetCompatibility = JavaVersion.VERSION_17 39 | } 40 | } 41 | 42 | mavenPublishing { 43 | publishToMavenCentral(automaticRelease = true) 44 | signAllPublications() 45 | 46 | configure( 47 | platform = AndroidSingleVariantLibrary( 48 | variant = "release", 49 | sourcesJar = true, 50 | publishJavadocJar = false, 51 | ) 52 | ) 53 | 54 | coordinates( 55 | groupId = "dev.jdtech.mpv", 56 | artifactId = "libmpv", 57 | version = "0.5.1" 58 | ) 59 | 60 | pom { 61 | name = "libmpv-android" 62 | description = "libmpv for Android" 63 | inceptionYear = "2023" 64 | url = "https://github.com/jarnedemeulemeester/libmpv-android" 65 | licenses { 66 | license { 67 | name = "MIT license" 68 | url = "https://github.com/jarnedemeulemeester/libmpv-android/blob/main/LICENSE" 69 | } 70 | } 71 | developers { 72 | developer { 73 | id = "jarnedemeulemeester" 74 | name = "Jarne Demeulemeester" 75 | email = "jarnedemeulemeester@gmail.com" 76 | } 77 | } 78 | scm { 79 | url = "https://github.com/jarnedemeulemeester/libmpv-android.git" 80 | connection = "scm:git@github.com:jarnedemeulemeester/libmpv-android.git" 81 | developerConnection = "scm:git@github.com:jarnedemeulemeester/libmpv-android.git" 82 | } 83 | issueManagement { 84 | system = "GitHub" 85 | url = "https://github.com/jarnedemeulemeester/libmpv-android/issues" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | extern "C" { 13 | #include 14 | } 15 | 16 | #include "log.h" 17 | #include "jni_utils.h" 18 | #include "event.h" 19 | 20 | #define ARRAYLEN(a) (sizeof(a)/sizeof(a[0])) 21 | 22 | extern "C" { 23 | jni_func(void, create, jobject appctx); 24 | jni_func(void, init); 25 | jni_func(void, destroy); 26 | 27 | jni_func(void, command, jobjectArray jarray); 28 | }; 29 | 30 | JavaVM *g_vm; 31 | mpv_handle *g_mpv; 32 | std::atomic g_event_thread_request_exit(false); 33 | 34 | static pthread_t event_thread_id; 35 | 36 | static void prepare_environment(JNIEnv *env, jobject appctx) { 37 | setlocale(LC_NUMERIC, "C"); 38 | 39 | if (!env->GetJavaVM(&g_vm) && g_vm) 40 | av_jni_set_java_vm(g_vm, NULL); 41 | 42 | jobject global_appctx = env->NewGlobalRef(appctx); 43 | if (global_appctx) 44 | av_jni_set_android_app_ctx(global_appctx, NULL); 45 | 46 | init_methods_cache(env); 47 | } 48 | 49 | jni_func(void, create, jobject appctx) { 50 | prepare_environment(env, appctx); 51 | 52 | if (g_mpv) 53 | die("mpv is already initialized"); 54 | 55 | g_mpv = mpv_create(); 56 | if (!g_mpv) 57 | die("context init failed"); 58 | 59 | mpv_request_log_messages(g_mpv, "v"); 60 | } 61 | 62 | jni_func(void, init) { 63 | if (!g_mpv) 64 | die("mpv is not created"); 65 | 66 | if (mpv_initialize(g_mpv) < 0) 67 | die("mpv init failed"); 68 | 69 | g_event_thread_request_exit = false; 70 | if (pthread_create(&event_thread_id, NULL, event_thread, NULL) != 0) 71 | die("thread create failed"); 72 | pthread_setname_np(event_thread_id, "event_thread"); 73 | } 74 | 75 | jni_func(void, destroy) { 76 | if (!g_mpv) { 77 | ALOGV("mpv destroy called but it's already destroyed"); 78 | return; 79 | } 80 | 81 | // poke event thread and wait for it to exit 82 | g_event_thread_request_exit = true; 83 | mpv_wakeup(g_mpv); 84 | pthread_join(event_thread_id, NULL); 85 | 86 | mpv_terminate_destroy(g_mpv); 87 | g_mpv = NULL; 88 | } 89 | 90 | jni_func(void, command, jobjectArray jarray) { 91 | CHECK_MPV_INIT(); 92 | 93 | const char *arguments[128] = { 0 }; 94 | int len = env->GetArrayLength(jarray); 95 | if (len >= ARRAYLEN(arguments)) 96 | die("too many command arguments"); 97 | 98 | for (int i = 0; i < len; ++i) 99 | arguments[i] = env->GetStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), NULL); 100 | 101 | mpv_command(g_mpv, arguments); 102 | 103 | for (int i = 0; i < len; ++i) 104 | env->ReleaseStringUTFChars((jstring)env->GetObjectArrayElement(jarray, i), arguments[i]); 105 | } 106 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/event.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "globals.h" 6 | #include "jni_utils.h" 7 | #include "log.h" 8 | 9 | static void sendPropertyUpdateToJava(JNIEnv *env, mpv_event_property *prop) { 10 | jstring jprop = env->NewStringUTF(prop->name); 11 | jstring jvalue = NULL; 12 | switch (prop->format) { 13 | case MPV_FORMAT_NONE: 14 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_S, jprop); 15 | break; 16 | case MPV_FORMAT_FLAG: 17 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sb, jprop, *(int*)prop->data); 18 | break; 19 | case MPV_FORMAT_INT64: 20 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sl, jprop, *(int64_t*)prop->data); 21 | break; 22 | case MPV_FORMAT_DOUBLE: 23 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_Sd, jprop, *(double*)prop->data); 24 | break; 25 | case MPV_FORMAT_STRING: 26 | jvalue = env->NewStringUTF(*(const char**)prop->data); 27 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_eventProperty_SS, jprop, jvalue); 28 | break; 29 | default: 30 | ALOGV("sendPropertyUpdateToJava: Unknown property update format received in callback: %d!", prop->format); 31 | break; 32 | } 33 | if (jprop) 34 | env->DeleteLocalRef(jprop); 35 | if (jvalue) 36 | env->DeleteLocalRef(jvalue); 37 | } 38 | 39 | static void sendEventToJava(JNIEnv *env, int event) { 40 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_event, event); 41 | } 42 | 43 | static inline bool invalid_utf8(unsigned char c) { 44 | return c == 0xc0 || c == 0xc1 || c >= 0xf5; 45 | } 46 | 47 | static void sendLogMessageToJava(JNIEnv *env, mpv_event_log_message *msg) { 48 | // filter the most obvious cases of invalid utf-8, since Java would choke on it 49 | const auto invalid_utf8 = [] (unsigned char c) { 50 | return c == 0xc0 || c == 0xc1 || c >= 0xf5; 51 | }; 52 | for (int i = 0; msg->text[i]; i++) { 53 | if (invalid_utf8(static_cast(msg->text[i]))) 54 | return; 55 | } 56 | 57 | jstring jprefix = env->NewStringUTF(msg->prefix); 58 | jstring jtext = env->NewStringUTF(msg->text); 59 | 60 | env->CallStaticVoidMethod(mpv_MPVLib, mpv_MPVLib_logMessage_SiS, 61 | jprefix, (jint) msg->log_level, jtext); 62 | 63 | if (jprefix) 64 | env->DeleteLocalRef(jprefix); 65 | if (jtext) 66 | env->DeleteLocalRef(jtext); 67 | } 68 | 69 | void *event_thread(void *arg) { 70 | JNIEnv *env = NULL; 71 | acquire_jni_env(g_vm, &env); 72 | if (!env) 73 | die("failed to acquire java env"); 74 | 75 | while (true) { 76 | mpv_event *mp_event; 77 | mpv_event_property *mp_property; 78 | mpv_event_log_message *msg; 79 | 80 | mp_event = mpv_wait_event(g_mpv, -1.0); 81 | 82 | if (g_event_thread_request_exit) 83 | break; 84 | 85 | if (mp_event->event_id == MPV_EVENT_NONE) 86 | continue; 87 | 88 | switch (mp_event->event_id) { 89 | case MPV_EVENT_LOG_MESSAGE: 90 | msg = (mpv_event_log_message*)mp_event->data; 91 | ALOGV("[%s:%s] %s", msg->prefix, msg->level, msg->text); 92 | sendLogMessageToJava(env, msg); 93 | break; 94 | case MPV_EVENT_PROPERTY_CHANGE: 95 | mp_property = (mpv_event_property*)mp_event->data; 96 | sendPropertyUpdateToJava(env, mp_property); 97 | break; 98 | default: 99 | ALOGV("event: %s\n", mpv_event_name(mp_event->event_id)); 100 | sendEventToJava(env, mp_event->event_id); 101 | break; 102 | } 103 | } 104 | 105 | g_vm->DetachCurrentThread(); 106 | 107 | return NULL; 108 | } 109 | -------------------------------------------------------------------------------- /libmpv/src/main/cpp/property.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "jni_utils.h" 7 | #include "log.h" 8 | #include "globals.h" 9 | 10 | extern "C" { 11 | jni_func(jint, setOptionString, jstring option, jstring value); 12 | 13 | jni_func(jobject, getPropertyInt, jstring property); 14 | jni_func(void, setPropertyInt, jstring property, jstring value); 15 | jni_func(jobject, getPropertyDouble, jstring property); 16 | jni_func(void, setPropertyDouble, jstring property, jdouble value); 17 | jni_func(jobject, getPropertyBoolean, jstring property); 18 | jni_func(void, setPropertyBoolean, jstring property, jboolean value); 19 | jni_func(jstring, getPropertyString, jstring jproperty); 20 | jni_func(void, setPropertyString, jstring jproperty, jstring jvalue); 21 | 22 | jni_func(void, observeProperty, jstring property, jint format); 23 | } 24 | 25 | jni_func(jint, setOptionString, jstring joption, jstring jvalue) { 26 | CHECK_MPV_INIT(); 27 | 28 | const char *option = env->GetStringUTFChars(joption, NULL); 29 | const char *value = env->GetStringUTFChars(jvalue, NULL); 30 | 31 | int result = mpv_set_option_string(g_mpv, option, value); 32 | 33 | env->ReleaseStringUTFChars(joption, option); 34 | env->ReleaseStringUTFChars(jvalue, value); 35 | 36 | return result; 37 | } 38 | 39 | static int common_get_property(JNIEnv *env, jstring jproperty, mpv_format format, void *output) { 40 | CHECK_MPV_INIT(); 41 | 42 | const char *prop = env->GetStringUTFChars(jproperty, NULL); 43 | int result = mpv_get_property(g_mpv, prop, format, output); 44 | if (result < 0) 45 | ALOGE("mpv_get_property(%s) format %d returned error %s", prop, format, mpv_error_string(result)); 46 | env->ReleaseStringUTFChars(jproperty, prop); 47 | 48 | return result; 49 | } 50 | 51 | static int common_set_property(JNIEnv *env, jstring jproperty, mpv_format format, void *value) { 52 | CHECK_MPV_INIT(); 53 | 54 | const char *prop = env->GetStringUTFChars(jproperty, NULL); 55 | int result = mpv_set_property(g_mpv, prop, format, value); 56 | if (result < 0) 57 | ALOGE("mpv_set_property(%s, %p) format %d returned error %s", prop, value, format, mpv_error_string(result)); 58 | env->ReleaseStringUTFChars(jproperty, prop); 59 | 60 | return result; 61 | } 62 | 63 | jni_func(jobject, getPropertyInt, jstring jproperty) { 64 | int64_t value = 0; 65 | if (common_get_property(env, jproperty, MPV_FORMAT_INT64, &value) < 0) 66 | return NULL; 67 | return env->NewObject(java_Integer, java_Integer_init, (jint)value); 68 | } 69 | 70 | jni_func(jobject, getPropertyDouble, jstring jproperty) { 71 | double value = 0; 72 | if (common_get_property(env, jproperty, MPV_FORMAT_DOUBLE, &value) < 0) 73 | return NULL; 74 | return env->NewObject(java_Double, java_Double_init, (jdouble)value); 75 | } 76 | 77 | jni_func(jobject, getPropertyBoolean, jstring jproperty) { 78 | int value = 0; 79 | if (common_get_property(env, jproperty, MPV_FORMAT_FLAG, &value) < 0) 80 | return NULL; 81 | return env->NewObject(java_Boolean, java_Boolean_init, (jboolean)value); 82 | } 83 | 84 | jni_func(jstring, getPropertyString, jstring jproperty) { 85 | char *value; 86 | if (common_get_property(env, jproperty, MPV_FORMAT_STRING, &value) < 0) 87 | return NULL; 88 | jstring jvalue = env->NewStringUTF(value); 89 | mpv_free(value); 90 | return jvalue; 91 | } 92 | 93 | jni_func(void, setPropertyInt, jstring jproperty, jint jvalue) { 94 | int64_t value = static_cast(jvalue); 95 | common_set_property(env, jproperty, MPV_FORMAT_INT64, &value); 96 | } 97 | 98 | jni_func(void, setPropertyDouble, jstring jproperty, jdouble jvalue) { 99 | double value = static_cast(jvalue); 100 | common_set_property(env, jproperty, MPV_FORMAT_DOUBLE, &value); 101 | } 102 | 103 | jni_func(void, setPropertyBoolean, jstring jproperty, jboolean jvalue) { 104 | int value = jvalue == JNI_TRUE ? 1 : 0; 105 | common_set_property(env, jproperty, MPV_FORMAT_FLAG, &value); 106 | } 107 | 108 | jni_func(void, setPropertyString, jstring jproperty, jstring jvalue) { 109 | const char *value = env->GetStringUTFChars(jvalue, NULL); 110 | common_set_property(env, jproperty, MPV_FORMAT_STRING, &value); 111 | env->ReleaseStringUTFChars(jvalue, value); 112 | } 113 | 114 | jni_func(void, observeProperty, jstring property, jint format) { 115 | CHECK_MPV_INIT(); 116 | const char *prop = env->GetStringUTFChars(property, NULL); 117 | int result = mpv_observe_property(g_mpv, 0, prop, (mpv_format)format); 118 | if (result < 0) 119 | ALOGE("mpv_observe_property(%s) format %d returned error %s", prop, format, mpv_error_string(result)); 120 | env->ReleaseStringUTFChars(property, prop); 121 | } 122 | -------------------------------------------------------------------------------- /libmpv/src/main/java/dev/jdtech/mpv/MPVLib.kt: -------------------------------------------------------------------------------- 1 | package dev.jdtech.mpv 2 | 3 | import android.content.Context 4 | import android.view.Surface 5 | 6 | // Wrapper for native library 7 | 8 | @Suppress("unused") 9 | object MPVLib { 10 | init { 11 | val libs = arrayOf("mpv", "player") 12 | for (lib in libs) { 13 | System.loadLibrary(lib) 14 | } 15 | } 16 | 17 | external fun create(appctx: Context) 18 | external fun init() 19 | external fun destroy() 20 | external fun attachSurface(surface: Surface) 21 | external fun detachSurface() 22 | 23 | external fun command(cmd: Array) 24 | 25 | external fun setOptionString(name: String, value: String): Int 26 | 27 | external fun getPropertyInt(property: String): Int? 28 | external fun setPropertyInt(property: String, value: Int) 29 | external fun getPropertyDouble(property: String): Double? 30 | external fun setPropertyDouble(property: String, value: Double) 31 | external fun getPropertyBoolean(property: String): Boolean? 32 | external fun setPropertyBoolean(property: String, value: Boolean) 33 | external fun getPropertyString(property: String): String? 34 | external fun setPropertyString(property: String, value: String) 35 | 36 | external fun observeProperty(property: String, format: Int) 37 | 38 | private val observers = mutableListOf() 39 | 40 | @JvmStatic 41 | fun addObserver(o: EventObserver) { 42 | synchronized(observers) { 43 | observers.add(o) 44 | } 45 | } 46 | 47 | @JvmStatic 48 | fun removeObserver(o: EventObserver) { 49 | synchronized(observers) { 50 | observers.remove(o) 51 | } 52 | } 53 | 54 | @JvmStatic 55 | fun eventProperty(property: String, value: Long) { 56 | synchronized(observers) { 57 | for (o in observers) 58 | o.eventProperty(property, value) 59 | } 60 | } 61 | 62 | @JvmStatic 63 | fun eventProperty(property: String, value: Double) { 64 | synchronized(observers) { 65 | for (o in observers) 66 | o.eventProperty(property, value) 67 | } 68 | } 69 | 70 | @JvmStatic 71 | fun eventProperty(property: String, value: Boolean) { 72 | synchronized(observers) { 73 | for (o in observers) 74 | o.eventProperty(property, value) 75 | } 76 | } 77 | 78 | @JvmStatic 79 | fun eventProperty(property: String, value: String) { 80 | synchronized(observers) { 81 | for (o in observers) 82 | o.eventProperty(property, value) 83 | } 84 | } 85 | 86 | @JvmStatic 87 | fun eventProperty(property: String) { 88 | synchronized(observers) { 89 | for (o in observers) 90 | o.eventProperty(property) 91 | } 92 | } 93 | 94 | @JvmStatic 95 | fun event(eventId: Int) { 96 | synchronized(observers) { 97 | for (o in observers) 98 | o.event(eventId) 99 | } 100 | } 101 | 102 | private val log_observers = mutableListOf() 103 | 104 | @JvmStatic 105 | fun addLogObserver(o: LogObserver) { 106 | synchronized(log_observers) { 107 | log_observers.add(o) 108 | } 109 | } 110 | 111 | @JvmStatic 112 | fun removeLogObserver(o: LogObserver) { 113 | synchronized(log_observers) { 114 | log_observers.remove(o) 115 | } 116 | } 117 | 118 | @JvmStatic 119 | fun logMessage(prefix: String, level: Int, text: String) { 120 | synchronized(log_observers) { 121 | for (o in log_observers) 122 | o.logMessage(prefix, level, text) 123 | } 124 | } 125 | 126 | interface EventObserver { 127 | fun eventProperty(property: String) 128 | fun eventProperty(property: String, value: Long) 129 | fun eventProperty(property: String, value: Double) 130 | fun eventProperty(property: String, value: Boolean) 131 | fun eventProperty(property: String, value: String) 132 | fun event(eventId: Int) 133 | } 134 | 135 | interface LogObserver { 136 | fun logMessage(prefix: String, level: Int, text: String) 137 | } 138 | 139 | object MpvFormat { 140 | const val MPV_FORMAT_NONE: Int = 0 141 | const val MPV_FORMAT_STRING: Int = 1 142 | const val MPV_FORMAT_OSD_STRING: Int = 2 143 | const val MPV_FORMAT_FLAG: Int = 3 144 | const val MPV_FORMAT_INT64: Int = 4 145 | const val MPV_FORMAT_DOUBLE: Int = 5 146 | const val MPV_FORMAT_NODE: Int = 6 147 | const val MPV_FORMAT_NODE_ARRAY: Int = 7 148 | const val MPV_FORMAT_NODE_MAP: Int = 8 149 | const val MPV_FORMAT_BYTE_ARRAY: Int = 9 150 | } 151 | 152 | object MpvEvent { 153 | const val MPV_EVENT_NONE: Int = 0 154 | const val MPV_EVENT_SHUTDOWN: Int = 1 155 | const val MPV_EVENT_LOG_MESSAGE: Int = 2 156 | const val MPV_EVENT_GET_PROPERTY_REPLY: Int = 3 157 | const val MPV_EVENT_SET_PROPERTY_REPLY: Int = 4 158 | const val MPV_EVENT_COMMAND_REPLY: Int = 5 159 | const val MPV_EVENT_START_FILE: Int = 6 160 | const val MPV_EVENT_END_FILE: Int = 7 161 | const val MPV_EVENT_FILE_LOADED: Int = 8 162 | @Deprecated("") 163 | const val MPV_EVENT_IDLE: Int = 11 164 | @Deprecated("") 165 | const val MPV_EVENT_TICK: Int = 14 166 | const val MPV_EVENT_CLIENT_MESSAGE: Int = 16 167 | const val MPV_EVENT_VIDEO_RECONFIG: Int = 17 168 | const val MPV_EVENT_AUDIO_RECONFIG: Int = 18 169 | const val MPV_EVENT_SEEK: Int = 20 170 | const val MPV_EVENT_PLAYBACK_RESTART: Int = 21 171 | const val MPV_EVENT_PROPERTY_CHANGE: Int = 22 172 | const val MPV_EVENT_QUEUE_OVERFLOW: Int = 24 173 | const val MPV_EVENT_HOOK: Int = 25 174 | } 175 | 176 | object MpvLogLevel { 177 | const val MPV_LOG_LEVEL_NONE: Int = 0 178 | const val MPV_LOG_LEVEL_FATAL: Int = 10 179 | const val MPV_LOG_LEVEL_ERROR: Int = 20 180 | const val MPV_LOG_LEVEL_WARN: Int = 30 181 | const val MPV_LOG_LEVEL_INFO: Int = 40 182 | const val MPV_LOG_LEVEL_V: Int = 50 183 | const val MPV_LOG_LEVEL_DEBUG: Int = 60 184 | const val MPV_LOG_LEVEL_TRACE: Int = 70 185 | } 186 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" --------------------------------------------------------------------------------