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:
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()
}
}