深度剖析GSYVideoPlayer:从架构设计到自定义实现的全攻略
引言:视频播放的痛点与解决方案
你是否还在为Android视频播放开发中的兼容性问题而头疼?是否在寻找一个既能满足复杂业务需求,又具备高度可定制性的播放器框架?GSYVideoPlayer作为一款功能全面的开源视频播放器,通过灵活的架构设计和丰富的自定义接口,为开发者提供了一站式解决方案。本文将带你深入探索GSYVideoPlayer的架构设计,并通过实战案例演示如何基于该框架实现高度个性化的视频播放体验。
读完本文,你将能够:
- 理解GSYVideoPlayer的五层架构设计与核心组件
- 掌握播放器内核、缓存策略、渲染模式的灵活切换
- 实现自定义UI控件与交互逻辑
- 解决列表播放、全屏切换、多分辨率适配等常见场景问题
- 优化视频加载速度与播放性能
一、GSYVideoPlayer架构设计解析
1.1 整体架构概览
GSYVideoPlayer采用分层设计思想,将整个播放器系统划分为五个核心层次,每层职责明确且可独立替换。这种松耦合架构确保了框架的高扩展性和灵活性。
1.2 核心组件详解
1.2.1 播放内核层
播放内核层定义了统一的IPlayerManager接口,封装了不同播放引擎的实现细节。目前支持四种内核:
- IjkPlayerManager:基于FFmpeg的IJKPlayer内核,支持多种格式和协议
- ExoPlayerManager:Google官方推荐的ExoPlayer内核,支持DASH、HLS等自适应流
- SystemPlayerManager:Android系统自带的MediaPlayer内核,兼容性好但功能有限
- AliPlayerManager:阿里云播放器内核,针对网络视频优化
通过PlayerFactory可动态切换内核:
// 切换为ExoPlayer内核
PlayerFactory.setPlayManager(Exo2PlayerManager.class);
// 切换为系统播放器内核
PlayerFactory.setPlayManager(SystemPlayerManager.class);
1.2.2 缓存管理层
缓存管理层通过ICacheManager接口定义了缓存的标准操作,提供两种实现:
- ProxyCacheManager:基于代理模式的通用缓存方案,支持所有播放内核
- ExoPlayerCacheManager:专为ExoPlayer设计的缓存实现,支持HLS/DASH等流式缓存
缓存策略可通过以下代码配置:
// 使用ExoPlayer缓存模式(仅支持ExoPlayer)
CacheFactory.setCacheManager(new ExoPlayerCacheManager());
// 使用代理缓存模式(支持所有内核,不支持m3u8)
CacheFactory.setCacheManager(new ProxyCacheManager());
1.2.3 内核管理层
内核管理层是整个框架的核心协调者,主要由GSYVideoManager实现,通过GSYVideoViewBridge接口与UI层交互。它负责:
- 播放状态管理与控制
- 视频信息解析与处理
- 缓存策略应用
- 多播放器实例协调
1.2.4 渲染控件层
渲染控件层提供了三种渲染实现,可根据需求选择:
- TextureRenderView:基于TextureView,支持动画和透明度
- SurfaceRenderView:基于SurfaceView,性能较好但不支持动画
- GLSurfaceRenderView:基于GLSurfaceView,支持滤镜等高级渲染效果
渲染模式可通过以下代码切换:
// 默认TextureView渲染
GSYVideoType.setRenderType(GSYVideoType.TEXTURE);
// SurfaceView渲染
GSYVideoType.setRenderType(GSYVideoType.SUFRACE);
// GLSurfaceView渲染(支持滤镜)
GSYVideoType.setRenderType(GSYVideoType.GLSURFACE);
1.2.5 播放器控件层
播放器控件层是用户直接交互的部分,采用继承体系设计,每层封装特定功能:
- GSYBaseVideoPlayer:基础抽象类,定义核心接口
- GSYVideoPlayer:实现基础播放控制逻辑
- StandardGSYVideoPlayer:标准播放器实现,包含完整控件
- ListGSYVideoPlayer:列表场景专用播放器
- GSYADVideoPlayer:支持广告播放的播放器
1.3 核心工作流程
GSYVideoPlayer的播放流程可概括为以下步骤:
二、基础使用与配置
2.1 快速集成
2.1.1 添加依赖
在项目根目录的build.gradle中添加:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
在app模块的build.gradle中添加:
dependencies {
implementation 'com.github.ZuoYueLiang:GSYVideoPlayer:v8.4.0'
}
2.1.2 配置权限
在AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.1.3 配置Activity
为支持屏幕旋转和尺寸变化,需要在AndroidManifest.xml中配置Activity:
<activity
android:name=".PlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:screenOrientation="portrait" />
2.2 三种基础播放模式
2.2.1 直接播放模式
适用于简单的单个视频播放场景:
public class SimplePlayerActivity extends AppCompatActivity {
private StandardGSYVideoPlayer videoPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_player);
videoPlayer = findViewById(R.id.video_player);
// 配置播放参数
String url = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
videoPlayer.setUp(url, true, "测试视频标题");
// 隐藏标题和返回按钮
videoPlayer.getTitleTextView().setVisibility(View.GONE);
videoPlayer.getBackButton().setVisibility(View.GONE);
// 设置封面
ImageView coverImg = new ImageView(this);
coverImg.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(this).load("封面图片URL").into(coverImg);
videoPlayer.setThumbImageView(coverImg);
// 开始播放
videoPlayer.startPlayLogic();
}
@Override
protected void onPause() {
super.onPause();
videoPlayer.onVideoPause();
}
@Override
protected void onResume() {
super.onResume();
videoPlayer.onVideoResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
videoPlayer.release();
}
@Override
public void onBackPressed() {
if (videoPlayer.backFromWindowFull(this)) {
return;
}
super.onBackPressed();
}
}
布局文件activity_simple_player.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
android:id="@+id/video_player"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_centerInParent="true" />
</RelativeLayout>
2.2.2 列表播放模式
适用于RecyclerView或ListView中的视频列表播放:
public class ListVideoAdapter extends RecyclerView.Adapter<ListVideoAdapter.VideoViewHolder> {
private Context context;
private List<VideoModel> videoList;
public static final String TAG = "ListVideoAdapter";
// 构造函数和其他重写方法省略...
@Override
public void onBindViewHolder(@NonNull VideoViewHolder holder, int position) {
VideoModel video = videoList.get(position);
// 设置视频封面
Glide.with(context).load(video.getCoverUrl()).into(holder.coverImg);
// 配置播放器
holder.videoPlayer.setUpLazy(video.getVideoUrl(), true, null, null, video.getTitle());
// 隐藏标题和返回按钮
holder.videoPlayer.getTitleTextView().setVisibility(View.GONE);
holder.videoPlayer.getBackButton().setVisibility(View.GONE);
// 设置全屏按钮点击事件
holder.videoPlayer.getFullscreenButton().setOnClickListener(v ->
holder.videoPlayer.startWindowFullscreen(context, false, true));
// 设置播放标记和位置,用于列表滑动时的播放控制
holder.videoPlayer.setPlayTag(TAG);
holder.videoPlayer.setPlayPosition(position);
// 配置列表播放参数
holder.videoPlayer.setAutoFullWithSize(true);
holder.videoPlayer.setReleaseWhenLossAudio(false);
holder.videoPlayer.setShowFullAnimation(true);
holder.videoPlayer.setIsTouchWiget(false);
}
public static class VideoViewHolder extends RecyclerView.ViewHolder {
StandardGSYVideoPlayer videoPlayer;
ImageView coverImg;
public VideoViewHolder(View itemView) {
super(itemView);
videoPlayer = itemView.findViewById(R.id.item_video_player);
coverImg = itemView.findViewById(R.id.item_cover_img);
}
}
}
Activity中需要添加滑动监听,在列表滑动时暂停不可见的视频:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
// 滚动停止时检查可见项
checkVisibleVideo();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 正在播放的位置
int playPosition = GSYVideoManager.instance().getPlayPosition();
// 播放标记是否匹配当前列表
if (playPosition >= 0 && GSYVideoManager.instance().getPlayTag().equals(ListVideoAdapter.TAG)) {
// 获取可见区域的位置范围
int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
int lastVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();
// 如果播放位置不在可见区域,释放视频
if (playPosition < firstVisibleItem || playPosition > lastVisibleItem) {
if (!GSYVideoManager.isFullState(context)) {
GSYVideoManager.releaseAllVideos();
adapter.notifyDataSetChanged();
}
}
}
}
});
2.2.3 详情页播放模式
详情页播放通常需要更丰富的交互和控制能力:
public class DetailPlayerActivity extends AppCompatActivity {
private StandardGSYVideoPlayer videoPlayer;
private OrientationUtils orientationUtils;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail_player);
videoPlayer = findViewById(R.id.detail_video_player);
orientationUtils = new OrientationUtils(this, videoPlayer);
// 配置播放器
String videoUrl = getIntent().getStringExtra("video_url");
String title = getIntent().getStringExtra("title");
// 设置封面
ImageView coverImg = new ImageView(this);
coverImg.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(this).load(getIntent().getStringExtra("cover_url")).into(coverImg);
// 使用Builder模式配置播放器参数
GSYVideoOptionBuilder optionBuilder = new GSYVideoOptionBuilder()
.setThumbImageView(coverImg)
.setUrl(videoUrl)
.setCacheWithPlay(true)
.setVideoTitle(title)
.setIsTouchWiget(true)
.setRotateViewAuto(false)
.setLockLand(false)
.setShowFullAnimation(true)
.setNeedLockFull(true)
.setSeekRatio(1.0f)
.setVideoAllCallBack(new GSYSampleCallBack() {
@Override
public void onPrepared(String url, Object... objects) {
super.onPrepared(url, objects);
// 准备完成后启用旋转
orientationUtils.setEnable(true);
}
@Override
public void onQuitFullscreen(String url, Object... objects) {
super.onQuitFullscreen(url, objects);
if (orientationUtils != null) {
orientationUtils.backToProtVideo();
}
}
});
optionBuilder.build(videoPlayer);
// 设置全屏按钮点击事件
videoPlayer.getFullscreenButton().setOnClickListener(v -> {
// 触发旋转
orientationUtils.resolveByClick();
// 进入全屏
videoPlayer.startWindowFullscreen(DetailPlayerActivity.this, true, true);
});
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 屏幕旋转时调整播放器
if (orientationUtils != null && !isPause) {
videoPlayer.onConfigurationChanged(this, newConfig, orientationUtils, true, true);
}
}
@Override
protected void onPause() {
super.onPause();
videoPlayer.onVideoPause();
isPause = true;
}
@Override
protected void onResume() {
super.onResume();
videoPlayer.onVideoResume(false);
isPause = false;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (orientationUtils != null) {
orientationUtils.releaseListener();
}
videoPlayer.release();
}
@Override
public void onBackPressed() {
if (orientationUtils != null) {
orientationUtils.backToProtVideo();
}
if (GSYVideoManager.backFromWindowFull(this)) {
return;
}
super.onBackPressed();
}
}
2.3 核心配置选项
GSYVideoPlayer提供了丰富的配置选项,可通过GSYVideoOptionBuilder或直接调用播放器方法进行设置:
2.3.1 播放控制配置
// 设置播放速度(0.5-2.0)
videoPlayer.setSpeed(1.5f);
// 设置循环播放
videoPlayer.setLooping(true);
// 设置静音
videoPlayer.getAudioManager().setVolume(0);
// 设置初始播放位置(毫秒)
videoPlayer.setSeekOnStart(10000);
// 设置多音轨选择
videoPlayer.setAudioTrackIndex(1);
2.3.2 显示模式配置
// 设置视频比例类型
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_16_9); // 16:9
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_4_3); // 4:3
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL); // 全屏裁减
GSYVideoType.setShowType(GSYVideoType.SCREEN_MATCH_FULL); // 全屏拉伸
// 设置渲染模式
GSYVideoType.setRenderType(GSYVideoType.TEXTURE); // TextureView
GSYVideoType.setRenderType(GSYVideoType.SUFRACE); // SurfaceView
GSYVideoType.setRenderType(GSYVideoType.GLSURFACE); // GLSurfaceView
2.3.3 缓存配置
// 设置缓存路径
File cacheDir = new File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), "video_cache");
videoPlayer.setCachePath(cacheDir);
// 设置预加载大小(字节)
videoPlayer.setPreloadSize(1024 * 1024 * 5); // 5MB
// 清除当前视频缓存
videoPlayer.clearCurrentCache();
三、高级自定义实现
3.1 自定义播放器UI
GSYVideoPlayer的UI层设计允许开发者通过继承实现高度自定义的播放器界面。以下是实现自定义UI的步骤:
3.1.1 创建自定义布局文件
创建layout/custom_video_player.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 渲染视图容器 -->
<FrameLayout
android:id="@+id/surface_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 封面图 -->
<ImageView
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<!-- 加载中视图 -->
<RelativeLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<ProgressBar
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerInParent="true"
android:indeterminateDrawable="@drawable/video_loading" />
</RelativeLayout>
<!-- 底部控制栏 -->
<LinearLayout
android:id="@+id/control_bottom"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:background="@drawable/video_title_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/start"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginLeft="8dp"
android:src="@drawable/video_click_play_selector" />
<TextView
android:id="@+id/current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:text="00:00" />
<SeekBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="3dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:progressDrawable="@drawable/video_progress"
android:thumb="@drawable/video_seek_thumb" />
<TextView
android:id="@+id/total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:textColor="@android:color/white"
android:textSize="12sp"
android:text="00:00" />
<ImageView
android:id="@+id/fullscreen"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginRight="8dp"
android:src="@drawable/video_enlarge" />
</LinearLayout>
<!-- 顶部控制栏 -->
<LinearLayout
android:id="@+id/control_top"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/video_title_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="@+id/back"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginLeft="8dp"
android:src="@drawable/video_back" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="16sp" />
<ImageView
android:id="@+id/setting"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginRight="8dp"
android:src="@drawable/video_setting" />
</LinearLayout>
</RelativeLayout>
3.1.2 创建自定义播放器类
继承StandardGSYVideoPlayer并实现自定义逻辑:
public class CustomVideoPlayer extends StandardGSYVideoPlayer {
// 自定义控件
private ImageView settingBtn;
public CustomVideoPlayer(Context context) {
super(context);
}
public CustomVideoPlayer(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
public CustomVideoPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void init(Context context) {
super.init(context);
// 初始化自定义控件
settingBtn = findViewById(R.id.setting);
if (settingBtn != null) {
settingBtn.setOnClickListener(v -> showSettingDialog());
}
}
@Override
public int getLayoutId() {
// 返回自定义布局
return R.layout.layout_custom_video_player;
}
private void showSettingDialog() {
// 显示自定义设置对话框
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("视频设置")
.setItems(new String[]{"清晰度选择", "播放速度", "画面比例", "字幕设置"}, (dialog, which) -> {
switch (which) {
case 0:
// 清晰度选择逻辑
showResolutionDialog();
break;
case 1:
// 播放速度设置
showSpeedDialog();
break;
case 2:
// 画面比例设置
showRatioDialog();
break;
case 3:
// 字幕设置
showSubtitleDialog();
break;
}
}).show();
}
private void showSpeedDialog() {
// 播放速度选择对话框
String[] speeds = {"0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "2.0x"};
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("播放速度")
.setItems(speeds, (dialog, which) -> {
float speed = 1.0f;
switch (which) {
case 0: speed = 0.5f; break;
case 1: speed = 0.75f; break;
case 2: speed = 1.0f; break;
case 3: speed = 1.25f; break;
case 4: speed = 1.5f; break;
case 5: speed = 2.0f; break;
}
setSpeed(speed);
}).show();
}
// 其他设置方法省略...
@Override
public void setUiWigetVisibility(int visibility) {
super.setUiWigetVisibility(visibility);
// 控制自定义控件的显示/隐藏
if (settingBtn != null) {
settingBtn.setVisibility(visibility);
}
}
@Override
protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
super.cloneParams(from, to);
// 克隆自定义参数
if (from instanceof CustomVideoPlayer && to instanceof CustomVideoPlayer) {
((CustomVideoPlayer) to).settingBtn = ((CustomVideoPlayer) from).settingBtn;
}
}
}
3.1.3 在布局中使用自定义播放器
<com.example.CustomVideoPlayer
android:id="@+id/custom_video_player"
android:layout_width="match_parent"
android:layout_height="200dp" />
3.2 自定义播放控制逻辑
除了UI自定义,GSYVideoPlayer还允许开发者重写播放控制逻辑,实现特定的交互需求:
3.2.1 自定义手势控制
public class GestureVideoPlayer extends StandardGSYVideoPlayer {
private boolean isVolumeGesture = false;
private boolean isBrightnessGesture = false;
private float startX, startY;
private float startBrightness, startVolume;
// 构造函数省略...
@Override
public boolean onTouch(View v, MotionEvent event) {
if (!mIfCurrentIsFullscreen && mSmallVideoView != null) {
return super.onTouch(v, event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
startBrightness = getWindow().getAttributes().screenBrightness;
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
startVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
isVolumeGesture = false;
isBrightnessGesture = false;
break;
case MotionEvent.ACTION_MOVE:
float deltaX = event.getX() - startX;
float deltaY = event.getY() - startY;
// 判断手势类型(左右滑动调整进度,右侧上下滑动调整音量,左侧上下滑动调整亮度)
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 左右滑动,调整进度
if (Math.abs(deltaX) > mTouchSlop) {
touchSurfaceMoveFullLogic(deltaX, deltaY);
}
} else {
if (event.getX() < getWidth() / 2) {
// 左侧上下滑动,调整亮度
if (!isVolumeGesture) {
isBrightnessGesture = true;
adjustBrightness(deltaY);
}
} else {
// 右侧上下滑动,调整音量
if (!isBrightnessGesture) {
isVolumeGesture = true;
adjustVolume(deltaY);
}
}
}
break;
case MotionEvent.ACTION_UP:
// 抬起手势,隐藏控制栏
cancelDismissControlViewTimer();
startDismissControlViewTimer();
break;
}
return true;
}
private void adjustBrightness(float deltaY) {
Window window = ((Activity) getContext()).getWindow();
WindowManager.LayoutParams params = window.getAttributes();
float brightness = startBrightness + deltaY / getHeight() * 2;
brightness = Math.max(0.1f, Math.min(brightness, 1.0f));
params.screenBrightness = brightness;
window.setAttributes(params);
// 显示亮度调整提示
showBrightnessDialog((int)(brightness * 100));
}
private void adjustVolume(float deltaY) {
AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int currentVolume = (int)(startVolume + deltaY / getHeight() * maxVolume * 2);
currentVolume = Math.max(0, Math.min(currentVolume, maxVolume));
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
// 显示音量调整提示
showVolumeDialog(currentVolume * 100 / maxVolume);
}
@Override
protected void touchDoubleUp(MotionEvent e) {
// 双击放大/缩小
if (mIfCurrentIsFullscreen) {
// 全屏状态下双击退出全屏
backFromFullScreen();
} else {
// 非全屏状态下双击进入全屏
startWindowFullscreen(getContext(), true, true);
}
}
}
3.2.2 自定义播放状态回调
public class TrackingVideoPlayer extends StandardGSYVideoPlayer {
private VideoTrackingListener trackingListener;
// 构造函数省略...
public void setTrackingListener(VideoTrackingListener listener) {
this.trackingListener = listener;
}
@Override
public void onPrepared(String url, Object... objects) {
super.onPrepared(url, objects);
// 发送准备完成事件
if (trackingListener != null) {
trackingListener.onVideoPrepared(url, getCurrentPosition());
}
}
@Override
public void onCompletion() {
super.onCompletion();
// 发送播放完成事件
if (trackingListener != null) {
trackingListener.onVideoCompleted(getPlayTag(), getPlayPosition());
}
}
@Override
public void onError(int what, int extra) {
super.onError(what, extra);
// 发送播放错误事件
if (trackingListener != null) {
trackingListener.onVideoError(getPlayTag(), what, extra);
}
}
@Override
public void onSeekComplete() {
super.onSeekComplete();
// 发送 seek 完成事件
if (trackingListener != null) {
trackingListener.onSeekCompleted(getCurrentPosition());
}
}
// 播放进度跟踪
private Runnable progressTrackingRunnable = new Runnable() {
@Override
public void run() {
if (mCurrentState == CURRENT_STATE_PLAYING && trackingListener != null) {
trackingListener.onProgressUpdate(getCurrentPosition(), getDuration());
// 每秒发送一次进度更新
postDelayed(this, 1000);
}
}
};
@Override
public void startPlayLogic() {
super.startPlayLogic();
// 开始进度跟踪
removeCallbacks(progressTrackingRunnable);
post(progressTrackingRunnable);
}
@Override
public void onVideoPause() {
super.onVideoPause();
// 暂停进度跟踪
removeCallbacks(progressTrackingRunnable);
if (trackingListener != null) {
trackingListener.onVideoPaused(getCurrentPosition());
}
}
// 跟踪监听器接口
public interface VideoTrackingListener {
void onVideoPrepared(String url, long position);
void onVideoCompleted(String playTag, int position);
void onVideoError(String playTag, int what, int extra);
void onSeekCompleted(long position);
void onProgressUpdate(long currentPosition, long duration);
void onVideoPaused(long position);
}
}
3.3 自定义播放内核
GSYVideoPlayer允许通过实现IPlayerManager接口来集成自定义播放内核:
public class CustomPlayerManager implements IPlayerManager {
private CustomMediaPlayer mMediaPlayer;
private Context mContext;
private IPlayerInitSuccessListener mListener;
@Override
public void initVideoPlayer(Context context, String url, Map<String, String> mapHeadData, File cachePath) {
mContext = context;
mMediaPlayer = new CustomMediaPlayer();
// 初始化自定义播放器
try {
mMediaPlayer.setDataSource(context, Uri.parse(url), mapHeadData);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(mp -> {
if (mListener != null) {
mListener.onPlayerInitSuccess();
}
});
// 设置其他监听器...
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void start() {
if (mMediaPlayer != null) {
mMediaPlayer.start();
}
}
@Override
public void pause() {
if (mMediaPlayer != null) {
mMediaPlayer.pause();
}
}
@Override
public void stop() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
}
}
@Override
public void reset() {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
}
}
@Override
public boolean isPlaying() {
return mMediaPlayer != null && mMediaPlayer.isPlaying();
}
@Override
public void seekTo(long time) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(time);
}
}
@Override
public long getCurrentPosition() {
return mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : 0;
}
@Override
public long getDuration() {
return mMediaPlayer != null ? mMediaPlayer.getDuration() : 0;
}
@Override
public void release() {
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}
// 其他接口方法实现...
@Override
public void setPlayerInitSuccessListener(IPlayerInitSuccessListener listener) {
mListener = listener;
}
}
使用自定义内核:
// 设置自定义播放器内核
PlayerFactory.setPlayManager(CustomPlayerManager.class);
四、性能优化与最佳实践
4.1 列表播放优化
列表播放是视频应用中常见的场景,也是性能问题的高发区。以下是几点优化建议:
4.1.1 复用播放器实例
public class ReuseVideoPlayerManager {
private static ReuseVideoPlayerManager instance;
private StandardGSYVideoPlayer mVideoPlayer;
private boolean isInUse;
private ReuseVideoPlayerManager() {}
public static ReuseVideoPlayerManager getInstance() {
if (instance == null) {
instance = new ReuseVideoPlayerManager();
}
return instance;
}
public StandardGSYVideoPlayer getVideoPlayer(Context context) {
if (mVideoPlayer == null) {
mVideoPlayer = new StandardGSYVideoPlayer(context);
mVideoPlayer.setUpLazy(null, true, null, null, null);
mVideoPlayer.setPlayTag("reuse_tag");
mVideoPlayer.setReleaseWhenLossAudio(false);
}
isInUse = true;
return mVideoPlayer;
}
public void releaseVideoPlayer() {
if (mVideoPlayer != null && !isInUse) {
mVideoPlayer.release();
mVideoPlayer = null;
}
}
public void setInUse(boolean inUse) {
isInUse = inUse;
if (!inUse) {
releaseVideoPlayer();
}
}
}
4.1.2 滑动时暂停播放
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private Handler mHandler = new Handler();
private Runnable mScrollRunnable = () -> {
// 滚动停止后检查可见项
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager == null) return;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
// 获取当前播放位置
int currentPlayPosition = GSYVideoManager.instance().getPlayPosition();
String currentPlayTag = GSYVideoManager.instance().getPlayTag();
// 如果当前播放的视频不在可见区域,则暂停播放
if (currentPlayPosition >= 0 && currentPlayTag.equals("video_list_tag")) {
if (currentPlayPosition < firstVisibleItem || currentPlayPosition > lastVisibleItem) {
if (!GSYVideoManager.isFullState((Activity) context)) {
GSYVideoManager.releaseAllVideos();
adapter.notifyDataSetChanged();
}
}
}
};
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
// 滚动停止,延迟检查可见项
mHandler.postDelayed(mScrollRunnable, 300);
} else {
// 滚动中,移除延迟任务
mHandler.removeCallbacks(mScrollRunnable);
// 如果不是空闲状态且不是拖动状态,则暂停所有视频
if (newState != RecyclerView.SCROLL_STATE_DRAGGING) {
int currentPlayPosition = GSYVideoManager.instance().getPlayPosition();
if (currentPlayPosition != -1) {
GSYVideoManager.instance().getCurrentPlayer().onVideoPause();
}
}
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
4.2 视频加载速度优化
4.2.1 预加载策略
// 设置预加载大小
CacheFactory.setCacheManager(new ProxyCacheManager() {
@Override
public long getPreloadSize() {
return 1024 * 1024 * 10; // 预加载10MB
}
});
// 列表预加载
public void preloadVideo(String url, int position) {
if (position <= mNextPreloadPosition + 2 && position > mNextPreloadPosition) {
// 预加载下下个视频
mNextPreloadPosition = position;
String nextUrl = mVideoList.get(position + 2).getVideoUrl();
if (!TextUtils.isEmpty(nextUrl)) {
HttpProxyCacheServer proxyServer = getProxyServer();
if (proxyServer != null) {
proxyServer.preload(nextUrl, null);
}
}
}
}
4.2.2 多CDN切换
public class MultiCDNVideoManager {
private List<String> cdnUrls = new ArrayList<>();
private int currentCdnIndex = 0;
private long[] cdnSpeeds; // 记录各CDN速度
public MultiCDNVideoManager(List<String> urls) {
cdnUrls.addAll(urls);
cdnSpeeds = new long[urls.size()];
}
// 测试各CDN速度
public void testCDNSpeeds(final CDNTestListener listener) {
for (int i = 0; i < cdnUrls.size(); i++) {
final int index = i;
testSingleCDNSpeed(cdnUrls.get(i), speed -> {
cdnSpeeds[index] = speed;
boolean allTested = true;
for (long s : cdnSpeeds) {
if (s == 0) {
allTested = false;
break;
}
}
if (allTested) {
// 找出最快的CDN
currentCdnIndex = 0;
long maxSpeed = 0;
for (int j = 0; j < cdnSpeeds.length; j++) {
if (cdnSpeeds[j] > maxSpeed) {
maxSpeed = cdnSpeeds[j];
currentCdnIndex = j;
}
}
if (listener != null) {
listener.onCDNTestCompleted(cdnUrls.get(currentCdnIndex));
}
}
});
}
}
private void testSingleCDNSpeed(String url, final SpeedTestListener listener) {
// 测试单个CDN速度
new Thread(() -> {
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
URL testUrl = new URL(url);
connection = (HttpURLConnection) testUrl.openConnection();
connection.setConnectTimeout(3000);
connection.setReadTimeout(5000);
connection.connect();
if (connection.getResponseCode() == 200) {
inputStream = connection.getInputStream();
byte[] buffer = new byte[1024 * 10]; // 10KB缓冲区
long startTime = System.currentTimeMillis();
int bytesRead = inputStream.read(buffer);
long endTime = System.currentTimeMillis();
if (bytesRead > 0 && endTime > startTime) {
long speed = (bytesRead * 1000) / (endTime - startTime); // B/s
listener.onSpeedTestCompleted(speed);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
listener.onSpeedTestCompleted(0); // 测试失败
}).start();
}
// 获取当前最优CDN URL
public String getCurrentBestUrl() {
return cdnUrls.get(currentCdnIndex);
}
// 切换到下一个CDN
public String switchNextCDN() {
currentCdnIndex = (currentCdnIndex + 1) % cdnUrls.size();
return cdnUrls.get(currentCdnIndex);
}
public interface CDNTestListener {
void onCDNTestCompleted(String bestUrl);
}
private interface SpeedTestListener {
void onSpeedTestCompleted(long speed);
}
}
4.3 常见问题解决方案
4.3.1 全屏切换闪烁问题
@Override
public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
// 禁止默认动画
setShowFullAnimation(false);
GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
// 添加自定义过渡动画
if (gsyBaseVideoPlayer != null) {
ViewGroup parent = (ViewGroup) gsyBaseVideoPlayer.getParent();
if (parent != null) {
parent.setBackgroundColor(Color.BLACK);
}
// 添加淡入动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(300);
gsyBaseVideoPlayer.startAnimation(alphaAnimation);
}
return gsyBaseVideoPlayer;
}
4.3.2 音频焦点冲突问题
public class AudioFocusManager implements AudioManager.OnAudioFocusChangeListener {
private Context mContext;
private AudioManager mAudioManager;
private StandardGSYVideoPlayer mVideoPlayer;
private boolean isPausedByFocusLoss;
public AudioFocusManager(Context context, StandardGSYVideoPlayer videoPlayer) {
mContext = context;
mVideoPlayer = videoPlayer;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
// 请求音频焦点
public boolean requestAudioFocus() {
if (mAudioManager == null) return false;
int result = mAudioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
// 放弃音频焦点
public void abandonAudioFocus() {
if (mAudioManager != null) {
mAudioManager.abandonAudioFocus(this);
}
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 重新获得焦点,恢复播放
if (mVideoPlayer != null && isPausedByFocusLoss) {
mVideoPlayer.onVideoResume();
isPausedByFocusLoss = false;
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 永久失去焦点,停止播放
if (mVideoPlayer != null) {
mVideoPlayer.onVideoPause();
abandonAudioFocus();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放
if (mVideoPlayer != null && mVideoPlayer.getCurrentPlayer().isPlaying()) {
mVideoPlayer.onVideoPause();
isPausedByFocusLoss = true;
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 暂时失去焦点,但可以继续播放(降低音量)
if (mVideoPlayer != null) {
mVideoPlayer.getAudioManager().setVolume(0.2f);
}
break;
}
}
}
五、总结与展望
GSYVideoPlayer凭借其灵活的分层架构和丰富的自定义接口,为Android视频播放开发提供了强大的支持。本文从架构设计到实际应用,详细介绍了GSYVideoPlayer的核心组件、基础使用和高级自定义实现。通过合理利用框架提供的扩展点,开发者可以轻松实现从简单播放到复杂业务场景的各种需求。
随着5G技术的普及和用户对视频体验要求的提高,未来视频播放技术将朝着更高清、更流畅、更智能的方向发展。GSYVideoPlayer也将继续迭代优化,进一步提升播放性能,支持更多新兴格式和协议,为开发者提供更强大的视频播放解决方案。
掌握GSYVideoPlayer的架构设计与自定义实现,不仅能够快速解决实际项目中的视频播放问题,更能帮助开发者理解复杂Android组件的设计思想,提升系统架构能力。希望本文能为你的视频播放开发之路提供有益的参考和启发。
附录:常用API速查表
| 功能类别 | 核心方法 | 说明 |
|---|---|---|
| 播放控制 | start() | 开始播放 |
| pause() | 暂停播放 | |
| stop() | 停止播放 | |
| seekTo(long time) | 定位到指定时间 | |
| release() | 释放播放器资源 | |
| 状态查询 | isPlaying() | 是否正在播放 |
| getCurrentPosition() | 获取当前播放位置 | |
| getDuration() | 获取视频总时长 | |
| getBufferPercentage() | 获取缓冲进度 | |
| 配置设置 | setUp(String url, boolean cacheWithPlay, String title) | 设置播放地址和标题 |
| setPlaySpeed(float speed) | 设置播放速度 | |
| setLooping(boolean looping) | 设置循环播放 | |
| setCachePath(File path) | 设置缓存路径 | |
| 显示控制 | startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) | 进入全屏 |
| backFromWindowFull() | 退出全屏 | |
| setShowFullAnimation(boolean show) | 设置全屏切换动画 | |
| setVideoScalingMode(int mode) | 设置视频缩放模式 | |
| 事件监听 | setVideoAllCallBack(VideoAllCallBack callBack) | 设置全局播放回调 |
| setOnPreparedListener(OnPreparedListener listener) | 设置准备完成监听 | |
| setOnCompletionListener(OnCompletionListener listener) | 设置播放完成监听 | |
| setOnErrorListener(OnErrorListener listener) | 设置错误监听 |
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



