musikr: integrate metadatajni into app
Just extracts title rn while I still work on the ffi.
This commit is contained in:
parent
729a3c3273
commit
2ee9556564
10 changed files with 101 additions and 145 deletions
|
@ -42,10 +42,6 @@ android {
|
|||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-DEBUG"
|
||||
|
||||
ndk {
|
||||
debugSymbolLevel 'FULL'
|
||||
}
|
||||
}
|
||||
|
||||
release {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ version = "1.0.0"
|
|||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
name = "metadatajni"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0.137"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,50 +88,36 @@ 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",
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue