1.调节视频音量

VideoView并没有提供调节视频音量的接口,如果要调节音量,可以有两种方式实现。

1.1 AudioManager实现音量的管理

Android系统分为三种音量类型,一是闹钟(Alarm),二是铃声(Ring),三是媒体(Media),如下图所示。

Android 视频音量 手机视频音量调节_mediaplyaer

视频音量类型属于媒体(Media),如想要调节视频音量用AudioManager调用媒体类型的音量设置方法即可达到目的。

方法如下:

//第一步:获取AudioManager,context都有getSystemService方法
AudioManager audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
//第二步:调用调节音量的方法
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE,
AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI);

参数说明:

  • 参数1:AudioManager.STREAM_MUSIC,代表媒体类型的音量, 还有STREAM_RING代表铃声类型,STREAM_ALARM代表闹钟类型
  • 参数2: AudioManager.ADJUST_RAISE表示增加音量,相应的ADJUST_LOWER减少音量。
  • 参数3:AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_SHOW_UI,设置时的Flag,前者表示设置音量有声音提示,后者表示设置音量会弹出音量条界面。

上述方法只能每次增加或减少一格音量,无法做到设定特定的音量,如果想把媒体声音在静音与最大音量间互相切换,则需要调用AudioManager的另一个方法setStreamVolume(int streamType, int index, int flags),参数类型与上述加减音量的方法一致

示例如下:

//第一步:获取AudioManager对象
 AudioManager audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
//第二步:得到媒体的最大音量
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//第三步:获取媒体当前音量
int curVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    if (curVolume != maxVolume) {
        //第四步:如果不是最大音量,则将媒体音量设置成最大音量
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, AudioManager.FLAG_PLAY_SOUND);
    } else {
       //第五步: 如果是最大音量,则将媒体音量设置成0,即静音
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, AudioManager.FLAG_PLAY_SOUND);
    }

三种类型的音量范围:

  • AudioManager.STREAM_MUSIC:0~15
  • AudioManager.STREAM_RING:0~7
  • AudioManager.STREAM_ALARM:0~7

此方法的不足之外在于,它控制的是系统某个类型的音量,只要设置之后所有的媒体音量都是这样大小,可能会影响到其它程序的播放,如音乐。

1.2 持有一个MediaPlayer对象用于设置音量

MediaPlyaer有一个音量设置的API,可设置左右声道的输出音量,音量取值范围为0~1,0表示静音,1表示最大音量。

public void setVolume(float leftVolume, float rightVolume)

现在需要做的就是从VideoView中获取到一个MediaPlayer对象来调用此方法,有两种方式可以拿到一个MediaPlayer对象。

方法一:从播放回调中保存一个对象

最好在onInfoListener中的回调中保存一个对象,因为这个回调只有在视频播放时才会触发,调节音量也是在有视频播放时才有意义。

//用成员变量保存一个MediaPlayer对象
private MediaPlayer mMediaPlayer;
//音量大小默认100%
private int mCurrentVolume = 100;

vvMain.setOnInfoListener(new MediaPlayer.OnInfoListener() {
            @Override
            public boolean onInfo(MediaPlayer mp, int what, int extra) {
                //在视频播放信息回调中保存一个mediaplayer实例
                mMediaPlayer = mp;
                return false;
            }
        });

vvMain.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                //视频播放完毕如果不是设置的循环播放,则要将mMediaPlayer对象置空
                if(mp.isLooping()){
                    LogUtils.dTag(TAG,"视频循环播放!");
                }else {
                    mMediaPlayer = null;
                }
                
            }
vvMain.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                //当播放出现异常时,要记得将mediaplyaer置空,避免内存泄漏
                mMediaPlayer = null;
                //返回值设置为false,依然会弹窗提示错误,返回值为true,则不会弹,但也不会再调用onCompletion
                return true;
            }
        });  
 
 //增加音量方法       
public void increase(View view) {
    //这里注意一定不要用mMediaPlayer.isPlay方法判断视频是否在播放,后很容易出现状态异常,当mMediaPlayer被释放再调用判断状态的方法就会报错
    if(mMediaPlayer!=null && vvMain.isPlaying()){
            if( mCurrentVolume == 100 ){
                return;
            }
            mCurrentVolume+=10;
            //音量的取值范围为0~1,小数数值,用整数加减再除以100的方式获得小数值是最合适的,如果用小数值进行加减很容易浮点数运算不精确
            mMediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
    }
}
 //减少音量方法       
public void decrease(View view) {
    if(mMediaPlayer!=null && vvMain.isPlaying()){
            if( mCurrentVolume == 0 ){
                return;
            }
            mCurrentVolume-=10;
            mMediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
    }
}

  @Override
    protected void onPause() {
        super.onPause();
        vvMain.stopPlayback();
        //当activity不在前台时置空
        mMediaPlayer = null;
    }

运用此方法设置视频的音量,一定要注意mMediaPlayer对象的生命周期处理,不然容易出现较难察觉的内存泄漏。

方法二:运用反射获取VideoView中封装的MediaPlayer对象

VideoView中持有一个MediaPlayer对象,只要运用反射获取此对象的引用即可像上述方式一样设置视频的音量。

private MediaPlayer mMediaPlayer = null;

反射获取MediaPlayer对象方法

private MediaPlayer getReflectMediaPlayer(){
        try {
            Class clazz = Class.forName("android.widget.VideoView");
            Field field = clazz.getDeclaredField("mMediaPlayer");
            field.setAccessible(true);
            return (MediaPlayer) field.get(vvMain);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

接下来用反射获取的对象去设置音量即可,同方法一一致,用反射方法不用额外处理MediaPlayer对象的生命周期,在方法体中的对象用过即消失。反射的方式更加简洁,但会对性能有些影响。

2.VideoView在界面中的显示大小与视频源大小的关系

当我们在界面中放置VideoView控件时,会设置宽高属性,但视频源本身也是有一个宽高的,这两者有何关系呢?界面中显示的VideoView大小到底会如何显现,下面就用一个视频源大小与控件大小的关系表格来说明。

屏幕的宽高像素为: 1080*1920

视频源的宽高像素为: 1920*1080/1.78:1

屏幕是竖屏的,视频源是横屏的。

控件设置

显现的大小

宽200dp,高200dp

550*309/1.78:1

宽250,高200dp

688*387/1.78:1

宽200,高250dp

550*309/1.78:1

宽wrap,高wrap

1080*607/1.78:1

宽wrap,高match

1080*1700/0.64:1

宽match,高wrap

1080*607/1.78:1

宽match,高match

1080*607/1.78:1

除了宽wrap,高match这个设置外,其它的设置最终显示都让视频的宽高比例等到了保持,所以VideoView是会调整控件大小以达到显示与原视频宽高比一致的效果。

下面从源码的角度进行分析,VideoView到底是如何调整的,主要是在onMeasure方法中进行调整的。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
        //        + MeasureSpec.toString(heightMeasureSpec) + ")");
      
        //MeasureSpec.EXACTLY 是精确尺寸,用以精确数值和match_parent
        //MeasureSpec.AT_MOST 是最大尺寸,用以wrap_content
        //MeasureSpec.UNSPECIFIED 是未确定模式,多用于scrollview
         //获取控件的测量宽度和高度,如设置的EXACTLY和AT_MOST,则宽和高即为该确定值
         //如模式是MeasureSpec.UNSPECIFIED,则宽高为视频的原始值
        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
        //如果视频的宽和高都大于0,该值由mediaplayer解码而来
        if (mVideoWidth > 0 && mVideoHeight > 0) {
            //再次获取控件的测量模式和测量宽高值
            int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
            //如果宽高模式都是精确值
            if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
                // the size is fixed ,控件大小就是测量值
                width = widthSpecSize;
                height = heightSpecSize;

                // for compatibility, we adjust size based on aspect ratio
                //此句翻译是,我们根据宽高比调整大小
                 //假如控件宽高为480*640,视频宽高为1080*1920
                //如果视频的宽*控件高 < 控件宽*视频高,(ex:1080*640=691200,1920*480=921600)
                if ( mVideoWidth * height  < width * mVideoHeight ) {
                    //Log.i("@@@", "image too wide, correcting");如果控件太宽,需要调整
                    //宽度值被被赋值为:控件高*视频宽/视频高,即是控件宽高比=视频宽高比
                    width = height * mVideoWidth / mVideoHeight;
                } else if ( mVideoWidth * height  > width * mVideoHeight ) {
                     //如果视频的宽*控件高 >控件宽*视频高
                    //Log.i("@@@", "image too tall, correcting");如果控件太高需要调整
                    //高度值被赋值为:控件宽*视频高/视频宽,保持视频的纵横比
                    height = width * mVideoHeight / mVideoWidth;
                }
            } else if (widthSpecMode == MeasureSpec.EXACTLY) {
                // only the width is fixed, adjust the height to match aspect ratio if possible
                //只有宽度固定,如果可能,调整高度以匹配纵横比
                //宽度等于测量值
                width = widthSpecSize;
                //高度赋值为视频纵横比*控件宽度
                height = width * mVideoHeight / mVideoWidth;
                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                    //如果高的测量模式是AT_MOST类型,并且根据纵横比新赋的值大于测量值
                    // couldn't match aspect ratio within the constraints,
                    //无法在此约束内匹配纵横比,即是测量值是上限值,不能超过这个值
                    //高度重新赋值回测量值
                    height = heightSpecSize;
                }
            } else if (heightSpecMode == MeasureSpec.EXACTLY) {
                // only the height is fixed, adjust the width to match aspect ratio if possible
                //只有高度固定,如果可能,调整宽度以匹配纵横比
                //高度等于测量值
                height = heightSpecSize;
                //宽度赋值为控件高*视频纵横比
                width = height * mVideoWidth / mVideoHeight;
                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                    // couldn't match aspect ratio within the constraints
                     //如果宽的测量模式是AT_MOST类型,并且根据纵横比新赋的值大于测量值
                     //宽度重新赋值回测量值
                    width = widthSpecSize;
                }
            } else {
                // neither the width nor the height are fixed, try to use actual video size
                //无论宽度还是高度都不固定,请尝试使用实际的视频大小
                //宽高值为源视频宽高值
                width = mVideoWidth;
                height = mVideoHeight;
                //保持纵横比缩放,当视频高度大于测量高度值时
                if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
                    //如果高度测量模式为AT_MOST且视频高度大于测量值
                    // too tall, decrease both width and height,过高则宽和高都一起缩小
                    //高度等于测量值
                    height = heightSpecSize;
                    //宽度等于视频纵横比*高
                    width = height * mVideoWidth / mVideoHeight;
                }
                if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
                //如果宽度测量模式为AT_MOST且视频宽度大于测量值
                    // too wide, decrease both width and height
                    //宽度等于测量值
                    width = widthSpecSize;
                    //高度等于视频纵横比*宽
                    height = width * mVideoHeight / mVideoWidth;
                }
                
            }
        } else {
            // no size yet, just adopt the given spec sizes
            //如果视频宽高未获取到,只能用给定的测量值
        }
        //最后就是设置最终在界面显示的宽高大小
        setMeasuredDimension(width, height);
    }

由此可以得出如下几个结论:

  • VideoView控件为固定大小时,不管是宽大还是高比较多,最后一定会调整大小以适应宽高比
  • 当其一属性为固定值,另一属性为适应值wrap_content时,固定值不变,适应值会尝试调整大小,但调整后的大小不能超过屏幕的最大值,否则还是使用测量值。如宽wrap,高match会呈现1080*1700/0.64:1,因为源视频是宽大于高的,现高固定为1700即屏幕高度,宽如若调整必大于1700,但屏幕宽度也就1080,达不到调整的要求,所以最后还是使用了测量值即是1080。
  • 当两者属性都为适应值时,会优先使用视频源大小,但如果超过屏幕大小,会将大于的那一边固定大小,另一边进行视频宽高比的缩放。

特别注意点:当父布局是约束布局ConstraintLayout时,宽高都是match_parent时无法达到预期保持宽高比的效果,实际的显示是直接填满了屏幕,但在其它如FrameLayout和LineraLayout中是按预期显示的,可能与约束布局的测量有关,还未做进一步的研究,暂时无法得出具体原因。

3.视频缓冲时进行提示

这个功能适用于当加载的是网络视频,且体积比较大时。
实现也比较简单,主要是监听onInfoListener,根据里面的回调参数值来决定当前的响应策略。

vvMain.setOnInfoListener(new MediaPlayer.OnInfoListener() {
            @Override
            public boolean onInfo(MediaPlayer mp, int what, int extra) {
                //视频需要缓冲
                if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){
                    //显示缓冲提示
                    ToastUtils.showShort("视频缓冲中...");
                }else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
                    //视频缓冲完毕
                    ToastUtils.showShort("视频缓冲完毕...");
                }
                return false;
            }
        });

4.最后附上demo的代码以供大家参考

package cn.pigdreams.videoviewdemo;

import android.app.Service;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;

import androidx.appcompat.app.AppCompatActivity;

import com.blankj.utilcode.constant.PermissionConstants;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.PermissionUtils;
import com.blankj.utilcode.util.ToastUtils;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private VideoView vvMain;
    /**
     * 动态视频的存放地址
     */
    private String mDynamicVideo;
    /**
     * 静态视频的存放地址
     */
    private String mStaticVideo;
    private MediaPlayer mMediaPlayer;
    private TextView tvInfo;
    private int mCurrentVolume = 100;
    private boolean isFirst;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
        setListeners();
        setData();
    }


    private void initView() {
        vvMain = findViewById(R.id.vv_main);
        tvInfo = findViewById(R.id.tv_info);
    }

    private void initData() {
//        mDynamicVideo = PathUtils.getExternalMoviesPath()+"/shupin.mp4";
        mDynamicVideo = PathUtils.getExternalMoviesPath() + "/dync.mp4";
        //static
        mStaticVideo = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/shupin.mp4";
        PermissionUtils.permission(
                PermissionConstants.STORAGE
        ).request();
        //此处的context一定要传入activity,不然会崩溃的程序,因为没有windows
        // android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
        MediaController mediaController = new MediaController(this);
        mediaController.setPrevNextListeners(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //下一首,实现具体的切换逻辑
                ToastUtils.showShort("点击了下一首");
            }
        }, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //上一首,实现具体的切换逻辑
                ToastUtils.showShort("点击了上一首");
            }
        });
        vvMain.setMediaController(mediaController);
        vvMain.setZOrderOnTop(true);
    }


    private void setData() {
        String videoPath = mDynamicVideo;
//        videoPath = "http://vfx.mtime.cn/Video/2019/07/12/mp4/190712140656051701.mp4";
//        String videoPath= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)+"/dync.mp4";
        vvMain.setVideoPath(videoPath);
        //播放视频路径:/storage/emulated/0/Moviesdync.mp4
        LogUtils.dTag(TAG, "播放视频路径:" + videoPath);
//       vvMain.setBackgroundColor(getResources().getColor(R.color.white_full));
//       vvMain.start();
    }

    private void setListeners() {
        vvMain.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                LogUtils.dTag(TAG, "视频准备ok", "width=" + mp.getVideoWidth(), "height=" + mp.getVideoHeight());
                LogUtils.dTag(TAG, "控件宽=" + vvMain.getWidth() + ",高=" + vvMain.getHeight());
                mp.start();
            }
        });
        vvMain.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if(mp.isLooping()){
                    LogUtils.dTag(TAG,"视频循环播放!");
                }else {
                    mMediaPlayer = null;
                }
                if(isFirst){
                    vvMain.setVideoPath(mStaticVideo);
                    isFirst = false;
                }else {
                    vvMain.setVideoPath(mDynamicVideo);
                    isFirst = true;
                }
                vvMain.start();
                LogUtils.dTag(TAG, "视频播放完毕");

//                mp.start();
                //调用start也可以
                //调用resume方法亦可重复播放
//                vvMain.resume();
            }
        });
        vvMain.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                LogUtils.dTag(TAG, "视频播放异常", "what=" + what, "extra=" + extra);
                mMediaPlayer = null;
//                vvMain.stopPlayback();
                //返回值设置为false,依然会弹窗提示错误,返回值为true,则不会弹,但也不会再调用onCompletion
                return true;
            }
        });
        vvMain.setOnInfoListener(new MediaPlayer.OnInfoListener() {
            @Override
            public boolean onInfo(MediaPlayer mp, int what, int extra) {
                //视频需要缓冲
                if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){
                    //显示缓冲提示
                    ToastUtils.showShort("视频缓冲中...");
                }else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
                    //视频缓冲完毕
                    ToastUtils.showShort("视频缓冲完毕...");
                }
                mMediaPlayer = null;
                mMediaPlayer = mp;
                LogUtils.dTag(TAG, "视频播放INFO", "what=" + what, "extra=" + extra);
                return false;
            }
        });

    }

    @Override
    protected void onPause() {
        super.onPause();
        vvMain.stopPlayback();
        mMediaPlayer = null;
//        if(vvMain.canPause()){
//            vvMain.pause();
//        }
        LogUtils.dTag(TAG, "触发pause");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
//        vvMain.resume();
        LogUtils.dTag(TAG, "触发restart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        LogUtils.dTag(TAG, "触发onResume");
//        vvMain.resume();
//        vvMain.start();
    }

    public void getState(View view) {
        String state = vvMain.isPlaying() ? "播放中" : "未播放!";
        LogUtils.dTag(TAG, state);
        ToastUtils.showLong(state);
    }

    public void stop(View view) {
        vvMain.stopPlayback();
//        vvMain.suspend();
    }

    public void pause(View view) {
        vvMain.pause();
    }

    public void play(View view) {
        vvMain.start();
    }

    public void resume(View view) {
        vvMain.resume();
    }

    public void suspend(View view) {
        vvMain.suspend();
    }

    public void position(View view) {
        if (!vvMain.isPlaying()) {
            ToastUtils.showShort("视频未播放");
            return;
        }
        String info = "total=" + vvMain.getDuration() + "\npos=" + vvMain.getCurrentPosition();
        tvInfo.setText(info);
    }

    public void buffer(View view) {
        if (!vvMain.isPlaying()) {
            ToastUtils.showShort("视频未播放");
            return;
        }
        String percent = "缓冲进度=" + vvMain.getBufferPercentage();
        tvInfo.setText(percent);
    }

    public void increase(View view) {
        //通过audioManager设置全局音量
//        increaseByAudioManager();
        //通过持有回调中的对象设置音量
//        increaseByMediaPlayer();
        //通过反射获取对象设置音量
        increaseByReflect();
    }

    public void decrease(View view) {
//        decreaseByAudioManager();
//        decreaseByMediaPlayer();

        decreaseByReflect();
    }

    private void decreaseByAudioManager(){
        AudioManager audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
        int beforeVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER,
                AudioManager.FLAG_PLAY_SOUND);
        int afterVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        LogUtils.dTag(TAG, "设置前=" + beforeVolume + ",设置后=" + afterVolume);
    }

    private void decreaseByMediaPlayer(){
        if(mMediaPlayer!=null && vvMain.isPlaying()){
            if(mCurrentVolume == 0  ){
                return;
            }
            mCurrentVolume-=10;
            mMediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
        }
    }

    private void decreaseByReflect(){
        long beforeTime = System.currentTimeMillis();
        MediaPlayer mediaPlayer = getReflectMediaPlayer();
        LogUtils.dTag(TAG,"反射获取对象时间:"+(System.currentTimeMillis()-beforeTime));
        if(mediaPlayer!=null && vvMain.isPlaying()){
            if(mCurrentVolume == 0  ){
                return;
            }
            mCurrentVolume-=10;
            mediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
        }
    }


    private void increaseByAudioManager(){
        AudioManager audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
        int beforeVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE,
                AudioManager.FLAG_PLAY_SOUND);
        int afterVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        LogUtils.dTag(TAG, "设置前=" + beforeVolume + ",设置后=" + afterVolume);
    }

    private void increaseByMediaPlayer(){
        if(mMediaPlayer!=null && vvMain.isPlaying()){
            if( mCurrentVolume == 100 ){
                return;
            }
            mCurrentVolume+=10;
            mMediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
        }
    }

    private void increaseByReflect(){
        MediaPlayer mediaPlayer = getReflectMediaPlayer();
        if(mediaPlayer!=null && vvMain.isPlaying()){
            if( mCurrentVolume == 100 ){
                return;
            }
            mCurrentVolume+=10;
            mediaPlayer.setVolume(mCurrentVolume/100F,mCurrentVolume/100F);
            LogUtils.dTag(TAG,"当前音量="+mCurrentVolume/100F);
        }
    }


    public void mute(View view) {
//        AudioManager audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
//        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//        int curVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
//        LogUtils.dTag(TAG, "最大音量=" + maxVolume + ",当前音量=" + curVolume);
//        if (curVolume != maxVolume) {
//            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, AudioManager.FLAG_PLAY_SOUND);
//        } else {
//            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, AudioManager.FLAG_PLAY_SOUND);
//        }
        if(mMediaPlayer!=null && vvMain.isPlaying()){
            mMediaPlayer.setVolume(0,1);
            ToastUtils.showShort("设置静音!");
        }
    }

    public void restoreVolume(View view) {
        if(mMediaPlayer!=null && vvMain.isPlaying()){
            mMediaPlayer.setVolume(1,0);
            ToastUtils.showShort("恢复音量!");
        }
    }

    private MediaPlayer getReflectMediaPlayer(){
        try {
            Class clazz = Class.forName("android.widget.VideoView");
            Field field = clazz.getDeclaredField("mMediaPlayer");
            field.setAccessible(true);
            return (MediaPlayer) field.get(vvMain);
        } catch (Exception e) {
            LogUtils.eTag(TAG,"反射错误:"+e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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"
    tools:context=".MainActivity">

    <VideoView
        android:id="@+id/vv_main"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="12dp"
        android:onClick="getState"
        android:text="state"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:onClick="stop"
        android:text="stopplayback"
        app:layout_constraintBottom_toBottomOf="@+id/button3"
        app:layout_constraintStart_toEndOf="@+id/button3" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:onClick="pause"
        android:text="pause"
        app:layout_constraintBottom_toBottomOf="@+id/button"
        app:layout_constraintStart_toEndOf="@+id/button" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="8dp"
        android:onClick="play"
        android:text="start"
        app:layout_constraintBottom_toTopOf="@+id/button4"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginBottom="8dp"
        android:onClick="resume"
        android:text="resume"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:layout_marginBottom="8dp"
        android:onClick="suspend"
        android:text="suspend"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/button4" />

    <Button
        android:id="@+id/button6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginBottom="12dp"
        android:text="position"
        android:onClick="position"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintStart_toEndOf="@+id/button7" />

    <Button
        android:id="@+id/button8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="bufpercent"
        android:onClick="buffer"
        app:layout_constraintBottom_toBottomOf="@+id/button6"
        app:layout_constraintStart_toEndOf="@+id/button6" />

    <TextView
        android:id="@+id/tv_info"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:textColor="#0E0303"
        app:layout_constraintStart_toEndOf="@+id/button5"
        app:layout_constraintTop_toBottomOf="@+id/button2" />

    <Button
        android:id="@+id/button9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="8dp"
        android:onClick="increase"
        android:text="Volum+"
        app:layout_constraintBottom_toTopOf="@+id/button7"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:onClick="decrease"
        android:text="VOLUM-"
        app:layout_constraintBottom_toBottomOf="@+id/button9"
        app:layout_constraintStart_toEndOf="@+id/button9" />

    <Button
        android:id="@+id/button11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:onClick="mute"
        android:text="mute"
        app:layout_constraintBottom_toBottomOf="@+id/button10"
        app:layout_constraintStart_toEndOf="@+id/button10" />

    <Button
        android:id="@+id/button12"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="4dp"
        android:onClick="restoreVolume"
        android:text="restore"
        app:layout_constraintStart_toEndOf="@+id/button11"
        app:layout_constraintTop_toTopOf="@+id/button11" />


</androidx.constraintlayout.widget.ConstraintLayout>