直接上代码:
自定义surfaceview
package com.blue.mvvm.media.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;
public class VideoSurfaceView extends SurfaceView {
// 视频宽度
private int videoWidth;
// 视频高度
private int videoHeight;
public VideoSurfaceView(Context context) {
this(context, null);
}
public VideoSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
videoWidth = 0;
videoHeight = 0;
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
}
/**
* 根据视频的宽高设置SurfaceView的宽高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(videoWidth, widthMeasureSpec);
int height = getDefaultSize(videoHeight, heightMeasureSpec);
if (videoWidth > 0 && videoHeight > 0) {
// 获取测量模式和测量大小
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 分情况设置大小
if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 确定值或match_parent
// layout_height = 确定值或match_parent
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// 做适配,不让视频拉伸,保持原来宽高的比例
// for compatibility, we adjust size based on aspect ratio
if ( videoWidth * height < width * videoHeight) {
//Log.i("@@@", "image too wide, correcting");
width = height * videoWidth / videoHeight;
} else if ( videoWidth * height > width * videoHeight) {
//Log.i("@@@", "image too tall, correcting");
height = width * videoHeight / videoWidth;
}
} else if (widthSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 确定值或match_parent
// layout_height = wrap_content
// only the width is fixed, adjust the height to match aspect ratio if possible
width = widthSpecSize;
// 计算高多少,保持原来宽高的比例
height = width * videoHeight / videoWidth;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// couldn't match aspect ratio within the constraints
height = heightSpecSize;
}
} else if (heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = wrap_content
// layout_height = 确定值或match_parent
// only the height is fixed, adjust the width to match aspect ratio if possible
height = heightSpecSize;
// 计算宽多少,保持原来宽高的比例
width = height * videoWidth / videoHeight;
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// couldn't match aspect ratio within the constraints
width = widthSpecSize;
}
} else {
// layout_width = wrap_content
// layout_height = wrap_content
// neither the width nor the height are fixed, try to use actual video size
width = videoWidth;
height = videoHeight;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// too tall, decrease both width and height
height = heightSpecSize;
width = height * videoWidth / videoHeight;
}
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// too wide, decrease both width and height
width = widthSpecSize;
height = width * videoHeight / videoWidth;
}
}
} else {
// no size yet, just adopt the given spec sizes
}
// 设置SurfaceView的宽高
setMeasuredDimension(width, height);
}
/**
* 调整大小
* @param videoWidth
* @param videoHeight
*/
public void adjustSize(int videoWidth, int videoHeight) {
if (videoWidth == 0 || videoHeight == 0) return;
// 赋值自己的宽高
this.videoWidth = videoWidth;
this.videoHeight = videoHeight;
// 设置Holder固定的大小
getHolder().setFixedSize(videoWidth, videoHeight);
// 重新设置自己的大小
requestLayout();
}
}
自定义视频 VideoSurfaceView +MediaPlayer
package com.blue.mvvm.media;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Outline;
import android.graphics.Rect;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.blue.mvvm.R;
import com.blue.mvvm.media.view.VideoSurfaceView;
import com.blue.mvvm.utils.LogUtil;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
/**
* 自定义 视频播放器
*/
public class VideoPlay extends LinearLayout {
private String TAG=VideoPlay.class.getSimpleName();
private Activity mactivity;
private ImageView playOrPauseIv, previewIv; //中间播放按钮
private ImageView playOrPause2, playFull;
private SeekBar mSeekBar;//进度条
private TextView startTime, endTime; //开始时间 结束时间
private VideoSurfaceView surfaceView; //
private RelativeLayout rootViewRl;
private LinearLayout controlLl;
private MediaPlayer mediaPlayer;
private boolean isShow = false;
private int mProgress;
public static final int UPDATE_TIME = 0x0001;
public static final int HIDE_CONTROL = 0x0002;
private VideoPlayListeren mvideoPlayListeren;
private VideoPlayFullListeren mvideoPlayFullListeren;
private Uri mUri;
public VideoPlay(Context context) {
super(context);
this.mactivity = (Activity) context;
initViews();
}
public VideoPlay(Context context, AttributeSet attrs) {
super(context, attrs);
this.mactivity = (Activity) context;
initViews();
}
public VideoPlay(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mactivity = (Activity) context;
initViews();
}
@SuppressLint("NewApi")
public VideoPlay(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mactivity = (Activity) context;
initViews();
}
/**
* 初始化View
*/
private void initViews() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.layout_video_play, null, false);
addView(view);
playOrPauseIv = findViewById(R.id.video_play_playOrPause);
previewIv = findViewById(R.id.video_play_previewImage);
playOrPause2 = findViewById(R.id.video_play_playOrPause2);
startTime = findViewById(R.id.video_play_start_time);
endTime = findViewById(R.id.video_play_end_time);
mSeekBar = findViewById(R.id.video_play_progess);
controlLl = findViewById(R.id.video_play_control_ll);
rootViewRl = findViewById(R.id.video_play_root_rl);
playFull = findViewById(R.id.video_play_full);
initSurfaceView();
initMediaPlayer();
initEvent();
}
/**
* 初始化SurfaceView
*/
private void initSurfaceView() {
surfaceView = findViewById(R.id.video_play_surfaceView);
//设置视频在顶部false
surfaceView.setZOrderOnTop(false);
//设置视频圆角 api>=21 也就是5.0 及以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
surfaceView.setOutlineProvider(new RadiusViewOutlineProvider(10f));
surfaceView.setClipToOutline(true);
}
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mediaPlayer.setDisplay(surfaceHolder);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
} else {
return;
}
}
});
}
/**
* 初始化MediaPlayer
*/
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
//播放时时间变化
startTime.setText(formatTime(mediaPlayer.getCurrentPosition()));//开始时间
endTime.setText(formatTime(mediaPlayer.getDuration()));//结束时间
mSeekBar.setMax(mediaPlayer.getDuration());//进度条最大值 视频最长时间
mSeekBar.setProgress(mediaPlayer.getCurrentPosition());更新进度条
}
});
}
/**
* 格式化时间
* mm:ss
*
* @param time
* @return
*/
private String formatTime(long time) {
String resultTime = "00:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
Date date = new Date(time);
resultTime = simpleDateFormat.format(date);
return resultTime;
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TIME:
updateTime();
mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500);//跟新播放时间 500毫秒更新
break;
case HIDE_CONTROL:
hideControl();//显示操作栏
break;
}
}
};
/**
* 更新播放时间
*/
private void updateTime() {
startTime.setText(formatTime(mediaPlayer.getCurrentPosition()));//更新播放了多长时间
mSeekBar.setProgress(mediaPlayer.getCurrentPosition());//更新进度条
if (null != mvideoPlayListeren) {
mvideoPlayListeren.progressChanged(mediaPlayer.getCurrentPosition());
}
}
/**
* 隐藏进度条
*/
private void hideControl() {
isShow = false;
controlLl.animate().setDuration(300).translationY(controlLl.getHeight());//动画缩出
}
/**
* 显示进度条
*/
private void showControl() {
if (isShow) {
play();
}
isShow = true;
mHandler.removeMessages(HIDE_CONTROL);
mHandler.sendEmptyMessage(UPDATE_TIME);
mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 5000);//控制栏显示5秒
controlLl.animate().setDuration(300).translationY(0);//动画缩进
}
private void play() {
previewIv.setVisibility(GONE);
if (mediaPlayer == null) return;
if (mediaPlayer.isPlaying()) {
//暂停
onPause();
} else {
//开始
mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500);//500毫秒更新开始时间
mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 5000);
playOrPauseIv.setVisibility(GONE);
playOrPause2.setImageResource(android.R.drawable.ic_media_pause);
playOrPause2.setVisibility(VISIBLE);
mediaPlayer.start();
}
}
/**
* 初始化操作 view 点击事件 的监听
*/
private void initEvent() {
rootViewRl.setOnClickListener(view -> {
showControl();
});
playOrPause2.setOnClickListener(view -> {
play();
});
playOrPauseIv.setOnClickListener(view -> {
play();
});
playFull.setOnClickListener(view -> {
if (null != mvideoPlayFullListeren) {
mvideoPlayFullListeren.FullChanged(true);
}
if (mactivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
//如果是横屏 则 切换竖屏
mactivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
///如果是竖屏 则 切换横屏
mactivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
});
//进度条改变监听
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if (mediaPlayer != null && b) {
mProgress = progress;
mediaPlayer.seekTo(mProgress);//设置播放的进度条位置
if (null != mvideoPlayListeren) {
mvideoPlayListeren.progressChanged(mProgress);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
/**
* 初始化播放路径
*
* @param urlOrPath 播放地址 网络地址 本地地址
*/
public void setUrlOrPath(String urlOrPath) {
mUri = Uri.parse(urlOrPath);
if (null == mUri) return;
setPreviewIv(urlOrPath);
//初始化要播放的视频地址 本地 和网络
try {
mediaPlayer.setDataSource(getContext(), mUri);
mediaPlayer.prepareAsync();
} catch (IOException e) {
// e.printStackTrace();
LogUtil.logE(TAG,e+"");
}
}
/**
* 设置首
* @param path
*/
public void setPreviewIv(String path) {
//获取第一帧 图片
previewIv.setVisibility(VISIBLE);
//获取视频缩略图
Glide.with(getContext())
.load(mUri)
.frame(0) //第一桢图
.into(previewIv);
}
/**
* 设置播放回调监听
*
* @param videoPlayListeren
*/
public void setVideoPlayListeren(VideoPlayListeren videoPlayListeren) {
this.mvideoPlayListeren = videoPlayListeren;
}
@FunctionalInterface
public interface VideoPlayListeren {
/**
* 播放回调方法
*
* @param progress 当前播放进度时间
*/
public void progressChanged(int progress);
}
/**
* 设置全屏监听 这里还没写 需求有可能不要
*
* @param videoPlayFullListeren
*/
public void setVideoPlayFullListeren(VideoPlayFullListeren videoPlayFullListeren) {
this.mvideoPlayFullListeren = videoPlayFullListeren;
}
@FunctionalInterface
public interface VideoPlayFullListeren {
public void FullChanged(boolean b);
}
/**
* 设置圆角类
*/
@SuppressLint("NewApi")
public class RadiusViewOutlineProvider extends ViewOutlineProvider {
private float mRadius;
public RadiusViewOutlineProvider(float radius) {
this.mRadius = radius;
}
@Override
public void getOutline(View view, Outline outline) {
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
int leftMargin = 0;
int topMargin = 0;
Rect selfRect = new Rect(leftMargin, topMargin,
rect.right - rect.left - leftMargin,
rect.bottom - rect.top - topMargin);
outline.setRoundRect(selfRect, mRadius);//设置圆角 和
}
}
/**
* 暂停播放
* 在用的activity onPause 周期里面调用
*/
public void onPause() {
//暂停
if (null == mediaPlayer) return;
mHandler.removeMessages(UPDATE_TIME);
mHandler.removeMessages(HIDE_CONTROL);
playOrPauseIv.setVisibility(VISIBLE);
playOrPauseIv.setImageResource(android.R.drawable.ic_media_play);
playOrPause2.setImageResource(android.R.drawable.ic_media_play);
playOrPause2.setVisibility(VISIBLE);
mediaPlayer.pause();
}
}
相应布局 layout_video_play
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_play_root_rl"
android:layout_width="match_parent"
android:clickable="true"
android:layout_height="wrap_content">
<com.blue.mvvm.media.view.VideoSurfaceView
android:id="@+id/video_play_surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/video_play_previewImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:scaleType="fitXY"
/>
<ImageView
android:id="@+id/video_play_playOrPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clickable="true"
android:src="@android:drawable/ic_media_play" />
<LinearLayout
android:id="@+id/video_play_control_ll"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/video_play_playOrPause2"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="12dp"
android:clickable="true"
android:src="@android:drawable/ic_media_play"
android:visibility="visible" />
<TextView
android:id="@+id/video_play_start_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:text="00.00"
android:textColor="#ffffff" />
<SeekBar
android:id="@+id/video_play_progess"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="6"
android:maxHeight="1dp"
android:progressDrawable="@drawable/po_seekbar"
android:thumb="@drawable/seekbar_thumb" />
<TextView
android:id="@+id/video_play_end_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="12dp"
android:text="00.00"
android:textColor="#ffffff" />
<ImageView
android:id="@+id/video_play_full"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:layout_marginLeft="12dp"
android:scaleType="centerCrop"
android:clickable="true"
android:src="@drawable/ic_media_full"
android:visibility="visible" />
</LinearLayout>
</RelativeLayout>
用法在mainActivity main_layout.xml
<com.blue.mvvm.media.VideoPlay
android:id="@+id/main_VideoPlay"
android:layout_width="match_parent"
android:layout_height="300dp" />
用法在mainActivity setUrlOrPath(url)本地视频地址 和 网络地址
binding.mainVideoPlay.setUrlOrPath("https://vdse.bdstatic.com/9712394c6705f0b81841b28590061766.mp4");
****注意重要事情说三遍 如果要全屏播放
在activity里重写
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//横屏 竖屏切换的操作 android:configChanges="orientation|keyboardHidden|screenSize" 不重走activity 的生命周期
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)binding.mainVideoPlay.getLayoutParams();
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
//如果是横屏
params.width=size.x;
params.height=size.y;
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
///如果是竖屏
params.width=size.x;
params.height=size.y/3;
}
}
在重写该方法是要让他全屏时运行到这个方法,必须在manifest里面设置以下方才有效
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"
<activity
android:name=".module.MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"/>
在补充一点 在gradle 里加入glide 获取第一帧图片需要 速度快
implementation 'com.github.bumptech.glide:glide:4.9.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'