musikr: integrate metadatajni into app

Just extracts title rn while I still work on the ffi.
This commit is contained in:
Alexander Capehart 2025-02-08 11:55:59 -07:00
parent 729a3c3273
commit 2ee9556564
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 101 additions and 145 deletions

View file

@ -42,10 +42,6 @@ android {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-DEBUG"
ndk {
debugSymbolLevel 'FULL'
}
}
release {

View file

@ -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"
}

View file

@ -24,34 +24,7 @@ internal data class Metadata(
val mp4: Map<String, List<String>>,
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,

View file

@ -17,7 +17,7 @@
*/
package org.oxycblt.musikr.metadata
import android.os.ParcelFileDescriptor
import android.util.Log
import java.io.FileInputStream

View file

@ -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
}

View file

@ -4,8 +4,8 @@ version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
name = "metadatajni"
crate-type = ["cdylib"]
[dependencies]
cxx = "1.0.137"

View file

@ -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

View file

@ -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<Self, jni::errors::Error> {
// 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<Self, jni::errors::Error> {
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<usize> {
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<u64> {
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)
}
}
}

View file

@ -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>,

View file

@ -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