Android进阶之路 - VideoView从入门到实践

在之前的项目中其实很少有用到VideoView的场景,只是去年看书的时候有看到音视频的一篇功能介绍,趁着最近有时间就学习整理了一下关于VideoView的blog ~

基础效果

其实实际效果比你当前看的效果要好,因为视频缓存过后的加载都比较快 ~
在这里插入图片描述

基础知识

在Android中的视屏功能,大部分使用的都是VideoView控件,关于控制VideoView的方法,一般除了其本身自带方法以外,都搭配了MediaController操作视频,这里主要记录我学习的一个过程和结果 ~

VideoView控件引用

    <VideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="200dp"
	 />
基础方法

VideoView、MediaController 常用方法

method含义
start()开始播放视频
pause()暂停播放视频
resume()重新播放
getDuration()获取视屏时长
getCurrentPosition()获取当前已播放视频时长
setVideoPath()设置视频文件路径
setVideoURI()设置视频文件路径
seekTo(int pos)滑动到指定播放进度
isPlaying()判断视屏是否在播放
canPause()是否禁用暂停按钮功能
canSeekBackward()是否禁用滑动进度条功能
canSeekForward()是否功能快进功能
getAudioSessionId()获取音频会话ID
suspend()将VideoView所占用的资源释放掉

如何区别VideoView、MediaController的方法?

直接通过 MediaController 源码可以看到以下这些接口方法就都是 MediaController 的方法咯

在这里插入图片描述

视频来源

VideoView播放的视屏来源,主要有以下三种

  1. 本地内存(加载快,无卡顿,不过资源单一,如果要提速的话,可以将网络资源下载到本地)
   //加载本地视频,每个人存储地址不同,新手别抄 ~
   File file = new File(Environment.getExternalStorageDirectory() + "/video.mp4");
   if (file.exists()) {
       //设置视频地址
       mVideo.setVideoPath(file.getAbsolutePath());
   } else {
       Toast.makeText(this, "视频不存在", Toast.LENGTH_SHORT).show();
   }
  1. 项目raw资源(加载快,无卡顿,不过资源单一)
   //加载项目内视频, "xxx": 视频名称(在res目录下新建raw,将xxx视频放在raw文件夹中)
   Uri uri = Uri.parse("android.resource://$packageName/${R.raw.xxx}");
   mVideo.setVideoURI(uri);
  1. 网络加载(资源丰富,不过随着视频的大小,加载的时间也不尽相同,可以采用预加载优化用户体检验)
   //加载网络视频,记得适配 6.0,7.0,9.0
   String videoPath = "https://vfx.mtime.cn/Video/2019/07/12/mp4/190712140656051701.mp4";
   mVideo.setVideoPath(videoPath);

开发实践

此处仅为一个初级使用的小demo,会有部分思考的问题

注意

  • 需自行适配6.0动态权限
  • 需自行适配7.0临时授权

AndroidMainfest 加入权限

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.INTERNET"/>

MainActivity

package nk.com.video;

import android.content.res.Configuration;
import android.media.MediaPlayer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;


public class MainActivity extends AppCompatActivity {

    private VideoView mVideo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mVideo = findViewById(R.id.video);
        //网络加载 - 视屏地址
        String videoPath = "https://vfx.mtime.cn/Video/2019/07/12/mp4/190712140656051701.mp4";
		//视屏控制器
        MediaController mediaController = new MediaController(MainActivity.this);
        //VideoView绑定控制器
        mVideo.setMediaController(mediaController);
        //设置视频地址
        mVideo.setVideoPath(videoPath);
        //获取焦点
        mVideo.requestFocus();
        //播放视频
        mVideo.start();
       }
   }

activity_main

<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    tools:context=".MainActivity">

    <VideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

视频控制器

在正式开发中很少有用到原始的MediaController控制View,一般都会定制化视图Controller ~

原始效果与自定义效果
  • MediaController自带的控制器
    在这里插入图片描述
  • 效果 - 自定义Controller操作视图
    在这里插入图片描述
实现过程
  1. 隐藏原始控制器
 //不设置以下控制器属性
 mVideo.setMediaController(mediaController);
 //如果已设置,则通过mediaController 隐藏操作栏
 mediaController.setVisibility(View.GONE);
  1. 自定义控制器视图

这里我仅写了一个基础事件来进行效果展示,如果要写的完善一点可以参考原始封装的MediaController类

伪代码

		//Video基础设置
		tring videoPath = "https://media.w3.org/2010/05/sintel/trailer.mp4";
        MediaController mediaController = new MediaController(MainActivity.this);
        mVideo.setVideoPath(videoPath);
        mVideo.requestFocus();
        mVideo.start();
		//自定义控制器功能
        TextView pause = findViewById(R.id.pause_start);
        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mVideo.isPlaying()){
                    mVideo.pause();
                    pause.setText("播放");
                }else{
                    mVideo.start();
                    pause.setText("暂停");
                }
            }
        });

伪代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    
    <VideoView
        android:layout_below="@+id/tv_display"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:id="@+id/video"
        />

    <TextView
        android:layout_centerHorizontal="true"
        android:background="#ff0"
        android:layout_width="35dp"
        android:gravity="center"
        android:id="@+id/pause_start"
        android:layout_height="35dp"
        android:text="暂停"
        />
</RelativeLayout>

功能扩展

从 VideoView + 默认MediaController实现的功能来看,其主要覆盖视频播放、暂停、快进、后退、上一首、下一首的功能,这里首先讲一下这些带给我的思考 ~

Look here:我在解决VideoView相关功能时查看MediaController,发现MediaController本身就是系统封装好的一款自定义控件,所以遇到问题直接通过此类排查问题就行 ~

视频停止,释放资源

以下方法都有视频停止并且释放内存的作用,但又稍有不同

  • stopPlayback() 通过底层代码可以发现并没有重置,所以应该只是释放内存,并没有释放配置资源
  • suspend() 则更加彻底的释放了所有配置信息和内存.
 //停止播放视频,并且释放
 mVideoView.stopPlayback();
 //在任何状态下释放媒体播放器
 mVideoView.suspend();
保持屏幕常亮

音视频开发的基本操作,在xml的根布局添加以下属性

 android:keepScreenOn="true"
循环播放

关于音视频循环播放的方式,主要有以下三种

  • 方式1:视频播放完成后,在onCompletion进行监听,重新播放视频

主要调用VideoView的resume()方法

    mVideo.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
       @Override
       public void onCompletion(MediaPlayer mp) {
            mVideo.resume();
       }
    });
  • 方式2:通过MediaPlayer控制器实现循环播放
 mp.setLooping(true);

这种方式 - 方法可用在OnPreparedListener加载回调OnInfoListener视频信息回调,但不能用在OnCompletionListener播放完毕的回调中,在播放完毕时再调用此方法并不会让视频循环播放。

还有,设置了视频循环播放后,下一轮的播放不会再触发OnPreparedListener和OnInfoListener,但一样会触发在OnCompletionListener和异常回调。

  • 方式3:这三种方式当视频重复播放时都会触发信息回调,与mp.setLooping(true)不太一样。
   mVideo.setOnInfoListener(new MediaPlayer.OnInfoListener() {
            @Override
            public boolean onInfo(MediaPlayer mp, int what, int extra) {
                return false;
            }
        });

但VedioView.resume()方法还会触发加载状态回调OnPreparedListener,所以说resume()方法其实是再一次加载了这个视频内容,然后从头开始播放,与前二种方式是有所区别,前两种方式是再次播放已经加载好的视频,所以不会再触发OnPreparedListener这个回调。

监听上一首、下一首切换功能

主要调用MediaController控件封装的PrevNextListeners切换监听

    mediaController.setPrevNextListeners(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("tag", "下一首");
        }
    }, new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e("tag", "上一首");
        }
    });
监听视频是否播放完毕

VideoView提供了获取视频当前播放时长、总时长,以及状态监听的功能

    mVideo.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
       @Override
       public void onCompletion(MediaPlayer mp) {
           Log.e("tag","播放完成");
       }
    });
获取视频当前播放时长与视频总时长

主要涉及getCurrentPosition、getDuration俩个方法,其实在基础部分都有讲明 ~

    int currentPosition = mVideo.getCurrentPosition();
    Log.e("tag", "当前时长:" + currentPosition);
    int duration = mVideo.getDuration();
    Log.e("tag", "视频时长:" + duration);
隐藏视频操作栏,如隐藏切换、快进、播放等功能

方式一:VideoView不调用setMediaController()即可

在这里插入图片描述

方式二:MediaController本身就是自定义控件,直接用setVisibility()即可

 //隐藏操作栏
 mediaController.setVisibility(View.GONE);

示例

在这里插入图片描述

重新设定快进、后退时间

嗯哼,我找了挺多blog发现都没有人讲这方面的功能,我尝试在 MediaController 找重写快进、后退的方法,发现并没有... 后来发现源码中直接把快进时间和后退时间写固定了,具体如下

MediaController 关于快进功能的实现

在这里插入图片描述

MediaController 关于后退功能的实现

在这里插入图片描述

好吧,既然不支持重写,我想了俩种方法,不过这俩种方式最终没有实现快进、后退的时间修改,仅作为思想延伸,想法扩展的记录

方式一(无效):自己往 MediaController 里面加一加重写快进、后退的代码(因为没锁,所以可编译)

在这里插入图片描述

虽然在原始代码中加了设置方式,但是系统压根不识别,所以此路无效

在这里插入图片描述

方式二(无效):因为第一种方法无效,所以我只能copy出原始MediaController,然后自己改改咯(copy后有很多报错,首先要更改调用的包名,其次删除部分无用功能,我们主要尝试去重写快进、后退方法)

在这里插入图片描述

在上方我们已经写好我们的视频控制类了,然后然后… 什么鬼!白写!!!系统压根不认 ~ 无语,当然我们还有一种方式,就是弃用他的控制类,我们自己写一套操作视频的布局和功能(自定义控件也行 ~)

在这里插入图片描述

横竖屏适配

很多视频相关功能都支持用户横竖屏观看,根据以下设置后,我发现横竖屏并不会影响到视频重播

常规思路:监听用户横竖屏改变时,记录用户视频的播放进度,当方向转换后,重新将视频播放进度设置到之前记录的播放进度处

考虑问题

  • 横竖屏监听
  • 视频播放进度记录与设置

视频适配

  1. 修改承载横竖屏Activity的configChanges属性,如下
 android:configChanges="orientation|keyboard|layoutDirection|screenSize"

示例

 <activity android:name=".MainActivity"
     android:configChanges="orientation|keyboard|layoutDirection|screenSize">
     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
 </activity>
  1. 对应Activity内重写onConfigurationChanged监听屏幕方向的改变
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            Toast.makeText(getApplicationContext(), "横屏", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getApplicationContext(), "竖屏", Toast.LENGTH_SHORT).show();
        }
    }
长按快进、快退功能实践

常规的长按事件处理,只能触发一次,即点击一次,快进一次(不适用于当前场景)

implements View.OnLongClickListener,
 
mPreviousView.setOnLongClickListener(this)

@Override
public boolean onLongClick(View view) {
    if (view == mNextView) {
        if (null != mPlaylistListener) {
            Log.d("hrl", "onLongClick");
            mPlaylistListener.onFastPlay();
            return true;
        }
    }
    if (view == mPreviousView) {
        if (null != mPlaylistListener) {
            Log.d("hrl", "onLongClick");
            mPlaylistListener.onBackPlay();
            return true;
        }
    }
    return false;
}

适用方案 - 自定义VideoView中加入以下设置(不可完全copy,需要有选择的借鉴)

private int mLastMotionX, mLastMotionY;
//是否移动了
private boolean isMoved;
//长按的runnable
private Runnable mLongPressFastRunnable = new Runnable() {
 
    @Override
    public void run() {
       //调用快进函数
        mPlaylistListener.onFastPlay();
        Log.d("hrl", "mLongPressFastRunnable");
       //若是postDelay(),会发生左右摇摆的现象,原因是获取当前位置的时候会出现延时,使得俩次postDelay获取到的pos一致。
        post(this);
    }
};

private Runnable mLongPressBackRunnable = new Runnable() {
 
    @Override
    public void run() {
        mPlaylistListener.onBackPlay();
        Log.d("hrl", "mLongPressBackRunnable");
        post(this);
    }
};

//移动的阈值
private static final int TOUCH_SLOP = 20;
private boolean longPress = false;
 
@Override
public boolean onTouch(View view, MotionEvent event) {
    if (view == mNextView) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d("hrl", "mNextView ACTION_DOWN");
                mLastMotionX = x;
                mLastMotionY = y;
                isMoved = false;
                postDelayed(mLongPressFastRunnable, ViewConfiguration.getLongPressTimeout());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("hrl", "mNextView ACTION_MOVE");
                if (isMoved) break;
                if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
                        || Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
                    //移动超过阈值,则表示移动了
                    isMoved = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d("hrl", "mNextView ACTION_UP");
                removeCallbacks(mLongPressFastRunnable);
                break;
        }
    }
    
    if (view == mPreviousView) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d("hrl", "mNextView ACTION_DOWN");
                mLastMotionX = x;
                mLastMotionY = y;
                isMoved = false;
                postDelayed(mLongPressBackRunnable, ViewConfiguration.getLongPressTimeout());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("hrl", "mNextView ACTION_MOVE");
                if (isMoved) break;
                if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
                        || Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
                    //移动超过阈值,则表示移动了
                    isMoved = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d("hrl", "mNextView ACTION_UP");
                removeCallbacks(mLongPressBackRunnable);
                break;
        }
    }
    return false;
}
解决播放视频时,采用三方应用播放音乐,导致音视频声音并发的问题

借鉴的2016年以为前辈的blog,具体效果未亲自尝试

解决音视频并发问题,可以在自定义的xxxVideoView中或视频播放的xxxActivity中添加如下代码;

	//用AudioManager获取音频焦点避免音视频声音并发问题
	private AudioManager mAudioManager;
	private OnAudioFocusChangeListener mAudioFocusChangeListener;

	//在播放视频的时候请求音频焦点,第三方应用在失去音频焦点后会暂停播放(音视频应用一般都会遵守音频焦点机制,在失去焦点的回调中做暂停等处理);
	@Override
	public void start() {
    	if (requestTheAudioFocus() == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
			//焦点获取成功,播放操作
		}else {
			//提示用户关闭其他音频再播放,不然用户以为是bug呢...
		}
    }


	//在暂停视频、播放完成或退到后台时释放音频焦点;
	@Override
		public void pause() {
			releaseTheAudioFocus(mAudioFocusChangeListener);
			//暂停逻辑
		}

请求音频焦点,并设置监听器

	//请求音频焦点 设置监听
    private int requestTheAudioFocus() {
    	if (Build.VERSION.SDK_INT < 8) {//Android 2.2开始(API8)才有音频焦点机制
			return 0;
		}
    	if (mAudioManager == null) {
    		mAudioManager  = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
		}
    	if (mAudioFocusChangeListener == null) {
    		mAudioFocusChangeListener = new OnAudioFocusChangeListener() {//监听器
        		@Override
        		public void onAudioFocusChange(int focusChange) {
        			switch (focusChange) {
        			case AudioManager.AUDIOFOCUS_GAIN:
        			case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
					case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
						//播放操作
        				break;
        				
        			case AudioManager.AUDIOFOCUS_LOSS:
        			case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
        			case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
        				//暂停操作
        				break;
        			default:
        				break;
        			}
        		}
        	};
		}
		
    	//下面两个常量参数试过很多 都无效,最终反编译了其他app才搞定,汗~
    	int requestFocusResult = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
    			AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
    			AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    	
    	return requestFocusResult;
	}

释放音频焦点 - 暂停、播放完成或退到后台

    //暂停、播放完成或退到后台释放音频焦点
    private void releaseTheAudioFocus(OnAudioFocusChangeListener mAudioFocusChangeListener) {
		if (mAudioManager != null && mAudioFocusChangeListener != null) {
			mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
		}
	}

问题锦集

大多记载着我在项目中遇到的实战问题,有的问题往往会卡我半天,一天的 ~

每次加载视频时,会黑屏一刹那

通过查询很多人都是通过setOnPreparedListener()的setOnInfoListener()将VideoView背景设置成透明颜色,这确实是一种解决方案,但是我个人认为也可以通过动画的形式使切换视频加载的效果看起来更流畅~

        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
                    @Override
                    public boolean onInfo(MediaPlayer mp, int what, int extra) {
                        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START)
                            mVideoView.setBackgroundColor(Color.TRANSPARENT);
                        return true;
                    }
                });
            }
        });
无法加载视频,显示黑屏、‘无法播放此视屏’ 等

权限问题 - 不论 Video 是网络数据,亦或本地数据,加入以下权限基本都够用了

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.INTERNET" />

使用方式

首先关于视频加载的方式主要有俩种

  • setVideoPath(String path):加载 path 文件所代表的视频。
  • setVideoURI(Uri uri):加载uri所对应的视频。

常规设置ViewView的方式
关于所谓的两种加载方式都可以使用,因为内部实现一样,只是setVideoPaht将Uri.parse做了内部封装

 //VideoView基础设置
 tring videoPath = "https://media.w3.org/2010/05/sintel/trailer.mp4";
 //方式1:网络加载\本地加载 
 mVideo.setVideoPath(videoPath);
 //方式2:本地加载
 //mVideo.setVideoURI(Uri.parse(videoPath));
 mVideo.requestFocus();
 mVideo.start();

扩展:通过源码可以发现以上俩种方法其实本质是一样的,只是内部进行转换,最终实现setVideoURI的三参方法

   /**
     * Sets video path.
     *
     * @param path the path of the video.
     */
    public void setVideoPath(String path) {
        setVideoURI(Uri.parse(path));
    }

    /**
     * Sets video URI.
     *
     * @param uri the URI of the video.
     */
    public void setVideoURI(Uri uri) {
        setVideoURI(uri, null);
    }
    
    /**
     * Sets video URI using specific headers.
     *
     * @param uri     the URI of the video.
     * @param headers the headers for the URI request.
     *                Note that the cross domain redirection is allowed by default, but that can be
     *                changed with key/value pairs through the headers parameter with
     *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
     *                to disallow or allow cross domain redirection.
     */
    public void setVideoURI(Uri uri, Map<String, String> headers) {
        mUri = uri;
        mHeaders = headers;
        mSeekWhenPrepared = 0;
        openVideo();
        requestLayout();
        invalidate();
    }
视屏加载成功,但报出’无法播放此视屏’的弹框

这个问题断断续续卡了我小一天,后来无意之间看到了几年前的一篇blog中2015年的一句只言片语,才知道了折中方案...

我面临的场景就像上方说的一样,我视频可以正常加载,就是老出弹框…

问题分析

  • 既然视频可以正常加载,我们就可以排除权限问题和加载问题
  • 有的人说网上找的三方视频会出现保护机制?导致出现类似问题,那么是否可以理解为:出现问题的时机分为首次加载和二次加载?如果首次加载都没问题的话,二次加载按理也不会有问题。为了排除这方面的问题,我们可以自己随便拍一个视频传到后台或本地进行加载测试
  • 明确一下是自己的弹框,还是VideoView自带的弹框,如果是自己弹出去的框,自行修改逻辑就行,如果是系统弹出的,请看下方的问题处理

问题处理
在这里插入图片描述

在VideoView有个ErrorListener错误监听,我们手动监听后,直接返回true就好了...

   mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
      @Override
      public boolean onError(MediaPlayer mp, int what, int extra) {
          return true;
        }
  });

扩展:上面就是我遇到的问题,经过查询也有一种别的错误场景 ,放在下方当给大家做个思维扩展吧

在这里插入图片描述

流量消耗巨大,出现流量损失

我在物联网的售货机做过一个不太合适的视频逻辑展示,曾导致半天跑了1.7G的流量

首先要明确,出现该问题的场景

  • 视频可以正常加载
  • 使用的网络链接形式的加载方式
  • 是否针对同一个VideoView重复进行加载,因为每重新加载一次就消耗一次的流量(重要:因为针对我的业务是我是使用同一个VideoView加载不同的视频)

所以,我自己总结了一下,如何避免消耗用户大量流量 - 通过查询后,发现Github存在关于视频缓存的成熟三方框架 - AndroidVideoCache ,如有诉求,也可直接使用

  • 减少VideoView频繁加载网络视频的场景
  • 针对同一个VideoViiew需要加载多次时,最好做本地缓存
  • 因为视频流量普遍消耗较大,最好是下载到本地,然后加载本地资源
  • 可以将视频压缩后进行加载,如我们看视频时经常有清晰、高清、蓝光等画质(我虽未如此操作,但是这个方案我认为很成熟)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值