我来详细梳理 GSYVideoPlayer 播放视频的完整流程:
GSYVideoPlayer 视频播放流程详解
1. 启动播放入口
用户点击播放 → startPlayLogic() → prepareVideo()
关键方法链:
startButtonLogic()- 点击开始按钮startPlayLogic()- 开始播放逻辑prepareVideo()- 准备视频startPrepare()- 开始准备(真正的入口)
2. startPrepare() 阶段
// GSYVideoView.startPrepare()
1. getGSYVideoManager().listener().onCompletion() // 通知上一个播放完成
2. mVideoAllCallBack.onStartPrepared() // 回调:开始准备
3. getGSYVideoManager().setListener(this) // 设置监听器
4. getGSYVideoManager().prepare(url, headers...) // ★ 核心:调用 prepare
5. setStateAndUi(CURRENT_STATE_PREPAREING) // 设置状态为"准备中"
3. GSYVideoManager.prepare() 阶段
// GSYVideoBaseManager.prepare()
1. 构建 GSYModel 对象(包含 url, headers, loop, speed 等)
2. 发送 Message: msg.what = HANDLER_PREPARE
3. Handler 接收消息 → initVideo(msg)
4. initVideo() - 播放器初始化
// GSYVideoBaseManager.initVideo()
1. playerManager = getPlayManager() // 获取播放器管理器(IjkPlayerManager)
2. cacheManager = getCacheManager() // 获取缓存管理器
3. playerManager.initVideoPlayer(context, msg, options, cacheManager) // ★ 初始化播放器
4. mediaPlayer.setOnXxxListener(this) // 设置各种监听器
5. mediaPlayer.prepareAsync() // ★ 异步准备
5. IjkPlayerManager.initVideoPlayer() - IJK 播放器创建
// IjkPlayerManager.initVideoPlayer()
1. mediaPlayer = new IjkMediaPlayer() // 创建 IJK 播放器实例
2. mediaPlayer.setAudioStreamType(STREAM_MUSIC)
// 设置硬解码选项
3. if (GSYVideoType.isMediaCodec()) {
mediaPlayer.setOption("mediacodec", 1)
mediaPlayer.setOption("mediacodec-auto-rotate", 1)
mediaPlayer.setOption("mediacodec-handle-resolution-change", 1)
}
// 设置数据源
4. mediaPlayer.setDataSource(url, headers) // 或处理缓存
// 其他设置
5. mediaPlayer.setLooping(loop)
6. mediaPlayer.setSpeed(speed)
7. initIJKOption(mediaPlayer, optionModelList) // 应用自定义选项
// ★ 回调通知
8. initSuccess(gsyModel) → mPlayerInitSuccessListener.onPlayerInitSuccess()
这里是我们修复的关键点:在 initSuccess() 回调中设置 Surface,此时 MediaPlayer 已创建但还未 prepareAsync。
6. IjkMediaPlayer.prepareAsync() - Native 层准备
Java: prepareAsync()
↓
JNI: _prepareAsync()
↓
C: ijkmp_prepare_async()
↓
C: ffp_prepare_async_l()
↓
创建解码管道 (Pipeline)
Native 层流程:
// ff_ffplay.c
ffp_prepare_async_l()
├── stream_open() // 打开流
├── read_thread() // 读取线程
│ ├── avformat_open_input() // 打开输入
│ ├── avformat_find_stream_info() // 查找流信息
│ └── stream_component_open() // 打开各个流组件
│ ├── audio_thread() // 音频解码线程
│ └── video_thread() // 视频解码线程
│ └── ffpipenode_run_sync() // 解码节点
7. MediaCodec 创建 - 关键的 Surface 检查
// ffpipenode_android_mediacodec_vdec.c
func_run_sync()
├── create_codec_l() // ★ 创建 MediaCodec
│ ├── 检查 jsurface 是否为 NULL
│ │ ├── NULL → 创建 SDL_AMediaCodecDummy(占位符)
│ │ └── 有效 → 创建真实 MediaCodec
│ └── AMediaCodec_configure(codec, format, surface)
这就是黑屏问题的根源:如果此时 Surface 为 null,会创建 Dummy Codec。
8. TextureView 创建流程
onPrepared() 回调
↓
GSYVideoView.onPrepared()
↓
addTextureView() // ★ 此时才添加 TextureView
↓
TextureView.onSurfaceAvailable()
↓
GSYTextureRenderView.onSurfaceAvailable()
↓
pauseLogic(surface, true)
↓
mSurface = surface
setDisplay(mSurface) // 设置 Surface 到播放器
9. Surface 设置到播放器
// GSYVideoView.setDisplay()
getGSYVideoManager().setDisplay(surface)
↓
// GSYVideoBaseManager.setDisplay()
Message msg (HANDLER_SETDISPLAY)
showDisplay(msg)
↓
// IjkPlayerManager.showDisplay()
if (mediaPlayer != null && holder.isValid()) {
mediaPlayer.setSurface(holder) // ★ 设置到 IjkMediaPlayer
}
10. Native 层 Surface 设置
Java: setSurface(surface)
↓
JNI: _setVideoSurface()
↓
C: ijkmp_android_set_surface()
↓
C: ffpipeline_set_surface()
↓
检查是否需要重新配置 MediaCodec
↓
如果之前是 Dummy → "quirk: reconfigure with new codec" → 黑屏
11. 视频渲染流程
解码线程输出帧
↓
video_refresh_thread() // 视频刷新线程
↓
video_display2() // 显示视频帧
↓
SDL_VoutDisplayYUVOverlay() // 输出到 Surface
↓
ANativeWindow_lock/unlock // 写入 Native Window
↓
屏幕显示
12. 时序图总结
┌─────────────────────────────────────────────────────────────────────────────┐
│ GSYVideoPlayer 播放时序 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 用户点击播放 │
│ │ │
│ ▼ │
│ startPrepare() │
│ │ │
│ ├──► GSYVideoManager.prepare() │
│ │ │ │
│ │ ▼ │
│ │ initVideo() │
│ │ │ │
│ │ ├──► IjkPlayerManager.initVideoPlayer() │
│ │ │ │ │
│ │ │ ├──► new IjkMediaPlayer() │
│ │ │ ├──► setDataSource() │
│ │ │ ├──► setOptions() │
│ │ │ └──► initSuccess() ──► ★ 我们在这里设置 Surface │
│ │ │ │
│ │ └──► prepareAsync() ──────────────────────┐ │
│ │ │ │
│ │ ┌─────────── Native 层 ───────────────┤ │
│ │ │ │ │
│ │ │ ffp_prepare_async_l() │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ stream_open() │ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ video_thread() ─► create_codec_l() │ │
│ │ │ │ │ │ │
│ │ │ │ 检查 Surface 是否存在 │ │
│ │ │ │ │ │ │
│ │ │ │ ├─ 有 → 创建真实 Codec │ │
│ │ │ │ └─ 无 → Dummy Codec │ │
│ │ │ │ │ │
│ │ └───────┼─────────────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ onPrepared() 回调 │
│ │ │ │
│ │ ▼ │
│ │ addTextureView() ──► TextureView 创建 │
│ │ │ │
│ │ ▼ │
│ │ onSurfaceAvailable() ──► setDisplay(surface) │
│ │ │ │
│ │ ▼ │
│ │ 如果之前是 Dummy ──► reconfigure ──► 短暂黑屏 │
│ │ │ │
│ │ ▼ │
│ └──────────────► 开始渲染视频帧 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
我们的修复方案
★ 利用 IPlayerInitSuccessListener 回调
│
│ 时机:MediaPlayer 创建后,prepareAsync() 之前
│
▼
onPlayerInitSuccess(player, model)
│
├── 检查 mSurface 是否存在(重复播放时会存在)
│
└── 存在 → player.setSurface(mSurface)
│
▼
Native 层 create_codec_l() 时 Surface 已存在
│
▼
直接创建真实 MediaCodec,无需 Dummy
│
▼
无需 reconfigure,无黑屏 ✓