ANDROID 六月 24, 2022

Android • Video

文章字数 9.4k 阅读约需 17 mins. 阅读次数 0

引言

本篇将介绍我最近在对 Android 项目进行视频编辑功能开发的一些笔记分享。

在阅读本文前,你需要了解常用的 Android 视频播放方式:

  • (1):使用 系统自带的播放器 进行播放,该方式需要指定 Intent的Action为ACTION_VIEW,Data为视频Uri,Type为待播放视频的MIME类型;
  • (2):使用 VideoView 播放视频;
  • (3):使用 SurfaceView + MediaPlayer 播放视频(推荐做法)。

SurfaceView

Google 官方文档中对于 SurfaceView 的介绍:

Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen.


MediaPlayer

Google 官方文档中对于 MediaPlayer 的介绍:

The Android multimedia framework includes support for playing variety of common media types, so that you can easily integrate audio, video and images into your applications. You can play audio or video from media files stored in your application’s resources (raw resources), from standalone files in the filesystem, or from a data stream arriving over a network connection, all using MediaPlayer APIs.

MediaPlayer 是 Android 多媒体组件库中至关重要的一员,通常用于对视频进行装载,播放,控制等操作。

val mediaPlayer = MediaPlayer()

// 通过 Surface + MediaPlayer 进行视频播放
try {
  // 从videoPath中加载视频资源,该路径可以是视频路径,也可以是网络地址
  mediaPlayer.setDataSource(videoPath)

  // 配合 Surface 进行视频渲染
  val surface = Surface(surfaceTexture)
  mediaPlayer.setSurface(surface)
  surface.release() // 加载完毕后及时释放 Surface 资源

  // 控制视频循环播放
  mediaPlayer.isLooping = true

  // 视频资源装载完成时,回调该监听器,该监听器类型为:MediaPlayer.OnPreparedListener
  // 在该监听器中,可进行一些常用组件的自定义设置,比如通过获取视频宽高,动态调整相应布局
  mediaPlayer.setOnPreparedListener(listener: MediaPlayer.OnPreparedListener)

  // 视频播放预处理
  mediaPlayer.prepare()

  // 播放视频,该方法也可用来恢复当前播放进度
  mediaPlayer.start()
} catch(exception: Exception) {
  println("mediaPlayer Error")
}

// 其他常用 MediaPlayer 控制视频播放方法:
// 暂停播放
mediaPlayer.pause()

// 重置 MediaPlayer资源,调用该方法后需重新对 MediaPlayer 进行初始化
mediaPlayer.reset()

// 释放视频资源
mediaPlayer.release()

// 跳转到指定进度
mediaPlayer.seekTo(msec: Int)
// mediaPlayer.seekTo(msec: Int, mode: Int)
// MediaPlayer 视频信息相关方法:
// 获取视频总长度
mediaPlayer.duration

// 获取视频当前进度 
mediaPlayer.currentPosition

// 获取视频宽度
mediaPlayer.videoWidth

// 获取视频高度
mediaPlayer.videoHeight

// 获取视频轨道信息
mediaPlayer.trackInfo

// 调整视频音量
mediaPlayer.setVolume(leftVolume: Float, rightVolume: Float) // 左,右声道音量

MediaMetadataRetriever

Google 官方文档中对于 MediaMetadataRetriever 的介绍:

MediaMetadataRetriever class provides a unified interface for retrieving frame and meta data from an input media file.

MediaMetaDataRetriever 常用于提取视频原始信息,如视频第一帧图片,视频源数据长度,视频源数据作者等静态信息。

val mediaMetaRetriever = MediaMetadataRetriever()

// 设置视频资源
mediaMetadataRetriever.setDataSource(path: String!)

mediaMetadataRetriever.setDataSource(path: String!, headers: (Mutable)Map<String!, String!>!)

mediaMetadataRetriever.setDataSource(fileDescriptor: FileDescriptor!)

mediaMetadataRetriever.setDataSource(fileDescriptor: FileDescriptor!, offset: Long, length: Long)

mediaMetadataRetriever.setDataSource(context: Context!, uri: Uri!)

mediaMetadataRetriever.setDataSource(dataSource: MediaDataSource!)
// 常用的一些视频元数据信息
MediaMetadataRetriever.METADATA_KEY_DURATION // 视频长度
MediaMetadataRetriever.METADATA_KEY_DATE // 视频创建日期
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT // 视频高度
MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH // 视频高度
...

// 使用 extractMetadata() 方法提取视频元数据信息,建议在子线程中调用该方法
// 如:获取视频长度信息
mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)

MediaController

Google 官方文档中对于 MediaController 的介绍:

A view containing controls for a MediaPlayer. Typically contains the buttons like “Play/Pause”, “Rewind”, “Fast Forward” and a progress slider. It takes care of synchronizing the controls with the state of the MediaPlayer.

MediaController 提供了一个悬浮的控制栏,默认情况下,该控制栏会显示3秒,然后自动隐藏,该控制栏通常配合 VideoView 使用。

// MediaController 控制器相关方法:
// 添加 MediaController 至 VideoView,对视频播放进行控制。
val mediaController = MediaController(requireContext())

// 显示控制器
mediaController.show()

mediaController.show(timeout: Int) // 在 timeout 个微秒时间后,自动隐藏控制器

// 隐藏控制器
mediaController.hide()

// 控制器显示状态,返回值类型:Boolean
mediaController.isShowing()

// 控制是否显示 <上一个/下一个视频> 按钮
mediaController.setPrevNextListeners(next: View.OnClickListener!, prev: View.OnClickListener!)

与 MediaPlayerControl 配合使用

mediaController.setMediaPlayer(player: MediaController.MediaPlayControl!)

与 VideoView 配合使用

// 绑定 mediaController 到 videoView
mediaController.setAnchorView(view: View!) // Example: mediaController.setAnchorView(mVideoView)
videoView.setMediaController(mediaController)

VideoView

Google 官方文档中对于 VideoView 的介绍:

Displays a video file. The VideoView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the video so that it can be used in any layout manager, and provides various display options such as scaling and tinting.

VideoView = SurfaceView + MediaPlayer + MediaController,VideoView 是对 SurfaceView,MediaPlayer与MediaController 的视频化封装,对上层提供开箱即用的接口方法支持。

// VideoView有多种方法可进行视频播放:
// 方法1:通过视频Uri进行视频播放
videoView.setVideoURI(uri: Uri!)

videoView.setVideoURI(uri: Uri!, headers: (Mutable)Map<String!, String!>)

// 方法2:通过视频文件路径进行视频播放,该方法也可用于播放在线视频
// 若播放在线视频,需先在 Manifests清单文件 中申请网络请求权限:<uses-permission android:name="android.permission.INTERNET" />
videoView.setVideoPath(path: String!)

// 开始播放
videoView.start()

// 请求当前屏幕焦点至VideoView,可配合 videoView.start() 使用
videoView.requestFocus() 

// 恢复播放
videoView.resume()

// 暂停播放
videoView.pause()

// 检查当前播放视频是否可被暂停,可用于 App 从后台切换至前台
videoView.canPause()

// 该方法会调用 mediaPlayer.release() 方法,会释放当前绑定的视频资源
videoView.stopPlayback()

// 视频跳至目标点播放
videoView.seekTo(msec: Int)

// 检查当前视频可跳至目标点播放
// Forward:向前播放
// Backward:向后播放
videoView.canSeekForward()
videoView.canSeekBackward()
// VideoView 视频相关信息方法:
// 获取视频总长度,返回值类型:Int
videoView.duration

// 获取当前播放进度,返回值类型:Int
videoView.currentPosition

// 检查当前视频是否正在播放,返回值类型:Boolean
videoView.isPlaying

// 视频资源装载完成时,回调该监听器,该监听器类型为:MediaPlayer.OnPreparedListener
videoView.setOnPreparedListener(listener: MediaPlayer.OnPreparedListener!)

// 视频播放出错时,回调该监听器,该监听器类型为:MediaPlayer.OnErrorListener
videoView.setOnErrorListener(listener: MediaPlayer.OnErrorListener!)

// 视频播放完成时,回调该监听器,该监听器类型为:MediaPlayer.OnCompletionListener
videoView.setOnCompletionListener(listener: MediaPlayer.OnCompletionListener!)

与 MediaPlayer 配合使用

循环播放

// 推荐写法:
videoVideo.setOnPreparedListener { mediaPlayer -> 
    mediaPlayer.isLooping = true
}

// 也可在 setOnCompletionListener 中对 mediaPlayer 进行设置(本质上只是对 VideoView 中封装的 MediaPlayer 进行再播放设置)
videoView.setOnCompletionListener { mediaPlayer ->
    mediaPlayer.start()
}

Open Source Framework

Mp4Parser

作者仓库 README 中对于 Mp4Parser 的介绍:

A Java API to read, write and create MP4 container. Manipulating containers is different from encoding and decoding videos and audio.

MP4音视频处理:

  • 将音视频文件合并到一个MP4文件;
  • 将编码格式相同的录音进行拼接合并;
  • 添加或修改音视频元数据;
  • 通过省略帧缩短音视频;

Mp4Composer-Android

Github • Mp4Composer-Android

作者仓库 README 中对于 Mp4Composer-Android 的介绍:

This library generate an Mp4 movie using Android MediaCodec API and apply filter, scale, trim, transcode, crop, mute and rotate Mp4.

Android 视频处理,添加滤镜,裁剪,旋转,改变大小。


SiliCompressor

Github • SiliCompressor

作者仓库 README 中对于 SiliCompressor 的介绍:

A powerful, flexible and easy to use Video and Image compression library for Android.

Image: It’s usually said that “A picture is worth a thousand words”. Images adds flair and beauty to our android apps, but we usaully have problems with these images due to thier large size. With SiliCompressor you can now compress and use your images more smoothly.

Video: Due to the high resolution of our Smartphone cameras and cameras from other devices, Video files have become large in size and thus difficult for it to be shared with others on social apps, social media and even when we need to upload it on our server. With SiliCompressor you can now compress you video file while maintaining it quality.

图像与视频压缩。


Wechat Video Editor

本章节将介绍如何仿制 Wechat (微信)的视频编辑页,使用到的技术如下:

  • SurfaceView + MediaPlayer

  • Custom RangeSeekbar ( Thumbnail RecyclerView + Video Indicator )

  • Video Clipping & Compress


SurfaceView + MediaPlyer

  1. 初始化 SurfaceView

  2. 初始化 MediaPlayer

val mediaPlayer = MediaPlayer()

// 通过 Surface + MediaPlayer 进行视频播放
try {
  // 从videoPath中加载视频资源,该路径可以是视频路径,也可以是网络地址
  mediaPlayer.setDataSource(videoPath)

  // 配合 SurfaceView 进行视频渲染
  val surface = Surface(surfaceTexture)
  mediaPlayer.setSurface(surface)
  surface.release() // 加载完毕后及时释放 Surface 资源

  // 控制视频循环播放
  mediaPlayer.isLooping = true

  // 视频资源装载完成时,回调该监听器,该监听器类型为:MediaPlayer.OnPreparedListener
  // 在该监听器中,可进行一些常用组件的自定义设置,比如通过获取视频宽高,动态调整相应布局
  mediaPlayer.setOnPreparedListener{ player ->
    // 可对 Ui 布局进行适配,如:根据视频宽高动态设置 Ui 布局宽高
  }

  // 视频播放预处理
  mediaPlayer.prepare()

  // 播放视频,该方法也可用来恢复当前播放进度
  mediaPlayer.start()
} catch(exception: Exception) {
  println("mediaPlayer Error")
}

Thumbnail RecyclerView

  1. 准备 RecyclerView Item Layout(此处省略)

  2. 准备 RecyclerView Adapter(此处省略)

  3. 绑定 RecyclerView OnScrollListener

private val rvScrollListener: RecyclerView.OnScrollListener = object: RecyclerView.OnScrollListener(){
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int){
        super.onScrollStateCHanged(recyclerView, newState)
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            // RecyclerView 停止滑动,进行相应事件处理
        } else {
            // RecyclerView 开始滑动,进行相应事件处理
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int){
        super.onScrolled(recyclerView, dx, dy)
        // RecyclerView 滑动中,进行相应事件处理
    }
}

// 绑定滑动监听器至 RecyclerView
recyclerView.addOnScrollListener(rvScrollListener)

Video Indictor

  1. 准备 Indictor 外框

  2. 设置 Indictor 动画


Custom RangeSeekbar

  1. 从视频元数据抽取逐帧预览图

  2. 加载预览图至 Thumbnail RecyclerView

  3. 设置 RangeSeekbar 滑动监听


Video Clipping

此方案使用来自GIthub的开源视频编辑库:Mp4Composer-android

  1. 设置原始视频路径,裁剪视频输出路径,视频裁剪模式等参数

  2. 子线程中进行视频裁剪

  3. 将裁剪完成的视频路径作为待压缩视频的原始路径


Video Compress

此方案使用来自Github的开源视频压缩库:SiliCompressor

  1. 设置原始视频路径,压缩视频输出路径

  2. 子线程中进行视频压缩

  3. 输出最终的视频路径

// SiliCompressor 可在压缩前设置输出参数,包括输出视频宽高与比特率

SiliCompressor.with(context).compressVideo(srcUri, destPath)

SiliCompressor.with(context).compressVideo(srcPath, destPath)

SiliCompressor.with(context).compressVideo(srcPath, destPath, outputVideoWidth, outputVideoHeight, outputBitrate)

SiliCompressor.with(context).compressVideo(srcUri, destPath, outputVideoWidth, outputVideoHeight, outputBitrate)

Reference


0%