Android: wait to have a valid video surface before loading a media.
Setting the video surface on the Android media player after it has loaded the media doesn't work on some hardware. Change-Id: I5e621a34ace9de458bfc65bfac8fa50c29cee9a5 Reviewed-by: Christian Stromme <christian.stromme@digia.com>
This commit is contained in:
committed by
The Qt Project
parent
1dfbe44d90
commit
5e7e8e04d1
@@ -45,6 +45,12 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static void textureReadyCallback(void *context)
|
||||
{
|
||||
if (context)
|
||||
reinterpret_cast<QAndroidMediaPlayerControl *>(context)->onSurfaceTextureReady();
|
||||
}
|
||||
|
||||
QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent)
|
||||
: QMediaPlayerControl(parent),
|
||||
mMediaPlayer(new JMediaPlayer),
|
||||
@@ -58,7 +64,8 @@ QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent)
|
||||
mVideoAvailable(false),
|
||||
mBuffering(false),
|
||||
mMediaPlayerReady(false),
|
||||
mPendingPosition(-1)
|
||||
mPendingPosition(-1),
|
||||
mPendingSetMedia(false)
|
||||
{
|
||||
connect(mMediaPlayer, SIGNAL(bufferingUpdate(qint32)),
|
||||
this, SLOT(onBufferChanged(qint32)));
|
||||
@@ -208,6 +215,13 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
|
||||
mMediaContent = mediaContent;
|
||||
mMediaStream = stream;
|
||||
|
||||
if (mVideoOutput && !mMediaPlayer->display()) {
|
||||
// if a video output is set but the video texture is not ready, delay loading the media
|
||||
// since it can cause problems on some hardware
|
||||
mPendingSetMedia = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString uri = mediaContent.canonicalUrl().toString();
|
||||
|
||||
if (!uri.isEmpty())
|
||||
@@ -231,6 +245,13 @@ void QAndroidMediaPlayerControl::setVideoOutput(QAndroidVideoOutput *videoOutput
|
||||
mVideoOutput->stop();
|
||||
|
||||
mVideoOutput = videoOutput;
|
||||
|
||||
if (mVideoOutput && !mMediaPlayer->display()) {
|
||||
if (mVideoOutput->isTextureReady())
|
||||
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
|
||||
else
|
||||
mVideoOutput->setTextureReadyCallback(textureReadyCallback, this);
|
||||
}
|
||||
}
|
||||
|
||||
void QAndroidMediaPlayerControl::play()
|
||||
@@ -239,7 +260,8 @@ void QAndroidMediaPlayerControl::play()
|
||||
mPendingState = QMediaPlayer::PlayingState;
|
||||
if (mCurrentState == QMediaPlayer::StoppedState
|
||||
&& !mMediaContent.isNull()
|
||||
&& mCurrentMediaStatus != QMediaPlayer::LoadingMedia) {
|
||||
&& mCurrentMediaStatus != QMediaPlayer::LoadingMedia
|
||||
&& !mPendingSetMedia) {
|
||||
setMedia(mMediaContent, 0);
|
||||
}
|
||||
return;
|
||||
@@ -392,16 +414,23 @@ void QAndroidMediaPlayerControl::onBufferChanged(qint32 percent)
|
||||
|
||||
void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
QSize newSize(width, height);
|
||||
|
||||
if (width == 0 || height == 0 || newSize == mVideoSize)
|
||||
return;
|
||||
|
||||
setVideoAvailable(true);
|
||||
mVideoSize = newSize;
|
||||
|
||||
if (mVideoOutput) {
|
||||
if (!mMediaPlayer->display())
|
||||
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
|
||||
if (mMediaPlayer->display())
|
||||
mVideoOutput->setVideoSize(QSize(width, height));
|
||||
if (mVideoOutput)
|
||||
mVideoOutput->setVideoSize(mVideoSize);
|
||||
}
|
||||
|
||||
void QAndroidMediaPlayerControl::onSurfaceTextureReady()
|
||||
{
|
||||
if (!mMediaPlayer->display() && mVideoOutput) {
|
||||
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
|
||||
flushPendingStates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +494,9 @@ void QAndroidMediaPlayerControl::setVideoAvailable(bool available)
|
||||
if (mVideoAvailable == available)
|
||||
return;
|
||||
|
||||
if (!available)
|
||||
mVideoSize = QSize();
|
||||
|
||||
mVideoAvailable = available;
|
||||
Q_EMIT videoAvailableChanged(mVideoAvailable);
|
||||
}
|
||||
@@ -479,6 +511,12 @@ void QAndroidMediaPlayerControl::resetBufferingProgress()
|
||||
|
||||
void QAndroidMediaPlayerControl::flushPendingStates()
|
||||
{
|
||||
if (mPendingSetMedia) {
|
||||
setMedia(mMediaContent, 0);
|
||||
mPendingSetMedia = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mPendingState) {
|
||||
case QMediaPlayer::PlayingState:
|
||||
if (mPendingPosition > -1)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#include <qglobal.h>
|
||||
#include <QMediaPlayerControl>
|
||||
#include <qsize.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@@ -75,6 +76,7 @@ public:
|
||||
void setMedia(const QMediaContent &mediaContent, QIODevice *stream) Q_DECL_OVERRIDE;
|
||||
|
||||
void setVideoOutput(QAndroidVideoOutput *videoOutput);
|
||||
void onSurfaceTextureReady();
|
||||
|
||||
Q_SIGNALS:
|
||||
void metaDataUpdated();
|
||||
@@ -105,11 +107,13 @@ private:
|
||||
int mBufferPercent;
|
||||
bool mAudioAvailable;
|
||||
bool mVideoAvailable;
|
||||
QSize mVideoSize;
|
||||
bool mBuffering;
|
||||
QMediaTimeRange mAvailablePlaybackRange;
|
||||
bool mMediaPlayerReady;
|
||||
QMediaPlayer::State mPendingState;
|
||||
qint64 mPendingPosition;
|
||||
bool mPendingSetMedia;
|
||||
|
||||
void setState(QMediaPlayer::State state);
|
||||
void setMediaStatus(QMediaPlayer::MediaStatus status);
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
typedef void (*TextureReadyCallback)(void*);
|
||||
|
||||
class QAndroidVideoOutput
|
||||
{
|
||||
public:
|
||||
@@ -55,6 +57,10 @@ public:
|
||||
virtual ~QAndroidVideoOutput() { }
|
||||
|
||||
virtual jobject surfaceHolder() = 0;
|
||||
|
||||
virtual bool isTextureReady() = 0;
|
||||
virtual void setTextureReadyCallback(TextureReadyCallback cb, void *context = 0) = 0;
|
||||
|
||||
virtual void setVideoSize(const QSize &size) = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include <QVideoSurfaceFormat>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <qevent.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@@ -134,6 +135,8 @@ QAndroidVideoRendererControl::QAndroidVideoRendererControl(QObject *parent)
|
||||
, m_surfaceTexture(0)
|
||||
, m_surfaceHolder(0)
|
||||
, m_externalTex(0)
|
||||
, m_textureReadyCallback(0)
|
||||
, m_textureReadyContext(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -177,42 +180,66 @@ void QAndroidVideoRendererControl::setSurface(QAbstractVideoSurface *surface)
|
||||
if (surface == m_surface)
|
||||
return;
|
||||
|
||||
if (m_surface && m_surface->isActive())
|
||||
if (m_surface && m_surface->isActive()) {
|
||||
m_surface->stop();
|
||||
m_surface->removeEventFilter(this);
|
||||
}
|
||||
|
||||
m_surface = surface;
|
||||
|
||||
if (m_surface)
|
||||
if (m_surface) {
|
||||
m_useImage = !m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).contains(QVideoFrame::Format_BGR32);
|
||||
m_surface->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
jobject QAndroidVideoRendererControl::surfaceHolder()
|
||||
bool QAndroidVideoRendererControl::isTextureReady()
|
||||
{
|
||||
if (m_surfaceHolder)
|
||||
return m_surfaceHolder->object();
|
||||
return QOpenGLContext::currentContext() || (m_surface && m_surface->property("GLContext").isValid());
|
||||
}
|
||||
|
||||
void QAndroidVideoRendererControl::setTextureReadyCallback(TextureReadyCallback cb, void *context)
|
||||
{
|
||||
m_textureReadyCallback = cb;
|
||||
m_textureReadyContext = context;
|
||||
}
|
||||
|
||||
bool QAndroidVideoRendererControl::initSurfaceTexture()
|
||||
{
|
||||
if (m_surfaceTexture)
|
||||
return true;
|
||||
|
||||
if (!m_surface)
|
||||
return false;
|
||||
|
||||
QOpenGLContext *currContext = QOpenGLContext::currentContext();
|
||||
|
||||
// If we don't have a GL context in the current thread, create one and share it
|
||||
// with the render thread GL context
|
||||
if (!currContext && !m_glContext) {
|
||||
QOpenGLContext *shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>());
|
||||
if (!shareContext)
|
||||
return false;
|
||||
|
||||
m_offscreenSurface = new QOffscreenSurface;
|
||||
QSurfaceFormat format;
|
||||
format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
|
||||
m_offscreenSurface->setFormat(format);
|
||||
m_offscreenSurface->create();
|
||||
|
||||
QOpenGLContext *shareContext = 0;
|
||||
if (m_surface)
|
||||
shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>());
|
||||
m_glContext = new QOpenGLContext;
|
||||
m_glContext->setFormat(m_offscreenSurface->requestedFormat());
|
||||
|
||||
if (shareContext)
|
||||
m_glContext->setShareContext(shareContext);
|
||||
|
||||
if (!m_glContext->create())
|
||||
return 0;
|
||||
if (!m_glContext->create()) {
|
||||
delete m_glContext;
|
||||
m_glContext = 0;
|
||||
delete m_offscreenSurface;
|
||||
m_offscreenSurface = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// if sharing contexts is not supported, fallback to image rendering and send the bits
|
||||
// to the video surface
|
||||
@@ -228,7 +255,21 @@ jobject QAndroidVideoRendererControl::surfaceHolder()
|
||||
|
||||
if (m_surfaceTexture->isValid()) {
|
||||
connect(m_surfaceTexture, SIGNAL(frameAvailable()), this, SLOT(onFrameAvailable()));
|
||||
} else {
|
||||
delete m_surfaceTexture;
|
||||
m_surfaceTexture = 0;
|
||||
glDeleteTextures(1, &m_externalTex);
|
||||
}
|
||||
|
||||
return m_surfaceTexture != 0;
|
||||
}
|
||||
|
||||
jobject QAndroidVideoRendererControl::surfaceHolder()
|
||||
{
|
||||
if (!initSurfaceTexture())
|
||||
return 0;
|
||||
|
||||
if (!m_surfaceHolder) {
|
||||
QJNILocalRef<jobject> surfaceTex = m_surfaceTexture->surfaceTexture();
|
||||
|
||||
m_androidSurface = new QJNIObject("android/view/Surface",
|
||||
@@ -236,16 +277,9 @@ jobject QAndroidVideoRendererControl::surfaceHolder()
|
||||
surfaceTex.object());
|
||||
|
||||
m_surfaceHolder = new JSurfaceTextureHolder(m_androidSurface->object());
|
||||
} else {
|
||||
delete m_surfaceTexture;
|
||||
m_surfaceTexture = 0;
|
||||
glDeleteTextures(1, &m_externalTex);
|
||||
}
|
||||
|
||||
if (m_surfaceHolder)
|
||||
return m_surfaceHolder->object();
|
||||
|
||||
return 0;
|
||||
return m_surfaceHolder->object();
|
||||
}
|
||||
|
||||
void QAndroidVideoRendererControl::setVideoSize(const QSize &size)
|
||||
@@ -373,4 +407,18 @@ void QAndroidVideoRendererControl::createGLResources()
|
||||
}
|
||||
}
|
||||
|
||||
bool QAndroidVideoRendererControl::eventFilter(QObject *, QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::DynamicPropertyChange) {
|
||||
QDynamicPropertyChangeEvent *event = static_cast<QDynamicPropertyChangeEvent*>(e);
|
||||
if (event->propertyName() == "GLContext" && m_textureReadyCallback) {
|
||||
m_textureReadyCallback(m_textureReadyContext);
|
||||
m_textureReadyCallback = 0;
|
||||
m_textureReadyContext = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
@@ -65,14 +65,18 @@ public:
|
||||
void setSurface(QAbstractVideoSurface *surface) Q_DECL_OVERRIDE;
|
||||
|
||||
jobject surfaceHolder() Q_DECL_OVERRIDE;
|
||||
bool isTextureReady() Q_DECL_OVERRIDE;
|
||||
void setTextureReadyCallback(TextureReadyCallback cb, void *context = 0) Q_DECL_OVERRIDE;
|
||||
void setVideoSize(const QSize &size) Q_DECL_OVERRIDE;
|
||||
void stop() Q_DECL_OVERRIDE;
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onFrameAvailable();
|
||||
|
||||
private:
|
||||
void setupSurface();
|
||||
bool initSurfaceTexture();
|
||||
void renderFrameToFbo();
|
||||
void createGLResources();
|
||||
|
||||
@@ -88,6 +92,9 @@ private:
|
||||
JSurfaceTexture *m_surfaceTexture;
|
||||
JSurfaceTextureHolder *m_surfaceHolder;
|
||||
uint m_externalTex;
|
||||
|
||||
TextureReadyCallback m_textureReadyCallback;
|
||||
void *m_textureReadyContext;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
Reference in New Issue
Block a user