You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

实现步骤:

  1. 自定义GifRenderer:继承BaseRenderer,实现Renderer接口,使用GIF解析库(如android-gif-drawable)读取GIF帧数据。
  2. 管理帧时序:通过ExoPlayer的时钟控制GIF帧的播放间隔,确保动画流畅。
  3. 注册自定义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))
        }
    }
    
  4. 配置MediaItem:设置GIF的MIME类型为"image/gif",让ExoPlayer路由到自定义GifRenderer处理。

注意事项:

  • 需处理GIF的暂停、播放、进度跳转等Player控制逻辑,确保与ExoPlayer的状态同步。
  • 优化GIF帧的内存占用,避免频繁创建Bitmap导致OOM。

内容的提问来源于stack exchange,提问作者chadewik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:22:04