Android: Make the meta-data reader async

Reading the meta-data can be slow and cause the UI to become
unresponsive. This is especially noticeable when the media source
is loaded from a remote location, or over a slow network.
To improve this situation the media-reader is moved off the main thread
and we wait until the media is loaded before starting the meta-data
extraction.

Task-number: QTBUG-46491
Change-Id: I0b9cf2ae6b8e08596a2f0b8fa0042d74604c46f9
Reviewed-by: Yoann Lopes <yoann.lopes@theqtcompany.com>
This commit is contained in:
Christian Strømme
2015-11-19 17:58:10 +01:00
committed by Christian Stromme
parent 6166f102f5
commit 63c53d21bd
3 changed files with 143 additions and 106 deletions

View File

@@ -563,6 +563,7 @@ void QAndroidMediaPlayerControl::onStateChanged(qint32 state)
} else { } else {
onBufferingChanged(100); onBufferingChanged(100);
} }
Q_EMIT metaDataUpdated();
setAudioAvailable(true); setAudioAvailable(true);
flushPendingStates(); flushPendingStates();
break; break;

View File

@@ -37,6 +37,8 @@
#include <QtMultimedia/qmediametadata.h> #include <QtMultimedia/qmediametadata.h>
#include <qsize.h> #include <qsize.h>
#include <QDate> #include <QDate>
#include <QtConcurrent/qtconcurrentrun.h>
#include <QtCore/qvector.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -63,147 +65,180 @@ static const char* qt_ID3GenreNames[] =
"Euro-House", "Dance Hall" "Euro-House", "Dance Hall"
}; };
typedef QVector<QAndroidMetaDataReaderControl *> AndroidMetaDataReaders;
Q_GLOBAL_STATIC(AndroidMetaDataReaders, g_metaDataReaders)
Q_GLOBAL_STATIC(QMutex, g_metaDataReadersMtx)
QAndroidMetaDataReaderControl::QAndroidMetaDataReaderControl(QObject *parent) QAndroidMetaDataReaderControl::QAndroidMetaDataReaderControl(QObject *parent)
: QMetaDataReaderControl(parent) : QMetaDataReaderControl(parent)
, m_available(false) , m_available(false)
, m_retriever(new AndroidMediaMetadataRetriever)
{ {
} }
QAndroidMetaDataReaderControl::~QAndroidMetaDataReaderControl() QAndroidMetaDataReaderControl::~QAndroidMetaDataReaderControl()
{ {
if (m_retriever) { QMutexLocker l(g_metaDataReadersMtx);
m_retriever->release(); const int idx = g_metaDataReaders->indexOf(this);
delete m_retriever; if (idx != -1)
} g_metaDataReaders->remove(idx);
} }
bool QAndroidMetaDataReaderControl::isMetaDataAvailable() const bool QAndroidMetaDataReaderControl::isMetaDataAvailable() const
{ {
return m_available; const QMutexLocker l(&m_mtx);
return m_available && !m_metadata.isEmpty();
} }
QVariant QAndroidMetaDataReaderControl::metaData(const QString &key) const QVariant QAndroidMetaDataReaderControl::metaData(const QString &key) const
{ {
const QMutexLocker l(&m_mtx);
return m_metadata.value(key); return m_metadata.value(key);
} }
QStringList QAndroidMetaDataReaderControl::availableMetaData() const QStringList QAndroidMetaDataReaderControl::availableMetaData() const
{ {
const QMutexLocker l(&m_mtx);
return m_metadata.keys(); return m_metadata.keys();
} }
void QAndroidMetaDataReaderControl::onMediaChanged(const QMediaContent &media) void QAndroidMetaDataReaderControl::onMediaChanged(const QMediaContent &media)
{ {
if (!m_retriever) const QMutexLocker l(&m_mtx);
return; m_metadata.clear();
m_mediaContent = media; m_mediaContent = media;
updateData();
} }
void QAndroidMetaDataReaderControl::onUpdateMetaData() void QAndroidMetaDataReaderControl::onUpdateMetaData()
{ {
if (!m_retriever || m_mediaContent.isNull()) {
return; const QMutexLocker l(g_metaDataReadersMtx);
if (!g_metaDataReaders->contains(this))
updateData(); g_metaDataReaders->append(this);
}
void QAndroidMetaDataReaderControl::updateData()
{
m_metadata.clear();
if (!m_mediaContent.isNull()) {
if (m_retriever->setDataSource(m_mediaContent.canonicalUrl())) {
QString mimeType = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::MimeType);
if (!mimeType.isNull())
m_metadata.insert(QMediaMetaData::MediaType, mimeType);
bool isVideo = !m_retriever->extractMetadata(AndroidMediaMetadataRetriever::HasVideo).isNull()
|| mimeType.startsWith(QStringLiteral("video"));
QString string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Album);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::AlbumTitle, string);
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::AlbumArtist);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::AlbumArtist, string);
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Artist);
if (!string.isNull()) {
m_metadata.insert(isVideo ? QMediaMetaData::LeadPerformer
: QMediaMetaData::ContributingArtist,
string.split('/', QString::SkipEmptyParts));
}
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Author);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Author, string.split('/', QString::SkipEmptyParts));
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Bitrate);
if (!string.isNull()) {
m_metadata.insert(isVideo ? QMediaMetaData::VideoBitRate
: QMediaMetaData::AudioBitRate,
string.toInt());
}
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::CDTrackNumber);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::TrackNumber, string.toInt());
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Composer);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Composer, string.split('/', QString::SkipEmptyParts));
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Date);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Date, QDateTime::fromString(string, QStringLiteral("yyyyMMddTHHmmss.zzzZ")).date());
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Duration);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Duration, string.toLongLong());
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Genre);
if (!string.isNull()) {
// The genre can be returned as an ID3v2 id, get the name for it in that case
if (string.startsWith('(') && string.endsWith(')')) {
bool ok = false;
int genreId = string.midRef(1, string.length() - 2).toInt(&ok);
if (ok && genreId >= 0 && genreId <= 125)
string = QLatin1String(qt_ID3GenreNames[genreId]);
}
m_metadata.insert(QMediaMetaData::Genre, string);
}
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Title);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Title, string);
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::VideoHeight);
if (!string.isNull()) {
int height = string.toInt();
int width = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::VideoWidth).toInt();
m_metadata.insert(QMediaMetaData::Resolution, QSize(width, height));
}
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Writer);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Writer, string.split('/', QString::SkipEmptyParts));
string = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::Year);
if (!string.isNull())
m_metadata.insert(QMediaMetaData::Year, string.toInt());
}
} }
bool oldAvailable = m_available; const QMutexLocker ml(&m_mtx);
if (m_mediaContent.isNull())
return;
const QUrl &url = m_mediaContent.canonicalUrl();
QtConcurrent::run(&extractMetadata, this, url);
}
void QAndroidMetaDataReaderControl::updateData(const QVariantMap &metadata, const QUrl &url)
{
const QMutexLocker l(&m_mtx);
if (m_mediaContent.canonicalUrl() != url)
return;
const bool oldAvailable = m_available;
m_metadata = metadata;
m_available = !m_metadata.isEmpty(); m_available = !m_metadata.isEmpty();
if (m_available != oldAvailable) if (m_available != oldAvailable)
Q_EMIT metaDataAvailableChanged(m_available); Q_EMIT metaDataAvailableChanged(m_available);
Q_EMIT metaDataChanged(); Q_EMIT metaDataChanged();
} }
void QAndroidMetaDataReaderControl::extractMetadata(QAndroidMetaDataReaderControl *caller,
const QUrl &url)
{
QVariantMap metadata;
if (!url.isEmpty()) {
AndroidMediaMetadataRetriever retriever;
if (!retriever.setDataSource(url))
return;
QString mimeType = retriever.extractMetadata(AndroidMediaMetadataRetriever::MimeType);
if (!mimeType.isNull())
metadata.insert(QMediaMetaData::MediaType, mimeType);
bool isVideo = !retriever.extractMetadata(AndroidMediaMetadataRetriever::HasVideo).isNull()
|| mimeType.startsWith(QStringLiteral("video"));
QString string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Album);
if (!string.isNull())
metadata.insert(QMediaMetaData::AlbumTitle, string);
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::AlbumArtist);
if (!string.isNull())
metadata.insert(QMediaMetaData::AlbumArtist, string);
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Artist);
if (!string.isNull()) {
metadata.insert(isVideo ? QMediaMetaData::LeadPerformer
: QMediaMetaData::ContributingArtist,
string.split('/', QString::SkipEmptyParts));
}
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Author);
if (!string.isNull())
metadata.insert(QMediaMetaData::Author, string.split('/', QString::SkipEmptyParts));
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Bitrate);
if (!string.isNull()) {
metadata.insert(isVideo ? QMediaMetaData::VideoBitRate
: QMediaMetaData::AudioBitRate,
string.toInt());
}
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::CDTrackNumber);
if (!string.isNull())
metadata.insert(QMediaMetaData::TrackNumber, string.toInt());
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Composer);
if (!string.isNull())
metadata.insert(QMediaMetaData::Composer, string.split('/', QString::SkipEmptyParts));
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Date);
if (!string.isNull())
metadata.insert(QMediaMetaData::Date, QDateTime::fromString(string, QStringLiteral("yyyyMMddTHHmmss.zzzZ")).date());
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Duration);
if (!string.isNull())
metadata.insert(QMediaMetaData::Duration, string.toLongLong());
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Genre);
if (!string.isNull()) {
// The genre can be returned as an ID3v2 id, get the name for it in that case
if (string.startsWith('(') && string.endsWith(')')) {
bool ok = false;
const int genreId = string.midRef(1, string.length() - 2).toInt(&ok);
if (ok && genreId >= 0 && genreId <= 125)
string = QLatin1String(qt_ID3GenreNames[genreId]);
}
metadata.insert(QMediaMetaData::Genre, string);
}
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Title);
if (!string.isNull())
metadata.insert(QMediaMetaData::Title, string);
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::VideoHeight);
if (!string.isNull()) {
const int height = string.toInt();
const int width = retriever.extractMetadata(AndroidMediaMetadataRetriever::VideoWidth).toInt();
metadata.insert(QMediaMetaData::Resolution, QSize(width, height));
}
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Writer);
if (!string.isNull())
metadata.insert(QMediaMetaData::Writer, string.split('/', QString::SkipEmptyParts));
string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Year);
if (!string.isNull())
metadata.insert(QMediaMetaData::Year, string.toInt());
retriever.release();
}
const QMutexLocker lock(g_metaDataReadersMtx);
if (!g_metaDataReaders->contains(caller))
return;
caller->updateData(metadata, url);
}
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@@ -36,6 +36,7 @@
#include <QMetaDataReaderControl> #include <QMetaDataReaderControl>
#include <qmediacontent.h> #include <qmediacontent.h>
#include <QMutex>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -58,13 +59,13 @@ public Q_SLOTS:
void onUpdateMetaData(); void onUpdateMetaData();
private: private:
void updateData(); void updateData(const QVariantMap &metadata, const QUrl &url);
static void extractMetadata(QAndroidMetaDataReaderControl *caller, const QUrl &url);
mutable QMutex m_mtx;
QMediaContent m_mediaContent; QMediaContent m_mediaContent;
bool m_available; bool m_available;
QVariantMap m_metadata; QVariantMap m_metadata;
AndroidMediaMetadataRetriever *m_retriever;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE