转载请注明出处:【From李诗雨---blog.csdn.net/cjm24848365…】
不诗意的女程序猿不是好厨师~
【代码下载】:节操播放器在Activity中的使用代码
节操播放器在github的地址:github.com/lipangit/Ji…
节操播放器,现在改名叫饺子播放器了,你们发现了吗?
项目中使用到了节操播放器(饺子播放器),还真遇到了不少问题。
今天就在这里来好好地整理总结一下。
【主要内容】
节操播放器在Activity中的使用:
1.节操播放器的简单集成
2.横竖屏切换
2.1 点击全屏,直接进入到横屏全屏的实现
2.2 重力感应下横竖屏切换的实现
2.3 重力感应下横竖屏切换不灵敏问题的解决
3.清晰度切换
3.1 清晰度切换的简单实现
3.2 清晰度切换的自定义样式
3.3 直播流清晰度切换时,出现灰色闪屏问题的解决
4.沉浸式状态栏
4.1 沉浸式状态的简单集成
4.2 状态栏与布局重叠问题的解决
4.3 全屏播放视频时,沉浸式状态栏的问题解决
4.4 华为5.0虚拟导航栏引起的问题处理
节操播放器在Fragment的使用
下面就让我们来看看这些七七八八的问题及解决办法吧:
节操播放器在activity中的使用
建议使用第三方库,最好以module的形式导入!
主要原因有二:
①方便修改库中的代码,为我所用
②安全,防止库发生改变后会对自己的代码造成不必要的影响
一、节操播放器的简单集成(这里我们根据github上的内容进行集成)
1.将jiaozivideoplayer以module的形式导入,并关联
2.AndroidManifest中
①添加联网权限
<uses-permission android:name="android.permission.INTERNET" />
②配置对应的activity(这一步不可忘!),否则横屏时会出问题。
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait">
</activity>
2.布局中:
<cn.jzvd.JZVideoPlayerStandard
android:id="@+id/lsy_video_player"
android:layout_width="match_parent"
android:layout_height="200dp"/>
3.Acitivity中:
@Override
public void onBackPressed() {
if (JZVideoPlayer.backPress()) {
return;
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
JZVideoPlayer.releaseAllVideos();
}
//设置播放器播放地址
lsyVideoPlayer.setUp("http://jzvd.nathen.cn/342a5f7ef6124a4a8faf00e738b8bee4/cf6d9db0bd4d41f59d09ea0a81e918fd-5287d2089db37e62345123a1be272f8b.mp4"
, JZVideoPlayerStandard.SCREEN_LAYOUT_NORMAL, "小孩子");
//设置播放器封面
Picasso.with(this)
.load("http://jzvd-pic.nathen.cn/jzvd-pic/1bb2ebbe-140d-4e2e-abd2-9e7e564f71ac.png")
.into(lsyVideoPlayer.thumbImageView);
好的,让我们来看下集成效果:
二.横竖屏切换
2.1
点击全屏,直接进入到横屏全屏的实现
刚才我们看到集成的效果,当点击全屏按钮时,切换到的并不是横屏全屏状态。 现在我们的需求是点击全屏按钮时,直接切换到横屏全屏状态,该怎么操作呢?
这个简单,在设置播放器前加两行代码就行了。
JZVideoPlayer.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; //横向
JZVideoPlayer.NORMAL_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; //纵向
欧了,看下效果(由于是模拟器,所以可能看起来有点晃哈):
2.2
重力感应下横竖屏切换的实现
①首先在初始化播放器时:
//用于实现重力感应下切换横竖屏
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensorEventListener = new JZVideoPlayer.JZAutoFullscreenListener();
②然后在onResume()方法中:
@Override
protected void onResume() {
super.onResume();
//播放器重力感应
Sensor accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(sensorEventListener, accelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
③然后在onPause()方法中要注意解注册:
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(sensorEventListener);
JZVideoPlayer.releaseAllVideos();
}
这个模拟器没法展示重力感应效果了,你可用真机进行测试一下。
确实是可以进行重力感应下的横竖屏切换了,但是不怎么灵敏好用,又要怎么办呢?
2.3
重力感应下横竖屏切换不灵敏问题的解决 这就需要我们来改播放器的源码了。
这里我们都是对JZVideoPlayer这个类进行的操作,具体操作如下:
①自己新定义一个变量来记录旋转的几种状态。
private static int canFullScreen = 0;
②在onClick()中,当fullscreen被点击时:
//退出全屏时,令 canFullScreen = 1;
//进入全屏时,令canFullScreen = 5;
else if (i == R.id.fullscreen) {
Log.i(TAG, "onClick fullscreen [" + this.hashCode() + "] ");
if (currentState == CURRENT_STATE_AUTO_COMPLETE) return;
if (currentScreen == SCREEN_WINDOW_FULLSCREEN) {
//quit fullscreen
backPress();
canFullScreen = 1;
isFullScreenNow = false;//
} else {
canFullScreen = 5;
Log.d(TAG, "toFullscreenActivity [" + this.hashCode() + "] ");
onEvent(JCUserAction.ON_ENTER_FULLSCREEN);
startWindowFullscreen();
isFullScreenNow = true;//
}
③在onAutoCompletion()方法中,也添加对canFullScreen的记录:
public void onAutoCompletion() {
//加上这句,避免循环播放video的时候,内存不断飙升。
Runtime.getRuntime().gc();
Log.i(TAG, "onAutoCompletion " + " [" + this.hashCode() + "] ");
onEvent(JZUserAction.ON_AUTO_COMPLETE);
dismissVolumeDialog();
dismissProgressDialog();
dismissBrightnessDialog();
cancelProgressTimer();
onStateAutoComplete();
if (currentScreen == SCREEN_WINDOW_FULLSCREEN || currentScreen == SCREEN_WINDOW_TINY) {
backPress();
//全屏结束恢复状态
canFullScreen = 0;
}
canFullScreen = 0;
JZMediaManager.instance().mediaPlayer.release();
JZUtils.saveProgress(getContext(), JZUtils.getCurrentUrlFromMap(urlMap, currentUrlMapIndex), 0);
}
④在releaseAllVideos()方法中: 令 canFullScreen = 0;
public static void releaseAllVideos() {
if ((System.currentTimeMillis() - CLICK_QUIT_FULLSCREEN_TIME) > FULL_SCREEN_NORMAL_DELAY) {
Log.d(TAG, "releaseAllVideos");
JZVideoPlayerManager.completeAll();
JZMediaManager.instance().positionInList = -1;
JZMediaManager.instance().releaseMediaPlayer();
canFullScreen = 0;
}
}
⑤我们的核心是对autoFullscreen()方法进行修改
主要原理是根据旋转角度,来规定状态
修改后如下模样: (你也可以根据自己对灵敏度的要求,针对角度进行相应的修改)
public void autoFullscreen(float x) {
// if (isCurrentPlay()
// && currentState == CURRENT_STATE_PLAYING
// && currentScreen != SCREEN_WINDOW_FULLSCREEN
// && currentScreen != SCREEN_WINDOW_TINY) {
// if (x > 0) {
// JZUtils.setRequestedOrientation(getContext(), ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// } else {
// JZUtils.setRequestedOrientation(getContext(), ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
// }
// onEvent(JZUserAction.ON_ENTER_FULLSCREEN);
// startWindowFullscreen();
// }
if (isCurrentJZVD() && currentState == CURRENT_STATE_PLAYING) {
if (x > 45 && x < 115 && currentScreen != SCREEN_WINDOW_FULLSCREEN && (canFullScreen == 2 || canFullScreen == 4 || canFullScreen == 0 || canFullScreen == 3)) {//左转
canFullScreen = 1;
onEvent(JZUserAction.ON_ENTER_FULLSCREEN);
startWindowFullscreen();
JZUtils.getAppCompActivity(getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else if (x > 135 && x < 225 && currentScreen == SCREEN_WINDOW_FULLSCREEN && (canFullScreen == 1 || canFullScreen == 3 || canFullScreen == 0)) {//上
canFullScreen = 2;
backPress();
} else if (x > 245 && x < 315 && currentScreen != SCREEN_WINDOW_FULLSCREEN && (canFullScreen == 2 || canFullScreen == 4 || canFullScreen == 0 || canFullScreen == 1)) {//右转
canFullScreen = 3;
onEvent(JZUserAction.ON_ENTER_FULLSCREEN);
startWindowFullscreen();
JZUtils.getAppCompActivity(getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
} else if ((x > 315 && x < 360) || (x > 0 && x < 45) && currentScreen == SCREEN_WINDOW_FULLSCREEN && (canFullScreen == 1 || canFullScreen == 3 || canFullScreen == 0)) {//下
canFullScreen = 4;
backPress();
} else if (x > 45 && x < 135 && currentScreen == SCREEN_WINDOW_FULLSCREEN && canFullScreen == 5) {//全屏后恢复自动旋转
canFullScreen = 1;
} else if (x > 225 && x < 315 && currentScreen == SCREEN_WINDOW_FULLSCREEN && canFullScreen == 5) {//全屏后恢复自动旋转
canFullScreen = 3;
} else if (x > 135 && x < 225 && currentScreen != SCREEN_WINDOW_FULLSCREEN && (canFullScreen == 1 || canFullScreen == 3 || canFullScreen == 2 || canFullScreen == 5)) {//返回后恢复自动旋转
canFullScreen = 2;
}
}
}
⑥最后JCAutoFullscreenListener也要做相应的修改,修改后如下所示:
//再定义一个变量来记录重力传感器倾斜角度
private static float orientation;
public static class JZAutoFullscreenListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {//可以得到传感器实时测量出来的变化值
final float x = event.values[SensorManager.DATA_X];
float y = event.values[SensorManager.DATA_Y];
float z = event.values[SensorManager.DATA_Z];
//过滤掉用力过猛会有一个反向的大数值
// if (((x > -15 && x < -10) || (x < 15 && x > 10)) && Math.abs(y) < 1.5) {
// if ((System.currentTimeMillis() - lastAutoFullscreenTime) > 2000) {
// if (JCVideoPlayerManager.getCurrentJcvd() != null) {
// JCVideoPlayerManager.getCurrentJcvd().autoFullscreen(orientation);
// }
// if (JZVideoPlayerManager.getCurrentJzvd() != null) {
// JZVideoPlayerManager.getCurrentJzvd().autoFullscreen(x);
// }
// lastAutoFullscreenTime = System.currentTimeMillis();
// }
// }
float magnitude = (x * x) + (y * y);
if (magnitude * 4 >= z * z) {
// 屏幕旋转时
float OneEightyOverPi = 57.29577957855f;
float angle = (float) Math.atan2(-y, x) * OneEightyOverPi;
orientation = 90 - Math.round(angle); // normalize to 0 - 359 range
while (orientation >= 360) {
orientation -= 360;
}
while (orientation < 0) {
orientation += 360;
}
}
if (JZVideoPlayerManager.getCurrentJzvd() != null) {
JZVideoPlayerManager.getCurrentJzvd() .autoFullscreen(orientation);
}
}
这时再次运行代码,你就会发现重力感应下的横竖屏切换就已经很灵敏了。 当然如果还不能满足你的要求,你可以参看上面的步骤再做相应的调整。
三、清晰度切换
3.1
清晰度切换的简单实现
饺子播放器已经帮我们做好了清晰度切换的简单封装,全屏时可进行清晰度的切换。
如果想要实现切换清晰度,我们传播放地址时只要以Map的形式传进去就可以了:
private void initClarityVideoPlayer() {
String videoUrl1_biaoqing= "http://jzvd.nathen.cn/c494b340ff704015bb6682ffde3cd302/64929c369124497593205a4190d7d128-5287d2089db37e62345123a1be272f8b.mp4";
String videoUrl2_puqing= "http://jzvd.nathen.cn/342a5f7ef6124a4a8faf00e738b8bee4/cf6d9db0bd4d41f59d09ea0a81e918fd-5287d2089db37e62345123a1be272f8b.mp4";
String videoUrl3_gaoqing= "http://jzvd.nathen.cn/f07fa9fddd1e45a6ae1570c7fe7967c1/c6db82685b894e25b523b1cb28d79f2e-5287d2089db37e62345123a1be272f8b.mp4";
//播放带清晰度的视频
LinkedHashMap map = new LinkedHashMap();
map.put("标清", videoUrl1_biaoqing);
map.put("普清", videoUrl2_puqing);
map.put("高清", videoUrl3_gaoqing);
clarityVideoPlayer.setUp(map, 0
, JZVideoPlayerStandard.SCREEN_LAYOUT_NORMAL, "全屏时清晰度切换");
}
我们开看一下效果:
你可能会觉得弹出清晰度的popupwidow不好点,又或者你需要其他的效果来显示,
那么,这时候就需要我们来修改饺子播放器的源码了。
3.2 清晰度切换的自定义样式
现在比如我们的需求是实现下图所示的清晰度切换:
如何才能实现这种效果呢?
下面让我们来对饺子播放器的源码做相应的修改:
注:以下的操作都是对饺子播放器的源码库进行操作的
①先找到,清晰度对应的popupwindow所在的布局文件jzlayoutclarity.xml:
修改成现在我们需要的样子
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_quality_wrapper_area"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:background="#66000000"
android:orientation="vertical"
/>
<!--原始的切换清晰度布局-->
<!--<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"-->
<!--android:id="@+id/video_quality_wrapper_area"-->
<!--android:layout_width="80dp"-->
<!--android:layout_height="wrap_content"-->
<!--android:background="@drawable/jz_clarity_popwindow_bg"-->
<!--android:orientation="vertical"-->
<!--android:paddingBottom="16dp"-->
<!--android:paddingTop="10dp" />-->
再修改清晰度item对应的布局,来到jzlayoutclarity_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/video_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:background="#fff"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="8dp"
android:textColor="#000"
android:textSize="15sp" />
<!--android:textColor="#FFF"-->
<!--设置清晰度tv的颜色-->
<View
android:background="#05000000"
android:layout_width="match_parent"
android:layout_height="1dp"/>
</LinearLayout>
<!--原始的item对应的布局-->
<!--<TextView xmlns:android="http://schemas.android.com/apk/res/android"-->
<!--android:id="@+id/video_item"-->
<!--android:layout_width="fill_parent"-->
<!--android:layout_height="wrap_content"-->
<!--android:gravity="center"-->
<!--android:orientation="vertical"-->
<!--android:paddingBottom="8dp"-->
<!--android:paddingLeft="10dp"-->
<!--android:paddingRight="10dp"-->
<!--android:paddingTop="8dp"-->
<!--android:textColor="#FFF"-->
<!--android:textSize="14sp" />-->
再添加一个cancel对应的item布局:
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<View
android:background="#00000000"
android:layout_width="match_parent"
android:layout_height="4dp"/>
<TextView
android:textStyle="bold"
android:id="@+id/video_item"
android:text="Cancel"
android:background="#ffffff"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="8dp"
android:textColor="#000"
android:textSize="16sp" />
<!--android:textColor="#FFF"-->
<!--设置清晰度tv的颜色-->
</LinearLayout>
②下面在JZVideoPlayerStandard类中的onClick()方法中
做一下修改(修改的主要是popupwindow,包括它的显示位置、大小等)
else if (i == R.id.clarity) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.jz_layout_clarity, null);
OnClickListener mQualityListener = new OnClickListener() {
public void onClick(View v) {
int index = (int) v.getTag();
onStatePreparingChangingUrl(index, getCurrentPositionWhenPlaying());
clarity.setText(JZUtils.getKeyFromLinkedMap(urlMap, currentUrlMapIndex));
for (int j = 0; j < layout.getChildCount(); j++) {//设置点击之后的颜色
if (j == currentUrlMapIndex) {
//选中设为蓝色
// ((TextView) layout.getChildAt(j)).setTextColor(Color.parseColor("#fff85959"));
((TextView) (layout.getChildAt(j)).findViewById(R.id.video_item)).setTextColor(Color.parseColor("#003dd5"));
} else {
//未选中显示黑色
// ((TextView) layout.getChildAt(j)).setTextColor(Color.parseColor("#ffffff"));
((TextView) (layout.getChildAt(j)).findViewById(R.id.video_item)).setTextColor(Color.parseColor("#000000"));
}
}
if (clarityPopWindow != null) {
clarityPopWindow.dismiss();
}
}
};
for (int j = 0; j < urlMap.size()+1; j++) {
//设置普通的清晰度item
if (j < urlMap.size()) {
String key = JZUtils.getKeyFromLinkedMap(urlMap, j);
// TextView clarityItem = (TextView) View.inflate(getContext(), R.layout.jz_layout_clarity_item, null);
//这里发生变化(这里要细心点,不要写错了哈)
LinearLayout clarityItemNormal = (LinearLayout) View.inflate(getContext(), R.layout.jz_layout_clarity_item, null);
TextView clarityItem = (TextView) (clarityItemNormal.findViewById(R.id.video_item));
clarityItem.setText(key);
clarityItemNormal.setTag(j);
layout.addView(clarityItemNormal, j);
//点击具体某条清晰度tv的点击事件的监听
clarityItemNormal.setOnClickListener(mQualityListener);
if (j == currentUrlMapIndex) {
// clarityItem.setTextColor(Color.parseColor("#fff85959"));
clarityItem.setTextColor(Color.parseColor("#003dd5"));
}
//设置cancel 的 item
}else if(j==urlMap.size()) {
LinearLayout clarityItemCancel = (LinearLayout) View.inflate(getContext(), R.layout.cancel_jc_layout_clarity_item, null);
((TextView)(clarityItemCancel.findViewById(R.id.video_item))).setText("Cancel");
// clarityItem.setText("Cancel");
layout.addView(clarityItemCancel, j);
//点击具体某条清晰度tv的点击事件的监听
clarityItemCancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (clarityPopWindow != null) {
clarityPopWindow.dismiss();
}
}
});
}
}
// clarityPopWindow = new PopupWindow(layout, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
clarityPopWindow = new PopupWindow(layout, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, true);
clarityPopWindow.setContentView(layout);
// clarityPopWindow.showAsDropDown(clarity);
layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
//原始的popupwindow的显示位置
// clarityPopWindow.update(clarity, -40, 46, Math.round(layout.getMeasuredWidth() * 2), layout.getMeasuredHeight());
//重新设置使得popupwindow从底部弹出
int fullscreenButtonHeight=fullscreenButton.getMeasuredHeight();
int[] location = new int[2];
v.getLocationOnScreen(location);
clarityPopWindow.showAtLocation(bottomProgressBar, Gravity.BOTTOM,location[0]-Math.round(layout_bottom.getMeasuredWidth()),0);
//注:layout_bottom在jz_layout_standard中已经有了
// android:id="@+id/layout_bottom",这里我们再把它初始化一下,目的是为了是popupwindow可以从底部开始显示
}
这时你会发现,popupwindow在很短的时间内就自动消失了,体验不太好
所以,我们还要更改一下,让一段时间内他不自动消失。
仍然在JZVideoPlayerStandard类中
找到DismissControlViewTimerTask的dissmissControlView()方法,注释掉popupwindow的相关代码即可:
public class DismissControlViewTimerTask extends TimerTask {
@Override
public void run() {
dissmissControlView();
}
}
public void dissmissControlView() {
if (currentState != CURRENT_STATE_NORMAL
&& currentState != CURRENT_STATE_ERROR
&& currentState != CURRENT_STATE_AUTO_COMPLETE) {
if (getContext() != null && getContext() instanceof Activity) {
((Activity) getContext()).runOnUiThread(new Runnable() {
@Override
public void run() {
bottomContainer.setVisibility(View.INVISIBLE);
topContainer.setVisibility(View.INVISIBLE);
startButton.setVisibility(View.INVISIBLE);
//不让清晰度的popupWindow自动消失,所以此代码注释掉
// if (clarityPopWindow != null) {
// clarityPopWindow.dismiss();
// }
if (currentScreen != SCREEN_WINDOW_TINY) {
bottomProgressBar.setVisibility(View.VISIBLE);
}
}
});
}
}
}
还要注意一点,播放完成时,要保证popupwindow是消失的
@Override
public void onStateAutoComplete() {
super.onStateAutoComplete();
changeUiToComplete();
cancelDismissControlViewTimer();
//保证自动播放结束时,popupwindow是消失的
if(clarityPopWindow!=null) {
clarityPopWindow.dismiss();
}
bottomProgressBar.setProgress(100);
}
好了,现在的清晰度切换就基本满足我的要求了,如果你还要其他的显示效果,也照着修改相应的地方即可。
让我们再看下修改后的效果:
3.3
直播流清晰度切换时,出现灰色闪屏问题的解决
直播流如果也有清晰度切换的需求,如果直接使用以上方法, 你会发现在切换清晰度时,会有灰色闪屏现象出现。 这是由于直播流没有总时长和切换时时长记录一说, 而饺子播放器默认在切换清晰度时,会先记录切换清晰度时的进度, 然后新的清晰度会在这个进度的基础上继续进行播放, 直播流是没有记录当前进度,以及从某个进度接着播放之说的, 所以在切换时就会出现灰色闪屏。
要解决这个问题我们只要对onStatePreparingChangingUrl()做相应修改即可,
让其切换清晰度时每次都从0开始,就不会出现灰色闪屏了。
四.沉浸式状态栏
现在的app基本上都要设置沉浸式状态栏,沉浸式状态栏的设置并不困难,但是你会发现设置了沉浸式状态栏后,在播放器进入全屏时,就会变成这个样子了:
下面我们还是一步一步来:
4.1
沉浸式状态的简单集成
这里给大家推荐一个比较好用的设置沉浸式状态栏的第三方库ImmersionBar:
github地址:github.com/gyf-dev/Imm…
设置一个粉红色的少女粉作为我们的主题吧
①来到我们的colors.xml文件,稍作修改
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--<color name="colorPrimary">#3F51B5</color>-->
<!--<color name="colorPrimaryDark">#303F9F</color>-->
<color name="colorPrimary">#FFAEB9</color>
<color name="colorPrimaryDark">#FFAEB9</color>
<color name="colorAccent">#FF4081</color>
<color name="girlColor">#FFAEB9</color>
</resources>
各个色对应的是那个部位呢,给大家一张图大家就知道了:
②使用第三方库设置沉浸式状态栏:
在Activity的onCreate()中添加如下代码:
//设置沉浸式状态栏---这里把状态栏和导航栏都设置为纷纷地少女粉
mImmersionBar = ImmersionBar.with(this);
mImmersionBar
.statusBarColor(R.color.girlColor) //状态栏颜色,不写默认透明色
.init();
在Acivity的onDestroy()中:
@Override
protected void onDestroy() {
super.onDestroy();
if (mImmersionBar != null)
mImmersionBar.destroy(); //必须调用该方法,防止内存泄漏,不调用该方法,如果界面bar发生改变,在不关闭app的情况下,退出此界面再进入将记忆最后一次bar改变的状态
}
好了沉浸式状态栏设置结束了
却发现状态栏和布局重叠了~
4.2
状态栏与布局重叠问题的解决
其实只要在布局文件中加一个属性就可以了:
android:fitsSystemWindows="true"
看下效果:
布局重叠确实解决了
4.3
全屏播放视频时,沉浸式状态栏的问题解决
问题是这样的: 播放器全屏时,状态栏仍然执迷不悟的显示,真的很难看。
这里我做的处理是,写一个接口,用来监听播放器进入全屏和退出全屏。
//为了解决状态栏问题,设置的进入全屏和退出全屏的监听
public void setFullScreenChangedListener(FullScreenChangedListener fullScreenChangedListener){
this.mFullScreenChangedListener=fullScreenChangedListener;
}
public interface FullScreenChangedListener{
void enterFullScreen();
void quitFullScreen();
}
在进入全屏和退出全屏的地方调用接口的相关方法:
public void startWindowFullscreen() {
Log.i(TAG, "startWindowFullscreen " + " [" + this.hashCode() + "] ");
hideSupportActionBar(getContext());
JZUtils.setRequestedOrientation(getContext(), FULLSCREEN_ORIENTATION);
ViewGroup vp = (ViewGroup) (JZUtils.scanForActivity(getContext()))//.getWindow().getDecorView();
.findViewById(Window.ID_ANDROID_CONTENT);
View old = vp.findViewById(R.id.jz_fullscreen_id);
if (old != null) {
vp.removeView(old);
}
textureViewContainer.removeView(JZMediaManager.textureView);
try {
//在这里会进入全屏状态:
if(mFullScreenChangedListener!=null) {
mFullScreenChangedListener.enterFullScreen();
}
Constructor<JZVideoPlayer> constructor = (Constructor<JZVideoPlayer>) JZVideoPlayer.this.getClass().getConstructor(Context.class);
JZVideoPlayer jzVideoPlayer = constructor.newInstance(getContext());
jzVideoPlayer.setId(R.id.jz_fullscreen_id);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
vp.addView(jzVideoPlayer, lp);
jzVideoPlayer.setUp(urlMap, currentUrlMapIndex, JZVideoPlayerStandard.SCREEN_WINDOW_FULLSCREEN, objects);
jzVideoPlayer.setState(currentState);
jzVideoPlayer.addTextureView();
JZVideoPlayerManager.setSecondFloor(jzVideoPlayer);
// final Animation ra = AnimationUtils.loadAnimation(getContext(), R.anim.start_fullscreen);
// jzVideoPlayer.setAnimation(ra);
onStateNormal();
jzVideoPlayer.progressBar.setSecondaryProgress(progressBar.getSecondaryProgress());
jzVideoPlayer.startProgressTimer();
CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
}
}
public static boolean backPress() {
Log.i(TAG, "backPress");
if ((System.currentTimeMillis() - CLICK_QUIT_FULLSCREEN_TIME) < FULL_SCREEN_NORMAL_DELAY)
return false;
if (JZVideoPlayerManager.getSecondFloor() != null) {
CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis();
if (JZVideoPlayerManager.getFirstFloor().getCurrentUrl().equals(JZMediaManager.CURRENT_PLAYING_URL)) {
JZVideoPlayer jzVideoPlayer = JZVideoPlayerManager.getSecondFloor();
jzVideoPlayer.onEvent(jzVideoPlayer.currentScreen == JZVideoPlayerStandard.SCREEN_WINDOW_FULLSCREEN ?
JZUserAction.ON_QUIT_FULLSCREEN :
JZUserAction.ON_QUIT_TINYSCREEN);
JZVideoPlayerManager.getFirstFloor().playOnThisJzvd();
} else {
//直接退出全屏和小窗
JZVideoPlayerManager.getCurrentJzvd().currentState = CURRENT_STATE_NORMAL;
JZVideoPlayerManager.getFirstFloor().clearFloatScreen();
JZMediaManager.instance().releaseMediaPlayer();
JZVideoPlayerManager.setFirstFloor(null);
}
//return true时会退出全屏
if(mFullScreenChangedListener!=null) {
mFullScreenChangedListener.quitFullScreen();
}
return true;
} else if (JZVideoPlayerManager.getFirstFloor() != null &&
(JZVideoPlayerManager.getFirstFloor().currentScreen == SCREEN_WINDOW_FULLSCREEN ||
JZVideoPlayerManager.getFirstFloor().currentScreen == SCREEN_WINDOW_TINY)) {//以前我总想把这两个判断写到一起,这分明是两个独立是逻辑
CLICK_QUIT_FULLSCREEN_TIME = System.currentTimeMillis();
//直接退出全屏和小窗
JZVideoPlayerManager.getCurrentJzvd().currentState = CURRENT_STATE_NORMAL;
JZVideoPlayerManager.getFirstFloor().clearFloatScreen();
JZMediaManager.instance().releaseMediaPlayer();
JZVideoPlayerManager.setFirstFloor(null);
//return true时会退出全屏
if(mFullScreenChangedListener!=null) {
mFullScreenChangedListener.quitFullScreen();
}
return true;
}
return false;
}
然后我们再在Activity中取实现这个接口:
public class MainActivity extends AppCompatActivity implements JZVideoPlayer.FullScreenChangedListener {
lsyVideoPlayer.setFullScreenChangedListener(this);
实现接口的相关方法:
在其进入全屏时,设置状态栏的颜色为透明。
在其退出全屏时,恢复设置状态栏的颜色为少女粉。
/**
* 进入全屏时的回调
*/
@Override
public void enterFullScreen() {
//将状态栏设置为透明
mImmersionBar
.statusBarColor(R.color.transparent)
.init();
}
/**
* 退出全屏时的回调
*/
@Override
public void quitFullScreen() {
在开发微信公众号或小程序的时候,由于微信平台规则的限制,部分接口需要通过线上域名才能正常访问。但我们一般都会在本地开发,因为这能快速的看到源码修改后的运行结果。但当涉及到需要调用微信接口时,由于不和你在同一个局域网中的用户是无法访问你的本地开发机的,就必须把修改后的代码重新发布到线上域名所在的服务器才能去验证结果。每次修改都重新发布很繁琐也很浪费时间。
本文将教你如何通过 SSH 隧道把本地服务映射到外网,以方便调试,通常把这种方法叫内网穿透。
阅读完本文后,你能解决以下常见问题:
- 开发微信公众号等应用时把本地服务映射到外网,加速调试流程;
- 把你正在开发的本地服务分享给互联网上其它人访问体验;
- 在任何地方通过互联网控制你家中在局域网里的电脑;
最终目的
把运行在本地开发机上的 HTTP 服务映射到外网,让全世界都能通过外网 IP 服务到你本地开发机上的 HTTP 服务。例如你本地的 HTTP 服务监听在 127.0.0.1:8080
,你有一台公网 IP 为 12.34.56.78
的服务器,通过本文介绍的方法,可以让全世界的用户通过 http://12.34.56.78:8080
访问到你本地开发机上的 HTTP 服务。
总结成一句话就是:把内网端口映射到外网。
前提条件
为了把内网服务映射到外网,以下资源为必须的:
- 一台有外网 IP 的服务器;
- 能在本地开发机上通过
ssh
登入到外网服务器。
要满足以上条件很简单:
- 对于条件1:购买一台低配 Linux 服务器,推荐国外的 DigitalOcean;
- 对于条件2:对于 Mac、Linux 开发机是内置了 ssh 客户端的,对于 Windows 可以安装 Cygwin。
实现原理
要实现把内网端口映射到外网,最简单的方式就是通过 SSH 隧道。
SSH 隧道就像一根管道,能把任何2台机器连接在一起,把发送到其中一台机器的数据通过管道传输到另一台机器。假如已经通过 SSH 隧道把本地开发机和外网服务器连接在了一起,外网服务器端监听在 12.34.56.78:8080
,那么所有发给 12.34.56.78:8080
的数据都会通过 SSH 隧道原封不动地传输给本地开发机的 127.0.0.1:8080
,如图所示:
也就是说,去访问 12.34.56.78:8080
就像是访问本地开发机的 127.0.0.1:8080
,本地开发机上的 8080 端口被映射到了外网服务器上的 8080 端口。
如果你的外网服务器 IP 配置了域名解析,例如 yourdomin.com
会通过 DNS 解析为 12.34.56.78
,那么也可以通过 yourdomin.com:8080
去访问本地开发机上的服务。
这样就做到了访问外网地址时其实是本地服务返回的结果。
通过 SSH 隧道传输数据时,数据会被加密,就算中间被劫持,黑客也无法得到数据的原内容。 所以 SSH 隧道还有一个功能就是保证数据传输的安全性。
实现步骤
把本地开机和外网服务器通过 SSH 隧道连接起来就和在本地开发机 SSH 登入远程登入到外网服务器一样简单。
先来回顾以下 SSH 远程登入命令,假如想在本地远程登入到 12.34.56.78
,可以在本地开发机上执行以下命令:
ssh username@12.34.56.78
而实现 SSH 隧道只需在本地开发机上执行:
ssh -R 8080:127.0.0.1:8080 username@12.34.56.78
可以看出实现 SSH 隧道的命令相对于 SSH 登入多出来 -R 8080:127.0.0.1:8080
,多出的这部分的含义是:
在远程机器(12.34.56.78
)上启动 TCP 8080端口监听着,再把远程机器(12.34.56.78
)上8080端口映射到本地的127.0.0.1:8080
。
执行完以上命令后,就可以通过 12.34.56.78:8080
去访问本地的 127.0.0.1:8080
了。
通常把这种技术叫做 SSH 远程端口转发(remote forwarding)。
其实不限于只能把本地开发机上运行的服务映射到外网服务器上去,还可以把任何本地开发机可以访问的服务映射到外网服务器上去。例如在本地开发机上能访问 github.com:80
,在本地开发机上执行:
ssh -R 8080:github.com:80 username@12.34.56.78
就能通过 12.34.56.78:8080
去访问 github.com:80
了。
保持运行
在执行完上面介绍的 SSH 隧道命令后,你会发现登入到了外网服务器上去了,如果你登出外网服务器,就会发现 12.34.56.78:8080
无法访问了。导致这个问题的原因是你登出外网服务器时,在外网服务器上本次操作对应的 SSH 进程也跟着退出了,而这个退出的进程曾负责监听在 8080 端口进行转发操作。
为了让 SSH 隧道一直保持在后台执行,有以下方法。
通过 SSH 自带的参数
SSH 还支持这些参数:
- N参数:表示只连接远程主机,不打开远程shell;
- T参数:表示不为这个连接分配TTY;
- f参数:表示连接成功后,转入后台运行;
因此要让 SSH 隧道一直保持在后台执行,可以通过以下命令:
ssh -NTf -R 8080:127.0.0.1:8080 username@12.34.56.78
通过 AutoSSH
SSH 隧道是不稳定的,在网络恶劣的情况下可能随时断开。如果断开就需要手动去本地开发机再次向外网服务器发起连接。 AutoSSH 能让 SSH 隧道一直保持执行,他会启动一个 SSH 进程,并监控该进程的健康状况;当 SSH 进程崩溃或停止通信时,AutoSSH 将重启动 SSH 进程。
使用AutoSSH 只需在本地开发机上安装 AutoSSH ,方法如下:
- Mac 系统:
brew install autossh
;
- Linux 系统:
apt-get install autossh
;
安装成功后,在本地开发机上执行:
autossh -N -R 8080:127.0.0.1:8080 username@12.34.56.78
就能完成和上面一样的效果,但本方法能保持 SSH 隧道一直运行。
可以看出这行命令和上面的区别在于把 ssh
换成了 autossh
,并且少了 -f
参数,原因是 autossh 默认会转入后台运行。
常见问题
如果你遇到通过以上方法成功启动 SSH 隧道后,还是无法访问 12.34.56.78:8080
,那么很有可能是外网服务器上的 SSH 没有配置对。为此你需要去外网服务器上修改 /etc/ssh/sshd_config
文件如下:
GatewayPorts yes
这个选项的意思是,SSH 隧道监听的服务的 IP 是对外开放的 0.0.0.0,而不是只对本机的 127.0.0.1。不开 GatewayPorts 的后果是不能通过 12.34.56.78:8080
访问,只能在外网服务器上通过 127.0.0.1:8080
服务到本地开发机的服务。
修改好配置文件后,你还需要重启 sshd 服务来加载新的配置,命令如下:
service sshd restart
如果使用以上方法还是无法访问 12.34.56.78:8080
,请检查你外网服务器的防火墙配置,确保 8080 端口是对外开放的。
其它代替方案
除了 SSH 隧道能实现内网穿透外,还有以下常用方法。
frp
frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp, http, https 协议。 frp 有以下特性:
- frp 比 SSH 隧道功能更多,配置项更多;
- frp 也需要一台外网服务器,并且需要在外网服务器上安装 frps,在本地开发机上安装 frpc;
ngrok
ngrok 是一个商用的内网穿透工具,它有以下特点:
- 不需要有外网服务器,因为 ngrok 会为你提供;
- 只需要在本地开发机安装 ngrok 客户端,和注册 ngrok 账户;
- 按照服务收费;
这些代替方案的缺点在于都需要再额外安装其它工具,没有 SSH 隧道来的直接。 想了解更多可以访问它们的主页。
//恢复少女粉 mImmersionBar .statusBarColor(R.color.girlColor) .init(); }
这是我们发现播放器横屏时的状态栏问题就解决了。
4.4
华为5.0虚拟导航栏引起的问题处理
满心欢喜,本以为全屏时导航栏问题解决了,可是拿了个华为5.0的手机一测,又出问题了,问题如下图所示,下面会出一大截黑色条:
从github的issue上找到了相似的问题,看到评论中的方法,试了一下管用!
加上这两句问题就解决了。
.keyboardEnable(true)
.navigationBarWithKitkatEnable(false)
【代码下载】:节操播放器在Activity中的使用代码
好了,节操播放器在activity中的使用就先记录到这里,
下次我们说节操播放器在fragment中使用时遇到的各种问题~
积累点滴,做好自己~