Android: wait to have a valid video surface before loading a media.
authorYoann Lopes <yoann.lopes@digia.com>
Fri, 5 Jul 2013 14:26:55 +0000 (16:26 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Tue, 9 Jul 2013 07:41:32 +0000 (09:41 +0200)
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>
src/plugins/android/mediaplayer/qandroidmediaplayercontrol.cpp
src/plugins/android/mediaplayer/qandroidmediaplayercontrol.h
src/plugins/android/mediaplayer/qandroidvideooutput.h
src/plugins/android/mediaplayer/qandroidvideorendercontrol.cpp
src/plugins/android/mediaplayer/qandroidvideorendercontrol.h

index cb34fba..a70f4e1 100644 (file)
 
 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)
index 445e8de..93eced8 100644 (file)
@@ -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);
index 99db7c3..d59971f 100644 (file)
@@ -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;
 };
index c63e0e7..fe26b45 100644 (file)
@@ -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()
+{
+    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_surfaceHolder)
-        return m_surfaceHolder->object();
+    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
index 525291e..cd93550 100644 (file)
@@ -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