androidsurfaceviewexoplayertextureview

Play same video on multiple views on Android


I have one player (in my case ExoPlayer) and I have one video URL. I want to play this video into multiple views at the same time as shown in the screenshot below. Here are some notes:

enter image description here


Solution

  • As you probably know the player can only render to a single surface (as mentioned in the issue).

    A relatively simple way to overcome this limitation is to have the player render to one of the views and copy the frames to the rest of the views (assuming they all have equal size).

    And in order to know when each frame is actually drawn we can extend the MediaCodecVideoRenderer.

    Here's an example:

    private fun setupPlayer() {
        player = ExoPlayer.Builder(context)
            .setRenderersFactory(ReplicatingRendererFactory(context))
            .build()
    
        player.setMediaItem(MediaItem.fromUri(videoUri))
    }
    
    private fun setupViews() {
        val textureView1 = findViewById<TextureView>(R.id.texture_view_1)
        val textureView2 = findViewById<TextureView>(R.id.texture_view_2)
        val textureView3 = findViewById<TextureView>(R.id.texture_view_3)
        val textureView4 = findViewById<TextureView>(R.id.texture_view_4)
    
        sourceView = textureView1
        destinationViews = listOf(textureView2, textureView2, textureView3, textureView4)
    
        player.setVideoTextureView(textureView1)
        player.prepare()
        player.playWhenReady = true
    }
    
    private fun copyFrame() {
        val bitmap = sourceView.bitmap ?: return
        destinationViews.forEach { view ->
            check(view.width == sourceView.width && view.height == sourceView.height) {
                "Source and destination views must have equal size."
            }
            val canvas = view.lockCanvas() ?: return@forEach
            canvas.drawBitmap(bitmap, 0f, 0f, paint)
            view.unlockCanvasAndPost(canvas)
        }
        bitmap.recycle()
    }
    
    inner class ReplicatingRendererFactory(
        context: Context
    ): DefaultRenderersFactory(context) {
        override fun buildVideoRenderers(
            context: Context,
            extensionRendererMode: Int,
            mediaCodecSelector: MediaCodecSelector,
            enableDecoderFallback: Boolean,
            eventHandler: Handler,
            eventListener: VideoRendererEventListener,
            allowedVideoJoiningTimeMs: Long,
            out: ArrayList<Renderer>
        ) {
            out.add(ReplicatingRenderer(context, mediaCodecSelector))
            // This results in creating 2 video renderers. You are free to optimize.
            super.buildVideoRenderers(
                context,
                extensionRendererMode,
                mediaCodecSelector,
                enableDecoderFallback,
                eventHandler,
                eventListener,
                allowedVideoJoiningTimeMs,
                out
            )
        }
    }
    
    inner class ReplicatingRenderer(
        context: Context,
        mediaCodecSelector: MediaCodecSelector
    ) : MediaCodecVideoRenderer(context, mediaCodecSelector) {
        override fun onProcessedOutputBuffer(presentationTimeUs: Long) {
            super.onProcessedOutputBuffer(presentationTimeUs)
            copyFrame()
        }
    }