Android: Make the Mediaplayer more robust
authorChristian Strømme <christian.stromme@digia.com>
Thu, 20 Feb 2014 13:25:35 +0000 (14:25 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Thu, 20 Mar 2014 23:31:17 +0000 (00:31 +0100)
In some cases the the Android media player would get into a unexpected
state and we where then not able to recover.
With this patch we monitor the state changes more closely and recover
when possible.

Task-number: QTBUG-35651

Change-Id: I142c63fbbf716d3f94ebdcf016a7cadad7b13207
Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer.java
src/plugins/android/src/common/qandroidvideooutput.h
src/plugins/android/src/common/qandroidvideorendercontrol.cpp
src/plugins/android/src/common/qandroidvideorendercontrol.h
src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp
src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h
src/plugins/android/src/wrappers/jmediaplayer.cpp
src/plugins/android/src/wrappers/jmediaplayer.h

index cd79c97..86ec30a 100644 (file)
@@ -53,59 +53,56 @@ import android.util.Log;
 import java.io.FileDescriptor;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
+import android.view.SurfaceHolder;
 
-public class QtAndroidMediaPlayer extends MediaPlayer
+public class QtAndroidMediaPlayer
 {
     // Native callback functions for MediaPlayer
     native public void onErrorNative(int what, int extra, long id);
     native public void onBufferingUpdateNative(int percent, long id);
+    native public void onProgressUpdateNative(int progress, long id);
+    native public void onDurationChangedNative(int duration, long id);
     native public void onInfoNative(int what, int extra, long id);
-    native public void onMediaPlayerInfoNative(int what, int extra, long id);
     native public void onVideoSizeChangedNative(int width, int height, long id);
+    native public void onStateChangedNative(int state, long id);
 
+    private MediaPlayer mMediaPlayer = null;
     private Uri mUri = null;
     private final long mID;
+    private final Activity mActivity;
     private boolean mMuted = false;
-    private boolean mPreparing = false;
-    private boolean mInitialized = false;
     private int mVolume = 100;
     private static final String TAG = "Qt MediaPlayer";
-    private static Context mApplicationContext = null;
-
-    final int MEDIA_PLAYER_INVALID_STATE = 1;
-    final int MEDIA_PLAYER_PREPARING = 2;
-    final int MEDIA_PLAYER_READY = 3;
-    final int MEDIA_PLAYER_DURATION = 4;
-    final int MEDIA_PLAYER_PROGRESS = 5;
-    final int MEDIA_PLAYER_FINISHED = 6;
-
-    // Activity set by Qt on load.
-    static public void setActivity(final Activity activity)
-    {
-        try {
-            mApplicationContext = activity.getApplicationContext();
-        } catch(final Exception e) {
-            Log.d(TAG, "" + e.getMessage());
-        }
+    private SurfaceHolder mSurfaceHolder = null;
+
+    private class State {
+        final static int Uninitialized = 0x1 /* End */;
+        final static int Idle = 0x2;
+        final static int Preparing = 0x4;
+        final static int Prepared = 0x8;
+        final static int Initialized = 0x10;
+        final static int Started = 0x20;
+        final static int Stopped = 0x40;
+        final static int Paused = 0x80;
+        final static int PlaybackCompleted = 0x100;
+        final static int Error = 0x200;
     }
 
-    private class ProgressWatcher implements Runnable
+    private volatile int mState = State.Uninitialized;
+
+    private class ProgressWatcher
+    implements Runnable
     {
         @Override
         public void run()
         {
-            final int duratation = getDuration();
-            int currentPosition = getCurrentPosition();
-
             try {
-                while (duratation >= currentPosition && isPlaying()) {
-                    onMediaPlayerInfoNative(MEDIA_PLAYER_PROGRESS, currentPosition, mID);
+                while ((mState & (State.Started)) != 0) {
+                    onProgressUpdateNative(getCurrentPosition(), mID);
                     Thread.sleep(1000);
-                    currentPosition = getCurrentPosition();
                 }
             } catch (final InterruptedException e) {
-                Log.d(TAG, "" + e.getMessage());
-                return;
+                // Ignore
             }
         }
     }
@@ -121,7 +118,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
                                final int what,
                                final int extra)
         {
-            reset();
+            setState(State.Error);
             onErrorNative(what, extra, mID);
             return true;
         }
@@ -158,7 +155,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
         @Override
         public void onCompletion(final MediaPlayer mp)
         {
-            onMediaPlayerInfoNative(MEDIA_PLAYER_FINISHED, 0, mID);
+            setState(State.PlaybackCompleted);
         }
 
     }
@@ -190,9 +187,8 @@ public class QtAndroidMediaPlayer extends MediaPlayer
         @Override
         public void onPrepared(final MediaPlayer mp)
         {
-            mPreparing = false;
-            onMediaPlayerInfoNative(MEDIA_PLAYER_READY, 0, mID);
-            onMediaPlayerInfoNative(MEDIA_PLAYER_DURATION, getDuration(), mID);
+            setState(State.Prepared);
+            onDurationChangedNative(getDuration(), mID);
         }
 
     }
@@ -207,7 +203,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
         @Override
         public void onSeekComplete(final MediaPlayer mp)
         {
-            onMediaPlayerInfoNative(MEDIA_PLAYER_PROGRESS, getCurrentPosition(), mID);
+            onProgressUpdateNative(getCurrentPosition(), mID);
         }
 
     }
@@ -229,98 +225,117 @@ public class QtAndroidMediaPlayer extends MediaPlayer
 
     }
 
-    public QtAndroidMediaPlayer(final long id)
+    public QtAndroidMediaPlayer(final Activity activity, final long id)
     {
-        super();
         mID = id;
-        setOnBufferingUpdateListener(new MediaPlayerBufferingListener());
-        setOnCompletionListener(new MediaPlayerCompletionListener());
-        setOnInfoListener(new MediaPlayerInfoListener());
-        setOnSeekCompleteListener(new MediaPlayerSeekCompleteListener());
-        setOnVideoSizeChangedListener(new MediaPlayerVideoSizeChangedListener());
-        setOnErrorListener(new MediaPlayerErrorListener());
+        mActivity = activity;
     }
 
-    @Override
-    public void start()
+    private void setState(int state)
     {
-        if (!mInitialized) {
-            onMediaPlayerInfoNative(MEDIA_PLAYER_INVALID_STATE, 0, mID);
+        if (mState == state)
             return;
-        }
 
-        if (mApplicationContext == null)
-            return;
+        mState = state;
 
-        if (mPreparing)
-            return;
+        onStateChangedNative(mState, mID);
+    }
+
+
+    private void init()
+    {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+            setState(State.Idle);
+        }
+    }
 
-        if (isPlaying())
+    public void start()
+    {
+        if ((mState & (State.Prepared
+                       | State.Started
+                       | State.Paused
+                       | State.PlaybackCompleted)) == 0) {
             return;
+        }
 
         try {
-            super.start();
+            mMediaPlayer.start();
+            setState(State.Started);
             Thread progressThread = new Thread(new ProgressWatcher());
             progressThread.start();
         } catch (final IllegalStateException e) {
-            reset();
             Log.d(TAG, "" + e.getMessage());
         }
     }
 
-    @Override
+
     public void pause()
     {
-        if (!isPlaying())
+        if ((mState & (State.Started | State.Paused | State.PlaybackCompleted)) == 0)
             return;
 
         try {
-            super.pause();
+            mMediaPlayer.pause();
+            setState(State.Paused);
         } catch (final IllegalStateException e) {
-            reset();
             Log.d(TAG, "" + e.getMessage());
         }
     }
 
-    @Override
+
     public void stop()
     {
-        if (!mInitialized)
+        if ((mState & (State.Prepared
+                       | State.Started
+                       | State.Stopped
+                       | State.Paused
+                       | State.PlaybackCompleted)) == 0) {
             return;
+        }
 
         try {
-            super.stop();
+            mMediaPlayer.stop();
+            setState(State.Stopped);
         } catch (final IllegalStateException e) {
             Log.d(TAG, "" + e.getMessage());
-        } finally {
-            reset();
         }
     }
 
-    @Override
+
     public void seekTo(final int msec)
     {
-        if (!mInitialized)
+        if ((mState & (State.Prepared
+                       | State.Started
+                       | State.Paused
+                       | State.PlaybackCompleted)) == 0) {
             return;
+        }
 
         try {
-            super.seekTo(msec);
-            onMediaPlayerInfoNative(MEDIA_PLAYER_PROGRESS, msec, mID);
+            mMediaPlayer.seekTo(msec);
+            onProgressUpdateNative(msec, mID);
         } catch (final IllegalStateException e) {
             Log.d(TAG, "" + e.getMessage());
         }
     }
 
-    @Override
+
     public boolean isPlaying()
     {
         boolean playing = false;
-
-        if (!mInitialized)
+        if ((mState & (State.Idle
+                       | State.Initialized
+                       | State.Prepared
+                       | State.Started
+                       | State.Paused
+                       | State.Stopped
+                       | State.PlaybackCompleted)) == 0) {
             return playing;
+        }
 
         try {
-            playing = super.isPlaying();
+            playing = mMediaPlayer.isPlaying();
         } catch (final IllegalStateException e) {
             Log.d(TAG, "" + e.getMessage());
         }
@@ -328,34 +343,56 @@ public class QtAndroidMediaPlayer extends MediaPlayer
         return playing;
     }
 
-    public void setMediaPath(final String path)
+    public void prepareAsync()
+    {
+        if ((mState & (State.Initialized | State.Stopped)) == 0)
+           return;
+
+        try {
+            mMediaPlayer.prepareAsync();
+            setState(State.Preparing);
+        } catch (final IllegalStateException e) {
+            Log.d(TAG, "" + e.getMessage());
+        }
+    }
+
+    public void setDataSource(final String path)
     {
-        if (mInitialized)
-            reset();
+        if ((mState & State.Uninitialized) != 0)
+            init();
+
+        if ((mState & State.Idle) == 0)
+           return;
+
+        mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayerBufferingListener());
+        mMediaPlayer.setOnCompletionListener(new MediaPlayerCompletionListener());
+        mMediaPlayer.setOnInfoListener(new MediaPlayerInfoListener());
+        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayerSeekCompleteListener());
+        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayerVideoSizeChangedListener());
+        mMediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
+        mMediaPlayer.setOnPreparedListener(new MediaPlayerPreparedListener());
 
+        if (mSurfaceHolder != null)
+            mMediaPlayer.setDisplay(mSurfaceHolder);
+
+        AssetFileDescriptor afd = null;
         try {
-            mPreparing = true;
-            onMediaPlayerInfoNative(MEDIA_PLAYER_PREPARING, 0, mID);
             mUri = Uri.parse(path);
-            if (mUri.getScheme().compareTo("assets") == 0) {
+            final boolean inAssets = (mUri.getScheme().compareTo("assets") == 0);
+            if (inAssets) {
                 final String asset = mUri.getPath().substring(1 /* Remove first '/' */);
-                final AssetManager am = mApplicationContext.getAssets();
-                final AssetFileDescriptor afd = am.openFd(asset);
+                final AssetManager am = mActivity.getAssets();
+                afd = am.openFd(asset);
                 final long offset = afd.getStartOffset();
                 final long length = afd.getLength();
                 FileDescriptor fd = afd.getFileDescriptor();
-                setDataSource(fd, offset, length);
+                mMediaPlayer.setDataSource(fd, offset, length);
             } else {
-                setDataSource(mApplicationContext, mUri);
+                mMediaPlayer.setDataSource(mActivity, mUri);
             }
-            mInitialized = true;
-            setOnPreparedListener(new MediaPlayerPreparedListener());
-            prepareAsync();
+            setState(State.Initialized);
         } catch (final IOException e) {
-            mPreparing = false;
-            onErrorNative(MEDIA_ERROR_UNKNOWN,
-                          /* MEDIA_ERROR_UNSUPPORTED= */ -1010,
-                          mID);
+            Log.d(TAG, "" + e.getMessage());
         } catch (final IllegalArgumentException e) {
             Log.d(TAG, "" + e.getMessage());
         } catch (final SecurityException e) {
@@ -364,19 +401,36 @@ public class QtAndroidMediaPlayer extends MediaPlayer
             Log.d(TAG, "" + e.getMessage());
         } catch (final NullPointerException e) {
             Log.d(TAG, "" + e.getMessage());
+        } finally {
+            if (afd !=null) {
+                try { afd.close(); } catch (final IOException ioe) { /* Ignore... */ }
+            }
+            if ((mState & State.Initialized) == 0) {
+                setState(State.Error);
+                onErrorNative(MediaPlayer.MEDIA_ERROR_UNKNOWN,
+                              -1004 /*MEDIA_ERROR_IO*/,
+                              mID);
+                return;
+            }
         }
     }
 
-   @Override
+
    public int getCurrentPosition()
    {
        int currentPosition = 0;
-
-       if (!mInitialized)
+       if ((mState & (State.Idle
+                      | State.Initialized
+                      | State.Prepared
+                      | State.Started
+                      | State.Paused
+                      | State.Stopped
+                      | State.PlaybackCompleted)) == 0) {
            return currentPosition;
+       }
 
        try {
-           currentPosition = super.getCurrentPosition();
+           currentPosition = mMediaPlayer.getCurrentPosition();
        } catch (final IllegalStateException e) {
            Log.d(TAG, "" + e.getMessage());
        }
@@ -384,16 +438,20 @@ public class QtAndroidMediaPlayer extends MediaPlayer
        return currentPosition;
    }
 
-   @Override
+
    public int getDuration()
    {
        int duration = 0;
-
-       if (!mInitialized)
+       if ((mState & (State.Prepared
+                      | State.Started
+                      | State.Paused
+                      | State.Stopped
+                      | State.PlaybackCompleted)) == 0) {
            return duration;
+       }
 
        try {
-           duration = super.getDuration();
+           duration = mMediaPlayer.getDuration();
        } catch (final IllegalStateException e) {
            Log.d(TAG, "" + e.getMessage());
        }
@@ -414,6 +472,16 @@ public class QtAndroidMediaPlayer extends MediaPlayer
 
    public void setVolume(int volume)
    {
+       if ((mState & (State.Idle
+                      | State.Initialized
+                      | State.Stopped
+                      | State.Prepared
+                      | State.Started
+                      | State.Paused
+                      | State.PlaybackCompleted)) == 0) {
+           return;
+       }
+
        if (volume < 0)
            volume = 0;
 
@@ -423,7 +491,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
        float newVolume = adjustVolume(volume);
 
        try {
-           super.setVolume(newVolume, newVolume);
+           mMediaPlayer.setVolume(newVolume, newVolume);
            if (!mMuted)
                mVolume = volume;
        } catch (final IllegalStateException e) {
@@ -431,6 +499,22 @@ public class QtAndroidMediaPlayer extends MediaPlayer
        }
    }
 
+   public SurfaceHolder display()
+   {
+       return mSurfaceHolder;
+   }
+
+   public void setDisplay(SurfaceHolder sh)
+   {
+       mSurfaceHolder = sh;
+
+       if ((mState & State.Uninitialized) != 0)
+           return;
+
+       mMediaPlayer.setDisplay(mSurfaceHolder);
+   }
+
+
    public int getVolume()
    {
        return mVolume;
@@ -447,11 +531,32 @@ public class QtAndroidMediaPlayer extends MediaPlayer
         return mMuted;
     }
 
-    @Override
+
     public void reset()
     {
-        mInitialized = false;
-        super.reset();
+        if ((mState & (State.Idle
+                       | State.Initialized
+                       | State.Prepared
+                       | State.Started
+                       | State.Paused
+                       | State.Stopped
+                       | State.PlaybackCompleted
+                       | State.Error)) == 0) {
+            return;
+        }
+
+        mMediaPlayer.reset();
+        setState(State.Idle);
     }
 
+    public void release()
+    {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.reset();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+
+        setState(State.Uninitialized);
+    }
 }
index 6e4a32e..8bf6be6 100644 (file)
@@ -60,6 +60,7 @@ public:
 
     virtual void setVideoSize(const QSize &) { }
     virtual void stop() { }
+    virtual void reset() { }
 
     // signals:
     // void readyChanged(bool);
index 55f71d7..b737e8a 100644 (file)
@@ -122,20 +122,8 @@ QAndroidVideoRendererControl::QAndroidVideoRendererControl(QObject *parent)
 
 QAndroidVideoRendererControl::~QAndroidVideoRendererControl()
 {
-    if (m_surfaceTexture) {
-        m_surfaceTexture->callMethod<void>("release");
-        delete m_surfaceTexture;
-        m_surfaceTexture = 0;
-    }
-    if (m_androidSurface) {
-        m_androidSurface->callMethod<void>("release");
-        delete m_androidSurface;
-        m_androidSurface = 0;
-    }
-    if (m_surfaceHolder) {
-        delete m_surfaceHolder;
-        m_surfaceHolder = 0;
-    }
+    clearSurfaceTexture();
+
     if (m_glDeleter)
         m_glDeleter->deleteLater();
 }
@@ -202,6 +190,24 @@ bool QAndroidVideoRendererControl::initSurfaceTexture()
     return m_surfaceTexture != 0;
 }
 
+void QAndroidVideoRendererControl::clearSurfaceTexture()
+{
+    if (m_surfaceTexture) {
+        m_surfaceTexture->callMethod<void>("release");
+        delete m_surfaceTexture;
+        m_surfaceTexture = 0;
+    }
+    if (m_androidSurface) {
+        m_androidSurface->callMethod<void>("release");
+        delete m_androidSurface;
+        m_androidSurface = 0;
+    }
+    if (m_surfaceHolder) {
+        delete m_surfaceHolder;
+        m_surfaceHolder = 0;
+    }
+}
+
 jobject QAndroidVideoRendererControl::surfaceHolder()
 {
     if (!initSurfaceTexture())
@@ -245,6 +251,11 @@ void QAndroidVideoRendererControl::stop()
     m_nativeSize = QSize();
 }
 
+void QAndroidVideoRendererControl::reset()
+{
+    clearSurfaceTexture();
+}
+
 void QAndroidVideoRendererControl::onFrameAvailable()
 {
     if (!m_nativeSize.isValid() || !m_surface)
index 6ce1e2d..56407d5 100644 (file)
@@ -92,6 +92,7 @@ public:
     bool isReady() Q_DECL_OVERRIDE;
     void setVideoSize(const QSize &size) Q_DECL_OVERRIDE;
     void stop() Q_DECL_OVERRIDE;
+    void reset() Q_DECL_OVERRIDE;
 
     void customEvent(QEvent *) Q_DECL_OVERRIDE;
 
@@ -107,6 +108,7 @@ private:
     void createGLResources();
 
     QMutex m_mutex;
+    void clearSurfaceTexture();
 
     QAbstractVideoSurface *m_surface;
     QSize m_nativeSize;
index 7b0c582..4b836dd 100644 (file)
@@ -57,25 +57,31 @@ QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent)
       mAudioAvailable(false),
       mVideoAvailable(false),
       mBuffering(false),
-      mMediaPlayerReady(false),
+      mState(JMediaPlayer::Uninitialized),
+      mPendingState(-1),
       mPendingPosition(-1),
-      mPendingSetMedia(false)
-{
-    connect(mMediaPlayer, SIGNAL(bufferingUpdate(qint32)),
-            this, SLOT(onBufferChanged(qint32)));
-    connect(mMediaPlayer, SIGNAL(info(qint32,qint32)),
-            this, SLOT(onInfo(qint32,qint32)));
-    connect(mMediaPlayer, SIGNAL(error(qint32,qint32)),
-            this, SLOT(onError(qint32,qint32)));
-    connect(mMediaPlayer, SIGNAL(mediaPlayerInfo(qint32,qint32)),
-            this, SLOT(onMediaPlayerInfo(qint32,qint32)));
-    connect(mMediaPlayer, SIGNAL(videoSizeChanged(qint32,qint32)),
-            this, SLOT(onVideoSizeChanged(qint32,qint32)));
+      mPendingSetMedia(false),
+      mPendingVolume(-1),
+      mPendingMute(-1)
+{
+    connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)),
+            this,SLOT(onBufferingChanged(qint32)));
+    connect(mMediaPlayer,SIGNAL(info(qint32,qint32)),
+            this,SLOT(onInfo(qint32,qint32)));
+    connect(mMediaPlayer,SIGNAL(error(qint32,qint32)),
+            this,SLOT(onError(qint32,qint32)));
+    connect(mMediaPlayer,SIGNAL(stateChanged(qint32)),
+            this,SLOT(onStateChanged(qint32)));
+    connect(mMediaPlayer,SIGNAL(videoSizeChanged(qint32,qint32)),
+            this,SLOT(onVideoSizeChanged(qint32,qint32)));
+    connect(mMediaPlayer,SIGNAL(progressChanged(qint64)),
+            this,SIGNAL(positionChanged(qint64)));
+    connect(mMediaPlayer,SIGNAL(durationChanged(qint64)),
+            this,SIGNAL(durationChanged(qint64)));
 }
 
 QAndroidMediaPlayerControl::~QAndroidMediaPlayerControl()
 {
-    mMediaPlayer->stop();
     mMediaPlayer->release();
     delete mMediaPlayer;
 }
@@ -92,18 +98,33 @@ QMediaPlayer::MediaStatus QAndroidMediaPlayerControl::mediaStatus() const
 
 qint64 QAndroidMediaPlayerControl::duration() const
 {
-    return (mCurrentMediaStatus == QMediaPlayer::InvalidMedia
-            || mCurrentMediaStatus == QMediaPlayer::NoMedia
-            || !mMediaPlayerReady) ? 0
-                                   : mMediaPlayer->getDuration();
+    if ((mState & (JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::Stopped
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+        return 0;
+    }
+
+    return mMediaPlayer->getDuration();
 }
 
 qint64 QAndroidMediaPlayerControl::position() const
 {
-    if (!mMediaPlayerReady)
-        return mPendingPosition < 0 ? 0 : mPendingPosition;
+    if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia)
+        return duration();
+
+    if ((mState & (JMediaPlayer::Idle
+                   | JMediaPlayer::Initialized
+                   | JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::Stopped
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+         return (mPendingPosition == -1) ? 0 : mPendingPosition;
+    }
 
-    return mMediaPlayer->getCurrentPosition();
+    return (mCurrentState == QMediaPlayer::StoppedState) ? 0 : mMediaPlayer->getCurrentPosition();
 }
 
 void QAndroidMediaPlayerControl::setPosition(qint64 position)
@@ -113,35 +134,88 @@ void QAndroidMediaPlayerControl::setPosition(qint64 position)
 
     const int seekPosition = (position > INT_MAX) ? INT_MAX : position;
 
-    if (!mMediaPlayerReady) {
-        mPendingPosition = seekPosition;
-        Q_EMIT positionChanged(seekPosition);
+    if ((mState & (JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+        if (mPendingPosition != seekPosition) {
+            mPendingPosition = seekPosition;
+            Q_EMIT positionChanged(seekPosition);
+        }
         return;
     }
 
+    if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia)
+        setMediaStatus(QMediaPlayer::LoadedMedia);
+
     mMediaPlayer->seekTo(seekPosition);
-    mPendingPosition = -1;
+
+    if (mPendingPosition != -1) {
+        mPendingPosition = -1;
+    }
+
+    Q_EMIT positionChanged(seekPosition);
 }
 
 int QAndroidMediaPlayerControl::volume() const
 {
-    return mMediaPlayer->volume();
+    return (mPendingVolume == -1) ? mMediaPlayer->volume() : mPendingVolume;
 }
 
 void QAndroidMediaPlayerControl::setVolume(int volume)
 {
+    if ((mState & (JMediaPlayer::Idle
+                   | JMediaPlayer::Initialized
+                   | JMediaPlayer::Stopped
+                   | JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+        if (mPendingVolume != volume) {
+            mPendingVolume = volume;
+            Q_EMIT volumeChanged(volume);
+        }
+        return;
+    }
+
     mMediaPlayer->setVolume(volume);
+
+    if (mPendingVolume != -1) {
+        mPendingVolume = -1;
+        return;
+    }
+
     Q_EMIT volumeChanged(volume);
 }
 
 bool QAndroidMediaPlayerControl::isMuted() const
 {
-    return mMediaPlayer->isMuted();
+    return (mPendingMute == -1) ? mMediaPlayer->isMuted() : (mPendingMute == 1);
 }
 
 void QAndroidMediaPlayerControl::setMuted(bool muted)
 {
+    if ((mState & (JMediaPlayer::Idle
+                   | JMediaPlayer::Initialized
+                   | JMediaPlayer::Stopped
+                   | JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+        if (mPendingMute != muted) {
+            mPendingMute = muted;
+            Q_EMIT mutedChanged(muted);
+        }
+        return;
+    }
+
     mMediaPlayer->setMuted(muted);
+
+    if (mPendingMute != -1) {
+        mPendingMute = -1;
+        return;
+    }
+
     Q_EMIT mutedChanged(muted);
 }
 
@@ -208,10 +282,21 @@ const QIODevice *QAndroidMediaPlayerControl::mediaStream() const
 void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
                                           QIODevice *stream)
 {
-    mMediaContent = mediaContent;
-    mMediaStream = stream;
+    const bool reloading = (mMediaContent == mediaContent);
+
+    if (!reloading) {
+        mMediaContent = mediaContent;
+        mMediaStream = stream;
+    }
 
-    if (mVideoOutput && !mMediaPlayer->display()) {
+    mMediaPlayer->release();
+
+    if (mediaContent.isNull()) {
+        setMediaStatus(QMediaPlayer::NoMedia);
+        return;
+    }
+
+    if (mVideoOutput && !mVideoOutput->isReady()) {
         // 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;
@@ -229,68 +314,88 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
         mediaPath = url.toString();
     }
 
-    if (!mediaPath.isEmpty())
-        mMediaPlayer->setDataSource(mediaPath);
-    else
-        setMediaStatus(QMediaPlayer::NoMedia);
+    if (mVideoSize.isValid() && mVideoOutput)
+        mVideoOutput->setVideoSize(mVideoSize);
+
+    if (!mMediaPlayer->display() && mVideoOutput)
+        mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
+    mMediaPlayer->setDataSource(mediaPath);
+    mMediaPlayer->prepareAsync();
 
-    Q_EMIT mediaChanged(mMediaContent);
+    if (!reloading)
+        Q_EMIT mediaChanged(mMediaContent);
 
     resetBufferingProgress();
-
-    // reset some properties
-    setAudioAvailable(false);
-    setVideoAvailable(false);
-    setSeekable(true);
 }
 
 void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput)
 {
-    if (mVideoOutput)
+    if (mVideoOutput) {
+        mMediaPlayer->setDisplay(0);
         mVideoOutput->stop();
+        mVideoOutput->reset();
+    }
 
     mVideoOutput = qobject_cast<QAndroidVideoOutput *>(videoOutput);
 
-    if (mVideoOutput && !mMediaPlayer->display()) {
-        if (mVideoOutput->isReady())
-            mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
-        else
-            connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool)));
-    }
+    if (!mVideoOutput)
+        return;
+
+    if (mVideoOutput->isReady())
+        mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
+
+    connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool)));
 }
 
 void QAndroidMediaPlayerControl::play()
 {
-    if (!mMediaPlayerReady) {
+    // We need to prepare the mediaplayer again.
+    if ((mState & JMediaPlayer::Stopped) && !mMediaContent.isNull()) {
+        setMedia(mMediaContent, mMediaStream);
+    }
+
+    setState(QMediaPlayer::PlayingState);
+
+    if ((mState & (JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
         mPendingState = QMediaPlayer::PlayingState;
-        if (mCurrentState == QMediaPlayer::StoppedState
-            && !mMediaContent.isNull()
-            && mCurrentMediaStatus != QMediaPlayer::LoadingMedia
-            && !mPendingSetMedia) {
-            setMedia(mMediaContent, 0);
-        }
         return;
     }
 
     mMediaPlayer->play();
-    setState(QMediaPlayer::PlayingState);
 }
 
 void QAndroidMediaPlayerControl::pause()
 {
-    if (!mMediaPlayerReady) {
+    setState(QMediaPlayer::PausedState);
+
+    if ((mState & (JMediaPlayer::Started
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
         mPendingState = QMediaPlayer::PausedState;
         return;
     }
 
     mMediaPlayer->pause();
-    setState(QMediaPlayer::PausedState);
 }
 
 void QAndroidMediaPlayerControl::stop()
 {
-    mMediaPlayer->stop();
     setState(QMediaPlayer::StoppedState);
+
+    if ((mState & (JMediaPlayer::Prepared
+                   | JMediaPlayer::Started
+                   | JMediaPlayer::Stopped
+                   | JMediaPlayer::Paused
+                   | JMediaPlayer::PlaybackCompleted)) == 0) {
+        if ((mState & (JMediaPlayer::Idle | JMediaPlayer::Uninitialized | JMediaPlayer::Error)) == 0)
+            mPendingState = QMediaPlayer::StoppedState;
+        return;
+    }
+
+    mMediaPlayer->stop();
 }
 
 void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra)
@@ -310,8 +415,8 @@ void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra)
         setMediaStatus(QMediaPlayer::StalledMedia);
         break;
     case JMediaPlayer::MEDIA_INFO_BUFFERING_END:
-        setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia);
-        flushPendingStates();
+        if (mCurrentState != QMediaPlayer::StoppedState)
+            flushPendingStates();
         break;
     case JMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING:
         break;
@@ -324,41 +429,6 @@ void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra)
     }
 }
 
-void QAndroidMediaPlayerControl::onMediaPlayerInfo(qint32 what, qint32 extra)
-{
-    switch (what) {
-    case JMediaPlayer::MEDIA_PLAYER_INVALID_STATE:
-        setError(what, QStringLiteral("Error: Invalid state"));
-        break;
-    case JMediaPlayer::MEDIA_PLAYER_PREPARING:
-        setMediaStatus(QMediaPlayer::LoadingMedia);
-        setState(QMediaPlayer::StoppedState);
-        break;
-    case JMediaPlayer::MEDIA_PLAYER_READY:
-        setMediaStatus(QMediaPlayer::LoadedMedia);
-        if (mBuffering) {
-            setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
-                                                 : QMediaPlayer::BufferingMedia);
-        } else {
-            onBufferChanged(100);
-        }
-        setAudioAvailable(true);
-        mMediaPlayerReady = true;
-        flushPendingStates();
-        break;
-    case JMediaPlayer::MEDIA_PLAYER_DURATION:
-        Q_EMIT durationChanged(extra);
-        break;
-    case JMediaPlayer::MEDIA_PLAYER_PROGRESS:
-        Q_EMIT positionChanged(extra);
-        break;
-    case JMediaPlayer::MEDIA_PLAYER_FINISHED:
-        stop();
-        setMediaStatus(QMediaPlayer::EndOfMedia);
-        break;
-    }
-}
-
 void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
 {
     QString errorString;
@@ -372,6 +442,10 @@ void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
         errorString = QLatin1String("Error: Server died");
         error = QMediaPlayer::ServiceMissingError;
         break;
+    case JMediaPlayer::MEDIA_ERROR_INVALID_STATE:
+        errorString = QLatin1String("Error: Invalid state");
+        error = QMediaPlayer::ServiceMissingError;
+        break;
     }
 
     switch (extra) {
@@ -398,12 +472,16 @@ void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
         error = QMediaPlayer::FormatError;
         setMediaStatus(QMediaPlayer::InvalidMedia);
         break;
+    case JMediaPlayer::MEDIA_ERROR_BAD_THINGS_ARE_GOING_TO_HAPPEN:
+        errorString += QLatin1String(" (Unknown error/Insufficient resources)");
+        error = QMediaPlayer::ServiceMissingError;
+        break;
     }
 
-    setError(error, errorString);
+    Q_EMIT QMediaPlayerControl::error(error, errorString);
 }
 
-void QAndroidMediaPlayerControl::onBufferChanged(qint32 percent)
+void QAndroidMediaPlayerControl::onBufferingChanged(qint32 percent)
 {
     mBuffering = percent != 100;
     mBufferPercent = percent;
@@ -411,8 +489,8 @@ void QAndroidMediaPlayerControl::onBufferChanged(qint32 percent)
 
     updateAvailablePlaybackRanges();
 
-    if (mBufferPercent == 100)
-        setMediaStatus(QMediaPlayer::BufferedMedia);
+    if (mCurrentState != QMediaPlayer::StoppedState)
+        setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia);
 }
 
 void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height)
@@ -429,27 +507,98 @@ void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height)
         mVideoOutput->setVideoSize(mVideoSize);
 }
 
-void QAndroidMediaPlayerControl::onVideoOutputReady(bool ready)
+void QAndroidMediaPlayerControl::onStateChanged(qint32 state)
 {
-    if (!mMediaPlayer->display() && mVideoOutput && ready) {
-        mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
+    // If reloading, don't report state changes unless the new state is Prepared or Error.
+    if ((mState & JMediaPlayer::Stopped) && !(state & (JMediaPlayer::Prepared | JMediaPlayer::Error)))
+        return;
+
+    mState = state;
+    switch (mState) {
+    case JMediaPlayer::Idle:
+        break;
+    case JMediaPlayer::Initialized:
+        break;
+    case JMediaPlayer::Preparing:
+        setMediaStatus(QMediaPlayer::LoadingMedia);
+        break;
+    case JMediaPlayer::Prepared:
+        setMediaStatus(QMediaPlayer::LoadedMedia);
+        if (mBuffering) {
+            setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
+                                                 : QMediaPlayer::BufferingMedia);
+        } else {
+            onBufferingChanged(100);
+        }
+        setAudioAvailable(true);
         flushPendingStates();
+        break;
+    case JMediaPlayer::Started:
+        setState(QMediaPlayer::PlayingState);
+        if (mBuffering) {
+            setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
+                                                 : QMediaPlayer::BufferingMedia);
+        } else {
+            setMediaStatus(QMediaPlayer::BufferedMedia);
+        }
+        break;
+    case JMediaPlayer::Paused:
+        setState(QMediaPlayer::PausedState);
+        break;
+    case JMediaPlayer::Error:
+        setState(QMediaPlayer::StoppedState);
+        setMediaStatus(QMediaPlayer::UnknownMediaStatus);
+        mMediaPlayer->release();
+        break;
+    case JMediaPlayer::Stopped:
+        setState(QMediaPlayer::StoppedState);
+        setMediaStatus(QMediaPlayer::LoadedMedia);
+        setPosition(0);
+        break;
+    case JMediaPlayer::PlaybackCompleted:
+        setState(QMediaPlayer::StoppedState);
+        setPosition(0);
+        setMediaStatus(QMediaPlayer::EndOfMedia);
+        break;
+    case JMediaPlayer::Uninitialized:
+        // reset some properties
+        resetBufferingProgress();
+        mPendingPosition = -1;
+        mPendingSetMedia = false;
+        mPendingState = -1;
+
+        setAudioAvailable(false);
+        setVideoAvailable(false);
+        setSeekable(true);
+        break;
+    default:
+        break;
+    }
+
+    if ((mState & (JMediaPlayer::Stopped | JMediaPlayer::Uninitialized)) != 0) {
+        mMediaPlayer->setDisplay(0);
+        if (mVideoOutput) {
+            mVideoOutput->stop();
+            mVideoOutput->reset();
+        }
     }
 }
 
+void QAndroidMediaPlayerControl::onVideoOutputReady(bool ready)
+{
+    if (!mMediaPlayer->display() && mVideoOutput && ready)
+        mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
+
+    flushPendingStates();
+}
+
 void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state)
 {
     if (mCurrentState == state)
         return;
 
-    if (state == QMediaPlayer::StoppedState) {
-        if (mVideoOutput)
-            mVideoOutput->stop();
-        resetBufferingProgress();
-        mMediaPlayerReady = false;
-        mPendingPosition = -1;
-        Q_EMIT positionChanged(0);
-    }
+    if (mCurrentState == QMediaPlayer::StoppedState && state == QMediaPlayer::PausedState)
+        return;
 
     mCurrentState = state;
     Q_EMIT stateChanged(mCurrentState);
@@ -463,17 +612,13 @@ void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status
     if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia)
         Q_EMIT durationChanged(0);
 
+    if (status == QMediaPlayer::EndOfMedia)
+        Q_EMIT durationChanged(duration());
+
     mCurrentMediaStatus = status;
     Q_EMIT mediaStatusChanged(mCurrentMediaStatus);
 }
 
-void QAndroidMediaPlayerControl::setError(int error,
-                                          const QString &errorString)
-{
-    setState(QMediaPlayer::StoppedState);
-    Q_EMIT QMediaPlayerControl::error(error, errorString);
-}
-
 void QAndroidMediaPlayerControl::setSeekable(bool seekable)
 {
     if (mSeekable == seekable)
@@ -515,15 +660,23 @@ void QAndroidMediaPlayerControl::resetBufferingProgress()
 void QAndroidMediaPlayerControl::flushPendingStates()
 {
     if (mPendingSetMedia) {
-        setMedia(mMediaContent, 0);
         mPendingSetMedia = false;
+        setMedia(mMediaContent, 0);
         return;
     }
 
-    switch (mPendingState) {
+    const int newState = mPendingState;
+    mPendingState = -1;
+
+    if (mPendingPosition != -1)
+        setPosition(mPendingPosition);
+    if (mPendingVolume != -1)
+        setVolume(mPendingVolume);
+    if (mPendingMute != -1)
+        setMuted((mPendingMute == 1));
+
+    switch (newState) {
     case QMediaPlayer::PlayingState:
-        if (mPendingPosition > -1)
-            setPosition(mPendingPosition);
         play();
         break;
     case QMediaPlayer::PausedState:
@@ -532,6 +685,8 @@ void QAndroidMediaPlayerControl::flushPendingStates()
     case QMediaPlayer::StoppedState:
         stop();
         break;
+    default:
+        break;
     }
 }
 
index fadac3c..1be3b44 100644 (file)
@@ -93,9 +93,9 @@ private Q_SLOTS:
     void onVideoOutputReady(bool ready);
     void onError(qint32 what, qint32 extra);
     void onInfo(qint32 what, qint32 extra);
-    void onMediaPlayerInfo(qint32 what, qint32 extra);
-    void onBufferChanged(qint32 percent);
+    void onBufferingChanged(qint32 percent);
     void onVideoSizeChanged(qint32 width, qint32 height);
+    void onStateChanged(qint32 state);
 
 private:
     JMediaPlayer *mMediaPlayer;
@@ -111,15 +111,16 @@ private:
     QSize mVideoSize;
     bool mBuffering;
     QMediaTimeRange mAvailablePlaybackRange;
-    bool mMediaPlayerReady;
-    QMediaPlayer::State mPendingState;
+    int mState;
+    int mPendingState;
     qint64 mPendingPosition;
     bool mPendingSetMedia;
+    int mPendingVolume;
+    int mPendingMute;
     QScopedPointer<QTemporaryFile> mTempFile;
 
     void setState(QMediaPlayer::State state);
     void setMediaStatus(QMediaPlayer::MediaStatus status);
-    void setError(int error, const QString &errorString);
     void setSeekable(bool seekable);
     void setAudioAvailable(bool available);
     void setVideoAvailable(bool available);
index 3d7f7f9..de86cd0 100644 (file)
 #include <QtCore/private/qjnihelpers_p.h>
 #include <QMap>
 
-namespace {
-
-jclass mediaPlayerClass = 0;
-
-QMap<jlong, JMediaPlayer *> mplayers;
-
-}
+static jclass mediaPlayerClass = Q_NULLPTR;
+typedef QMap<jlong, JMediaPlayer *> MediaPlayerMap;
+Q_GLOBAL_STATIC(MediaPlayerMap, mediaPlayers)
 
 QT_BEGIN_NAMESPACE
 
-bool JMediaPlayer::mActivitySet = false;
-
 JMediaPlayer::JMediaPlayer()
     : QObject()
-    , QJNIObjectPrivate(mediaPlayerClass, "(J)V", reinterpret_cast<jlong>(this))
-    , mId(reinterpret_cast<jlong>(this))
-    , mDisplay(0)
 {
-    mplayers.insert(mId, this);
-
-    if (!mActivitySet) {
-        QJNIObjectPrivate::callStaticMethod<void>(mediaPlayerClass,
-                                           "setActivity",
-                                           "(Landroid/app/Activity;)V",
-                                           QtAndroidPrivate::activity());
-        mActivitySet = true;
-    }
+
+    const jlong id = reinterpret_cast<jlong>(this);
+    mMediaPlayer = QJNIObjectPrivate(mediaPlayerClass,
+                                      "(Landroid/app/Activity;J)V",
+                                      QtAndroidPrivate::activity(),
+                                      id);
+    (*mediaPlayers)[id] = this;
 }
 
 JMediaPlayer::~JMediaPlayer()
 {
-    mplayers.remove(mId);
+    mediaPlayers->remove(reinterpret_cast<jlong>(this));
 }
 
 void JMediaPlayer::release()
 {
-    callMethod<void>("release");
+    mMediaPlayer.callMethod<void>("release");
 }
 
-void JMediaPlayer::onError(qint32 what, qint32 extra)
+void JMediaPlayer::reset()
 {
-    Q_EMIT error(what, extra);
-}
-
-void JMediaPlayer::onBufferingUpdate(qint32 percent)
-{
-    Q_EMIT bufferingUpdate(percent);
-}
-
-void JMediaPlayer::onInfo(qint32 what, qint32 extra)
-{
-    Q_EMIT info(what, extra);
-}
-
-void JMediaPlayer::onMediaPlayerInfo(qint32 what, qint32 extra)
-{
-    Q_EMIT mediaPlayerInfo(what, extra);
-}
-
-void JMediaPlayer::onVideoSizeChanged(qint32 width, qint32 height)
-{
-    Q_EMIT videoSizeChanged(width, height);
+    mMediaPlayer.callMethod<void>("reset");
 }
 
 int JMediaPlayer::getCurrentPosition()
 {
-    return callMethod<jint>("getCurrentPosition");
+    return mMediaPlayer.callMethod<jint>("getCurrentPosition");
 }
 
 int JMediaPlayer::getDuration()
 {
-    return callMethod<jint>("getDuration");
+    return mMediaPlayer.callMethod<jint>("getDuration");
 }
 
 bool JMediaPlayer::isPlaying()
 {
-    return callMethod<jboolean>("isPlaying");
+    return mMediaPlayer.callMethod<jboolean>("isPlaying");
 }
 
 int JMediaPlayer::volume()
 {
-    return callMethod<jint>("getVolume");
+    return mMediaPlayer.callMethod<jint>("getVolume");
 }
 
 bool JMediaPlayer::isMuted()
 {
-    return callMethod<jboolean>("isMuted");
+    return mMediaPlayer.callMethod<jboolean>("isMuted");
+}
+
+jobject JMediaPlayer::display()
+{
+    return mMediaPlayer.callObjectMethod("display", "()Landroid/view/SurfaceHolder;").object();
 }
 
 void JMediaPlayer::play()
 {
-    callMethod<void>("start");
+    mMediaPlayer.callMethod<void>("start");
 }
 
 void JMediaPlayer::pause()
 {
-    callMethod<void>("pause");
+    mMediaPlayer.callMethod<void>("pause");
 }
 
 void JMediaPlayer::stop()
 {
-    callMethod<void>("stop");
+    mMediaPlayer.callMethod<void>("stop");
 }
 
 void JMediaPlayer::seekTo(qint32 msec)
 {
-    callMethod<void>("seekTo", "(I)V", jint(msec));
+    mMediaPlayer.callMethod<void>("seekTo", "(I)V", jint(msec));
 }
 
 void JMediaPlayer::setMuted(bool mute)
 {
-    callMethod<void>("mute", "(Z)V", jboolean(mute));
+    mMediaPlayer.callMethod<void>("mute", "(Z)V", jboolean(mute));
 }
 
 void JMediaPlayer::setDataSource(const QString &path)
 {
     QJNIObjectPrivate string = QJNIObjectPrivate::fromString(path);
-    callMethod<void>("setMediaPath", "(Ljava/lang/String;)V", string.object());
+    mMediaPlayer.callMethod<void>("setDataSource", "(Ljava/lang/String;)V", string.object());
+}
+
+void JMediaPlayer::prepareAsync()
+{
+    mMediaPlayer.callMethod<void>("prepareAsync");
 }
 
 void JMediaPlayer::setVolume(int volume)
 {
-    callMethod<void>("setVolume", "(I)V", jint(volume));
+    mMediaPlayer.callMethod<void>("setVolume", "(I)V", jint(volume));
 }
 
 void JMediaPlayer::setDisplay(jobject surfaceHolder)
 {
-    mDisplay = surfaceHolder;
-    callMethod<void>("setDisplay", "(Landroid/view/SurfaceHolder;)V", mDisplay);
+    mMediaPlayer.callMethod<void>("setDisplay", "(Landroid/view/SurfaceHolder;)V", surfaceHolder);
 }
 
 QT_END_NAMESPACE
@@ -183,44 +161,66 @@ static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlon
 {
     Q_UNUSED(env);
     Q_UNUSED(thiz);
-    JMediaPlayer *const mp = mplayers[id];
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
     if (!mp)
         return;
 
-    mp->onError(what, extra);
+    Q_EMIT mp->error(what, extra);
 }
 
 static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id)
 {
     Q_UNUSED(env);
     Q_UNUSED(thiz);
-    JMediaPlayer *const mp = mplayers[id];
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
+    if (!mp)
+        return;
+
+    Q_EMIT mp->bufferingChanged(percent);
+}
+
+static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id)
+{
+    Q_UNUSED(env);
+    Q_UNUSED(thiz);
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
+    if (!mp)
+        return;
+
+    Q_EMIT mp->progressChanged(progress);
+}
+
+static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id)
+{
+    Q_UNUSED(env);
+    Q_UNUSED(thiz);
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
     if (!mp)
         return;
 
-    mp->onBufferingUpdate(percent);
+    Q_EMIT mp->durationChanged(duration);
 }
 
 static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
 {
     Q_UNUSED(env);
     Q_UNUSED(thiz);
-    JMediaPlayer *const mp = mplayers[id];
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
     if (!mp)
         return;
 
-    mp->onInfo(what, extra);
+    Q_EMIT mp->info(what, extra);
 }
 
-static void onMediaPlayerInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
+static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id)
 {
     Q_UNUSED(env);
     Q_UNUSED(thiz);
-    JMediaPlayer *const mp = mplayers[id];
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
     if (!mp)
         return;
 
-    mp->onMediaPlayerInfo(what, extra);
+    Q_EMIT mp->stateChanged(state);
 }
 
 static void onVideoSizeChangedNative(JNIEnv *env,
@@ -231,11 +231,11 @@ static void onVideoSizeChangedNative(JNIEnv *env,
 {
     Q_UNUSED(env);
     Q_UNUSED(thiz);
-    JMediaPlayer *const mp = mplayers[id];
+    JMediaPlayer *const mp = (*mediaPlayers)[id];
     if (!mp)
         return;
 
-    mp->onVideoSizeChanged(width, height);
+    Q_EMIT mp->videoSizeChanged(width, height);
 }
 
 QT_BEGIN_NAMESPACE
@@ -250,9 +250,11 @@ bool JMediaPlayer::initJNI(JNIEnv *env)
         JNINativeMethod methods[] = {
             {"onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative)},
             {"onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative)},
+            {"onProgressUpdateNative", "(IJ)V", reinterpret_cast<void *>(onProgressUpdateNative)},
+            {"onDurationChangedNative", "(IJ)V", reinterpret_cast<void *>(onDurationChangedNative)},
             {"onInfoNative", "(IIJ)V", reinterpret_cast<void *>(onInfoNative)},
-            {"onMediaPlayerInfoNative", "(IIJ)V", reinterpret_cast<void *>(onMediaPlayerInfoNative)},
-            {"onVideoSizeChangedNative", "(IIJ)V", reinterpret_cast<void *>(onVideoSizeChangedNative)}
+            {"onVideoSizeChangedNative", "(IIJ)V", reinterpret_cast<void *>(onVideoSizeChangedNative)},
+            {"onStateChangedNative", "(IJ)V", reinterpret_cast<void *>(onStateChangedNative)}
         };
 
         if (env->RegisterNatives(mediaPlayerClass,
index c737cfa..cd469e6 100644 (file)
@@ -47,7 +47,7 @@
 
 QT_BEGIN_NAMESPACE
 
-class JMediaPlayer : public QObject, public QJNIObjectPrivate
+class JMediaPlayer : public QObject
 {
     Q_OBJECT
 public:
@@ -59,12 +59,14 @@ public:
         // What
         MEDIA_ERROR_UNKNOWN = 1,
         MEDIA_ERROR_SERVER_DIED = 100,
+        MEDIA_ERROR_INVALID_STATE = -38, // Undocumented
         // Extra
         MEDIA_ERROR_IO = -1004,
         MEDIA_ERROR_MALFORMED = -1007,
         MEDIA_ERROR_UNSUPPORTED = -1010,
         MEDIA_ERROR_TIMED_OUT = -110,
-        MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200
+        MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200,
+        MEDIA_ERROR_BAD_THINGS_ARE_GOING_TO_HAPPEN = -2147483648 // Undocumented
     };
 
     enum MediaInfo
@@ -79,24 +81,29 @@ public:
         MEDIA_INFO_METADATA_UPDATE = 802
     };
 
-    enum MediaPlayerInfo
+    enum MediaPlayerState
     {
-        MEDIA_PLAYER_INVALID_STATE = 1,
-        MEDIA_PLAYER_PREPARING = 2,
-        MEDIA_PLAYER_READY = 3,
-        MEDIA_PLAYER_DURATION = 4,
-        MEDIA_PLAYER_PROGRESS = 5,
-        MEDIA_PLAYER_FINISHED = 6
+        Uninitialized = 0x1, /* End */
+        Idle = 0x2,
+        Preparing = 0x4,
+        Prepared = 0x8,
+        Initialized = 0x10,
+        Started = 0x20,
+        Stopped = 0x40,
+        Paused = 0x80,
+        PlaybackCompleted = 0x100,
+        Error = 0x200
     };
 
     void release();
+    void reset();
 
     int getCurrentPosition();
     int getDuration();
     bool isPlaying();
     int volume();
     bool isMuted();
-    jobject display() { return mDisplay; }
+    jobject display();
 
     void play();
     void pause();
@@ -104,30 +111,23 @@ public:
     void seekTo(qint32 msec);
     void setMuted(bool mute);
     void setDataSource(const QString &path);
+    void prepareAsync();
     void setVolume(int volume);
     void setDisplay(jobject surfaceHolder);
 
-    void onError(qint32 what, qint32 extra);
-    void onBufferingUpdate(qint32 percent);
-    void onInfo(qint32 what, qint32 extra);
-    void onMediaPlayerInfo(qint32 what, qint32 extra);
-    void onVideoSizeChanged(qint32 width, qint32 height);
-
     static bool initJNI(JNIEnv *env);
 
 Q_SIGNALS:
     void error(qint32 what, qint32 extra);
-    void bufferingUpdate(qint32 percent);
-    void completion();
+    void bufferingChanged(qint32 percent);
+    void durationChanged(qint64 duration);
+    void progressChanged(qint64 progress);
+    void stateChanged(qint32 state);
     void info(qint32 what, qint32 extra);
-    void mediaPlayerInfo(qint32 what, qint32 extra);
     void videoSizeChanged(qint32 width, qint32 height);
 
 private:
-    jlong mId;
-    jobject mDisplay;
-
-    static bool mActivitySet;
+    QJNIObjectPrivate mMediaPlayer;
 };
 
 QT_END_NAMESPACE