我正在开发一项功能,需要对图库中选定的视频执行一些操作,然后将其上传到后端服务器,我提到了一些我现在遇到的视频编辑功能:
我已经尝试了一些使用TextureView的解决方案并且它正在处理它,但没有使用SurfaceView。
下面是使用TextureView的解决方案:
class VideoEditActivity : ComponentActivity() {
private lateinit var textureView: TextureView
private lateinit var mediaPlayer: MediaPlayer
private lateinit var videoContainer: FrameLayout
private var scaleGestureDetector: ScaleGestureDetector? = null
private var gestureDetector: GestureDetector? = null
private var scaleFactor = 1.0f
private var translationX = 0f
private var translationY = 0f
private var rotationDegrees = 0f
private var initialRotationDegrees = 0f
private lateinit var selectedFilePath: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_edit)
textureView = findViewById(R.id.textureView)
videoContainer = findViewById(R.id.videoContainer)
videoContainer.setBackgroundResource(R.color.purple_200)
selectedFilePath = getPathFromUri(Uri.parse(intent.getStringExtra(INTENT_DATA)))
Logger.log("Selected file path: $selectedFilePath")
findViewById<ImageView>(R.id.ivVideoPlay).setOnClickListener {
// captureAndCreateVideo()
}
setupMediaPlayer()
setupGestures()
}
private fun setupMediaPlayer() {
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
val selectedMediaUri = Uri.parse(intent.getStringExtra(INTENT_DATA))
selectedMediaUri?.let {
mediaPlayer = MediaPlayer.create(this@VideoEditActivity, it) // Adjust the source accordingly
}
mediaPlayer.setSurface(Surface(surface))
mediaPlayer.isLooping = true
mediaPlayer.start()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
mediaPlayer.release()
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}
}
private fun setupGestures() {
scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
textureView.scaleX = scaleFactor
textureView.scaleY = scaleFactor
return true
}
})
gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
translationX -= distanceX
translationY -= distanceY
textureView.translationX = translationX
textureView.translationY = translationY
return true
}
})
videoContainer.setOnTouchListener { _, event ->
if (event.pointerCount == 2) {
if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
initialRotationDegrees = rotation(event)
} else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
val currentRotationDegrees = rotation(event)
val deltaDegrees = initialRotationDegrees - currentRotationDegrees
rotationDegrees -= deltaDegrees
textureView.rotation = rotationDegrees
initialRotationDegrees = currentRotationDegrees
}
}
scaleGestureDetector?.onTouchEvent(event)
gestureDetector?.onTouchEvent(event)
true
}
}
private fun rotation(event: MotionEvent): Float {
val dx = (event.getX(0) - event.getX(1)).toDouble()
val dy = (event.getY(0) - event.getY(1)).toDouble()
return Math.toDegrees(atan2(dy, dx)).toFloat()
}
private fun getPathFromUri(uri: Uri): String {
var filePath: String? = null
val projection = arrayOf(MediaStore.Video.Media.DATA)
val cursor = contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
filePath = cursor.getString(columnIndex)
cursor.close()
}
return filePath ?: ""
}
}
XML 活动_视频_编辑文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/ivSelectedImage"
android:src="@drawable/app_logo"
android:layout_centerInParent="true"
android:foregroundGravity="center"
android:visibility="gone"
android:contentDescription="selected_image" />
<FrameLayout
android:id="@+id/videoContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"> <!-- Background color -->
<TextureView
android:id="@+id/textureView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/play_icon"
android:layout_centerInParent="true"
android:visibility="gone"
android:id="@+id/ivVideoPlay"/>
</RelativeLayout>
预期结果是:https://drive.google.com/file/d/1459VgzTby7P6prwDGkz2aHClhc2VW206/view?usp=sharing
更新:
我可以使用以下解决方案实现捏合/放大/缩小,但旋转不起作用,正如我在预期结果视频中显示的那样(只有容器旋转,而不是 SurfaceView 中的内容):
private fun setupSurfaceGestures() {
scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
scaleFactor = max(0.1f, min(scaleFactor, 5.0f)) // Limit scaling
// Update the SurfaceView scale here if possible or handle via custom drawing
surfaceView.scaleX = scaleFactor
surfaceView.scaleY = scaleFactor
return true
}
})
gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
translationX -= distanceX
translationY -= distanceY
// Update the SurfaceView translation here if possible or handle via custom drawing
surfaceView.translationX = translationX
surfaceView.translationY = translationY
return true
}
})
videoContainer.setOnTouchListener { _, event ->
if (event.pointerCount == 2) {
if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
initialRotationDegrees = rotation(event)
} else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
val currentRotationDegrees = rotation(event)
val deltaDegrees = initialRotationDegrees - currentRotationDegrees
rotationDegrees -= deltaDegrees
surfaceView.rotation = rotationDegrees
initialRotationDegrees = currentRotationDegrees
}
}
scaleGestureDetector?.onTouchEvent(event)
gestureDetector?.onTouchEvent(event)
true
}
更改后输出视频:https://drive.google.com/file/d/1hHAfWf_NnplzrRdHYKgjXk-tf8etZRmJ/view?usp=sharing
SurfaceView
与其他视图的不同之处在于,它不会在其 Canvas
上绘制任何内容,而是“在其窗口上打一个洞以允许显示其表面”。尽管它允许您对 Surface
应用某些变换(例如更改位置和比例),但它不支持更改旋转。
另一方面,TextureView
的行为与常规 View
一样。所以你可以自由改变它的旋转和alpha。
但是,如果您必须使用
SurfaceView
,则任何自定义转换都必须手动完成。为了提高效率,您可能希望利用硬件加速。请注意,这需要编写大量代码。
这是一种可能的工作流程:
MediaCodec
解码视频帧; SurfaceTexture
使它们可以作为外部纹理供 GPU 使用; OpenGL ES 在应用变换矩阵的同时渲染纹理矩形; Surface
的SurfaceView
用作EGL窗口表面。并且不要忘记根据视频帧速率来控制播放速度。