美文网首页
MediaPlayer源码简析

MediaPlayer源码简析

作者: 慎独静思 | 来源:发表于2023-02-28 23:03 被阅读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层才是大头。

    相关文章

      网友评论

          本文标题:MediaPlayer源码简析

          本文链接:https://www.haomeiwen.com/subject/eacrldtx.html