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); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】