实现一个视频画廊,播放本地视频 可以切换不同视频的功能
文章目录
需求:
实现一个视频画廊,播放本地视频 可以切换不同视频的功能
场景
图片画廊的场景很多,视频也一样,主要商显上面用得后很多。
- 广告机、各种大屏广告显示提醒
- 宣传-会展用的需求
- 壁画功能,如家庭相框也可以一直播放视频使用
实现效果如下
实现方案
- 加载页面滑动实现:viewPager+Fragment 方案
- 视频布局和播放组件用:VideoView
- 最简单的 VideoView 来实现
遇到的坑
播放器选择
最开始用得饺子播放器和Ex播放器封装sdk,遇到加载时候默认预览画面、默认有电量、wifi 控制显示等。 针对于在线视频播放非常实用,并不适合本地视频简单播放。
可以尝试用的情况下,直接用依赖包不方便,拿到依赖包源码放到本地,需要去掉默认很多选项,太折腾。
各种配置针对本地播放么有那么麻烦,最终就用VideoView 来实现
部分封装播放器存在对本地视频文件的不友好,如饺子播放器无法播放本地视频的。
界面显示不全
遇到界面显示不全问题,横竖屏切换时候。 用横竖屏两套布局文件来实现即可。
视频友好显示问题
存在视频显示不全、比例对比、压缩变形问题。 无外乎进行参数配置,裁剪等。特别是第三方播放器 用sdk 来进行配置必须的。
最简单方案:横竖屏两套视频,横竖屏情况下播放的其实是不同资源相同画面的视频。

缓存
如同抖音播放在线视频一样,一定要尽量提前缓存,或者播放之后进行缓存一次。 不然切换不同视频资源播放的时候 也就是 切换不同画面播放视频时候回黑以下。
部分源码分享如下,仅提供播放逻辑而已。
java
class VideoViewPlayFragment : Fragment() {
private val TAG = "VideoViewPlayFragment"
var pathUrl: String = ""
private lateinit var viewModel: VideoPlayViewModel
private lateinit var viewBindingFragmentVideoPlayBinding: FragmentVideoviewPlayBinding
private val binding get() = viewBindingFragmentVideoPlayBinding!!
companion object {
fun newInstance(videoPath: String): VideoViewPlayFragment {
val fragment = VideoViewPlayFragment()
val args = Bundle()
args.putString("pathUrl", videoPath)
fragment.arguments = args
Log.d("VideoViewPlayFragment"," newInstance videoPath:${videoPath}")
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewBindingFragmentVideoPlayBinding =
FragmentVideoviewPlayBinding.inflate(inflater, container, false)
pathUrl = arguments?.getString("pathUrl").toString()
Log.d(TAG, " 传递过来的数据:pathUrl:$pathUrl")
Log.d(TAG," onCreateView videoPath:${pathUrl}")
parseUrl(pathUrl)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG," onCreate onCreate:${pathUrl}")
viewModel = ViewModelProvider(this).get(VideoPlayViewModel::class.java)
}
private fun parseUrl(pathUrl: String) {
Log.d(TAG," parseUrl pathUrl value:${pathUrl} ")
when (pathUrl) {
"beach" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.beach_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.beach_s)
}
}
"bird" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.bird_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.bird_s)
}
}
"fire" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.fire_new_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.fire_new_s)
}
}
"flower" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.flower_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.flower_s)
}
}
"fog" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.fog_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.fog_s)
}
}
"rain" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.rain_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.rain_s)
}
}
"snow" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.snow_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.snow_s)
}
}
"summer" -> {
if (isLandscape()) {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.summer_h)
} else {
videoPlay("android.resource://" + ContextProvider.get().context.packageName + "/" + R.raw.summer_s)
}
}
}
}
fun videoPlay(path1: String) {
Log.d(TAG,"videoPlay path1:${path1}")
viewBindingFragmentVideoPlayBinding.sourceVideo.setVideoURI(Uri.parse(path1))
//viewBindingFragmentVideoPlayBinding.sourceVideo.start()
viewBindingFragmentVideoPlayBinding.sourceVideo.setOnCompletionListener(MediaPlayer.OnCompletionListener {
viewBindingFragmentVideoPlayBinding.sourceVideo.setVideoURI(Uri.parse(path1))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
viewBindingFragmentVideoPlayBinding.sourceVideo.setAudioFocusRequest(AudioManager.AUDIOFOCUS_NONE)
}
Log.d(TAG, " OnCompletionListener 播放完成了 继续播放... ")
viewBindingFragmentVideoPlayBinding.sourceVideo.start()
})
viewBindingFragmentVideoPlayBinding.sourceVideo.setOnErrorListener(MediaPlayer.OnErrorListener { mp, what, extra ->
Log.d(TAG, " OnErrorListener 播放异常了... ")
true
})
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
parseUrl(pathUrl)
}
override fun onResume() {
super.onResume()
Log.d(TAG, " onResume pathUrl:$pathUrl")
if (!viewBindingFragmentVideoPlayBinding.sourceVideo.isPlaying()) {
Log.d(TAG, " 开始播放")
viewBindingFragmentVideoPlayBinding.sourceVideo.start()
}
}
override fun onPause() {
super.onPause()
if (viewBindingFragmentVideoPlayBinding.sourceVideo.isPlaying()) {
Log.d(TAG, " 暂停播放")
viewBindingFragmentVideoPlayBinding.sourceVideo.pause()
}
}
}
最后显示效果就很nice 了。
总结
针对市场上面播放显示效果做了一个简单的总结 和 避坑指南,实际效果确实不一样,很nice