一、概述
一个简单的视频播放器,满足一般的需求。使用原生的 MediaPlayer 和 TextureView来实现。
功能点:
- 获取视频的首帧进行展示,网络视频的首帧会缓存
- 视频播放,本地视频或者网络视频
- 感知生命周期,页面不可见自动暂停播放,页面关闭,自动释放
- 可以在RecyclerView的item中使用
- 网络视频可配置下载(如果网络视频地址可以下载),下次再播放时播放下载好的视频。
演示图:
二、使用
VideoPlayView videoPlayView = findViewById(R.id.videoPlayView);
getLifecycle().addObserver(videoPlayView);
//设置视频文件路径
videoPlayView.setFileDataSource(filePath);
//设置网络视频地址
//videoPlayView.setNetDataSource(netAddress);
int position = intent.getIntExtra("position", 0);
videoPlayView.setTargetPosition(position);
三、实现代码
主要涉及三个类:
- VideoPlayView 播放器
- VideoRepository 获取视频首帧,缓存视频首帧,判断网络视频是否有缓存等处理
- 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