6 changed files with 424 additions and 170 deletions
@ -1,35 +1,347 @@ |
|||
package qianmu.container.view; |
|||
|
|||
import android.content.Context; |
|||
import android.graphics.Color; |
|||
import android.graphics.PixelFormat; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.util.AttributeSet; |
|||
import android.util.Log; |
|||
import android.view.SurfaceView; |
|||
import android.view.TextureView; |
|||
import android.view.View; |
|||
import android.widget.FrameLayout; |
|||
import android.widget.VideoView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
|
|||
import com.google.android.exoplayer2.PlaybackException; |
|||
import com.google.android.exoplayer2.ExoPlayer; |
|||
import com.google.android.exoplayer2.MediaItem; |
|||
import com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; |
|||
import com.google.android.exoplayer2.ui.StyledPlayerView; |
|||
|
|||
/** |
|||
* Created by Android Studio. |
|||
* User: linzhibin |
|||
* Date: 2021/5/19 |
|||
* Time: 13:59 |
|||
*/ |
|||
public class CustomerVideoView extends VideoView { |
|||
public class CustomerVideoView extends FrameLayout { |
|||
|
|||
private StyledPlayerView playerView; |
|||
private ExoPlayer player; |
|||
private OnCompletionListener onCompletionListener; |
|||
private OnErrorListener onErrorListener; |
|||
private OnPreparedListener onPreparedListener; |
|||
private OnInfoListener onInfoListener; |
|||
|
|||
// 监听器接口,保持与 VideoView 兼容
|
|||
public interface OnCompletionListener { |
|||
void onCompletion(); |
|||
} |
|||
|
|||
public interface OnErrorListener { |
|||
boolean onError(); |
|||
} |
|||
|
|||
public interface OnPreparedListener { |
|||
void onPrepared(); |
|||
} |
|||
|
|||
public interface OnInfoListener { |
|||
void onInfo(); |
|||
} |
|||
|
|||
|
|||
public CustomerVideoView(Context context) { |
|||
super(context); |
|||
init(context); |
|||
} |
|||
|
|||
public CustomerVideoView(Context context, AttributeSet attrs) { |
|||
super(context, attrs); |
|||
init(context); |
|||
} |
|||
|
|||
public CustomerVideoView(Context context, AttributeSet attrs, int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
init(context); |
|||
} |
|||
|
|||
private void init(Context context) { |
|||
// 创建 StyledPlayerView
|
|||
playerView = new StyledPlayerView(context); |
|||
addView(playerView); |
|||
// 初始化 ExoPlayer
|
|||
initializePlayer(); |
|||
// 设置背景透明
|
|||
playerView.setBackgroundColor(Color.TRANSPARENT); |
|||
playerView.setShutterBackgroundColor(Color.TRANSPARENT); |
|||
|
|||
// 3. 设置使用透明背景
|
|||
playerView.setUseArtwork(false); // 如果不需要 artwork
|
|||
playerView.setDefaultArtwork(null); // 清除默认 artwork
|
|||
|
|||
// 4. 设置 SurfaceView 透明
|
|||
View videoSurfaceView = playerView.getVideoSurfaceView(); |
|||
if (videoSurfaceView != null) { |
|||
videoSurfaceView.setBackgroundColor(Color.TRANSPARENT); |
|||
|
|||
// 如果使用 SurfaceView,设置格式支持透明
|
|||
if (videoSurfaceView instanceof SurfaceView) { |
|||
SurfaceView surfaceView = (SurfaceView) videoSurfaceView; |
|||
surfaceView.setZOrderOnTop(true); // 必须设置为 true 才能使透明生效
|
|||
surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); |
|||
}else if (videoSurfaceView instanceof TextureView) { |
|||
// TextureView 默认支持透明
|
|||
videoSurfaceView.setBackgroundColor(Color.TRANSPARENT); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void initializePlayer() { |
|||
// 创建 ExoPlayer 实例
|
|||
player = new SimpleExoPlayer.Builder(getContext()) |
|||
.setTrackSelector(new DefaultTrackSelector(getContext())) |
|||
.build(); |
|||
player.setPlayWhenReady(false); |
|||
// 设置播放器到 PlayerView
|
|||
playerView.setPlayer(player); |
|||
setupPlayerListeners(); |
|||
// 默认设置(可以根据需要调整)
|
|||
playerView.setUseController(false); |
|||
// 默认设置 Surface 在底部
|
|||
setZOrderOnTop(false); |
|||
setZOrderMediaOverlay(false); |
|||
} |
|||
|
|||
private void setupPlayerListeners() { |
|||
if (player == null) return; |
|||
// 播放完成监听
|
|||
player.addListener(new Player.Listener() { |
|||
@Override |
|||
public void onPlaybackStateChanged(int playbackState) { |
|||
if (playbackState == Player.STATE_ENDED) { |
|||
if (onCompletionListener != null) { |
|||
onCompletionListener.onCompletion(); |
|||
} |
|||
} |
|||
if(playbackState == Player.STATE_READY){ |
|||
// 视频准备就绪
|
|||
if (onPreparedListener != null) { |
|||
onPreparedListener.onPrepared(); |
|||
} |
|||
if (playerView != null) { |
|||
playerView.setVisibility(VISIBLE); |
|||
playerView.setAlpha(0); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPlayerError(@NonNull PlaybackException error) { |
|||
if (onErrorListener != null) { |
|||
onErrorListener.onError(); |
|||
} |
|||
} |
|||
@Override |
|||
public void onRenderedFirstFrame() { |
|||
// 视频开始渲染第一帧
|
|||
if (onInfoListener != null) { |
|||
onInfoListener.onInfo(); |
|||
onInfoListener=null; |
|||
} |
|||
if(playerView != null){ |
|||
playerView.setAlpha(1); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
|||
// 其实就是在这里做了一些处理。
|
|||
// 保持原有的测量逻辑
|
|||
int width = getDefaultSize(0, widthMeasureSpec); |
|||
int height = getDefaultSize(0, heightMeasureSpec); |
|||
setMeasuredDimension(width, height); |
|||
|
|||
// 让 StyledPlayerView 填满整个父布局
|
|||
if (playerView != null) { |
|||
playerView.measure( |
|||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), |
|||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) |
|||
); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
|||
super.onLayout(changed, left, top, right, bottom); |
|||
// 布局 StyledPlayerView 使其填满整个控件
|
|||
if (playerView != null) { |
|||
playerView.layout(0, 0, getWidth(), getHeight()); |
|||
} |
|||
} |
|||
|
|||
// ==================== 实现 VideoView 的常用方法 ====================
|
|||
|
|||
// 1. setBackground - 设置背景
|
|||
@Override |
|||
public void setBackground(Drawable background) { |
|||
super.setBackground(background); |
|||
// 如果需要设置 PlayerView 的背景
|
|||
if (playerView != null) { |
|||
playerView.setVisibility(GONE); |
|||
} |
|||
} |
|||
|
|||
public void clearBackground(){ |
|||
super.setBackground(null); |
|||
} |
|||
|
|||
@Override |
|||
public void setBackgroundColor(int color) { |
|||
super.setBackgroundColor(color); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void setBackgroundResource(int resid) { |
|||
super.setBackgroundResource(resid); |
|||
if (playerView != null) { |
|||
playerView.setVisibility(GONE); |
|||
} |
|||
} |
|||
|
|||
// 2. setZOrderOnTop - 设置 Surface 是否在最顶层
|
|||
public void setZOrderOnTop(boolean onTop) { |
|||
if (playerView != null) { |
|||
// 获取 PlayerView 内部的 SurfaceView
|
|||
View videoSurfaceView = playerView.getVideoSurfaceView(); |
|||
if (videoSurfaceView instanceof SurfaceView) { |
|||
SurfaceView surfaceView = (SurfaceView) videoSurfaceView; |
|||
surfaceView.setZOrderOnTop(onTop); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 3. setZOrderMediaOverlay - 设置 Surface 作为媒体叠加层
|
|||
public void setZOrderMediaOverlay(boolean isMediaOverlay) { |
|||
if (playerView != null) { |
|||
View videoSurfaceView = playerView.getVideoSurfaceView(); |
|||
if (videoSurfaceView instanceof SurfaceView) { |
|||
SurfaceView surfaceView = (SurfaceView) videoSurfaceView; |
|||
surfaceView.setZOrderMediaOverlay(isMediaOverlay); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 4. setVisibility - 设置可见性
|
|||
@Override |
|||
public void setVisibility(int visibility) { |
|||
super.setVisibility(visibility); |
|||
if (playerView != null) { |
|||
playerView.setVisibility(visibility); |
|||
} |
|||
} |
|||
|
|||
// 5. 视频控制相关方法
|
|||
public void setVideoPath(String path) { |
|||
if (player != null && path != null) { |
|||
MediaItem mediaItem = MediaItem.fromUri(path); |
|||
player.setMediaItem(mediaItem); |
|||
player.prepare(); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void start() { |
|||
if (player != null) { |
|||
player.play(); |
|||
} |
|||
} |
|||
|
|||
public void pause() { |
|||
if (player != null) { |
|||
player.pause(); |
|||
} |
|||
} |
|||
|
|||
public void stopPlayback() { |
|||
if (player != null) { |
|||
player.stop(); |
|||
} |
|||
} |
|||
|
|||
public void seekTo(int position) { |
|||
if (player != null) { |
|||
player.seekTo(position); |
|||
} |
|||
} |
|||
|
|||
public boolean isPlaying() { |
|||
return player != null && player.isPlaying(); |
|||
} |
|||
|
|||
// 6. 监听器设置
|
|||
public void setOnCompletionListener(OnCompletionListener listener) { |
|||
this.onCompletionListener = listener; |
|||
} |
|||
|
|||
public void setOnPreparedListener(OnPreparedListener listener) { |
|||
this.onPreparedListener = listener; |
|||
} |
|||
|
|||
public void setOnInfoListener(OnInfoListener listener) { |
|||
this.onInfoListener = listener; |
|||
} |
|||
|
|||
public void setOnErrorListener(OnErrorListener listener) { |
|||
this.onErrorListener = listener; |
|||
} |
|||
|
|||
// 可选:添加与 ExoPlayer 兼容的错误监听器
|
|||
public void setOnExoPlayerErrorListener(Player.Listener errorListener) { |
|||
if (player != null && errorListener != null) { |
|||
player.addListener(errorListener); |
|||
} |
|||
} |
|||
|
|||
public void release() { |
|||
if (player != null) { |
|||
player.release(); |
|||
player = null; |
|||
} |
|||
if (playerView != null) { |
|||
playerView.setPlayer(null); |
|||
} |
|||
onCompletionListener = null; |
|||
onErrorListener = null; |
|||
} |
|||
|
|||
@Override |
|||
protected void onDetachedFromWindow() { |
|||
super.onDetachedFromWindow(); |
|||
release(); |
|||
} |
|||
|
|||
// 重置播放器
|
|||
public void reset() { |
|||
if (player != null) { |
|||
player.stop(); |
|||
player.clearMediaItems(); |
|||
} |
|||
} |
|||
|
|||
// 设置音量
|
|||
public void setVolume(float volume) { |
|||
if (player != null) { |
|||
player.setVolume(volume); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
Loading…
Reference in new issue