Android: fix retrieving metadata from assets, qrc and remote files.

We need the same logic as for the media player: local files and assets
must be loaded with a FileDescriptor.
Because of a bug in Android API level >= 14, remote files have to be
loaded in different ways depending on the version.

Task-number: QTBUG-40274
Change-Id: I6411b959064d22219cf981a4dc8f4f26cf16f65f
Reviewed-by: Christian Stromme <christian.stromme@digia.com>
This commit is contained in:
Yoann Lopes
2014-07-17 18:41:44 +02:00
parent fb35f025e3
commit 341b86c63f
7 changed files with 113 additions and 47 deletions

View File

@@ -325,8 +325,10 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
mMediaPlayer->setDataSource(mediaPath); mMediaPlayer->setDataSource(mediaPath);
mMediaPlayer->prepareAsync(); mMediaPlayer->prepareAsync();
if (!reloading) if (!reloading) {
Q_EMIT mediaChanged(mMediaContent); Q_EMIT mediaChanged(mMediaContent);
Q_EMIT actualMediaLocationChanged(mediaPath);
}
resetBufferingProgress(); resetBufferingProgress();
} }

View File

@@ -80,6 +80,7 @@ public:
Q_SIGNALS: Q_SIGNALS:
void metaDataUpdated(); void metaDataUpdated();
void actualMediaLocationChanged(const QString &url);
public Q_SLOTS: public Q_SLOTS:
void setPosition(qint64 position) Q_DECL_OVERRIDE; void setPosition(qint64 position) Q_DECL_OVERRIDE;

View File

@@ -53,8 +53,8 @@ QAndroidMediaService::QAndroidMediaService(QObject *parent)
{ {
mMediaControl = new QAndroidMediaPlayerControl; mMediaControl = new QAndroidMediaPlayerControl;
mMetadataControl = new QAndroidMetaDataReaderControl; mMetadataControl = new QAndroidMetaDataReaderControl;
connect(mMediaControl, SIGNAL(mediaChanged(QMediaContent)), connect(mMediaControl, SIGNAL(actualMediaLocationChanged(QString)),
mMetadataControl, SLOT(onMediaChanged(QMediaContent))); mMetadataControl, SLOT(onMediaChanged(QString)));
connect(mMediaControl, SIGNAL(metaDataUpdated()), connect(mMediaControl, SIGNAL(metaDataUpdated()),
mMetadataControl, SLOT(onUpdateMetaData())); mMetadataControl, SLOT(onUpdateMetaData()));
} }

View File

@@ -101,18 +101,18 @@ QStringList QAndroidMetaDataReaderControl::availableMetaData() const
return m_metadata.keys(); return m_metadata.keys();
} }
void QAndroidMetaDataReaderControl::onMediaChanged(const QMediaContent &media) void QAndroidMetaDataReaderControl::onMediaChanged(const QString &url)
{ {
if (!m_retriever) if (!m_retriever)
return; return;
m_mediaContent = media; m_mediaLocation = url;
updateData(); updateData();
} }
void QAndroidMetaDataReaderControl::onUpdateMetaData() void QAndroidMetaDataReaderControl::onUpdateMetaData()
{ {
if (!m_retriever || m_mediaContent.isNull()) if (!m_retriever || m_mediaLocation.isEmpty())
return; return;
updateData(); updateData();
@@ -122,8 +122,8 @@ void QAndroidMetaDataReaderControl::updateData()
{ {
m_metadata.clear(); m_metadata.clear();
if (!m_mediaContent.isNull()) { if (!m_mediaLocation.isEmpty()) {
if (m_retriever->setDataSource(m_mediaContent.canonicalUrl())) { if (m_retriever->setDataSource(m_mediaLocation)) {
QString mimeType = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::MimeType); QString mimeType = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::MimeType);
if (!mimeType.isNull()) if (!mimeType.isNull())
m_metadata.insert(QMediaMetaData::MediaType, mimeType); m_metadata.insert(QMediaMetaData::MediaType, mimeType);

View File

@@ -62,13 +62,13 @@ public:
QStringList availableMetaData() const Q_DECL_OVERRIDE; QStringList availableMetaData() const Q_DECL_OVERRIDE;
public Q_SLOTS: public Q_SLOTS:
void onMediaChanged(const QMediaContent &media); void onMediaChanged(const QString &url);
void onUpdateMetaData(); void onUpdateMetaData();
private: private:
void updateData(); void updateData();
QMediaContent m_mediaContent; QString m_mediaLocation;
bool m_available; bool m_available;
QVariantMap m_metadata; QVariantMap m_metadata;

View File

@@ -43,9 +43,24 @@
#include <QtCore/private/qjnihelpers_p.h> #include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/private/qjni_p.h> #include <QtCore/private/qjni_p.h>
#include <QtCore/QUrl>
#include <qdebug.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
static bool exceptionCheckAndClear(JNIEnv *env)
{
if (Q_UNLIKELY(env->ExceptionCheck())) {
#ifdef QT_DEBUG
env->ExceptionDescribe();
#endif // QT_DEBUG
env->ExceptionClear();
return true;
}
return false;
}
AndroidMediaMetadataRetriever::AndroidMediaMetadataRetriever() AndroidMediaMetadataRetriever::AndroidMediaMetadataRetriever()
{ {
m_metadataRetriever = QJNIObjectPrivate("android/media/MediaMetadataRetriever"); m_metadataRetriever = QJNIObjectPrivate("android/media/MediaMetadataRetriever");
@@ -76,55 +91,105 @@ void AndroidMediaMetadataRetriever::release()
m_metadataRetriever.callMethod<void>("release"); m_metadataRetriever.callMethod<void>("release");
} }
bool AndroidMediaMetadataRetriever::setDataSource(const QUrl &url) bool AndroidMediaMetadataRetriever::setDataSource(const QString &urlString)
{ {
if (!m_metadataRetriever.isValid()) if (!m_metadataRetriever.isValid())
return false; return false;
QJNIEnvironmentPrivate env; QJNIEnvironmentPrivate env;
QUrl url(urlString);
bool loaded = false; if (url.isLocalFile()) { // also includes qrc files (copied to a temp file)
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.path());
QJNIObjectPrivate fileInputStream("java/io/FileInputStream",
"(Ljava/lang/String;)V",
string.object());
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.toString()); if (exceptionCheckAndClear(env))
return false;
QJNIObjectPrivate uri = m_metadataRetriever.callStaticObjectMethod("android/net/Uri", QJNIObjectPrivate fd = fileInputStream.callObjectMethod("getFD",
"parse", "()Ljava/io/FileDescriptor;");
"(Ljava/lang/String;)Landroid/net/Uri;", if (exceptionCheckAndClear(env)) {
string.object()); fileInputStream.callMethod<void>("close");
if (env->ExceptionCheck()) { exceptionCheckAndClear(env);
env->ExceptionClear(); return false;
}
m_metadataRetriever.callMethod<void>("setDataSource",
"(Ljava/io/FileDescriptor;)V",
fd.object());
bool ok = !exceptionCheckAndClear(env);
fileInputStream.callMethod<void>("close");
exceptionCheckAndClear(env);
if (!ok)
return false;
} else if (url.scheme() == QLatin1String("assets")) {
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.path().mid(1)); // remove first '/'
QJNIObjectPrivate activity(QtAndroidPrivate::activity());
QJNIObjectPrivate assetManager = activity.callObjectMethod("getAssets",
"()Landroid/content/res/AssetManager;");
QJNIObjectPrivate assetFd = assetManager.callObjectMethod("openFd",
"(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;",
string.object());
if (exceptionCheckAndClear(env))
return false;
QJNIObjectPrivate fd = assetFd.callObjectMethod("getFileDescriptor",
"()Ljava/io/FileDescriptor;");
if (exceptionCheckAndClear(env)) {
assetFd.callMethod<void>("close");
exceptionCheckAndClear(env);
return false;
}
m_metadataRetriever.callMethod<void>("setDataSource",
"(Ljava/io/FileDescriptor;JJ)V",
fd.object(),
assetFd.callMethod<jlong>("getStartOffset"),
assetFd.callMethod<jlong>("getLength"));
bool ok = !exceptionCheckAndClear(env);
assetFd.callMethod<void>("close");
exceptionCheckAndClear(env);
if (!ok)
return false;
} else if (QtAndroidPrivate::androidSdkVersion() >= 14) {
// On API levels >= 14, only setDataSource(String, Map<String, String>) accepts remote media
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(urlString);
QJNIObjectPrivate hash("java/util/HashMap");
m_metadataRetriever.callMethod<void>("setDataSource",
"(Ljava/lang/String;Ljava/util/Map;)V",
string.object(),
hash.object());
if (exceptionCheckAndClear(env))
return false;
} else { } else {
// While on API levels < 14, only setDataSource(Context, Uri) is available and works for
// remote media...
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(urlString);
QJNIObjectPrivate uri = m_metadataRetriever.callStaticObjectMethod("android/net/Uri",
"parse",
"(Ljava/lang/String;)Landroid/net/Uri;",
string.object());
if (exceptionCheckAndClear(env))
return false;
m_metadataRetriever.callMethod<void>("setDataSource", m_metadataRetriever.callMethod<void>("setDataSource",
"(Landroid/content/Context;Landroid/net/Uri;)V", "(Landroid/content/Context;Landroid/net/Uri;)V",
QtAndroidPrivate::activity(), QtAndroidPrivate::activity(),
uri.object()); uri.object());
if (env->ExceptionCheck()) if (exceptionCheckAndClear(env))
env->ExceptionClear(); return false;
else
loaded = true;
} }
return loaded; return true;
}
bool AndroidMediaMetadataRetriever::setDataSource(const QString &path)
{
if (!m_metadataRetriever.isValid())
return false;
QJNIEnvironmentPrivate env;
bool loaded = false;
m_metadataRetriever.callMethod<void>("setDataSource",
"(Ljava/lang/String;)V",
QJNIObjectPrivate::fromString(path).object());
if (env->ExceptionCheck())
env->ExceptionClear();
else
loaded = true;
return loaded;
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@@ -43,7 +43,6 @@
#define ANDROIDMEDIAMETADATARETRIEVER_H #define ANDROIDMEDIAMETADATARETRIEVER_H
#include <QtCore/private/qjni_p.h> #include <QtCore/private/qjni_p.h>
#include <qurl.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -81,8 +80,7 @@ public:
QString extractMetadata(MetadataKey key); QString extractMetadata(MetadataKey key);
void release(); void release();
bool setDataSource(const QUrl &url); bool setDataSource(const QString &url);
bool setDataSource(const QString &path);
private: private:
QJNIObjectPrivate m_metadataRetriever; QJNIObjectPrivate m_metadataRetriever;