6 changed files with 424 additions and 170 deletions
@ -1,35 +1,347 @@ |
|||||
package qianmu.container.view; |
package qianmu.container.view; |
||||
|
|
||||
import android.content.Context; |
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.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 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. |
* Created by Android Studio. |
||||
* User: linzhibin |
* User: linzhibin |
||||
* Date: 2021/5/19 |
* Date: 2021/5/19 |
||||
* Time: 13:59 |
* 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) { |
public CustomerVideoView(Context context) { |
||||
super(context); |
super(context); |
||||
|
init(context); |
||||
} |
} |
||||
|
|
||||
public CustomerVideoView(Context context, AttributeSet attrs) { |
public CustomerVideoView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
super(context, attrs); |
||||
|
init(context); |
||||
} |
} |
||||
|
|
||||
public CustomerVideoView(Context context, AttributeSet attrs, int defStyleAttr) { |
public CustomerVideoView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, 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 |
@Override |
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
||||
// 其实就是在这里做了一些处理。
|
|
||||
|
// 保持原有的测量逻辑
|
||||
int width = getDefaultSize(0, widthMeasureSpec); |
int width = getDefaultSize(0, widthMeasureSpec); |
||||
int height = getDefaultSize(0, heightMeasureSpec); |
int height = getDefaultSize(0, heightMeasureSpec); |
||||
setMeasuredDimension(width, height); |
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