视频播放(三)——视频播放

这一篇开始讲视频播放,这是整个项目最重要的部分,所以尽量说的详细点。我们的视频播放使用的是surfaceView+MediaPlayer,下面一步一步来看具体的实现,先看效果图:

一. 初始化

1. 进入PlayActivity后,肯定是需要先初始化此页面的所有控件,这个就不多说了。然后看其他初始化的信息:

 @Override
    protected void initView() {
     mHolder = mSv.getHolder();
     mHolder.setType(SurfaceHolder.
         SURFACE_TYPE_PUSH_BUFFERS);
     mHolder.addCallback(this);

     Intent intent = getIntent();
        mVideoFrom = intent.getIntExtra(Contants.VIDEO_FROM, Contants.LOCAL);
        mCurrentPosition = intent.getIntExtra(Contants.VIDEO_POSITION, 0);
        mVideoList = (List<VideoInfo>) intent.getSerializableExtra(Contants.VIDEO_FILES);
        if (mVideoList == null || mVideoList.size() == 0) {
            Toast.makeText(this, "没有可播放的视频", Toast.LENGTH_SHORT).show();
            finish();
        }
        if (mCurrentPosition < mVideoList.size()) {
            mVideo = mVideoList.get(mCurrentPosition);
        }

        visibleSurfaceTopAndBottom();
        mHandler.sendEmptyMessage(SYSTEM_TIME_CHANED);

    }

首先从SurfaceView中获取SurfaceHolder对象mHolder,然后调用addCallback方法为mHolder设置回调接口,此接口中包括surfaceCreated,surfaceChanged,surfaceDestroyed三个方法,来控制SurfaceView内部的surface的生命周期。再是获取intent传递过来的数据,分别赋给mVideoFrom(视频来源),mCurrentPosition(视频在集合中的位置),mVideoList(视频集合),对集合做一些不合法判断的处理。最后,调用了visibleSurfaceTopAndBottom方法和使用mHandler发送了一个SYSTEM_TIME_CHANED的空消息。

visibleSurfaceTopAndBottom方法后边会详细讲,这里我们调用,只是为了一开始播放时隐藏播放界面的上下两个布局。看横屏的效果图,可以看到右上角有一个系统时间的显示,发送SYSTEM_TIME_CHANED消息就是为了获取系统时间并显示在右上角。

当然,在setListener中还需要对一些控件设置监听,这个就不贴代码了,回头再源码中自己看。

二. 视频的播放/暂停:

使用SurfaceView播放视频,必须等到其内部的surface初始化完成后才可以播放,所以在surfaceCreated方法中初始化MediaPlayer:

public void surfaceCreated(SurfaceHolder holder) {
        mVideoPlayer = new MediaPlayer();
        mVideoPlayer.setDisplay(mHolder);
        mVideoPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mVideoPlayer.setOnCompletionListener(PlayActivity.this);
        // 错误监听回调函数
        mVideoPlayer.setOnErrorListener(PlayActivity.this);
        // 设置缓存变化监听
     mVideoPlayer.setOnBufferingUpdateListener(PlayActivity.this);
        play(mPlayPosition);

    }

首先创建MediaPlayer对象mVideoPlayer,设置显示画面为mHolder,再设置音频流为STREAM_MUSIC类型,然后为mVideoPlayer设置各种监听。最后调用play方法播放视频。

    private void play(final int playPosition) {
        try {
            //获取音频焦点
            if (Utils.getAudioFocus(PlayActivity.this, null)) {
                mVideoPlayer.reset();
                mVideoPlayer.setDataSource(mVideo.getUrl());
                mVideoPlayer.prepare();
                mVideoPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        try {
                            mVideoPlayer.seekTo(playPosition);
                            mVideoPlayer.setScreenOnWhilePlaying(true);
                            updateUiInfo();
                            mVideoPlayer.start();
                        } catch (IllegalStateException e) {
                            e.printStackTrace();
                            Toast.makeText(PlayActivity.this, "非法状态", Toast.LENGTH_LONG).show();
                        }

                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "加载视频错误,可能格式不支持!", Toast.LENGTH_LONG).show();
        } catch (IllegalStateException e) {
            e.printStackTrace();
            Toast.makeText(this, "非法状态", Toast.LENGTH_LONG).show();
        }
    }

来看这段代码,首先调用reset方法是mVideoPlayer进入Idle(空闲)状态,然后调用setDataSource设置播放视频的路径,成功后进入Initialized状态,如果不是Idle状态调用setDataSource方法,则会抛IllegalStateException 异常。在Initialized状态下,调用prepare方法进入prepared状态,成功后调用onPrepared方法。如果不是Initialized状态,调用prepare方法也会抛IllegalStateException 异常。

在onPrepared方法中,现在视频恢复到之前播放的位置(如果之前有保存的话),设置播放时屏幕常亮,更新界面上视频相关的一下信息,最后,调用start方法播放视频,此时mVideoPlayer就处于Started状态。

如果播放的视频格式不支持,则会抛IOException异常。

当点击播放按钮时,如果视频在播放,则暂停播放,如果视频暂停,则播放视频,在onClick方法中的代码如下:

 case R.id.play_play:
                if (isPlaying()) {
                    mVideoPlayer.pause();
                    changeState(PAUSE);
                } else {
                    if (mVideoState == PAUSE) {
                        mVideoPlayer.start();
                        changeState(PLAY);
                    } else if (mVideoState == STOP) {
                        play(0);
                    }
                }
                break;

每次播放暂停时,都需要通过changeState方法来改变播放状态。

在finish页面或者Activity进入Stop状态时,surfaceDestroyed方法会被调用,此时应该释放的mVideoPlayer占用的资源,因为下次进来会重新初始化mVideoPlayer。

 @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mVideoPlayer != null) {
            changeState(STOP);
            mVideoPlayer.release();
            mVideoPlayer = null;
        }
    }

三. 上一首,下一首功能:

(1) 下一首:

private void playNext() {
        mCurrentPosition++;
        if (mCurrentPosition < mVideoList.size()) {
            mVideo = mVideoList.get(mCurrentPosition);
            play(0);
        } else {
            mCurrentPosition--;
            Toast.makeText(PlayActivity.this, "已经是最后一个了",
                    Toast.LENGTH_SHORT).show();
        }
    }

将mCurrentPosition++,判断是否超出集合范围,如果没有,获取当前的视频,调用play播放此视频。如果已经是最后一个,在给出提示。

(2)上一首:

 private void playPrevious() {
        mCurrentPosition--;
        if (mCurrentPosition >= 0) {
            mVideo = mVideoList.get(mCurrentPosition);
            play(0);
        } else {
            mCurrentPosition++;
            Toast.makeText(PlayActivity.this, "已经是第一个了",
                    Toast.LENGTH_SHORT).show();
        }
    }

将mCurrentPosition–,判断是否小于0,如果没有,则获取当前视频,调用play播放,如果已经是第一个了,则给出提示。

四. 进度条的更新及快进快退:

进度条我们使用seekbar控件。

(1) 更新进度条:

对于视频来说,一般调用start方法开始播放后,进度条就需要开始更新,所以我们在changeState方法中,当状态改变为PLAY时,开始更新进度条。

    /**
     * 改变Video的状态
     *
     * @param state
     */
    private void changeState(int state) {
        mVideoState = state;
        mHandler.sendEmptyMessage(STATE_CHANGED);
        if (state == PLAY) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    /**防止当onDestroy方法调用时,mVideoPlayer
                     * 已经为null,但是这边还在发消息,导致空指针异常**/
                    if (mVideoPlayer == null)
                        return;
                    int position = mVideoPlayer.getCurrentPosition();
                    mTopSeekBar.setProgress(position);
                    mBottomSeekBar.setProgress(position);
                    mTvPlayedTime.setText(Utils.formatToString(position));
                    if (isPlaying()) {
                        mHandler.postDelayed(this, 1000);
                    }
                }
            });
        }
    }

可以看到,当state==PLAY时,我们handler的post方法启动一个线程更新进度条,每隔1秒更新一次。这里边有一个mVideoPlayer的判空操作,这点很重要,防止页面退出时调用onDestroy方法释放mVideoPlayer后,handler这边还在继续发送消息,此时mVideoPlayer已经为null,调用mVideoPlayer.getCurrentPosition()方法回报空指针异常。

这里我们的进度条有两个,一个在竖屏时显示,一个在横屏时显示,竖屏时是不能快进快退的。

(2) 快进快退:

seekbar本身支持快进快退的功能,但是在手动操作完成后,我们需要让mVideoPlayer在相应的位置播放,所以seekbar应该注册OnSeekBarChangeListener监听:

        mTopSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                mHandler.sendEmptyMessage(SEEKBAR_TOUCHED);
            }
        });

    }

可以看到,监听中实现三个方法, onProgressChanged是当进度条改变时调用,onStartTrackingTouch方法是在开始滑动进度条时调用,onStopTrackingTouch是在滑动结束后调用。这里我们只需要在滑动结束后发送SEEKBAR_TOUCHED消息,改变mVideoPlayer的播放位置就好。

在hanlderMessage方法中对此消息的处理:

case SEEKBAR_TOUCHED:
                    mVideoPlayer.seekTo(mTopSeekBar.getProgress());
                    mBottomSeekBar.setProgress(mTopSeekBar.getProgress());
                    mTvPlayedTime.setText(Utils.formatToString(mTopSeekBar.getProgress()));
  break;

调用seekTo方法改变播放位置,并且更新已播放时间。

(3) 在线视频缓存的更新:

记得在初始化mVideoPlayer时设置的一堆监听吗?有一个mVideoPlayer.setOnBufferingUpdateListener(PlayActivity.this),这个就是设置缓存变化的监听。当缓存发生变化时,会调用onBufferingUpdate方法,如下:

 /**
     *
     * @param mp
     * @param percent 表示缓存加载进度,0为没开始,100表示加载完成,
     *                在加载完成以后也会一直调用该方法
     */
    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        // 如果是本地视频,则不更新缓存进度(按理说本地缓存不会调用此方法,
        // 但不知道为什么rmvb格式的视频会调用这个,所以需要做此判断)
        if (mVideoFrom == Contants.LOCAL)
            return;

        if (!isVideoCacheComplate){
            int second = (mTopSeekBar.getMax() * percent / 100);
            mTopSeekBar.setSecondaryProgress(second);
            mBottomSeekBar.setSecondaryProgress(second);
            if (percent == 100){
                isVideoCacheComplate = true;
            }
        }
    }

参数precent表示缓存加载进度,因为当precent==100时,此方法还是会不停地调用,所以设置了一个标志isVideoCacheComplate,当percent==100时让isVideoCacheComplate = true,就不需要再设置缓存进度了。当然,当isVideoCacheComplate == false时,需要根据percent的值计算当前的缓存进度,然后通过setSecondaryProgress方法设置给seekbar。

按照个人理解,onBufferingUpdate放只有播放在线视频时才会调用,但是测试时发现在播放本地的rmvb格式时候是也会调用,所以需要判断当前视频的来源mVideoFrom,如果时本地视频直接返回。

来张在线视频的图:

可以看到,缓存完的进度是黄色的,对比之前的横屏图,没有缓存的是灰色的。

这一篇将的东西有些多,还有两个功能播放界面顶部底部布局的隐藏和横竖屏的切换放在下一篇总结中讲,这一篇就到这里。

时间: 2024-10-12 12:11:42

视频播放(三)——视频播放的相关文章

网页视频播放器/视频播放插件

支持的音视频格式: .swf..wmv..asf..wma..mp3..asx..mid..midi..rm..ra..rmvb..mp4..mov..avi..wav..ram..mpg..mpeg.flv; 下载地址 下载:站长下载  Admin5下载 使用方法: 步骤1:网站中引用video.js文件(src路径根据网站实际情况自行修改) <script src="video.js" type="text/javascript"></scri

Android进阶:自定义视频播放器开发(上)

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer 需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等.这么多控件显然无法播放视频,但是他们都在控制视频的播放.由此可见视频播放器可以分为两层,一层为视频播放器控制层,一层为真正的视频播放层. 所

Android进阶 自定义视频播放器

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:SmallVideoPlayer 一.需求分析 我们观察一个视频播放器,可以看到视频播放器除了正在播放的视频还有很多控件,比如播放按钮,暂停按钮,播放进度条,播放计时器等.这么多控件显然无法播放视频,但是他们都在控制视频的播放.由此可见视频播放器可以分为两层,一层为视频播放器控制层,一层为真正的视频播放层.

视频播放 iOS8.0 与iOS 9.0

#import "ViewController.h" #import <AVFoundation/AVFoundation.h> #import <AVKit/AVKit.h> #import <MediaPlayer/MediaPlayer.h> @interface ViewController () @property(nonatomic ,strong)MPMoviePlayerController * PlayerController; @

使用javafx实现视频播放器

使用javafx实现视频播放器 代码地址:https://github.com/JiaDingCN/JavaMediaPlayer 本项目是基于项目https://github.com/Al-assad/Simple-Media-Player进行修改的,向原作者的工作致敬 功能介绍 本地视频播放 在线视频播放 项目介绍 基于Oracle JDK1.8 ,使用JavaFX开发,调用JavaFX 内置的MediaPlayer播放使用. 主要功能: 播放器常用功能:开始/暂停/全屏/控制音量 播放本地

WPF技术触屏上的应用系列(三): 视频播放器的使用及视频播放、播放、暂停、可拖动播放进度效果实现

原文:WPF技术触屏上的应用系列(三): 视频播放器的使用及视频播放.播放.暂停.可拖动播放进度效果实现 去年某客户单位要做个大屏触屏应用,要对档案资源进行展示之用.客户端是Window7操作系统,54寸大屏电脑电视一体机.要求有很炫的展示效果,要有一定的视觉冲击力,可触控操作.当然满足客户的要求也可以有其它途径.但鉴于咱是搞 .NET技术的,首先其冲想到的微软WPF方面,之前对WPF的了解与学习也只是停留在比较浅的层面,没有进一步深入学习与应用.所以在项目接来以后,也就赶鸭子上架了,经过努力奋

pyglet -- 视频播放器 (简单实现,效果不是太好,切换资源会卡死)(三)

实现一个简单的视频播放器,效果不是很好.这里不多说,直接贴代码了. 1 #-*- coding:gbk -*- 2 import pyglet 3 import os 4 from pyglet.gl import * 5 6 def draw_rec(x,y,width,height): 7 """ 8 矩形 9 """ 10 glLoadIdentity() 11 glPushMatrix() 12 glBegin(GL_LINE_LOOP)

关于Unity视频播放器插件 AVPro Video(三)360度全景视频播放

1.官网下载该插件或者我分享的链接: 链接:https://pan.baidu.com/s/1boGeJ8r 密码:mvbf 2.拖入官方做好的预制体"360SphereVideo"或者"360CubeVideo"(两种不同的360度全景)到场景中,并将场景中的摄像机拖动到"Sphere"下方并Resert 3.设置"AVPro Video Media Player"上的Media Player 控制组件即可(具体操作参考&q

一步一实现视频播放器客户端(三)显示热门电影列表

(三) 今天实现热门电影这块功能,主要是从服务器获取数据,然后显示在界面上.这块虽然说是从服务器获取电影信息数据,但是,没有用到http相关的知识,我们直接使用sdk包(56网提供的api),就能获取服务器返回的json格式的数据了.以后,我还会写一篇笑话客户端的完整例子,会讲http这块,现在就不多说了. 效果图:                      第2将,我们把主界面做好了,但是没有数据,今天,我们获取数据,绑定界面上. 在说下界面,顶部一个标题,显示app名字,一次下面是一排的按钮