android - 如何在 ExoPlayer 的 PlayerView 上有类似的 center-crop 机制,但不在中心?

标签 android scale crop exoplayer

背景

我们录制用户人脸的视频,通常人脸位于视频的上半部分。

后来我们想看视频,但是PlayerView的纵横比可能与视频不同,因此需要进行一些缩放和裁剪。

问题

我发现缩放 PlayerView 的唯一方法这样它就会显示在它拥有的整个空间中,但要保持纵横比(当然,这会在需要时进行裁剪),是通过使用 app:resize_mode="zoom" .以下是它如何与 center-crop 一起使用的示例:http://s000.tinyupload.com/?file_id=00574047057406286563 .显示内容的 View 具有相似的纵横比越多,需要的裁剪就越少。

但这仅适用于中心,这意味着它需要视频的 0.5x0.5 点,并从该点进行缩放。这导致了许多丢失视频重要内容的情况。

例如,如果我们有一个纵向拍摄的视频,并且我们有一个正方形的 PlayerView 并且想要显示顶部区域,那么这就是可见的部分:

PlayerView

当然,如果内容本身是方形的,并且 View 也是方形的,它应该显示整个内容,而不是裁剪。

我试过的

我尝试在 Internet、StackOverflow(此处)和 Github 上进行搜索。 ,但我找不到怎么做。我发现的唯一线索是关于 AspectRatioFrameLayout 和 AspectRatioTextureView,但我没有找到如何将它们用于此任务,如果可能的话。

有人告诉我 (here) 我应该使用普通的 TextureView ,并直接提供给 SimpleExoPlayer使用 SimpleExoPlayer.setVideoTextureView .并使用 TextureView.setTransform 对其进行特殊转换.

经过大量尝试最好使用的东西(并查看 video-crop repositorySuperImageView repositoryJCropImageView repository 有 ImageView 和视频的缩放/裁剪示例),我发布了一个工作示例,似乎显示视频正确,但我仍然不确定,因为我还使用了在开始播放之前显示在其顶部的 ImageView(以获得更好的过渡而不是黑色内容)。

这是当前代码:

class MainActivity : AppCompatActivity() {
    private val imageResId = R.drawable.test
    private val videoResId = R.raw.test
    private val percentageY = 0.2f
    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)
//        imageView.visibility = View.INVISIBLE
        imageView.setImageResource(imageResId)
        imageView.doOnPreDraw {
            imageView.imageMatrix = prepareMatrixForImageView(imageView, imageView.drawable.intrinsicWidth.toFloat(), imageView.drawable.intrinsicHeight.toFloat())
//            imageView.imageMatrix = prepareMatrix(imageView, imageView.drawable.intrinsicWidth.toFloat(), imageView.drawable.intrinsicHeight.toFloat())
//            imageView.visibility = View.VISIBLE
        }
    }

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

    private fun prepareMatrix(view: View, contentWidth: Float, contentHeight: Float): Matrix {
        var scaleX = 1.0f
        var scaleY = 1.0f
        val viewWidth = view.measuredWidth.toFloat()
        val viewHeight = view.measuredHeight.toFloat()
        Log.d("AppLog", "viewWidth $viewWidth viewHeight $viewHeight contentWidth:$contentWidth contentHeight:$contentHeight")
        if (contentWidth > viewWidth && contentHeight > viewHeight) {
            scaleX = contentWidth / viewWidth
            scaleY = contentHeight / viewHeight
        } else if (contentWidth < viewWidth && contentHeight < viewHeight) {
            scaleY = viewWidth / contentWidth
            scaleX = viewHeight / contentHeight
        } else if (viewWidth > contentWidth)
            scaleY = viewWidth / contentWidth / (viewHeight / contentHeight)
        else if (viewHeight > contentHeight)
            scaleX = viewHeight / contentHeight / (viewWidth / contentWidth)
        val matrix = Matrix()
        val pivotPercentageX = 0.5f
        val pivotPercentageY = percentageY

        matrix.setScale(scaleX, scaleY, viewWidth * pivotPercentageX, viewHeight * pivotPercentageY)
        return matrix
    }

    private fun prepareMatrixForVideo(view: View, contentWidth: Float, contentHeight: Float): Matrix {
        val msWidth = view.measuredWidth
        val msHeight = view.measuredHeight
        val matrix = Matrix()
        matrix.setScale(1f, (contentHeight / contentWidth) * (msWidth.toFloat() / msHeight), msWidth / 2f, percentageY * msHeight) /*,msWidth/2f,msHeight/2f*/
        return matrix
    }

    private fun prepareMatrixForImageView(view: View, contentWidth: Float, contentHeight: Float): Matrix {
        val dw = contentWidth
        val dh = contentHeight
        val msWidth = view.measuredWidth
        val msHeight = view.measuredHeight
//        Log.d("AppLog", "viewWidth $msWidth viewHeight $msHeight contentWidth:$contentWidth contentHeight:$contentHeight")
        val scalew = msWidth.toFloat() / dw
        val theoryh = (dh * scalew).toInt()
        val scaleh = msHeight.toFloat() / dh
        val theoryw = (dw * scaleh).toInt()
        val scale: Float
        var dx = 0
        var dy = 0
        if (scalew > scaleh) { // fit width
            scale = scalew
//            dy = ((msHeight - theoryh) * 0.0f + 0.5f).toInt() // + 0.5f for rounding
        } else {
            scale = scaleh
            dx = ((msWidth - theoryw) * 0.5f + 0.5f).toInt() // + 0.5f for rounding
        }
        dy = ((msHeight - theoryh) * percentageY + 0.5f).toInt() // + 0.5f for rounding
        val matrix = Matrix()
//        Log.d("AppLog", "scale:$scale dx:$dx dy:$dy")
        matrix.setScale(scale, scale)
        matrix.postTranslate(dx.toFloat(), dy.toFloat())
        return matrix
    }

    private fun playVideo() {
        player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
        player!!.setVideoTextureView(textureView)
        player!!.addVideoListener(object : VideoListener {
            override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
                super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio)
                Log.d("AppLog", "onVideoSizeChanged: $width $height")
                val videoWidth = if (unappliedRotationDegrees % 180 == 0) width else height
                val videoHeight = if (unappliedRotationDegrees % 180 == 0) height else width
                val matrix = prepareMatrixForVideo(textureView, videoWidth.toFloat(), videoHeight.toFloat())
                textureView.setTransform(matrix)
            }

            override fun onRenderedFirstFrame() {
                Log.d("AppLog", "onRenderedFirstFrame")
                player!!.removeVideoListener(this)
//                imageView.animate().alpha(0f).setDuration(5000).start()
                imageView.visibility = View.INVISIBLE
            }
        })
        player!!.volume = 0f
        player!!.repeatMode = Player.REPEAT_MODE_ALL
        player!!.playRawVideo(this, videoResId)
        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)
    }
}

在我了解当前情况之前,我在尝试这个时遇到了各种问题,并且我已经相应地多次更新了这个问题。现在它甚至适用于我谈到的百分比,所以如果我愿意,我可以将其设置为视频顶部的 20%。但是,我仍然认为它很有可能出现问题,因为当我尝试将其设置为 50% 时,我注意到内容可能不适合整个 View 。

我什至查看了 ImageView (here) 的源代码,了解如何使用 center-crop。当应用于 ImageView 时,它仍然可以作为中心裁剪,但是当我在视频上使用相同的技术时,它给了我一个非常错误的结果。

问题

我的目标是同时显示 ImageView 和视频,以便从静态图像平滑过渡到视频。所有这一切,虽然两者都具有从顶部起 20% 的顶级裁剪(例如)。我已经发布了一个示例项目 here尝试一下并与人们分享我的发现。

所以现在我的问题是为什么这似乎不适用于 imageView 和/或 video :
  • 事实证明,我尝试过的矩阵创建都不适用于 ImageView 或视频。它到底有什么问题?我怎样才能改变它让它们看起来一样?例如,从前 20% 的人口中进行规模种植?
  • 我尝试对两者都使用精确的矩阵,但似乎每个人都需要不同的矩阵,即使两者的大小和内容大小完全相同。为什么我需要一个不同的矩阵?


  • 编辑:在回答了这个问题之后,我决定制作一个如何使用它的小样本(Github 存储库可用 here):
    import android.content.Context
    import android.graphics.Matrix
    import android.graphics.PointF
    import android.net.Uri
    import android.os.Bundle
    import android.view.TextureView
    import android.view.View
    import androidx.annotation.RawRes
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.view.doOnPreDraw
    import com.google.android.exoplayer2.ExoPlayerFactory
    import com.google.android.exoplayer2.Player
    import com.google.android.exoplayer2.SimpleExoPlayer
    import com.google.android.exoplayer2.source.ExtractorMediaSource
    import com.google.android.exoplayer2.source.LoopingMediaSource
    import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
    import com.google.android.exoplayer2.upstream.*
    import com.google.android.exoplayer2.upstream.cache.Cache
    import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory
    import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
    import com.google.android.exoplayer2.upstream.cache.SimpleCache
    import com.google.android.exoplayer2.util.Util
    import com.google.android.exoplayer2.video.VideoListener
    import kotlinx.android.synthetic.main.activity_main.*
    import java.io.File
    
    // https://stackoverflow.com/questions/54216273/how-to-have-similar-mechanism-of-center-crop-on-exoplayers-playerview-but-not
    class MainActivity : AppCompatActivity() {
        companion object {
            private val FOCAL_POINT = PointF(0.5f, 0.2f)
            private const val IMAGE_RES_ID = R.drawable.test
            private const val VIDEO_RES_ID = R.raw.test
            private var cache: Cache? = null
            private const val MAX_PREVIEW_CACHE_SIZE_IN_BYTES = 20L * 1024L * 1024L
    
            @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)
            }
        }
    
        private var player: SimpleExoPlayer? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            if (cache == null)
                cache = SimpleCache(File(cacheDir, "media"), LeastRecentlyUsedCacheEvictor(MAX_PREVIEW_CACHE_SIZE_IN_BYTES))
            //        imageView.visibility = View.INVISIBLE
            imageView.setImageResource(IMAGE_RES_ID)
        }
    
        private fun prepareMatrix(view: View, mediaWidth: Float, mediaHeight: Float, focalPoint: PointF): Matrix? {
            if (view.visibility == View.GONE)
                return null
            val viewHeight = (view.height - view.paddingTop - view.paddingBottom).toFloat()
            val viewWidth = (view.width - view.paddingStart - view.paddingEnd).toFloat()
            if (viewWidth <= 0 || viewHeight <= 0)
                return null
            val matrix = Matrix()
            if (view is TextureView)
            // Restore true media size for further manipulation.
                matrix.setScale(mediaWidth / viewWidth, mediaHeight / viewHeight)
            val scaleFactorY = viewHeight / mediaHeight
            val scaleFactor: Float
            var px = 0f
            var py = 0f
            if (mediaWidth * scaleFactorY >= viewWidth) {
                // Fit height
                scaleFactor = scaleFactorY
                px = -(mediaWidth * scaleFactor - viewWidth) * focalPoint.x / (1 - scaleFactor)
            } else {
                // Fit width
                scaleFactor = viewWidth / mediaWidth
                py = -(mediaHeight * scaleFactor - viewHeight) * focalPoint.y / (1 - scaleFactor)
            }
            matrix.postScale(scaleFactor, scaleFactor, px, py)
            return matrix
        }
    
        private fun playVideo() {
            player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
            player!!.setVideoTextureView(textureView)
            player!!.addVideoListener(object : VideoListener {
                override fun onVideoSizeChanged(videoWidth: Int, videoHeight: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
                    super.onVideoSizeChanged(videoWidth, videoHeight, unappliedRotationDegrees, pixelWidthHeightRatio)
                    textureView.setTransform(prepareMatrix(textureView, videoWidth.toFloat(), videoHeight.toFloat(), FOCAL_POINT))
                }
    
                override fun onRenderedFirstFrame() {
                    //                Log.d("AppLog", "onRenderedFirstFrame")
                    player!!.removeVideoListener(this)
                    imageView.animate().alpha(0f).setDuration(2000).start()
                    //                imageView.visibility = View.INVISIBLE
                }
            })
            player!!.volume = 0f
            player!!.repeatMode = Player.REPEAT_MODE_ALL
            player!!.playRawVideo(this, VIDEO_RES_ID)
            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 onStart() {
            super.onStart()
            imageView.doOnPreDraw {
                val imageWidth: Float = imageView.drawable.intrinsicWidth.toFloat()
                val imageHeight: Float = imageView.drawable.intrinsicHeight.toFloat()
                imageView.imageMatrix = prepareMatrix(imageView, imageWidth, imageHeight, FOCAL_POINT)
            }
            playVideo()
        }
    
        override fun onStop() {
            super.onStop()
            if (player != null) {
                player!!.setVideoTextureView(null)
                //        playerView.player = null
                player!!.release()
                player = null
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            if (!isChangingConfigurations)
                cache?.release()
        }
    
        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)
        }
    }
    

    如果需要,这是一个单独的 ImageView 解决方案:
    class ScaleCropImageView(context: Context, attrs: AttributeSet?) : AppCompatImageView(context, attrs) {
        var focalPoint = PointF(0.5f, 0.5f)
            set(value) {
                field = value
                updateMatrix()
            }
        private val viewWidth: Float
            get() = (width - paddingLeft - paddingRight).toFloat()
    
        private val viewHeight: Float
            get() = (height - paddingTop - paddingBottom).toFloat()
    
        init {
            scaleType = ScaleType.MATRIX
        }
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            updateMatrix()
        }
    
        override fun setImageDrawable(drawable: Drawable?) {
            super.setImageDrawable(drawable)
            updateMatrix()
        }
    
        @Suppress("MemberVisibilityCanBePrivate")
        fun updateMatrix() {
            if (scaleType != ImageView.ScaleType.MATRIX)
                return
            val dr = drawable ?: return
            imageMatrix = prepareMatrix(
                    viewWidth, viewHeight,
                    dr.intrinsicWidth.toFloat(), dr.intrinsicHeight.toFloat(), focalPoint, Matrix()
            )
        }
    
        private fun prepareMatrix(
                viewWidth: Float, viewHeight: Float, mediaWidth: Float, mediaHeight: Float,
                focalPoint: PointF, matrix: Matrix
        ): Matrix? {
            if (viewWidth <= 0 || viewHeight <= 0)
                return null
            var scaleFactor = viewHeight / mediaHeight
            if (mediaWidth * scaleFactor >= viewWidth) {
                // Fit height
                matrix.postScale(scaleFactor, scaleFactor, -(mediaWidth * scaleFactor - viewWidth) * focalPoint.x / (1 - scaleFactor), 0f)
            } else {
                // Fit width
                scaleFactor = viewWidth / mediaWidth
                matrix.postScale(scaleFactor, scaleFactor, 0f, -(mediaHeight * scaleFactor - viewHeight) * focalPoint.y / (1 - scaleFactor))
            }
            return matrix
        }
    }
    

    最佳答案

    问题是如何处理像 ImageView.ScaleType.CENTER_CROP 这样的图像。而是将焦点从中心转移到距离图像顶部 20% 的另一个位置。首先我们来看看CENTER_CROP是什么做:

    来自 documentation :

    CENTER_CROP

    Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding). The image is then centered in the view. From XML, use this syntax: android:scaleType="centerCrop".



    换句话说,在不失真的情况下缩放图像,使图像的宽度或高度(或宽度和高度)都适合 View ,从而使 View 完全被图像填充(没有间隙)。

    另一种思考方式是图像的中心被“固定”到 View 的中心。然后缩放图像以满足上述标准。

    在下面的视频中,白线标记了图像的中心;红线标记 View 的中心。刻度类型为 CENTER_CROP .注 Intent 像和 View 的中心点是如何重合的。随着 View 大小的改变,这两个点继续重叠并始终出现在 View 的中心,而与 View 大小无关。

    enter image description here

    那么,在不同的位置(例如距离顶部 20%)具有类似中心裁剪的行为意味着什么?与中心裁剪一样,我们可以指定距图像顶部 20% 的点以及距 View 顶部 20% 的点将被“固定”,就像 50% 的点在中心裁剪中“固定”一样。该点的水平位置保持在图像和 View 的 50% 处。现在可以缩放图像以满足中心裁剪的其他条件,这些条件指定图像的宽度和/或高度将适合 View 而没有间隙。 ( View 大小被理解为 View 大小减去填充。)

    这是这个 20% 裁剪行为的简短视频。在此视频中,白线表示图像的中间,红线表示 View 中的固定点,而水平红线后面的蓝线表示距离图像顶部 20% 的位置。 (演示项目在 GitHub 上。

    enter image description here

    这是显示提供的完整图像和从静止图像过渡的方形框架中的视频的结果。 .

    enter image description here

    MainActivity.kt prepareMatrix()是确定如何缩放/裁剪图像的方法。由于该视频似乎适合 TextureView当它分配给 TextureView 时,作为比例类型“FIT_XY” .由于这种缩放,媒体大小必须在 prepareMatrix() 之前恢复。被要求播放视频
    class MainActivity : AppCompatActivity() {
        private val imageResId = R.drawable.test
        private val videoResId = R.raw.test
        private var player: SimpleExoPlayer? = null
        private val mFocalPoint = PointF(0.5f, 0.2f)
    
        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)
            //        imageView.visibility = View.INVISIBLE
            imageView.setImageResource(imageResId)
            imageView.doOnPreDraw {
                imageView.scaleType = ImageView.ScaleType.MATRIX
                val imageWidth: Float = ContextCompat.getDrawable(this, imageResId)!!.intrinsicWidth.toFloat()
                val imageHeight: Float = ContextCompat.getDrawable(this, imageResId)!!.intrinsicHeight.toFloat()
                imageView.imageMatrix = prepareMatrix(imageView, imageWidth, imageHeight, mFocalPoint, Matrix())
                val b = BitmapFactory.decodeResource(resources, imageResId)
                val d = BitmapDrawable(resources, b.copy(Bitmap.Config.ARGB_8888, true))
                val c = Canvas(d.bitmap)
                val p = Paint()
                p.color = resources.getColor(android.R.color.holo_red_dark)
                p.style = Paint.Style.STROKE
                val strokeWidth = 10
                p.strokeWidth = strokeWidth.toFloat()
                // Horizontal line
                c.drawLine(0f, imageHeight * mFocalPoint.y, imageWidth, imageHeight * mFocalPoint.y, p)
                // Vertical line
                c.drawLine(imageWidth * mFocalPoint.x, 0f, imageWidth * mFocalPoint.x, imageHeight, p)
                // Line in horizontal and vertical center
                p.color = resources.getColor(android.R.color.white)
                c.drawLine(imageWidth / 2, 0f, imageWidth / 2, imageHeight, p)
                c.drawLine(0f, imageHeight / 2, imageWidth, imageHeight / 2, p)
    
                imageView.setImageBitmap(d.bitmap)
                imageViewFull.setImageBitmap(d.bitmap)
            }
        }
    
        fun startPlay(view: View) {
            playVideo()
        }
    
        private fun getViewWidth(view: View): Float {
            return (view.width - view.paddingStart - view.paddingEnd).toFloat()
        }
    
        private fun getViewHeight(view: View): Float {
            return (view.height - view.paddingTop - view.paddingBottom).toFloat()
        }
    
        private fun prepareMatrix(targetView: View, mediaWidth: Float, mediaHeight: Float,
                                  focalPoint: PointF, matrix: Matrix): Matrix {
            if (targetView.visibility != View.VISIBLE) {
                return matrix
            }
            val viewHeight = getViewHeight(targetView)
            val viewWidth = getViewWidth(targetView)
            val scaleFactorY = viewHeight / mediaHeight
            val scaleFactor: Float
            val px: Float
            val py: Float
            if (mediaWidth * scaleFactorY >= viewWidth) {
                // Fit height
                scaleFactor = scaleFactorY
                px = -(mediaWidth * scaleFactor - viewWidth) * focalPoint.x / (1 - scaleFactor)
                py = 0f
            } else {
                // Fit width
                scaleFactor = viewWidth / mediaWidth
                px = 0f
                py = -(mediaHeight * scaleFactor - viewHeight) * focalPoint.y / (1 - scaleFactor)
            }
            matrix.postScale(scaleFactor, scaleFactor, px, py)
            return matrix
        }
    
        private fun playVideo() {
            player = ExoPlayerFactory.newSimpleInstance(this@MainActivity, DefaultTrackSelector())
            player!!.setVideoTextureView(textureView)
            player!!.addVideoListener(object : VideoListener {
                override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
                    super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio)
                    val matrix = Matrix()
                    // Restore true media size for further manipulation.
                    matrix.setScale(width / getViewWidth(textureView), height / getViewHeight(textureView))
                    textureView.setTransform(prepareMatrix(textureView, width.toFloat(), height.toFloat(), mFocalPoint, matrix))
                }
    
                override fun onRenderedFirstFrame() {
                    Log.d("AppLog", "onRenderedFirstFrame")
                    player!!.removeVideoListener(this)
                    imageView.animate().alpha(0f).setDuration(2000).start()
                    imageView.visibility = View.INVISIBLE
                }
            })
            player!!.volume = 0f
            player!!.repeatMode = Player.REPEAT_MODE_ALL
            player!!.playRawVideo(this, videoResId)
            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()
            if (player != null) {
                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)
        }
    }
    

    关于android - 如何在 ExoPlayer 的 PlayerView 上有类似的 center-crop 机制,但不在中心?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54216273/

    相关文章:

    android - 使用 Path.lineTo 在 x 轴上增加一个小数点

    java - 如何使用drawImage()来裁剪图像?

    matlab - 如何在 MATLAB 中将裁剪后的人脸图像 reshape 为一维图像向量?

    android - 防止 HttpClient 4 跟随重定向

    android - MixPanel 问题消息未发布

    css - 如何仅使用 CSS 按比例缩放调整 SVG 内容的大小?

    ios - xamarin ios 像 Android 一样裁剪图像

    android - Looper 线程上的 CoroutineDispatcher

    java - openJdk 升级到 8u292 会破坏我的 aosp 构建系统吗?

    javascript - 缩略图悬停缩放(放大)w/CSS3 和 javascript z 轴问题