Android: Make the Mediaplayer more robust

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>
This commit is contained in:
Christian Strømme
2014-02-20 14:25:35 +01:00
committed by The Qt Project
parent ddd22fab2b
commit dcf11bba3c
8 changed files with 613 additions and 336 deletions

View File

@@ -53,59 +53,56 @@ import android.util.Log;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.view.SurfaceHolder;
public class QtAndroidMediaPlayer extends MediaPlayer public class QtAndroidMediaPlayer
{ {
// Native callback functions for MediaPlayer // Native callback functions for MediaPlayer
native public void onErrorNative(int what, int extra, long id); native public void onErrorNative(int what, int extra, long id);
native public void onBufferingUpdateNative(int percent, 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 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 onVideoSizeChangedNative(int width, int height, long id);
native public void onStateChangedNative(int state, long id);
private MediaPlayer mMediaPlayer = null;
private Uri mUri = null; private Uri mUri = null;
private final long mID; private final long mID;
private final Activity mActivity;
private boolean mMuted = false; private boolean mMuted = false;
private boolean mPreparing = false;
private boolean mInitialized = false;
private int mVolume = 100; private int mVolume = 100;
private static final String TAG = "Qt MediaPlayer"; private static final String TAG = "Qt MediaPlayer";
private static Context mApplicationContext = null; private SurfaceHolder mSurfaceHolder = null;
final int MEDIA_PLAYER_INVALID_STATE = 1; private class State {
final int MEDIA_PLAYER_PREPARING = 2; final static int Uninitialized = 0x1 /* End */;
final int MEDIA_PLAYER_READY = 3; final static int Idle = 0x2;
final int MEDIA_PLAYER_DURATION = 4; final static int Preparing = 0x4;
final int MEDIA_PLAYER_PROGRESS = 5; final static int Prepared = 0x8;
final int MEDIA_PLAYER_FINISHED = 6; final static int Initialized = 0x10;
final static int Started = 0x20;
// Activity set by Qt on load. final static int Stopped = 0x40;
static public void setActivity(final Activity activity) final static int Paused = 0x80;
{ final static int PlaybackCompleted = 0x100;
try { final static int Error = 0x200;
mApplicationContext = activity.getApplicationContext();
} catch(final Exception e) {
Log.d(TAG, "" + e.getMessage());
}
} }
private class ProgressWatcher implements Runnable private volatile int mState = State.Uninitialized;
private class ProgressWatcher
implements Runnable
{ {
@Override @Override
public void run() public void run()
{ {
final int duratation = getDuration();
int currentPosition = getCurrentPosition();
try { try {
while (duratation >= currentPosition && isPlaying()) { while ((mState & (State.Started)) != 0) {
onMediaPlayerInfoNative(MEDIA_PLAYER_PROGRESS, currentPosition, mID); onProgressUpdateNative(getCurrentPosition(), mID);
Thread.sleep(1000); Thread.sleep(1000);
currentPosition = getCurrentPosition();
} }
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
Log.d(TAG, "" + e.getMessage()); // Ignore
return;
} }
} }
} }
@@ -121,7 +118,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
final int what, final int what,
final int extra) final int extra)
{ {
reset(); setState(State.Error);
onErrorNative(what, extra, mID); onErrorNative(what, extra, mID);
return true; return true;
} }
@@ -158,7 +155,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
@Override @Override
public void onCompletion(final MediaPlayer mp) 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 @Override
public void onPrepared(final MediaPlayer mp) public void onPrepared(final MediaPlayer mp)
{ {
mPreparing = false; setState(State.Prepared);
onMediaPlayerInfoNative(MEDIA_PLAYER_READY, 0, mID); onDurationChangedNative(getDuration(), mID);
onMediaPlayerInfoNative(MEDIA_PLAYER_DURATION, getDuration(), mID);
} }
} }
@@ -207,7 +203,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
@Override @Override
public void onSeekComplete(final MediaPlayer mp) 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; mID = id;
setOnBufferingUpdateListener(new MediaPlayerBufferingListener()); mActivity = activity;
setOnCompletionListener(new MediaPlayerCompletionListener()); }
setOnInfoListener(new MediaPlayerInfoListener());
setOnSeekCompleteListener(new MediaPlayerSeekCompleteListener()); private void setState(int state)
setOnVideoSizeChangedListener(new MediaPlayerVideoSizeChangedListener()); {
setOnErrorListener(new MediaPlayerErrorListener()); if (mState == state)
return;
mState = state;
onStateChangedNative(mState, mID);
}
private void init()
{
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
setState(State.Idle);
}
} }
@Override
public void start() public void start()
{ {
if (!mInitialized) { if ((mState & (State.Prepared
onMediaPlayerInfoNative(MEDIA_PLAYER_INVALID_STATE, 0, mID); | State.Started
| State.Paused
| State.PlaybackCompleted)) == 0) {
return; return;
} }
if (mApplicationContext == null)
return;
if (mPreparing)
return;
if (isPlaying())
return;
try { try {
super.start(); mMediaPlayer.start();
setState(State.Started);
Thread progressThread = new Thread(new ProgressWatcher()); Thread progressThread = new Thread(new ProgressWatcher());
progressThread.start(); progressThread.start();
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
reset();
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
} }
@Override
public void pause() public void pause()
{ {
if (!isPlaying()) if ((mState & (State.Started | State.Paused | State.PlaybackCompleted)) == 0)
return; return;
try { try {
super.pause(); mMediaPlayer.pause();
setState(State.Paused);
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
reset();
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
} }
@Override
public void stop() public void stop()
{ {
if (!mInitialized) if ((mState & (State.Prepared
| State.Started
| State.Stopped
| State.Paused
| State.PlaybackCompleted)) == 0) {
return; return;
}
try { try {
super.stop(); mMediaPlayer.stop();
setState(State.Stopped);
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} finally {
reset();
} }
} }
@Override
public void seekTo(final int msec) public void seekTo(final int msec)
{ {
if (!mInitialized) if ((mState & (State.Prepared
| State.Started
| State.Paused
| State.PlaybackCompleted)) == 0) {
return; return;
}
try { try {
super.seekTo(msec); mMediaPlayer.seekTo(msec);
onMediaPlayerInfoNative(MEDIA_PLAYER_PROGRESS, msec, mID); onProgressUpdateNative(msec, mID);
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
} }
@Override
public boolean isPlaying() public boolean isPlaying()
{ {
boolean playing = false; boolean playing = false;
if ((mState & (State.Idle
if (!mInitialized) | State.Initialized
| State.Prepared
| State.Started
| State.Paused
| State.Stopped
| State.PlaybackCompleted)) == 0) {
return playing; return playing;
}
try { try {
playing = super.isPlaying(); playing = mMediaPlayer.isPlaying();
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
@@ -328,34 +343,56 @@ public class QtAndroidMediaPlayer extends MediaPlayer
return playing; return playing;
} }
public void setMediaPath(final String path) public void prepareAsync()
{ {
if (mInitialized) if ((mState & (State.Initialized | State.Stopped)) == 0)
reset(); return;
try { try {
mPreparing = true; mMediaPlayer.prepareAsync();
onMediaPlayerInfoNative(MEDIA_PLAYER_PREPARING, 0, mID); setState(State.Preparing);
} catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage());
}
}
public void setDataSource(final String path)
{
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 {
mUri = Uri.parse(path); 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 String asset = mUri.getPath().substring(1 /* Remove first '/' */);
final AssetManager am = mApplicationContext.getAssets(); final AssetManager am = mActivity.getAssets();
final AssetFileDescriptor afd = am.openFd(asset); afd = am.openFd(asset);
final long offset = afd.getStartOffset(); final long offset = afd.getStartOffset();
final long length = afd.getLength(); final long length = afd.getLength();
FileDescriptor fd = afd.getFileDescriptor(); FileDescriptor fd = afd.getFileDescriptor();
setDataSource(fd, offset, length); mMediaPlayer.setDataSource(fd, offset, length);
} else { } else {
setDataSource(mApplicationContext, mUri); mMediaPlayer.setDataSource(mActivity, mUri);
} }
mInitialized = true; setState(State.Initialized);
setOnPreparedListener(new MediaPlayerPreparedListener());
prepareAsync();
} catch (final IOException e) { } catch (final IOException e) {
mPreparing = false; Log.d(TAG, "" + e.getMessage());
onErrorNative(MEDIA_ERROR_UNKNOWN,
/* MEDIA_ERROR_UNSUPPORTED= */ -1010,
mID);
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} catch (final SecurityException e) { } catch (final SecurityException e) {
@@ -364,19 +401,36 @@ public class QtAndroidMediaPlayer extends MediaPlayer
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} catch (final NullPointerException e) { } catch (final NullPointerException e) {
Log.d(TAG, "" + e.getMessage()); 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() public int getCurrentPosition()
{ {
int currentPosition = 0; int currentPosition = 0;
if ((mState & (State.Idle
if (!mInitialized) | State.Initialized
| State.Prepared
| State.Started
| State.Paused
| State.Stopped
| State.PlaybackCompleted)) == 0) {
return currentPosition; return currentPosition;
}
try { try {
currentPosition = super.getCurrentPosition(); currentPosition = mMediaPlayer.getCurrentPosition();
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
@@ -384,16 +438,20 @@ public class QtAndroidMediaPlayer extends MediaPlayer
return currentPosition; return currentPosition;
} }
@Override
public int getDuration() public int getDuration()
{ {
int duration = 0; int duration = 0;
if ((mState & (State.Prepared
if (!mInitialized) | State.Started
| State.Paused
| State.Stopped
| State.PlaybackCompleted)) == 0) {
return duration; return duration;
}
try { try {
duration = super.getDuration(); duration = mMediaPlayer.getDuration();
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Log.d(TAG, "" + e.getMessage()); Log.d(TAG, "" + e.getMessage());
} }
@@ -414,6 +472,16 @@ public class QtAndroidMediaPlayer extends MediaPlayer
public void setVolume(int volume) 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) if (volume < 0)
volume = 0; volume = 0;
@@ -423,7 +491,7 @@ public class QtAndroidMediaPlayer extends MediaPlayer
float newVolume = adjustVolume(volume); float newVolume = adjustVolume(volume);
try { try {
super.setVolume(newVolume, newVolume); mMediaPlayer.setVolume(newVolume, newVolume);
if (!mMuted) if (!mMuted)
mVolume = volume; mVolume = volume;
} catch (final IllegalStateException e) { } 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() public int getVolume()
{ {
return mVolume; return mVolume;
@@ -447,11 +531,32 @@ public class QtAndroidMediaPlayer extends MediaPlayer
return mMuted; return mMuted;
} }
@Override
public void reset() public void reset()
{ {
mInitialized = false; if ((mState & (State.Idle
super.reset(); | 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);
}
} }

View File

@@ -60,6 +60,7 @@ public:
virtual void setVideoSize(const QSize &) { } virtual void setVideoSize(const QSize &) { }
virtual void stop() { } virtual void stop() { }
virtual void reset() { }
// signals: // signals:
// void readyChanged(bool); // void readyChanged(bool);

View File

@@ -122,20 +122,8 @@ QAndroidVideoRendererControl::QAndroidVideoRendererControl(QObject *parent)
QAndroidVideoRendererControl::~QAndroidVideoRendererControl() QAndroidVideoRendererControl::~QAndroidVideoRendererControl()
{ {
if (m_surfaceTexture) { clearSurfaceTexture();
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;
}
if (m_glDeleter) if (m_glDeleter)
m_glDeleter->deleteLater(); m_glDeleter->deleteLater();
} }
@@ -202,6 +190,24 @@ bool QAndroidVideoRendererControl::initSurfaceTexture()
return m_surfaceTexture != 0; 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() jobject QAndroidVideoRendererControl::surfaceHolder()
{ {
if (!initSurfaceTexture()) if (!initSurfaceTexture())
@@ -245,6 +251,11 @@ void QAndroidVideoRendererControl::stop()
m_nativeSize = QSize(); m_nativeSize = QSize();
} }
void QAndroidVideoRendererControl::reset()
{
clearSurfaceTexture();
}
void QAndroidVideoRendererControl::onFrameAvailable() void QAndroidVideoRendererControl::onFrameAvailable()
{ {
if (!m_nativeSize.isValid() || !m_surface) if (!m_nativeSize.isValid() || !m_surface)

View File

@@ -92,6 +92,7 @@ public:
bool isReady() Q_DECL_OVERRIDE; bool isReady() Q_DECL_OVERRIDE;
void setVideoSize(const QSize &size) Q_DECL_OVERRIDE; void setVideoSize(const QSize &size) Q_DECL_OVERRIDE;
void stop() Q_DECL_OVERRIDE; void stop() Q_DECL_OVERRIDE;
void reset() Q_DECL_OVERRIDE;
void customEvent(QEvent *) Q_DECL_OVERRIDE; void customEvent(QEvent *) Q_DECL_OVERRIDE;
@@ -107,6 +108,7 @@ private:
void createGLResources(); void createGLResources();
QMutex m_mutex; QMutex m_mutex;
void clearSurfaceTexture();
QAbstractVideoSurface *m_surface; QAbstractVideoSurface *m_surface;
QSize m_nativeSize; QSize m_nativeSize;

View File

@@ -57,25 +57,31 @@ QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent)
mAudioAvailable(false), mAudioAvailable(false),
mVideoAvailable(false), mVideoAvailable(false),
mBuffering(false), mBuffering(false),
mMediaPlayerReady(false), mState(JMediaPlayer::Uninitialized),
mPendingState(-1),
mPendingPosition(-1), mPendingPosition(-1),
mPendingSetMedia(false) mPendingSetMedia(false),
mPendingVolume(-1),
mPendingMute(-1)
{ {
connect(mMediaPlayer, SIGNAL(bufferingUpdate(qint32)), connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)),
this, SLOT(onBufferChanged(qint32))); this,SLOT(onBufferingChanged(qint32)));
connect(mMediaPlayer,SIGNAL(info(qint32,qint32)), connect(mMediaPlayer,SIGNAL(info(qint32,qint32)),
this,SLOT(onInfo(qint32,qint32))); this,SLOT(onInfo(qint32,qint32)));
connect(mMediaPlayer,SIGNAL(error(qint32,qint32)), connect(mMediaPlayer,SIGNAL(error(qint32,qint32)),
this,SLOT(onError(qint32,qint32))); this,SLOT(onError(qint32,qint32)));
connect(mMediaPlayer, SIGNAL(mediaPlayerInfo(qint32,qint32)), connect(mMediaPlayer,SIGNAL(stateChanged(qint32)),
this, SLOT(onMediaPlayerInfo(qint32,qint32))); this,SLOT(onStateChanged(qint32)));
connect(mMediaPlayer,SIGNAL(videoSizeChanged(qint32,qint32)), connect(mMediaPlayer,SIGNAL(videoSizeChanged(qint32,qint32)),
this,SLOT(onVideoSizeChanged(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() QAndroidMediaPlayerControl::~QAndroidMediaPlayerControl()
{ {
mMediaPlayer->stop();
mMediaPlayer->release(); mMediaPlayer->release();
delete mMediaPlayer; delete mMediaPlayer;
} }
@@ -92,18 +98,33 @@ QMediaPlayer::MediaStatus QAndroidMediaPlayerControl::mediaStatus() const
qint64 QAndroidMediaPlayerControl::duration() const qint64 QAndroidMediaPlayerControl::duration() const
{ {
return (mCurrentMediaStatus == QMediaPlayer::InvalidMedia if ((mState & (JMediaPlayer::Prepared
|| mCurrentMediaStatus == QMediaPlayer::NoMedia | JMediaPlayer::Started
|| !mMediaPlayerReady) ? 0 | JMediaPlayer::Paused
: mMediaPlayer->getDuration(); | JMediaPlayer::Stopped
| JMediaPlayer::PlaybackCompleted)) == 0) {
return 0;
}
return mMediaPlayer->getDuration();
} }
qint64 QAndroidMediaPlayerControl::position() const qint64 QAndroidMediaPlayerControl::position() const
{ {
if (!mMediaPlayerReady) if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia)
return mPendingPosition < 0 ? 0 : mPendingPosition; return duration();
return mMediaPlayer->getCurrentPosition(); if ((mState & (JMediaPlayer::Idle
| JMediaPlayer::Initialized
| JMediaPlayer::Prepared
| JMediaPlayer::Started
| JMediaPlayer::Paused
| JMediaPlayer::Stopped
| JMediaPlayer::PlaybackCompleted)) == 0) {
return (mPendingPosition == -1) ? 0 : mPendingPosition;
}
return (mCurrentState == QMediaPlayer::StoppedState) ? 0 : mMediaPlayer->getCurrentPosition();
} }
void QAndroidMediaPlayerControl::setPosition(qint64 position) void QAndroidMediaPlayerControl::setPosition(qint64 position)
@@ -113,35 +134,88 @@ void QAndroidMediaPlayerControl::setPosition(qint64 position)
const int seekPosition = (position > INT_MAX) ? INT_MAX : position; const int seekPosition = (position > INT_MAX) ? INT_MAX : position;
if (!mMediaPlayerReady) { if ((mState & (JMediaPlayer::Prepared
| JMediaPlayer::Started
| JMediaPlayer::Paused
| JMediaPlayer::PlaybackCompleted)) == 0) {
if (mPendingPosition != seekPosition) {
mPendingPosition = seekPosition; mPendingPosition = seekPosition;
Q_EMIT positionChanged(seekPosition); Q_EMIT positionChanged(seekPosition);
}
return; return;
} }
if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia)
setMediaStatus(QMediaPlayer::LoadedMedia);
mMediaPlayer->seekTo(seekPosition); mMediaPlayer->seekTo(seekPosition);
if (mPendingPosition != -1) {
mPendingPosition = -1; mPendingPosition = -1;
} }
Q_EMIT positionChanged(seekPosition);
}
int QAndroidMediaPlayerControl::volume() const int QAndroidMediaPlayerControl::volume() const
{ {
return mMediaPlayer->volume(); return (mPendingVolume == -1) ? mMediaPlayer->volume() : mPendingVolume;
} }
void QAndroidMediaPlayerControl::setVolume(int volume) 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); mMediaPlayer->setVolume(volume);
if (mPendingVolume != -1) {
mPendingVolume = -1;
return;
}
Q_EMIT volumeChanged(volume); Q_EMIT volumeChanged(volume);
} }
bool QAndroidMediaPlayerControl::isMuted() const bool QAndroidMediaPlayerControl::isMuted() const
{ {
return mMediaPlayer->isMuted(); return (mPendingMute == -1) ? mMediaPlayer->isMuted() : (mPendingMute == 1);
} }
void QAndroidMediaPlayerControl::setMuted(bool muted) 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); mMediaPlayer->setMuted(muted);
if (mPendingMute != -1) {
mPendingMute = -1;
return;
}
Q_EMIT mutedChanged(muted); Q_EMIT mutedChanged(muted);
} }
@@ -208,10 +282,21 @@ const QIODevice *QAndroidMediaPlayerControl::mediaStream() const
void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
QIODevice *stream) QIODevice *stream)
{ {
const bool reloading = (mMediaContent == mediaContent);
if (!reloading) {
mMediaContent = mediaContent; mMediaContent = mediaContent;
mMediaStream = stream; 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 // 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 // since it can cause problems on some hardware
mPendingSetMedia = true; mPendingSetMedia = true;
@@ -229,68 +314,88 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
mediaPath = url.toString(); mediaPath = url.toString();
} }
if (!mediaPath.isEmpty()) if (mVideoSize.isValid() && mVideoOutput)
mMediaPlayer->setDataSource(mediaPath); mVideoOutput->setVideoSize(mVideoSize);
else
setMediaStatus(QMediaPlayer::NoMedia);
if (!mMediaPlayer->display() && mVideoOutput)
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
mMediaPlayer->setDataSource(mediaPath);
mMediaPlayer->prepareAsync();
if (!reloading)
Q_EMIT mediaChanged(mMediaContent); Q_EMIT mediaChanged(mMediaContent);
resetBufferingProgress(); resetBufferingProgress();
// reset some properties
setAudioAvailable(false);
setVideoAvailable(false);
setSeekable(true);
} }
void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput) void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput)
{ {
if (mVideoOutput) if (mVideoOutput) {
mMediaPlayer->setDisplay(0);
mVideoOutput->stop(); mVideoOutput->stop();
mVideoOutput->reset();
}
mVideoOutput = qobject_cast<QAndroidVideoOutput *>(videoOutput); mVideoOutput = qobject_cast<QAndroidVideoOutput *>(videoOutput);
if (mVideoOutput && !mMediaPlayer->display()) { if (!mVideoOutput)
return;
if (mVideoOutput->isReady()) if (mVideoOutput->isReady())
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder()); mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
else
connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool))); connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool)));
} }
}
void QAndroidMediaPlayerControl::play() void QAndroidMediaPlayerControl::play()
{ {
if (!mMediaPlayerReady) { // We need to prepare the mediaplayer again.
mPendingState = QMediaPlayer::PlayingState; if ((mState & JMediaPlayer::Stopped) && !mMediaContent.isNull()) {
if (mCurrentState == QMediaPlayer::StoppedState setMedia(mMediaContent, mMediaStream);
&& !mMediaContent.isNull()
&& mCurrentMediaStatus != QMediaPlayer::LoadingMedia
&& !mPendingSetMedia) {
setMedia(mMediaContent, 0);
} }
setState(QMediaPlayer::PlayingState);
if ((mState & (JMediaPlayer::Prepared
| JMediaPlayer::Started
| JMediaPlayer::Paused
| JMediaPlayer::PlaybackCompleted)) == 0) {
mPendingState = QMediaPlayer::PlayingState;
return; return;
} }
mMediaPlayer->play(); mMediaPlayer->play();
setState(QMediaPlayer::PlayingState);
} }
void QAndroidMediaPlayerControl::pause() void QAndroidMediaPlayerControl::pause()
{ {
if (!mMediaPlayerReady) { setState(QMediaPlayer::PausedState);
if ((mState & (JMediaPlayer::Started
| JMediaPlayer::Paused
| JMediaPlayer::PlaybackCompleted)) == 0) {
mPendingState = QMediaPlayer::PausedState; mPendingState = QMediaPlayer::PausedState;
return; return;
} }
mMediaPlayer->pause(); mMediaPlayer->pause();
setState(QMediaPlayer::PausedState);
} }
void QAndroidMediaPlayerControl::stop() void QAndroidMediaPlayerControl::stop()
{ {
mMediaPlayer->stop();
setState(QMediaPlayer::StoppedState); 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) void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra)
@@ -310,7 +415,7 @@ void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra)
setMediaStatus(QMediaPlayer::StalledMedia); setMediaStatus(QMediaPlayer::StalledMedia);
break; break;
case JMediaPlayer::MEDIA_INFO_BUFFERING_END: case JMediaPlayer::MEDIA_INFO_BUFFERING_END:
setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia); if (mCurrentState != QMediaPlayer::StoppedState)
flushPendingStates(); flushPendingStates();
break; break;
case JMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING: case JMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING:
@@ -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) void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
{ {
QString errorString; QString errorString;
@@ -372,6 +442,10 @@ void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
errorString = QLatin1String("Error: Server died"); errorString = QLatin1String("Error: Server died");
error = QMediaPlayer::ServiceMissingError; error = QMediaPlayer::ServiceMissingError;
break; break;
case JMediaPlayer::MEDIA_ERROR_INVALID_STATE:
errorString = QLatin1String("Error: Invalid state");
error = QMediaPlayer::ServiceMissingError;
break;
} }
switch (extra) { switch (extra) {
@@ -398,12 +472,16 @@ void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra)
error = QMediaPlayer::FormatError; error = QMediaPlayer::FormatError;
setMediaStatus(QMediaPlayer::InvalidMedia); setMediaStatus(QMediaPlayer::InvalidMedia);
break; 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; mBuffering = percent != 100;
mBufferPercent = percent; mBufferPercent = percent;
@@ -411,8 +489,8 @@ void QAndroidMediaPlayerControl::onBufferChanged(qint32 percent)
updateAvailablePlaybackRanges(); updateAvailablePlaybackRanges();
if (mBufferPercent == 100) if (mCurrentState != QMediaPlayer::StoppedState)
setMediaStatus(QMediaPlayer::BufferedMedia); setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia);
} }
void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height) void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height)
@@ -429,27 +507,98 @@ void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height)
mVideoOutput->setVideoSize(mVideoSize); mVideoOutput->setVideoSize(mVideoSize);
} }
void QAndroidMediaPlayerControl::onStateChanged(qint32 state)
{
// 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) void QAndroidMediaPlayerControl::onVideoOutputReady(bool ready)
{ {
if (!mMediaPlayer->display() && mVideoOutput && ready) { if (!mMediaPlayer->display() && mVideoOutput && ready)
mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder()); mMediaPlayer->setDisplay(mVideoOutput->surfaceHolder());
flushPendingStates(); flushPendingStates();
} }
}
void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state) void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state)
{ {
if (mCurrentState == state) if (mCurrentState == state)
return; return;
if (state == QMediaPlayer::StoppedState) { if (mCurrentState == QMediaPlayer::StoppedState && state == QMediaPlayer::PausedState)
if (mVideoOutput) return;
mVideoOutput->stop();
resetBufferingProgress();
mMediaPlayerReady = false;
mPendingPosition = -1;
Q_EMIT positionChanged(0);
}
mCurrentState = state; mCurrentState = state;
Q_EMIT stateChanged(mCurrentState); Q_EMIT stateChanged(mCurrentState);
@@ -463,17 +612,13 @@ void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status
if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia)
Q_EMIT durationChanged(0); Q_EMIT durationChanged(0);
if (status == QMediaPlayer::EndOfMedia)
Q_EMIT durationChanged(duration());
mCurrentMediaStatus = status; mCurrentMediaStatus = status;
Q_EMIT mediaStatusChanged(mCurrentMediaStatus); 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) void QAndroidMediaPlayerControl::setSeekable(bool seekable)
{ {
if (mSeekable == seekable) if (mSeekable == seekable)
@@ -515,15 +660,23 @@ void QAndroidMediaPlayerControl::resetBufferingProgress()
void QAndroidMediaPlayerControl::flushPendingStates() void QAndroidMediaPlayerControl::flushPendingStates()
{ {
if (mPendingSetMedia) { if (mPendingSetMedia) {
setMedia(mMediaContent, 0);
mPendingSetMedia = false; mPendingSetMedia = false;
setMedia(mMediaContent, 0);
return; return;
} }
switch (mPendingState) { const int newState = mPendingState;
case QMediaPlayer::PlayingState: mPendingState = -1;
if (mPendingPosition > -1)
if (mPendingPosition != -1)
setPosition(mPendingPosition); setPosition(mPendingPosition);
if (mPendingVolume != -1)
setVolume(mPendingVolume);
if (mPendingMute != -1)
setMuted((mPendingMute == 1));
switch (newState) {
case QMediaPlayer::PlayingState:
play(); play();
break; break;
case QMediaPlayer::PausedState: case QMediaPlayer::PausedState:
@@ -532,6 +685,8 @@ void QAndroidMediaPlayerControl::flushPendingStates()
case QMediaPlayer::StoppedState: case QMediaPlayer::StoppedState:
stop(); stop();
break; break;
default:
break;
} }
} }

View File

@@ -93,9 +93,9 @@ private Q_SLOTS:
void onVideoOutputReady(bool ready); void onVideoOutputReady(bool ready);
void onError(qint32 what, qint32 extra); void onError(qint32 what, qint32 extra);
void onInfo(qint32 what, qint32 extra); void onInfo(qint32 what, qint32 extra);
void onMediaPlayerInfo(qint32 what, qint32 extra); void onBufferingChanged(qint32 percent);
void onBufferChanged(qint32 percent);
void onVideoSizeChanged(qint32 width, qint32 height); void onVideoSizeChanged(qint32 width, qint32 height);
void onStateChanged(qint32 state);
private: private:
JMediaPlayer *mMediaPlayer; JMediaPlayer *mMediaPlayer;
@@ -111,15 +111,16 @@ private:
QSize mVideoSize; QSize mVideoSize;
bool mBuffering; bool mBuffering;
QMediaTimeRange mAvailablePlaybackRange; QMediaTimeRange mAvailablePlaybackRange;
bool mMediaPlayerReady; int mState;
QMediaPlayer::State mPendingState; int mPendingState;
qint64 mPendingPosition; qint64 mPendingPosition;
bool mPendingSetMedia; bool mPendingSetMedia;
int mPendingVolume;
int mPendingMute;
QScopedPointer<QTemporaryFile> mTempFile; QScopedPointer<QTemporaryFile> mTempFile;
void setState(QMediaPlayer::State state); void setState(QMediaPlayer::State state);
void setMediaStatus(QMediaPlayer::MediaStatus status); void setMediaStatus(QMediaPlayer::MediaStatus status);
void setError(int error, const QString &errorString);
void setSeekable(bool seekable); void setSeekable(bool seekable);
void setAudioAvailable(bool available); void setAudioAvailable(bool available);
void setVideoAvailable(bool available); void setVideoAvailable(bool available);

View File

@@ -46,135 +46,113 @@
#include <QtCore/private/qjnihelpers_p.h> #include <QtCore/private/qjnihelpers_p.h>
#include <QMap> #include <QMap>
namespace { static jclass mediaPlayerClass = Q_NULLPTR;
typedef QMap<jlong, JMediaPlayer *> MediaPlayerMap;
jclass mediaPlayerClass = 0; Q_GLOBAL_STATIC(MediaPlayerMap, mediaPlayers)
QMap<jlong, JMediaPlayer *> mplayers;
}
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
bool JMediaPlayer::mActivitySet = false;
JMediaPlayer::JMediaPlayer() JMediaPlayer::JMediaPlayer()
: QObject() : QObject()
, QJNIObjectPrivate(mediaPlayerClass, "(J)V", reinterpret_cast<jlong>(this))
, mId(reinterpret_cast<jlong>(this))
, mDisplay(0)
{ {
mplayers.insert(mId, this);
if (!mActivitySet) { const jlong id = reinterpret_cast<jlong>(this);
QJNIObjectPrivate::callStaticMethod<void>(mediaPlayerClass, mMediaPlayer = QJNIObjectPrivate(mediaPlayerClass,
"setActivity", "(Landroid/app/Activity;J)V",
"(Landroid/app/Activity;)V", QtAndroidPrivate::activity(),
QtAndroidPrivate::activity()); id);
mActivitySet = true; (*mediaPlayers)[id] = this;
}
} }
JMediaPlayer::~JMediaPlayer() JMediaPlayer::~JMediaPlayer()
{ {
mplayers.remove(mId); mediaPlayers->remove(reinterpret_cast<jlong>(this));
} }
void JMediaPlayer::release() 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); mMediaPlayer.callMethod<void>("reset");
}
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);
} }
int JMediaPlayer::getCurrentPosition() int JMediaPlayer::getCurrentPosition()
{ {
return callMethod<jint>("getCurrentPosition"); return mMediaPlayer.callMethod<jint>("getCurrentPosition");
} }
int JMediaPlayer::getDuration() int JMediaPlayer::getDuration()
{ {
return callMethod<jint>("getDuration"); return mMediaPlayer.callMethod<jint>("getDuration");
} }
bool JMediaPlayer::isPlaying() bool JMediaPlayer::isPlaying()
{ {
return callMethod<jboolean>("isPlaying"); return mMediaPlayer.callMethod<jboolean>("isPlaying");
} }
int JMediaPlayer::volume() int JMediaPlayer::volume()
{ {
return callMethod<jint>("getVolume"); return mMediaPlayer.callMethod<jint>("getVolume");
} }
bool JMediaPlayer::isMuted() 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() void JMediaPlayer::play()
{ {
callMethod<void>("start"); mMediaPlayer.callMethod<void>("start");
} }
void JMediaPlayer::pause() void JMediaPlayer::pause()
{ {
callMethod<void>("pause"); mMediaPlayer.callMethod<void>("pause");
} }
void JMediaPlayer::stop() void JMediaPlayer::stop()
{ {
callMethod<void>("stop"); mMediaPlayer.callMethod<void>("stop");
} }
void JMediaPlayer::seekTo(qint32 msec) 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) 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) void JMediaPlayer::setDataSource(const QString &path)
{ {
QJNIObjectPrivate string = QJNIObjectPrivate::fromString(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) 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) void JMediaPlayer::setDisplay(jobject surfaceHolder)
{ {
mDisplay = surfaceHolder; mMediaPlayer.callMethod<void>("setDisplay", "(Landroid/view/SurfaceHolder;)V", surfaceHolder);
callMethod<void>("setDisplay", "(Landroid/view/SurfaceHolder;)V", mDisplay);
} }
QT_END_NAMESPACE QT_END_NAMESPACE
@@ -183,44 +161,66 @@ static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlon
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
JMediaPlayer *const mp = mplayers[id]; JMediaPlayer *const mp = (*mediaPlayers)[id];
if (!mp) if (!mp)
return; return;
mp->onError(what, extra); Q_EMIT mp->error(what, extra);
} }
static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id) static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id)
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
JMediaPlayer *const mp = mplayers[id]; JMediaPlayer *const mp = (*mediaPlayers)[id];
if (!mp) if (!mp)
return; return;
mp->onBufferingUpdate(percent); 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;
Q_EMIT mp->durationChanged(duration);
} }
static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id) static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id)
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
JMediaPlayer *const mp = mplayers[id]; JMediaPlayer *const mp = (*mediaPlayers)[id];
if (!mp) if (!mp)
return; 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(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
JMediaPlayer *const mp = mplayers[id]; JMediaPlayer *const mp = (*mediaPlayers)[id];
if (!mp) if (!mp)
return; return;
mp->onMediaPlayerInfo(what, extra); Q_EMIT mp->stateChanged(state);
} }
static void onVideoSizeChangedNative(JNIEnv *env, static void onVideoSizeChangedNative(JNIEnv *env,
@@ -231,11 +231,11 @@ static void onVideoSizeChangedNative(JNIEnv *env,
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
JMediaPlayer *const mp = mplayers[id]; JMediaPlayer *const mp = (*mediaPlayers)[id];
if (!mp) if (!mp)
return; return;
mp->onVideoSizeChanged(width, height); Q_EMIT mp->videoSizeChanged(width, height);
} }
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -250,9 +250,11 @@ bool JMediaPlayer::initJNI(JNIEnv *env)
JNINativeMethod methods[] = { JNINativeMethod methods[] = {
{"onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative)}, {"onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative)},
{"onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative)}, {"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)}, {"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, if (env->RegisterNatives(mediaPlayerClass,

View File

@@ -47,7 +47,7 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class JMediaPlayer : public QObject, public QJNIObjectPrivate class JMediaPlayer : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
@@ -59,12 +59,14 @@ public:
// What // What
MEDIA_ERROR_UNKNOWN = 1, MEDIA_ERROR_UNKNOWN = 1,
MEDIA_ERROR_SERVER_DIED = 100, MEDIA_ERROR_SERVER_DIED = 100,
MEDIA_ERROR_INVALID_STATE = -38, // Undocumented
// Extra // Extra
MEDIA_ERROR_IO = -1004, MEDIA_ERROR_IO = -1004,
MEDIA_ERROR_MALFORMED = -1007, MEDIA_ERROR_MALFORMED = -1007,
MEDIA_ERROR_UNSUPPORTED = -1010, MEDIA_ERROR_UNSUPPORTED = -1010,
MEDIA_ERROR_TIMED_OUT = -110, 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 enum MediaInfo
@@ -79,24 +81,29 @@ public:
MEDIA_INFO_METADATA_UPDATE = 802 MEDIA_INFO_METADATA_UPDATE = 802
}; };
enum MediaPlayerInfo enum MediaPlayerState
{ {
MEDIA_PLAYER_INVALID_STATE = 1, Uninitialized = 0x1, /* End */
MEDIA_PLAYER_PREPARING = 2, Idle = 0x2,
MEDIA_PLAYER_READY = 3, Preparing = 0x4,
MEDIA_PLAYER_DURATION = 4, Prepared = 0x8,
MEDIA_PLAYER_PROGRESS = 5, Initialized = 0x10,
MEDIA_PLAYER_FINISHED = 6 Started = 0x20,
Stopped = 0x40,
Paused = 0x80,
PlaybackCompleted = 0x100,
Error = 0x200
}; };
void release(); void release();
void reset();
int getCurrentPosition(); int getCurrentPosition();
int getDuration(); int getDuration();
bool isPlaying(); bool isPlaying();
int volume(); int volume();
bool isMuted(); bool isMuted();
jobject display() { return mDisplay; } jobject display();
void play(); void play();
void pause(); void pause();
@@ -104,30 +111,23 @@ public:
void seekTo(qint32 msec); void seekTo(qint32 msec);
void setMuted(bool mute); void setMuted(bool mute);
void setDataSource(const QString &path); void setDataSource(const QString &path);
void prepareAsync();
void setVolume(int volume); void setVolume(int volume);
void setDisplay(jobject surfaceHolder); 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); static bool initJNI(JNIEnv *env);
Q_SIGNALS: Q_SIGNALS:
void error(qint32 what, qint32 extra); void error(qint32 what, qint32 extra);
void bufferingUpdate(qint32 percent); void bufferingChanged(qint32 percent);
void completion(); void durationChanged(qint64 duration);
void progressChanged(qint64 progress);
void stateChanged(qint32 state);
void info(qint32 what, qint32 extra); void info(qint32 what, qint32 extra);
void mediaPlayerInfo(qint32 what, qint32 extra);
void videoSizeChanged(qint32 width, qint32 height); void videoSizeChanged(qint32 width, qint32 height);
private: private:
jlong mId; QJNIObjectPrivate mMediaPlayer;
jobject mDisplay;
static bool mActivitySet;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE