Saturday, 19 January 2019

How to have similar mechanism of center-crop on ExoPlayer's PlayerView , but not on the center?

Background

We record a video of the user's face, and usually the face is located at the upper half of the video.

Later we wish to view the video, but the aspect ratio of the PlayerView might be different than the one of the video, so there needs to be some scaling and cropping.

The problem

The only way I've found to scale the PlayerView so that it will be shown in the entire space it has but keeping the aspect ratio (which will result in cropping, of course) , is by using app:resize_mode="zoom" . But this is only for the center, meaning it takes a point of 0.5x0.5 of the video, and scale-crops from that point. This causes many cases of losing the important content of the video.

For example, if we have a video that was taken in portrait, and we have a square PlayerView, this is the part that will be visible:

PlayerView

What I've tried

I've tried searching over the Internet, StackOverflow (here) and on Github, but I couldn't find how to do it. The only clue I've found is about AspectRatioFrameLayout and AspectRatioTextureView, but I didn't find how to use them for this task, if it's even possible.

EDIT: I was told (here) that I should use a normal TextureView , and provide it directly to SimpleExoPlayer using SimpleExoPlayer.setVideoTextureView. And to set a special transformation to it using TextureView.setTransform , but what exactly should I give it? How do I set it to be on the top-center-horizontal position, or any other position that's related to the video?

I tried this, but the video doesn't fill the view itself. Based on some example of how to do it for ImageView (here), this is what I did : Here's what I tried (full sample project available here) :

class MainActivity : AppCompatActivity() {
    private var player: SimpleExoPlayer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        window.setBackgroundDrawable(ColorDrawable(0xff000000.toInt()))
        super.onCreate(savedInstanceState)
        if (cache == null) {
            cache = SimpleCache(File(cacheDir, "media"), LeastRecentlyUsedCacheEvictor(MAX_PREVIEW_CACHE_SIZE_IN_BYTES))
        }
        setContentView(R.layout.activity_main)
    }

    override fun onStart() {
        super.onStart()
        playVideo()
    }

    private fun playVideo() {
        player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
        player!!.setVideoTextureView(textureView)
//        player!!.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
//        playerView.player = player
        player!!.addVideoListener(object : VideoListener {
            override fun onRenderedFirstFrame() {
                player!!.removeVideoListener(this)
                val videoWidth = if (player!!.videoFormat.rotationDegrees % 180 == 0) player!!.videoFormat.width else player!!.videoFormat.height
                val videoHeight = if (player!!.videoFormat.rotationDegrees % 180 == 0) player!!.videoFormat.height else player!!.videoFormat.width
                if (videoWidth <= 0 || videoHeight <= 0) {
                    //TODO revert to center-crop . This doesn't work :
                    //                player!!.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
                } else {
                    val viewWidth = textureView.width - textureView.paddingRight - textureView.paddingLeft
                    val viewHeight = textureView.height - textureView.paddingTop - textureView.paddingBottom

                    val scale = if (videoWidth * viewHeight > videoHeight * viewWidth)
                        viewHeight.toFloat() / videoHeight.toFloat()
                    else
                        viewWidth.toFloat() / videoWidth.toFloat()
                    val matrix = Matrix()
                    val pivotPointX = viewWidth / 2f
                    val pivotPointY = 0f //viewHeight / 2f
                    matrix.setScale(scale, scale, pivotPointX, pivotPointY)
                    textureView.setTransform(matrix)
                    Log.d("appLog", "onRenderedFirstFrame video: $videoWidth,$videoHeight into $viewWidth,$viewHeight scale:$scale -> ${videoWidth * scale},${videoHeight * scale}")
                }
            }
        })
        player!!.volume = 0f
        player!!.repeatMode = Player.REPEAT_MODE_ALL
        player!!.playRawVideo(this, R.raw.test)
        player!!.playWhenReady = true
//        player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/240/big_buck_bunny_240p_20mb.mkv", cache!!)
//        player!!.playVideoFromUrl(this, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv", cache!!)
//        player!!.playVideoFromUrl(this@MainActivity, "https://sample-videos.com/video123/mkv/720/big_buck_bunny_720p_1mb.mkv")
    }

    override fun onStop() {
        super.onStop()
        player!!.setVideoTextureView(null)
//        playerView.player = null
        player!!.release()
        player = null
    }

    companion object {
        const val MAX_PREVIEW_CACHE_SIZE_IN_BYTES = 20L * 1024L * 1024L
        var cache: com.google.android.exoplayer2.upstream.cache.Cache? = null

        @JvmStatic
        fun getUserAgent(context: Context): String {
            val packageManager = context.packageManager
            val info = packageManager.getPackageInfo(context.packageName, 0)
            val appName = info.applicationInfo.loadLabel(packageManager).toString()
            return Util.getUserAgent(context, appName)
        }
    }

    fun SimpleExoPlayer.playRawVideo(context: Context, @RawRes rawVideoRes: Int) {
        val dataSpec = DataSpec(RawResourceDataSource.buildRawResourceUri(rawVideoRes))
        val rawResourceDataSource = RawResourceDataSource(context)
        rawResourceDataSource.open(dataSpec)
        val factory: DataSource.Factory = DataSource.Factory { rawResourceDataSource }
        prepare(LoopingMediaSource(ExtractorMediaSource.Factory(factory).createMediaSource(rawResourceDataSource.uri)))
    }

    fun SimpleExoPlayer.playVideoFromUrl(context: Context, url: String, cache: Cache? = null) = playVideoFromUri(context, Uri.parse(url), cache)

    fun SimpleExoPlayer.playVideoFile(context: Context, file: File) = playVideoFromUri(context, Uri.fromFile(file))

    fun SimpleExoPlayer.playVideoFromUri(context: Context, uri: Uri, cache: Cache? = null) {
        val factory = if (cache != null)
            CacheDataSourceFactory(cache, DefaultHttpDataSourceFactory(getUserAgent(context)))
        else
            DefaultDataSourceFactory(context, MainActivity.getUserAgent(context))
        val mediaSource = ExtractorMediaSource.Factory(factory).createMediaSource(uri)
        prepare(mediaSource)
    }
}

This is what I get :

enter image description here

The questions

  1. Is there a way to specify the position on the video resolution (in percentage, preferably) to start the scale-cropping? For example, in the case of top-center-horizontal, it would be 0.5x0 (0.5 is the center of horizontally, and 0 for top vertically).

  2. Is it possible to at least do it from the top?

  3. What is wrong with what I wrote? What should be changed?



from How to have similar mechanism of center-crop on ExoPlayer's PlayerView , but not on the center?

No comments:

Post a Comment