From 0238561156a3ee5fcafbede3d5b045ebff8d0074 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 18 Jul 2022 19:04:06 +0100 Subject: [PATCH] AndroidDocument: Make input stream more robust Previously, input streams created by AndroidDocument instances did not implement setPosition, so they were not useful for reading some file formats, such as WAV. Due to limitations of the Java InputStream interface, seeking backwards in a stream requires creating a whole new stream and seeking from the beginning, so it could be quite slow. --- .../native/juce_android_AndroidDocument.cpp | 19 ++-- .../juce_core/native/juce_android_Files.cpp | 89 ++++++++++++++++--- .../juce_core/native/juce_android_Network.cpp | 19 ---- 3 files changed, 82 insertions(+), 45 deletions(-) diff --git a/modules/juce_core/native/juce_android_AndroidDocument.cpp b/modules/juce_core/native/juce_android_AndroidDocument.cpp index 5b09605fc1..e3dba249e4 100644 --- a/modules/juce_core/native/juce_android_AndroidDocument.cpp +++ b/modules/juce_core/native/juce_android_AndroidDocument.cpp @@ -417,14 +417,18 @@ struct AndroidDocument::Utils return false; } - std::unique_ptr createInputStream() const override + std::unique_ptr createInputStream() const override { - return makeStream (AndroidStreamHelpers::StreamKind::input); + auto result = std::make_unique (uri); + return result->openedSuccessfully() ? std::move (result) : nullptr; } std::unique_ptr createOutputStream() const override { - return makeStream (AndroidStreamHelpers::StreamKind::output); + auto stream = AndroidStreamHelpers::createStream (uri, AndroidStreamHelpers::StreamKind::output); + + return stream.get() != nullptr ? std::make_unique (std::move (stream)) + : nullptr; } AndroidDocumentInfo getInfo() const override @@ -507,15 +511,6 @@ struct AndroidDocument::Utils NativeInfo getNativeInfo() const override { return { uri }; } private: - template - std::unique_ptr makeStream (AndroidStreamHelpers::StreamKind kind) const - { - auto stream = AndroidStreamHelpers::createStream (uri, kind); - - return stream.get() != nullptr ? std::make_unique (std::move (stream)) - : nullptr; - } - GlobalRef uri; }; diff --git a/modules/juce_core/native/juce_android_Files.cpp b/modules/juce_core/native/juce_android_Files.cpp index c0364541b2..efd43534d2 100644 --- a/modules/juce_core/native/juce_android_Files.cpp +++ b/modules/juce_core/native/juce_android_Files.cpp @@ -79,7 +79,8 @@ DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream") #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") \ - METHOD (read, "read", "([B)I") + METHOD (read, "read", "([B)I") \ + METHOD (skip, "skip", "(J)J") DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream") #undef JNI_CLASS_MEMBERS @@ -550,15 +551,38 @@ private: jsize size = 0; }; +//============================================================================== +struct AndroidStreamHelpers +{ + enum class StreamKind { output, input }; + + static LocalRef createStream (const GlobalRef& uri, StreamKind kind) + { + auto* env = getEnv(); + auto contentResolver = AndroidContentUriResolver::getContentResolver(); + + if (contentResolver == nullptr) + return {}; + + return LocalRef (env->CallObjectMethod (contentResolver.get(), + kind == StreamKind::input ? ContentResolver.openInputStream + : ContentResolver.openOutputStream, + uri.get())); + } +}; + //============================================================================== struct AndroidContentUriInputStream : public InputStream { - explicit AndroidContentUriInputStream (LocalRef&& streamIn) - : stream (std::move (streamIn)) {} + explicit AndroidContentUriInputStream (const GlobalRef& uriIn) + : uri (uriIn), + stream (AndroidStreamHelpers::createStream (uri, AndroidStreamHelpers::StreamKind::input)) + {} ~AndroidContentUriInputStream() override { getEnv()->CallVoidMethod (stream.get(), AndroidInputStream.close); + jniCheckHasExceptionOccurredAndClear(); } int64 getTotalLength() override { return -1; } @@ -569,30 +593,36 @@ struct AndroidContentUriInputStream : public InputStream { auto* env = getEnv(); - if ((jsize) maxBytesToRead > byteArray.getSize()) + if ((jsize) maxBytesToRead != byteArray.getSize()) byteArray = CachedByteArray { (jsize) maxBytesToRead }; const auto result = env->CallIntMethod (stream.get(), AndroidInputStream.read, byteArray.getNativeArray()); - if (result != -1) - { - pos += result; - - auto* rawBytes = env->GetByteArrayElements (byteArray.getNativeArray(), nullptr); - std::memcpy (destBuffer, rawBytes, static_cast (result)); - env->ReleaseByteArrayElements (byteArray.getNativeArray(), rawBytes, 0); - } - else + if (jniCheckHasExceptionOccurredAndClear() || result == -1) { exhausted = true; + return -1; } + pos += result; + + auto* rawBytes = env->GetByteArrayElements (byteArray.getNativeArray(), nullptr); + std::memcpy (destBuffer, rawBytes, static_cast (result)); + env->ReleaseByteArrayElements (byteArray.getNativeArray(), rawBytes, 0); + return result; } bool setPosition (int64 newPos) override { - return (newPos == pos); + if (newPos == pos) + return true; + + if (pos < newPos) + return skipImpl (newPos - pos); + + AndroidContentUriInputStream (uri).swap (*this); + return skipImpl (newPos); } int64 getPosition() override @@ -600,6 +630,37 @@ struct AndroidContentUriInputStream : public InputStream return pos; } + bool openedSuccessfully() const { return stream != nullptr; } + + void skipNextBytes (int64 num) override + { + skipImpl (num); + } + +private: + bool skipImpl (int64 num) + { + if (stream == nullptr) + return false; + + const auto skipped = getEnv()->CallLongMethod (stream, AndroidInputStream.skip, (jlong) num); + + if (jniCheckHasExceptionOccurredAndClear()) + return false; + + pos += skipped; + return skipped == num; + } + + auto tie() { return std::tie (uri, byteArray, stream, pos, exhausted); } + + void swap (AndroidContentUriInputStream& other) noexcept + { + auto toSwap = other.tie(); + tie().swap (toSwap); + } + + GlobalRef uri; CachedByteArray byteArray; GlobalRef stream; int64 pos = 0; diff --git a/modules/juce_core/native/juce_android_Network.cpp b/modules/juce_core/native/juce_android_Network.cpp index e9e11022de..f5a3afa89b 100644 --- a/modules/juce_core/native/juce_android_Network.cpp +++ b/modules/juce_core/native/juce_android_Network.cpp @@ -312,25 +312,6 @@ String URL::getFileName() const return toString (false).fromLastOccurrenceOf ("/", false, true); } -struct AndroidStreamHelpers -{ - enum class StreamKind { output, input }; - - static LocalRef createStream (const GlobalRef& uri, StreamKind kind) - { - auto* env = getEnv(); - auto contentResolver = AndroidContentUriResolver::getContentResolver(); - - if (contentResolver == nullptr) - return {}; - - return LocalRef (env->CallObjectMethod (contentResolver.get(), - kind == StreamKind::input ? ContentResolver.openInputStream - : ContentResolver.openOutputStream, - uri.get())); - } -}; - //============================================================================== class WebInputStream::Pimpl {