實現一個視頻畫廊,播放本地視頻 可以切換不同視頻的功能
文章目錄
- 需求:
- 場景
- 實現方案
- 遇到的坑
- 播放器選擇
- 界面顯示不全
- 視頻友好顯示問題
- 緩存
- 總結
需求:
實現一個視頻畫廊,播放本地視頻 可以切換不同視頻的功能
場景
圖片畫廊的場景很多,視頻也一樣,主要商顯上面用得后很多。
- 廣告機、各種大屏廣告顯示提醒
- 宣傳-會展用的需求
- 壁畫功能,如家庭相框也可以一直播放視頻使用
實現效果如下
實現方案
- 加載頁面滑動實現:viewPager+Fragment 方案
- 視頻布局和播放組件用:VideoView
- 最簡單的 VideoView 來實現
遇到的坑
播放器選擇
最開始用得餃子播放器和Ex播放器封裝sdk,遇到加載時候默認預覽畫面、默認有電量、wifi 控制顯示等。 針對于在線視頻播放非常實用,并不適合本地視頻簡單播放。
可以嘗試用的情況下,直接用依賴包不方便,拿到依賴包源碼放到本地,需要去掉默認很多選項,太折騰。
各種配置針對本地播放么有那么麻煩,最終就用VideoView 來實現
部分封裝播放器存在對本地視頻文件的不友好,如餃子播放器無法播放本地視頻的。
界面顯示不全
遇到界面顯示不全問題,橫豎屏切換時候。 用橫豎屏兩套布局文件來實現即可。
視頻友好顯示問題
存在視頻顯示不全、比例對比、壓縮變形問題。 無外乎進行參數配置,裁剪等。特別是第三方播放器 用sdk 來進行配置必須的。
最簡單方案:橫豎屏兩套視頻,橫豎屏情況下播放的其實是不同資源相同畫面的視頻。
緩存
如同抖音播放在線視頻一樣,一定要盡量提前緩存,或者播放之后進行緩存一次。 不然切換不同視頻資源播放的時候 也就是 切換不同畫面播放視頻時候回黑以下。
部分源碼分享如下,僅提供播放邏輯而已。
class VideoViewPlayFragment : Fragment() {private val TAG = "VideoViewPlayFragment"var pathUrl: String = ""private lateinit var viewModel: VideoPlayViewModelprivate lateinit var viewBindingFragmentVideoPlayBinding: FragmentVideoviewPlayBindingprivate val binding get() = viewBindingFragmentVideoPlayBinding!!companion object {fun newInstance(videoPath: String): VideoViewPlayFragment {val fragment = VideoViewPlayFragment()val args = Bundle()args.putString("pathUrl", videoPath)fragment.arguments = argsLog.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