SimpleVideoView

最近编辑于2018年8月4日

为什么要使用TextureView重写VideoView,因为原先的VideoView在viewpager多个页面都含有VideoView的时候,左右滑动时会导致Surface绘制不及时,会出现下一个SurfaceView显示在上一个SurfaceView的某一帧。

这是原项目链接https://github.com/klinker24/Android-SimpleVideoView

这个开源项目是我通过AndroidStudio的dependency搜索VideoView得到。

我仿造VideoView源码,在其基础上添加了setOnPreparedListener监听,以及一些状态码。

public class SimpleVideoView extends RelativeLayout {

    private MediaPlayer mediaPlayer;

    private LinearLayout progressBar;
    private TextureView textureView;
    private Surface surface;


    private SimpleVideoView.VideoPlaybackErrorTracker errorTracker;

    private boolean loop = false;
    private boolean stopSystemAudio = false;
    private boolean muted = false;
    private boolean showSpinner = true;

    private Uri videoUri = null;
    private MediaPlayer.OnPreparedListener mOnPreparedListener;

    private int mCurrentState = STATE_IDLE;
    // all possible internal states
    private static final int STATE_ERROR = -1;
    private static final int STATE_IDLE = 0;
    private static final int STATE_PREPARING = 1;
    private static final int STATE_PREPARED = 2;
    private static final int STATE_PLAYING = 3;
    private static final int STATE_PAUSED = 4;
    private static final int STATE_PLAYBACK_COMPLETED = 5;

    /**
     * Default constructor
     *
     * @param context context for the activity
     */
    public SimpleVideoView(Context context) {
        super(context);
        init();
    }

    /**
     * Constructor for XML layout
     *
     * @param context activity context
     * @param attrs   xml attributes
     */
    public SimpleVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleVideoView, 0, 0);
        loop = a.getBoolean(R.styleable.SimpleVideoView_loop, false);
        stopSystemAudio = a.getBoolean(R.styleable.SimpleVideoView_stopSystemAudio, false);
        muted = a.getBoolean(R.styleable.SimpleVideoView_muted, false);
        showSpinner = a.getBoolean(R.styleable.SimpleVideoView_showSpinner, true);
        a.recycle();

        init();
    }

    /**
     * Initialize the layout for the SimpleVideoView.
     */
    private void init() {
        // add a progress spinner
        progressBar = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.progress_bar, this, false);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            progressBar.setElevation(6);
        }

        addView(progressBar);

        setGravity(Gravity.CENTER);
        mCurrentState = STATE_IDLE;
    }

    /**
     * Add the SurfaceView to the layout.
     */
    private void addSurfaceView() {
        // disable the spinner if we don't want it
        if (!showSpinner && progressBar.getVisibility() != View.GONE) {
            progressBar.setVisibility(View.GONE);
        }

        final RelativeLayout.LayoutParams surfaceViewParams =
                new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

        textureView = new TextureView(getContext());
        textureView.setLayoutParams(surfaceViewParams);
        addView(textureView, 0);

        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
                surface = new Surface(surfaceTexture);
                setMediaPlayerDataSource();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

            }
        });
    }

    /**
     * Prepare to play the media.
     */
    private void prepareMediaPlayer() {
        if (mediaPlayer != null) {
            release();
            mCurrentState = STATE_IDLE;
        }

        // initialize the media player
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(final MediaPlayer mediaPlayer) {
                mCurrentState = STATE_PREPARED;
                scalePlayer();

                if (stopSystemAudio) {
                    AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
                    am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
                }

                if (muted) {
                    mediaPlayer.setVolume(0, 0);
                }

                if (progressBar.getVisibility() != View.GONE) {
                    progressBar.setVisibility(View.GONE);
                }

                if (mOnPreparedListener != null) {
                    mOnPreparedListener.onPrepared(mediaPlayer);
                }

                try {
                    mediaPlayer.setSurface(surface);
//                    mediaPlayer.start();
                } catch (IllegalArgumentException e) {
                    // the surface has already been released
                    mCurrentState = STATE_ERROR;
                }
            }
        });

        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                mCurrentState = STATE_PLAYBACK_COMPLETED;
                if (loop) {
                    mp.seekTo(0);
                    mp.start();
                }
            }
        });

        mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
            /**
             * Called to indicate an error.
             *
             * @param mp      the MediaPlayer the error pertains to
             * @param what    the type of error that has occurred:
             * <ul>
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_UNKNOWN}
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_SERVER_DIED}
             * </ul>
             * @param extra an extra code, specific to the error. Typically
             * implementation dependent.
             * <ul>
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_IO}
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_MALFORMED}
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_UNSUPPORTED}
             * <li>{@link android.media.MediaPlayer.OnErrorListener#MEDIA_ERROR_TIMED_OUT}
             * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error.
             * </ul>
             */
            @Override
            public boolean onError(MediaPlayer mp, int what, int extra) {
                mCurrentState = STATE_ERROR;
                if (errorTracker != null) {
                    errorTracker.onPlaybackError(
                            new RuntimeException("Error playing video! what code: " + what + ", extra code: " + extra)
                    );
                }
                return true;
            }
        });
    }

    private void setMediaPlayerDataSource() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // this needs to be run on a background thread.
                    // set data source can take upwards of 1-2 seconds
                    Map<String, String> headers = new HashMap<>();
                    headers.put("Cache-control", "no-cache");
                    mediaPlayer.setDataSource(getContext(), videoUri, headers);
                    mediaPlayer.prepareAsync();
                    mCurrentState = STATE_PREPARING;
                } catch (Exception e) {
                    mCurrentState = STATE_ERROR;
                    if (errorTracker != null) {
                        errorTracker.onPlaybackError(e);
                    }
                }
            }
        }).start();
    }

    /**
     * Adjust the size of the player so it fits on the screen.
     */
    private void scalePlayer() {
        int videoWidth = mediaPlayer.getVideoWidth();
        int videoHeight = mediaPlayer.getVideoHeight();
        float videoProportion = (float) videoWidth / (float) videoHeight;

        float screenProportion = (float) getWidth() / (float) getHeight();
        ViewGroup.LayoutParams lp = textureView.getLayoutParams();

        if (videoProportion > screenProportion) {
            lp.width = getWidth();
            lp.height = (int) ((float) getWidth() / videoProportion);
        } else {
            lp.width = (int) (videoProportion * (float) getHeight());
            lp.height = getHeight();
        }

        textureView.setLayoutParams(lp);
    }

    /**
     * Load the video into the player and initialize the layouts
     *
     * @param videoUrl String url to the video
     */
    public void start(String videoUrl) {
        start(Uri.parse(videoUrl));
    }

    /**
     * Load the video into the player and initialize the layouts.
     *
     * @param videoUri uri to the video.
     */
    public void start(Uri videoUri) {
        this.videoUri = videoUri;

        // we will not load the surface view or anything else until we are given a video.
        // That way, if, say, you wanted to add the simple video view on a list or something,
        // it won't be as intensive. ( == Better performance.)
        if (textureView == null) {
            addSurfaceView();
            prepareMediaPlayer();
        } else {
            prepareMediaPlayer();
            setMediaPlayerDataSource();
        }
    }

    /**
     * Start video playback. Called automatically with the SimpleVideoPlayer#start method
     */
    public void play() {
        if (isInPlaybackState()) {
            mediaPlayer.start();
            mCurrentState = STATE_PLAYING;
        }
    }

    /**
     * Pause video playback
     */
    public void pause() {
        if (isInPlaybackState() && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
            mCurrentState = STATE_PAUSED;
        }
    }

    /**
     * Release the video to stop playback immediately.
     * <p>
     * Should be called when you are leaving the playback activity
     */
    public void release() {
        try {
            mediaPlayer.stop();
            mediaPlayer.release();
            mCurrentState = STATE_IDLE;
        } catch (Exception e) {
            mCurrentState = STATE_ERROR;
        }

        mediaPlayer = null;
    }

    /**
     * Whether you want the video to loop or not
     *
     * @param shouldLoop
     */
    public void setShouldLoop(boolean shouldLoop) {
        this.loop = shouldLoop;
    }

    /**
     * Whether you want the app to stop the currently playing audio when you start the video
     *
     * @param stopSystemAudio
     */
    public void setStopSystemAudio(boolean stopSystemAudio) {
        this.stopSystemAudio = stopSystemAudio;
    }

    /**
     * Whether or not you want to show the spinner while loading the video
     *
     * @param showSpinner
     */
    public void setShowSpinner(boolean showSpinner) {
        this.showSpinner = showSpinner;
    }

    /**
     * Get whether or not the video is playing
     *
     * @return true if the video is playing, false otherwise
     */
    public boolean isPlaying() {
        try {
            return mediaPlayer != null && mediaPlayer.isPlaying();
        } catch (Exception e) {
            mCurrentState = STATE_ERROR;
            return false;
        }
    }

    /**
     * Will return a result if there is an error playing the video
     *
     * @param tracker
     */
    public void setErrorTracker(SimpleVideoView.VideoPlaybackErrorTracker tracker) {
        this.errorTracker = tracker;
    }

    public interface VideoPlaybackErrorTracker {
        void onPlaybackError(Exception e);
    }

    private boolean isInPlaybackState() {
        return (mediaPlayer != null &&
                mCurrentState != STATE_ERROR &&
                mCurrentState != STATE_IDLE &&
                mCurrentState != STATE_PREPARING);
    }

    public int getDuration() {
        if (isInPlaybackState()) {
            return mediaPlayer.getDuration();
        }
        return -1;
    }

    public int getCurrentPosition() {
        if (isInPlaybackState()) {
            return mediaPlayer.getCurrentPosition();
        }
        return -1;
    }

    public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) {
        mOnPreparedListener = l;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值