Home

smartbeng

做好今天的自己!

Blog About Email Github

2017-04-01
Android 与 H5 视频交互的故事

最近在做一个电影社区讨论平台的项目 , 性质类似于「豆瓣」那种 , APP 足够轻量 , 因为涉及到原生的很少 , 大多是与 H5 交互 , 特此来记录一下最近的点滴。

1. android 播放H5 视频设置全屏

对于android客户端直接访问的 H5 页面来说 , H5 自己则提供了视频全屏播放的属性设置 , 但是光有这点是完全不够的 , 因为我们还得在 android 端完成一系列的处理。

让视频在各个 Android 版本都能够正常播放

在AndroidManifest.xml中声明HardwareAccelerate的标志,一般是添加在Activity的级别上。代码如下:

<activity ... android:hardwareAccelerated="true" >

下面引申一下HardwareAccelerate声明的方式:

(a).如果需要声明整个应用都要加速,则在Application级别下面进行声明:

< application ... android:hardwareAccelerated ="true">

(b).如果需要某个Activity加速,则可以进行下面的声明:

<activity ... android:hardwareAccelerated="true" >

或者在代码里面进行动态的声明:

getWindow.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

(c).如果Application和Activity都声明了HardwareAccelerate,但是由于某些特殊原因,一些View不需要硬件加速,那么在View里面设置:

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

完成声明操作后,基本上WebView就能够很好的支持在页面上播放视频了,下面展示的是在Html5上的Viedeo的声明方法:

<!DOCTYPE html>
<html>
<body>
<video width="305" height="305" controls="controls" preload="none" poster="http://****.png" >
<source src="http://*****.mp4" type="video/mp4">
</video>
</body>
</html>

单单在 H5 端做了处理是不够的 , 以下是支持网页视频全屏播放以及退出的解决方案:

public class WebVideoActivity extends Activity {
private WebView webView;
/** 视频全屏参数 */
protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private View customView;
private FrameLayout fullscreenContainer;
private WebChromeClient.CustomViewCallback customViewCallback;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_xx);
webView = (WebView) findViewById(R.id.xx);
initWebView();
}
@Override
protected void onStop() {
super.onStop();
webView.reload();
}
/** 展示网页界面 **/
  public void initWebView() {
WebChromeClient wvcc = new WebChromeClient();
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setUseWideViewPort(true); // 关键点
webSettings.setAllowFileAccess(true); // 允许访问文件
webSettings.setSupportZoom(true); // 支持缩放
webSettings.setLoadWithOverviewMode(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // 不加载缓存内容
webView.setWebChromeClient(wvcc);
WebViewClient wvc = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
webView.loadUrl(url);
return true;
}
};
webView.setWebViewClient(wvc);
webView.setWebChromeClient(new WebChromeClient() {
/*** 视频播放相关的方法 **/
@Override
public View getVideoLoadingProgressView() {
FrameLayout frameLayout = new FrameLayout(WebVideoActivity.this);
frameLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
return frameLayout;
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
hideCustomView();
}
});
// 加载Web地址
webView.loadUrl(webUrl);
}
/** 视频播放全屏 **/
private void showCustomView(View view, CustomViewCallback callback) {
// if a view already exists then immediately terminate the new one
if (customView != null) {
callback.onCustomViewHidden();
return;
}
WebVideoActivity.this.getWindow().getDecorView();
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
fullscreenContainer = new FullscreenHolder(WebVideoActivity.this);
fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
setStatusBarVisibility(false);
customViewCallback = callback;
}
/** 隐藏视频全屏 */
private void hideCustomView() {
if (customView == null) {
return;
}
setStatusBarVisibility(true);
FrameLayout decor = (FrameLayout) getWindow().getDecorView();
decor.removeView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.onCustomViewHidden();
webView.setVisibility(View.VISIBLE);
}
/** 全屏容器界面 */
static class FullscreenHolder extends FrameLayout {
public FullscreenHolder(Context ctx) {
super(ctx);
setBackgroundColor(ctx.getResources().getColor(android.R.color.black));
}
@Override
public boolean onTouchEvent(MotionEvent evt) {
return true;
}
}
private void setStatusBarVisibility(boolean visible) {
int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
/** 回退键 事件处理 优先级:视频播放全屏-网页回退-关闭页面 */
if (customView != null) {
hideCustomView();
} else if (webView.canGoBack()) {
webView.goBack();
} else {
finish();
}
return true;
default:
return super.onKeyUp(keyCode, event);
}
}
}

在onShowCustomView方法中,将获取到的view放到当前Activity的最上方,在onHideCustomView中,将之前的view隐藏或者删除,将原来被覆盖的webview放回来。

2. android 4.X 不兼容 H5 端视频黑屏问题

Android 4.4 中播放 HTML5 视频的 Bug

应用中使用了 Webview 播放 HTML5 视频,代码如下:

<video width="480" height="280" poster="test.jpg" src="test.mp4" preload="auto"></video>

在4.3版本之前播放正常,新版本中播放时只能听到声音,而画面停留在最初的画面,也就是poster属性中的图片,但不会显示视频动画,只有点击暂停按钮,然后再次点击播放按钮时,视频动画才会显示正常。

网上查阅了很多国外论坛,发现两个类似的反馈,但解决方法不同,其中的一个办法是使用CSS3的-webkit-transform: translate3d(0, 0, 0)属性强制打开3D渲染,可以使视频播放正常,但这种方法会导致初始的poster图片只显示一下,然后一闪而过停留在视频播放界面,点击播放时,视频播放倒是一切正常。

问题可能是由于preload属性引起,只有设定preload=”none”才可以显示视频,官方已经确认了这个bug,并表示已经解决,下个版本会更新。但经我测试,单独设置preload=”none”并不会完全解决此问题,视频仍旧只有声音,画面停留在初始界面。

经我多次试验,将上述两种方法结合起来,最终解决了这个问题,代码如下:

<video width="480" height="280" poster="test.jpg" src="test.mp4" preload="none" ></video>

这个Bug产生的原因我认为是在视频开始播放时没有启动3D加速,导致原始的poster图片未被刷新到视频画面。设置preload=”none”禁止了视频的自动载入,确保了poster画面被正确载入;同时在点击播放视频时,-webkit-transform: translate3d(0, 0, 0)确保打开了3D加速,自动刷新了poster的原始画面。

当然以上原因都为猜测,解决办法也是临时的,因为这不符合HTML5标签的原始定义,未来还要看官方的下一个Android版本是否解决掉这个Bug。

比较稳定的版本开源video

https://github.com/videojs/video.js/releases

3. 加载网页端视频时的布局问题

比如我在视频上加入改变声音时的图标 , 或者你有自己的需求要加入 TextView 之类的 , 上代码:

private ImageView showhide; //返回键
/**
* 设置返回键
*/
showhide.setBackgroundResource(R.drawable.ic_back);
LayoutParams params = new FrameLayout.LayoutParams(80, 70);
showhide.setLayoutParams(params);
showhide.setVisibility(View.GONE); //设置不可见
video.addView(showhide); //添加视图到布局

也许大家在看了最后一行代码后会觉得我的布局是 VideoView , 告诉你 , 并不是!

<FrameLayout
android:id="@+id/video_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone" >
</FrameLayout>

我只是加了个帧布局而已 , 因为我们的 APP 将轻量发挥到了极致 , 况且这里完全一个帧布局就可以搞定 , H5 已经完成了对视频的绝对控制权 , 再者这里需要加布局的话直接加进去是不行的 , 还是得在动态的控制 , 所以一般是实现起来都是以上的方式!

4. 横屏下上下滑动改变声音以及亮度

Android 4.4 低版本屏幕滑动事件问题

关于这个你以为我要长篇大论了吗?不不不 , 这个问题的解决方案很简单 , 但却让我一通好找。

在这里我没有去重写父类的 onScoll 方法 , 而是选择重写 onTouchEvent 方法 , 上代码:

@Override
public boolean onTouch(View v, MotionEvent event) {
v.setClickable(true); //处理4.x系统下不能进入滑动事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}

没错 , 第三行就是我的解决方案 , 关于低版本这里需要代码设置事件!

关于滑动屏幕控制声音亮度我也就不多说了 , 这种功能实现起来很简单。我参考的如下方法:

滑动控制声音亮度 + 全屏播放

5. H5 视频自带横屏切换后的屏幕自动旋转

说到这里我就要嘲笑一下自己了 , 我本来以为 H5 页面的视屏全屏我没有办法让她反向旋转 , 唯一可行的办法可能就是通过监测手机旋转角度 , 去动态控制布局文件的翻转角度 , 我真的这么干了…..

等我发觉不必这么麻烦的时候 , 我试了一下我曾经几乎写到吐的方式 , 算了 , 上代码吧!也不怕被人笑话了。

我们需要在处理整个事件的方法体内部加上这段初始化角度监听

// 进入全屏的时候
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
AlbumOrientationEventListener mAlbumOrientationEventListener = new AlbumOrientationEventListener(act, SensorManager.SENSOR_DELAY_NORMAL);
if (mAlbumOrientationEventListener.canDetectOrientation()) {
mAlbumOrientationEventListener.enable();
} else {
Log.d("chengcj1", "Can't Detect Orientation");
}

比如我则是在事件进入全屏处理的时候加入了引入

然后通过内部类的方式实现整个功能

/**
* 用户手机横竖屏监听类
* @author MaiBenBen
*
*/
class AlbumOrientationEventListener extends OrientationEventListener {
public AlbumOrientationEventListener(Context context) {
super(context);
}
public AlbumOrientationEventListener(Context context, int rate) {
super(context, rate);
}
@Override
public void onOrientationChanged(int orientation) {
//Log.e("屏幕旋转度数","" +orientation);
int screenOrientation = act.getResources().getConfiguration().orientation;
/*if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
return;
} */
if (((orientation >= 0) && (orientation < 45)) || (orientation > 315)) {//设置竖屏
if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
} else if (orientation > 225 && orientation < 315) { //设置横屏
if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
} else if (orientation > 45 && orientation < 135) {// 设置反向横屏
if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
act.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
}
}
}
}

其实就是这个万年不变的老方式了!所以我们在处理程序这件事上 , 应该是尽可能的想出简单的方式来实现功能 , 否则就跟我一样浪费这么多时间却做得是无用功!

微信公众号「smartbeng」,吐血版珍藏持续更新,一个会爱上的公众号。

webwxgetmsgimg.jpg


smartbeng

scribble

Blog About Email Github