MediaPlayer源码简析

2023-02-28  本文已影响0人  慎独静思

上一节中我们简单学习了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层才是大头。

上一篇下一篇

猜你喜欢

热点阅读