Android视频播放-SurfaceView和Mediaplayer

好几天没写博客了,处理了一点个人私事加上平时加班,基本上时间不充裕,上篇文章讲了一下用Mediaplayer来播放音乐,这次就讲讲使用Mediaplayer来和SurfaceView配合播放一个视频流媒体。MediaPlayer不仅可以播放视频,还可以与SurfaceView的配合,SurfaceView主要用于显示MediaPlayer播放的视频流媒体的画面渲染,两者可以一起协同播放视频。

基础维护

先来看下要实现的界面:

 

如果你看过上篇文章,就发现其实很简单的就是多了一个进度条,还有一个就是SurfaceView,就是下面那块黑色区域;

布局文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.googlevideo.MainActivity" >
 
    <EditText
        android:id="@+id/edit_musicPath"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入MV的路径" />
 
    <SeekBar
        android:id="@+id/seekBar_video"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
 
        <Button
            android:id="@+id/btn_Play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="playEvent"
            android:text="播放" />
 
        <Button
            android:id="@+id/btn_Pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="pauseEvent"
            android:text="暂停" />
 
        <Button
            android:id="@+id/btn_Stop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="stopEvent"
            android:text="停止" />
 
        <Button
            android:id="@+id/btn_Replay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="replayEvent"
            android:text="重播" />
    </LinearLayout>
 
    <SurfaceView
        android:id="@+id/surface_video"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
</LinearLayout>

 Demo实现

实现Demo之前应该讲讲视频播放的原理,先确定视频的格式,这个和解码相关,不同的格式视频编码不同,然后通过编码格式进行解码,最后得到一帧一帧的图像,并把这些图像快速的显示在界面上,即为播放一段视频。SurfaceView在Android中就是完成这个功能的。SurfaceView是配合MediaPlayer使用的,MediaPlayer也提供了相应的方法设置SurfaceView显示图片,只需要为MediaPlayer指定SurfaceView显示图像即可。它的完整签名:void setDisplay(SurfaceHolder sh)。它需要传递一个SurfaceHolder对象,SurfaceHolder可以理解为SurfaceView装载需要显示的一帧帧图像的容器,它可以通过SurfaceHolder.getHolder()方法获得。使用MediaPlayer配合SurfaceView播放视频的步骤与播放使用MediaPlayer播放MP3大体一致,只需要额外设置显示的SurfaceView即可。

先准备一段能播放的视频:

 

播放视频效果图:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
editText = (EditText) findViewById(R.id.edit_musicPath);
     pathString = editText.getText().toString().trim();
    File file = new File(pathString);
    if (file.exists()) {
        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.setDataSource(pathString);
            //通过SurfaceView获取的Holder
            mediaPlayer.setDisplay(holder);
            mediaPlayer.prepare();
            mediaPlayer.start();
            btn_PlayButton.setEnabled(false);
            //设置Bar的最大值
            int max=mediaPlayer.getDuration();
            seekBarVideo.setMax(max);
            //定时器更新进度条
            timer=new  Timer();
            timeTask=new TimerTask() {
                 
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    seekBarVideo.setProgress(mediaPlayer.getCurrentPosition());
                }
            };     
            timer.schedule(timeTask, 0, 500);
            //视频播放完之后重新设置显示
            mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
                 
                @Override
                public void onCompletion(MediaPlayer mp) {
                    // TODO Auto-generated method stub
                    btn_PlayButton.setEnabled(true);
                }
            });
 
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } else {
        Toast.makeText(this, "Sorry,你输入的路径有问题,请仔细检查", Toast.LENGTH_SHORT)
                .show();
    }

   SeekBar的使用: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//设置Bar的最大值
            int max=mediaPlayer.getDuration();
            seekBarVideo.setMax(max);
            //定时器更新进度条
            timer=new  Timer();
            timeTask=new TimerTask() {
                 
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    seekBarVideo.setProgress(mediaPlayer.getCurrentPosition());
                }
            };     
            timer.schedule(timeTask, 0, 500);

 其中holder是SurfaceHolder,在onCreate中获取:

1
2
3
4
surfaceView = (SurfaceView) findViewById(R.id.surface_video);
    holder = surfaceView.getHolder();
    //兼容4.0以后的手机版本,本身是不维护的
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

 如果正在播放视频,最小化的时候是会有声音的,需要在回调函数中处理一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
holder.addCallback(new Callback() {
     
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        Log.i(tag, "销毁了Holder");
        if (mediaPlayer!=null&&mediaPlayer.isPlaying()) {
            currentPosition=mediaPlayer.getCurrentPosition();
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer=null;
            timer.cancel();
            timeTask.cancel();
            timer=null;
            timeTask=null;
        }
    }
     
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        Log.i(tag,"创建了Holder");
        if (currentPosition>0) {
            try {
                mediaPlayer = new MediaPlayer();
                mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                mediaPlayer.setDataSource(pathString);
                //通过SurfaceView获取的Holder
                mediaPlayer.setDisplay(holder);
                mediaPlayer.prepare();
                mediaPlayer.start();
                mediaPlayer.seekTo(currentPosition);
                btn_PlayButton.setEnabled(false);
                //视频播放完之后重新设置显示
                mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
 
                            @Override
                            public void onCompletion(MediaPlayer mp) {
                                // TODO Auto-generated method stub
                                btn_PlayButton.setEnabled(true);
                            }
                        });
                 
                int max=mediaPlayer.getDuration();
                seekBarVideo.setMax(max);
                //定时器更新进度条
                timer=new  Timer();
                timeTask=new TimerTask() {
                     
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        seekBarVideo.setProgress(mediaPlayer.getCurrentPosition());
                    }
                };     
                timer.schedule(timeTask, 0, 500);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
     
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
        Log.i(tag,"改变了Holder");
    }
});

  进度条是SeekBar,如果需要快进或者后退的时候是需要将焦点赋值给mediaPlayer的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
seekBarVideo=(SeekBar) findViewById(R.id.seekBar_video);
    seekBarVideo.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
         
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            // TODO Auto-generated method stub
            int position=seekBar.getProgress();
            if (mediaPlayer!=null&&mediaPlayer.isPlaying()) {
                mediaPlayer.seekTo(position);
            }
        }
         
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            // TODO Auto-generated method stub
             
        }
         
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                boolean fromUser) {
            // TODO Auto-generated method stub
             
        }
    });

  暂停事件:

1
2
3
4
5
6
7
8
9
10
11
public void pauseEvent(View view) {
    if (btn_PauseButton.getText().equals("继续")) {
        mediaPlayer.start();
        btn_PauseButton.setText("暂停");
        return;
    }
    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
        mediaPlayer.pause();
        btn_PauseButton.setText("继续");
    }
}

 停止事件:

1
2
3
4
5
6
7
8
9
10
11
public void stopEvent(View view) {
    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
        btn_PlayButton.setEnabled(true);
        mediaPlayer.stop();
        // 释放mediaplayer否则的话会占用内存
        mediaPlayer.release();
        mediaPlayer = null;
    }
    btn_PauseButton.setText("暂停");
    btn_PlayButton.setEnabled(true);
}

  重播事件:

1
2
3
4
5
6
7
8
9
10
public void replayEvent(View view) {
    surfaceView.setVisibility(View.VISIBLE);
    if (mediaPlayer != null && mediaPlayer.isPlaying()) {
        mediaPlayer.seekTo(0);
    } else {
        playEvent(view);
    }
    // 重播的时候应该设置播放的状态
    btn_PlayButton.setEnabled(true);
}
posted @   Fly_Elephant  阅读(3438)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示