@Implements(value=android.media.MediaPlayer.class) public class ShadowMediaPlayer extends ShadowPlayerBase
Automated testing of media playback can be a difficult thing - especially testing that your code properly handles asynchronous errors and events. This near impossible task is made quite straightforward using this implementation of MediaPlayer
with Robolectric.
This shadow implementation provides much of the functionality needed to emulate MediaPlayer
initialization and playback behavior without having to play actual media files. A summary of the features included are:
ShadowMediaPlayer.CreateListener
so that newly-created MediaPlayer
instances can have their shadows configured before they are used.OnCompletionListener
, OnErrorListener
, OnInfoListener
, OnPreparedListener
and OnSeekCompleteListener
.MediaPlayer
internal states and their transition map.ShadowMediaPlayer.MediaInfo
inner class.setInvalidStateBehavior(org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior)
).setDataSource()
, using addMediaInfo(org.robolectric.shadows.util.DataSource, org.robolectric.shadows.ShadowMediaPlayer.MediaInfo)
.setDataSource(org.robolectric.shadows.util.DataSource)
using addException(org.robolectric.shadows.util.DataSource, java.lang.RuntimeException)
.Note: One gotcha with this shadow is that you need to either configure an exception or a ShadowMediaPlayer.MediaInfo
instance for that data source (using addException(DataSource, IOException)
or addMediaInfo(DataSource, MediaInfo)
respectively) before calling setDataSource(org.robolectric.shadows.util.DataSource)
, otherwise you’ll get an IllegalArgumentException
.
The current features of ShadowMediaPlayer
were focused on development for testing playback of audio tracks. Thus support for emulating timed text and video events is incomplete. None of these features would be particularly onerous to add/fix - contributions welcome, of course!
Modifier and Type | Class and Description |
---|---|
static interface |
ShadowMediaPlayer.CreateListener
Callback interface for clients that wish to be informed when a new
MediaPlayer instance is constructed. |
static class |
ShadowMediaPlayer.InvalidStateBehavior
Possible behavior modes for the media player when a method is invoked in an invalid state.
|
static interface |
ShadowMediaPlayer.MediaEvent |
static class |
ShadowMediaPlayer.MediaInfo
Class specifying information for an emulated media object.
|
static class |
ShadowMediaPlayer.State
Possible states for the media player to be in.
|
Modifier and Type | Field and Description |
---|---|
protected static ShadowMediaPlayer.CreateListener |
createListener
Listener that is called when a new MediaPlayer is constructed.
|
static int |
MEDIA_EVENT |
Constructor and Description |
---|
ShadowMediaPlayer() |
Modifier and Type | Method and Description |
---|---|
protected void |
__constructor__() |
protected static void |
__staticInitializer__() |
protected void |
_pause()
Simulates
MediaPlayer._pause() . |
protected void |
_release()
Simulates call to
MediaPlayer._release() . |
protected void |
_reset()
Simulates call to
MediaPlayer._reset() . |
protected void |
_stop()
Simulates call to
MediaPlayer.release() . |
static void |
addException(DataSource dataSource,
java.io.IOException e) |
static void |
addException(DataSource dataSource,
java.lang.RuntimeException e) |
static void |
addMediaInfo(DataSource dataSource,
ShadowMediaPlayer.MediaInfo info) |
protected void |
attachAuxEffect(int effectId) |
protected static android.media.MediaPlayer |
create(android.content.Context context,
int resId) |
protected static android.media.MediaPlayer |
create(android.content.Context context,
android.net.Uri uri) |
void |
doSetDataSource(DataSource dataSource)
Sets the data source without doing any other emulation.
|
void |
doStart()
Starts simulated playback.
|
void |
doStop()
Pauses simulated playback.
|
protected int |
getAudioSessionId() |
int |
getAuxEffect()
Useful for assertions.
|
protected int |
getCurrentPosition()
Simulates call to
MediaPlayer.getCurrentPosition() . |
int |
getCurrentPositionRaw()
Retrieves the current position without doing the state checking that the emulated version of
getCurrentPosition() does. |
DataSource |
getDataSource()
Retrieves the data source (if any) that was passed in to
setDataSource(DataSource) . |
protected int |
getDuration()
Simulates call to
MediaPlayer.getDuration() . |
int |
getDurationRaw()
Retrieves the current duration without doing the state checking that the emulated version does.
|
android.os.Handler |
getHandler()
Retrieves the
Handler object used by this ShadowMediaPlayer . |
ShadowMediaPlayer.InvalidStateBehavior |
getInvalidStateBehavior()
Retrieves current flag specifying the behavior of the media player when a method is invoked in an invalid state.
|
float |
getLeftVolume()
Retrieves the current setting for the left channel volume.
|
ShadowMediaPlayer.MediaInfo |
getMediaInfo()
Retrieves the currently selected
ShadowMediaPlayer.MediaInfo . |
static ShadowMediaPlayer.MediaInfo |
getMediaInfo(DataSource dataSource) |
android.media.MediaPlayer.OnCompletionListener |
getOnCompletionListener() |
android.media.MediaPlayer.OnPreparedListener |
getOnPreparedListener() |
int |
getPendingSeek()
Retrieves the pending seek setting.
|
float |
getRightVolume() |
int |
getSeekDelay() |
int |
getSourceResId()
Retrieves the resource ID used in the call to
create(Context, int) (if any). |
android.net.Uri |
getSourceUri()
Retrieves the source path (if any) that was passed in to
MediaPlayer.setDataSource(Context, Uri, Map) or MediaPlayer.setDataSource(Context, Uri) . |
ShadowMediaPlayer.State |
getState()
Retrieves the current state of the
MediaPlayer . |
int |
getTheAudioStreamType()
Note: This has a funny name at the moment to avoid having to produce an API-specific shadow - if it were called
getAudioStreamType() then the RobolectricWiringTest will inform us that it should be annotated with Implementation , because there is a private method in the later API versions with the same name, however this would fail on earlier versions. |
protected int |
getVideoHeight() |
protected int |
getVideoWidth() |
void |
invokeCompletionListener()
Simulates end-of-playback.
|
void |
invokeErrorListener(int what,
int extra)
Allows test cases to directly simulate invocation of the OnError event.
|
void |
invokeInfoListener(int what,
int extra)
Allows test cases to directly simulate invocation of the OnInfo event.
|
void |
invokePreparedListener()
Allows test cases to simulate ‘prepared’ state by invoking callback.
|
void |
invokeSeekCompleteListener()
Allows test cases to simulate seek completion by invoking callback.
|
protected boolean |
isLooping() |
protected boolean |
isPlaying() |
boolean |
isPrepared()
Tests to see if the player is in the PREPARED state.
|
boolean |
isReallyPlaying()
Tests to see if the player is really playing.
|
void |
postEvent(ShadowMediaPlayer.MediaEvent e) |
void |
postEventDelayed(ShadowMediaPlayer.MediaEvent e,
long delay) |
protected void |
prepare()
Simulates
MediaPlayer.prepareAsync() . |
protected void |
prepareAsync()
Simulates
MediaPlayer.prepareAsync() . |
static void |
resetStaticState() |
protected void |
seekTo(int seekTo)
Simulates seeking to specified position.
|
protected void |
seekTo(long seekTo,
int mode) |
protected void |
setAudioSessionId(int sessionId) |
protected void |
setAudioStreamType(int audioStreamType) |
static void |
setCreateListener(ShadowMediaPlayer.CreateListener createListener)
Sets a listener that is invoked whenever a new shadowed
MediaPlayer object is constructed. |
void |
setCurrentPosition(int position)
Sets the current position, bypassing the normal state checking.
|
protected void |
setDataSource(android.content.Context context,
android.net.Uri uri,
java.util.Map<java.lang.String,java.lang.String> headers) |
void |
setDataSource(DataSource dataSource)
Common code path for all
setDataSource() implementations. |
protected void |
setDataSource(java.io.FileDescriptor fd,
long offset,
long length) |
protected void |
setDataSource(java.lang.String path) |
protected void |
setDataSource(java.lang.String uri,
java.util.Map<java.lang.String,java.lang.String> headers) |
void |
setInvalidStateBehavior(ShadowMediaPlayer.InvalidStateBehavior invalidStateBehavior)
Specifies how the media player should behave when a method is invoked in an invalid state.
|
protected void |
setLooping(boolean looping) |
protected void |
setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener listener) |
protected void |
setOnErrorListener(android.media.MediaPlayer.OnErrorListener listener) |
protected void |
setOnInfoListener(android.media.MediaPlayer.OnInfoListener listener) |
protected void |
setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener listener) |
protected void |
setOnSeekCompleteListener(android.media.MediaPlayer.OnSeekCompleteListener listener) |
void |
setSeekDelay(int seekDelay)
Sets the length of time (ms) that seekTo() will delay before completing.
|
void |
setState(ShadowMediaPlayer.State state)
Forces the @link MediaPlayer} into the specified state.
|
protected void |
setVolume(float left,
float right) |
protected void |
start()
Simulates private native method
MediaPlayer._start() . |
getService
protected static ShadowMediaPlayer.CreateListener createListener
Listener that is called when a new MediaPlayer is constructed.
setCreateListener(CreateListener)
public static final int MEDIA_EVENT
@Implementation protected static void __staticInitializer__()
public void postEvent(ShadowMediaPlayer.MediaEvent e)
public void postEventDelayed(ShadowMediaPlayer.MediaEvent e, long delay)
@Implementation protected static android.media.MediaPlayer create(android.content.Context context, int resId)
@Implementation protected static android.media.MediaPlayer create(android.content.Context context, android.net.Uri uri)
@Implementation protected void __constructor__()
public void setDataSource(DataSource dataSource) throws java.io.IOException
Common code path for all setDataSource()
implementations.
doSetDataSource(DataSource)
is called to set the data source.INITIALIZED
.Usually this method would not be called directly, but indirectly through one of the other setDataSource(String)
implementations, which use DataSource.toDataSource(String)
methods to convert their discrete parameters into a single DataSource
instance.
dataSource
- the data source that is being set.java.io.IOException
- if the specified data source has been configured to throw an IO exception.addException(DataSource, IOException)
,
addException(DataSource, RuntimeException)
,
doSetDataSource(DataSource)
public void doSetDataSource(DataSource dataSource)
Sets the data source without doing any other emulation. Sets the internal data source only. Calling directly can be useful for setting up a ShadowMediaPlayer
instance during specific testing so that you don’t have to clutter your tests catching exceptions you know won’t be thrown.
dataSource
- the data source that is being set.setDataSource(DataSource)
@Implementation protected void setDataSource(java.lang.String path) throws java.io.IOException
java.io.IOException
@Implementation protected void setDataSource(android.content.Context context, android.net.Uri uri, java.util.Map<java.lang.String,java.lang.String> headers) throws java.io.IOException
java.io.IOException
@Implementation protected void setDataSource(java.lang.String uri, java.util.Map<java.lang.String,java.lang.String> headers) throws java.io.IOException
java.io.IOException
@Implementation protected void setDataSource(java.io.FileDescriptor fd, long offset, long length) throws java.io.IOException
java.io.IOException
public static ShadowMediaPlayer.MediaInfo getMediaInfo(DataSource dataSource)
public static void addMediaInfo(DataSource dataSource, ShadowMediaPlayer.MediaInfo info)
public static void addException(DataSource dataSource, java.lang.RuntimeException e)
public static void addException(DataSource dataSource, java.io.IOException e)
@Implementation protected void setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener listener)
@Implementation protected void setOnSeekCompleteListener(android.media.MediaPlayer.OnSeekCompleteListener listener)
@Implementation protected void setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener listener)
@Implementation protected void setOnInfoListener(android.media.MediaPlayer.OnInfoListener listener)
@Implementation protected void setOnErrorListener(android.media.MediaPlayer.OnErrorListener listener)
@Implementation protected boolean isLooping()
@Implementation protected void setLooping(boolean looping)
@Implementation protected void setVolume(float left, float right)
@Implementation protected boolean isPlaying()
@Implementation protected void prepare()
Simulates MediaPlayer.prepareAsync()
. Sleeps for preparationDelay
ms by calling SystemClock.sleep(long)
before calling invokePreparedListener()
.
If preparationDelay
is not positive and non-zero, there is no sleep.
@Implementation protected void prepareAsync()
Simulates MediaPlayer.prepareAsync()
. Sets state to PREPARING and posts a callback to invokePreparedListener()
if the current preparation delay for the current media (see getMediaInfo()
) is >= 0, otherwise the test suite is responsible for calling invokePreparedListener()
directly if required.
@Implementation protected void start()
Simulates private native method MediaPlayer._start()
. Sets state to STARTED and calls doStart()
to start scheduling playback callback events.
If the current state is PLAYBACK_COMPLETED, the current position is reset to zero before starting playback.
doStart()
public boolean isReallyPlaying()
Tests to see if the player is really playing.
The player is defined as “really playing” if simulated playback events (including playback completion) are being scheduled and invoked and currentPosition
is being updated as time passes. Note that while the player will normally be really playing if in the STARTED state, this is not always the case - for example, if a pending seek is in progress, or perhaps a buffer underrun is being simulated.
public void doStart()
Starts simulated playback. Until this method is called, the player is not “really playing” (see isReallyPlaying()
for a definition of “really playing”).
This method is used internally by the various shadow method implementations of the MediaPlayer public API, but may also be called directly by the test suite if you wish to simulate an internal pause. For example, to simulate a buffer underrun (player is in PLAYING state but isn’t actually advancing the current position through the media), you could call doStop()
to mark the start of the buffer underrun and doStart()
to mark its end and restart normal playback (which is what scheduleBufferUnderrunAtOffset()
does).
isReallyPlaying()
,
doStop()
public void doStop()
Pauses simulated playback. After this method is called, the player is no longer “really playing” (see isReallyPlaying()
for a definition of “really playing”).
This method is used internally by the various shadow method implementations of the MediaPlayer public API, but may also be called directly by the test suite if you wish to simulate an internal pause.
isReallyPlaying()
,
doStart()
@Implementation protected void _pause()
Simulates MediaPlayer._pause()
. Invokes doStop()
to suspend playback event callbacks and sets the state to PAUSED.
doStop()
@Implementation protected void _release()
Simulates call to MediaPlayer._release()
. Calls doStop()
to suspend playback event callbacks and sets the state to END.
@Implementation protected void _reset()
Simulates call to MediaPlayer._reset()
. Calls doStop()
to suspend playback event callbacks and sets the state to IDLE.
@Implementation protected void _stop()
Simulates call to MediaPlayer.release()
. Calls doStop()
to suspend playback event callbacks and sets the state to STOPPED.
@Implementation protected void attachAuxEffect(int effectId)
@Implementation protected int getAudioSessionId()
@Implementation protected int getCurrentPosition()
Simulates call to MediaPlayer.getCurrentPosition()
. Simply does the state validity checks and then invokes getCurrentPositionRaw()
to calculate the simulated playback position.
getCurrentPositionRaw()
@Implementation protected int getDuration()
Simulates call to MediaPlayer.getDuration()
. Retrieves the duration as defined by the current ShadowMediaPlayer.MediaInfo
instance.
addMediaInfo(DataSource, MediaInfo)
@Implementation protected int getVideoHeight()
@Implementation protected int getVideoWidth()
@Implementation protected void seekTo(int seekTo)
Simulates seeking to specified position. The seek will complete after seekDelay
ms (defaults to 0), or else if seekDelay is negative then the controlling test is expected to simulate seek completion by manually invoking invokeSeekCompleteListener()
.
seekTo
- the offset (in ms) from the start of the track to seek to.@Implementation(minSdk=26) protected void seekTo(long seekTo, int mode)
@Implementation protected void setAudioSessionId(int sessionId)
@Implementation protected void setAudioStreamType(int audioStreamType)
public static void setCreateListener(ShadowMediaPlayer.CreateListener createListener)
Sets a listener that is invoked whenever a new shadowed MediaPlayer
object is constructed.
Registering a listener gives you a chance to customize the shadowed object appropriately without needing to modify the application-under-test to provide access to the instance at the appropriate point in its life cycle. This is useful because normally a new MediaPlayer
is created and setDataSource()
is invoked soon after, without a break in the code. Using this callback means you don’t have to change this common pattern just so that you can customize the shadow for testing.
createListener
- the listener to be invokedpublic android.os.Handler getHandler()
Retrieves the Handler
object used by this ShadowMediaPlayer
. Can be used for posting custom asynchronous events to the thread (eg, asynchronous errors). Use this for scheduling events to take place at a particular “real” time (ie, time as measured by the scheduler). For scheduling events to occur at a particular playback offset (no matter how long playback may be paused for, or where you seek to, etc), see ShadowMediaPlayer.MediaInfo.scheduleEventAtOffset(int, ShadowMediaPlayer.MediaEvent)
and its various helpers.
public ShadowMediaPlayer.InvalidStateBehavior getInvalidStateBehavior()
Retrieves current flag specifying the behavior of the media player when a method is invoked in an invalid state. See setInvalidStateBehavior(InvalidStateBehavior)
for a discussion of the available modes and their associated behaviors.
setInvalidStateBehavior(org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior)
public void setInvalidStateBehavior(ShadowMediaPlayer.InvalidStateBehavior invalidStateBehavior)
Specifies how the media player should behave when a method is invoked in an invalid state. Three modes are supported (as defined by the ShadowMediaPlayer.InvalidStateBehavior
enum):
ShadowMediaPlayer.InvalidStateBehavior.SILENT
No invalid state checking is done at all. All methods can be invoked from any state without throwing any exceptions or invoking the error listener.
This mode is provided primarily for backwards compatibility, and for this reason it is the default. For proper testing one of the other two modes is probably preferable.
ShadowMediaPlayer.InvalidStateBehavior.EMULATE
The shadow will attempt to emulate the behavior of the actual MediaPlayer
implementation. This is based on a reading of the documentation and on actual experiments done on a Jelly Bean device. The official documentation is not all that clear, but basically methods fall into three categories: * Those that log an error when invoked in an invalid state but don’t throw an exception or invoke onError()
. An example is getVideoHeight()
. * Synchronous error handling: methods always throw an exception (usually IllegalStateException
but don’t invoke onError()
. Examples are prepare()
and setDataSource(String)
. * Asynchronous error handling: methods don’t throw an exception but invoke onError()
.
Additionally, all three methods behave synchronously (throwing IllegalStateException
when invoked from the END state.
To complicate matters slightly, the official documentation sometimes contradicts observed behavior. For example, the documentation says it is illegal to call setDataSource(org.robolectric.shadows.util.DataSource)
from the ERROR state - however, in practice it works fine. Conversely, the documentation says that it is legal to invoke getCurrentPosition()
from the INITIALIZED state, however testing showed that this caused an error. Wherever there is a discrepancy between documented and observed behavior, this implementation has gone with the most conservative implementation (ie, it is illegal to invoke setDataSource(org.robolectric.shadows.util.DataSource)
from the ERROR state and likewise illegal to invoke getCurrentPosition()
from the INITIALIZED state.
ShadowMediaPlayer.InvalidStateBehavior.ASSERT
The shadow will raise an assertion any time that a method is invoked in an invalid state. The philosophy behind this mode is that to invoke a method in an invalid state is a programming error - a bug, pure and simple. As such it should be discovered and eliminated at development and testing time, rather than anticipated and handled at runtime. Asserting is a way of testing for these bugs during testing.
invalidStateBehavior
- the behavior mode for this shadow to use during testing.getInvalidStateBehavior()
public ShadowMediaPlayer.MediaInfo getMediaInfo()
Retrieves the currently selected ShadowMediaPlayer.MediaInfo
. This instance is used to define current duration, preparation delay, exceptions for setDataSource()
, playback events, etc.
ShadowMediaPlayer.MediaInfo
.addMediaInfo(org.robolectric.shadows.util.DataSource, org.robolectric.shadows.ShadowMediaPlayer.MediaInfo)
,
doSetDataSource(DataSource)
public void setCurrentPosition(int position)
Sets the current position, bypassing the normal state checking. Use with care.
position
- the new playback position.public int getCurrentPositionRaw()
Retrieves the current position without doing the state checking that the emulated version of getCurrentPosition()
does.
public int getDurationRaw()
Retrieves the current duration without doing the state checking that the emulated version does.
public ShadowMediaPlayer.State getState()
Retrieves the current state of the MediaPlayer
. Uses the states as defined in the MediaPlayer
documentation.
MediaPlayer
, as defined in the MediaPlayer documentation.setState(org.robolectric.shadows.ShadowMediaPlayer.State)
,
MediaPlayer
public void setState(ShadowMediaPlayer.State state)
Forces the @link MediaPlayer} into the specified state. Uses the states as defined in the MediaPlayer
documentation.
Note that by invoking this method directly you can get the player into an inconsistent state that a real player could not be put in (eg, in the END state but with playback events still happening). Use with care.
state
- the new state of the MediaPlayer
, as defined in the MediaPlayer documentation.getState()
,
MediaPlayer
public int getTheAudioStreamType()
Note: This has a funny name at the moment to avoid having to produce an API-specific shadow - if it were called getAudioStreamType()
then the RobolectricWiringTest
will inform us that it should be annotated with Implementation
, because there is a private method in the later API versions with the same name, however this would fail on earlier versions.
public int getSeekDelay()
public void setSeekDelay(int seekDelay)
Sets the length of time (ms) that seekTo() will delay before completing. Default is 0. If set to -1, then seekTo() will not call the OnSeekCompleteListener automatically; you will need to call invokeSeekCompleteListener() manually.
seekDelay
- length of time to delay (ms)public int getAuxEffect()
Useful for assertions.
auxEffect
setting.public int getPendingSeek()
Retrieves the pending seek setting.
seekTo(int)
but before a call to invokeSeekCompleteListener()
). Returns -1
if no seek is in progress.public DataSource getDataSource()
Retrieves the data source (if any) that was passed in to setDataSource(DataSource)
.
Useful for assertions.
setDataSource
.public android.net.Uri getSourceUri()
Retrieves the source path (if any) that was passed in to MediaPlayer.setDataSource(Context, Uri, Map)
or MediaPlayer.setDataSource(Context, Uri)
.
setDataSource
.public int getSourceResId()
Retrieves the resource ID used in the call to create(Context, int)
(if any).
create()
, or -1
if a different method of setting the source was used.public float getLeftVolume()
Retrieves the current setting for the left channel volume.
public float getRightVolume()
public boolean isPrepared()
Tests to see if the player is in the PREPARED state. This is mainly used for backward compatibility. getState()
may be more useful for new testing applications.
true
if the MediaPlayer is in the PREPARED state, false otherwise.public android.media.MediaPlayer.OnCompletionListener getOnCompletionListener()
public android.media.MediaPlayer.OnPreparedListener getOnPreparedListener()
public void invokePreparedListener()
Allows test cases to simulate ‘prepared’ state by invoking callback. Sets the player’s state to PREPARED and invokes the preparedListener()
public void invokeCompletionListener()
Simulates end-of-playback. Changes the player into PLAYBACK_COMPLETED state and calls onCompletion()
if a listener has been set.
public void invokeSeekCompleteListener()
Allows test cases to simulate seek completion by invoking callback.
public void invokeInfoListener(int what, int extra)
Allows test cases to directly simulate invocation of the OnInfo event.
what
- parameter to pass in to what
in MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)
.extra
- parameter to pass in to extra
in MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)
.public void invokeErrorListener(int what, int extra)
Allows test cases to directly simulate invocation of the OnError event.
what
- parameter to pass in to what
in MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
.extra
- parameter to pass in to extra
in MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
.@Resetter public static void resetStaticState()