上一节中我们简单学习了MediaPlayer使用,我们知道MediaPlayer有个空构造方法,create(),prepare(),prepareAsync(),setDataSource(),start(),pause(),release(),以及状态管理等。
这节我们仅从Framework层源码的角度简单解析一下以上内容,Native层另起内容学习。
MediaPlayer继承了PlayerBase,实现了SubtitleController.Listener, VolumeAutomation和AudioRouting。
/**
* Default constructor. Consider using one of the create() methods for
* synchronously instantiating a MediaPlayer from a Uri or resource.
* <p>When done with the MediaPlayer, you should call {@link #release()},
* to free the resources. If not released, too many MediaPlayer instances may
* result in an exception.</p>
*/
public MediaPlayer() {
super(new AudioAttributes.Builder().build(),
AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>();
/* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
baseRegisterPlayer();
}
首先看一下MediaPlayer的构造方法,它先调用了父类PlayeBase的构造方法,创建了一个事件handler,调用了native的setup方法。
/**
* Constructor. Must be given audio attributes, as they are required for AppOps.
* @param attr non-null audio attributes
* @param class non-null class of the implementation of this abstract class
*/
PlayerBase(@NonNull AudioAttributes attr, int implType) {
if (attr == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes");
}
mAttributes = attr;
mImplType = implType;
mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE;
};
父类构造方法中最重要的一步就是把state置为IDLE了。
public static MediaPlayer create(Context context, int resid,
AudioAttributes audioAttributes, int audioSessionId) {
try {
AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
if (afd == null) return null;
MediaPlayer mp = new MediaPlayer();
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
mp.setAudioSessionId(audioSessionId);
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
mp.prepare();
return mp;
} catch (IOException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (IllegalArgumentException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (SecurityException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
}
return null;
}
create方法帮我们创建了MediaPlayer实例,并调用了setDataSource和prepare。
/**
* Prepares the player for playback, synchronously.
*
* After setting the datasource and the display surface, you need to either
* call prepare() or prepareAsync(). For files, it is OK to call prepare(),
* which blocks until MediaPlayer is ready for playback.
*
* @throws IllegalStateException if it is called in an invalid state
*/
public void prepare() throws IOException, IllegalStateException {
_prepare();
scanInternalSubtitleTracks();
// DrmInfo, if any, has been resolved by now.
synchronized (mDrmLock) {
mDrmInfoResolved = true;
}
}
private native void _prepare() throws IOException, IllegalStateException;
/**
* Prepares the player for playback, asynchronously.
*
* After setting the datasource and the display surface, you need to either
* call prepare() or prepareAsync(). For streams, you should call prepareAsync(),
* which returns immediately, rather than blocking until enough data has been
* buffered.
*
* @throws IllegalStateException if it is called in an invalid state
*/
public native void prepareAsync() throws IllegalStateException;
prepare和prepareAsync均调用Native层方法,本篇不做分析。
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
setDataSource(path, null, null);
}
private void setDataSource(String path, String[] keys, String[] values,
List<HttpCookie> cookies)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final Uri uri = Uri.parse(path);
final String scheme = uri.getScheme();
if ("file".equals(scheme)) {
path = uri.getPath();
} else if (scheme != null) {
// handle non-file sources
nativeSetDataSource(
MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),
path,
keys,
values);
return;
}
final File file = new File(path);
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
FileDescriptor fd = is.getFD();
setDataSource(fd);
is.close();
} else {
throw new IOException("setDataSource failed.");
}
}
private native void nativeSetDataSource(
IBinder httpServiceBinder, String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
setDataSource的逻辑也很简单,最后调用原生的方法,看来原生的逻辑是绕不过去了。
public void start() throws IllegalStateException {
//FIXME use lambda to pass startImpl to superclass
final int delay = getStartDelayMs();
if (delay == 0) {
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
// fail silently for a state exception when it is happening after
// a delayed start, as the player state could have changed between the
// call to start() and the execution of startImpl()
}
}
}.start();
}
}
private void startImpl() {
baseStart();
stayAwake(true);
_start();
}
start方法中主要是调用了基类的baseStart方法,设置weak lock,调用原生的start方法。
void baseStart() {
if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
synchronized (mLock) {
if (isRestricted_sync()) {
playerSetVolume(true/*muting*/,0, 0);
}
}
}
private void updateState(int state) {
final int piid;
synchronized (mLock) {
mState = state;
piid = mPlayerIId;
}
try {
getService().playerEvent(piid, state);
} catch (RemoteException e) {
Log.e(TAG, "Error talking to audio service, "
+ AudioPlaybackConfiguration.toLogFriendlyPlayerState(state)
+ " state will not be tracked for piid=" + piid, e);
}
}
baseStart方法中主要是更新状态为STARTED。
/**
* Pauses playback. Call start() to resume.
*
* @throws IllegalStateException if the internal player engine has not been
* initialized.
*/
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
basePause();
}
void basePause() {
if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED);
}
pause方法中也没有复杂的逻辑,基本和start是对应的。
public void release() {
baseRelease();
stayAwake(false);
updateSurfaceScreenOn();
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
if (mTimeProvider != null) {
mTimeProvider.close();
mTimeProvider = null;
}
synchronized(this) {
mSubtitleDataListenerDisabled = false;
mExtSubtitleDataListener = null;
mExtSubtitleDataHandler = null;
mOnMediaTimeDiscontinuityListener = null;
mOnMediaTimeDiscontinuityHandler = null;
}
// Modular DRM clean up
mOnDrmConfigHelper = null;
mOnDrmInfoHandlerDelegate = null;
mOnDrmPreparedHandlerDelegate = null;
resetDrmState();
_release();
}
private native void _release();
release方法中主要是释放操作。
/*
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app thread.
* We use a weak reference to the original MediaPlayer object so that the native
* code is safe from the object disappearing from underneath it. (This is
* the cookie passed to native_setup().)
*/
private static void postEventFromNative(Object mediaplayer_ref,
int what, int arg1, int arg2, Object obj)
{
final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
if (mp == null) {
return;
}
switch (what) {
case MEDIA_INFO:
if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
new Thread(new Runnable() {
@Override
public void run() {
// this acquires the wakelock if needed, and sets the client side state
mp.start();
}
}).start();
Thread.yield();
}
break;
case MEDIA_DRM_INFO:
// We need to derive mDrmInfo before prepare() returns so processing it here
// before the notification is sent to EventHandler below. EventHandler runs in the
// notification looper so its handleMessage might process the event after prepare()
// has returned.
Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
if (obj instanceof Parcel) {
Parcel parcel = (Parcel)obj;
DrmInfo drmInfo = new DrmInfo(parcel);
synchronized (mp.mDrmLock) {
mp.mDrmInfo = drmInfo;
}
} else {
Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
}
break;
case MEDIA_PREPARED:
// By this time, we've learned about DrmInfo's presence or absence. This is meant
// mainly for prepareAsync() use case. For prepare(), this still can run to a race
// condition b/c MediaPlayerNative releases the prepare() lock before calling notify
// so we also set mDrmInfoResolved in prepare().
synchronized (mp.mDrmLock) {
mp.mDrmInfoResolved = true;
}
break;
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
我们看到有一个方法可以处理来自底层的消息,消息会发送给EventHandler处理。
case MEDIA_PLAYBACK_COMPLETE:
{
mOnCompletionInternalListener.onCompletion(mMediaPlayer);
OnCompletionListener onCompletionListener = mOnCompletionListener;
if (onCompletionListener != null)
onCompletionListener.onCompletion(mMediaPlayer);
}
stayAwake(false);
return;
case MEDIA_STOPPED:
{
TimeProvider timeProvider = mTimeProvider;
if (timeProvider != null) {
timeProvider.onStopped();
}
}
break;
EventHandler部分的逻辑也不复杂,主要是处理方法回调。
从目前看到的内容来看,基本都是触发的Native层操作,Native层才是大头。
网友评论