基于MediaPlayer实现视频播放

该文介绍了一个使用原生MediaPlayer和TextureView实现的Android视频播放器,支持网络和本地视频,能感知页面生命周期并自动暂停/播放,还提供了视频首帧展示、网络视频下载以及在RecyclerView中的使用功能。

一、概述

一个简单的视频播放器,满足一般的需求。使用原生的 MediaPlayer 和 TextureView来实现。

功能点:

  1. 获取视频的首帧进行展示,网络视频的首帧会缓存
  2. 视频播放,本地视频或者网络视频
  3. 感知生命周期,页面不可见自动暂停播放,页面关闭,自动释放
  4. 可以在RecyclerView的item中使用
  5. 网络视频可配置下载(如果网络视频地址可以下载),下次再播放时播放下载好的视频。

演示图:
在这里插入图片描述

二、使用

VideoPlayView videoPlayView = findViewById(R.id.videoPlayView);
getLifecycle().addObserver(videoPlayView);
//设置视频文件路径
videoPlayView.setFileDataSource(filePath);
//设置网络视频地址
//videoPlayView.setNetDataSource(netAddress);
int position = intent.getIntExtra("position", 0);
videoPlayView.setTargetPosition(position);

三、实现代码

主要涉及三个类:

  1. VideoPlayView 播放器
  2. VideoRepository 获取视频首帧,缓存视频首帧,判断网络视频是否有缓存等处理
  3. VideoDownload 网络视频下载

VideoPlayView

VideoPlayView布局
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:background="@color/black"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />

    <ImageView
        android:id="@+id/previewIv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@null" />

    <ProgressBar
        android:id="@+id/loadProgressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:layout_gravity="center" />

    <FrameLayout
        android:id="@+id/mediaControllerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/ivPlay"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="center"
            android:contentDescription="@null"
            android:src="@drawable/play" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_gravity="bottom"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tvTime"
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:gravity="center"
                android:textColor="@color/white"
                android:text="00:00"
                tools:ignore="HardcodedText" />

            <androidx.appcompat.widget.AppCompatSeekBar
                android:id="@+id/seekBar"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginHorizontal="4dp"
                android:layout_weight="1" />

            <TextView
                android:id="@+id/tvDuration"
                android:layout_width="60dp"
                android:layout_height="match_parent"
                android:gravity="center"
                android:textColor="@color/white"
                tools:text="2:40:10" />

            <ImageView
                android:id="@+id/ivScreen"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:contentDescription="@null"
                android:padding="5dp"
                android:src="@drawable/fullscreen" />

        </LinearLayout>
    </FrameLayout>

</FrameLayout>
VideoPlayView 代码
public class VideoPlayView extends FrameLayout implements LifecycleObserver,
        View.OnClickListener, SeekBar.OnSeekBarChangeListener, TextureView.SurfaceTextureListener,
        MediaPlayer.OnInfoListener, MediaPlayer.OnErrorListener,
        MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,
        MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnBufferingUpdateListener,
        MediaPlayer.OnVideoSizeChangedListener, VideoRepository.VideoFrameCallback {
   
   

    private final int DURATION_REFRESH_PROGRESS = 1000;//播放进度更新间隔
    private final int DURATION_CLOSE_CONTROLLER = 6000;//控制视图显示时长
    private final int CLOSE_CONTROLLER = 122;//关闭控制视图消息
    private final int REFRESH_PROGRESS = 133;//刷新播放进度

    @Nullable
    private MediaPlayer mediaPlayer;

    private final VideoRepository videoRepository;

    public final TextureView textureView;
    public final ImageView ivPreview;
    public final ImageView ivPlay;
    public final AppCompatSeekBar seekBar;
    public final FrameLayout mediaControllerView;
    public final ProgressBar loadProgressBar;
    public final TextView currentTimeTv;
    public final TextView durationTimeTv;
    public final ImageView ivScreen;

    private int mWidth;
    private int mHeight;
    private int screenOrientation;

    private boolean isMediaAutoPausing = false;//是否是自动暂停的(页面在后台时自动暂停,回到前台时自动播放),手动暂停的不算
    private boolean isPause = false;//页面是否pause
    private int duration;//视频总长度
    private int pausePosition;//暂停时的播放进度
    private int targetPosition;//目标播放进度,从这个进度开始播放
    //目标播放比例,还没prepare之前,不知道视频的总长度。用户拖动了进度条,记住这个比例,等prepare之后根据比例计算出进度
    private float targetRatio;
    private boolean hadSetDataSource = false;//是否设置了播放的资源
    private boolean hadPrepare = false;//是否prepare成功,只有调用过才能正常播放
    private String videoSource;//视频源,本地文件路径或者网络地址
    //是否是网络视频源
    private boolean isNetSource = false;
    //是否下载网络视频源
    private boolean needDownloadNetSource = false;

    public VideoPlayView(@NonNull Context context) {
   
   
        this(context, null);
    }

    public VideoPlayView(@NonNull Context context, @Nullable AttributeSet attrs) {
   
   
        this(context, attrs, 0);
    }

    public VideoPlayView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
   
   
        super(context, attrs, defStyleAttr);
        inflate(context, R.layout.media_play_layout, this);
        textureView = findViewById(R.id.textureView);
        textureView.setSurfaceTextureListener(this);
        ivPreview = findViewById(R.id.previewIv);
        ivPlay = findViewById(R.id.ivPlay);
        seekBar = findViewById(R.id.seekBar);
        loadProgressBar = findViewById(R.id.loadProgressBar);
        currentTimeTv = findViewById(R.id.tvTime);
        durationTimeTv = findViewById(R.id.tvDuration);
        mediaControllerView = findViewById(R.id.mediaControllerView);
        ivScreen = findViewById(R.id.ivScreen);
        findViewById(R.id.frameLayout).setOnClickListener(this);
        seekBar.setOnSeekBarChangeListener(this);
        ivPlay.setOnClickListener(this);
        ivScreen.setOnClickListener(this);
        videoRepository = new VideoRepository();
        initMediaPlayer();
        screenOrientation = getResources().getConfiguration().orientation;
    }

    private void initMediaPlayer() {
   
   
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setScreenOnWhilePlaying(true);
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setOnInfoListener(this);
        mediaPlayer.setOnErrorListener(this);
        mediaPlayer.setOnPreparedListener(this);
        mediaPlayer.setOnCompletionListener(this);
        mediaPlayer.setOnSeekCompleteListener(this);
        mediaPlayer.setOnBufferingUpdateListener(this);
        mediaPlayer.setOnVideoSizeChangedListener(this);
    }

    /**
     * 给MediaPlayer设置播放源
     *
     * @param videoSource 视频源
     */
    private void realSetDataSource(String videoSource) {
   
   
        this.videoSource = videoSource;
        duration = 0;
        durationTimeTv.setText(null);
        hadPrepare = false;
        Uri mediaUri;
        if (isNetSource) {
   
   
            mediaUri = videoRepository.getMediaUri(getContext(), videoSource);
        } else {
   
   
            mediaUri = videoRepository.getLocalMediaUri(videoSource);
        }
        if (mediaUri 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值