package com.imaginato.qravedconsumer.widget.player;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.qraved.app.R;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
public class YoutubePlayerView extends WebView {
private static final String TAG = YoutubePlayerView.class.getSimpleName();
private QualsonBridge bridge = new QualsonBridge();
private YTParams params = new YTParams();
private YouTubeListener youTubeListener;
private String backgroundColor = "#000000";
private STATE mPlayState = STATE.UNSTARTED;
public YoutubePlayerView(Context context) {
super(context);
setWebViewClient(new MyWebViewClient((Activity) context));
}
public YoutubePlayerView(Context context, AttributeSet attrs) {
super(context, attrs);
setWebViewClient(new MyWebViewClient((Activity) context));
}
@SuppressLint("JavascriptInterface")
public void initialize(String videoId, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
WebSettings set = this.getSettings();
set.setJavaScriptEnabled(true);
set.setUseWideViewPort(true);
set.setLoadWithOverviewMode(true);
set.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
set.setCacheMode(WebSettings.LOAD_NO_CACHE);
set.setPluginState(WebSettings.PluginState.ON);
set.setPluginState(WebSettings.PluginState.ON_DEMAND);
set.setAllowContentAccess(true);
set.setAllowFileAccess(true);
if (webChromeClient != null) {
this.setWebChromeClient(webChromeClient);
}
this.mPlayState = STATE.UNSTARTED;
this.youTubeListener = youTubeListener;
this.setLayerType(View.LAYER_TYPE_NONE, null);
this.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
this.addJavascriptInterface(bridge, "QualsonInterface");//注册js代码调用java代码的接口
this.loadDataWithBaseURL("https://www.youtube.com", getVideoHTML(videoId), "text/html", "utf-8", null);
this.setLongClickable(true);
this.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return true;
}
});
}
public void initialize(String videoId, YTParams params, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
if (params != null) {
this.params = params;
}
initialize(videoId, youTubeListener, webChromeClient);
}
public void setWhiteBackgroundColor() {
backgroundColor = "#ffffff";
}
public void setAutoPlayerHeight(Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
this.getLayoutParams().height = (int) (displayMetrics.widthPixels * 0.5625);
}
/**
* 让WebView去执行JS代码javascript:onVideoPause(),来暂停视频
*/
public void pause() {
Log.d(TAG, "pause");
this.loadUrl("javascript:onVideoPause()");
}
/**
* 让WebView去执行JS代码,来停止视频
*/
public void stop(){
Log.d(TAG,"stop");
this.loadUrl("javascript:onVideoStop()");
}
public STATE getPlayerState(){
Log.d(TAG,"getPlayerState");
return mPlayState;
}
public void play() {
Log.d(TAG, "play");
this.loadUrl("javascript:onVideoPlay()");
}
private void notifyStateChange(STATE state){
if(youTubeListener!=null){
youTubeListener.onStateChange(state);
}
this.mPlayState = state;
}
/**
* WEB TO APP Javascript的安卓接口,用于在安卓上部署JS代码,这里是将JS回调到Android中,让JS触发Java代码
* 需要在JS代码合适地方调用这里面的方法,在js中有一个函数如下:
* function onPlayerStateChange(event)
* 和这样一个函数
* function onStateChange(e){
window.QualsonInterface.onStateChange(e);//用于回调java代码
}
并且这个需要在java代码中使用 this.addJavascriptInterface(bridge, "QualsonInterface");来注册
*/
private class QualsonBridge {
@JavascriptInterface
public void onReady(String arg) {
Log.d(TAG, "onReady(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.onReady();
}
}
@JavascriptInterface
public void onStateChange(String arg) {
Log.d(TAG, "onStateChange(" + arg + ")");
if ("UNSTARTED".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.UNSTARTED);
} else if ("ENDED".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.ENDED);
} else if ("PLAYING".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.PLAYING);
} else if ("PAUSED".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.PAUSED);
} else if ("BUFFERING".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.BUFFERING);
} else if ("CUED".equalsIgnoreCase(arg)) {
notifyStateChange(STATE.CUED);
}
}
@JavascriptInterface
public void onPlaybackQualityChange(String arg) {
Log.d(TAG, "onPlaybackQualityChange(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.onPlaybackQualityChange(arg);
}
}
@JavascriptInterface
public void onPlaybackRateChange(String arg) {
Log.d(TAG, "onPlaybackRateChange(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.onPlaybackRateChange(arg);
}
}
@JavascriptInterface
public void onError(String arg) {
Log.e(TAG, "onError(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.onError(arg);
}
}
@JavascriptInterface
public void onApiChange(String arg) {
Log.d(TAG, "onApiChange(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.onApiChange(arg);
}
}
@JavascriptInterface
public void currentSeconds(String seconds) {
if (youTubeListener != null) {
youTubeListener.onCurrentSecond(Double.parseDouble(seconds));
}
}
@JavascriptInterface
public void duration(String seconds) {
if (youTubeListener != null) {
youTubeListener.onDuration(Double.parseDouble(seconds));
}
}
@JavascriptInterface
public void logs(String arg) {
Log.d(TAG, "logs(" + arg + ")");
if (youTubeListener != null) {
youTubeListener.logs(arg);
}
}
}
/**
* NonLeakingWebView
*/
private static Field sConfigCallback;
static {
try {
sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
sConfigCallback.setAccessible(true);
} catch (Exception e) {
// ignored
}
}
public void onDestroy() {
super.onDetachedFromWindow();
// View is now detached, and about to be destroyed
youTubeListener = null;
this.clearCache(true);
this.clearHistory();
try {
if (sConfigCallback != null)
sConfigCallback.set(null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private class MyWebViewClient extends WebViewClient {
protected WeakReference activityRef;
public MyWebViewClient(Activity activity) {
this.activityRef = new WeakReference(activity);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
final Activity activity = activityRef.get();
if (activity != null)
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
} catch (RuntimeException ignored) {
// ignore any url parsing exceptions
}
return true;
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.d(TAG, "onPageFinished()");
}
}
public interface YouTubeListener {
void onReady();//可以显示播放按钮进行播放
void onStateChange(STATE state);//暂停等等状态
void onPlaybackQualityChange(String arg);//清晰度改变
void onPlaybackRateChange(String arg);
void onError(String arg);
void onApiChange(String arg);
void onCurrentSecond(double second);
void onDuration(double duration);
void logs(String log);
}
public enum STATE {
UNSTARTED,
ENDED,
PLAYING,
PAUSED,
BUFFERING,
CUED,
NONE
}
/**
* 自己写一段HTML,并设置好Youtube的视频id,放到WebView中进行显示
* @param videoId
* @return
*/
private String getVideoHTML(String videoId) {
try {
InputStream in = getResources().openRawResource(R.raw.players);
if (in != null) {
InputStreamReader stream = new InputStreamReader(in, "utf-8");
BufferedReader buffer = new BufferedReader(stream);
String read;
StringBuilder sb = new StringBuilder("");
while ((read = buffer.readLine()) != null) {
sb.append(read + "\n");
}
in.close();
String html = sb.toString().replace("[VIDEO_ID]", videoId).replace("[BG_COLOR]", backgroundColor);
html = html.replace("[AUTO_PLAY]", String.valueOf(params.getAutoplay())).replace("[AUTO_HIDE]", String.valueOf(params.getAutohide())).replace("[REL]", String.valueOf(params.getRel())).replace("[SHOW_INFO]", String.valueOf(params.getShowinfo())).replace("[ENABLE_JS_API]", String.valueOf(params.getEnablejsapi())).replace("[DISABLE_KB]", String.valueOf(params.getDisablekb())).replace("[CC_LANG_PREF]", String.valueOf(params.getCc_lang_pref())).replace("[CONTROLS]", String.valueOf(params.getControls())).replace("[FS]", String.valueOf(params.getFs()));
return html;
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
在Fragment中初始化的代码
View youtubeView = LayoutInflater.from(journalActivity).inflate(R.layout.layout_youtube_player, null);
YoutubePlayerView youtubePlayerView = (YoutubePlayerView) youtubeView.findViewById(R.id.youtubePlayerView);
youtubePlayerView.setAutoPlayerHeight(journalActivity);
youtubePlayerView.initialize(videoID, new YoutubePlayerCallBack(youtubePlayerView), mWebChromeClient);
ll_journal.addView(youtubeView,ll_journal.getChildCount()-1);
上面提到的布局文件R.layout.layout_youtube_player如下
android:layout_
android:layout_
android:orientation="vertical">
android:id="@+id/youtubePlayerView"
android:layout_
android:layout_
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp"/>
android:layout_
android:layout_
android:layout_gravity="top"
android:clickable="true"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp">
上面提到的WebChromeClient定义如下,用于控制全屏播放的
private WebChromeClient mWebChromeClient = new WebChromeClient(){
@Override
public View getVideoLoadingProgressView() {
LayoutInflater inflater = LayoutInflater.from(activity);
mVideoProgressView = inflater.inflate(R.layout.video_layout_loading, null);
return mVideoProgressView;
}
@Override
public void onShowCustomView(View view,
WebChromeClient.CustomViewCallback callback) {
// if a view already exists then immediately terminate the new one
if(journalActivity==null){
return;
}
if (mCustomView != null) {
onHideCustomView();
return;
}
// 1. Stash the current state
mCustomView = view;
mOriginalSystemUiVisibility = journalActivity.getWindow().getDecorView().getSystemUiVisibility();
mOriginalOrientation = journalActivity.getRequestedOrientation();
// 2. Stash the custom view callback
mCustomViewCallback = callback;
// 3. Add the custom view to the view hierarchy
FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
decor.addView(mCustomView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if(mVideoFullScreenBack!=null){
mVideoFullScreenBack.setVisibility(View.VISIBLE);
}
// 4. Change the state of the window
activity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
public void onHideCustomView() {
if (journalActivity == null) {
return;
}
// 1. Remove the custom view
FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
decor.removeView(mCustomView);
mCustomView = null;
if(mVideoFullScreenBack!=null){
mVideoFullScreenBack.setVisibility(View.GONE);
}
// 2. Restore the state to it's original form
journalActivity.getWindow().getDecorView()
.setSystemUiVisibility(mOriginalSystemUiVisibility);
journalActivity.setRequestedOrientation(mOriginalOrientation);
// 3. Call the custom view callback
if(mCustomViewCallback!=null){
mCustomViewCallback.onCustomViewHidden();
mCustomViewCallback = null;
}
}
};
上面提到的R.layout.view_layout_loading布局文件如下,仅仅是一个progressBar当占位符用的
android:layout_
android:layout_
android:gravity="center"
android:orientation="vertical">
android:id="@android:id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_
android:layout_
android:layout_gravity="center"/>
从url中抽取VideoId的方法如下
private String parseIDfromVideoUrl(String videoUrl){
int startIndex = videoUrl.indexOf(VIDEO_ID_START);
if(startIndex != -1){
startIndex = startIndex + VIDEO_ID_START.length();
int endIndex = videoUrl.indexOf("?");
if(endIndex == -1){
endIndex = videoUrl.length();
}
if(startIndex < endIndex){
return videoUrl.substring(startIndex,endIndex);
}
}
return "";
}
因为本项目在同一个fragment中放了好多的这样的视频播放控件,所以为了统一他们暂停,销毁操作,这里使用了一个ArrayList进行维护
当切换到其他fragment或者有新的Activity压到上面的时候暂停WebView的播放,fragment总的onPause方法这么写:
@Override
public void onPause() {
if(playerViewList!=null){
for(YoutubePlayerView v : playerViewList){
if(v.getPlayerState() == YoutubePlayerView.STATE.PLAYING ){
v.pause();
}else if(v.getPlayerState() == YoutubePlayerView.STATE.BUFFERING){
v.stop();
}
}
}
super.onPause();
}还需要让fragment在销毁的时候释放WebView的资源如下:
@Override
public void onDestroy() {
super.onDestroy();
if(playerViewList!=null){
for(YoutubePlayerView v : playerViewList){
if(v!=null){
v.onDestroy();
}
}
}
}
在按下返回按钮时关闭全屏显示的代码
@Override
public void onBackPressed() {
boolean isClose = currentJournalFragment.closeFullScreen();
if(isClose){
&nbs
|