diff --git a/app/build.gradle b/app/build.gradle index 8ca75ca7b..44235cae5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,10 +42,6 @@ android { debug { applicationIdSuffix ".debug" versionNameSuffix "-DEBUG" - - ndk { - debugSymbolLevel 'FULL' - } } release { diff --git a/musikr/build.gradle b/musikr/build.gradle index 81c6697c7..d952a7427 100644 --- a/musikr/build.gradle +++ b/musikr/build.gradle @@ -25,6 +25,7 @@ android { buildTypes { release { + // R8 flips out that random classes were stripped for unknown reasons minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -52,7 +53,53 @@ cargo { libname = "metadatajni" targets = ["arm", "arm64", "x86", "x86_64"] prebuiltToolchains = true - profile = 'debug' + profile = "release" +} + +tasks.whenTaskAdded { task -> + if (task.name == 'mergeDebugJniLibFolders' || task.name == 'mergeReleaseJniLibFolders') { + task.dependsOn 'cargoBuild' + } + for (target in cargo.targets) { + if (task.name == "cargoBuild${target.capitalize()}") { + task.dependsOn "copy_libc++_shared${target.capitalize()}" + } + } +} + +for (target in cargo.targets) { + tasks.register("copy_libc++_shared${target.capitalize()}", Copy) { + def ndkDir = android.ndkDirectory + // hostTag, abi and archTriple from: https://developer.android.com/ndk/guides/other_build_systems + + def hostTag + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + if (Os.isArch("x86_64") || Os.isArch("amd64")) { + hostTag = "windows-x86_64" + } else { + hostTag = "windows" + } + } else if (Os.isFamily(Os.FAMILY_MAC)) { + hostTag = "darwin-x86_64" + } else { + hostTag = "linux-x86_64" + } + + def (abi, archTriple) = [ + arm: ['armeabi-v7a', 'arm-linux-androideabi'], + arm64: ['arm64-v8a', 'aarch64-linux-android'], + x86: ['x86', 'i686-linux-android'], + x86_64: ['x86_64', 'x86_64-linux-android'], + ][target] + + def from_path = "$ndkDir/toolchains/llvm/prebuilt/$hostTag/sysroot/usr/lib/$archTriple/libc++_shared.so" + def into_path = layout.buildDirectory.dir("rustJniLibs/android/$abi") + + assert file(from_path).exists() + + from from_path + into into_path + } } dependencies { @@ -80,23 +127,6 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' } -// -//task assembleTaglib(type: Exec) { -// def jniDir = "$projectDir/src/main/cpp" -// def libs = new File("$jniDir/taglib/pkg") -// if (libs.exists()) { -// commandLine "true" -// return -// } -// commandLine "sh", "-c", "$jniDir/build_taglib.sh $jniDir $android.ndkDirectory" -//} -// -//afterEvaluate { -// preDebugBuild.dependsOn assembleTaglib -// preReleaseBuild.dependsOn assembleTaglib -//} -// -//clean { -// delete "$projectDir/src/main/cpp/taglib/pkg" -// delete "$projectDir/src/main/cpp/taglib/build" -//} +clean { + delete "$projectDir/build/rustJniLibs" +} diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt index 758e4483a..397acba85 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/Metadata.kt @@ -24,34 +24,7 @@ internal data class Metadata( val mp4: Map>, val cover: ByteArray?, val properties: Properties -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Metadata - - if (id3v2 != other.id3v2) return false - if (xiph != other.xiph) return false - if (mp4 != other.mp4) return false - if (cover != null) { - if (other.cover == null) return false - if (!cover.contentEquals(other.cover)) return false - } else if (other.cover != null) return false - if (properties != other.properties) return false - - return true - } - - override fun hashCode(): Int { - var result = id3v2.hashCode() - result = 31 * result + xiph.hashCode() - result = 31 * result + mp4.hashCode() - result = 31 * result + (cover?.contentHashCode() ?: 0) - result = 31 * result + properties.hashCode() - return result - } -} +) internal data class Properties( val mimeType: String, diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataExtractor.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataExtractor.kt index abf00fc3d..5b6b50bde 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataExtractor.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataExtractor.kt @@ -17,7 +17,7 @@ */ package org.oxycblt.musikr.metadata - + import android.os.ParcelFileDescriptor import android.util.Log import java.io.FileInputStream diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataJNI.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataJNI.kt index 74aaa358a..4969e7d04 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataJNI.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/MetadataJNI.kt @@ -24,6 +24,5 @@ internal object MetadataJNI { } // This is a rust function, Android Studio has no idea how to link to it - @Suppress("KotlinJniMissingFunction") external fun openFile(input: NativeInputStream): String } diff --git a/musikr/src/main/jni/Cargo.toml b/musikr/src/main/jni/Cargo.toml index 2b3deda83..926a1e73f 100644 --- a/musikr/src/main/jni/Cargo.toml +++ b/musikr/src/main/jni/Cargo.toml @@ -4,8 +4,8 @@ version = "1.0.0" edition = "2021" [lib] -crate-type = ["cdylib"] name = "metadatajni" +crate-type = ["cdylib"] [dependencies] cxx = "1.0.137" diff --git a/musikr/src/main/jni/build.rs b/musikr/src/main/jni/build.rs index 6c446502c..50f693467 100644 --- a/musikr/src/main/jni/build.rs +++ b/musikr/src/main/jni/build.rs @@ -113,6 +113,7 @@ fn main() { .include("shim") .include(".") // Add the current directory to include path .flag_if_supported("-std=c++14") + .cpp_link_stdlib("c++_shared") // Use shared C++ runtime for Android compatibility .compile("taglib_cxx_bindings"); // Rebuild if shim files change diff --git a/musikr/src/main/jni/src/jni_stream.rs b/musikr/src/main/jni/src/jni_stream.rs index 58bffada6..5077a2bcb 100644 --- a/musikr/src/main/jni/src/jni_stream.rs +++ b/musikr/src/main/jni/src/jni_stream.rs @@ -1,37 +1,19 @@ -use std::io::{Read, Seek, SeekFrom, Write}; +use crate::taglib::TagLibStream; use jni::objects::{JObject, JValue}; use jni::JNIEnv; -use crate::taglib::TagLibStream; +use std::io::{Read, Seek, SeekFrom, Write}; pub struct JInputStream<'local, 'a> { env: &'a mut JNIEnv<'local>, input: JObject<'local>, - // Cache the method IDs - name_method: jni::objects::JMethodID, - read_block_method: jni::objects::JMethodID, - is_open_method: jni::objects::JMethodID, - seek_from_beginning_method: jni::objects::JMethodID, - seek_from_current_method: jni::objects::JMethodID, - seek_from_end_method: jni::objects::JMethodID, - tell_method: jni::objects::JMethodID, - length_method: jni::objects::JMethodID, } impl<'local, 'a> JInputStream<'local, 'a> { - pub fn new(env: &'a mut JNIEnv<'local>, input: JObject<'local>) -> Result { - // Get the class reference - let class = env.find_class("org/oxycblt/musikr/metadata/NativeInputStream")?; - - // Cache all method IDs + pub fn new( + env: &'a mut JNIEnv<'local>, + input: JObject<'local>, + ) -> Result { Ok(JInputStream { - name_method: env.get_method_id(&class, "name", "()Ljava/lang/String;")?, - read_block_method: env.get_method_id(&class, "readBlock", "(Ljava/nio/ByteBuffer;)Z")?, - is_open_method: env.get_method_id(&class, "isOpen", "()Z")?, - seek_from_beginning_method: env.get_method_id(&class, "seekFromBeginning", "(J)Z")?, - seek_from_current_method: env.get_method_id(&class, "seekFromCurrent", "(J)Z")?, - seek_from_end_method: env.get_method_id(&class, "seekFromEnd", "(J)Z")?, - tell_method: env.get_method_id(&class, "tell", "()J")?, - length_method: env.get_method_id(&class, "length", "()J")?, env, input, }) @@ -40,23 +22,16 @@ impl<'local, 'a> JInputStream<'local, 'a> { impl<'local, 'a> TagLibStream for JInputStream<'local, 'a> { fn name(&mut self) -> String { - // Call the Java name() method - let name = unsafe { - self.env - .call_method_unchecked( - &self.input, - self.name_method, - jni::signature::ReturnType::Object, - &[] - ) - .unwrap() - .l() - .unwrap() - }; - + // Call the Java name() method safely + let name = self + .env + .call_method(&self.input, "name", "()Ljava/lang/String;", &[]) + .and_then(|result| result.l()) + .expect("Failed to call name() method"); + self.env .get_string(&name.into()) - .unwrap() + .expect("Failed to convert Java string") .into() } @@ -74,24 +49,22 @@ impl<'local, 'a> Read for JInputStream<'local, 'a> { .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))? }; - // Call readBlock - let success = unsafe { - self.env - .call_method_unchecked( - &self.input, - self.read_block_method, - jni::signature::ReturnType::Primitive(jni::signature::Primitive::Boolean), - &[JValue::Object(&byte_buffer).as_jni()] - ) - .unwrap() - .z() - .unwrap() - }; + // Call readBlock safely + let success = self + .env + .call_method( + &self.input, + "readBlock", + "(Ljava/nio/ByteBuffer;)Z", + &[JValue::Object(&byte_buffer)], + ) + .and_then(|result| result.z()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; if !success { return Err(std::io::Error::new( std::io::ErrorKind::Other, - "Failed to read block" + "Failed to read block", )); } @@ -103,7 +76,7 @@ impl<'local, 'a> Write for JInputStream<'local, 'a> { fn write(&mut self, _buf: &[u8]) -> std::io::Result { Err(std::io::Error::new( std::io::ErrorKind::PermissionDenied, - "JInputStream is read-only" + "JInputStream is read-only", )) } @@ -115,53 +88,39 @@ impl<'local, 'a> Write for JInputStream<'local, 'a> { impl<'local, 'a> Seek for JInputStream<'local, 'a> { fn seek(&mut self, pos: SeekFrom) -> std::io::Result { let (method, offset) = match pos { - SeekFrom::Start(offset) => (self.seek_from_beginning_method, offset as i64), - SeekFrom::Current(offset) => (self.seek_from_current_method, offset), - SeekFrom::End(offset) => (self.seek_from_end_method, offset), + SeekFrom::Start(offset) => ("seekFromBeginning", offset as i64), + SeekFrom::Current(offset) => ("seekFromCurrent", offset), + SeekFrom::End(offset) => ("seekFromEnd", offset), }; - // Call the appropriate seek method - let success = unsafe { - self.env - .call_method_unchecked( - &self.input, - method, - jni::signature::ReturnType::Primitive(jni::signature::Primitive::Boolean), - &[JValue::Long(offset).as_jni()] - ) - .unwrap() - .z() - .unwrap() - }; + // Call the appropriate seek method safely + let success = self + .env + .call_method(&self.input, method, "(J)Z", &[JValue::Long(offset)]) + .and_then(|result| result.z()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; if !success { return Err(std::io::Error::new( std::io::ErrorKind::Other, - "Failed to seek" + "Failed to seek", )); } - // Return current position - let position = unsafe { - self.env - .call_method_unchecked( - &self.input, - self.tell_method, - jni::signature::ReturnType::Primitive(jni::signature::Primitive::Long), - &[] - ) - .unwrap() - .j() - .unwrap() - }; + // Return current position safely + let position = self + .env + .call_method(&self.input, "tell", "()J", &[]) + .and_then(|result| result.j()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; if position == i64::MIN { return Err(std::io::Error::new( std::io::ErrorKind::Other, - "Failed to get position" + "Failed to get position", )); } Ok(position as u64) } -} \ No newline at end of file +} diff --git a/musikr/src/main/jni/src/lib.rs b/musikr/src/main/jni/src/lib.rs index e1c7f8e76..6c4941b14 100644 --- a/musikr/src/main/jni/src/lib.rs +++ b/musikr/src/main/jni/src/lib.rs @@ -9,7 +9,7 @@ pub use taglib::*; use jni_stream::JInputStream; #[no_mangle] -pub extern "system" fn Java_HelloWorld_hello<'local>( +pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, input: JObject<'local>, diff --git a/musikr/src/main/jni/src/taglib/ffi.rs b/musikr/src/main/jni/src/taglib/ffi.rs index b8005dea7..fb64e2ae2 100644 --- a/musikr/src/main/jni/src/taglib/ffi.rs +++ b/musikr/src/main/jni/src/taglib/ffi.rs @@ -32,8 +32,6 @@ pub(crate) mod bindings { // File tag methods #[namespace = "taglib_shim"] - fn File_tag(file: &File) -> bool; - #[namespace = "taglib_shim"] fn File_tag_title(file: &File) -> &TagString; // String conversion utilities