ijk Android demo源码的整体结构如下
demo
ijkplayer-example是demo程序的主module,它依赖其它module,并实现一个简单的播放器程序
ijkplayer-java 是ijk库的Java实现代码,它的作用有三个
1、加载ijk的so
2、实现对ijk so的jni调用封装
3、封装IjkMediaPlayer供调用者直接使用
ijkplayer-exo 提供了一个使用 google exoplayer的实现封装IjkExoMediaPlayer,IjkExoMediaPlayer的实现继承了ijkplayer-java中的抽象类AbstractMediaPlayer,在ijkplayer-example调用的时候可与IjkMediaPlayer保持一致。
ijkplayer-arm64等,这类module中存放了与各芯片架构对应c源文件与编译ijk之后生成的so等,无Java实现,ijkplayer-example引入对应的moudle,可将该module所包含的so编译进最终的apk中。
一、ijkplayer-example
ijkplayer-example中的Java代码如下
example/
├── activities
│ ├── FileExplorerActivity.java
│ ├── RecentMediaActivity.java
│ ├── SampleMediaActivity.java
│ ├── SettingsActivity.java
│ └── VideoActivity.java
├── application
│ ├── AppActivity.java
│ └── Settings.java
├── content
│ ├── PathCursor.java
│ ├── PathCursorLoader.java
│ └── RecentMediaStorage.java
├── eventbus
│ └── FileExplorerEvents.java
├── fragments
│ ├── FileListFragment.java
│ ├── RecentMediaListFragment.java
│ ├── SampleMediaListFragment.java
│ ├── SettingsFragment.java
│ └── TracksFragment.java
├── services
│ └── MediaPlayerService.java
└── widget
├── media
│ ├── AndroidMediaController.java
│ ├── FileMediaDataSource.java
│ ├── IMediaController.java
│ ├── IRenderView.java
│ ├── IjkVideoView.java
│ ├── InfoHudViewHolder.java
│ ├── MeasureHelper.java
│ ├── MediaPlayerCompat.java
│ ├── SurfaceRenderView.java
│ ├── TableLayoutBinder.java
│ └── TextureRenderView.java
└── preference
└── IjkListPreference.java
播放器的实现在VideoActivity中,其余的Activity都是用来配合VideoActivity的
FileExplorerActivity 文件浏览器,可选择本机视频播放
RecentMediaActivity 记录最近的播放信息
SampleMediaActivity 提供了示例视频url,可直接播放
SettingsActivity 播放器参数设置
VideoActivity 播放器
二、如何使用ijkplayer-java封装的IjkMediaPlayer
Android系统播放器的使用是MediaPlayer + Surface,Surface可以通过SurfaceView或TextureView获取。
ijkplayer-example中封装了一个类IjkVideoView,IjkVideoView中演示了三种播放器实现的调用
IjkExoMediaPlayer在Ijkplayer-exo中对google exoplayer的调用封装
AndroidMediaPlayer对android系统播放器MediaPlayer的调用封装
IjkMediaPlayer在Ijkplayer-java中对ffmpeg的调用封装
1、创建IMediaPlayer
在使用ijk之前先加载so
IjkMediaPlayer.loadLibrariesOnce(null);
IjkMediaPlayer.native_profileBegin("libijkplayer.so");
IjkExoMediaPlayer、AndroidMediaPlayer、IjkMediaPlayer都实现了接口IMediaPlayer
public IMediaPlayer createPlayer(int playerType) {
IMediaPlayer mediaPlayer = null;
switch (playerType) {
case Settings.PV_PLAYER__IjkExoMediaPlayer: {
IjkExoMediaPlayer IjkExoMediaPlayer = new IjkExoMediaPlayer(mAppContext);
mediaPlayer = IjkExoMediaPlayer;
}
break;
case Settings.PV_PLAYER__AndroidMediaPlayer: {
AndroidMediaPlayer androidMediaPlayer = new AndroidMediaPlayer();
mediaPlayer = androidMediaPlayer;
}
break;
case Settings.PV_PLAYER__IjkMediaPlayer:
default: {
IjkMediaPlayer ijkMediaPlayer = null;
if (mUri != null) {
ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
if (mSettings.getUsingMediaCodec()) {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
if (mSettings.getUsingMediaCodecAutoRotate()) {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
} else {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);
}
if (mSettings.getMediaCodecHandleResolutionChange()) {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1);
} else {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);
}
} else {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
}
if (mSettings.getUsingOpenSLES()) {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
} else {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);
}
String pixelFormat = mSettings.getPixelFormat();
if (TextUtils.isEmpty(pixelFormat)) {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
} else {
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", pixelFormat);
}
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
}
mediaPlayer = ijkMediaPlayer;
}
break;
}
...
return mediaPlayer;
}
上面的代码根据设置界面的选择,可以创建对应类型的播放器,这里先忽略IjkExoMediaPlayer和AndroidMediaPlayer,重点关注IjkMediaPlayer。
2、创建Surface
Surface可以通过SurfaceView或TextureView获取,在ijkplayer-example中封装了一个接口IRenderView用于将SurfaceView和TextureView的实现和使用统一。
IRenderView
public interface IRenderView {
int AR_ASPECT_FIT_PARENT = 0; // without clip
int AR_ASPECT_FILL_PARENT = 1; // may clip
int AR_ASPECT_WRAP_CONTENT = 2;
int AR_MATCH_PARENT = 3;
int AR_16_9_FIT_PARENT = 4;
int AR_4_3_FIT_PARENT = 5;
View getView();
boolean shouldWaitForResize();
void setVideoSize(int videoWidth, int videoHeight);
void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen);
void setVideoRotation(int degree);
void setAspectRatio(int aspectRatio);
void addRenderCallback(@NonNull IRenderCallback callback);
void removeRenderCallback(@NonNull IRenderCallback callback);
interface ISurfaceHolder {
void bindToMediaPlayer(IMediaPlayer mp);
@NonNull
IRenderView getRenderView();
@Nullable
SurfaceHolder getSurfaceHolder();
@Nullable
Surface openSurface();
@Nullable
SurfaceTexture getSurfaceTexture();
}
interface IRenderCallback {
/**
* @param holder
* @param width could be 0
* @param height could be 0
*/
void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height);
/**
* @param holder
* @param format could be 0
* @param width
* @param height
*/
void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height);
void onSurfaceDestroyed(@NonNull ISurfaceHolder holder);
}
}
TextureRenderView
public class TextureRenderView extends TextureView implements IRenderView {
...
}
SurfaceRenderView
public class SurfaceRenderView extends SurfaceView implements IRenderView {
...
}
这里略过了SurfaceRenderView和TextureRenderView的内部实现,它们的目的只有一个,用于生成Surface,供播放器渲染画面。
根据设置界面的选择创建SurfaceView或TextureView
public void setRender(int render) {
switch (render) {
case RENDER_NONE:
setRenderView(null);
break;
case RENDER_TEXTURE_VIEW: {
TextureRenderView renderView = new TextureRenderView(getContext());
if (mMediaPlayer != null) {
renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer);
renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());
renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen());
renderView.setAspectRatio(mCurrentAspectRatio);
}
setRenderView(renderView);
break;
}
case RENDER_SURFACE_VIEW: {
SurfaceRenderView renderView = new SurfaceRenderView(getContext());
setRenderView(renderView);
break;
}
default:
Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render));
break;
}
}
3、打开视频
private void setVideoURI(Uri uri, Map headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
private void openVideo() {
if (mUri == null || mSurfaceHolder == null) {
// 视频地址无效或Surface还未创建完成
return;
}
release(false);
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
// 获取音频焦点
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
try {
mMediaPlayer = createPlayer(mSettings.getPlayer());
final Context context = getContext();
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnInfoListener(mInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
mMediaPlayer.setOnTimedTextListener(mOnTimedTextListener);
mCurrentBufferPercentage = 0;
String scheme = mUri.getScheme();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
mSettings.getUsingMediaDataSource() &&
(TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
mMediaPlayer.setDataSource(dataSource);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
} else {
mMediaPlayer.setDataSource(mUri.toString());
}
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setScreenOnWhilePlaying(true);
mPrepareStartTime = System.currentTimeMillis();
mMediaPlayer.prepareAsync();
if (mHudViewHolder != null)
mHudViewHolder.setMediaPlayer(mMediaPlayer);
mCurrentState = STATE_PREPARING;
attachMediaController();
} catch (IOException ex) {
Log.w(TAG, "Unable to open content: " + mUri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
} catch (IllegalArgumentException ex) {
Log.w(TAG, "Unable to open content: " + mUri, ex);
mCurrentState = STATE_ERROR;
mTargetState = STATE_ERROR;
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
} finally {
// REMOVED: mPendingSubtitleTracks.clear();
}
}
从openVideo中可以看到,IJKMediaPlayer的调用大致分为下面几个部分
设置监听 mMediaPlayer.setOnPreparedListener 等
设置视频源 mMediaPlayer.setDataSource(mUri.toString())
开始加载 mMediaPlayer.prepareAsync()
经过IJKMediaPlayer的封装之后,其调用方法基本与系统播放器MediaPlayer保持一致。
4、响应播放器状态变化
通过上面设置的监听,可以实时监听播放器的状态变化
mMediaPlayer.setOnPreparedListener(mPreparedListener)
监听视频加载,回调视频加载完成状态,需要在这里调用播放器的start方法
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener)
监听视频尺寸变化,在onVideoSizeChanged中可获取视频的宽高信息,根据视频的宽高信息需要设置SurfaceView或TextureView的大小
mMediaPlayer.setOnCompletionListener(mCompletionListener)
视频播放完成监听,需要释放播放器
mMediaPlayer.setOnErrorListener(mErrorListener)
视频加载失败监听,需要释放播放器
mMediaPlayer.setOnInfoListener(mInfoListener)
通常用来做视频缓冲监听,701表示开始缓冲,702表示缓冲完成
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener)
缓冲进度监听
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener)
seek状态监听,通过需要做seek保护,在上次seek未完成之前,不允许做新的seek操作
关于IJKMediaPlayer,需要特殊关注的是setOnInfoListener中回调的信息,
IJKMediaPlayer回调了比系统播放器更多的播放器信息,从demo中可以看到
private IMediaPlayer.OnInfoListener mInfoListener =
new IMediaPlayer.OnInfoListener() {
public boolean onInfo(IMediaPlayer mp, int arg1, int arg2) {
if (mOnInfoListener != null) {
mOnInfoListener.onInfo(mp, arg1, arg2);
}
switch (arg1) {
case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:");
break;
case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:");
break;
case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
Log.d(TAG, "MEDIA_INFO_BUFFERING_START:");
break;
case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
Log.d(TAG, "MEDIA_INFO_BUFFERING_END:");
break;
case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2);
break;
case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:");
break;
case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:");
break;
case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:");
break;
case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:");
break;
case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:");
break;
case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
mVideoRotationDegree = arg2;
Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2);
if (mRenderView != null)
mRenderView.setVideoRotation(arg2);
break;
case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:");
break;
}
return true;
}
};
以上就是IJKMediaPlayer关于视频播放的实现。