Android MediaPlayer+SurfaceView+自定义控制器实现视频播放

这篇具有很好参考价值的文章主要介绍了Android MediaPlayer+SurfaceView+自定义控制器实现视频播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android提供了多种视频播放的方式,如下:

1、MediaController+VideoView实现方式

这种方式是最简单的实现方式。VideoView继承了SurfaceView同时实现了MediaPlayerControl接口,MediaController则是安卓封装的辅助控制器,带有暂停,播放,停止,进度条等控件。通过VideoView+MediaController可以很轻松的实现视频播放、停止、快进、快退等功能。 

2、MediaPlayer+SurfaceView+MediaController控制器或VideoView+自定义控制器

这种方式多少都方便一点,要求不高的时候可以使用。

3、MediaPlayer+SurfaceView+自定义控制器

这种方式最为复杂,但可以自由控制播放器的大小、位置以及各种事件,更为灵活。                                                        

本demo基于Android11开发,实现了简单的视频播放功能,可以拖动进度条,快进/快退和播放上一个视频/播放下一个视频,同时通过悬浮窗的形式查看视频详情。点击视频任意处即可暂停,播放中三秒不操作隐藏所有按钮。

         android视频videoview播放器,android,音视频android视频videoview播放器,android,音视频

之所以采用MediaPlayer+SurfaceView来播放是因为拖动进度条可以跳到最近的一帧,而VideoView只能跳到关键帧,但这种播放的实现方式更为复杂,具体步骤如下:

1、创建MediaPlayer对象,让它加载指定的视频文件。可以是应用的资源文件或本地文件路径。并添加setOnPreparedListener,setOnVideoSizeChangedListener和setOnCompletionListener三个监听分别用来监听视频装载完成事件,视频大小改变事件(屏幕适配)和视频播放完毕事件。

mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                vseekBar.setProgress(0);
                vgress.setText(Tool.millisToStringShort(0));//mediaplay的时间要进行处理
                total=mediaPlayer.getDuration();
                vtotal.setText(Tool.millisToStringShort(total));//mediaplay的时间要进行处理
                vseekBar.setMax(total);
            }
        });
        mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { //尺寸变化回调
            @Override
            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                changeVideoSize();
            }
        });
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                vseekBar.setProgress(0);
                vpause.setVisibility(View.GONE);
                vplay.setVisibility(View.VISIBLE);
                relativeLayout1.setVisibility(View.VISIBLE);
                relativeLayout2.setVisibility(View.VISIBLE);
            }
        });

2、在界面布局文件中定义SurfaceView组件,并为SurfaceView的SurfaceHolder添加Callback监听器。监听器中继承surfaceCreated方法,在该方法中调用MediaPlayer的setDisplay()和setDataSource()将所播放的视频图像输出到指定的SurfaceView组件并利用prepareAsync()方法装载流媒体文件。利用setOnTouchListener为surfaceView设置点击监听,用来控制按钮的隐藏和点击暂停/播放。

surfaceHolder=surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            //当SurfaceView中Surface创建时回掉
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                mediaPlayer.reset();
                try {
                    mediaPlayer.setDisplay(holder);
                    mediaPlayer.setDataSource(context, Uri.parse(vPath)); 
                    mediaPlayer.prepareAsync();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //当SurfaceView的大小发生改变时候触发该方法
            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            }
            //Surface销毁时回调
            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                if (mediaPlayer!=null) {
                    mediaPlayer.stop();
                    mediaPlayer.release();
                }
            }
        });
//设置 surfaceView点击监听
        surfaceView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        if (mediaPlayer.isPlaying()) {
                            mediaPlayer.pause();
                            relativeLayout1.setVisibility(View.VISIBLE);
                            relativeLayout2.setVisibility(View.VISIBLE);
                            vplay.setVisibility(View.VISIBLE);
                            vpause.setVisibility(View.GONE);
                        } else {
                            mediaPlayer.start();
                            relativeLayout1.setVisibility(View.GONE);
                            relativeLayout2.setVisibility(View.GONE);
                            vplay.setVisibility(View.GONE);
                            vpause.setVisibility(View.GONE);
                        }
                        break;
                }
                return true;
            }
        });

3、调用callback接口来完成Handler的实例化,视频播放时接受消息传入并同步更新进度条和视频。同时为Seekbar组件添加setOnSeekBarChangeListener监听,用以实现进度条的拖动事件。

Handler handler=new Handler(new Handler.Callback() {   
        @Override
        public boolean handleMessage(@NonNull Message msg) {   /
            if(msg.what==1){        
                try {
                    nowtime=mediaPlayer.getCurrentPosition();
                    vgress.setText(Tool.millisToStringShort(nowtime));
                    vseekBar.setProgress(nowtime);
                    handler.sendEmptyMessageDelayed(1,10);   //设置延迟
                }catch (IllegalStateException e){
                    e.printStackTrace();
                }

            }
            return false;
        }
    });
public SeekBar.OnSeekBarChangeListener seekBarChangeListener=new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (!mediaPlayer.isPlaying()){
                mediaPlayer.seekTo(seekBar.getProgress(),MediaPlayer.SEEK_CLOSEST);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            handler.removeMessages(1);
            mediaPlayer.pause();
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            mediaPlayer.start();
            handler.sendEmptyMessage(1);
        }
    };

4、为其他按钮设置点击事件,快进快退只需要对当前进度条的时间进行加减,3秒后不操作隐藏按钮要用到handler.postDelayed()来实现延时。左右切换需要先调用ContentResolver.query()查询本地所有的视频,然后找到当前播放视频在所有视频列表中的索引,再重新加载切换后的mediaplay。

//用Thread类会报错
handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            relativeLayout1.setVisibility(View.GONE);
                            relativeLayout2.setVisibility(View.GONE);
                            vpause.setVisibility(View.GONE);
                            vplay.setVisibility(View.GONE);
                        }
                    }, 3000);

public static ArrayList<DataValue> getVideo() {
        Cursor cursor = null;
        cursor = ContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                null, null, null, MediaStore.Video.Media.DEFAULT_SORT_ORDER);
        while (cursor.moveToNext()) {
            String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));// 路径
            File file = new File(path);
            if (file == null || !file.exists()) {
                continue;
            }
            DataValue value = new DataValue();
            value.fileName = file.getName();
            if (file.getName().length()>=30){
                String name=value.fileName;
                String name1=name.substring(0,4);
                int b=name.lastIndexOf(".");
                String name2=name.substring(b,name.length());
                String name3=name1+"*********"+name2;
                value.fileName = name3;
            }
            value.filePath = path;
            Videolist.add(value);
        }
        return Videolist;
    }

5、将展示视频详情的布局及组件初始化,创建WindowManager对象并设置窗口对应的值,将设置好的值和布局添加进WindowManager对象。

private void initInfoView() {
        infoView = View.inflate(this, R.layout.information, null);
        nameTv = infoView.findViewById(R.id.tv_name);
        createTv = infoView.findViewById(R.id.tv_create);
        sizeTv = infoView.findViewById(R.id.tv_size);
        formatTv = infoView.findViewById(R.id.tv_format);
        resolutionTv = infoView.findViewById(R.id.tv_resolution);
        pathTv = infoView.findViewById(R.id.tv_path);
        int index = vFile.getName().lastIndexOf(".");
        if (index > 0) {
            nameTv.setText("文件名: "+ vFile.getName().substring(0, index));
            formatTv.setText("类型: "+ vFile.getName().substring(index));
        } else {
            nameTv.setText("文件名: "+ vFile.getName());
            formatTv.setText("类型: 未知");
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        if(!switchlist){
            createTv.setText("新建时间: 无法识别");
            resolutionTv.setText("分辨率:无法识别");
            sizeTv.setText("文件大小: 无法识别");
            pathTv.setText("文件路径: "+ vPath);
        }else {
            createTv.setText("新建时间: "+ format.format(vFile.lastModified()));
            MediaMetadataRetriever retr = new MediaMetadataRetriever();
            retr.setDataSource(vPath);
            String height = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); // 视频高度
            String width = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); // 视频宽度
            if (height!=null&&width!=null){
                resolutionTv.setText("分辨率:"+width+"*"+height);
            }else {
                resolutionTv.setText("分辨率:无法识别");
            }
            sizeTv.setText("文件大小: "+ Tool.formatSize(vFile.length()));
            pathTv.setText("文件路径: "+ vPath);
        }

    }

    private void createInfoDialog() {
        DisplayMetrics dm= getResources().getDisplayMetrics();
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        params.gravity = Gravity.TOP | Gravity.START;
        params.flags =  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        params.width = (int) (dm.widthPixels*0.3);
        params.height = (int) (dm.heightPixels*0.2);
        params.x = dm.widthPixels;
        params.y = (int) (dm.heightPixels*0.6);
        params.format = PixelFormat.RGBA_8888;
        windowManager.addView(infoView, params);
    }

最后,MainActivity通过Intent将视频路径传给VideoActivity,因为是Android11,还要动态申请权限。文章来源地址https://www.toymoban.com/news/detail-761623.html

public class MainActivity extends AppCompatActivity {
    public static Context context;
    private EditText mvideofile;
    private Button mfileplay,mexamplay;
    private String file;
    private Intent intentexam,intentfile;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        requestPermission();
        context=getApplicationContext();

        mexamplay=findViewById(R.id.exampleplay);
        mexamplay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intentexam=new Intent(context,VideoActivity.class);
                String exfile= "android.resource://" + getPackageName() + "/" + R.raw.examvideo;
                intentexam.putExtra("path",exfile);
                intentexam.putExtra("switch",false);
                startActivity(intentexam);
            }
        });
        mvideofile=findViewById(R.id.videofile);
        mfileplay=findViewById(R.id.fileplay);
        mfileplay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                file=mvideofile.getText().toString();
                intentfile = new Intent(context, VideoActivity.class);
                intentfile.putExtra("path", file);
                intentfile.putExtra("switch",true);
                startActivity(intentfile);
            }
        });
    }

    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!Environment.isExternalStorageManager()) {
                Toast.makeText(this, "未打开管理所有文件权限", Toast.LENGTH_SHORT).show();
                Intent intentall = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intentall.setData(Uri.parse("package:" + this.getPackageName()));
                startActivity(intentall);
            }
        }
        if (!canDrawOverlays(this)) {
            Toast.makeText(this, "未打开悬浮窗权限", Toast.LENGTH_SHORT).show();
            Intent intentoverlay = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intentoverlay.setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID));
            startActivity(intentoverlay);
        }

    }
}

到了这里,关于Android MediaPlayer+SurfaceView+自定义控制器实现视频播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 18-k8s控制器资源-cronjob控制器

            job控制器是执行完一次任务,就结束;         cronjob控制器,是基于job控制器,定期频率性执行任务;等同于linux系统中的crontab一样; [root@k8s231 pi]# vim cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata:   name: xinjizhiwa spec:   schedule: \\\"* * * * *\\\"   #定义job的模板   jobTemplate

    2024年02月22日
    浏览(72)
  • 15-k8s控制器资源-deployment/部署控制器

            在学习rc和rs控制器资源时,我们指导,这两个资源都是控制pod的副本数量的,但是,他们两个有个缺点,就是在部署新版本pod或者回滚代码的时候,需要先apply资源清单,然后再删除现有pod,通过资源控制,重新拉取新的pod来实现回滚或者迭代升级;         那么

    2024年02月21日
    浏览(70)
  • 14-k8s控制器资源-rs控制器replicasets

            replicaset副本控制器,简称:rs控制器;         用法:与rc控制器“几乎”相同;         能力:可以指定pod的副本始终存活,相比于rc控制器;支持标签匹配,也支持标签表达式         注意:不论是rc还是rs资源,都是通过“标签”惊醒匹配pod的,如果有同样

    2024年02月21日
    浏览(61)
  • 13-k8s的控制器资源-rc控制器replicationcontrollers

            replicationcontrollers控制器资源,简称:rc控制器;         简单理解,rc控制器就是控制相同的pod副本数量;         使用rc控制器资源创建pod,就可以设定创建pod的数量; [root@k8s231 rc]# vim rc.yaml apiVersion: v1 kind: ReplicationController metadata:   name: rc01 spec:   #控制pod的副本

    2024年02月20日
    浏览(64)
  • 【进口控制器替代】基于Zynq-7020 FPGA的NI 8槽CompactRIO控制器

    667 MHz双核CPU,512 MB DRAM,1 GB存储容量,Zynq-7020 FPGA,更宽工作温度范围,8槽CompactRIO控制器 cRIO-9068是一款坚固耐用的无风扇嵌入式控制器,可用于高级控制和监测应用。这款软件设计控制器搭载FPGA、运行NI Linux Real-Time操作系统的实时处理器以及嵌入式用户界面功能。cRIO-906

    2024年01月25日
    浏览(62)
  • Spring MVC学习随笔-控制器(Controller)开发详解:控制器跳转与作用域(一)

    学习视频:孙哥说SpringMVC:结合Thymeleaf,重塑你的MVC世界!|前所未有的Web开发探索之旅 3.流程跳转 在web.xml里添加Servlet然后执行 可以看到通过url拼接可以获取传递的数据 四种跳转指的是:在SpringMVC中控制器与JSP或者控制器与控制器之间的跳转。 Controller - - forward — JSP Co

    2024年02月05日
    浏览(57)
  • JMeter 逻辑控制之IF条件控制器

    JMeter-5.4.1 添加While Controller 右键线程组-添加-逻辑控制器-While控制器 添加后,面板如下 仅Expression值为true,才会执行位于其下的操作 最好勾选(默认配置)Interpret Condition as Variable Expression?,这样Expression输入框可以有两种输入选择: 输入一个值为true 或者false的变量 比如,如果

    2024年02月02日
    浏览(59)
  • 自抗扰(ADRC)控制原理及控制器设计

    自抗扰控制是在PID控制算法基础上进行改进的新型控制方法,它具有不依赖于控制对象模型、不区分系统内外扰的结构特点。常用的自抗扰控制器主要由 跟踪微分器 (Tracking Differentiator,TD)、 扩张状态观测器 (Extended State Observer,ESO)和 非线性状态误差反馈控制率 (Non

    2024年01月18日
    浏览(55)
  • kubernetes-控制器

    目录 一、replicaset 二、deployment 1、版本迭代 2、回滚 3、滚动更新策略 4、暂停与恢复 三、daemonset 四、statefulset 五、job 六、cronjob ReplicaSet用于保证指定数量的 Pod 副本一直运行 replicaset是通过标签匹配pod replicaset自动控制副本数量,pod可以自愈 回收资源 Deployment 的主要作用是实

    2024年02月06日
    浏览(46)
  • Kubernetes 准入控制器

    Kubernetes 极大地提高了当今生产中后端集群的速度和可管理性。由于灵活、可扩展、易用,Kubernetes 已成为容器编排的事实标准。Kubernetes 还提供了一系列保护功能。而 Admission Controllers(准入控制器) 是一组安全相关的插件,启用后能进一步使用 Kubernetes 更高级的安全功能。

    2024年02月06日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包