diff --git a/.gitignore b/.gitignore index 124894a..531f93c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /.idea/ /app/src/test/java/com/uiui/videoplayer/ /app/src/androidTest/java/com/uiui/videoplayer/ +/library/build/ diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..f0ad8f5 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 30 + buildToolsVersion = '30.0.3' + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 30 + versionCode 106 + versionName "7.6.0" + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + lintOptions { + abortOnError false + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "androidx.core:core-ktx:1.3.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +} + +//apply from: '../gradle/build_upload.gradle' \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b20611d --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/java/cn/jzvd/JZDataSource.java b/library/src/main/java/cn/jzvd/JZDataSource.java new file mode 100644 index 0000000..9fcad12 --- /dev/null +++ b/library/src/main/java/cn/jzvd/JZDataSource.java @@ -0,0 +1,88 @@ +package cn.jzvd; + +import java.util.HashMap; +import java.util.LinkedHashMap; + +public class JZDataSource { + + public static final String URL_KEY_DEFAULT = "URL_KEY_DEFAULT"; + + public int currentUrlIndex; + public LinkedHashMap urlsMap = new LinkedHashMap(); + public String title = ""; + public HashMap headerMap = new HashMap<>(); + public boolean looping = false; + public Object[] objects; + + public JZDataSource(String url) { + urlsMap.put(URL_KEY_DEFAULT, url); + currentUrlIndex = 0; + } + + public JZDataSource(String url, String title) { + urlsMap.put(URL_KEY_DEFAULT, url); + this.title = title; + currentUrlIndex = 0; + } + + public JZDataSource(Object url) { + urlsMap.put(URL_KEY_DEFAULT, url); + currentUrlIndex = 0; + } + + public JZDataSource(LinkedHashMap urlsMap) { + this.urlsMap.clear(); + this.urlsMap.putAll(urlsMap); + currentUrlIndex = 0; + } + + public JZDataSource(LinkedHashMap urlsMap, String title) { + this.urlsMap.clear(); + this.urlsMap.putAll(urlsMap); + this.title = title; + currentUrlIndex = 0; + } + + public Object getCurrentUrl() { + return getValueFromLinkedMap(currentUrlIndex); + } + + public Object getCurrentKey() { + return getKeyFromDataSource(currentUrlIndex); + } + + public String getKeyFromDataSource(int index) { + int currentIndex = 0; + for (Object key : urlsMap.keySet()) { + if (currentIndex == index) { + return key.toString(); + } + currentIndex++; + } + return null; + } + + public Object getValueFromLinkedMap(int index) { + int currentIndex = 0; + for (Object key : urlsMap.keySet()) { + if (currentIndex == index) { + return urlsMap.get(key); + } + currentIndex++; + } + return null; + } + + public boolean containsTheUrl(Object object) { + if (object != null) { + return urlsMap.containsValue(object); + } + return false; + } + + public JZDataSource cloneMe() { + LinkedHashMap map = new LinkedHashMap(); + map.putAll(urlsMap); + return new JZDataSource(map, title); + } +} diff --git a/library/src/main/java/cn/jzvd/JZMediaInterface.java b/library/src/main/java/cn/jzvd/JZMediaInterface.java new file mode 100644 index 0000000..a8202b2 --- /dev/null +++ b/library/src/main/java/cn/jzvd/JZMediaInterface.java @@ -0,0 +1,47 @@ +package cn.jzvd; + +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.Surface; +import android.view.TextureView; + +/** + * Created by Nathen on 2017/11/7. + * 自定义播放器 + */ +public abstract class JZMediaInterface implements TextureView.SurfaceTextureListener { + + public static SurfaceTexture SAVED_SURFACE; + public HandlerThread mMediaHandlerThread; + public Handler mMediaHandler; + public Handler handler; + public Jzvd jzvd; + + + public JZMediaInterface(Jzvd jzvd) { + this.jzvd = jzvd; + } + + public abstract void start(); + + public abstract void prepare(); + + public abstract void pause(); + + public abstract boolean isPlaying(); + + public abstract void seekTo(long time); + + public abstract void release(); + + public abstract long getCurrentPosition(); + + public abstract long getDuration(); + + public abstract void setVolume(float leftVolume, float rightVolume); + + public abstract void setSpeed(float speed); + + public abstract void setSurface(Surface surface); +} diff --git a/library/src/main/java/cn/jzvd/JZMediaSystem.java b/library/src/main/java/cn/jzvd/JZMediaSystem.java new file mode 100644 index 0000000..565e44a --- /dev/null +++ b/library/src/main/java/cn/jzvd/JZMediaSystem.java @@ -0,0 +1,203 @@ +package cn.jzvd; + +import android.graphics.SurfaceTexture; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.PlaybackParams; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.Surface; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * Created by Nathen on 2017/11/8. + * 实现系统的播放引擎 + */ +public class JZMediaSystem extends JZMediaInterface implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, MediaPlayer.OnVideoSizeChangedListener { + + public MediaPlayer mediaPlayer; + + public JZMediaSystem(Jzvd jzvd) { + super(jzvd); + } + + @Override + public void prepare() { + release(); + mMediaHandlerThread = new HandlerThread("JZVD"); + mMediaHandlerThread.start(); + mMediaHandler = new Handler(mMediaHandlerThread.getLooper());//主线程还是非主线程,就在这里 + handler = new Handler(); + + mMediaHandler.post(() -> { + try { + mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setLooping(jzvd.jzDataSource.looping); + mediaPlayer.setOnPreparedListener(JZMediaSystem.this); + mediaPlayer.setOnCompletionListener(JZMediaSystem.this); + mediaPlayer.setOnBufferingUpdateListener(JZMediaSystem.this); + mediaPlayer.setScreenOnWhilePlaying(true); + mediaPlayer.setOnSeekCompleteListener(JZMediaSystem.this); + mediaPlayer.setOnErrorListener(JZMediaSystem.this); + mediaPlayer.setOnInfoListener(JZMediaSystem.this); + mediaPlayer.setOnVideoSizeChangedListener(JZMediaSystem.this); + Class clazz = MediaPlayer.class; + //如果不用反射,没有url和header参数的setDataSource函数 + Method method = clazz.getDeclaredMethod("setDataSource", String.class, Map.class); + method.invoke(mediaPlayer, jzvd.jzDataSource.getCurrentUrl().toString(), jzvd.jzDataSource.headerMap); + mediaPlayer.prepareAsync(); + mediaPlayer.setSurface(new Surface(SAVED_SURFACE)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + @Override + public void start() { + mMediaHandler.post(() -> mediaPlayer.start()); + } + + @Override + public void pause() { + mMediaHandler.post(() -> mediaPlayer.pause()); + } + + @Override + public boolean isPlaying() { + return mediaPlayer.isPlaying(); + } + + @Override + public void seekTo(long time) { + mMediaHandler.post(() -> { + try { + mediaPlayer.seekTo((int) time); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + }); + } + + @Override + public void release() {//not perfect change you later + if (mMediaHandler != null && mMediaHandlerThread != null && mediaPlayer != null) {//不知道有没有妖孽 + HandlerThread tmpHandlerThread = mMediaHandlerThread; + MediaPlayer tmpMediaPlayer = mediaPlayer; + JZMediaInterface.SAVED_SURFACE = null; + + mMediaHandler.post(() -> { + tmpMediaPlayer.setSurface(null); + tmpMediaPlayer.release(); + tmpHandlerThread.quit(); + }); + mediaPlayer = null; + } + } + + //TODO 测试这种问题是否在threadHandler中是否正常,所有的操作mediaplayer是否不需要thread,挨个测试,是否有问题 + @Override + public long getCurrentPosition() { + if (mediaPlayer != null) { + return mediaPlayer.getCurrentPosition(); + } else { + return 0; + } + } + + @Override + public long getDuration() { + if (mediaPlayer != null) { + return mediaPlayer.getDuration(); + } else { + return 0; + } + } + + @Override + public void setVolume(float leftVolume, float rightVolume) { + if (mMediaHandler == null) return; + mMediaHandler.post(() -> { + if (mediaPlayer != null) mediaPlayer.setVolume(leftVolume, rightVolume); + }); + } + + @Override + public void setSpeed(float speed) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + PlaybackParams pp = mediaPlayer.getPlaybackParams(); + pp.setSpeed(speed); + mediaPlayer.setPlaybackParams(pp); + } + } + + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + handler.post(() -> jzvd.onPrepared());//如果是mp3音频,走这里 + } + + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + handler.post(() -> jzvd.onCompletion()); + } + + @Override + public void onBufferingUpdate(MediaPlayer mediaPlayer, final int percent) { + handler.post(() -> jzvd.setBufferProgress(percent)); + } + + @Override + public void onSeekComplete(MediaPlayer mediaPlayer) { + handler.post(() -> jzvd.onSeekComplete()); + } + + @Override + public boolean onError(MediaPlayer mediaPlayer, final int what, final int extra) { + handler.post(() -> jzvd.onError(what, extra)); + return true; + } + + @Override + public boolean onInfo(MediaPlayer mediaPlayer, final int what, final int extra) { + handler.post(() -> jzvd.onInfo(what, extra)); + return false; + } + + @Override + public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) { + handler.post(() -> jzvd.onVideoSizeChanged(width, height)); + } + + @Override + public void setSurface(Surface surface) { + mediaPlayer.setSurface(surface); + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + if (SAVED_SURFACE == null) { + SAVED_SURFACE = surface; + prepare(); + } else { + jzvd.textureView.setSurfaceTexture(SAVED_SURFACE); + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + + } +} diff --git a/library/src/main/java/cn/jzvd/JZTextureView.java b/library/src/main/java/cn/jzvd/JZTextureView.java new file mode 100644 index 0000000..0e850c1 --- /dev/null +++ b/library/src/main/java/cn/jzvd/JZTextureView.java @@ -0,0 +1,161 @@ +package cn.jzvd; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.TextureView; +import android.view.View; + +/** + *

参照Android系统的VideoView的onMeasure方法 + *
注意!relativelayout中无法全屏,要嵌套一个linearlayout

+ *

Referring Android system Video View of onMeasure method + *
NOTE! Can not fullscreen relativelayout, to nest a linearlayout

+ * Created by Nathen + * On 2016/06/02 00:01 + */ +public class JZTextureView extends TextureView { + protected static final String TAG = "JZResizeTextureView"; + + public int currentVideoWidth = 0; + public int currentVideoHeight = 0; + + public JZTextureView(Context context) { + super(context); + currentVideoWidth = 0; + currentVideoHeight = 0; + } + + public JZTextureView(Context context, AttributeSet attrs) { + super(context, attrs); + currentVideoWidth = 0; + currentVideoHeight = 0; + } + + public void setVideoSize(int currentVideoWidth, int currentVideoHeight) { + if (this.currentVideoWidth != currentVideoWidth || this.currentVideoHeight != currentVideoHeight) { + this.currentVideoWidth = currentVideoWidth; + this.currentVideoHeight = currentVideoHeight; + requestLayout(); + } + } + + @Override + public void setRotation(float rotation) { + if (rotation != getRotation()) { + super.setRotation(rotation); + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Log.i(TAG, "onMeasure " + " [" + this.hashCode() + "] "); + int viewRotation = (int) getRotation(); + int videoWidth = currentVideoWidth; + int videoHeight = currentVideoHeight; + + + int parentHeight = ((View) getParent()).getMeasuredHeight(); + int parentWidth = ((View) getParent()).getMeasuredWidth(); + if (parentWidth != 0 && parentHeight != 0 && videoWidth != 0 && videoHeight != 0) { + if (Jzvd.VIDEO_IMAGE_DISPLAY_TYPE == Jzvd.VIDEO_IMAGE_DISPLAY_TYPE_FILL_PARENT) { + if (viewRotation == 90 || viewRotation == 270) { + int tempSize = parentWidth; + parentWidth = parentHeight; + parentHeight = tempSize; + } + /**强制充满**/ + videoHeight = videoWidth * parentHeight / parentWidth; + } + } + + // 如果判断成立,则说明显示的TextureView和本身的位置是有90度的旋转的,所以需要交换宽高参数。 + if (viewRotation == 90 || viewRotation == 270) { + int tempMeasureSpec = widthMeasureSpec; + widthMeasureSpec = heightMeasureSpec; + heightMeasureSpec = tempMeasureSpec; + } + + int width = getDefaultSize(videoWidth, widthMeasureSpec); + int height = getDefaultSize(videoHeight, heightMeasureSpec); + if (videoWidth > 0 && videoHeight > 0) { + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + Log.i(TAG, "widthMeasureSpec [" + MeasureSpec.toString(widthMeasureSpec) + "]"); + Log.i(TAG, "heightMeasureSpec [" + MeasureSpec.toString(heightMeasureSpec) + "]"); + + if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { + // the size is fixed + width = widthSpecSize; + height = heightSpecSize; + // for compatibility, we adjust size based on aspect ratio + if (videoWidth * height < width * videoHeight) { + width = height * videoWidth / videoHeight; + } else if (videoWidth * height > width * videoHeight) { + height = width * videoHeight / videoWidth; + } + } else if (widthSpecMode == MeasureSpec.EXACTLY) { + // only the width is fixed, adjust the height to match aspect ratio if possible + width = widthSpecSize; + height = width * videoHeight / videoWidth; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // couldn't match aspect ratio within the constraints + height = heightSpecSize; + width = height * videoWidth / videoHeight; + } + } else if (heightSpecMode == MeasureSpec.EXACTLY) { + // only the height is fixed, adjust the width to match aspect ratio if possible + height = heightSpecSize; + width = height * videoWidth / videoHeight; + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // couldn't match aspect ratio within the constraints + width = widthSpecSize; + height = width * videoHeight / videoWidth; + } + } else { + // neither the width nor the height are fixed, try to use actual video size + width = videoWidth; + height = videoHeight; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // too tall, decrease both width and height + height = heightSpecSize; + width = height * videoWidth / videoHeight; + } + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // too wide, decrease both width and height + width = widthSpecSize; + height = width * videoHeight / videoWidth; + } + } + } else { + // no size yet, just adopt the given spec sizes + } + if (parentWidth != 0 && parentHeight != 0 && videoWidth != 0 && videoHeight != 0) { + if (Jzvd.VIDEO_IMAGE_DISPLAY_TYPE == Jzvd.VIDEO_IMAGE_DISPLAY_TYPE_ORIGINAL) { + /**原图**/ + height = videoHeight; + width = videoWidth; + } else if (Jzvd.VIDEO_IMAGE_DISPLAY_TYPE == Jzvd.VIDEO_IMAGE_DISPLAY_TYPE_FILL_SCROP) { + if (viewRotation == 90 || viewRotation == 270) { + int tempSize = parentWidth; + parentWidth = parentHeight; + parentHeight = tempSize; + } + /**充满剪切**/ + if (((double) videoHeight / videoWidth) > ((double) parentHeight / parentWidth)) { + height = (int) (((double) parentWidth / (double) width * (double) height)); + width = parentWidth; + } else if (((double) videoHeight / videoWidth) < ((double) parentHeight / parentWidth)) { + width = (int) (((double) parentHeight / (double) height * (double) width)); + height = parentHeight; + } + } + } + setMeasuredDimension(width, height); + } +} diff --git a/library/src/main/java/cn/jzvd/JZUtils.java b/library/src/main/java/cn/jzvd/JZUtils.java new file mode 100644 index 0000000..a14f2a8 --- /dev/null +++ b/library/src/main/java/cn/jzvd/JZUtils.java @@ -0,0 +1,199 @@ +package cn.jzvd; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; + +import java.util.Formatter; +import java.util.Locale; + +/** + * Created by Nathen + * On 2016/02/21 12:25 + */ +public class JZUtils { + public static final String TAG = "JZVD"; + public static int SYSTEM_UI = 0; + + public static String stringForTime(long timeMs) { + if (timeMs <= 0 || timeMs >= 24 * 60 * 60 * 1000) { + return "00:00"; + } + long totalSeconds = timeMs / 1000; + int seconds = (int) (totalSeconds % 60); + int minutes = (int) ((totalSeconds / 60) % 60); + int hours = (int) (totalSeconds / 3600); + StringBuilder stringBuilder = new StringBuilder(); + Formatter mFormatter = new Formatter(stringBuilder, Locale.getDefault()); + if (hours > 0) { + return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); + } else { + return mFormatter.format("%02d:%02d", minutes, seconds).toString(); + } + } + + /** + * This method requires the caller to hold the permission ACCESS_NETWORK_STATE. + * + * @param context context + * @return if wifi is connected,return true + */ + public static boolean isWifiConnected(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + } + + /** + * Get activity from context object + * + * @param context context + * @return object of Activity or null if it is not Activity + */ + public static Activity scanForActivity(Context context) { + if (context == null) return null; + + if (context instanceof Activity) { + return (Activity) context; + } else if (context instanceof ContextWrapper) { + return scanForActivity(((ContextWrapper) context).getBaseContext()); + } + + return null; + } + + public static void setRequestedOrientation(Context context, int orientation) { + if (JZUtils.scanForActivity(context) != null) { + JZUtils.scanForActivity(context).setRequestedOrientation( + orientation); + } else { + JZUtils.scanForActivity(context).setRequestedOrientation( + orientation); + } + } + + public static Window getWindow(Context context) { + if (JZUtils.scanForActivity(context) != null) { + return JZUtils.scanForActivity(context).getWindow(); + } else { + return JZUtils.scanForActivity(context).getWindow(); + } + } + + public static int dip2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + public static void saveProgress(Context context, Object url, long progress) { + if (!Jzvd.SAVE_PROGRESS) return; + Log.i(TAG, "saveProgress: " + progress); + if (progress < 5000) { + progress = 0; + } + SharedPreferences spn = context.getSharedPreferences("JZVD_PROGRESS", + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = spn.edit(); + editor.putLong("newVersion:" + url.toString(), progress).apply(); + } + + public static long getSavedProgress(Context context, Object url) { + if (!Jzvd.SAVE_PROGRESS) return 0; + SharedPreferences spn = context.getSharedPreferences("JZVD_PROGRESS", + Context.MODE_PRIVATE); + return spn.getLong("newVersion:" + url.toString(), 0); + } + + /** + * if url == null, clear all progress + * + * @param context context + * @param url if url!=null clear this url progress + */ + public static void clearSavedProgress(Context context, Object url) { + if (url == null) { + SharedPreferences spn = context.getSharedPreferences("JZVD_PROGRESS", + Context.MODE_PRIVATE); + spn.edit().clear().apply(); + } else { + SharedPreferences spn = context.getSharedPreferences("JZVD_PROGRESS", + Context.MODE_PRIVATE); + spn.edit().putLong("newVersion:" + url.toString(), 0).apply(); + } + } + + @SuppressLint("RestrictedApi") + public static void showStatusBar(Context context) { + if (Jzvd.TOOL_BAR_EXIST) { + JZUtils.getWindow(context).clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + //如果是沉浸式的,全屏前就没有状态栏 + @SuppressLint("RestrictedApi") + public static void hideStatusBar(Context context) { + if (Jzvd.TOOL_BAR_EXIST) { + JZUtils.getWindow(context).setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + @SuppressLint("NewApi") + public static void hideSystemUI(Context context) { + int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + ; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + } + SYSTEM_UI = JZUtils.getWindow(context).getDecorView().getSystemUiVisibility(); + JZUtils.getWindow(context).getDecorView().setSystemUiVisibility(uiOptions); + + } + + @SuppressLint("NewApi") + public static void showSystemUI(Context context) { + int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; + JZUtils.getWindow(context).getDecorView().setSystemUiVisibility(SYSTEM_UI); + } + + //获取状态栏的高度 + public static int getStatusBarHeight(Context context) { + Resources resources = context.getResources(); + int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); + int height = resources.getDimensionPixelSize(resourceId); + return height; + } + + //获取NavigationBar的高度 + public static int getNavigationBarHeight(Context context) { + boolean var1 = ViewConfiguration.get(context).hasPermanentMenuKey(); + int var2; + return (var2 = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android")) > 0 && !var1 ? context.getResources().getDimensionPixelSize(var2) : 0; + } + + //获取屏幕的宽度 + public static int getScreenWidth(Context context) { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + return dm.widthPixels; + } + + //获取屏幕的高度 + public static int getScreenHeight(Context context) { + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + return dm.heightPixels; + } + +} diff --git a/library/src/main/java/cn/jzvd/Jzvd.java b/library/src/main/java/cn/jzvd/Jzvd.java new file mode 100644 index 0000000..2b7ad44 --- /dev/null +++ b/library/src/main/java/cn/jzvd/Jzvd.java @@ -0,0 +1,1135 @@ +package cn.jzvd; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.provider.Settings; +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 android.widget.Toast; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +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 SCREEN_TINY = 2; + 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_ADAPTER = 0;//DEFAULT + 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 final int THRESHOLD = 80; + public static Jzvd CURRENT_JZVD; + public static LinkedList CONTAINER_LIST = new LinkedList<>(); + public static boolean TOOL_BAR_EXIST = true; + public static int FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + public static int NORMAL_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + public static boolean SAVE_PROGRESS = true; + public static boolean WIFI_TIP_DIALOG_SHOWED = false; + public static int VIDEO_IMAGE_DISPLAY_TYPE = 0; + public static long lastAutoFullscreenTime = 0; + public static int ON_PLAY_PAUSE_TMP_STATE = 0;//这个考虑不放到库里,去自定义 + public static int backUpBufferState = -1; + public static float PROGRESS_DRAG_RATE = 1f;//进度条滑动阻尼系数 越大播放进度条滑动越慢 + public static AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {//是否新建个class,代码更规矩,并且变量的位置也很尴尬 + @Override + public void onAudioFocusChange(int focusChange) { + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + break; + case AudioManager.AUDIOFOCUS_LOSS: + releaseAllVideos(); + Log.d(TAG, "AUDIOFOCUS_LOSS [" + this.hashCode() + "]"); + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + try { + Jzvd player = CURRENT_JZVD; + if (player != null && player.state == Jzvd.STATE_PLAYING) { + player.startButton.performClick(); + } + } catch (IllegalStateException e) { + e.printStackTrace(); + } + Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT [" + this.hashCode() + "]"); + break; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + break; + } + } + }; + 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 positionInList = -1;//很想干掉它 + 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 AudioManager mAudioManager; + 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 int mGestureDownVolume; + protected float mGestureDownBrightness; + protected long mSeekTimePosition; + protected Context jzvdContext; + protected long mCurrentPosition; + /** + * 如果不在列表中可以不加block + */ + protected ViewGroup.LayoutParams blockLayoutParams; + protected int blockIndex; + protected int blockWidth; + protected int blockHeight; + + 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) { + 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 startFullscreenDirectly(Context context, Class _class, String url, String title) { + startFullscreenDirectly(context, _class, new JZDataSource(url, title)); + } + + public static void startFullscreenDirectly(Context context, Class _class, JZDataSource jzDataSource) { + JZUtils.hideStatusBar(context); + JZUtils.setRequestedOrientation(context, FULLSCREEN_ORIENTATION); + JZUtils.hideSystemUI(context); + + ViewGroup vp = (ViewGroup) JZUtils.scanForActivity(context).getWindow().getDecorView(); + try { + Constructor constructor = _class.getConstructor(Context.class); + final Jzvd jzvd = constructor.newInstance(context); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + vp.addView(jzvd, lp); + jzvd.setUp(jzDataSource, JzvdStd.SCREEN_FULLSCREEN); + jzvd.startVideo(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void releaseAllVideos() { + Log.d(TAG, "releaseAllVideos"); + if (CURRENT_JZVD != null) { + CURRENT_JZVD.reset(); + CURRENT_JZVD = null; + } + } + + public static boolean backPress() { + Log.i(TAG, "backPress"); + if (CONTAINER_LIST.size() != 0 && CURRENT_JZVD != null) {//判断条件,因为当前所有goBack都是回到普通窗口 + CURRENT_JZVD.gotoNormalScreen(); + return true; + } else if (CONTAINER_LIST.size() == 0 && CURRENT_JZVD != null && CURRENT_JZVD.screen != SCREEN_NORMAL) {//退出直接进入的全屏 + CURRENT_JZVD.clearFloatScreen(); + 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; + } + + 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) { + 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) { + //quit fullscreen + backPress(); + } else { + Log.d(TAG, "toFullscreenActivity [" + this.hashCode() + "] "); + gotoFullscreen(); + } + } + + protected void clickStart() { + Log.i(TAG, "onClick start [" + this.hashCode() + "] "); + if (jzDataSource == null || jzDataSource.urlsMap.isEmpty() || jzDataSource.getCurrentUrl() == null) { + Toast.makeText(getContext(), getResources().getString(R.string.no_url), Toast.LENGTH_SHORT).show(); + return; + } + if (state == STATE_NORMAL) { + if (!jzDataSource.getCurrentUrl().toString().startsWith("file") && ! + jzDataSource.getCurrentUrl().toString().startsWith("/") && + !JZUtils.isWifiConnected(getContext()) && !WIFI_TIP_DIALOG_SHOWED) {//这个可以放到std中 + showWifiDialog(); + return; + } + 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; + dismissProgressDialog(); + dismissVolumeDialog(); + dismissBrightnessDialog(); + 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; + float absDeltaX = Math.abs(deltaX); + float absDeltaY = Math.abs(deltaY); + if (screen == SCREEN_FULLSCREEN) { + //拖动的是NavigationBar和状态栏 + if (mDownX > JZUtils.getScreenWidth(getContext()) || mDownY < JZUtils.getStatusBarHeight(getContext())) { + return; + } + if (!mChangePosition && !mChangeVolume && !mChangeBrightness) { + if (absDeltaX > THRESHOLD || absDeltaY > THRESHOLD) { + cancelProgressTimer(); + if (absDeltaX >= THRESHOLD) { + // 全屏模式下的CURRENT_STATE_ERROR状态下,不响应进度拖动事件. + // 否则会因为mediaplayer的状态非法导致App Crash + if (state != STATE_ERROR) { + mChangePosition = true; + mGestureDownPosition = getCurrentPositionWhenPlaying(); + } + } else { + //如果y轴滑动距离超过设置的处理范围,那么进行滑动事件处理 + if (mDownX < mScreenHeight * 0.5f) {//左侧改变亮度 + mChangeBrightness = true; + WindowManager.LayoutParams lp = JZUtils.getWindow(getContext()).getAttributes(); + if (lp.screenBrightness < 0) { + try { + mGestureDownBrightness = Settings.System.getInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); + Log.i(TAG, "current system brightness: " + mGestureDownBrightness); + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + } else { + mGestureDownBrightness = lp.screenBrightness * 255; + Log.i(TAG, "current activity brightness: " + mGestureDownBrightness); + } + } else {//右侧改变声音 + mChangeVolume = true; + mGestureDownVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + } + } + } + } + } + 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) { + deltaY = -deltaY; + int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int deltaV = (int) (max * deltaY * 3 / mScreenHeight); + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mGestureDownVolume + deltaV, 0); + //dialog中显示百分比 + int volumePercent = (int) (mGestureDownVolume * 100 / max + deltaY * 3 * 100 / mScreenHeight); + showVolumeDialog(-deltaY, volumePercent); + } + + if (mChangeBrightness) { + deltaY = -deltaY; + int deltaV = (int) (255 * deltaY * 3 / mScreenHeight); + WindowManager.LayoutParams params = JZUtils.getWindow(getContext()).getAttributes(); + if (((mGestureDownBrightness + deltaV) / 255) >= 1) {//这和声音有区别,必须自己过滤一下负值 + params.screenBrightness = 1; + } else if (((mGestureDownBrightness + deltaV) / 255) <= 0) { + params.screenBrightness = 0.01f; + } else { + params.screenBrightness = (mGestureDownBrightness + deltaV) / 255; + } + JZUtils.getWindow(getContext()).setAttributes(params); + //dialog中显示百分比 + int brightnessPercent = (int) (mGestureDownBrightness * 100 / 255 + deltaY * 3 * 100 / mScreenHeight); + showBrightnessDialog(brightnessPercent); +// mDownY = y; + } + } + + 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) {//如果是准备完成视频后第一次播放,先判断是否需要跳转进度。 + 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(); + } + + public void onStateError() { + Log.i(TAG, "onStateError " + " [" + this.hashCode() + "] "); + state = STATE_ERROR; + cancelProgressTimer(); + } + + public void onStateAutoComplete() { + Log.i(TAG, "onStateAutoComplete " + " [" + this.hashCode() + "] "); + state = STATE_AUTO_COMPLETE; + cancelProgressTimer(); + progressBar.setProgress(100); + currentTimeTextView.setText(totalTimeTextView.getText()); + } + + public void onInfo(int what, int extra) { + Log.d(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(); + dismissBrightnessDialog(); + dismissProgressDialog(); + dismissVolumeDialog(); + onStateAutoComplete(); + mediaInterface.release(); + JZUtils.scanForActivity(getContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + JZUtils.saveProgress(getContext(), jzDataSource.getCurrentUrl(), 0); + + if (screen == SCREEN_FULLSCREEN) { + if (CONTAINER_LIST.size() == 0) { + clearFloatScreen();//直接进入全屏 + } else { + gotoNormalCompletion(); + } + } + } + + public void gotoNormalCompletion() { + gobakFullscreenTime = System.currentTimeMillis();//退出全屏 + ViewGroup vg = (ViewGroup) (JZUtils.scanForActivity(jzvdContext)).getWindow().getDecorView(); + vg.removeView(this); + textureViewContainer.removeView(textureView); + CONTAINER_LIST.getLast().removeViewAt(blockIndex);//remove block + CONTAINER_LIST.getLast().addView(this, blockIndex, blockLayoutParams); + CONTAINER_LIST.pop(); + setScreenNormal(); + JZUtils.showStatusBar(jzvdContext); + JZUtils.setRequestedOrientation(jzvdContext, NORMAL_ORIENTATION); + JZUtils.showSystemUI(jzvdContext); + } + + /** + * 多数表现为中断当前播放 + */ + 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(); + dismissBrightnessDialog(); + dismissProgressDialog(); + dismissVolumeDialog(); + onStateNormal(); + textureViewContainer.removeAllViews(); + + AudioManager mAudioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + mAudioManager.abandonAudioFocus(onAudioFocusChangeListener); + JZUtils.scanForActivity(getContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (mediaInterface != null) mediaInterface.release(); + } + + /** + * 里面的的onState...()其实就是setState...(),因为要可以被复写,所以参考Activity的onCreate(),onState..()的方式看着舒服一些,老铁们有何高见。 + * + * @param state stateId + */ + public void setState(int 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; + case SCREEN_TINY: + setScreenTiny(); + break; + } + } + + public void startVideo() { + Log.d(TAG, "startVideo [" + this.hashCode() + "] "); + setCurrentJzvd(this); + try { + Constructor constructor = mediaInterfaceClass.getConstructor(Jzvd.class); + this.mediaInterface = constructor.newInstance(this); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + addTextureView(); + mAudioManager = (AudioManager) getApplicationContext().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); + + onStatePreparing(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (screen == SCREEN_FULLSCREEN || screen == SCREEN_TINY) { + 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); + + FrameLayout.LayoutParams layoutParams = + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER); + textureViewContainer.addView(textureView, layoutParams); + } + + public void clearFloatScreen() { + JZUtils.showStatusBar(getContext()); + JZUtils.setRequestedOrientation(getContext(), NORMAL_ORIENTATION); + JZUtils.showSystemUI(getContext()); + + ViewGroup vg = (ViewGroup) (JZUtils.scanForActivity(getContext())).getWindow().getDecorView(); + vg.removeView(this); + if (mediaInterface != null) mediaInterface.release(); + CURRENT_JZVD = null; + } + + public void onVideoSizeChanged(int width, int height) { + Log.i(TAG, "onVideoSizeChanged " + " [" + this.hashCode() + "] "); + if (textureView != null) { + if (videoRotation != 0) { + textureView.setRotation(videoRotation); + } + textureView.setVideoSize(width, height); + } + } + + 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 { + if (progress != 0) progressBar.setProgress(progress); + } + } + if (position != 0) currentTimeTextView.setText(JZUtils.stringForTime(position)); + totalTimeTextView.setText(JZUtils.stringForTime(duration)); + } + + public void setBufferProgress(int bufferProgress) { + if (bufferProgress != 0) 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 (IllegalStateException 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)); + } + } + + public void cloneAJzvd(ViewGroup vg) { + try { + Constructor constructor = (Constructor) Jzvd.this.getClass().getConstructor(Context.class); + Jzvd jzvd = constructor.newInstance(getContext()); + jzvd.setId(getId()); + jzvd.setMinimumWidth(blockWidth); + jzvd.setMinimumHeight(blockHeight); + vg.addView(jzvd, blockIndex, blockLayoutParams); + 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(); + } + } + + /** + * 如果全屏或者返回全屏的视图有问题,复写这两个函数gotoScreenNormal(),根据自己布局的情况重新布局。 + */ + public void gotoFullscreen() { + gotoFullscreenTime = System.currentTimeMillis(); + ViewGroup vg = (ViewGroup) getParent(); + jzvdContext = vg.getContext(); + blockLayoutParams = getLayoutParams(); + blockIndex = vg.indexOfChild(this); + blockWidth = getWidth(); + blockHeight = getHeight(); + + vg.removeView(this); + cloneAJzvd(vg); + CONTAINER_LIST.add(vg); + vg = (ViewGroup) (JZUtils.scanForActivity(jzvdContext)).getWindow().getDecorView(); + + ViewGroup.LayoutParams fullLayout = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + vg.addView(this, fullLayout); + + setScreenFullscreen(); + JZUtils.hideStatusBar(jzvdContext); + JZUtils.setRequestedOrientation(jzvdContext, FULLSCREEN_ORIENTATION); + JZUtils.hideSystemUI(jzvdContext);//华为手机和有虚拟键的手机全屏时可隐藏虚拟键 issue:1326 + + } + + public void gotoNormalScreen() {//goback本质上是goto + gobakFullscreenTime = System.currentTimeMillis();//退出全屏 + ViewGroup vg = (ViewGroup) (JZUtils.scanForActivity(jzvdContext)).getWindow().getDecorView(); + vg.removeView(this); +// CONTAINER_LIST.getLast().removeAllViews(); + CONTAINER_LIST.getLast().removeViewAt(blockIndex);//remove block + CONTAINER_LIST.getLast().addView(this, blockIndex, blockLayoutParams); + CONTAINER_LIST.pop(); + + setScreenNormal();//这块可以放到jzvd中 + JZUtils.showStatusBar(jzvdContext); + JZUtils.setRequestedOrientation(jzvdContext, NORMAL_ORIENTATION); + JZUtils.showSystemUI(jzvdContext); + } + + public void setScreenNormal() {//TODO 这块不对呀,还需要改进,设置flag之后要设置ui,不设置ui这么写没意义呀 + screen = SCREEN_NORMAL; + } + + public void setScreenFullscreen() { + screen = SCREEN_FULLSCREEN; + } + + public void setScreenTiny() { + screen = SCREEN_TINY; + } + + // //重力感应的时候调用的函数,、、这里有重力感应的参数,暂时不能删除 + public void autoFullscreen(float x) {//TODO写道demo中 + if (CURRENT_JZVD != null + && (state == STATE_PLAYING || state == STATE_PAUSE) + && screen != SCREEN_FULLSCREEN + && screen != SCREEN_TINY) { + if (x > 0) { + JZUtils.setRequestedOrientation(getContext(), ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + JZUtils.setRequestedOrientation(getContext(), ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + } + gotoFullscreen(); + } + } + + public void autoQuitFullscreen() { + if ((System.currentTimeMillis() - lastAutoFullscreenTime) > 2000 +// && CURRENT_JZVD != null + && state == STATE_PLAYING + && screen == SCREEN_FULLSCREEN) { + lastAutoFullscreenTime = System.currentTimeMillis(); + backPress(); + } + } + + public void onSeekComplete() { + + } + + public void showWifiDialog() { + } + + public void showProgressDialog(float deltaX, + String seekTime, long seekTimePosition, + String totalTime, long totalTimeDuration) { + } + + public void dismissProgressDialog() { + + } + + public void showVolumeDialog(float deltaY, int volumePercent) { + + } + + public void dismissVolumeDialog() { + + } + + public void showBrightnessDialog(int brightnessPercent) { + + } + + public void dismissBrightnessDialog() { + + } + + public Context getApplicationContext() {//这个函数必要吗 + Context context = getContext(); + if (context != null) { + Context applicationContext = context.getApplicationContext(); + if (applicationContext != null) { + return applicationContext; + } + } + return context; + } + + 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 < -12 || x > 12) { + if ((System.currentTimeMillis() - lastAutoFullscreenTime) > 2000) { + if (Jzvd.CURRENT_JZVD != null) Jzvd.CURRENT_JZVD.autoFullscreen(x); + lastAutoFullscreenTime = System.currentTimeMillis(); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } + + public class ProgressTimerTask extends TimerTask { + @Override + public void run() { + if (state == STATE_PLAYING || state == STATE_PAUSE || state == STATE_PREPARING_PLAYING) { +// Log.v(TAG, "onProgressUpdate " + "[" + this.hashCode() + "] "); + post(() -> { + long position = getCurrentPositionWhenPlaying(); + long duration = getDuration(); + int progress = (int) (position * 100 / (duration == 0 ? 1 : duration)); + onProgress(progress, position, duration); + }); + } + } + } + +} diff --git a/library/src/main/java/cn/jzvd/JzvdStd.java b/library/src/main/java/cn/jzvd/JzvdStd.java new file mode 100644 index 0000000..98812b0 --- /dev/null +++ b/library/src/main/java/cn/jzvd/JzvdStd.java @@ -0,0 +1,979 @@ +package cn.jzvd; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.net.ConnectivityManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.ProgressBar; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import java.text.SimpleDateFormat; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Created by Nathen + * On 2016/04/18 16:15 + */ +public class JzvdStd extends Jzvd { + + public static long LAST_GET_BATTERYLEVEL_TIME = 0; + public static int LAST_GET_BATTERYLEVEL_PERCENT = 70; + protected static Timer DISMISS_CONTROL_VIEW_TIMER; + + public ImageView backButton; + public ProgressBar bottomProgressBar, loadingProgressBar; + public TextView titleTextView; + public ImageView posterImageView; + public ImageView tinyBackImageView; + public LinearLayout batteryTimeLayout; + public ImageView batteryLevel; + public TextView videoCurrentTime; + public TextView replayTextView; + public TextView clarity; + public PopupWindow clarityPopWindow; + public TextView mRetryBtn; + public LinearLayout mRetryLayout; + public BroadcastReceiver battertReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { + int level = intent.getIntExtra("level", 0); + int scale = intent.getIntExtra("scale", 100); + int percent = level * 100 / scale; + LAST_GET_BATTERYLEVEL_PERCENT = percent; + setBatteryLevel(); + try { + jzvdContext.unregisterReceiver(battertReceiver); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + protected DismissControlViewTimerTask mDismissControlViewTimerTask; + protected Dialog mProgressDialog; + protected ProgressBar mDialogProgressBar; + protected TextView mDialogSeekTime; + protected TextView mDialogTotalTime; + protected ImageView mDialogIcon; + protected Dialog mVolumeDialog; + protected ProgressBar mDialogVolumeProgressBar; + protected TextView mDialogVolumeTextView; + protected ImageView mDialogVolumeImageView; + protected Dialog mBrightnessDialog; + protected ProgressBar mDialogBrightnessProgressBar; + protected TextView mDialogBrightnessTextView; + protected boolean mIsWifi; + public BroadcastReceiver wifiReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + boolean isWifi = JZUtils.isWifiConnected(context); + if (mIsWifi == isWifi) return; + mIsWifi = isWifi; + if (!mIsWifi && !WIFI_TIP_DIALOG_SHOWED && state == STATE_PLAYING) { + startButton.performClick(); + showWifiDialog(); + } + } + } + }; + protected ArrayDeque delayTask = new ArrayDeque<>(); + + public JzvdStd(Context context) { + super(context); + } + + public JzvdStd(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void init(Context context) { + super.init(context); + batteryTimeLayout = findViewById(R.id.battery_time_layout); + bottomProgressBar = findViewById(R.id.bottom_progress); + titleTextView = findViewById(R.id.title); + backButton = findViewById(R.id.back); + posterImageView = findViewById(R.id.poster); + loadingProgressBar = findViewById(R.id.loading); + tinyBackImageView = findViewById(R.id.back_tiny); + batteryLevel = findViewById(R.id.battery_level); + videoCurrentTime = findViewById(R.id.video_current_time); + replayTextView = findViewById(R.id.replay_text); + clarity = findViewById(R.id.clarity); + mRetryBtn = findViewById(R.id.retry_btn); + mRetryLayout = findViewById(R.id.retry_layout); + + if (batteryTimeLayout == null) { + batteryTimeLayout = new LinearLayout(context); + } + if (bottomProgressBar == null) { + bottomProgressBar = new ProgressBar(context); + } + if (titleTextView == null) { + titleTextView = new TextView(context); + } + if (backButton == null) { + backButton = new ImageView(context); + } + if (posterImageView == null) { + posterImageView = new ImageView(context); + } + if (loadingProgressBar == null) { + loadingProgressBar = new ProgressBar(context); + } + if (tinyBackImageView == null) { + tinyBackImageView = new ImageView(context); + } + if (batteryLevel == null) { + batteryLevel = new ImageView(context); + } + if (videoCurrentTime == null) { + videoCurrentTime = new TextView(context); + } + if (replayTextView == null) { + replayTextView = new TextView(context); + } + if (clarity == null) { + clarity = new TextView(context); + } + if (mRetryBtn == null) { + mRetryBtn = new TextView(context); + } + if (mRetryLayout == null) { + mRetryLayout = new LinearLayout(context); + } + + posterImageView.setOnClickListener(this); + backButton.setOnClickListener(this); + tinyBackImageView.setOnClickListener(this); + clarity.setOnClickListener(this); + mRetryBtn.setOnClickListener(this); + } + + public void setUp(JZDataSource jzDataSource, int screen, Class mediaInterfaceClass) { + if ((System.currentTimeMillis() - gobakFullscreenTime) < 200) { + return; + } + + if ((System.currentTimeMillis() - gotoFullscreenTime) < 200) { + return; + } + + + super.setUp(jzDataSource, screen, mediaInterfaceClass); + titleTextView.setText(jzDataSource.title); + setScreen(screen); + } + + @Override + public void changeUrl(JZDataSource jzDataSource, long seekToInAdvance) { + super.changeUrl(jzDataSource, seekToInAdvance); + titleTextView.setText(jzDataSource.title); + } + + public void changeStartButtonSize(int size) { + ViewGroup.LayoutParams lp = startButton.getLayoutParams(); + lp.height = size; + lp.width = size; + lp = loadingProgressBar.getLayoutParams(); + lp.height = size; + lp.width = size; + } + + @Override + public int getLayoutId() { + return R.layout.jz_layout_std; + } + + @Override + public void onStateNormal() { + super.onStateNormal(); + changeUiToNormal(); + } + + @Override + public void onStatePreparing() { + super.onStatePreparing(); + changeUiToPreparing(); + } + + public void onStatePreparingPlaying() { + super.onStatePreparingPlaying(); + changeUIToPreparingPlaying(); + } + + public void onStatePreparingChangeUrl() { + super.onStatePreparingChangeUrl(); + changeUIToPreparingChangeUrl(); + } + + @Override + public void onStatePlaying() { + super.onStatePlaying(); + changeUiToPlayingClear(); + } + + @Override + public void onStatePause() { + super.onStatePause(); + changeUiToPauseShow(); + cancelDismissControlViewTimer(); + } + + @Override + public void onStateError() { + super.onStateError(); + changeUiToError(); + } + + @Override + public void onStateAutoComplete() { + super.onStateAutoComplete(); + changeUiToComplete(); + cancelDismissControlViewTimer(); + bottomProgressBar.setProgress(100); + } + + @Override + public void startVideo() { + super.startVideo(); + registerWifiListener(getApplicationContext()); + } + + /** + * 双击 + */ + protected GestureDetector gestureDetector = new GestureDetector(getContext().getApplicationContext(), new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (state == STATE_PLAYING || state == STATE_PAUSE) { + Log.d(TAG, "doublClick [" + this.hashCode() + "] "); + startButton.performClick(); + } + return super.onDoubleTap(e); + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (!mChangePosition && !mChangeVolume) { + onClickUiToggle(); + } + return super.onSingleTapConfirmed(e); + } + + @Override + public void onLongPress(MotionEvent e) { + super.onLongPress(e); + } + }); + + @Override + public boolean onTouch(View v, MotionEvent event) { + int id = v.getId(); + if (id == R.id.surface_container) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + break; + case MotionEvent.ACTION_UP: + startDismissControlViewTimer(); + if (mChangePosition) { + long duration = getDuration(); + int progress = (int) (mSeekTimePosition * 100 / (duration == 0 ? 1 : duration)); + bottomProgressBar.setProgress(progress); + } + break; + } + gestureDetector.onTouchEvent(event); + } else if (id == R.id.bottom_seek_progress) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + cancelDismissControlViewTimer(); + break; + case MotionEvent.ACTION_UP: + startDismissControlViewTimer(); + break; + } + } + return super.onTouch(v, event); + } + + @Override + public void onClick(View v) { + super.onClick(v); + int i = v.getId(); + if (i == R.id.poster) { + clickPoster(); + } else if (i == R.id.surface_container) { + clickSurfaceContainer(); + if (clarityPopWindow != null) { + clarityPopWindow.dismiss(); + } + } else if (i == R.id.back) { + clickBack(); + } else if (i == R.id.back_tiny) { + clickBackTiny(); + } else if (i == R.id.clarity) { + clickClarity(); + } else if (i == R.id.retry_btn) { + clickRetryBtn(); + } + } + + protected void clickRetryBtn() { + if (jzDataSource.urlsMap.isEmpty() || jzDataSource.getCurrentUrl() == null) { + Toast.makeText(jzvdContext, getResources().getString(R.string.no_url), Toast.LENGTH_SHORT).show(); + return; + } + if (!jzDataSource.getCurrentUrl().toString().startsWith("file") && ! + jzDataSource.getCurrentUrl().toString().startsWith("/") && + !JZUtils.isWifiConnected(jzvdContext) && !WIFI_TIP_DIALOG_SHOWED) { + showWifiDialog(); + return; + } + seekToInAdvance = mCurrentPosition; + startVideo(); + } + + protected void clickClarity() { + onCLickUiToggleToClear(); + + LayoutInflater inflater = (LayoutInflater) jzvdContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.jz_layout_clarity, null); + + OnClickListener mQualityListener = v1 -> { + int index = (int) v1.getTag(); + +// this.seekToInAdvance = getCurrentPositionWhenPlaying(); + jzDataSource.currentUrlIndex = index; +// onStatePreparingChangeUrl(); + + changeUrl(jzDataSource, getCurrentPositionWhenPlaying()); + + clarity.setText(jzDataSource.getCurrentKey().toString()); + for (int j = 0; j < layout.getChildCount(); j++) {//设置点击之后的颜色 + if (j == jzDataSource.currentUrlIndex) { + ((TextView) layout.getChildAt(j)).setTextColor(Color.parseColor("#fff85959")); + } else { + ((TextView) layout.getChildAt(j)).setTextColor(Color.parseColor("#ffffff")); + } + } + if (clarityPopWindow != null) { + clarityPopWindow.dismiss(); + } + }; + + for (int j = 0; j < jzDataSource.urlsMap.size(); j++) { + String key = jzDataSource.getKeyFromDataSource(j); + TextView clarityItem = (TextView) View.inflate(jzvdContext, R.layout.jz_layout_clarity_item, null); + clarityItem.setText(key); + clarityItem.setTag(j); + layout.addView(clarityItem, j); + clarityItem.setOnClickListener(mQualityListener); + if (j == jzDataSource.currentUrlIndex) { + clarityItem.setTextColor(Color.parseColor("#fff85959")); + } + } + + clarityPopWindow = new PopupWindow(layout, JZUtils.dip2px(jzvdContext, 240), LayoutParams.MATCH_PARENT, true); + clarityPopWindow.setContentView(layout); + clarityPopWindow.setAnimationStyle(R.style.pop_animation); + clarityPopWindow.showAtLocation(textureViewContainer, Gravity.END, 0, 0); +// int offsetX = clarity.getMeasuredWidth() / 3; +// int offsetY = clarity.getMeasuredHeight() / 3; +// clarityPopWindow.update(clarity, -offsetX, -offsetY, Math.round(layout.getMeasuredWidth() * 2), layout.getMeasuredHeight()); + } + + protected void clickBackTiny() { + clearFloatScreen(); + } + + protected void clickBack() { + backPress(); + } + + protected void clickSurfaceContainer() { + startDismissControlViewTimer(); + } + + protected void clickPoster() { + if (jzDataSource == null || jzDataSource.urlsMap.isEmpty() || jzDataSource.getCurrentUrl() == null) { + Toast.makeText(jzvdContext, getResources().getString(R.string.no_url), Toast.LENGTH_SHORT).show(); + return; + } + if (state == STATE_NORMAL) { + if (!jzDataSource.getCurrentUrl().toString().startsWith("file") && + !jzDataSource.getCurrentUrl().toString().startsWith("/") && + !JZUtils.isWifiConnected(jzvdContext) && !WIFI_TIP_DIALOG_SHOWED) { + showWifiDialog(); + return; + } + startVideo(); + } else if (state == STATE_AUTO_COMPLETE) { + onClickUiToggle(); + } + } + + @Override + public void setScreenNormal() { + super.setScreenNormal(); + fullscreenButton.setImageResource(R.drawable.jz_enlarge); + backButton.setVisibility(View.GONE); + tinyBackImageView.setVisibility(View.INVISIBLE); + changeStartButtonSize((int) getResources().getDimension(R.dimen.jz_start_button_w_h_normal)); + batteryTimeLayout.setVisibility(View.GONE); + clarity.setVisibility(View.GONE); + } + + @Override + public void setScreenFullscreen() { + super.setScreenFullscreen(); + //进入全屏之后要保证原来的播放状态和ui状态不变,改变个别的ui + fullscreenButton.setImageResource(R.drawable.jz_shrink); + backButton.setVisibility(View.VISIBLE); + tinyBackImageView.setVisibility(View.INVISIBLE); + batteryTimeLayout.setVisibility(View.VISIBLE); + if (jzDataSource.urlsMap.size() == 1) { + clarity.setVisibility(GONE); + } else { + clarity.setText(jzDataSource.getCurrentKey().toString()); + clarity.setVisibility(View.VISIBLE); + } + changeStartButtonSize((int) getResources().getDimension(R.dimen.jz_start_button_w_h_fullscreen)); + setSystemTimeAndBattery(); + } + + @Override + public void setScreenTiny() { + super.setScreenTiny(); + tinyBackImageView.setVisibility(View.VISIBLE); + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, + View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, View.INVISIBLE); + batteryTimeLayout.setVisibility(View.GONE); + clarity.setVisibility(View.GONE); + } + + @Override + public void showWifiDialog() { + super.showWifiDialog(); + AlertDialog.Builder builder = new AlertDialog.Builder(jzvdContext); + builder.setMessage(getResources().getString(R.string.tips_not_wifi)); + builder.setPositiveButton(getResources().getString(R.string.tips_not_wifi_confirm), (dialog, which) -> { + dialog.dismiss(); + WIFI_TIP_DIALOG_SHOWED = true; + if (state == STATE_PAUSE) { + startButton.performClick(); + } else { + startVideo(); + } + + }); + builder.setNegativeButton(getResources().getString(R.string.tips_not_wifi_cancel), (dialog, which) -> { + dialog.dismiss(); + releaseAllVideos(); + clearFloatScreen(); + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + releaseAllVideos(); + clearFloatScreen(); + } + }); + + builder.create().show(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + super.onStartTrackingTouch(seekBar); + cancelDismissControlViewTimer(); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + super.onStopTrackingTouch(seekBar); + startDismissControlViewTimer(); + } + + public void onClickUiToggle() {//这是事件 + if (bottomContainer.getVisibility() != View.VISIBLE) { + setSystemTimeAndBattery(); + clarity.setText(jzDataSource.getCurrentKey().toString()); + } + if (state == STATE_PREPARING) { + changeUiToPreparing(); + if (bottomContainer.getVisibility() == View.VISIBLE) { + } else { + setSystemTimeAndBattery(); + } + } else if (state == STATE_PLAYING) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToPlayingClear(); + } else { + changeUiToPlayingShow(); + } + } else if (state == STATE_PAUSE) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToPauseClear(); + } else { + changeUiToPauseShow(); + } + } + } + + public void setSystemTimeAndBattery() { + SimpleDateFormat dateFormater = new SimpleDateFormat("HH:mm"); + Date date = new Date(); + videoCurrentTime.setText(dateFormater.format(date)); + if ((System.currentTimeMillis() - LAST_GET_BATTERYLEVEL_TIME) > 30000) { + LAST_GET_BATTERYLEVEL_TIME = System.currentTimeMillis(); + jzvdContext.registerReceiver( + battertReceiver, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ); + } else { + setBatteryLevel(); + } + } + + public void setBatteryLevel() { + int percent = LAST_GET_BATTERYLEVEL_PERCENT; + if (percent < 15) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_10); + } else if (percent >= 15 && percent < 40) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_30); + } else if (percent >= 40 && percent < 60) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_50); + } else if (percent >= 60 && percent < 80) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_70); + } else if (percent >= 80 && percent < 95) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_90); + } else if (percent >= 95 && percent <= 100) { + batteryLevel.setBackgroundResource(R.drawable.jz_battery_level_100); + } + } + + //** 和onClickUiToggle重复,要干掉 + public void onCLickUiToggleToClear() { + if (state == STATE_PREPARING) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToPreparing(); + } else { + } + } else if (state == STATE_PLAYING) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToPlayingClear(); + } else { + } + } else if (state == STATE_PAUSE) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToPauseClear(); + } else { + } + } else if (state == STATE_AUTO_COMPLETE) { + if (bottomContainer.getVisibility() == View.VISIBLE) { + changeUiToComplete(); + } else { + } + } + } + + @Override + public void onProgress(int progress, long position, long duration) { + super.onProgress(progress, position, duration); + if (progress != 0) bottomProgressBar.setProgress(progress); + } + + @Override + public void setBufferProgress(int bufferProgress) { + super.setBufferProgress(bufferProgress); + if (bufferProgress != 0) bottomProgressBar.setSecondaryProgress(bufferProgress); + } + + @Override + public void resetProgressAndTime() { + super.resetProgressAndTime(); + bottomProgressBar.setProgress(0); + bottomProgressBar.setSecondaryProgress(0); + } + + public void changeUiToNormal() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.INVISIBLE, View.VISIBLE, + View.INVISIBLE, View.VISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + } + + public void changeUiToPreparing() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, + View.VISIBLE, View.VISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + } + + public void changeUIToPreparingPlaying() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.VISIBLE, View.INVISIBLE, + View.VISIBLE, View.INVISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + } + + public void changeUIToPreparingChangeUrl() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, + View.VISIBLE, View.VISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + } + + public void changeUiToPlayingShow() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.VISIBLE, View.VISIBLE, + View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + + } + + public void changeUiToPlayingClear() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, + View.INVISIBLE, View.INVISIBLE, View.VISIBLE, View.INVISIBLE); + break; + case SCREEN_TINY: + break; + } + + } + + public void changeUiToPauseShow() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.VISIBLE, View.VISIBLE, + View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + } + + public void changeUiToPauseClear() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, + View.INVISIBLE, View.INVISIBLE, View.VISIBLE, View.INVISIBLE); + break; + case SCREEN_TINY: + break; + } + + } + + public void changeUiToComplete() { + switch (screen) { + case SCREEN_NORMAL: + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.INVISIBLE, View.VISIBLE, + View.INVISIBLE, View.VISIBLE, View.INVISIBLE, View.INVISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + + } + + public void changeUiToError() { + switch (screen) { + case SCREEN_NORMAL: + setAllControlsVisiblity(View.INVISIBLE, View.INVISIBLE, View.VISIBLE, + View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, View.VISIBLE); + updateStartImage(); + break; + case SCREEN_FULLSCREEN: + setAllControlsVisiblity(View.VISIBLE, View.INVISIBLE, View.VISIBLE, + View.INVISIBLE, View.INVISIBLE, View.INVISIBLE, View.VISIBLE); + updateStartImage(); + break; + case SCREEN_TINY: + break; + } + + } + + public void setAllControlsVisiblity(int topCon, int bottomCon, int startBtn, int loadingPro, + int posterImg, int bottomPro, int retryLayout) { + topContainer.setVisibility(topCon); + bottomContainer.setVisibility(bottomCon); + startButton.setVisibility(startBtn); + loadingProgressBar.setVisibility(loadingPro); + posterImageView.setVisibility(posterImg); + bottomProgressBar.setVisibility(bottomPro); + mRetryLayout.setVisibility(retryLayout); + } + + public void updateStartImage() { + if (state == STATE_PLAYING) { + startButton.setVisibility(VISIBLE); + startButton.setImageResource(R.drawable.jz_click_pause_selector); + replayTextView.setVisibility(GONE); + } else if (state == STATE_ERROR) { + startButton.setVisibility(INVISIBLE); + replayTextView.setVisibility(GONE); + } else if (state == STATE_AUTO_COMPLETE) { + startButton.setVisibility(VISIBLE); + startButton.setImageResource(R.drawable.jz_click_replay_selector); + replayTextView.setVisibility(VISIBLE); + } else { + startButton.setImageResource(R.drawable.jz_click_play_selector); + replayTextView.setVisibility(GONE); + } + } + + @Override + public void showProgressDialog(float deltaX, String seekTime, long seekTimePosition, String totalTime, long totalTimeDuration) { + super.showProgressDialog(deltaX, seekTime, seekTimePosition, totalTime, totalTimeDuration); + if (mProgressDialog == null) { + View localView = LayoutInflater.from(jzvdContext).inflate(R.layout.jz_dialog_progress, null); + mDialogProgressBar = localView.findViewById(R.id.duration_progressbar); + mDialogSeekTime = localView.findViewById(R.id.tv_current); + mDialogTotalTime = localView.findViewById(R.id.tv_duration); + mDialogIcon = localView.findViewById(R.id.duration_image_tip); + mProgressDialog = createDialogWithView(localView); + } + if (!mProgressDialog.isShowing()) { + mProgressDialog.show(); + } + + mDialogSeekTime.setText(seekTime); + mDialogTotalTime.setText(" / " + totalTime); + mDialogProgressBar.setProgress(totalTimeDuration <= 0 ? 0 : (int) (seekTimePosition * 100 / totalTimeDuration)); + if (deltaX > 0) { + mDialogIcon.setBackgroundResource(R.drawable.jz_forward_icon); + } else { + mDialogIcon.setBackgroundResource(R.drawable.jz_backward_icon); + } + onCLickUiToggleToClear(); + } + + @Override + public void dismissProgressDialog() { + super.dismissProgressDialog(); + if (mProgressDialog != null) { + mProgressDialog.dismiss(); + } + } + + @Override + public void showVolumeDialog(float deltaY, int volumePercent) { + super.showVolumeDialog(deltaY, volumePercent); + if (mVolumeDialog == null) { + View localView = LayoutInflater.from(jzvdContext).inflate(R.layout.jz_dialog_volume, null); + mDialogVolumeImageView = localView.findViewById(R.id.volume_image_tip); + mDialogVolumeTextView = localView.findViewById(R.id.tv_volume); + mDialogVolumeProgressBar = localView.findViewById(R.id.volume_progressbar); + mVolumeDialog = createDialogWithView(localView); + } + if (!mVolumeDialog.isShowing()) { + mVolumeDialog.show(); + } + if (volumePercent <= 0) { + mDialogVolumeImageView.setBackgroundResource(R.drawable.jz_close_volume); + } else { + mDialogVolumeImageView.setBackgroundResource(R.drawable.jz_add_volume); + } + if (volumePercent > 100) { + volumePercent = 100; + } else if (volumePercent < 0) { + volumePercent = 0; + } + mDialogVolumeTextView.setText(volumePercent + "%"); + mDialogVolumeProgressBar.setProgress(volumePercent); + onCLickUiToggleToClear(); + } + + @Override + public void dismissVolumeDialog() { + super.dismissVolumeDialog(); + if (mVolumeDialog != null) { + mVolumeDialog.dismiss(); + } + } + + @Override + public void showBrightnessDialog(int brightnessPercent) { + super.showBrightnessDialog(brightnessPercent); + if (mBrightnessDialog == null) { + View localView = LayoutInflater.from(jzvdContext).inflate(R.layout.jz_dialog_brightness, null); + mDialogBrightnessTextView = localView.findViewById(R.id.tv_brightness); + mDialogBrightnessProgressBar = localView.findViewById(R.id.brightness_progressbar); + mBrightnessDialog = createDialogWithView(localView); + } + if (!mBrightnessDialog.isShowing()) { + mBrightnessDialog.show(); + } + if (brightnessPercent > 100) { + brightnessPercent = 100; + } else if (brightnessPercent < 0) { + brightnessPercent = 0; + } + mDialogBrightnessTextView.setText(brightnessPercent + "%"); + mDialogBrightnessProgressBar.setProgress(brightnessPercent); + onCLickUiToggleToClear(); + } + + @Override + public void dismissBrightnessDialog() { + super.dismissBrightnessDialog(); + if (mBrightnessDialog != null) { + mBrightnessDialog.dismiss(); + } + } + + public Dialog createDialogWithView(View localView) { + Dialog dialog = new Dialog(jzvdContext, R.style.jz_style_dialog_progress); + dialog.setContentView(localView); + Window window = dialog.getWindow(); + window.addFlags(Window.FEATURE_ACTION_BAR); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + window.setLayout(-2, -2); + WindowManager.LayoutParams localLayoutParams = window.getAttributes(); + localLayoutParams.gravity = Gravity.CENTER; + window.setAttributes(localLayoutParams); + return dialog; + } + + public void startDismissControlViewTimer() { + cancelDismissControlViewTimer(); + DISMISS_CONTROL_VIEW_TIMER = new Timer(); + mDismissControlViewTimerTask = new DismissControlViewTimerTask(); + DISMISS_CONTROL_VIEW_TIMER.schedule(mDismissControlViewTimerTask, 2500); + } + + public void cancelDismissControlViewTimer() { + if (DISMISS_CONTROL_VIEW_TIMER != null) { + DISMISS_CONTROL_VIEW_TIMER.cancel(); + } + if (mDismissControlViewTimerTask != null) { + mDismissControlViewTimerTask.cancel(); + } + + } + + @Override + public void onCompletion() { + super.onCompletion(); + cancelDismissControlViewTimer(); + } + + @Override + public void reset() { + super.reset(); + cancelDismissControlViewTimer(); + unregisterWifiListener(getApplicationContext()); + } + + public void dissmissControlView() { + if (state != STATE_NORMAL + && state != STATE_ERROR + && state != STATE_AUTO_COMPLETE) { + post(() -> { + bottomContainer.setVisibility(View.INVISIBLE); + topContainer.setVisibility(View.INVISIBLE); + startButton.setVisibility(View.INVISIBLE); + + if (screen != SCREEN_TINY) { + bottomProgressBar.setVisibility(View.VISIBLE); + } + cancelProgressTimer(); + }); + } + } + + public void registerWifiListener(Context context) { + if (context == null) return; + mIsWifi = JZUtils.isWifiConnected(context); + IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + context.registerReceiver(wifiReceiver, intentFilter); + } + + public void unregisterWifiListener(Context context) { + if (context == null) return; + try { + context.unregisterReceiver(wifiReceiver); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + + public class DismissControlViewTimerTask extends TimerTask { + + @Override + public void run() { + dissmissControlView(); + } + } + +} diff --git a/library/src/main/java/org/jzvd/jzvideo/JZVideoA.kt b/library/src/main/java/org/jzvd/jzvideo/JZVideoA.kt new file mode 100644 index 0000000..bdb5e4b --- /dev/null +++ b/library/src/main/java/org/jzvd/jzvideo/JZVideoA.kt @@ -0,0 +1,37 @@ +package org.jzvd.jzvideo + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout + +const val TAG = "JZVD" + + +open class JZVideoA : RelativeLayout { + + enum class State { + IDLE, NORMAL, PREPARING, PREPARING_CHANGE_URL, PREPARING_PLAYING, + PREPARED, PLAYING, PAUSE, COMPLETE, ERROR + } + + enum class Screen { + NORMAL, FULLSCREEN, TINY + } + + lateinit var state: State + + + constructor(ctx: Context) : super(ctx) { + } + + constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) { + } + + companion object { + @JvmStatic + fun releaseAllVideos() { + + } + } + +} \ No newline at end of file diff --git a/library/src/main/res/anim/pop_from_bottom_anim_in.xml b/library/src/main/res/anim/pop_from_bottom_anim_in.xml new file mode 100644 index 0000000..7536a52 --- /dev/null +++ b/library/src/main/res/anim/pop_from_bottom_anim_in.xml @@ -0,0 +1,6 @@ + + + diff --git a/library/src/main/res/anim/pop_from_bottom_anim_out.xml b/library/src/main/res/anim/pop_from_bottom_anim_out.xml new file mode 100644 index 0000000..1ee5add --- /dev/null +++ b/library/src/main/res/anim/pop_from_bottom_anim_out.xml @@ -0,0 +1,5 @@ + + diff --git a/library/src/main/res/drawable-xhdpi/jz_add_volume.png b/library/src/main/res/drawable-xhdpi/jz_add_volume.png new file mode 100644 index 0000000..868b308 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_add_volume.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_back_normal.png b/library/src/main/res/drawable-xhdpi/jz_back_normal.png new file mode 100644 index 0000000..8b58353 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_back_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_back_pressed.png b/library/src/main/res/drawable-xhdpi/jz_back_pressed.png new file mode 100644 index 0000000..82d2ba6 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_back_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_back_tiny_normal.png b/library/src/main/res/drawable-xhdpi/jz_back_tiny_normal.png new file mode 100644 index 0000000..e6ca6f4 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_back_tiny_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_back_tiny_pressed.png b/library/src/main/res/drawable-xhdpi/jz_back_tiny_pressed.png new file mode 100644 index 0000000..2ca8252 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_back_tiny_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_backward_icon.png b/library/src/main/res/drawable-xhdpi/jz_backward_icon.png new file mode 100644 index 0000000..51f5ca3 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_backward_icon.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_10.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_10.png new file mode 100644 index 0000000..1d5b5f6 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_10.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_100.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_100.png new file mode 100644 index 0000000..6f0cf51 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_100.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_30.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_30.png new file mode 100644 index 0000000..947e905 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_30.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_50.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_50.png new file mode 100644 index 0000000..e4c75ff Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_50.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_70.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_70.png new file mode 100644 index 0000000..25177bf Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_70.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_battery_level_90.png b/library/src/main/res/drawable-xhdpi/jz_battery_level_90.png new file mode 100644 index 0000000..6c681c5 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_battery_level_90.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_brightness_video.png b/library/src/main/res/drawable-xhdpi/jz_brightness_video.png new file mode 100644 index 0000000..df25316 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_brightness_video.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_clarity_popwindow_bg.9.png b/library/src/main/res/drawable-xhdpi/jz_clarity_popwindow_bg.9.png new file mode 100644 index 0000000..c5f67fd Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_clarity_popwindow_bg.9.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_close_volume.png b/library/src/main/res/drawable-xhdpi/jz_close_volume.png new file mode 100644 index 0000000..1e08431 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_close_volume.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_enlarge.png b/library/src/main/res/drawable-xhdpi/jz_enlarge.png new file mode 100644 index 0000000..bf69e8b Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_enlarge.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_forward_icon.png b/library/src/main/res/drawable-xhdpi/jz_forward_icon.png new file mode 100644 index 0000000..f7e3188 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_forward_icon.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_loading_bg.png b/library/src/main/res/drawable-xhdpi/jz_loading_bg.png new file mode 100644 index 0000000..b85c037 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_loading_bg.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_pause_normal.png b/library/src/main/res/drawable-xhdpi/jz_pause_normal.png new file mode 100644 index 0000000..12b158e Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_pause_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_pause_pressed.png b/library/src/main/res/drawable-xhdpi/jz_pause_pressed.png new file mode 100644 index 0000000..3a2f8a3 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_pause_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_play_normal.png b/library/src/main/res/drawable-xhdpi/jz_play_normal.png new file mode 100644 index 0000000..74db6df Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_play_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_play_pressed.png b/library/src/main/res/drawable-xhdpi/jz_play_pressed.png new file mode 100644 index 0000000..3aaa183 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_play_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_restart_normal.png b/library/src/main/res/drawable-xhdpi/jz_restart_normal.png new file mode 100644 index 0000000..618616e Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_restart_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_restart_pressed.png b/library/src/main/res/drawable-xhdpi/jz_restart_pressed.png new file mode 100644 index 0000000..21d4632 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_restart_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_share_normal.png b/library/src/main/res/drawable-xhdpi/jz_share_normal.png new file mode 100644 index 0000000..73d62e1 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_share_normal.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_share_pressed.png b/library/src/main/res/drawable-xhdpi/jz_share_pressed.png new file mode 100644 index 0000000..e899314 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_share_pressed.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_shrink.png b/library/src/main/res/drawable-xhdpi/jz_shrink.png new file mode 100644 index 0000000..70bb00c Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_shrink.png differ diff --git a/library/src/main/res/drawable-xhdpi/jz_volume_icon.png b/library/src/main/res/drawable-xhdpi/jz_volume_icon.png new file mode 100644 index 0000000..deb5358 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/jz_volume_icon.png differ diff --git a/library/src/main/res/drawable/jz_bottom_bg.9.png b/library/src/main/res/drawable/jz_bottom_bg.9.png new file mode 100644 index 0000000..48a5b47 Binary files /dev/null and b/library/src/main/res/drawable/jz_bottom_bg.9.png differ diff --git a/library/src/main/res/drawable/jz_bottom_progress.xml b/library/src/main/res/drawable/jz_bottom_progress.xml new file mode 100644 index 0000000..5db2949 --- /dev/null +++ b/library/src/main/res/drawable/jz_bottom_progress.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/drawable/jz_bottom_seek_poster.xml b/library/src/main/res/drawable/jz_bottom_seek_poster.xml new file mode 100644 index 0000000..0cd8f9b --- /dev/null +++ b/library/src/main/res/drawable/jz_bottom_seek_poster.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_bottom_seek_progress.xml b/library/src/main/res/drawable/jz_bottom_seek_progress.xml new file mode 100644 index 0000000..212fccc --- /dev/null +++ b/library/src/main/res/drawable/jz_bottom_seek_progress.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/drawable/jz_clarity_popwindow_bg.9.png b/library/src/main/res/drawable/jz_clarity_popwindow_bg.9.png new file mode 100644 index 0000000..3bb3053 Binary files /dev/null and b/library/src/main/res/drawable/jz_clarity_popwindow_bg.9.png differ diff --git a/library/src/main/res/drawable/jz_click_back_selector.xml b/library/src/main/res/drawable/jz_click_back_selector.xml new file mode 100644 index 0000000..5379ed6 --- /dev/null +++ b/library/src/main/res/drawable/jz_click_back_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_click_back_tiny_selector.xml b/library/src/main/res/drawable/jz_click_back_tiny_selector.xml new file mode 100644 index 0000000..baca67f --- /dev/null +++ b/library/src/main/res/drawable/jz_click_back_tiny_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_click_pause_selector.xml b/library/src/main/res/drawable/jz_click_pause_selector.xml new file mode 100644 index 0000000..93559b8 --- /dev/null +++ b/library/src/main/res/drawable/jz_click_pause_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/src/main/res/drawable/jz_click_play_selector.xml b/library/src/main/res/drawable/jz_click_play_selector.xml new file mode 100644 index 0000000..e99799c --- /dev/null +++ b/library/src/main/res/drawable/jz_click_play_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_click_replay_selector.xml b/library/src/main/res/drawable/jz_click_replay_selector.xml new file mode 100644 index 0000000..9b5ca8c --- /dev/null +++ b/library/src/main/res/drawable/jz_click_replay_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_click_share_selector.xml b/library/src/main/res/drawable/jz_click_share_selector.xml new file mode 100644 index 0000000..0de0b6f --- /dev/null +++ b/library/src/main/res/drawable/jz_click_share_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_dialog_progress.xml b/library/src/main/res/drawable/jz_dialog_progress.xml new file mode 100644 index 0000000..35179bd --- /dev/null +++ b/library/src/main/res/drawable/jz_dialog_progress.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/drawable/jz_dialog_progress_bg.xml b/library/src/main/res/drawable/jz_dialog_progress_bg.xml new file mode 100644 index 0000000..421a56c --- /dev/null +++ b/library/src/main/res/drawable/jz_dialog_progress_bg.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/jz_loading.xml b/library/src/main/res/drawable/jz_loading.xml new file mode 100644 index 0000000..9eaf8e0 --- /dev/null +++ b/library/src/main/res/drawable/jz_loading.xml @@ -0,0 +1,7 @@ + + diff --git a/library/src/main/res/drawable/jz_retry.xml b/library/src/main/res/drawable/jz_retry.xml new file mode 100644 index 0000000..76c211a --- /dev/null +++ b/library/src/main/res/drawable/jz_retry.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/jz_seek_poster_normal.xml b/library/src/main/res/drawable/jz_seek_poster_normal.xml new file mode 100644 index 0000000..e928712 --- /dev/null +++ b/library/src/main/res/drawable/jz_seek_poster_normal.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_seek_poster_pressed.xml b/library/src/main/res/drawable/jz_seek_poster_pressed.xml new file mode 100644 index 0000000..14e409a --- /dev/null +++ b/library/src/main/res/drawable/jz_seek_poster_pressed.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/library/src/main/res/drawable/jz_title_bg.9.png b/library/src/main/res/drawable/jz_title_bg.9.png new file mode 100644 index 0000000..5d9df27 Binary files /dev/null and b/library/src/main/res/drawable/jz_title_bg.9.png differ diff --git a/library/src/main/res/drawable/jz_volume_progress_bg.xml b/library/src/main/res/drawable/jz_volume_progress_bg.xml new file mode 100644 index 0000000..a388074 --- /dev/null +++ b/library/src/main/res/drawable/jz_volume_progress_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/layout/jz_dialog_brightness.xml b/library/src/main/res/layout/jz_dialog_brightness.xml new file mode 100644 index 0000000..90c55e3 --- /dev/null +++ b/library/src/main/res/layout/jz_dialog_brightness.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/jz_dialog_progress.xml b/library/src/main/res/layout/jz_dialog_progress.xml new file mode 100644 index 0000000..b16b2e7 --- /dev/null +++ b/library/src/main/res/layout/jz_dialog_progress.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/layout/jz_dialog_volume.xml b/library/src/main/res/layout/jz_dialog_volume.xml new file mode 100644 index 0000000..5b4d2a1 --- /dev/null +++ b/library/src/main/res/layout/jz_dialog_volume.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/jz_layout_clarity.xml b/library/src/main/res/layout/jz_layout_clarity.xml new file mode 100644 index 0000000..3670c9d --- /dev/null +++ b/library/src/main/res/layout/jz_layout_clarity.xml @@ -0,0 +1,10 @@ + + diff --git a/library/src/main/res/layout/jz_layout_clarity_item.xml b/library/src/main/res/layout/jz_layout_clarity_item.xml new file mode 100644 index 0000000..efeadea --- /dev/null +++ b/library/src/main/res/layout/jz_layout_clarity_item.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/library/src/main/res/layout/jz_layout_std.xml b/library/src/main/res/layout/jz_layout_std.xml new file mode 100644 index 0000000..3bfbd9a --- /dev/null +++ b/library/src/main/res/layout/jz_layout_std.xml @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/values-es/strings.xml b/library/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..66ea803 --- /dev/null +++ b/library/src/main/res/values-es/strings.xml @@ -0,0 +1,10 @@ + + + Estás conectado a una red móvil, el reproductor utilizará tus datos si continuas + Reproducir + Cancelar + No hay vídeo + Volver a ver + Haga clic para volver a intentarlo + Error de carga de video + diff --git a/library/src/main/res/values-ja-rJP/strings.xml b/library/src/main/res/values-ja-rJP/strings.xml new file mode 100644 index 0000000..0a5dfcf --- /dev/null +++ b/library/src/main/res/values-ja-rJP/strings.xml @@ -0,0 +1,10 @@ + + + あなたは現在、モバイルネットワークを使用しています + ビデオを再開する + ビデオを止める + URLエラー + リプレイ + 再度試してください + エラーが発生しました + \ No newline at end of file diff --git a/library/src/main/res/values-ko-rKR/strings.xml b/library/src/main/res/values-ko-rKR/strings.xml new file mode 100644 index 0000000..98b752c --- /dev/null +++ b/library/src/main/res/values-ko-rKR/strings.xml @@ -0,0 +1,10 @@ + + + 현재 모바일 네트워크를 사용 중이면 계속 플레이어가 트래픽을 소비합니다 + 재생 다시 시작 + 재생을 중지 + 재생 주소 없음 + 재생 다시 시작 + 다시 시도 하십시오 + 버퍼링 실패 + \ No newline at end of file diff --git a/library/src/main/res/values-pt/strings.xml b/library/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000..87ebf5a --- /dev/null +++ b/library/src/main/res/values-pt/strings.xml @@ -0,0 +1,10 @@ + + + Você está usando a rede móvel, você deseja mesmo ver o vídeo? + Continuar + Parar + Sem vídeo + Replay + Clique para tentar novamente + O carregamento de vídeo falhou + diff --git a/library/src/main/res/values-tr/strings.xml b/library/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..037f81a --- /dev/null +++ b/library/src/main/res/values-tr/strings.xml @@ -0,0 +1,10 @@ + + + Şu anda mobil veriyi kullanıyorsunuz, yüksek veri kaybına yol açabilir + Devam Et + Durdur + URL Bulunamadı + Tekrar + Tekrar denemek için tıklayın + Video yüklenemedi + diff --git a/library/src/main/res/values-zh/strings.xml b/library/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..944669c --- /dev/null +++ b/library/src/main/res/values-zh/strings.xml @@ -0,0 +1,10 @@ + + + 您当前正在使用移动网络,继续播放将消耗流量 + 继续播放 + 停止播放 + 播放地址无效 + 重播 + 点击重试 + 视频加载失败 + diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml new file mode 100644 index 0000000..b6744b0 --- /dev/null +++ b/library/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + 45dp + 62dp + diff --git a/library/src/main/res/values/ids.xml b/library/src/main/res/values/ids.xml new file mode 100644 index 0000000..3d0d301 --- /dev/null +++ b/library/src/main/res/values/ids.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 0000000..2c11f97 --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + You are currently using the mobile network, the player will continue to consume traffic + Resume + Stop play + No url + Replay + Click to try again + Video loading failed + diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml new file mode 100644 index 0000000..86ad9af --- /dev/null +++ b/library/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/library/src/main/res/xml/jz_network_security_config.xml b/library/src/main/res/xml/jz_network_security_config.xml new file mode 100644 index 0000000..2439f15 --- /dev/null +++ b/library/src/main/res/xml/jz_network_security_config.xml @@ -0,0 +1,4 @@ + + + +