zoukankan      html  css  js  c++  java
  • MediaPlayer+SurfaceView 视频播放 示例


    SurfaceView的原理
            SurfaceView在视频播放中起到显示画面的作用,而视频的播放主要通过MediaPlayer来控制。
            SurfaceView 允许我们在非UI主线程中改变SurfaceView的内容,由于这个特点游戏开发在界面处理上大多会选择SurfaceView。
            首先介绍下大部分软件是如何解析一段视频流。
    • 首先它需要先确定视频的格式,这个和解码相关,不同的格式视频编码不同,不是这里的重点。
    • 知道了视频的编码格式后,再根据编码格式进行解码,最后得到一帧一帧的图像,并把这些图像快速的显示在界面上,即为播放一段视频。SurfaceView在Android中就是完成这个功能的。
            SurfaceView双缓冲
      上面有提到,SurfaceView和大部分视频应用一样,把视频流解析成一帧帧的图像进行显示,但是如果把这个解析的过程放到一个线程中完成,可能在上一帧图像已经显示过后,下一帧图像还没有来得及解析,这样会导致画面的不流畅或者声音和视频不同步的问题。所以SurfaceView和大部分视频应用一样,通过双缓冲的机制来显示帧图像。
            那么什么是双缓冲呢?双缓冲可以理解为有两个线程轮番去解析视频流的帧图像,当一个线程解析完帧图像后,把图像渲染到界面中,同时另一线程开始解析下一帧图像,使得两个线程轮番配合去解析视频流,以达到流畅播放的效果。

    SurfaceHolder的维护
      SurfaceView内部实现了双缓冲的机制,但是实现这个功能是非常消耗系统内存的。因为移动设备的局限性,Android在设计的时候规定,SurfaceView如果为用户可见,则创建SurfaceView的SurfaceHolder用于显示视频流解析的帧图片,如果发现SurfaceView变为用户【不可见】,则立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的。   如果开发人员不对SurfaceHolder进行维护,会出现最小化程序后,再打开应用的时候,视频的声音在继续播放,但是不显示画面了的情况,这就是因为当SurfaceView不被用户可见的时候,之前的SurfaceHolder已经被销毁了,再次进入的时候,界面上的SurfaceHolder已经是新的SurfaceHolder了。所以SurfaceHolder需要我们开发人员去编码维护。
            维护SurfaceHolder需要用到它的一个回调,SurfaceHolder.Callback(),它需要实现三个如下三个方法:
    • void surfaceDestroyed(SurfaceHolder holder):当SurfaceHolder被销毁的时候回调。
    • void surfaceCreated(SurfaceHolder holder):当SurfaceHolder被创建的时候回调。
    • void surfaceChange(SurfaceHolder holder):当SurfaceHolder的尺寸发生变化的时候被回调。
      在应用中分别为SurfaceHolder实现了这三个方法,先进入应用,SurfaceHolder被创建-->创建好之后会改变SurfaceHolder的大小-->然后按Home键回退到桌面时销毁SurfaceHolder-->最后再进入应用会重新创建SurfaceHolder并改变其大小。
    在使用SurfaceView的时候一定要等创建成功以后再使用,也就是在SurfaceView的getHolder中添加回调addCallback中的surfaceCreated中使用

    SurfaceView的兼容性
    SurfaceView的兼容性
      之前提到过,SurfaceView维护了一个双缓冲的机制,它会自己维护缓冲区,无需我们手动维护,但是对于4.0以下的设备,需要为其制定它缓冲区的维护类型,让其不自己维护缓冲区,而是等待界面渲染引擎将内容渲染到界面上。这里仅仅是使用SurfaceView播放一个视频,如果使用SurfaceView开发游戏应用,就需要我们自己维护这个缓冲区了。
    // 4.0版本之下需要设置的属性,设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
    sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    示例说明

    可以播放几乎任何音频、视频格式(实测支持的有MP3、mp4、rmvb、avi、3gp、mkv、flv……),但是实测时部分avi也是不能播的。

    代码
    // 在音乐播放器基础上加***部分的内容即可播放视频;加上===部分即可控制拖动栏;加上————部分可解决MediaPlayer与SurfaceHolder同步的问题
    public class MainActivity extends Activity implements OnClickListener, OnCompletionListener, OnPreparedListener, OnErrorListener,
            OnSeekBarChangeListener {
        private EditText et_pathet_Url;
        private Button bt_playbt_playUrlbt_pausebt_stopbt_replay;
        private MediaPlayer mediaPlayer;//多媒体播放器
        private static final String STATE_CONTINUE = "继续";
        private static final String STATE_PAUSE = "暂停";

        private SurfaceView sv;//****************SurfaceView是一个在其他线程中显示、更新画面的组件,专门用来完成在单位时间内大量画面变化的需求
        private SurfaceHolder holder;//****************SurfaceHolder接口为一个显示界面内容的容器

        private SeekBar seekBar;//===============进度条
        private static int savedPosition;//===============记录当前播放文件播放的进度
        private static String savedFilepath;//===============记录当前播放文件的位置
        private Timer timer;//===============定义一个计时器,每隔100ms更新一次进度条
        private TimerTask task;//===============计时器所执行的任务
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            et_path = (EditText) findViewById(R.id.et_path);
            et_Url = (EditText) findViewById(R.id.et_Url);
            bt_play = (Button) findViewById(R.id.bt_play);
            bt_playUrl = (Button) findViewById(R.id.bt_playUrl);
            bt_pause = (Button) findViewById(R.id.bt_pause);
            bt_stop = (Button) findViewById(R.id.bt_stop);
            bt_replay = (Button) findViewById(R.id.bt_replay);
            bt_play.setOnClickListener(this);
            bt_playUrl.setOnClickListener(this);
            bt_pause.setOnClickListener(this);
            bt_stop.setOnClickListener(this);
            bt_replay.setOnClickListener(this);

            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnCompletionListener(this);
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.setOnErrorListener(this);

            sv = (SurfaceView) findViewById(R.id.sv);//****************
            holder = sv.getHolder();//****************得到显示界面内容的容器
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //设置surfaceView自己不管理缓存区。虽然提示过时,但最好还是设置下
            seekBar = (SeekBar) findViewById(R.id.seekBar);//===============
            seekBar.setOnSeekBarChangeListener(this);//===============

            //在界面【最小化】时暂停播放,并记录holder播放的位置——————————————————————————————
            holder.addCallback(new Callback() {
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {//holder被销毁时回调。最小化时都会回调
                    if (mediaPlayer != null) {
                        Log.i("bqt""销毁了--surfaceDestroyed" + "--" + mediaPlayer.getCurrentPosition());
                        savedPosition = mediaPlayer.getCurrentPosition();//当前播放位置
                        mediaPlayer.stop();
                        timer.cancel();
                        task.cancel();
                        timer = null;
                        task = null;
                    }
                }
                @Override
                public void surfaceCreated(SurfaceHolder holder) {//holder被创建时回调
                    Log.i("bqt""创建了--" + savedPosition + "--" + savedFilepath);
                    if (savedPosition > 0) {//如果记录的数据有播放进度。
                        try {
                            mediaPlayer.reset();
                            mediaPlayer.setDataSource(savedFilepath);
                            mediaPlayer.setDisplay(holder);
                            mediaPlayer.prepare();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                //holder宽高发生变化(横竖屏切换)时回调
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                }
            });
        }
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.bt_play:
                play();
                break;
            case R.id.bt_playUrl:
                playUrl();
                break;
            case R.id.bt_pause:
                pause();
                break;
            case R.id.bt_stop:
                stop();
                break;
            case R.id.bt_replay:
                replay();
                break;
            default:
                break;
            }
        }

        /**
         * 播放本地多媒体
         */
        public void play() {
            String filepath = et_path.getText().toString().trim();
            File file = new File(filepath);
            if (file.exists() && mediaPlayer != null) {
                try {
                    savedFilepath = filepath;
                    mediaPlayer.setDataSource(filepath);
                    mediaPlayer.setDisplay(holder);//****************在哪个容器里显示内容
                    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    mediaPlayer.prepare();
                    bt_play.setEnabled(false);
                    bt_playUrl.setEnabled(false);
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(this"请检查是否有写SD卡权限", 0).show();
                }
            } else {
                Toast.makeText(this"文件不存在", 0).show();
            }
        }
        /**
         * 播放网络多媒体
         */
        public void playUrl() {
            String filepath = et_Url.getText().toString().trim();
            if (!TextUtils.isEmpty(filepath)) {
                try {
                    savedFilepath = filepath;
                    mediaPlayer.setDataSource(filepath);
                    mediaPlayer.setDisplay(holder);//****************
                    mediaPlayer.prepareAsync();//异步准备
                    bt_playUrl.setEnabled(false);
                    bt_play.setEnabled(false);
                    Toast.makeText(MainActivity.this"准备中,可能需要点时间……", 1).show();
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(this"播放失败,请检查是否有网络权限", 0).show();
                }
            } else {
                Toast.makeText(this"路径不能为空", 0).show();
            }
        }
        /**
         * 暂停
         */
        public void pause() {
            if (mediaPlayer != null) {
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                    bt_pause.setText(STATE_CONTINUE);
                } else {
                    mediaPlayer.start();
                    bt_pause.setText(STATE_PAUSE);
                    return;
                }
            }
        }
        /**
         * 停止
         */
        public void stop() {
            if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            mediaPlayer.reset();
            bt_play.setEnabled(true);
            bt_playUrl.setEnabled(true);
            bt_pause.setText("暂停");
        }
        /**
         * 重播
         */
        public void replay() {
            if (mediaPlayer != null) {
                mediaPlayer.start();
                mediaPlayer.seekTo(0);
            }
            bt_pause.setText("暂停");
        }
        //********************************************************************************************************************
        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            Toast.makeText(MainActivity.this"报错了--" + what + "--" + extra, 0).show();
            return false;
        }
        @Override
        public void onPrepared(MediaPlayer mp) {//只有准备好以后才能处理很多逻辑
            mediaPlayer.start();
            //=============
            mediaPlayer.seekTo(savedPosition);//开始时是从0开始播放,恢复时是从指定位置开始播放
            seekBar.setMax(mediaPlayer.getDuration());//将进度条的最大值设为文件的总时长
            timer = new Timer();
            task = new TimerTask() {
                public void run() {
                    seekBar.setProgress(mediaPlayer.getCurrentPosition());//将媒体播放器当前播放的位置赋值给进度条的进度
                }
            };
            timer.schedule(task, 0, 100);//0秒后执行,每隔100ms执行一次
            Toast.makeText(MainActivity.this"准备好了!", 0).show();
        }
        @Override
        public void onCompletion(MediaPlayer mp) {
            Toast.makeText(MainActivity.this"播放完毕!", 0).show();
            mediaPlayer.reset();
            bt_playUrl.setEnabled(true);
            bt_play.setEnabled(true);
        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {//进度发生变化时
        }
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {//停止拖拽时回调
            mediaPlayer.seekTo(seekBar.getProgress());//停止拖拽时进度条的进度
        }
    }

    布局
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:orientation="vertical" >
        <EditText
            android:id="@+id/et_path"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="/sdcard/a.rmvb" />
        <EditText
            android:id="@+id/et_Url"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
    android:singleLine="true"
      android:ellipsize="end"
    android:text="http://112.253.22.157/17/z/z/y/u/zzyuasjwufnqerzvyxgkuigrkcatxr/hc.yinyuetai.com   /D046015255134077DDB3ACA0D7E68D45.flv" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
            <Button
                android:id="@+id/bt_play"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="播放本地多媒体" />
            <Button
                android:id="@+id/bt_playUrl"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="播放网络多媒体" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
            <Button
                android:id="@+id/bt_pause"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="暂停" />
            <Button
                android:id="@+id/bt_stop"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="停止" />
            <Button
                android:id="@+id/bt_replay"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="重播" />
        </LinearLayout>
        <SurfaceView
            android:id="@+id/sv"
            android:layout_weight="2016"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <SeekBar
            android:id="@+id/seekBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>





  • 相关阅读:
    HDU 4069 Squiggly Sudoku
    SPOJ 1771 Yet Another NQueen Problem
    POJ 3469 Dual Core CPU
    CF 118E Bertown roads
    URAL 1664 Pipeline Transportation
    POJ 3076 Sudoku
    UVA 10330 Power Transmission
    HDU 1426 Sudoku Killer
    POJ 3074 Sudoku
    HDU 3315 My Brute
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/5422917.html
Copyright © 2011-2022 走看看