diff --git a/ktaglib/src/main/cpp/AndroidIOStream.cpp b/ktaglib/src/main/cpp/AndroidIOStream.cpp deleted file mode 100644 index 61ae43b90..000000000 --- a/ktaglib/src/main/cpp/AndroidIOStream.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// -// Created by oxycblt on 12/12/24. -// - -#include "AndroidIOStream.h" - - -AndroidIOStream::AndroidIOStream(JNIEnv *env, jobject &fileRef) : env(env), fileRef(fileRef) {} - -AndroidIOStream::~AndroidIOStream() {} - -/*! - * Returns the stream name in the local file system encoding. - */ -TagLib::FileName AndroidIOStream::name() const { - return TagLib::FileName(); -}; - -/*! - * Reads a block of size \a length at the current get pointer. - */ -TagLib::ByteVector AndroidIOStream::readBlock(size_t length) { - return {}; -}; - -void AndroidIOStream::writeBlock(const TagLib::ByteVector &data) { - throw std::runtime_error("Not implemented"); -}; - -void AndroidIOStream::insert(const TagLib::ByteVector &data, - TagLib::offset_t start, size_t replace) { - throw std::runtime_error("Not implemented"); -}; - -void AndroidIOStream::removeBlock(TagLib::offset_t start, size_t length) { - throw std::runtime_error("Not implemented"); -}; - -bool AndroidIOStream::readOnly() const { - return true; -}; - -bool AndroidIOStream::isOpen() const { - -}; - -void AndroidIOStream::seek(TagLib::offset_t offset, Position p) { - -}; - -void AndroidIOStream::clear() { - -} - -TagLib::offset_t AndroidIOStream::tell() const { - return 0; -}; - -TagLib::offset_t AndroidIOStream::length() { - return 0; -}; - -void AndroidIOStream::truncate(TagLib::offset_t length) { - -}; \ No newline at end of file diff --git a/ktaglib/src/main/cpp/CMakeLists.txt b/ktaglib/src/main/cpp/CMakeLists.txt index 5aeb06623..a4d3d6b09 100644 --- a/ktaglib/src/main/cpp/CMakeLists.txt +++ b/ktaglib/src/main/cpp/CMakeLists.txt @@ -46,7 +46,7 @@ set_target_properties( add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. ktaglib.cpp - AndroidIOStream.cpp) + JVMInputStream.cpp) # Specifies libraries CMake should link to your target library. You # can link libraries from various origins, such as libraries defined in this diff --git a/ktaglib/src/main/cpp/JVMInputStream.cpp b/ktaglib/src/main/cpp/JVMInputStream.cpp new file mode 100644 index 000000000..f00502829 --- /dev/null +++ b/ktaglib/src/main/cpp/JVMInputStream.cpp @@ -0,0 +1,101 @@ +// +// Created by oxycblt on 12/12/24. +// + +#include "JVMInputStream.h" + +#include + +// TODO: Handle stream exceptions + +JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) : + env(env), inputStream(inputStream) { + if (!env->IsInstanceOf(inputStream, env->FindClass("org/oxycblt/ktaglib/NativeInputStream"))) { + throw std::runtime_error("oStream is not an instance of TagLibOStream"); + } + jclass inputStreamClass = env->FindClass("org/oxycblt/ktaglib/NativeInputStream"); + inputStreamNameMethod = env->GetMethodID(inputStreamClass, "name", "()Ljava/lang/String;"); + inputStreamReadBlockMethod = env->GetMethodID(inputStreamClass, "readBlock", "(J)[B"); + inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen", "()Z"); + inputStreamSeekFromBeginningMethod = env->GetMethodID(inputStreamClass, "seekFromBeginning", + "(J)V"); + inputStreamSeekFromCurrentMethod = env->GetMethodID(inputStreamClass, "seekFromCurrent", + "(J)V"); + inputStreamSeekFromEndMethod = env->GetMethodID(inputStreamClass, "seekFromEnd", "(J)V"); + inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V"); + inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J"); + inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length", "()J"); +} + +JVMInputStream::~JVMInputStream() {} + +TagLib::FileName JVMInputStream::name() const { + auto name = (jstring) env->CallObjectMethod(inputStream, inputStreamNameMethod); + const char *nameChars = env->GetStringUTFChars(name, nullptr); + auto fileName = TagLib::FileName(nameChars); + env->ReleaseStringUTFChars(name, nameChars); + return fileName; +} + +TagLib::ByteVector JVMInputStream::readBlock(size_t length) { + auto data = (jbyteArray) env->CallObjectMethod(inputStream, inputStreamReadBlockMethod, length); + jsize dataLength = env->GetArrayLength(data); + auto dataBytes = env->GetByteArrayElements(data, nullptr); + TagLib::ByteVector byteVector(reinterpret_cast(dataBytes), dataLength); + env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT); + return byteVector; +} + +void JVMInputStream::writeBlock(const TagLib::ByteVector &data) { + throw std::runtime_error("Not implemented"); +} + +void JVMInputStream::insert(const TagLib::ByteVector &data, + TagLib::offset_t start, size_t replace) { + throw std::runtime_error("Not implemented"); +} + +void JVMInputStream::removeBlock(TagLib::offset_t start, size_t length) { + throw std::runtime_error("Not implemented"); +} + +bool JVMInputStream::readOnly() const { + return true; +} + +bool JVMInputStream::isOpen() const { + return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod); +} + +void JVMInputStream::seek(TagLib::offset_t offset, Position p) { + auto joffset = static_cast(std::llround(offset)); + switch (p) { + case Beginning: + env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod, joffset); + break; + case Current: + env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod, joffset); + break; + case End: + env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset); + break; + } +} + +void JVMInputStream::clear() { + env->CallVoidMethod(inputStream, inputStreamClearMethod); +} + +TagLib::offset_t JVMInputStream::tell() const { + jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod); + return static_cast(jposition); +} + +TagLib::offset_t JVMInputStream::length() { + jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod); + return static_cast(jlength); +} + +void JVMInputStream::truncate(TagLib::offset_t length) { + throw std::runtime_error("Not implemented"); +} \ No newline at end of file diff --git a/ktaglib/src/main/cpp/AndroidIOStream.h b/ktaglib/src/main/cpp/JVMInputStream.h similarity index 78% rename from ktaglib/src/main/cpp/AndroidIOStream.h rename to ktaglib/src/main/cpp/JVMInputStream.h index d85717e54..a35a1e631 100644 --- a/ktaglib/src/main/cpp/AndroidIOStream.h +++ b/ktaglib/src/main/cpp/JVMInputStream.h @@ -2,21 +2,21 @@ // Created by oxycblt on 12/12/24. // -#ifndef AUXIO_ANDROIDIOSTREAM_H -#define AUXIO_ANDROIDIOSTREAM_H +#ifndef AUXIO_JVMINPUTSTREAM_H +#define AUXIO_JVMINPUTSTREAM_H #include #include "taglib/tiostream.h" -class AndroidIOStream : public TagLib::IOStream { +class JVMInputStream : public TagLib::IOStream { public: - AndroidIOStream(JNIEnv *env, jobject &fileRef); + JVMInputStream(JNIEnv *env, jobject inputStream); - ~AndroidIOStream(); + ~JVMInputStream(); - AndroidIOStream(const AndroidIOStream &) = delete; - AndroidIOStream &operator=(const AndroidIOStream &) = delete; + JVMInputStream(const JVMInputStream &) = delete; + JVMInputStream &operator=(const JVMInputStream &) = delete; /*! * Returns the stream name in the local file system encoding. @@ -99,8 +99,18 @@ public: private: JNIEnv *env; - jobject &fileRef; + jobject inputStream; + jmethodID inputStreamNameMethod; + jmethodID inputStreamReadBlockMethod; + jmethodID inputStreamIsOpenMethod; + jmethodID inputStreamSeekFromBeginningMethod; + jmethodID inputStreamSeekFromCurrentMethod; + jmethodID inputStreamSeekFromEndMethod; + jmethodID inputStreamClearMethod; + jmethodID inputStreamTellMethod; + jmethodID inputStreamLengthMethod; + }; -#endif //AUXIO_ANDROIDIOSTREAM_H +#endif //AUXIO_JVMINPUTSTREAM_H diff --git a/ktaglib/src/main/cpp/ktaglib.cpp b/ktaglib/src/main/cpp/ktaglib.cpp index 6041f1153..cd2df7f60 100644 --- a/ktaglib/src/main/cpp/ktaglib.cpp +++ b/ktaglib/src/main/cpp/ktaglib.cpp @@ -1,16 +1,16 @@ #include #include -#include "AndroidIOStream.h" +#include "JVMInputStream.h" #include #include extern "C" JNIEXPORT jobject JNICALL -Java_org_oxycblt_ktaglib_KTagLib_load( +Java_org_oxycblt_ktaglib_KTagLib_openNative( JNIEnv* env, jobject /* this */, - jobject fileRef) { - AndroidIOStream stream { env, fileRef }; + jobject inputStream) { + JVMInputStream stream { env, inputStream }; TagLib::FileRef file { &stream }; if (file.isNull()) { return nullptr; @@ -27,7 +27,8 @@ Java_org_oxycblt_ktaglib_KTagLib_load( jclass tagClass = env->FindClass("org/oxycblt/ktaglib/Tag"); jmethodID tagInit = env->GetMethodID(tagClass, "", "(Ljava/util/Map;Ljava/util/Map;[B)V"); - // Create tag + jobject tagObj = env->NewObject(tagClass, tagInit, id3v2, vorbis, coverData); + return tagObj; } \ No newline at end of file diff --git a/ktaglib/src/main/java/org/oxycblt/ktaglib/AndroidInputStream.kt b/ktaglib/src/main/java/org/oxycblt/ktaglib/AndroidInputStream.kt new file mode 100644 index 000000000..5da72635d --- /dev/null +++ b/ktaglib/src/main/java/org/oxycblt/ktaglib/AndroidInputStream.kt @@ -0,0 +1,55 @@ +package org.oxycblt.ktaglib + +import android.content.Context +import java.io.FileInputStream +import java.nio.ByteBuffer + +class AndroidInputStream( + context: Context, + fileRef: FileRef +) : NativeInputStream { + private val fileName = fileRef.fileName + private val fd = requireNotNull(context.contentResolver.openFileDescriptor(fileRef.uri, "r")) { + "Failed to open file descriptor for ${fileRef.fileName}" + } + private val fis = FileInputStream(fd.fileDescriptor) + private val channel = fis.channel + + override fun name() = fileName + + override fun readBlock(length: Long): ByteArray { + val buffer = ByteBuffer.allocate(length.toInt()) + channel.read(buffer) + return buffer.array() + } + + override fun isOpen(): Boolean { + return channel.isOpen + } + + override fun seekFromBeginning(offset: Long) { + channel.position(offset) + } + + override fun seekFromCurrent(offset: Long) { + channel.position(channel.position() + offset) + } + + override fun seekFromEnd(offset: Long) { + channel.position(channel.size() - offset) + } + + override fun clear() { + // Nothing to clear + } + + override fun tell() = channel.position() + + override fun length() = channel.size() + + fun close() { + channel.close() + fis.close() + fd.close() + } +} \ No newline at end of file diff --git a/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt b/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt index e80950a58..e76526d74 100644 --- a/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt +++ b/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt @@ -1,23 +1,32 @@ package org.oxycblt.ktaglib -import java.io.InputStream +import android.content.Context +import android.net.Uri object KTagLib { - // Used to load the 'ktaglib' library on application startup. init { System.loadLibrary("ktaglib") } /** - * A native method that is implemented by the 'ktaglib' native library, - * which is packaged with this application. + * Open a file and extract a tag. + * + * Note: This method is blocking and should be handled as such if + * calling from a coroutine. */ - external fun load(fileRef: FileRef): Tag? + fun open(context: Context, ref: FileRef): Tag? { + val inputStream = AndroidInputStream(context, ref) + val tag = openNative(inputStream) + inputStream.close() + return tag + } + + private external fun openNative(ioStream: AndroidInputStream): Tag? } data class FileRef( val fileName: String, - val inputStream: InputStream + val uri: Uri ) data class Tag( diff --git a/ktaglib/src/main/java/org/oxycblt/ktaglib/NativeInputStream.kt b/ktaglib/src/main/java/org/oxycblt/ktaglib/NativeInputStream.kt new file mode 100644 index 000000000..e8330c7aa --- /dev/null +++ b/ktaglib/src/main/java/org/oxycblt/ktaglib/NativeInputStream.kt @@ -0,0 +1,27 @@ +package org.oxycblt.ktaglib + +/** + * Java interface for the read-only methods in TagLib's IOStream API. + * + * The vast majority of IO shim between Taglib/KTaglib should occur here + * to minimize JNI calls. + */ +interface NativeInputStream { + fun name(): String + + fun readBlock(length: Long): ByteArray + + fun isOpen(): Boolean + + fun seekFromBeginning(offset: Long) + + fun seekFromCurrent(offset: Long) + + fun seekFromEnd(offset: Long) + + fun clear() + + fun tell(): Long + + fun length(): Long +} \ No newline at end of file