Android 12(S) MultiMedia Learning(二)MediaPlayer Java

 Android提供了MediaPlayer这样一个简单易用的音视频java播放接口,通过几个接口调用即可实现音视频播放。

源码位置 http://aospxref.com/android-12.0.0_r3/xref/frameworJavaks/base/media/java/android/media/MediaPlayer.java ,可以从这里找到我们想要的播放接口,大致浏览一圈,除了没有倍速播放接口,其他还是比较全的。

好,我们要实现一个最简单的播放器要哪些步骤和哪些接口呢?

复制代码
// 1、创建MediaPlayer对象
    new MediaPlayer
// 2、设置数据源
    setDataSource
// 3、设置播放窗口
    setDisPlay
// 4、准备播放
    prepare/prepareAsync
// 5、开始播放以及其他功能
    start/pause/seekTo/stop
复制代码

1、构造函数

复制代码
    public MediaPlayer() {
        this(AudioSystem.AUDIO_SESSION_ALLOCATE);
    }

    private MediaPlayer(int sessionId) {
        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>();

        AttributionSource attributionSource = AttributionSource.myAttributionSource();
        // set the package name to empty if it was null
        if (attributionSource.getPackageName() == null) {
            attributionSource = attributionSource.withPackageName("");
        }

        /* Native setup requires a weak reference to our object.
         * It's easier to create it here than in C++.
         */
        try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
            native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
        }

        baseRegisterPlayer(sessionId);
    }
复制代码

这里主要做了两件事:

a. 调用baseRegisterPlayer方法获取AudioSevice,并且做一些设定。但是这边有一个疑问,这个sessionId并没有传给native,有什么作用的?

b. 调用JNI方法native_setup

JNI代码位置 http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/media/jni/

复制代码
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                       jobject jAttributionSource)
{
    ALOGV("native_setup");

    Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
    android::content::AttributionSourceState attributionSource;
    attributionSource.readFromParcel(parcel);
    sp<MediaPlayer> mp = new MediaPlayer(attributionSource);
    if (mp == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
        return;
    }

    // create new listener and give it to MediaPlayer
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);

    // Stow our new C++ MediaPlayer in an opaque field in the Java object.
    setMediaPlayer(env, thiz, mp);
}
复制代码

JNI代码中创建了一个native的MediaPlayer对象,并且给这个对象注册了一个Listener,这样native MediaPlayer就可以通过这个listener来调用Java层的postEventFromNative方法

复制代码
// native mediaplayer
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
     // ......
     sp<MediaPlayerListener> listener = mListener;
     listener->notify(msg, ext1, ext2, obj);
}

// JNI
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
        // ......
        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                msg, ext1, ext2, NULL);
        // ......
}

// 在native_init中初始化
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");

// Java mediaplayer
private static void postEventFromNative(Object mediaplayer_ref,
                                                            int what, int arg1, int arg2, Object obj)
复制代码

 

2、setDataSource

这个函数有很多重载版本,用的最多的应该是参数只有uri的版本,最后调用到私有的setDataSource当中,这个方法中会调用nativeSetDataSource方法

复制代码
    public void setDataSource(String path)
            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
        setDataSource(path, null, null);
    }

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    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);
        try (FileInputStream is = new FileInputStream(file)) {
            setDataSource(is.getFD());
        }
    }
复制代码

JNI代码主要调用了native mediaplayer 的 setDataSource方法

复制代码
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
        JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
        jobjectArray keys, jobjectArray values) {

// ......
    status_t opStatus =
        mp->setDataSource(
                httpService,
                pathStr,
                headersVector.size() > 0? &headersVector : NULL);
}
复制代码

 

3、setDisplay

这里比较简单,直接调用JNI函数 _setVideoSurface

复制代码
    public void setDisplay(SurfaceHolder sh) {
        mSurfaceHolder = sh;
        Surface surface;
        if (sh != null) {
            surface = sh.getSurface();
        } else {
            surface = null;
        }
        _setVideoSurface(surface);
        updateSurfaceScreenOn();
    }
复制代码

JNI中主要是调用native mediaplayer的setVideoSurfaceTexture方法,根据注释内容,这个方法的调用需要在setDataSource之后调用

复制代码
static void
setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
{
// .....
    sp<IGraphicBufferProducer> new_st;
    if (jsurface) {
        sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
        if (surface != NULL) {
            new_st = surface->getIGraphicBufferProducer();
            if (new_st == NULL) {
                jniThrowException(env, "java/lang/IllegalArgumentException",
                    "The surface does not have a binding SurfaceTexture!");
                return;
            }
            new_st->incStrong((void*)decVideoSurfaceRef);
        } else {
            jniThrowException(env, "java/lang/IllegalArgumentException",
                    "The surface has been released");
            return;
        }
    }

    env->SetLongField(thiz, fields.surface_texture, (jlong)new_st.get());

    // This will fail if the media player has not been initialized yet. This
    // can be the case if setDisplay() on MediaPlayer.java has been called
    // before setDataSource(). The redundant call to setVideoSurfaceTexture()
    // in prepare/prepareAsync covers for this case.
    mp->setVideoSurfaceTexture(new_st);
}
复制代码

 

4、prepareAsync

这个更简单了,直接调用JNI方法prepareAsync

public native void prepareAsync() throws IllegalStateException;

调用了native mediaplayer的prepareAsync方法

复制代码
static void
android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }

    // Handle the case where the display surface was set before the mp was
    // initialized. We try again to make it stick.
    sp<IGraphicBufferProducer> st = getVideoSurfaceTexture(env, thiz);
    mp->setVideoSurfaceTexture(st);

    process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );
}
复制代码

由于是异步调用,native mediaplayer 的prepare执行结束之后,会发送一个MEDIA_PREPARED,然后执行OnPreparedListener 方法

 

5、start/pause/stop/seekTo

这几个方法比较类似,都是在接口中去调用JNI方法,start调用 _start方法

复制代码
    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(0); // unknown device at this point
        stayAwake(true);
        tryToEnableNativeRoutingCallback();
        _start();
    }

    private native void _start() throws IllegalStateException;
复制代码

JNI中调用native mediaplayer的start方法

复制代码
static void
android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)
{
    ALOGV("start");
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
}
复制代码

 


接下来用这几个接口来写一个最简单的播放器,代码以及效果如下

 

 

复制代码
package com.example.mediaplayertest;

import androidx.appcompat.app.AppCompatActivity;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "MainActivity";

    TextView textView = null;
    Button start = null;
    Button pause = null;
    Button resume = null;
    Button stop = null;
    SurfaceView surfaceView = null;
    String uri = null;
    MediaPlayer player = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        start = findViewById(R.id.btn_play);
        pause = findViewById(R.id.btn_pause);
        resume = findViewById(R.id.btn_resume);
        stop = findViewById(R.id.btn_stop);
        textView = findViewById(R.id.textView);
        surfaceView = findViewById(R.id.surfaceView);

        start.setOnClickListener(clickListener);
        pause.setOnClickListener(clickListener);
        resume.setOnClickListener(clickListener);
        stop.setOnClickListener(clickListener);

    }

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_play:
                    Play();
                    break;
                case R.id.btn_pause:
                    Pause();
                    break;
                case R.id.btn_resume:
                    Resume();
                    break;
                case R.id.btn_stop:
                    Stop();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        if (player != null && player.isPlaying())
        {
            player.stop();
            player.release();
            player = null;
        }
    }

    private void Play()
    {
        uri = textView.getText().toString();
        player = new MediaPlayer();
        player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.start();
            }
        });
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mediaPlayer.reset();
                start.setEnabled(true);
            }
        });
        player.setDisplay(surfaceView.getHolder());
        try {
            player.setDataSource(uri);
        } catch (IOException e) {
            e.printStackTrace();
        }
        player.prepareAsync();
        start.setEnabled(false);
    }

    private void Pause()
    {
        if(player != null && player.isPlaying())
            player.pause();
    }

    private void Resume()
    {
        if (player != null)
            player.start();
    }

    private void Stop()
    {
        if (player != null)
        {
            player.stop();
            player.release();
            player = null;
        }
        start.setEnabled(true);
    }
}
复制代码

 

posted @   青山渺渺  阅读(715)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示