Android中使用ExoPlayer在Surface上显示图片的问题排查
解决ExoPlayer 1.4.1显示图片及扩展GIF播放问题
一、图片显示失败的修复方案
你的代码核心问题在于手动处理Surface生命周期时的潜在冲突,以及未确保图片播放的持续显示逻辑。以下是修复后的完整代码:
class ImagePlayerActivity : ComponentActivity() { val TAG = "ImagePlayerActivity" private lateinit var exoPlayer: ExoPlayer @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 明确启用图片渲染器,确保ExoPlayer支持图片播放 val renderersFactory = DefaultRenderersFactory(this) .setEnableImageRenderers(true) exoPlayer = ExoPlayer.Builder(this) .setRenderersFactory(renderersFactory) .build() // 设置重复模式为单次循环,避免图片播放一次后立即结束 exoPlayer.repeatMode = Player.REPEAT_MODE_ONE exoPlayer.addListener(playerListener) setContent { ExoplayerdiydemoTheme { Surface(modifier = Modifier.fillMaxSize()) { PlayerViewCompose(exoPlayer) } } } // 提前配置MediaSource,避免在Surface回调中延迟初始化 val mediaItem = MediaItem.Builder() .setUri(Uri.parse("android.resource://$packageName/${R.raw.textimage}")) .setMimeType(IMAGE_JPEG) .setImageDurationMs(60 * 1000L) .build() val dataSourceFactory = DefaultDataSource.Factory(this) val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(mediaItem) exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() exoPlayer.play() } @Composable fun PlayerViewCompose(player: ExoPlayer) { // 使用官方PlayerView替代自定义SurfaceView,自动处理Surface生命周期 AndroidView( factory = { context -> PlayerView(context).apply { this.player = player layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // 适配图片显示,保持宽高比 resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT } }, modifier = Modifier.fillMaxSize() ) } private val playerListener = object : Player.Listener { override fun onPlaybackStateChanged(playbackState: Int) { Log.d(TAG, "onPlaybackStateChanged: $playbackState") // 可在此处监控状态:STATE_READY表示准备就绪,STATE_ENDED表示播放结束 } override fun onPlayerError(error: PlaybackException) { Log.d(TAG, "onPlayerError: error code ${error.errorCode} message ${error.message}") } } override fun onDestroy() { super.onDestroy() // 在Activity销毁时释放Player,避免内存泄漏 exoPlayer.release() } }
关键修复点:
- 明确启用图片渲染器:通过
DefaultRenderersFactory.setEnableImageRenderers(true)确保ExoPlayer加载ImageRenderer,避免版本差异导致的默认配置问题。 - 使用官方PlayerView:PlayerView已封装Surface生命周期管理、Player绑定等逻辑,避免手动处理Surface时的时机错误(如Surface未完全初始化就绑定Player)。
- 设置重复模式:
player.repeatMode = Player.REPEAT_MODE_ONE让图片持续显示,不会因单次播放结束进入STATE_ENDED状态导致画面消失。 - 提前初始化MediaSource:将MediaSource配置移到onCreate中,避免Surface回调中的延迟初始化导致播放时序问题。
- 添加Player释放逻辑:在Activity销毁时调用
player.release(),避免内存泄漏。
二、扩展支持GIF播放
ExoPlayer 1.4.1默认不支持GIF格式,需通过自定义Renderer实现,核心思路是解析GIF帧并逐帧渲染到Surface:
实现步骤:
- 自定义GifRenderer:继承
BaseRenderer,实现Renderer接口,使用GIF解析库(如android-gif-drawable)读取GIF帧数据。 - 管理帧时序:通过ExoPlayer的时钟控制GIF帧的播放间隔,确保动画流畅。
- 注册自定义Renderer:在创建ExoPlayer时,将GifRenderer添加到RenderersFactory中:
val renderersFactory = object : DefaultRenderersFactory(this) { override fun createRenderers( context: Context, extensionRendererMode: Int, mediaCodecSelector: MediaCodecSelector, drmSessionManager: DrmSessionManager<FrameworkMediaCrypto>?, playClearSamplesWithoutKeys: Boolean, enableDecoderFallback: Boolean ): Array<Renderer> { val defaultRenderers = super.createRenderers( context, extensionRendererMode, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, enableDecoderFallback ) // 添加自定义GIF渲染器 return defaultRenderers.plus(GifRenderer(context)) } } - 配置MediaItem:设置GIF的MIME类型为
"image/gif",让ExoPlayer路由到自定义GifRenderer处理。
注意事项:
- 需处理GIF的暂停、播放、进度跳转等Player控制逻辑,确保与ExoPlayer的状态同步。
- 优化GIF帧的内存占用,避免频繁创建Bitmap导致OOM。
内容的提问来源于stack exchange,提问作者chadewik
相关产品推荐
相关产品推荐

