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:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user