饺子播放器使用问题请进群交流,如果失效,请添加cxw9681微信号进群
jzvd使用
使用jzvd控件之前,记住一句话:
无论是在xml中使用jzvd还是在代码中动态添加jzvd,jzvd的外面都要包一层,也就是在不能作为跟节点,要包在ViewGroup中。
xml中:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<cn.jzvd.JzvdStd
android:id="@+id/jz"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</FrameLayout>
在代码中动态添加:
RelativeLayout DECOR_VIEW = new RelativeLayout(jzvdContext);
ViewGroup.LayoutParams fullLayout = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
DECOR_VIEW.addView(this, fullLayout);
//这个wm添加的是ViewGroup,不能直接添加jzvd
mWindowManager.addView(DECOR_VIEW, getLayoutParams());
wm不能直接添加jzvd,需要在外边包一层。
WindowManager中添加饺子视频播放器
在WindowManager中添加jzvd,需要对jzvd进行改造(全屏播放也已经实现)。
代码如下:
package com.dynamic.widget.jzvd;
import android.content.Context;
import android.graphics.PixelFormat;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.lang.reflect.Constructor;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by Nathen on 16/7/30.
*/
public abstract class Jzvd extends FrameLayout implements View.OnClickListener, SeekBar.OnSeekBarChangeListener, View.OnTouchListener {
public static final String TAG = "JZVD";
public static final int SCREEN_NORMAL = 0;
public static final int SCREEN_FULLSCREEN = 1;
public static final int STATE_IDLE = -1;
public static final int STATE_NORMAL = 0;
public static final int STATE_PREPARING = 1;
public static final int STATE_PREPARING_CHANGE_URL = 2;
public static final int STATE_PREPARING_PLAYING = 3;
public static final int STATE_PREPARED = 4;
public static final int STATE_PLAYING = 5;
public static final int STATE_PAUSE = 6;
public static final int STATE_AUTO_COMPLETE = 7;
public static final int STATE_ERROR = 8;
public static final int VIDEO_IMAGE_DISPLAY_TYPE_FILL_PARENT = 1;
public static final int VIDEO_IMAGE_DISPLAY_TYPE_FILL_SCROP = 2;
public static final int VIDEO_IMAGE_DISPLAY_TYPE_ORIGINAL = 3;
public static Jzvd CURRENT_JZVD;
public static LinkedList<ViewGroup> CONTAINER_LIST = new LinkedList<>();
public static ViewGroup DECOR_VIEW;//模拟Activity的DecorView
public static boolean TOOL_BAR_EXIST = true;
public static boolean SAVE_PROGRESS = true;
public static int VIDEO_IMAGE_DISPLAY_TYPE = 0;
public static int ON_PLAY_PAUSE_TMP_STATE = 0;//这个考虑不放到库里,去自定义
public static int backUpBufferState = -1;
public static float PROGRESS_DRAG_RATE = 1f;//进度条滑动阻尼系数 越大播放进度条滑动越慢
public int state = -1;
public int screen = -1;
public JZDataSource jzDataSource;
public int widthRatio = 0;
public int heightRatio = 0;
public Class mediaInterfaceClass;
public JZMediaInterface mediaInterface;
public int videoRotation = 0;
public int seekToManulPosition = -1;
public long seekToInAdvance = 0;
public ImageView startButton;
public SeekBar progressBar;
public ImageView fullscreenButton;
public TextView currentTimeTextView, totalTimeTextView;
public ViewGroup textureViewContainer;
public ViewGroup topContainer, bottomContainer;
public JZTextureView textureView;
public boolean preloading = false;
protected long gobakFullscreenTime = 0;//这个应该重写一下,刷新列表,新增列表的刷新,不打断播放,应该是个flag
protected long gotoFullscreenTime = 0;
protected Timer UPDATE_PROGRESS_TIMER;
protected int mScreenWidth;
protected int mScreenHeight;
protected ProgressTimerTask mProgressTimerTask;
protected boolean mTouchingProgressBar;
protected float mDownX;
protected float mDownY;
protected boolean mChangeVolume;
protected boolean mChangePosition;
protected boolean mChangeBrightness;
protected long mGestureDownPosition;
protected long mSeekTimePosition;
protected Context jzvdContext;
protected long mCurrentPosition;
/**
* 如果不在列表中可以不加block
*/
protected ViewGroup.LayoutParams blockLayoutParams;
//原來(非全屏)视频的信息
protected int blockIndex;
protected int blockWidth;
protected int blockHeight;
//获取音频焦点参数 待商泰展开
private final static int USAGE = 16;
private final static int CONTENT_TYPE = 61;
private AudioFocusManager mAudioFocusManager;
WindowManager mWindowManager;
public Jzvd(Context context) {
super(context);
init(context);
}
public Jzvd(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 增加准备状态逻辑
*/
public static void goOnPlayOnResume() {
if (CURRENT_JZVD != null) {
LogUtil.e(TAG, "goOnPlayOnResume CURRENT_JZVD.state " + CURRENT_JZVD.state );
if (CURRENT_JZVD.state == Jzvd.STATE_PAUSE) {
if (ON_PLAY_PAUSE_TMP_STATE == STATE_PAUSE) {
CURRENT_JZVD.onStatePause();
CURRENT_JZVD.mediaInterface.pause();
} else {
CURRENT_JZVD.onStatePlaying();
CURRENT_JZVD.mediaInterface.start();
}
ON_PLAY_PAUSE_TMP_STATE = 0;
} else if (CURRENT_JZVD.state == Jzvd.STATE_PREPARING) {
//准备状态暂停后的
CURRENT_JZVD.startVideo();
}
if (CURRENT_JZVD.screen == Jzvd.SCREEN_FULLSCREEN) {
//JZUtils.hideStatusBar(CURRENT_JZVD.jzvdContext);
//JZUtils.hideSystemUI(CURRENT_JZVD.jzvdContext);
}
}
}
/**
* 增加准备状态逻辑
*/
public static void goOnPlayOnPause() {
if (CURRENT_JZVD != null) {
if (CURRENT_JZVD.state == Jzvd.STATE_AUTO_COMPLETE ||
CURRENT_JZVD.state == Jzvd.STATE_NORMAL ||
CURRENT_JZVD.state == Jzvd.STATE_ERROR) {
Jzvd.releaseAllVideos();
} else if (CURRENT_JZVD.state == Jzvd.STATE_PREPARING) {
//准备状态暂停的逻辑
Jzvd.setCurrentJzvd(CURRENT_JZVD);
CURRENT_JZVD.state = STATE_PREPARING;
} else {
ON_PLAY_PAUSE_TMP_STATE = CURRENT_JZVD.state;
CURRENT_JZVD.onStatePause();
CURRENT_JZVD.mediaInterface.pause();
}
}
}
public static void releaseAllVideos() {
Log.d(TAG, "releaseAllVideos");
if (CURRENT_JZVD != null) {
CURRENT_JZVD.reset();
CURRENT_JZVD = null;
}
CONTAINER_LIST.clear();
}
public static boolean backPress() {
Log.d(TAG, "backPress CONTAINER_LIST.size() = " + CONTAINER_LIST.size());
if (!CONTAINER_LIST.isEmpty() && CURRENT_JZVD != null) {//判断条件,因为当前所有goBack都是回到普通窗口
CURRENT_JZVD.gotoNormalScreen();
return true;
}
return false;
}
public static void setCurrentJzvd(Jzvd jzvd) {
if (CURRENT_JZVD != null) CURRENT_JZVD.reset();
CURRENT_JZVD = jzvd;
}
public static void setTextureViewRotation(int rotation) {
if (CURRENT_JZVD != null && CURRENT_JZVD.textureView != null) {
CURRENT_JZVD.textureView.setRotation(rotation);
}
}
public static void setVideoImageDisplayType(int type) {
Jzvd.VIDEO_IMAGE_DISPLAY_TYPE = type;
if (CURRENT_JZVD != null && CURRENT_JZVD.textureView != null) {
CURRENT_JZVD.textureView.requestLayout();
}
}
public abstract int getLayoutId();
public void init(Context context) {
View.inflate(context, getLayoutId(), this);
jzvdContext = context;
startButton = findViewById(R.id.start);
fullscreenButton = findViewById(R.id.fullscreen);
progressBar = findViewById(R.id.bottom_seek_progress);
currentTimeTextView = findViewById(R.id.current);
totalTimeTextView = findViewById(R.id.total);
bottomContainer = findViewById(R.id.layout_bottom);
textureViewContainer = findViewById(R.id.surface_container);
topContainer = findViewById(R.id.layout_top);
if (startButton == null) {
startButton = new ImageView(context);
}
if (fullscreenButton == null) {
fullscreenButton = new ImageView(context);
}
if (progressBar == null) {
progressBar = new SeekBar(context);
}
if (currentTimeTextView == null) {
currentTimeTextView = new TextView(context);
}
if (totalTimeTextView == null) {
totalTimeTextView = new TextView(context);
}
if (bottomContainer == null) {
bottomContainer = new LinearLayout(context);
}
if (textureViewContainer == null) {
textureViewContainer = new FrameLayout(context);
}
if (topContainer == null) {
topContainer = new RelativeLayout(context);
}
startButton.setOnClickListener(this);
fullscreenButton.setOnClickListener(this);
progressBar.setOnSeekBarChangeListener(this);
bottomContainer.setOnClickListener(this);
textureViewContainer.setOnClickListener(this);
textureViewContainer.setOnTouchListener(this);
mScreenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
mScreenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
state = STATE_IDLE;
//音频焦点初始化
mAudioFocusManager = AudioFocusManager.getInstance();
mAudioFocusManager.init(context);
mAudioFocusManager.setAudioFocusListener(audioFocusListener);
mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
}
public void setUp(String url, String title) {
setUp(new JZDataSource(url, title), SCREEN_NORMAL);
}
public void setUp(String url, String title, int screen) {
setUp(new JZDataSource(url, title), screen);
}
public void setUp(JZDataSource jzDataSource, int screen) {
setUp(jzDataSource, screen, JZMediaSystem.class);
}
public void setUp(String url, String title, int screen, Class mediaInterfaceClass) {
setUp(new JZDataSource(url, title), screen, mediaInterfaceClass);
}
public void setUp(JZDataSource jzDataSource, int screen, Class mediaInterfaceClass) {
this.jzDataSource = jzDataSource;
this.screen = screen;
onStateNormal();
this.mediaInterfaceClass = mediaInterfaceClass;
}
public void setMediaInterface(Class mediaInterfaceClass) {
reset();
this.mediaInterfaceClass = mediaInterfaceClass;
}
@Override
public void onClick(View v) {
Log.e(TAG, "onClick v.getId " + v.getId() + " hashCode " +hashCode());
int i = v.getId();
if (i == R.id.start) {
clickStart();
} else if (i == R.id.fullscreen) {
clickFullscreen();
}
}
protected void clickFullscreen() {
Log.i(TAG, "onClick fullscreen [" + this.hashCode() + "] ");
if (state == STATE_AUTO_COMPLETE) return;
if (screen == SCREEN_FULLSCREEN) {
Log.d(TAG, "backPress [" + this.hashCode() + "] ");
//quit fullscreen
backPress();
} else {
Log.d(TAG, "toFullscreenActivity [" + this.hashCode() + "] ");
gotoFullscreen();
}
}
protected void clickStart() {
Log.i(TAG, "onClick start [" + this.hashCode() + "] state " + state);
if (jzDataSource == null || jzDataSource.urlsMap.isEmpty() || jzDataSource.getCurrentUrl() == null) {
Log.d(TAG, "onClick start, play video need url");
return;
}
if (state == STATE_NORMAL) {
startVideo();
} else if (state == STATE_PLAYING) {
Log.d(TAG, "pauseVideo [" + this.hashCode() + "] ");
mediaInterface.pause();
onStatePause();
} else if (state == STATE_PAUSE) {
mediaInterface.start();
onStatePlaying();
} else if (state == STATE_AUTO_COMPLETE) {
startVideo();
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
int id = v.getId();
if (id == R.id.surface_container) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchActionDown(x, y);
break;
case MotionEvent.ACTION_MOVE:
touchActionMove(x, y);
break;
case MotionEvent.ACTION_UP:
touchActionUp();
break;
}
}
return false;
}
protected void touchActionUp() {
Log.i(TAG, "onTouch surfaceContainer actionUp [" + this.hashCode() + "] ");
mTouchingProgressBar = false;
if (mChangePosition) {
mediaInterface.seekTo(mSeekTimePosition);
long duration = getDuration();
int progress = (int) (mSeekTimePosition * 100 / (duration == 0 ? 1 : duration));
progressBar.setProgress(progress);
}
if (mChangeVolume) {
//change volume event
}
startProgressTimer();
}
protected void touchActionMove(float x, float y) {
Log.i(TAG, "onTouch surfaceContainer actionMove [" + this.hashCode() + "] ");
float deltaX = x - mDownX;
float deltaY = y - mDownY;
if (screen == SCREEN_FULLSCREEN) {
//拖动的是NavigationBar和状态栏
if (mDownX > JZUtils.getScreenWidth(getContext()) || mDownY < JZUtils.getStatusBarHeight(getContext())) {
return;
}
}
if (mChangePosition) {
long totalTimeDuration = getDuration();
if (PROGRESS_DRAG_RATE <= 0) {
Log.d(TAG, "error PROGRESS_DRAG_RATE value");
PROGRESS_DRAG_RATE = 1f;
}
mSeekTimePosition = (int) (mGestureDownPosition + deltaX * totalTimeDuration / (mScreenWidth * PROGRESS_DRAG_RATE));
if (mSeekTimePosition > totalTimeDuration)
mSeekTimePosition = totalTimeDuration;
String seekTime = JZUtils.stringForTime(mSeekTimePosition);
String totalTime = JZUtils.stringForTime(totalTimeDuration);
//showProgressDialog(deltaX, seekTime, mSeekTimePosition, totalTime, totalTimeDuration);
}
if (mChangeVolume) {
//调节音量,暂不支持
}
if (mChangeBrightness) {
//调节亮度,暂不支持
}
}
protected void touchActionDown(float x, float y) {
Log.i(TAG, "onTouch surfaceContainer actionDown [" + this.hashCode() + "] ");
mTouchingProgressBar = true;
mDownX = x;
mDownY = y;
mChangeVolume = false;
mChangePosition = false;
mChangeBrightness = false;
}
public void onStateNormal() {
Log.i(TAG, "onStateNormal " + " [" + this.hashCode() + "] ");
state = STATE_NORMAL;
cancelProgressTimer();
if (mediaInterface != null) mediaInterface.release();
}
public void onStatePreparing() {
Log.i(TAG, "onStatePreparing " + " [" + this.hashCode() + "] ");
state = STATE_PREPARING;
resetProgressAndTime();
}
public void onStatePreparingPlaying() {
Log.i(TAG, "onStatePreparingPlaying " + " [" + this.hashCode() + "] ");
state = STATE_PREPARING_PLAYING;
}
public void onStatePreparingChangeUrl() {
Log.i(TAG, "onStatePreparingChangeUrl " + " [" + this.hashCode() + "] ");
state = STATE_PREPARING_CHANGE_URL;
releaseAllVideos();
startVideo();
// mediaInterface.prepare();
}
public void changeUrl(JZDataSource jzDataSource, long seekToInAdvance) {
this.jzDataSource = jzDataSource;
this.seekToInAdvance = seekToInAdvance;
onStatePreparingChangeUrl();
}
public void onPrepared() {
Log.i(TAG, "onPrepared " + " [" + this.hashCode() + "] ");
state = STATE_PREPARED;
if (!preloading) {
mediaInterface.start();//这里原来是非县城
preloading = false;
}
if (jzDataSource.getCurrentUrl().toString().toLowerCase().contains("mp3") ||
jzDataSource.getCurrentUrl().toString().toLowerCase().contains("wma") ||
jzDataSource.getCurrentUrl().toString().toLowerCase().contains("aac") ||
jzDataSource.getCurrentUrl().toString().toLowerCase().contains("m4a") ||
jzDataSource.getCurrentUrl().toString().toLowerCase().contains("wav")) {
onStatePlaying();
}
}
public void startPreloading() {
preloading = true;
startVideo();
}
/**
* 如果STATE_PREPARED就播放,如果没准备完成就走正常的播放函数startVideo();
*/
public void startVideoAfterPreloading() {
if (state == STATE_PREPARED) {
mediaInterface.start();
} else {
preloading = false;
startVideo();
}
}
public void onStatePlaying() {
Log.i(TAG, "onStatePlaying " + " [" + this.hashCode() + "] ");
if (state == STATE_PREPARED) {//如果是准备完成视频后第一次播放,先判断是否需要跳转进度。
Log.d(TAG, "onStatePlaying:STATE_PREPARED ");
boolean requestAudioFocus = mAudioFocusManager.getAudioFocus(USAGE, CONTENT_TYPE);
Log.d(TAG, "onStatePlaying:requestAudioFocus " + requestAudioFocus);
if (seekToInAdvance != 0) {
mediaInterface.seekTo(seekToInAdvance);
seekToInAdvance = 0;
} else {
long position = JZUtils.getSavedProgress(getContext(), jzDataSource.getCurrentUrl());
if (position != 0) {
mediaInterface.seekTo(position);//这里为什么区分开呢,第一次的播放和resume播放是不一样的。 这里怎么区分是一个问题。然后
}
}
}
state = STATE_PLAYING;
startProgressTimer();
}
public void onStatePause() {
Log.i(TAG, "onStatePause " + " [" + this.hashCode() + "] ");
state = STATE_PAUSE;
startProgressTimer();
mAudioFocusManager.releaseAudioFocus(USAGE, CONTENT_TYPE);
}
public void onStateError() {
Log.i(TAG, "onStateError " + " [" + this.hashCode() + "] ");
state = STATE_ERROR;
cancelProgressTimer();
mAudioFocusManager.releaseAudioFocus(USAGE, CONTENT_TYPE);
}
public void onStateAutoComplete() {
Log.i(TAG, "onStateAutoComplete " + " [" + this.hashCode() + "] ");
state = STATE_AUTO_COMPLETE;
cancelProgressTimer();
progressBar.setProgress(100);
currentTimeTextView.setText(totalTimeTextView.getText());
//播放完成释放音频焦点
mAudioFocusManager.releaseAudioFocus(USAGE, CONTENT_TYPE);
}
public void onInfo(int what, int extra) {
Log.e(TAG, "onInfo what - " + what + " extra - " + extra);
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START");
if (state == Jzvd.STATE_PREPARED
|| state == Jzvd.STATE_PREPARING_CHANGE_URL
|| state == Jzvd.STATE_PREPARING_PLAYING) {
onStatePlaying();//开始渲染图像,真正进入playing状态
}
} else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
Log.d(TAG, "MEDIA_INFO_BUFFERING_START");
backUpBufferState = state;
setState(STATE_PREPARING_PLAYING);
} else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
Log.d(TAG, "MEDIA_INFO_BUFFERING_END");
if (backUpBufferState != -1) {
setState(backUpBufferState);
backUpBufferState = -1;
}
}
}
public void onError(int what, int extra) {
Log.e(TAG, "onError " + what + " - " + extra + " [" + this.hashCode() + "] ");
if (what != 38 && extra != -38 && what != -38 && extra != 38 && extra != -19) {
onStateError();
mediaInterface.release();
}
}
public void onCompletion() {
Runtime.getRuntime().gc();
Log.i(TAG, "onAutoCompletion " + " [" + this.hashCode() + "] ");
cancelProgressTimer();
onStateAutoComplete();
mediaInterface.release();
JZUtils.saveProgress(getContext(), jzDataSource.getCurrentUrl(), 0);
if (screen == SCREEN_FULLSCREEN) {
gotoNormalCompletion();
}
}
public void gotoNormalCompletion() {
Log.i(TAG, "gotoNormalCompletion " + " [" + this.hashCode() + "] ");
gobakFullscreenTime = System.currentTimeMillis();//退出全屏
if (DECOR_VIEW != null) {
mWindowManager.removeView(DECOR_VIEW);
DECOR_VIEW.removeAllViews();
DECOR_VIEW = null;
}
textureViewContainer.removeView(textureView);
CONTAINER_LIST.getLast().removeViewAt(blockIndex);//remove block
CONTAINER_LIST.getLast().addView(this, blockIndex, blockLayoutParams);
CONTAINER_LIST.pop();
setScreenNormal();
}
/**
* 多数表现为中断当前播放
*/
public void reset() {
Log.i(TAG, "reset " + " [" + this.hashCode() + "] ");
if (state == STATE_PLAYING || state == STATE_PAUSE) {
long position = getCurrentPositionWhenPlaying();
JZUtils.saveProgress(getContext(), jzDataSource.getCurrentUrl(), position);
}
cancelProgressTimer();
onStateNormal();
textureViewContainer.removeAllViews();
mAudioFocusManager.releaseAudioFocus(USAGE, CONTENT_TYPE);
if (mediaInterface != null) mediaInterface.release();
}
/**
* 里面的的onState...()其实就是setState...(),因为要可以被复写,所以参考Activity的onCreate(),onState..()的方式看着舒服一些,老铁们有何高见。
*
* @param state stateId
*/
public void setState(int state) {
Log.e(TAG, "setState state " + state);
switch (state) {
case STATE_NORMAL:
onStateNormal();
break;
case STATE_PREPARING:
onStatePreparing();
break;
case STATE_PREPARING_PLAYING:
onStatePreparingPlaying();
break;
case STATE_PREPARING_CHANGE_URL:
onStatePreparingChangeUrl();
break;
case STATE_PLAYING:
onStatePlaying();
break;
case STATE_PAUSE:
onStatePause();
break;
case STATE_ERROR:
onStateError();
break;
case STATE_AUTO_COMPLETE:
onStateAutoComplete();
break;
}
}
public void setScreen(int screen) {//特殊的个别的进入全屏的按钮在这里设置 只有setup的时候能用上
switch (screen) {
case SCREEN_NORMAL:
setScreenNormal();
break;
case SCREEN_FULLSCREEN:
setScreenFullscreen();
break;
}
}
public void startVideo() {
Log.d(TAG, "startVideo [" + this.hashCode() + "] ");
setCurrentJzvd(this);
try {
Constructor<JZMediaInterface> constructor = mediaInterfaceClass.getConstructor(Jzvd.class);
this.mediaInterface = constructor.newInstance(this);
} catch (Exception e) {
Log.d(TAG, "startVideo error : " + e.toString());
}
addTextureView();
onStatePreparing();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (screen == SCREEN_FULLSCREEN) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
if (widthRatio != 0 && heightRatio != 0) {
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specHeight = (int) ((specWidth * (float) heightRatio) / widthRatio);
setMeasuredDimension(specWidth, specHeight);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.EXACTLY);
getChildAt(0).measure(childWidthMeasureSpec, childHeightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void addTextureView() {
Log.d(TAG, "addTextureView [" + this.hashCode() + "] ");
if (textureView != null) textureViewContainer.removeView(textureView);
textureView = new JZTextureView(getContext().getApplicationContext());
textureView.setSurfaceTextureListener(mediaInterface);
LayoutParams layoutParams =
new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.CENTER);
textureViewContainer.addView(textureView, layoutParams);
}
public void onVideoSizeChanged(int width, int height) {
Log.i(TAG, "onVideoSizeChanged " + " [" + this.hashCode() + "] width " +width + " height " + height);
if (textureView != null) {
if (videoRotation != 0) {
textureView.setRotation(videoRotation);
}
textureView.setVideoSize(width, height);
} else {
Log.i(TAG, "onVideoSizeChanged textureView is null");
}
}
public void startProgressTimer() {
Log.i(TAG, "startProgressTimer: " + " [" + this.hashCode() + "] ");
cancelProgressTimer();
UPDATE_PROGRESS_TIMER = new Timer();
mProgressTimerTask = new ProgressTimerTask();
UPDATE_PROGRESS_TIMER.schedule(mProgressTimerTask, 0, 300);
}
public void cancelProgressTimer() {
if (UPDATE_PROGRESS_TIMER != null) {
UPDATE_PROGRESS_TIMER.cancel();
}
if (mProgressTimerTask != null) {
mProgressTimerTask.cancel();
}
}
public void onProgress(int progress, long position, long duration) {
Log.d(TAG, "onProgress: progress=" + progress + " position=" + position + " duration=" + duration);
mCurrentPosition = position;
if (!mTouchingProgressBar) {
if (seekToManulPosition != -1) {
if (seekToManulPosition > progress) {
return;
} else {
seekToManulPosition = -1;//这个关键帧有没有必要做
}
} else {
progressBar.setProgress(progress);
}
}
if (position != 0) currentTimeTextView.setText(JZUtils.stringForTime(position));
totalTimeTextView.setText(JZUtils.stringForTime(duration));
}
public void setBufferProgress(int bufferProgress) {
progressBar.setSecondaryProgress(bufferProgress);
}
public void resetProgressAndTime() {
mCurrentPosition = 0;
progressBar.setProgress(0);
progressBar.setSecondaryProgress(0);
currentTimeTextView.setText(JZUtils.stringForTime(0));
totalTimeTextView.setText(JZUtils.stringForTime(0));
}
public long getCurrentPositionWhenPlaying() {
long position = 0;
if (state == STATE_PLAYING || state == STATE_PAUSE || state == STATE_PREPARING_PLAYING) {
try {
position = mediaInterface.getCurrentPosition();
} catch (IllegalStateException e) {
e.printStackTrace();
return position;
}
}
return position;
}
public long getDuration() {
long duration = 0;
try {
duration = mediaInterface.getDuration();
} catch (Exception e) {
e.printStackTrace();
return duration;
}
return duration;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
Log.i(TAG, "bottomProgress onStartTrackingTouch [" + this.hashCode() + "] ");
cancelProgressTimer();
ViewParent vpdown = getParent();
while (vpdown != null) {
vpdown.requestDisallowInterceptTouchEvent(true);
vpdown = vpdown.getParent();
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.i(TAG, "bottomProgress onStopTrackingTouch [" + this.hashCode() + "] ");
startProgressTimer();
ViewParent vpup = getParent();
while (vpup != null) {
vpup.requestDisallowInterceptTouchEvent(false);
vpup = vpup.getParent();
}
if (state != STATE_PLAYING &&
state != STATE_PAUSE) return;
long time = seekBar.getProgress() * getDuration() / 100;
seekToManulPosition = seekBar.getProgress();
mediaInterface.seekTo(time);
Log.i(TAG, "seekTo " + time + " [" + this.hashCode() + "] ");
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
//设置这个progres对应的时间,给textview
long duration = getDuration();
currentTimeTextView.setText(JZUtils.stringForTime(progress * duration / 100));
}
}
/**
* 全屏后重新添加一个jzvd到原来的view上面
* @param vg
*/
public void cloneAJzvd(ViewGroup vg) {
try {
Constructor<Jzvd> constructor = (Constructor<Jzvd>) Jzvd.this.getClass().getConstructor(Context.class);
Jzvd jzvd = constructor.newInstance(getContext());
jzvd.setId(getId());
jzvd.setMinimumWidth(blockWidth);
jzvd.setMinimumHeight(blockHeight);
jzvd.bringToFront();
vg.addView(jzvd, blockIndex, blockLayoutParams);
jzvd.setUp(jzDataSource.cloneMe(), SCREEN_NORMAL, mediaInterfaceClass);
} catch (Exception e) {
LogUtil.e(TAG,"cloneAJzvd error ", e);
}
}
//注意方法命名,这里不能命名为getLayoutParams,因为View中也有个getLayoutParams方法
public WindowManager.LayoutParams getWMLayoutParams() {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
WindowInfoUtil.setWindowType(params);
params.format = PixelFormat.TRANSPARENT;
params.gravity = Gravity.TOP | Gravity.START;
params.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
params.x = 460;
params.y = 0;
params.width = DisplayUtil.getScreenWidthByPix(getContext()) - 460;
params.height = DisplayUtil.getScreenHeightByPix(getContext()) - 100;
return params;
}
/**
* 如果全屏或者返回全屏的视图有问题,复写这两个函数gotoScreenNormal(),根据自己布局的情况重新布局。
*/
public void gotoFullscreen() {
gotoFullscreenTime = System.currentTimeMillis();
ViewGroup vg = (ViewGroup) getParent();
jzvdContext = vg.getContext();
blockLayoutParams = getLayoutParams();
blockIndex = vg.indexOfChild(this);
blockWidth = getWidth();
blockHeight = getHeight();
//fix: JzvdStd being added, but it already has a parent
vg.removeView(this);//不能调用removeAllviews方法,会把和jzvd同级的其他控件也都删除
cloneAJzvd(vg);
//这里保存的是normal屏jzvd的Parent
CONTAINER_LIST.add(vg);
//在动态添加jzvd的时候,jzvd的控件要包在一个ViewGroup中,否则容易出现UI方面的问题
DECOR_VIEW = new RelativeLayout(jzvdContext);
ViewGroup.LayoutParams fullLayout = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
DECOR_VIEW.addView(this, fullLayout);
//这个wm添加的是ViewGroup,不能直接添加jzvd
mWindowManager.addView(DECOR_VIEW, getWMLayoutParams());
setScreenFullscreen();
}
public void gotoNormalScreen() {//goback本质上是goto
gobakFullscreenTime = System.currentTimeMillis();//退出全屏
Log.e(TAG, "gotoNormalScreen blockIndex " + blockIndex + " blockWidth " + blockWidth + " blockHeight " + blockHeight);
if (DECOR_VIEW != null) {
mWindowManager.removeView(DECOR_VIEW);
DECOR_VIEW.removeAllViews();
DECOR_VIEW = null;
}
CONTAINER_LIST.getLast().removeViewAt(blockIndex);//remove block
//这里会把当前的jzvd添加到正常视图上,CURRENT_JZVD会在前台,否则cloneJzvd会显示在view上
CONTAINER_LIST.getLast().addView(this, blockIndex, blockLayoutParams);
CONTAINER_LIST.removeLast();
setScreenNormal();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
LogUtil.d(TAG, "onDetachedFromWindow screen " + screen);
//在正常播放时如果销毁就重置进度
if (screen == SCREEN_NORMAL) {
//resetProgressAndTime();
}
}
public void setScreenNormal() {//TODO 这块不对呀,还需要改进,设置flag之后要设置ui,不设置ui这么写没意义呀
screen = SCREEN_NORMAL;
}
public void setScreenFullscreen() {
screen = SCREEN_FULLSCREEN;
}
public void onSeekComplete() {
}
public class ProgressTimerTask extends TimerTask {
@Override
public void run() {
if (state == STATE_PLAYING || state == STATE_PAUSE || state == STATE_PREPARING_PLAYING) {
//Log.d(TAG, "onProgressUpdate " + "[" + this.hashCode() + "] ");
post(() -> {
long position = getCurrentPositionWhenPlaying();
long duration = getDuration();
int progress = (int) (position * 100 / (duration == 0 ? 1 : duration));
onProgress(progress, position, duration);
});
}
}
}
public static AudioFocusManager.AudioFocusListener audioFocusListener = new AudioFocusManager.AudioFocusListener() {
@Override
public void onAudioFocusRequestSuccess() {
Log.d(TAG, "audioFocusListener:onAudioFocusRequestSuccess");
}
@Override
public void onAudioFocusRequestFail() {
Log.d(TAG, "audioFocusListener:onAudioFocusRequestFail");
}
@Override
public void onAudioFocusRelease() {
Log.d(TAG, "audioFocusListener:onAudioFocusRelease");
}
@Override
public void onAudioFocusChanged(int change) {
Log.d(TAG, "audioFocusListener:onAudioFocusChanged");
}
};
}
使用:
windowManager.addView(view, layoutParams);
其中,view可以使用布局inflate,看也可以动态生成view,里面要包含Jztd视频控件。
windowManager 网上封装的工具类有很多,这里就不提供了。
学习的第一步就是先找到函数的入口:根据R.id.fullscreen这个全屏按钮的点击事件,我们很快就能定位到名叫gotoScreenFullscreen的函数。
code line 737
public void gotoScreenFullscreen() {
ViewGroup vg = (ViewGroup) getParent();
vg.removeView(this);
cloneAJzvd(vg);
CONTAINER_LIST.add(vg);//作者在这里保存了原视图的父布局
vg = (ViewGroup) (JZUtils.scanForActivity(getContext())).getWindow().getDecorView();//和他也没有关系
vg.addView(this, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
setScreenFullscreen();
JZUtils.hideStatusBar(getContext());
JZUtils.setRequestedOrientation(getContext(), FULLSCREEN_ORIENTATION);
JZUtils.hideSystemUI(getContext());//华为手机和有虚拟键的手机全屏时可隐藏虚拟键 issue:1326
}
从上方代码第三行跳转到下面这个方法,可以看出作者用构造器构造了一个新的对象替代了原来位置的对象。
public void cloneAJzvd(ViewGroup vg) {
try {
Constructor<Jzvd> constructor = (Constructor<Jzvd>) Jzvd.this.getClass().getConstructor(Context.class);
Jzvd jzvd = constructor.newInstance(getContext());
jzvd.setId(getId());
vg.addView(jzvd);
jzvd.setUp(jzDataSource.cloneMe(), SCREEN_NORMAL, mediaInterfaceClass);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
继续看第一个方法第四行开始,毫无疑问,作者获取getDecorView()根视图,然后将原来的对象放入了跟视图,同时隐藏了状态栏,也即是开启了全屏模式,汗,简单粗暴。
还不够,那如何从全屏模式回来呢,作者在打开全屏的时候,就将原视图的父容器存放到了一个链表中。
public static boolean backPress() {
Log.i(TAG, "backPress");
if (CONTAINER_LIST.size() != 0 && CURRENT_JZVD != null) {//判断条件,因为当前所有goBack都是回到普通窗口
CURRENT_JZVD.gotoScreenNormal();
return true;
} else if (CONTAINER_LIST.size() == 0 && CURRENT_JZVD != null && CURRENT_JZVD.screen != SCREEN_NORMAL) {//退出直接进入的全屏
CURRENT_JZVD.clearFloatScreen();
return true;
}
return false;
}
进入正题
public void gotoScreenNormal() {//goback本质上是goto
gobakFullscreenTime = System.currentTimeMillis();//退出全屏
ViewGroup vg = (ViewGroup) (JZUtils.scanForActivity(getContext())).getWindow().getDecorView();
vg.removeView(this);
CONTAINER_LIST.getLast().removeAllViews();
CONTAINER_LIST.getLast().addView(this, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
CONTAINER_LIST.pop();
setScreenNormal();//这块可以放到jzvd中
JZUtils.showStatusBar(getContext());
JZUtils.setRequestedOrientation(getContext(), NORMAL_ORIENTATION);
JZUtils.showSystemUI(getContext());
}
看完上面的代码一开始有点蒙蔽,为啥要把父容器的中视图全清理掉。回忆之前的接入过程,是因为
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cn.jzvd.JzvdStd
android:id="@+id/jz_video"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
如上所示,作者一开始就要求为播放器增加一层嵌套,可能是为了图方便,毕竟这比从复杂布局中恢复容易的多。
至此了解饺子播放器全屏播放的原理了。
相关api介绍(有些可能失效)
自动播放
自动播放有两种 这里随便选择添加一个,
1. myJzvdStd.startButton.performClick();
2. myJzvdStd.startVideo();
跳转制定位置播放
//这里只有开始播放时才生效
mJzvdStd.seekToInAdvance = 20000;
//跳转制定位置播放
JZMediaManager.seekTo(30000);
2.播放sd卡下视频
public void cpAssertVideoToLocalPath() {
try {
InputStream myInput;
OutputStream myOutput = new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/local_video.mp4");
myInput = this.getAssets().open("local_video.mp4");
byte[] buffer = new byte[1024];
int length = myInput.read(buffer);
while (length > 0) {
myOutput.write(buffer, 0, length);
length = myInput.read(buffer);
}
myOutput.flush();
myInput.close();
myOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
myJzvdStd.setUp(Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/local_video.mp4", "视频标题",Jzvd.SCREEN_WINDOW_NORMAL, );
播放assets目录下的视频
复制Demo中CustomMediaPlayerAssertFolder类到你的项目下
----------------------------------------------------------------------------
JZDataSource jzDataSource = null;
try {
jzDataSource = new JZDataSource(getAssets().openFd("local_video.mp4"));
jzDataSource.title = "饺子快长大";
} catch (IOException e) {
e.printStackTrace();
}
jzvdStd.setUp(jzDataSource, JzvdStd.SCREEN_WINDOW_NORMAL);
Glide.with(this)
.load("http://jzvd-pic.nathen.cn/jzvd-pic/1bb2ebbe-140d-4e2e-abd2-9e7e564f71ac.png")
.into(jzvdStd.thumbImageView);
Jzvd.setMediaInterface(new CustomMediaPlayerAssertFolder());//进入此页面修改MediaInterface,让此页面的jzvd正常工作
直接全屏播放
JzvdStd.startFullscreen(this, JzvdStd.class, VideoConstant.videoUrlList[6], "视频标题");
开启小窗播放
mJzvdStd.startWindowTiny();
列表Item划出开启小窗播放
1.Listview
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Jzvd.onScrollAutoTiny(view, firstVisibleItem, visibleItemCount, totalItemCount);
// Jzvd.onScrollReleaseAllVideos(view, firstVisibleItem, visibleItemCount, totalItemCount); 这是不开启列表划出小窗 同时也是画出屏幕释放JZ 划出暂停
}
});
2. RecyclerView 划出列表开启小窗
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
Jzvd.onChildViewAttachedToWindow(view, R.id.videoplayer);
}
@Override
public void onChildViewDetachedFromWindow(View view) {
Jzvd.onChildViewDetachedFromWindow(view);
}
});
2.1 RecyclerView划出屏幕释放JZ,同时也是不开启列表划出显示小窗
recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
}
@Override
public void onChildViewDetachedFromWindow(View view) {
Jzvd jzvd = view.findViewById(R.id.videoplayer);
if (jzvd != null && jzvd.jzDataSource.containsTheUrl(JZMediaManager.getCurrentUrl())) {
Jzvd currentJzvd = JzvdMgr.getCurrentJzvd();
if (currentJzvd != null && currentJzvd.currentScreen != Jzvd.SCREEN_WINDOW_FULLSCREEN) {
Jzvd.releaseAllVideos();
}
}
}
});
小屏播放无声音,全屏有声音
创建一个类继承JzvdStd并在XML设置
public class JzvdStdVolumeAfterFullscreen extends JzvdStd {
public JzvdStdVolumeAfterFullscreen(Context context) {
super(context);
}
public JzvdStdVolumeAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onPrepared() {
super.onPrepared();
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
JZMediaManager.instance().jzMediaInterface.setVolume(1f, 1f);
} else {
JZMediaManager.instance().jzMediaInterface.setVolume(0f, 0f);
}
}
/**
* 进入全屏模式的时候关闭静音模式
*/
@Override
public void startWindowFullscreen() {
super.startWindowFullscreen();
JZMediaManager.instance().jzMediaInterface.setVolume(1f, 1f);
}
/**
* 退出全屏模式的时候开启静音模式
*/
@Override
public void playOnThisJzvd() {
super.playOnThisJzvd();
JZMediaManager.instance().jzMediaInterface.setVolume(0f, 0f);
}
}
全屏状态播放完成,不退出全屏
创建一个类继承JzvdStd并在XML设置
public class JzvdStdAutoCompleteAfterFullscreen extends JzvdStd {
public JzvdStdAutoCompleteAfterFullscreen(Context context) {
super(context);
}
public JzvdStdAutoCompleteAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void startVideo() {
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
Log.d(TAG, "startVideo [" + this.hashCode() + "] ");
initTextureView();
addTextureView();
AudioManager mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
JZUtils.scanForActivity(getContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
JZMediaManager.setDataSource(jzDataSource);
JZMediaManager.instance().positionInList = positionInList;
onStatePreparing();
} else {
super.startVideo();
}
}
@Override
public void onAutoCompletion() {
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
onStateAutoComplete();
} else {
super.onAutoCompletion();
}
}
}
全屏模式下显示分享按钮
复制DEMO下的layout文件在 layout_top 布局下 添加你的分享按钮
public class JzvdStdShowShareButtonAfterFullscreen extends JzvdStd {
public ImageView shareButton;
public JzvdStdShowShareButtonAfterFullscreen(Context context) {
super(context);
}
public JzvdStdShowShareButtonAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void init(Context context) {
super.init(context);
shareButton = findViewById(R.id.share);
shareButton.setOnClickListener(this);
}
@Override
public int getLayoutId() {
return R.layout.layout_standard_with_share_button;
}
@Override
public void onClick(View v) {
super.onClick(v);
if (v.getId() == R.id.share) {
Toast.makeText(getContext(), "Whatever the icon means", Toast.LENGTH_SHORT).show();
}
}
@Override
public void setUp(JZDataSource jzDataSource, int screen) {
super.setUp(jzDataSource, screen);
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
shareButton.setVisibility(View.VISIBLE);
} else {
shareButton.setVisibility(View.INVISIBLE);
}
}
}
小屏状态下不显示标题,全屏模式下显示标题
public class JzvdStdShowTitleAfterFullscreen extends JzvdStd {
public JzvdStdShowTitleAfterFullscreen(Context context) {
super(context);
}
public JzvdStdShowTitleAfterFullscreen(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setUp(JZDataSource jzDataSource, int screen) {
super.setUp(jzDataSource, screen);
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
titleTextView.setVisibility(View.VISIBLE);
} else {
titleTextView.setVisibility(View.INVISIBLE);
}
}
}
播放MP3
public class JzvdStdMp3 extends JzvdStd {
public JzvdStdMp3(Context context) {
super(context);
}
public JzvdStdMp3(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public int getLayoutId() {
return R.layout.jz_layout_standard_mp3;
}
@Override
public void onClick(View v) {
if (v.getId() == cn.jzvd.R.id.thumb &&
(currentState == CURRENT_STATE_PLAYING ||
currentState == CURRENT_STATE_PAUSE)) {
onClickUiToggle();
} else if (v.getId() == R.id.fullscreen) {
} else {
super.onClick(v);
}
}
//changeUiTo 真能能修改ui的方法
@Override
public void changeUiToNormal() {
super.changeUiToNormal();
}
@Override
public void changeUiToPreparing() {
super.changeUiToPreparing();
}
@Override
public void changeUiToPlayingShow() {
super.changeUiToPlayingShow();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPlayingClear() {
super.changeUiToPlayingClear();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseShow() {
super.changeUiToPauseShow();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToPauseClear() {
super.changeUiToPauseClear();
thumbImageView.setVisibility(View.VISIBLE);
}
@Override
public void changeUiToComplete() {
super.changeUiToComplete();
}
@Override
public void changeUiToError() {
super.changeUiToError();
}
}
jzvdStdMp3 = findViewById(R.id.jz_videoplayer_mp3);
jzvdStdMp3.setUp(URL, "饺子摇摆", Jzvd.SCREEN_WINDOW_NORMAL);
Glide.with(this)
.load(VideoConstant.videoThumbs[0][1])
.into(jzvdStdMp3.thumbImageView);
播放完成不显示预览图
public class JzvdStdShowTextureViewAfterAutoComplete extends JzvdStd {
public JzvdStdShowTextureViewAfterAutoComplete(Context context) {
super(context);
}
public JzvdStdShowTextureViewAfterAutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onAutoCompletion() {
super.onAutoCompletion();
thumbImageView.setVisibility(View.GONE);
}
}
Home键退出界面暂停播放,返回界面继续播放
@Override
protected void onResume() {
super.onResume();
//home back
JzvdStd.goOnPlayOnResume();
}
@Override
protected void onPause() {
super.onPause();
// Jzvd.clearSavedProgress(this, null);
//home back
JzvdStd.goOnPlayOnPause();
}
边播边缓存和清晰度切换
1. 集成videocache implementation 'com.danikula:videocache:2.7.0',并初始化
public class ApplicationDemo extends Application {
@Override
public void onCreate() {
super.onCreate();
// LeakCanary.install(this);
}
private HttpProxyCacheServer proxy;
public static HttpProxyCacheServer getProxy(Context context) {
ApplicationDemo app = (ApplicationDemo) context.getApplicationContext();
return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
}
private HttpProxyCacheServer newProxy() {
return new HttpProxyCacheServer(this);
}
}
2.引用
LinkedHashMap map = new LinkedHashMap();
String proxyUrl = ApplicationDemo.getProxy(this).getProxyUrl(VideoConstant.videoUrls[0][9]);
map.put("高清", proxyUrl);
map.put("标清", VideoConstant.videoUrls[0][6]);
map.put("普清", VideoConstant.videoUrlList[0]);
JZDataSource jzDataSource = new JZDataSource(map, "饺子不信");
jzDataSource.looping = true;
jzDataSource.currentUrlIndex = 2;
jzDataSource.headerMap.put("key", "value");//header
mJzvdStd.setUp(jzDataSource
, JzvdStd.SCREEN_WINDOW_NORMAL);
Glide.with(this).load(VideoConstant.videoThumbList[0]).into(mJzvdStd.thumbImageView);
重复播放
创建一个类集成JzvdStd并在XML设置
public class JZVideoPlayerStandardLoopVideo extends JzvdStd{
public JZVideoPlayerStandardLoopVideo (Context context) {
super(context);
}
public JZVideoPlayerStandardLoopVideo (Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onAutoCompletion() {
super.onAutoCompletion();
startVideo();
}
}
还有一种方法就是上面清晰度切换loop循环标志
重力感应自动进入全屏
SensorManager mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Jzvd.JZAutoFullscreenListener mSensorEventListener = new Jzvd.JZAutoFullscreenListener();
@Override
protected void onResume() {
super.onResume();
Sensor accelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mSensorEventListener, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
重力感应
Jzvd.FULLSCREEN_ORIENTATION=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
Jzvd.NORMAL_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
两个变量控制全屏前后的屏幕方向
不保存播放进度
Jzvd.SAVE_PROGRESS = false;
取消播放时在非WIFIDialog提示
Jzvd.WIFI_TIP_DIALOG_SHOWED=true;
清除某个URL进度
Jzvd.clearSavedProgress(this, "url");
切换播放内核
ijk
复制Demo中JZMediaIjkplayer类到你的项目下
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.4'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
Jzvd.setMediaInterface(new JZMediaIjkplayer()); // ijkMediaPlayer
Mediaplayer
Jzvd.setMediaInterface(new JZMediaSystem()); //
exo
复制Demo中JZExoPlayer类到你的项目下
implementation 'com.google.android.exoplayer:exoplayer:2.7.1'
Jzvd.setMediaInterface(new JZExoPlayer()); //exo
用户埋点统计
Jzvd.setJzUserAction(new MyUserActionStd());
/**
* 这只是给埋点统计用户数据用的,不能写和播放相关的逻辑,监听事件请参考MyJzvdStd,复写函数取得相应事件
*/
class MyUserActionStd implements JZUserActionStd {
@Override
public void onEvent(int type, Object url, int screen, Object... objects) {
switch (type) {
case JZUserAction.ON_CLICK_START_ICON:
Log.i("USER_EVENT", "ON_CLICK_START_ICON" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_START_ERROR:
Log.i("USER_EVENT", "ON_CLICK_START_ERROR" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_START_AUTO_COMPLETE:
Log.i("USER_EVENT", "ON_CLICK_START_AUTO_COMPLETE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_PAUSE:
Log.i("USER_EVENT", "ON_CLICK_PAUSE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_CLICK_RESUME:
Log.i("USER_EVENT", "ON_CLICK_RESUME" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_SEEK_POSITION:
Log.i("USER_EVENT", "ON_SEEK_POSITION" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_AUTO_COMPLETE:
Log.i("USER_EVENT", "ON_AUTO_COMPLETE" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_ENTER_FULLSCREEN:
Log.i("USER_EVENT", "ON_ENTER_FULLSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_QUIT_FULLSCREEN:
Log.i("USER_EVENT", "ON_QUIT_FULLSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_ENTER_TINYSCREEN:
Log.i("USER_EVENT", "ON_ENTER_TINYSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_QUIT_TINYSCREEN:
Log.i("USER_EVENT", "ON_QUIT_TINYSCREEN" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_TOUCH_SCREEN_SEEK_VOLUME:
Log.i("USER_EVENT", "ON_TOUCH_SCREEN_SEEK_VOLUME" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserAction.ON_TOUCH_SCREEN_SEEK_POSITION:
Log.i("USER_EVENT", "ON_TOUCH_SCREEN_SEEK_POSITION" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserActionStd.ON_CLICK_START_THUMB:
Log.i("USER_EVENT", "ON_CLICK_START_THUMB" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
case JZUserActionStd.ON_CLICK_BLANK:
Log.i("USER_EVENT", "ON_CLICK_BLANK" + " title is : " + (objects.length == 0 ? "" : objects[0]) + " url is : " + url + " screen is : " + screen);
break;
default:
Log.i("USER_EVENT", "unknow");
break;
}
}
}
相关函数回调,屏幕状态,播放器状态,事件
在继承JzvdStd之后,可以通过父类的mCurrentState,取得当前的播放状态。
- CURRENT_STATE_IDLE 未知状态,指控件被new出来之后什么都没做
- CURRENT_STATE_NORMAL 普通状态
- CURRENT_STATE_PREPARING 视频准备状态
- CURRENT_STATE_PREPARING_CHANGING_URL 播放中切换url的准备状态
- CURRENT_STATE_PLAYING 播放中状态
- CURRENT_STATE_PAUSE 暂停状态
- CURRENT_STATE_AUTO_COMPLETE 自动播放完成状态
- CURRENT_STATE_ERROR 错误状态
复写进入播放状态的函数,取得播放状态的回调
- onStateNormal 进入普通状态,通常指setUp之后
- onStatePreparing 进入准备中状态,就是loading状态
- onStatePlaying 进入播放状态
- onStatePause 进入暂停状态
- onStateError 进入错误状态
- onStateAutoComplete 进入自动播放完成状态
全屏、小窗、非全屏分别是不同的实例,在继承JzvdStd后,通过mCurrentScreen变量,取得当前屏幕类型
- SCREEN_WINDOW_NORMAL 普通窗口(进入全屏之前的)
- SCREEN_WINDOW_LIST 列表窗口(进入全屏之前)
- SCREEN_WINDOW_FULLSCREEN 全屏
- SCREEN_WINDOW_TINY 小窗
事件
- 复写onProgress函数,取得每次播放器设置底部seekBar的进度回调
- 调用changeUrl函数,切换url
- 复写onClick函数,取得各种按钮的点击事件
- 复写onTouch函数,取得全屏之后的手势操作
使用JiaoZiVideoPlayer播放视频方向不对问题
直接上代码
public class JZplayer extends JzvdStd {
public JZplayer(Context context) {
super(context);
}
public JZplayer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void init(Context context) {
super.init(context);
backButton.setVisibility(GONE);
}
@Override
public void onInfo(int what, int extra) {
super.onInfo(what, extra);
if(what==IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED){
//这里返回了视频的旋转角度,根据角度旋转视频到正确角度
JZMediaManager.textureView.setRotation(extra);
}
}
}