编写简单自定义VideoView

简单定制VideoView中做了简单的VideoView定制,其实就是在布局上做了一些事情。要向更灵活的定制播放器的行为,必须写自己的VideoView。参考android VideoView源代码,写了个最简单的实现。

看起来和简单定制VideoView中的效果差不多,但是还有很多逻辑没有加进来,比如:

  • 视频大小有问题,被拉长了,需要在后续版本中改进;
  • 还没有加入MediaController,没有前进、后退、暂停等按钮界面。

自定义的VideoView源代码:

package com.easymorse.videoplayer;

import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.widget.MediaController;
import android.widget.MediaController.MediaPlayerControl;

public class CustomerVideoView extends SurfaceView implements
        MediaPlayerControl {

    private static String TAG = "customer.videoplayer";

    private boolean pause;

    private boolean seekBackward;

    private boolean seekForward;

    private Uri videoUri;

    private MediaPlayer mediaPlayer;

    private Context context;

    private OnPreparedListener onPreparedListener;

    private int videoWidth;

    private int videoHeight;

    private MediaController mediaController;

    protected SurfaceHolder surfaceHolder;

    private Callback surfaceHolderCallback = new SurfaceHolder.Callback() {

        public void surfaceChanged(SurfaceHolder holder, int format, int w,
                int h) {
        }

        public void surfaceCreated(SurfaceHolder holder) {
            surfaceHolder = holder;
            if (mediaPlayer != null) {
                mediaPlayer.setDisplay(surfaceHolder);
                resume();
            } else {
                openVideo();
            }
        }

        public void surfaceDestroyed(SurfaceHolder holder) {
            surfaceHolder = null;
            if (mediaController != null) {
                mediaController.hide();
            }

            release(true);
        }
    };

    private void release(boolean cleartargetstate) {
        if (mediaPlayer != null) {
            mediaPlayer.reset();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    public void resume() {
        if (surfaceHolder == null) {
            return;
        }
        if (mediaPlayer != null) {
            return;
        }
        openVideo();
    }

    public CustomerVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        this.initVideoView();
    }

    public CustomerVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        this.initVideoView();
    }

    public CustomerVideoView(Context context) {
        super(context);
        this.context = context;
        this.initVideoView();
    }

    @Override
    public boolean canPause() {
        return this.pause;
    }

    @Override
    public boolean canSeekBackward() {
        return this.seekBackward;
    }

    @Override
    public boolean canSeekForward() {
        return this.seekForward;
    }

    @Override
    public int getBufferPercentage() {
        return 0;
    }

    @Override
    public int getCurrentPosition() {
        return mediaPlayer!=null?mediaPlayer.getCurrentPosition():0;
    }

    @Override
    public int getDuration() {
        return mediaPlayer!=null?mediaPlayer.getDuration():0;
    }

    @Override
    public boolean isPlaying() {
        return false;
    }

    @Override
    public void pause() {
    }

    @Override
    public void seekTo(int mSec) {
    }

    @Override
    public void start() {
    }

    public void setVideoURI(Uri uri) {
        this.videoUri = uri;
        openVideo();
        requestLayout();
        invalidate();
    }

    private void openVideo() {
        this.mediaPlayer = new MediaPlayer();
        try {
            this.mediaPlayer.setDataSource(this.context, this.videoUri);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            throw new RuntimeException(e);
        }
        this.mediaPlayer.prepareAsync();
        this.mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        this.mediaPlayer.setOnPreparedListener(onPreparedListener);
        attachMediaController();

    }

    private void attachMediaController() {
        if (mediaPlayer != null && mediaController != null) {
            mediaController.setMediaPlayer(this);
            View anchorView = this.getParent() instanceof View ? (View) this
                    .getParent() : this;
            mediaController.setAnchorView(anchorView);
            mediaController.setEnabled(true);
        }

    }

    public void setMediaController(MediaController controller) {
        if (mediaController != null) {
            mediaController.hide();
        }
        mediaController = controller;
        attachMediaController();
    }

    public void setOnPreparedListener(OnPreparedListener onPreparedListener) {
        this.onPreparedListener = onPreparedListener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = getDefaultSize(videoWidth, widthMeasureSpec);
        int height = getDefaultSize(videoHeight, heightMeasureSpec);
        if (videoWidth > 0 && videoHeight > 0) {
            if (videoWidth * height > width * videoHeight) {
                height = width * videoHeight / videoWidth;
            } else if (videoWidth * height < width * videoHeight) {
                width = height * videoWidth / videoHeight;
            }
        }
        Log.i(TAG, "setting size: " + width + ‘x’ + height);
        setMeasuredDimension(width, height);
    }

    private void initVideoView() {
        videoWidth = 0;
        videoHeight = 0;
        getHolder().addCallback(surfaceHolderCallback);
        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();
    }

}

 

和VideoView实现类似,继承了SurfaceView并且实现了MediaPlayerControl。

一般情况下,android界面的绘制和更新,要交给主ui线程来操作,通过Handler机制。但是播放视频,需要比较优先和实时的改变和绘制界面。android提供了使用单独线程绘制UI的机制,就是SurfaceView。

使用SurfaceView,需要实现SurfaceHolder.Callback接口:

  • surfaceCreated,在Surface(SurfaceView内部包含一个Surface实例)创建后,会立即调用该方法,可在该方法中做绘制界面相关的初始化工作;
  • surfaceChanged,当Surface的状态发生变化,比如大小,会调用该方法,在surfaceCreated方法调用过至少会调用一次该方法;
  • surfaceDestroyed,当销毁Surface的时候调用。

开发者不能直接操作Surface实例,要通过SurfaceHandler,在SurfaceView中可以通过getHandler方法获取到SurfaceHandler实例。

SurfaceHander有一些类型,用来标识Surface实例界面数据来源,可以通过setType来操作:

  • SURFACE_TYPE_NORMAL:RAM缓存的原生数据
  • SURFACE_TYPE_HARDWARE:通过DMA,direct memory access,就是直接写屏技术获取到的数据,或者其他硬件加速的数据
  • SURFACE_TYPE_GPU:通过GPU加速的数据
  • SURFACE_TYPE_PUSH_BUFFERS:标识数据来源于其他对象,比如照相机,比如视频播放服务器(android内部有视频播放的服务器,所有播放视频相当于客户端)

CustomerVideoView的构造方法,使用超类的构造方法。都会执行initVideoView()方法用来初始化界面和参数。

另外一个主要的内容是openVideo()方法:

  • mediaPlayer.prepareAsync(),用来异步准备播放,另外还有个prepare()方法,是同步的,也就是全部下载完毕才能播放,显然,在播放网上视频的时候需要用前者;
  • 通过attachMediaController()方法,把控制条附加到播放视频的SurfaceView上,这里实现的不完全,因此还不能使用,仅仅是把MediaPlayerControl实例通过setMediaPlayer方法设置一下,供OnPreparedListener用来得到加载成功的回调,另外供外面代码调用得到视频的时长和当前时长。

源代码见:

http://easymorse.googlecode.com/svn/tags/videoplayer-0.4/

PDF格式創作    发送文章为PDF   

3 Comments to “编写简单自定义VideoView”

  • Android API (125)-Chinese VideoView — 2011年06月16日 @ 07:21

  • android日文api(125)であるvideoviewだった | テクニカルブログ — 2011年07月19日 @ 13:03

  • Android中文API(125) —— VideoView - 关注安卓(Android) — 2011年09月5日 @ 13:24

  • 这篇文章上的评论的 RSS feed TrackBack URI

    Leave a Reply