+## 四、运行效果
-* ### 2、列表/详情模式
+* ### 1、打开一个播放(旋转、镜像、填充)
+
+
+* ### 2、列表/详情模式(动画、旋转、小窗体)
-
-
+
+
+
+
+
+* ### 4、滤镜和GL动画
+
+* ### 6、背景铺满模糊播放
-* ### 4、进度条小窗口预览
-
+
+* ### 7、进度条小窗口预览
+
-## 近期版本
+## 五、近期版本
-### 1.6.9(2017-07-08) >>>> [片头广告功能推迟到1.7.0]
-* 修改setup的设置参数。
-* 升级修改所有回调接口,回调接口中返回当前播放器。
-* 修正播放本地文件错误,会错删文件问题。
-* 兼容Appbar中使用,感谢[@loveRose](https://github.com/loveRose)
-* 非全屏播放器可获取全屏播放器对象。
+### 7.1.2(2019-12-02)
+
+* fix #2436 增加 exo 的 http timeout
```
-/**
- * 获取全屏播放器对象
- *
- * @return GSYVideoPlayer 如果没有则返回空。
- */
-public GSYVideoPlayer getFullWindowPlayer()
+ExoSourceManager
+
+ public static void setHttpReadTimeout(int httpReadTimeout)
+
+ public static void setHttpConnectTimeout(int httpConnectTimeout)
+
+```
+* 优化视频尺寸显示计算
+* 增加 exo 支持 raw 文件播放
+
+```
+String url = RawResourceDataSource.buildRawResourceUri(R.raw.test).toString();
+```
+* 增加模拟下载共用缓存例子 DetailDownloadPlayer
+* 适配 android 10 的全屏
+* fix #2382、#2411、#2343、#2379、#2350、#2328
+* 增加设置自定义显示比例的支持
+
+```
+GSYVideoType.setScreenScaleRatio
+```
+* 增加外挂字幕例子 [exo2模式下支持自定增加外挂字幕](https://github.com/CarGuo/GSYVideoPlayer/tree/master/app/src/main/java/com/example/gsyvideoplayer/exosubtitle)
+
+
+### 7.1.1(2019-10-12)
+
+* fix #2244、#2252(resolveFullVideoShow 不执行情况)、#2279、#2280
+* fix #2303(去除 TimerTask)、#2306(某些机型退到后台返回不显示)
+* 增加 setNeedAutoAdaptation
+```
+ /**
+ * 是否需要适配在竖屏横屏时,由于刘海屏或者打孔屏占据空间,导致标题显示被遮盖的问题
+ *
+ * @param needAutoAdaptation 默认false
+ */
+ public void setNeedAutoAdaptation(boolean needAutoAdaptation)
+```
+
+### 7.1.0(2019-09-01)
+
+* update ExoPlayer to 2.10.4
+* 添加沉浸式支持
+* 增加 IPlayerInitSuccessListener 播放器初始化成果回调
+```
+GSYVideoManager
+ .instance()
+ .setPlayerInitSuccessListener(new IPlayerInitSuccessListener() {
+ ///播放器初始化成果回调,可用于播放前的自定义设置
+ @Override
+ public void onPlayerInitSuccess(IMediaPlayer player, GSYModel model) {
+ if (player instanceof IjkExo2MediaPlayer) {
+ ((IjkExo2MediaPlayer) player).setTrackSelector(new DefaultTrackSelector());
+ ((IjkExo2MediaPlayer) player).setLoadControl(new DefaultLoadControl());
+ }
+ }
+ });
```
+* fix #2142
+* 增加硬解码不花屏幕 [RecyclerView3Activity](https://github.com/CarGuo/GSYVideoPlayer/blob/master/app/src/main/java/com/example/gsyvideoplayer/RecyclerView3Activity.java)
-### 更多版本请查阅:[版本更新说明](https://github.com/CarGuo/GSYVideoPlayer/blob/master/UPDATE_VERSION.md)
+### 非 androidx 版本为 6.0.3 以下版本。更多兼容版本请查阅版本更新。
--------------------
+### 更多版本请查阅:[版本更新说明](https://github.com/CarGuo/GSYVideoPlayer/blob/master/doc/UPDATE_VERSION.md)
-## 关于Issues
+## 六、关于Issues
```
提问题前可先查阅上方文档和说明,请在Demo中复现问题。
@@ -143,13 +283,43 @@ public GSYVideoPlayer getFullWindowPlayer()
4、补充问题的机型,android版本。
```
-## 混淆
+## 七、混淆
```
+-keep class com.shuyu.gsyvideoplayer.video.** { *; }
+-dontwarn com.shuyu.gsyvideoplayer.video.**
+-keep class com.shuyu.gsyvideoplayer.video.base.** { *; }
+-dontwarn com.shuyu.gsyvideoplayer.video.base.**
+-keep class com.shuyu.gsyvideoplayer.utils.** { *; }
+-dontwarn com.shuyu.gsyvideoplayer.utils.**
-keep class tv.danmaku.ijk.** { *; }
-dontwarn tv.danmaku.ijk.**
--keep class com.shuyu.gsyvideoplayer.** { *; }
--dontwarn com.shuyu.gsyvideoplayer.**
+
+-keep public class * extends android.view.View{
+ *** get*();
+ void set*(***);
+ public
+ * Created by guoshuyu on 2017/6/18.
+ */
+@RuntimePermissions
+public class DetailControlActivity extends GSYBaseActivityDetail By default this implementation will not follow cross-protocol redirects (i.e. redirects from
+ * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the {@link
+ * #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean,
+ * RequestProperties)} constructor and passing {@code true} as the second last argument.
+ */
+public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSource {
+
+ /**
+ * The default connection timeout, in milliseconds.
+ */
+ public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000;
+ /**
+ * The default read timeout, in milliseconds.
+ */
+ public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
+
+ private static final String TAG = "DefaultHttpDataSource";
+ private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
+ private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307;
+ private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308;
+ private static final long MAX_BYTES_TO_DRAIN = 2048;
+ private static final Pattern CONTENT_RANGE_HEADER =
+ Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
+ private static final AtomicReference
+ * If the total length of the data being read is known, then this length minus {@code bytesRead()}
+ * is returned. If the total length is unknown, {@link C#LENGTH_UNSET} is returned.
+ *
+ * @return The remaining length, or {@link C#LENGTH_UNSET}.
+ */
+ protected final long bytesRemaining() {
+ return bytesToRead == C.LENGTH_UNSET ? bytesToRead : bytesToRead - bytesRead;
+ }
+
+ /**
+ * Establishes a connection, following redirects to do so where permitted.
+ */
+ private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
+ URL url = new URL(dataSpec.uri.toString());
+ @HttpMethod int httpMethod = dataSpec.httpMethod;
+ byte[] httpBody = dataSpec.httpBody;
+ long position = dataSpec.position;
+ long length = dataSpec.length;
+ boolean allowGzip = dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP);
+
+ if (!allowCrossProtocolRedirects) {
+ // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection
+ // automatically. This is the behavior we want, so use it.
+ return makeConnection(
+ url, httpMethod, httpBody, position, length, allowGzip, true /* followRedirects */);
+ }
+
+ // We need to handle redirects ourselves to allow cross-protocol redirects.
+ int redirectCount = 0;
+ while (redirectCount++ <= MAX_REDIRECTS) {
+ HttpURLConnection connection =
+ makeConnection(
+ url, httpMethod, httpBody, position, length, allowGzip, false /* followRedirects */);
+ int responseCode = connection.getResponseCode();
+ String location = connection.getHeaderField("Location");
+ if ((httpMethod == DataSpec.HTTP_METHOD_GET || httpMethod == DataSpec.HTTP_METHOD_HEAD)
+ && (responseCode == HttpURLConnection.HTTP_MULT_CHOICE
+ || responseCode == HttpURLConnection.HTTP_MOVED_PERM
+ || responseCode == HttpURLConnection.HTTP_MOVED_TEMP
+ || responseCode == HttpURLConnection.HTTP_SEE_OTHER
+ || responseCode == HTTP_STATUS_TEMPORARY_REDIRECT
+ || responseCode == HTTP_STATUS_PERMANENT_REDIRECT)) {
+ connection.disconnect();
+ url = handleRedirect(url, location);
+ } else if (httpMethod == DataSpec.HTTP_METHOD_POST
+ && (responseCode == HttpURLConnection.HTTP_MULT_CHOICE
+ || responseCode == HttpURLConnection.HTTP_MOVED_PERM
+ || responseCode == HttpURLConnection.HTTP_MOVED_TEMP
+ || responseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
+ // POST request follows the redirect and is transformed into a GET request.
+ connection.disconnect();
+ httpMethod = DataSpec.HTTP_METHOD_GET;
+ httpBody = null;
+ url = handleRedirect(url, location);
+ } else {
+ return connection;
+ }
+ }
+
+ // If we get here we've been redirected more times than are permitted.
+ throw new NoRouteToHostException("Too many redirects: " + redirectCount);
+ }
+
+ /**
+ * Configures a connection and opens it.
+ *
+ * @param url The url to connect to.
+ * @param httpMethod The http method.
+ * @param httpBody The body data.
+ * @param position The byte offset of the requested data.
+ * @param length The length of the requested data, or {@link C#LENGTH_UNSET}.
+ * @param allowGzip Whether to allow the use of gzip.
+ * @param followRedirects Whether to follow redirects.
+ */
+ private HttpURLConnection makeConnection(
+ URL url,
+ @HttpMethod int httpMethod,
+ byte[] httpBody,
+ long position,
+ long length,
+ boolean allowGzip,
+ boolean followRedirects)
+ throws IOException {
+ HttpURLConnection connection;
+ if(url.getProtocol().endsWith("https")) {
+ /**去除证书限制**/
+ connection = (HttpsURLConnection) url.openConnection();
+ ((HttpsURLConnection) connection).setHostnameVerifier(new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ });
+ // Create a trust manager that does not validate certificate chains
+ final TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ @Override
+ public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ @Override
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+ };
+ // Install the all-trusting trust manager
+ final SSLContext sslContext;
+ try {
+ sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+ // Create an ssl socket factory with our all-trusting manager
+ final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+ ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory);
+ ((HttpsURLConnection) connection).setHostnameVerifier(new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ });
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (KeyManagementException e) {
+ e.printStackTrace();
+ }
+ /**去除证书限制**/
+ } else {
+ connection = (HttpURLConnection) url.openConnection();
+ }
+ connection.setConnectTimeout(connectTimeoutMillis);
+ connection.setReadTimeout(readTimeoutMillis);
+ if (defaultRequestProperties != null) {
+ for (Map.Entry
+ * This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}.
+ *
+ * @throws InterruptedIOException If the thread is interrupted during the operation.
+ * @throws EOFException If the end of the input stream is reached before the bytes are skipped.
+ */
+ private void skipInternal() throws IOException {
+ if (bytesSkipped == bytesToSkip) {
+ return;
+ }
+
+ // Acquire the shared skip buffer.
+ byte[] skipBuffer = skipBufferReference.getAndSet(null);
+ if (skipBuffer == null) {
+ skipBuffer = new byte[4096];
+ }
+
+ while (bytesSkipped != bytesToSkip) {
+ int readLength = (int) Math.min(bytesToSkip - bytesSkipped, skipBuffer.length);
+ int read = inputStream.read(skipBuffer, 0, readLength);
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedIOException();
+ }
+ if (read == -1) {
+ throw new EOFException();
+ }
+ bytesSkipped += read;
+ bytesTransferred(read);
+ }
+
+ // Release the shared skip buffer.
+ skipBufferReference.set(skipBuffer);
+ }
+
+ /**
+ * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
+ * index {@code offset}.
+ *
+ * This method blocks until at least one byte of data can be read, the end of the opened range is
+ * detected, or an exception is thrown.
+ *
+ * @param buffer The buffer into which the read data should be stored.
+ * @param offset The start offset into {@code buffer} at which data should be written.
+ * @param readLength The maximum number of bytes to read.
+ * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened
+ * range is reached.
+ * @throws IOException If an error occurs reading from the source.
+ */
+ private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
+ if (readLength == 0) {
+ return 0;
+ }
+ if (bytesToRead != C.LENGTH_UNSET) {
+ long bytesRemaining = bytesToRead - bytesRead;
+ if (bytesRemaining == 0) {
+ return C.RESULT_END_OF_INPUT;
+ }
+ readLength = (int) Math.min(readLength, bytesRemaining);
+ }
+
+ int read = inputStream.read(buffer, offset, readLength);
+ if (read == -1) {
+ if (bytesToRead != C.LENGTH_UNSET) {
+ // End of stream reached having not read sufficient data.
+ throw new EOFException();
+ }
+ return C.RESULT_END_OF_INPUT;
+ }
+
+ bytesRead += read;
+ bytesTransferred(read);
+ return read;
+ }
+
+ /**
+ * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can
+ * block for a long time if the stream has a lot of data remaining. Call this method before
+ * closing the input stream to make a best effort to cause the input stream to encounter an
+ * unexpected end of input, working around this issue. On other platform API levels, the method
+ * does nothing.
+ *
+ * @param connection The connection whose {@link InputStream} should be terminated.
+ * @param bytesRemaining The number of bytes remaining to be read from the input stream if its
+ * length is known. {@link C#LENGTH_UNSET} otherwise.
+ */
+ private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) {
+ if (Util.SDK_INT != 19 && Util.SDK_INT != 20) {
+ return;
+ }
+
+ try {
+ InputStream inputStream = connection.getInputStream();
+ if (bytesRemaining == C.LENGTH_UNSET) {
+ // If the input stream has already ended, do nothing. The socket may be re-used.
+ if (inputStream.read() == -1) {
+ return;
+ }
+ } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) {
+ // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be
+ // re-used.
+ return;
+ }
+ String className = inputStream.getClass().getName();
+ if ("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream".equals(className)
+ || "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream"
+ .equals(className)) {
+ Class> superclass = inputStream.getClass().getSuperclass();
+ Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput");
+ unexpectedEndOfInput.setAccessible(true);
+ unexpectedEndOfInput.invoke(inputStream);
+ }
+ } catch (Exception e) {
+ // If an IOException then the connection didn't ever have an input stream, or it was closed
+ // already. If another type of exception then something went wrong, most likely the device
+ // isn't using okhttp.
+ }
+ }
+
+
+ /**
+ * Closes the current connection quietly, if there is one.
+ */
+ private void closeConnectionQuietly() {
+ if (connection != null) {
+ try {
+ connection.disconnect();
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error while disconnecting", e);
+ }
+ connection = null;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/source/DefaultHttpDataSourceFactory.java b/app/src/main/java/com/example/gsyvideoplayer/source/DefaultHttpDataSourceFactory.java
new file mode 100644
index 000000000..63e528519
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/source/DefaultHttpDataSourceFactory.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.gsyvideoplayer.source;
+
+import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
+import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
+import com.google.android.exoplayer2.upstream.TransferListener;
+
+import androidx.annotation.Nullable;
+
+/** A {@link Factory} that produces {@link DefaultHttpDataSource} instances. */
+public final class DefaultHttpDataSourceFactory extends BaseFactory {
+
+ private final String userAgent;
+ private final @Nullable
+ TransferListener listener;
+ private final int connectTimeoutMillis;
+ private final int readTimeoutMillis;
+ private final boolean allowCrossProtocolRedirects;
+
+ /**
+ * Constructs a DefaultHttpDataSourceFactory. Sets {@link
+ * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link
+ * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables
+ * cross-protocol redirects.
+ *
+ * @param userAgent The User-Agent string that should be used.
+ */
+ public DefaultHttpDataSourceFactory(String userAgent) {
+ this(userAgent, null);
+ }
+
+ /**
+ * Constructs a DefaultHttpDataSourceFactory. Sets {@link
+ * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link
+ * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables
+ * cross-protocol redirects.
+ *
+ * @param userAgent The User-Agent string that should be used.
+ * @param listener An optional listener.
+ * @see #DefaultHttpDataSourceFactory(String, TransferListener, int, int, boolean)
+ */
+ public DefaultHttpDataSourceFactory(String userAgent, @Nullable TransferListener listener) {
+ this(userAgent, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
+ DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false);
+ }
+
+ /**
+ * @param userAgent The User-Agent string that should be used.
+ * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
+ * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
+ * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in
+ * milliseconds. A timeout of zero is interpreted as an infinite timeout.
+ * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
+ * to HTTPS and vice versa) are enabled.
+ */
+ public DefaultHttpDataSourceFactory(
+ String userAgent,
+ int connectTimeoutMillis,
+ int readTimeoutMillis,
+ boolean allowCrossProtocolRedirects) {
+ this(
+ userAgent,
+ /* listener= */ null,
+ connectTimeoutMillis,
+ readTimeoutMillis,
+ allowCrossProtocolRedirects);
+ }
+
+ /**
+ * @param userAgent The User-Agent string that should be used.
+ * @param listener An optional listener.
+ * @param connectTimeoutMillis The connection timeout that should be used when requesting remote
+ * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout.
+ * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in
+ * milliseconds. A timeout of zero is interpreted as an infinite timeout.
+ * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
+ * to HTTPS and vice versa) are enabled.
+ */
+ public DefaultHttpDataSourceFactory(
+ String userAgent,
+ @Nullable TransferListener listener,
+ int connectTimeoutMillis,
+ int readTimeoutMillis,
+ boolean allowCrossProtocolRedirects) {
+ this.userAgent = userAgent;
+ this.listener = listener;
+ this.connectTimeoutMillis = connectTimeoutMillis;
+ this.readTimeoutMillis = readTimeoutMillis;
+ this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
+ }
+
+ @Override
+ protected DefaultHttpDataSource createDataSourceInternal(
+ HttpDataSource.RequestProperties defaultRequestProperties) {
+ DefaultHttpDataSource dataSource =
+ new DefaultHttpDataSource(
+ userAgent,
+ /* contentTypePredicate= */ null,
+ connectTimeoutMillis,
+ readTimeoutMillis,
+ allowCrossProtocolRedirects,
+ defaultRequestProperties);
+ if (listener != null) {
+ dataSource.addTransferListener(listener);
+ }
+ return dataSource;
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchDetailActivity.java b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchDetailActivity.java
new file mode 100644
index 000000000..f29403cee
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchDetailActivity.java
@@ -0,0 +1,146 @@
+package com.example.gsyvideoplayer.switchplay;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.view.ViewCompat;
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.GSYVideoManager;
+import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack;
+import com.shuyu.gsyvideoplayer.utils.OrientationUtils;
+
+/**
+ * 切换详情
+ */
+public class SwitchDetailActivity extends AppCompatActivity {
+
+ public static final String URL = "url";
+ private static final String OPTION_VIEW = "view";
+
+ SwitchVideo detailPlayer;
+
+ private boolean isPlay = true;
+ private boolean isPause;
+
+ private OrientationUtils orientationUtils;
+
+ public static void startTActivity(Activity activity, View transitionView) {
+ Intent intent = new Intent(activity, SwitchDetailActivity.class);
+ // 这里指定了共享的视图元素
+ ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, transitionView, OPTION_VIEW);
+ ActivityCompat.startActivity(activity, intent, options.toBundle());
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.switch_activity_detail_player);
+
+ detailPlayer = (SwitchVideo) findViewById(R.id.detail_player);
+
+
+ //增加title
+ detailPlayer.getTitleTextView().setVisibility(View.GONE);
+ detailPlayer.getBackButton().setVisibility(View.GONE);
+
+ //外部辅助的旋转,帮助全屏
+ orientationUtils = new OrientationUtils(this, detailPlayer);
+ //初始化不打开外部的旋转
+ orientationUtils.setEnable(false);
+
+ SwitchUtil.optionPlayer(detailPlayer, getIntent().getStringExtra(URL), true, "这是title");
+
+ SwitchUtil.clonePlayState(detailPlayer);
+
+ detailPlayer.setIsTouchWiget(true);
+
+ detailPlayer.setVideoAllCallBack(new GSYSampleCallBack() {
+ @Override
+ public void onPrepared(String url, Object... objects) {
+ super.onPrepared(url, objects);
+ //开始播放了才能旋转和全屏
+ orientationUtils.setEnable(true);
+ }
+
+ @Override
+ public void onQuitFullscreen(String url, Object... objects) {
+ super.onQuitFullscreen(url, objects);
+ if (orientationUtils != null) {
+ orientationUtils.backToProtVideo();
+ }
+ }
+ });
+
+ detailPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //直接横屏
+ orientationUtils.resolveByClick();
+ //第一个true是否需要隐藏actionbar,第二个true是否需要隐藏statusbar
+ detailPlayer.startWindowFullscreen(SwitchDetailActivity.this, true, true);
+ }
+ });
+
+ detailPlayer.setSurfaceToPlay();
+
+
+ // 这里指定了被共享的视图元素
+ ViewCompat.setTransitionName(detailPlayer, OPTION_VIEW);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (orientationUtils != null) {
+ orientationUtils.backToProtVideo();
+ }
+ if (GSYVideoManager.backFromWindowFull(this)) {
+ return;
+ }
+ super.onBackPressed();
+ }
+
+
+ @Override
+ protected void onPause() {
+ detailPlayer.getCurrentPlayer().onVideoPause();
+ super.onPause();
+ isPause = true;
+ }
+
+ @Override
+ protected void onResume() {
+ detailPlayer.getCurrentPlayer().onVideoResume(false);
+ super.onResume();
+ isPause = false;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ detailPlayer.getGSYVideoManager().setListener(detailPlayer.getGSYVideoManager().lastListener());
+ detailPlayer.getGSYVideoManager().setLastListener(null);
+ GSYVideoManager.releaseAllVideos();
+ if (orientationUtils != null)
+ orientationUtils.releaseListener();
+
+ SwitchUtil.release();
+ }
+
+
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ //如果旋转了就全屏
+ if (isPlay && !isPause) {
+ detailPlayer.onConfigurationChanged(this, newConfig, orientationUtils, true, true);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoActivity.java b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoActivity.java
new file mode 100644
index 000000000..fd60cc510
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoActivity.java
@@ -0,0 +1,83 @@
+package com.example.gsyvideoplayer.switchplay;
+
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.GSYVideoManager;
+
+/**
+ * 可切换列表
+ */
+public class SwitchListVideoActivity extends AppCompatActivity {
+
+ ListView videoList;
+
+ SwitchListVideoAdapter listNormalAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_list_video);
+
+ videoList = (ListView)findViewById(R.id.video_list);
+
+ listNormalAdapter = new SwitchListVideoAdapter(this);
+ videoList.setAdapter(listNormalAdapter);
+
+ videoList.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ int lastVisibleItem = firstVisibleItem + visibleItemCount;
+ //大于0说明有播放
+ if (GSYVideoManager.instance().getPlayPosition() >= 0) {
+ //当前播放的位置
+ int position = GSYVideoManager.instance().getPlayPosition();
+ //对应的播放列表TAG
+ if (GSYVideoManager.instance().getPlayTag().equals(SwitchListVideoAdapter.TAG)
+ && (position < firstVisibleItem || position > lastVisibleItem)) {
+ if(GSYVideoManager.isFullState(SwitchListVideoActivity.this)) {
+ return;
+ }
+ //如果滑出去了上面和下面就是否,和今日头条一样
+ GSYVideoManager.releaseAllVideos();
+ listNormalAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (GSYVideoManager.backFromWindowFull(this)) {
+ return;
+ }
+ super.onBackPressed();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ //GSYVideoManager.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ //GSYVideoManager.onResume();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ GSYVideoManager.releaseAllVideos();
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoAdapter.java b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoAdapter.java
new file mode 100644
index 000000000..1698d95ee
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/switchplay/SwitchListVideoAdapter.java
@@ -0,0 +1,109 @@
+package com.example.gsyvideoplayer.switchplay;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+
+import com.example.gsyvideoplayer.R;
+import com.example.gsyvideoplayer.model.VideoModel;
+import com.shuyu.gsyvideoplayer.GSYVideoManager;
+import com.shuyu.gsyvideoplayer.utils.OrientationUtils;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SwitchListVideoAdapter extends BaseAdapter {
+
+ public static final String TAG = "SwitchListVideoAdapter";
+
+ private List
- * 配置弹幕使用的播放器,目前使用的是本地模拟数据。
- *
- * 模拟数据的弹幕时常比较短,后面的时长点是没有数据的。
- *
- * 注意:这只是一个例子,演示如何集合弹幕,需要完善如弹出输入弹幕等的,可以自行完善。
- * 注意:b站的弹幕so只有v5 v7 x86、没有64,所以记得配置上ndk过滤。
+ Created by guoshuyu on 2017/2/16.
+
+ 配置弹幕使用的播放器,目前使用的是本地模拟数据。
+
+ 模拟数据的弹幕时常比较短,后面的时长点是没有数据的。
+
+ 注意:这只是一个例子,演示如何集合弹幕,需要完善如弹出输入弹幕等的,可以自行完善。
+ 注意:b站的弹幕so只有v5 v7 x86、没有64,所以记得配置上ndk过滤。
*/
public class DanmakuVideoPlayer extends StandardGSYVideoPlayer {
@@ -72,6 +62,8 @@ public class DanmakuVideoPlayer extends StandardGSYVideoPlayer {
private boolean mDanmaKuShow = true;
+ private File mDumakuFile;
+
public DanmakuVideoPlayer(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
@@ -115,19 +107,24 @@ public void onPrepared() {
@Override
public void onVideoPause() {
super.onVideoPause();
- if (mDanmakuView != null && mDanmakuView.isPrepared()) {
- mDanmakuView.pause();
- }
+ danmakuOnPause();
}
@Override
- public void onVideoResume() {
- super.onVideoResume();
- if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
- mDanmakuView.resume();
- }
+ public void onVideoResume(boolean isResume) {
+ super.onVideoResume(isResume);
+ danmakuOnResume();
}
+ @Override
+ protected void clickStartIcon() {
+ super.clickStartIcon();
+ if (mCurrentState == CURRENT_STATE_PLAYING) {
+ danmakuOnResume();
+ } else if (mCurrentState == CURRENT_STATE_PAUSE) {
+ danmakuOnPause();
+ }
+ }
@Override
public void onCompletion() {
@@ -162,9 +159,15 @@ public void onClick(View v) {
}
}
+ @Override
+ protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
+ ((DanmakuVideoPlayer) to).mDumakuFile = ((DanmakuVideoPlayer) from).mDumakuFile;
+ super.cloneParams(from, to);
+ }
+
/**
- * 处理播放器在全屏切换时,弹幕显示的逻辑
- * 需要格外注意的是,因为全屏和小屏,是切换了播放器,所以需要同步之间的弹幕状态
+ 处理播放器在全屏切换时,弹幕显示的逻辑
+ 需要格外注意的是,因为全屏和小屏,是切换了播放器,所以需要同步之间的弹幕状态
*/
@Override
public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
@@ -180,8 +183,8 @@ public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionB
}
/**
- * 处理播放器在退出全屏时,弹幕显示的逻辑
- * 需要格外注意的是,因为全屏和小屏,是切换了播放器,所以需要同步之间的弹幕状态
+ 处理播放器在退出全屏时,弹幕显示的逻辑
+ 需要格外注意的是,因为全屏和小屏,是切换了播放器,所以需要同步之间的弹幕状态
*/
@Override
protected void resolveNormalVideoShow(View oldF, ViewGroup vp, GSYVideoPlayer gsyVideoPlayer) {
@@ -198,6 +201,25 @@ protected void resolveNormalVideoShow(View oldF, ViewGroup vp, GSYVideoPlayer gs
}
}
+ protected void danmakuOnPause() {
+ if (mDanmakuView != null && mDanmakuView.isPrepared()) {
+ mDanmakuView.pause();
+ }
+ }
+
+ protected void danmakuOnResume() {
+ if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) {
+ mDanmakuView.resume();
+ }
+ }
+
+ public void setDanmaKuStream(File is) {
+ mDumakuFile = is;
+ if (!getDanmakuView().isPrepared()) {
+ onPrepareDanmaku((DanmakuVideoPlayer) getCurrentPlayer());
+ }
+ }
+
private void initDanmaku() {
// 设置最大显示行数
@@ -215,8 +237,13 @@ private void initDanmaku() {
.setMaximumLines(maxLinesPair)
.preventOverlapping(overlappingEnablePair);
if (mDanmakuView != null) {
- //todo 替换成你的数据流
+ if (mDumakuFile != null) {
+ mParser = createParser(getIsStream(mDumakuFile));
+ }
+
+ //todo 这是为了demo效果,实际上需要去掉这个,外部传输文件进来
mParser = createParser(this.getResources().openRawResource(R.raw.comments));
+
mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() {
@Override
public void updateTimer(DanmakuTimer timer) {
@@ -247,8 +274,32 @@ public void prepared() {
}
}
+ private InputStream getIsStream(File file) {
+ try {
+ InputStream instream = new FileInputStream(file);
+ InputStreamReader inputreader = new InputStreamReader(instream);
+ BufferedReader buffreader = new BufferedReader(inputreader);
+ String line;
+ StringBuilder sb1 = new StringBuilder();
+ sb1.append("");
+ //分行读取
+ while ((line = buffreader.readLine()) != null) {
+ sb1.append(line);
+ }
+ sb1.append("");
+ Log.e("3333333", sb1.toString());
+ instream.close();
+ return new ByteArrayInputStream(sb1.toString().getBytes());
+ } catch (java.io.FileNotFoundException e) {
+ Log.d("TestFile", "The File doesn't not exist.");
+ } catch (IOException e) {
+ Log.d("TestFile", e.getMessage());
+ }
+ return null;
+ }
+
/**
- * 弹幕的显示与关闭
+ 弹幕的显示与关闭
*/
private void resolveDanmakuShow() {
post(new Runnable() {
@@ -269,30 +320,29 @@ public void run() {
}
/**
- * 开始播放弹幕
+ 开始播放弹幕
*/
private void onPrepareDanmaku(DanmakuVideoPlayer gsyVideoPlayer) {
- if (gsyVideoPlayer.getDanmakuView() != null && !gsyVideoPlayer.getDanmakuView().isPrepared()) {
+ if (gsyVideoPlayer.getDanmakuView() != null && !gsyVideoPlayer.getDanmakuView().isPrepared() && gsyVideoPlayer.getParser() != null) {
gsyVideoPlayer.getDanmakuView().prepare(gsyVideoPlayer.getParser(),
gsyVideoPlayer.getDanmakuContext());
}
}
/**
- * 弹幕偏移
+ 弹幕偏移
*/
private void resolveDanmakuSeek(DanmakuVideoPlayer gsyVideoPlayer, long time) {
- if (GSYVideoManager.instance().getMediaPlayer() != null && mHadPlay
- && gsyVideoPlayer.getDanmakuView() != null && gsyVideoPlayer.getDanmakuView().isPrepared()) {
+ if (mHadPlay && gsyVideoPlayer.getDanmakuView() != null && gsyVideoPlayer.getDanmakuView().isPrepared()) {
gsyVideoPlayer.getDanmakuView().seekTo(time);
}
}
/**
- * 创建解析器对象,解析输入流
- *
- * @param stream
- * @return
+ 创建解析器对象,解析输入流
+
+ @param stream
+ @return
*/
private BaseDanmakuParser createParser(InputStream stream) {
@@ -321,7 +371,7 @@ protected Danmakus parse() {
}
/**
- * 释放弹幕控件
+ 释放弹幕控件
*/
private void releaseDanmaku(DanmakuVideoPlayer danmakuVideoPlayer) {
if (danmakuVideoPlayer != null && danmakuVideoPlayer.getDanmakuView() != null) {
@@ -331,6 +381,11 @@ private void releaseDanmaku(DanmakuVideoPlayer danmakuVideoPlayer) {
}
public BaseDanmakuParser getParser() {
+ if (mParser == null) {
+ if (mDumakuFile != null) {
+ mParser = createParser(getIsStream(mDumakuFile));
+ }
+ }
return mParser;
}
@@ -359,7 +414,7 @@ public boolean getDanmaKuShow() {
}
/**
- * 模拟添加弹幕数据
+ 模拟添加弹幕数据
*/
private void addDanmaku(boolean islive) {
BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL);
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/EmptyControlVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/EmptyControlVideo.java
new file mode 100644
index 000000000..390385ad3
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/EmptyControlVideo.java
@@ -0,0 +1,51 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+
+/**
+ * 无任何控制ui的播放
+ * Created by guoshuyu on 2017/8/6.
+ */
+
+public class EmptyControlVideo extends StandardGSYVideoPlayer {
+
+ public EmptyControlVideo(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public EmptyControlVideo(Context context) {
+ super(context);
+ }
+
+ public EmptyControlVideo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.empty_control_video;
+ }
+
+ @Override
+ protected void touchSurfaceMoveFullLogic(float absDeltaX, float absDeltaY) {
+ super.touchSurfaceMoveFullLogic(absDeltaX, absDeltaY);
+ //不给触摸快进,如果需要,屏蔽下方代码即可
+ mChangePosition = false;
+
+ //不给触摸音量,如果需要,屏蔽下方代码即可
+ mChangeVolume = false;
+
+ //不给触摸亮度,如果需要,屏蔽下方代码即可
+ mBrightness = false;
+ }
+
+ @Override
+ protected void touchDoubleUp() {
+ //super.touchDoubleUp();
+ //不需要双击暂停
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/FloatingVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/FloatingVideo.java
new file mode 100644
index 000000000..142ec3f22
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/FloatingVideo.java
@@ -0,0 +1,186 @@
+package com.example.gsyvideoplayer.video;
+
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.AudioManager;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.utils.NetworkUtils;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.hideNavKey;
+
+/**
+ * 多窗体下的悬浮窗页面支持Video
+ * Created by shuyu on 2017/12/25.
+ */
+
+public class FloatingVideo extends StandardGSYVideoPlayer {
+
+ protected Timer mDismissControlViewTimer;
+
+ /**
+ * 1.5.0开始加入,如果需要不同布局区分功能,需要重载
+ */
+ public FloatingVideo(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public FloatingVideo(Context context) {
+ super(context);
+ }
+
+ public FloatingVideo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ if (getActivityContext() != null) {
+ this.mContext = getActivityContext();
+ } else {
+ this.mContext = context;
+ }
+
+ initInflate(mContext);
+
+ mTextureViewContainer = (ViewGroup) findViewById(R.id.surface_container);
+ mStartButton = findViewById(R.id.start);
+
+ if (isInEditMode())
+ return;
+ mScreenWidth = getActivityContext().getResources().getDisplayMetrics().widthPixels;
+ mScreenHeight = getActivityContext().getResources().getDisplayMetrics().heightPixels;
+ mAudioManager = (AudioManager) getActivityContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
+ mStartButton = findViewById(com.shuyu.gsyvideoplayer.R.id.start);
+ mStartButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ clickStartIcon();
+ }
+ });
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.layout_floating_video;
+ }
+
+
+ @Override
+ protected void startPrepare() {
+ if (getGSYVideoManager().listener() != null) {
+ getGSYVideoManager().listener().onCompletion();
+ }
+ getGSYVideoManager().setListener(this);
+ getGSYVideoManager().setPlayTag(mPlayTag);
+ getGSYVideoManager().setPlayPosition(mPlayPosition);
+ mAudioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ //((Activity) getActivityContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mBackUpPlayingBufferState = -1;
+ getGSYVideoManager().prepare(mUrl, mMapHeadData, mLooping, mSpeed, mCache, mCachePath, null);
+ setStateAndUi(CURRENT_STATE_PREPAREING);
+ }
+
+ @Override
+ public void onAutoCompletion() {
+ setStateAndUi(CURRENT_STATE_AUTO_COMPLETE);
+
+ mSaveChangeViewTIme = 0;
+
+ if (mTextureViewContainer.getChildCount() > 0) {
+ mTextureViewContainer.removeAllViews();
+ }
+
+ if (!mIfCurrentIsFullscreen)
+ getGSYVideoManager().setLastListener(null);
+ mAudioManager.abandonAudioFocus(onAudioFocusChangeListener);
+ //((Activity) getActivityContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ releaseNetWorkState();
+
+ if (mVideoAllCallBack != null && isCurrentMediaListener()) {
+ Debuger.printfLog("onAutoComplete");
+ mVideoAllCallBack.onAutoComplete(mOriginUrl, mTitle, this);
+ }
+ }
+
+ @Override
+ public void onCompletion() {
+ //make me normal first
+ setStateAndUi(CURRENT_STATE_NORMAL);
+
+ mSaveChangeViewTIme = 0;
+
+ if (mTextureViewContainer.getChildCount() > 0) {
+ mTextureViewContainer.removeAllViews();
+ }
+
+ if (!mIfCurrentIsFullscreen) {
+ getGSYVideoManager().setListener(null);
+ getGSYVideoManager().setLastListener(null);
+ }
+ getGSYVideoManager().setCurrentVideoHeight(0);
+ getGSYVideoManager().setCurrentVideoWidth(0);
+
+ mAudioManager.abandonAudioFocus(onAudioFocusChangeListener);
+ //((Activity) getActivityContext()).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ releaseNetWorkState();
+
+ }
+
+
+ @Override
+ protected Context getActivityContext() {
+ return getContext();
+ }
+
+
+ @Override
+ protected boolean isShowNetConfirm() {
+ return false;
+ }
+
+ @Override
+ protected void showWifiDialog() {
+ if (!NetworkUtils.isAvailable(mContext)) {
+ //Toast.makeText(mContext, getResources().getString(R.string.no_net), Toast.LENGTH_LONG).show();
+ startPlayLogic();
+ return;
+ }
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivityContext());
+ builder.setMessage(getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi));
+ builder.setPositiveButton(getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_confirm), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ startPlayLogic();
+ }
+ });
+ builder.setNegativeButton(getResources().getString(com.shuyu.gsyvideoplayer.R.string.tips_not_wifi_cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ AlertDialog alertDialog = builder.create();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ } else {
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ }
+ alertDialog.show();
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/LandLayoutVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/LandLayoutVideo.java
index 064412f11..6ef0daebb 100644
--- a/app/src/main/java/com/example/gsyvideoplayer/video/LandLayoutVideo.java
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/LandLayoutVideo.java
@@ -2,10 +2,17 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer;
/**
* Created by shuyu on 2016/12/23.
@@ -13,6 +20,8 @@
*/
public class LandLayoutVideo extends StandardGSYVideoPlayer {
+ private boolean isLinkScroll = false;
+
/**
* 1.5.0开始加入,如果需要不同布局区分功能,需要重载
*/
@@ -29,6 +38,36 @@ public LandLayoutVideo(Context context, AttributeSet attrs) {
}
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ gestureDetector = new GestureDetector(getContext().getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ touchDoubleUp();
+ return super.onDoubleTap(e);
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (!mChangePosition && !mChangeVolume && !mBrightness) {
+ onClickUiToggle();
+ }
+ return super.onSingleTapConfirmed(e);
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ super.onLongPress(e);
+ }
+ });
+ }
+ });
+ }
+
//这个必须配置最上面的构造才能生效
@Override
public int getLayoutId() {
@@ -56,5 +95,35 @@ protected void updateStartImage() {
}
}
+ @Override
+ public int getEnlargeImageRes() {
+ return R.drawable.custom_enlarge;
+ }
+ @Override
+ public int getShrinkImageRes() {
+ return R.drawable.custom_shrink;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (isLinkScroll && !isIfCurrentIsFullscreen()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+
+ @Override
+ protected void resolveNormalVideoShow(View oldF, ViewGroup vp, GSYVideoPlayer gsyVideoPlayer) {
+ LandLayoutVideo landLayoutVideo = (LandLayoutVideo)gsyVideoPlayer;
+ landLayoutVideo.dismissProgressDialog();
+ landLayoutVideo.dismissVolumeDialog();
+ landLayoutVideo.dismissBrightnessDialog();
+ super.resolveNormalVideoShow(oldF, vp, gsyVideoPlayer);
+ }
+
+ public void setLinkScroll(boolean linkScroll) {
+ isLinkScroll = linkScroll;
+ }
}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/ListADVideoPlayer.java b/app/src/main/java/com/example/gsyvideoplayer/video/ListADVideoPlayer.java
new file mode 100644
index 000000000..5b4b031a5
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/ListADVideoPlayer.java
@@ -0,0 +1,46 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.shuyu.gsyvideoplayer.listener.GSYMediaPlayerListener;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.video.GSYADVideoPlayer;
+
+public class ListADVideoPlayer extends GSYADVideoPlayer {
+
+ public ListADVideoPlayer(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public ListADVideoPlayer(Context context) {
+ super(context);
+ }
+
+ public ListADVideoPlayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void startPrepare() {
+ GSYMediaPlayerListener listener = getGSYVideoManager().listener();
+ super.startPrepare();
+ if (listener != null) {
+ listener.onAutoCompletion();
+ }
+ }
+
+ @Override
+ public void onAutoCompletion() {
+ super.onAutoCompletion();
+ if (!isCurrentMediaListener() && mVideoAllCallBack != null) {
+ Debuger.printfLog("onAutoComplete");
+ mVideoAllCallBack.onAutoComplete(mOriginUrl, mTitle, this);
+ }
+ }
+
+ @Override
+ public void onCompletion() {
+ super.onCompletion();
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/MultiSampleVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/MultiSampleVideo.java
new file mode 100644
index 000000000..648c795c5
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/MultiSampleVideo.java
@@ -0,0 +1,149 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.media.AudioManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import com.example.gsyvideoplayer.R;
+import com.example.gsyvideoplayer.video.manager.CustomManager;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge;
+
+
+/**
+ * 多个同时播放的播放控件
+ * Created by guoshuyu on 2018/1/31.
+ */
+
+public class MultiSampleVideo extends StandardGSYVideoPlayer {
+
+ private final static String TAG = "MultiSampleVideo";
+
+
+ ImageView mCoverImage;
+
+ String mCoverOriginUrl;
+
+ int mDefaultRes;
+
+ public MultiSampleVideo(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public MultiSampleVideo(Context context) {
+ super(context);
+ }
+
+ public MultiSampleVideo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ mCoverImage = (ImageView) findViewById(R.id.thumbImage);
+ if (mThumbImageViewLayout != null &&
+ (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) {
+ mThumbImageViewLayout.setVisibility(VISIBLE);
+ }
+ onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS:
+ //todo 判断如果不是外界造成的就不处理
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ //todo 判断如果不是外界造成的就不处理
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public GSYVideoViewBridge getGSYVideoManager() {
+ CustomManager.getCustomManager(getKey()).initContext(getContext().getApplicationContext());
+ return CustomManager.getCustomManager(getKey());
+ }
+
+ @Override
+ protected boolean backFromFull(Context context) {
+ return CustomManager.backFromWindowFull(context, getKey());
+ }
+
+ @Override
+ protected void releaseVideos() {
+ CustomManager.releaseAllVideos(getKey());
+ }
+
+
+ @Override
+ protected int getFullId() {
+ return CustomManager.FULLSCREEN_ID;
+ }
+
+ @Override
+ protected int getSmallId() {
+ return CustomManager.SMALL_ID;
+ }
+
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.video_layout_cover;
+ }
+
+ public void loadCoverImage(String url, int res) {
+ mCoverOriginUrl = url;
+ mDefaultRes = res;
+ Glide.with(getContext().getApplicationContext())
+ .setDefaultRequestOptions(
+ new RequestOptions()
+ .frame(1000000)
+ .centerCrop()
+ .error(res)
+ .placeholder(res))
+ .load(url)
+ .into(mCoverImage);
+ }
+
+ @Override
+ public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
+ GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
+ MultiSampleVideo multiSampleVideo = (MultiSampleVideo) gsyBaseVideoPlayer;
+ multiSampleVideo.loadCoverImage(mCoverOriginUrl, mDefaultRes);
+ return multiSampleVideo;
+ }
+
+
+ @Override
+ public GSYBaseVideoPlayer showSmallVideo(Point size, boolean actionBar, boolean statusBar) {
+ //下面这里替换成你自己的强制转化
+ MultiSampleVideo multiSampleVideo = (MultiSampleVideo) super.showSmallVideo(size, actionBar, statusBar);
+ multiSampleVideo.mStartButton.setVisibility(GONE);
+ multiSampleVideo.mStartButton = null;
+ return multiSampleVideo;
+ }
+
+ public String getKey() {
+ if (mPlayPosition == -22) {
+ Debuger.printfError(getClass().getSimpleName() + " used getKey() " + "******* PlayPosition never set. ********");
+ }
+ if (TextUtils.isEmpty(mPlayTag)) {
+ Debuger.printfError(getClass().getSimpleName() + " used getKey() " + "******* PlayTag never set. ********");
+ }
+ return TAG + mPlayPosition + mPlayTag;
+ }
+}
diff --git a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/video/CustomGSYVideoPlayer.java b/app/src/main/java/com/example/gsyvideoplayer/video/PreViewGSYVideoPlayer.java
similarity index 53%
rename from gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/video/CustomGSYVideoPlayer.java
rename to app/src/main/java/com/example/gsyvideoplayer/video/PreViewGSYVideoPlayer.java
index 6b0685fba..d15350a17 100644
--- a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/video/CustomGSYVideoPlayer.java
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/PreViewGSYVideoPlayer.java
@@ -1,50 +1,50 @@
-package com.shuyu.gsyvideoplayer.video;
+package com.example.gsyvideoplayer.video;
import android.content.Context;
-import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
-import android.view.Surface;
-import android.view.TextureView;
-import android.view.ViewGroup;
+import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
-import com.shuyu.gsyvideoplayer.GSYPreViewManager;
-import com.shuyu.gsyvideoplayer.GSYTextureView;
-import com.shuyu.gsyvideoplayer.GSYVideoManager;
-import com.shuyu.gsyvideoplayer.R;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.CommonUtil;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.video.NormalGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
/**
+ * 进度图小图预览的另类实现
* Created by shuyu on 2016/12/10.
- * 进度图小图预览,目前对本地视频还可以,网络视频效果不好,不建议使用
*/
-public class CustomGSYVideoPlayer extends StandardGSYVideoPlayer {
+public class PreViewGSYVideoPlayer extends NormalGSYVideoPlayer {
private RelativeLayout mPreviewLayout;
- private GSYTextureView mPreviewTexture;
+ private ImageView mPreView;
//是否因为用户点击
private boolean mIsFromUser;
//是否打开滑动预览
- private boolean mOpenPreView;
+ private boolean mOpenPreView = true;
private int mPreProgress = -2;
/**
* 1.5.0开始加入,如果需要不同布局区分功能,需要重载
*/
- public CustomGSYVideoPlayer(Context context, Boolean fullFlag) {
+ public PreViewGSYVideoPlayer(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
- public CustomGSYVideoPlayer(Context context) {
+ public PreViewGSYVideoPlayer(Context context) {
super(context);
}
- public CustomGSYVideoPlayer(Context context, AttributeSet attrs) {
+ public PreViewGSYVideoPlayer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -56,55 +56,17 @@ protected void init(Context context) {
private void initView() {
mPreviewLayout = (RelativeLayout) findViewById(R.id.preview_layout);
+ mPreView = (ImageView) findViewById(R.id.preview_image);
}
@Override
public int getLayoutId() {
- return R.layout.video_layout_custom;
+ return R.layout.video_layout_preview;
}
- @Override
- protected void addTextureView() {
- super.addTextureView();
-
- if (mPreviewLayout.getChildCount() > 0) {
- mPreviewLayout.removeAllViews();
- }
- mPreviewTexture = null;
- mPreviewTexture = new GSYTextureView(getActivityContext());
- mPreviewTexture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- GSYPreViewManager.instance().setDisplay(new Surface(surface));
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- GSYPreViewManager.instance().setDisplay(null);
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-
- }
- });
- mPreviewTexture.setRotation(mRotate);
-
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
- mPreviewLayout.addView(mPreviewTexture, layoutParams);
- }
-
@Override
protected void prepareVideo() {
- GSYPreViewManager.instance().prepare(mUrl, mMapHeadData, mLooping, mSpeed);
super.prepareVideo();
}
@@ -113,18 +75,16 @@ public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromU
super.onProgressChanged(seekBar, progress, fromUser);
if (fromUser && mOpenPreView) {
int width = seekBar.getWidth();
+ int time = progress * getDuration() / 100;
int offset = (int) (width - (getResources().getDimension(R.dimen.seek_bar_image) / 2)) / 100 * progress;
-
+ Debuger.printfError("***************** " + progress);
+ Debuger.printfError("***************** " + time);
+ showPreView(mOriginUrl, time);
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mPreviewLayout.getLayoutParams();
layoutParams.leftMargin = offset;
//设置帧预览图的显示位置
mPreviewLayout.setLayoutParams(layoutParams);
- if (GSYPreViewManager.instance().getMediaPlayer() != null
- && mHadPlay && (mOpenPreView)
- && GSYPreViewManager.instance().isSeekToComplete()) {
- GSYPreViewManager.instance().setSeekToComplete(false);
- int time = progress * getDuration() / 100;
- GSYPreViewManager.instance().getMediaPlayer().seekTo(time);
+ if (mHadPlay && mOpenPreView) {
mPreProgress = progress;
}
}
@@ -165,11 +125,18 @@ protected void setTextAndProgress(int secProgress) {
@Override
public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
- CustomGSYVideoPlayer customGSYVideoPlayer = (CustomGSYVideoPlayer) gsyBaseVideoPlayer;
+ PreViewGSYVideoPlayer customGSYVideoPlayer = (PreViewGSYVideoPlayer) gsyBaseVideoPlayer;
customGSYVideoPlayer.mOpenPreView = mOpenPreView;
return gsyBaseVideoPlayer;
}
+
+ @Override
+ public void onPrepared() {
+ super.onPrepared();
+ startDownFrame(mOriginUrl);
+ }
+
public boolean isOpenPreView() {
return mOpenPreView;
}
@@ -180,4 +147,38 @@ public boolean isOpenPreView() {
public void setOpenPreView(boolean localFile) {
this.mOpenPreView = localFile;
}
+
+
+ private void showPreView(String url, long time) {
+ int width = CommonUtil.dip2px(getContext(), 150);
+ int height = CommonUtil.dip2px(getContext(), 100);
+ Glide.with(getContext().getApplicationContext())
+ .setDefaultRequestOptions(
+ new RequestOptions()
+ //这里限制了只从缓存读取
+ .onlyRetrieveFromCache(true)
+ .frame(1000 * time)
+ .override(width, height)
+ .dontAnimate()
+ .centerCrop())
+ .load(url)
+ .into(mPreView);
+ }
+
+
+ private void startDownFrame(String url) {
+ for (int i = 1; i <= 100; i++) {
+ int time = i * getDuration() / 100;
+ int width = CommonUtil.dip2px(getContext(), 150);
+ int height = CommonUtil.dip2px(getContext(), 100);
+ Glide.with(getContext().getApplicationContext())
+ .setDefaultRequestOptions(
+ new RequestOptions()
+ .frame(1000 * time)
+ .override(width, height)
+ .centerCrop())
+ .load(url).preload(width, height);
+
+ }
+ }
}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/RequestListADVideoPlayer.java b/app/src/main/java/com/example/gsyvideoplayer/video/RequestListADVideoPlayer.java
new file mode 100644
index 000000000..8c34fe1ae
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/RequestListADVideoPlayer.java
@@ -0,0 +1,52 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+
+import com.shuyu.gsyvideoplayer.GSYVideoADManager;
+
+import java.util.Random;
+
+public class RequestListADVideoPlayer extends ListADVideoPlayer {
+
+ public RequestListADVideoPlayer(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public RequestListADVideoPlayer(Context context) {
+ super(context);
+ }
+
+ public RequestListADVideoPlayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void startPlayLogic() {
+ //模拟请求
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ int min = 1;
+ int max = 10;
+ Random random = new Random();
+ int i = random.nextInt(max) % (max - min + 1) + min;
+ if (i % 3 == 0) {
+ mOriginUrl = "http://wdquan-space.b0.upaiyun.com/VIDEO/2018/11/22/ae0645396048_hls_time10.m3u8";
+ mUrl = "http://wdquan-space.b0.upaiyun.com/VIDEO/2018/11/22/ae0645396048_hls_time10.m3u8";
+ mTitle = "ggg2";
+ } else if (i % 4 == 0) {
+ //模拟请求失败
+ onError(0, 0);
+ } else {
+ mOriginUrl = "http://7xjmzj.com1.z0.glb.clouddn.com/20171026175005_JObCxCE2.mp4";
+ mUrl = "http://7xjmzj.com1.z0.glb.clouddn.com/20171026175005_JObCxCE2.mp4";
+ mTitle = "ffff1";
+ }
+ mCache = false;
+ RequestListADVideoPlayer.super.startPlayLogic();
+ }
+ }, 2000);
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/SampleControlVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/SampleControlVideo.java
new file mode 100644
index 000000000..fdde5e217
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/SampleControlVideo.java
@@ -0,0 +1,265 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.GSYVideoType;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer;
+
+
+/**
+ * Created by shuyu on 2016/12/7.
+ * 注意
+ * 这个播放器的demo配置切换到全屏播放器
+ * 这只是单纯的作为全屏播放显示,如果需要做大小屏幕切换,请记得在这里耶设置上视频全屏的需要的自定义配置
+ */
+
+public class SampleControlVideo extends StandardGSYVideoPlayer {
+
+ private TextView mMoreScale;
+
+ private TextView mChangeRotate;
+
+ private TextView mChangeTransform;
+
+ //记住切换数据源类型
+ private int mType = 0;
+
+ private int mTransformSize = 0;
+
+ //数据源
+ private int mSourcePosition = 0;
+
+ /**
+ * 1.5.0开始加入,如果需要不同布局区分功能,需要重载
+ */
+ public SampleControlVideo(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public SampleControlVideo(Context context) {
+ super(context);
+ }
+
+ public SampleControlVideo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ initView();
+ }
+
+ private void initView() {
+ mMoreScale = (TextView) findViewById(R.id.moreScale);
+ mChangeRotate = (TextView) findViewById(R.id.change_rotate);
+ mChangeTransform = (TextView) findViewById(R.id.change_transform);
+
+ //切换清晰度
+ mMoreScale.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mHadPlay) {
+ return;
+ }
+ if (mType == 0) {
+ mType = 1;
+ } else if (mType == 1) {
+ mType = 2;
+ } else if (mType == 2) {
+ mType = 3;
+ } else if (mType == 3) {
+ mType = 4;
+ } else if (mType == 4) {
+ mType = 0;
+ }
+ resolveTypeUI();
+ }
+ });
+
+ //旋转播放角度
+ mChangeRotate.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mHadPlay) {
+ return;
+ }
+ if ((mTextureView.getRotation() - mRotate) == 270) {
+ mTextureView.setRotation(mRotate);
+ mTextureView.requestLayout();
+ } else {
+ mTextureView.setRotation(mTextureView.getRotation() + 90);
+ mTextureView.requestLayout();
+ }
+ }
+ });
+
+ //镜像旋转
+ mChangeTransform.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mHadPlay) {
+ return;
+ }
+ if (mTransformSize == 0) {
+ mTransformSize = 1;
+ } else if (mTransformSize == 1) {
+ mTransformSize = 2;
+ } else if (mTransformSize == 2) {
+ mTransformSize = 0;
+ }
+ resolveTransform();
+ }
+ });
+
+ }
+
+ @Override
+ public void onSurfaceSizeChanged(Surface surface, int width, int height) {
+ super.onSurfaceSizeChanged(surface, width, height);
+ resolveTransform();
+ }
+
+ /**
+ * 处理显示逻辑
+ */
+ @Override
+ public void onSurfaceAvailable(Surface surface) {
+ super.onSurfaceAvailable(surface);
+ resolveRotateUI();
+ resolveTransform();
+ }
+
+ /**
+ * 处理镜像旋转
+ * 注意,暂停时
+ */
+ protected void resolveTransform() {
+ switch (mTransformSize) {
+ case 1: {
+ Matrix transform = new Matrix();
+ transform.setScale(-1, 1, mTextureView.getWidth() / 2, 0);
+ mTextureView.setTransform(transform);
+ mChangeTransform.setText("左右镜像");
+ mTextureView.invalidate();
+ }
+ break;
+ case 2: {
+ Matrix transform = new Matrix();
+ transform.setScale(1, -1, 0, mTextureView.getHeight() / 2);
+ mTextureView.setTransform(transform);
+ mChangeTransform.setText("上下镜像");
+ mTextureView.invalidate();
+ }
+ break;
+ case 0: {
+ Matrix transform = new Matrix();
+ transform.setScale(1, 1, mTextureView.getWidth() / 2, 0);
+ mTextureView.setTransform(transform);
+ mChangeTransform.setText("旋转镜像");
+ mTextureView.invalidate();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.sample_video;
+ }
+
+
+ /**
+ * 全屏时将对应处理参数逻辑赋给全屏播放器
+ *
+ * @param context
+ * @param actionBar
+ * @param statusBar
+ * @return
+ */
+ @Override
+ public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
+ SampleControlVideo sampleVideo = (SampleControlVideo) super.startWindowFullscreen(context, actionBar, statusBar);
+ sampleVideo.mSourcePosition = mSourcePosition;
+ sampleVideo.mType = mType;
+ sampleVideo.mTransformSize = mTransformSize;
+ //sampleVideo.resolveTransform();
+ sampleVideo.resolveTypeUI();
+ //sampleVideo.resolveRotateUI();
+ //这个播放器的demo配置切换到全屏播放器
+ //这只是单纯的作为全屏播放显示,如果需要做大小屏幕切换,请记得在这里耶设置上视频全屏的需要的自定义配置
+ //比如已旋转角度之类的等等
+ //可参考super中的实现
+ return sampleVideo;
+ }
+
+ /**
+ * 推出全屏时将对应处理参数逻辑返回给非播放器
+ *
+ * @param oldF
+ * @param vp
+ * @param gsyVideoPlayer
+ */
+ @Override
+ protected void resolveNormalVideoShow(View oldF, ViewGroup vp, GSYVideoPlayer gsyVideoPlayer) {
+ super.resolveNormalVideoShow(oldF, vp, gsyVideoPlayer);
+ if (gsyVideoPlayer != null) {
+ SampleControlVideo sampleVideo = (SampleControlVideo) gsyVideoPlayer;
+ mSourcePosition = sampleVideo.mSourcePosition;
+ mType = sampleVideo.mType;
+ mTransformSize = sampleVideo.mTransformSize;
+ resolveTypeUI();
+ }
+ }
+
+ /**
+ * 旋转逻辑
+ */
+ private void resolveRotateUI() {
+ if (!mHadPlay) {
+ return;
+ }
+ mTextureView.setRotation(mRotate);
+ mTextureView.requestLayout();
+ }
+
+ /**
+ * 显示比例
+ * 注意,GSYVideoType.setShowType是全局静态生效,除非重启APP。
+ */
+ private void resolveTypeUI() {
+ if (!mHadPlay) {
+ return;
+ }
+ if (mType == 1) {
+ mMoreScale.setText("16:9");
+ GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_16_9);
+ } else if (mType == 2) {
+ mMoreScale.setText("4:3");
+ GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_4_3);
+ } else if (mType == 3) {
+ mMoreScale.setText("全屏");
+ GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL);
+ } else if (mType == 4) {
+ mMoreScale.setText("拉伸全屏");
+ GSYVideoType.setShowType(GSYVideoType.SCREEN_MATCH_FULL);
+ } else if (mType == 0) {
+ mMoreScale.setText("默认比例");
+ GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_DEFAULT);
+ }
+ changeTextureViewShowType();
+ if (mTextureView != null)
+ mTextureView.requestLayout();
+ }
+
+
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/SampleCoverVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/SampleCoverVideo.java
new file mode 100644
index 000000000..121e4f64b
--- /dev/null
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/SampleCoverVideo.java
@@ -0,0 +1,241 @@
+package com.example.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import com.example.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.CommonUtil;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.utils.GSYVideoType;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+
+
+/**
+ * 带封面
+ * Created by guoshuyu on 2017/9/3.
+ */
+
+public class SampleCoverVideo extends StandardGSYVideoPlayer {
+
+ ImageView mCoverImage;
+
+ String mCoverOriginUrl;
+
+ int mDefaultRes;
+
+ public SampleCoverVideo(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public SampleCoverVideo(Context context) {
+ super(context);
+ }
+
+ public SampleCoverVideo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ mCoverImage = (ImageView) findViewById(R.id.thumbImage);
+
+ if (mThumbImageViewLayout != null &&
+ (mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) {
+ mThumbImageViewLayout.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.video_layout_cover;
+ }
+
+ public void loadCoverImage(String url, int res) {
+ mCoverOriginUrl = url;
+ mDefaultRes = res;
+ Glide.with(getContext().getApplicationContext())
+ .setDefaultRequestOptions(
+ new RequestOptions()
+ .frame(1000000)
+ .centerCrop()
+ .error(res)
+ .placeholder(res))
+ .load(url)
+ .into(mCoverImage);
+ }
+
+ @Override
+ public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
+ GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
+ SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) gsyBaseVideoPlayer;
+ sampleCoverVideo.loadCoverImage(mCoverOriginUrl, mDefaultRes);
+ return gsyBaseVideoPlayer;
+ }
+
+
+ @Override
+ public GSYBaseVideoPlayer showSmallVideo(Point size, boolean actionBar, boolean statusBar) {
+ //下面这里替换成你自己的强制转化
+ SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) super.showSmallVideo(size, actionBar, statusBar);
+ sampleCoverVideo.mStartButton.setVisibility(GONE);
+ sampleCoverVideo.mStartButton = null;
+ return sampleCoverVideo;
+ }
+
+ @Override
+ protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
+ super.cloneParams(from, to);
+ SampleCoverVideo sf = (SampleCoverVideo) from;
+ SampleCoverVideo st = (SampleCoverVideo) to;
+ st.mShowFullAnimation = sf.mShowFullAnimation;
+ }
+
+
+ /**
+ * 退出window层播放全屏效果
+ */
+ @SuppressWarnings("ResourceType")
+ @Override
+ protected void clearFullscreenLayout() {
+ if (!mFullAnimEnd) {
+ return;
+ }
+ mIfCurrentIsFullscreen = false;
+ int delay = 0;
+ if (mOrientationUtils != null) {
+ delay = mOrientationUtils.backToProtVideo();
+ mOrientationUtils.setEnable(false);
+ if (mOrientationUtils != null) {
+ mOrientationUtils.releaseListener();
+ mOrientationUtils = null;
+ }
+ }
+
+ if (!mShowFullAnimation) {
+ delay = 0;
+ }
+
+ final ViewGroup vp = (CommonUtil.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
+ final View oldF = vp.findViewById(getFullId());
+ if (oldF != null) {
+ //此处fix bug#265,推出全屏的时候,虚拟按键问题
+ SampleCoverVideo gsyVideoPlayer = (SampleCoverVideo) oldF;
+ gsyVideoPlayer.mIfCurrentIsFullscreen = false;
+ }
+
+ if (delay == 0) {
+ backToNormal();
+ } else {
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ backToNormal();
+ }
+ }, delay);
+ }
+
+ }
+
+
+ /******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/
+ @Override
+ public void onSurfaceUpdated(Surface surface) {
+ super.onSurfaceUpdated(surface);
+ if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
+ mThumbImageViewLayout.setVisibility(INVISIBLE);
+ }
+ }
+
+ @Override
+ protected void setViewShowState(View view, int visibility) {
+ if (view == mThumbImageViewLayout && visibility != VISIBLE) {
+ return;
+ }
+ super.setViewShowState(view, visibility);
+ }
+
+ @Override
+ public void onSurfaceAvailable(Surface surface) {
+ super.onSurfaceAvailable(surface);
+ if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
+ if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
+ mThumbImageViewLayout.setVisibility(INVISIBLE);
+ }
+ }
+ }
+
+ /******************* 下方重载方法,在播放开始不显示底部进度和按键,不需要可屏蔽 ********************/
+
+ protected boolean byStartedClick;
+
+ @Override
+ protected void onClickUiToggle() {
+ if (mIfCurrentIsFullscreen && mLockCurScreen && mNeedLockFull) {
+ setViewShowState(mLockScreen, VISIBLE);
+ return;
+ }
+ byStartedClick = true;
+ super.onClickUiToggle();
+
+ }
+
+ @Override
+ protected void changeUiToNormal() {
+ super.changeUiToNormal();
+ byStartedClick = false;
+ }
+
+ @Override
+ protected void changeUiToPreparingShow() {
+ super.changeUiToPreparingShow();
+ Debuger.printfLog("Sample changeUiToPreparingShow");
+ setViewShowState(mBottomContainer, INVISIBLE);
+ setViewShowState(mStartButton, INVISIBLE);
+ }
+
+ @Override
+ protected void changeUiToPlayingBufferingShow() {
+ super.changeUiToPlayingBufferingShow();
+ Debuger.printfLog("Sample changeUiToPlayingBufferingShow");
+ if (!byStartedClick) {
+ setViewShowState(mBottomContainer, INVISIBLE);
+ setViewShowState(mStartButton, INVISIBLE);
+ }
+ }
+
+ @Override
+ protected void changeUiToPlayingShow() {
+ super.changeUiToPlayingShow();
+ Debuger.printfLog("Sample changeUiToPlayingShow");
+ if (!byStartedClick) {
+ setViewShowState(mBottomContainer, INVISIBLE);
+ setViewShowState(mStartButton, INVISIBLE);
+ }
+ }
+
+ @Override
+ public void startAfterPrepared() {
+ super.startAfterPrepared();
+ Debuger.printfLog("Sample startAfterPrepared");
+ setViewShowState(mBottomContainer, INVISIBLE);
+ setViewShowState(mStartButton, INVISIBLE);
+ setViewShowState(mBottomProgressBar, VISIBLE);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ byStartedClick = true;
+ super.onStartTrackingTouch(seekBar);
+ }
+}
diff --git a/app/src/main/java/com/example/gsyvideoplayer/video/SampleVideo.java b/app/src/main/java/com/example/gsyvideoplayer/video/SampleVideo.java
index 6e0922fc2..9d27a5a7a 100644
--- a/app/src/main/java/com/example/gsyvideoplayer/video/SampleVideo.java
+++ b/app/src/main/java/com/example/gsyvideoplayer/video/SampleVideo.java
@@ -5,19 +5,18 @@
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.util.AttributeSet;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.gsyvideoplayer.R;
import com.example.gsyvideoplayer.model.SwitchVideoModel;
import com.example.gsyvideoplayer.view.SwitchVideoTypeDialog;
-import com.shuyu.gsyvideoplayer.GSYVideoManager;
-import com.shuyu.gsyvideoplayer.GSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer;
import com.shuyu.gsyvideoplayer.utils.GSYVideoType;
-import com.shuyu.gsyvideoplayer.video.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
import java.io.File;
@@ -51,6 +50,8 @@ public class SampleVideo extends StandardGSYVideoPlayer {
//数据源
private int mSourcePosition = 0;
+ private String mTypeText = "标准";
+
/**
* 1.5.0开始加入,如果需要不同布局区分功能,需要重载
*/
@@ -82,7 +83,7 @@ private void initView() {
mMoreScale.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- if(!mHadPlay) {
+ if (!mHadPlay) {
return;
}
if (mType == 0) {
@@ -93,7 +94,7 @@ public void onClick(View v) {
mType = 3;
} else if (mType == 3) {
mType = 4;
- } else if(mType == 4) {
+ } else if (mType == 4) {
mType = 0;
}
resolveTypeUI();
@@ -112,19 +113,15 @@ public void onClick(View v) {
mChangeRotate.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- if(!mHadPlay) {
+ if (!mHadPlay) {
return;
}
if ((mTextureView.getRotation() - mRotate) == 270) {
mTextureView.setRotation(mRotate);
mTextureView.requestLayout();
- mCoverImageView.setRotation(mRotate);
- mCoverImageView.requestLayout();
} else {
mTextureView.setRotation(mTextureView.getRotation() + 90);
mTextureView.requestLayout();
- mCoverImageView.setRotation(mCoverImageView.getRotation() + 90);
- mCoverImageView.requestLayout();
}
}
});
@@ -133,7 +130,7 @@ public void onClick(View v) {
mChangeTransform.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- if(!mHadPlay) {
+ if (!mHadPlay) {
return;
}
if (mTransformSize == 0) {
@@ -153,45 +150,46 @@ public void onClick(View v) {
* 需要在尺寸发生变化的时候重新处理
*/
@Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
- super.onSurfaceTextureSizeChanged(surface, width, height);
+ public void onSurfaceSizeChanged(Surface surface, int width, int height) {
+ super.onSurfaceSizeChanged(surface, width, height);
+ resolveTransform();
+ }
+
+ @Override
+ public void onSurfaceAvailable(Surface surface) {
+ super.onSurfaceAvailable(surface);
+ resolveRotateUI();
resolveTransform();
}
/**
* 处理镜像旋转
+ * 注意,暂停时
*/
protected void resolveTransform() {
-
switch (mTransformSize) {
case 1: {
Matrix transform = new Matrix();
transform.setScale(-1, 1, mTextureView.getWidth() / 2, 0);
mTextureView.setTransform(transform);
- mCoverImageView.setScaleType(ImageView.ScaleType.MATRIX);
- mCoverImageView.setImageMatrix(transform);
- mTransformCover = transform;
mChangeTransform.setText("左右镜像");
+ mTextureView.invalidate();
}
break;
case 2: {
Matrix transform = new Matrix();
transform.setScale(1, -1, 0, mTextureView.getHeight() / 2);
mTextureView.setTransform(transform);
- mCoverImageView.setScaleType(ImageView.ScaleType.MATRIX);
- mCoverImageView.setImageMatrix(transform);
- mTransformCover = transform;
mChangeTransform.setText("上下镜像");
+ mTextureView.invalidate();
}
break;
case 0: {
Matrix transform = new Matrix();
transform.setScale(1, 1, mTextureView.getWidth() / 2, 0);
mTextureView.setTransform(transform);
- mCoverImageView.setScaleType(ImageView.ScaleType.MATRIX);
- mCoverImageView.setImageMatrix(transform);
- mTransformCover = null;
mChangeTransform.setText("旋转镜像");
+ mTextureView.invalidate();
}
break;
}
@@ -203,7 +201,7 @@ protected void resolveTransform() {
*
* @param url 播放url
* @param cacheWithPlay 是否边播边缓存
- * @param title title
+ * @param title title
* @return
*/
public boolean setUp(List
+ 超时后会走onError接口,播放器通过onPlayError回调出
+
+ 错误码为 : BUFFER_TIME_OUT_ERROR = -192
+
+ 由于onError之后执行GSYVideoPlayer的OnError,如果不想触发错误,
+ 可以重载onError,在super之前拦截处理。
+
+ public void onError(int what, int extra){
+ do you want before super and return;
+ super.onError(what, extra)
+ }
+
+ @param timeOut 超时时间,毫秒 默认8000
+ @param needTimeOutOther 是否需要延时设置,默认关闭
+ */
+ public void setTimeOut(int timeOut, boolean needTimeOutOther) {
+ this.timeOut = timeOut;
+ this.needTimeOutOther = needTimeOutOther;
+ }
+
+ public IPlayerManager getCurPlayerManager() {
+ return playerManager;
+ }
+
+ public ICacheManager getCurCacheManager() {
+ return cacheManager;
+ }
+
+ public IPlayerInitSuccessListener getPlayerPreparedSuccessListener() {
+ return mPlayerInitSuccessListener;
+ }
+
+ /**
+ 播放器初始化后接口
+ */
+ public void setPlayerInitSuccessListener(IPlayerInitSuccessListener listener) {
+ this.mPlayerInitSuccessListener = listener;
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/GSYVideoManager.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/GSYVideoManager.java
new file mode 100644
index 000000000..9c27235fd
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/GSYVideoManager.java
@@ -0,0 +1,153 @@
+package com.shuyu.gsyvideoplayer;
+
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import com.shuyu.gsyvideoplayer.listener.GSYMediaPlayerListener;
+import com.shuyu.gsyvideoplayer.utils.CommonUtil;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer;
+
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.hideNavKey;
+
+
+/**
+ * 视频管理,单例
+ * Created by shuyu on 2016/11/11.
+ */
+
+public class GSYVideoManager extends GSYVideoBaseManager {
+
+ public static final int SMALL_ID = R.id.small_id;
+
+ public static final int FULLSCREEN_ID = R.id.full_id;
+
+ public static String TAG = "GSYVideoManager";
+
+ @SuppressLint("StaticFieldLeak")
+ private static GSYVideoManager videoManager;
+
+
+ private GSYVideoManager() {
+ init();
+ }
+
+ /**
+ * 单例管理器
+ */
+ public static synchronized GSYVideoManager instance() {
+ if (videoManager == null) {
+ videoManager = new GSYVideoManager();
+ }
+ return videoManager;
+ }
+
+ /**
+ * 同步创建一个临时管理器
+ */
+ public static synchronized GSYVideoManager tmpInstance(GSYMediaPlayerListener listener) {
+ GSYVideoManager gsyVideoManager = new GSYVideoManager();
+ gsyVideoManager.bufferPoint = videoManager.bufferPoint;
+ gsyVideoManager.optionModelList = videoManager.optionModelList;
+ gsyVideoManager.playTag = videoManager.playTag;
+ gsyVideoManager.currentVideoWidth = videoManager.currentVideoWidth;
+ gsyVideoManager.currentVideoHeight = videoManager.currentVideoHeight;
+ gsyVideoManager.context = videoManager.context;
+ gsyVideoManager.lastState = videoManager.lastState;
+ gsyVideoManager.playPosition = videoManager.playPosition;
+ gsyVideoManager.timeOut = videoManager.timeOut;
+ gsyVideoManager.needMute = videoManager.needMute;
+ gsyVideoManager.needTimeOutOther = videoManager.needTimeOutOther;
+ gsyVideoManager.setListener(listener);
+ return gsyVideoManager;
+ }
+
+ /**
+ * 替换管理器
+ */
+ public static synchronized void changeManager(GSYVideoManager gsyVideoManager) {
+ videoManager = gsyVideoManager;
+ }
+
+ /**
+ * 退出全屏,主要用于返回键
+ *
+ * @return 返回是否全屏
+ */
+ @SuppressWarnings("ResourceType")
+ public static boolean backFromWindowFull(Context context) {
+ boolean backFrom = false;
+ ViewGroup vp = (ViewGroup) (CommonUtil.scanForActivity(context)).findViewById(Window.ID_ANDROID_CONTENT);
+ View oldF = vp.findViewById(FULLSCREEN_ID);
+ if (oldF != null) {
+ backFrom = true;
+ hideNavKey(context);
+ if (GSYVideoManager.instance().lastListener() != null) {
+ GSYVideoManager.instance().lastListener().onBackFullscreen();
+ }
+ }
+ return backFrom;
+ }
+
+ /**
+ * 页面销毁了记得调用是否所有的video
+ */
+ public static void releaseAllVideos() {
+ if (GSYVideoManager.instance().listener() != null) {
+ GSYVideoManager.instance().listener().onCompletion();
+ }
+ GSYVideoManager.instance().releaseMediaPlayer();
+ }
+
+
+ /**
+ * 暂停播放
+ */
+ public static void onPause() {
+ if (GSYVideoManager.instance().listener() != null) {
+ GSYVideoManager.instance().listener().onVideoPause();
+ }
+ }
+
+ /**
+ * 恢复播放
+ */
+ public static void onResume() {
+ if (GSYVideoManager.instance().listener() != null) {
+ GSYVideoManager.instance().listener().onVideoResume();
+ }
+ }
+
+
+ /**
+ * 恢复暂停状态
+ *
+ * @param seek 是否产生seek动作,直播设置为false
+ */
+ public static void onResume(boolean seek) {
+ if (GSYVideoManager.instance().listener() != null) {
+ GSYVideoManager.instance().listener().onVideoResume(seek);
+ }
+ }
+
+ /**
+ * 当前是否全屏状态
+ *
+ * @return 当前是否全屏状态, true代表是。
+ */
+ @SuppressWarnings("ResourceType")
+ public static boolean isFullState(Activity activity) {
+ ViewGroup vp = (ViewGroup) (CommonUtil.scanForActivity(activity)).findViewById(Window.ID_ANDROID_CONTENT);
+ final View full = vp.findViewById(FULLSCREEN_ID);
+ GSYVideoPlayer gsyVideoPlayer = null;
+ if (full != null) {
+ gsyVideoPlayer = (GSYVideoPlayer) full;
+ }
+ return gsyVideoPlayer != null;
+ }
+
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoBuilder.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoOptionBuilder.java
similarity index 50%
rename from gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoBuilder.java
rename to gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoOptionBuilder.java
index d7534b3e4..1abbaafa3 100644
--- a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoBuilder.java
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/builder/GSYVideoOptionBuilder.java
@@ -3,11 +3,12 @@
import android.graphics.drawable.Drawable;
import android.view.View;
-import com.shuyu.gsyvideoplayer.GSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoProgressListener;
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+import com.shuyu.gsyvideoplayer.render.effect.NoEffect;
import com.shuyu.gsyvideoplayer.listener.LockClickListener;
-import com.shuyu.gsyvideoplayer.listener.StandardVideoAllCallBack;
import com.shuyu.gsyvideoplayer.listener.VideoAllCallBack;
-import com.shuyu.gsyvideoplayer.video.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
import java.io.File;
@@ -23,47 +24,162 @@
* 这只是方便使用
*/
-public class GSYVideoBuilder {
+public class GSYVideoOptionBuilder {
- /******************************* GSYBaseVideoPlayer *****************************************/
+ //退出全屏显示的案件图片
+ protected int mShrinkImageRes = -1;
- protected boolean mHideKey = true;//是否隐藏虚拟按键
+ //全屏显示的案件图片
+ protected int mEnlargeImageRes = -1;
- protected boolean mShowFullAnimation = true;//是否使用全屏动画效果
+ //播放的tag,防止错误,因为普通的url也可能重复
+ protected int mPlayPosition = -22;
- protected boolean mNeedShowWifiTip = true; //是否需要显示流量提示
+ //触摸快进dialog的进度高量颜色
+ protected int mDialogProgressHighLightColor = -11;
+
+ //触摸快进dialog的进度普通颜色
+ protected int mDialogProgressNormalColor = -11;
+
+ //触摸隐藏等待时间
+ protected int mDismissControlTime = 2500;
+
+ //从哪个开始播放
+ protected long mSeekOnStart = -1;
- protected boolean mRotateViewAuto = true; //是否自动旋转
+ //触摸滑动进度的比例系数
+ protected float mSeekRatio = 1;
- protected boolean mLockLand = false;//当前全屏是否锁定全屏
+ //播放速度
+ protected float mSpeed = 1;
- protected boolean mLooping = false;//循环
+ //是否隐藏虚拟按键
+ protected boolean mHideKey = true;
- protected boolean mIsTouchWiget = true; //是否支持非全屏滑动触摸有效
+ //是否使用全屏动画效果
+ protected boolean mShowFullAnimation = true;
- protected boolean mIsTouchWigetFull = true; //是否支持全屏滑动触摸有效
+ //是否根据视频尺寸,自动选择竖屏全屏或者横屏全屏,注意,这时候默认旋转无效
+ protected boolean mAutoFullWithSize = false;
- protected boolean mShowPauseCover = true;//是否显示暂停图片
+ //是否需要显示流量提示
+ protected boolean mNeedShowWifiTip = true;
- protected boolean mRotateWithSystem = true; //旋转使能后是否跟随系统设置
+ //是否自动旋转
+ protected boolean mRotateViewAuto = true;
- protected float mSeekRatio = 1; //触摸滑动进度的比例系数
+ //当前全屏是否锁定全屏
+ protected boolean mLockLand = false;
- protected float mSpeed = 1;//播放速度
+ //循环
+ protected boolean mLooping = false;
- protected int mShrinkImageRes = -1; //退出全屏显示的案件图片
+ //是否支持非全屏滑动触摸有效
+ protected boolean mIsTouchWiget = true;
+
+ //是否支持全屏滑动触摸有效
+ protected boolean mIsTouchWigetFull = true;
+
+ //是否显示暂停图片
+ protected boolean mShowPauseCover = true;
+
+ //旋转使能后是否跟随系统设置
+ protected boolean mRotateWithSystem = true;
+
+ //边播放边缓存
+ protected boolean mCacheWithPlay;
- protected int mEnlargeImageRes = -1; //全屏显示的案件图片
+ //是否需要锁定屏幕
+ protected boolean mNeedLockFull;
+ //点击封面播放
+ protected boolean mThumbPlay;
+
+ //是否需要变速不变调
+ protected boolean mSounchTouch;
+
+ //是否需要lazy的setup
+ protected boolean mSetUpLazy = false;
+
+ //Prepared之后是否自动开始播放
+ protected boolean mStartAfterPrepared = true;
+
+ //是否播放器当失去音频焦点
+ protected boolean mReleaseWhenLossAudio = true;
+
+ //是否需要在利用window实现全屏幕的时候隐藏actionbar
+ protected boolean mActionBar = false;
+
+ //是否需要在利用window实现全屏幕的时候隐藏statusbar
+ protected boolean mStatusBar = false;
+
+ //播放的tag,防止错误,因为普通的url也可能重复
+ protected String mPlayTag = "";
+
+ //播放url
+ protected String mUrl;
+
+ //视频title
+ protected String mVideoTitle = null;
+
+ // 是否需要覆盖拓展类型
+ protected String mOverrideExtension;
+
+ private boolean mIsOnlyRotateLand = false;
+
+ //是否自定义的缓冲文件路径
+ protected File mCachePath;
+
+ //http请求头
+ protected Map
+ *
+ * 原 @author sheraz.khilji
+ */
+@SuppressLint("ViewConstructor")
+public class GSYVideoGLViewSimpleRender extends GSYVideoGLViewBaseRender {
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+
+ protected static final int GL_TEXTURE_EXTERNAL_OES = 0x8D65;
+
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ -1.0f, 0.0f, 1.0f,
+ 0.0f, -1.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f,};
+
+ private final String mVertexShader = "uniform mat4 uMVPMatrix;\n"
+ + "uniform mat4 uSTMatrix;\n"
+ + "attribute vec4 aPosition;\n"
+ + "attribute vec4 aTextureCoord;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ + " gl_Position = uMVPMatrix * aPosition;\n"
+ + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n"
+ + "}\n";
+
+ private int mProgram;
+
+ private int mTextureID[] = new int[2];
+
+ private int muMVPMatrixHandle;
+
+ private int muSTMatrixHandle;
+
+ private int maPositionHandle;
+
+ private int maTextureHandle;
+
+ private boolean mUpdateSurface = false;
+
+ private boolean mTakeShotPic = false;
+
+ private FloatBuffer mTriangleVertices;
+
+ private SurfaceTexture mSurface;
+
+ private GSYVideoShotListener mGSYVideoShotListener;
+
+ private GSYVideoGLView.ShaderInterface mEffect = new NoEffect();
+
+ public GSYVideoGLViewSimpleRender() {
+ mTriangleVertices = ByteBuffer
+ .allocateDirect(
+ mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder()).asFloatBuffer();
+ mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+ Matrix.setIdentityM(mSTMatrix, 0);
+ Matrix.setIdentityM(mMVPMatrix, 0);
+ }
+
+ @Override
+ public void onDrawFrame(GL10 glUnused) {
+ synchronized (this) {
+ if (mUpdateSurface) {
+ mSurface.updateTexImage();
+ mSurface.getTransformMatrix(mSTMatrix);
+ mUpdateSurface = false;
+ }
+ }
+ initDrawFrame();
+
+ bindDrawFrameTexture();
+
+ initPointerAndDraw();
+
+ takeBitmap(glUnused);
+
+ GLES20.glFinish();
+
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+ GLES20.glViewport(0, 0, width, height);
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+
+ mProgram = createProgram(getVertexShader(), getFragmentShader());
+ if (mProgram == 0) {
+ return;
+ }
+ maPositionHandle = GLES20
+ .glGetAttribLocation(mProgram, "aPosition");
+ checkGlError("glGetAttribLocation aPosition");
+ if (maPositionHandle == -1) {
+ throw new RuntimeException(
+ "Could not get attrib location for aPosition");
+ }
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram,
+ "aTextureCoord");
+ checkGlError("glGetAttribLocation aTextureCoord");
+ if (maTextureHandle == -1) {
+ throw new RuntimeException(
+ "Could not get attrib location for aTextureCoord");
+ }
+
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
+ "uMVPMatrix");
+ checkGlError("glGetUniformLocation uMVPMatrix");
+ if (muMVPMatrixHandle == -1) {
+ throw new RuntimeException(
+ "Could not get attrib location for uMVPMatrix");
+ }
+
+ muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram,
+ "uSTMatrix");
+ checkGlError("glGetUniformLocation uSTMatrix");
+ if (muSTMatrixHandle == -1) {
+ throw new RuntimeException(
+ "Could not get attrib location for uSTMatrix");
+ }
+
+ GLES20.glGenTextures(2, mTextureID, 0);
+
+ GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID[0]);
+ checkGlError("glBindTexture mTextureID");
+
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+
+ mSurface = new SurfaceTexture(mTextureID[0]);
+ mSurface.setOnFrameAvailableListener(this);
+
+ Surface surface = new Surface(mSurface);
+
+ sendSurfaceForPlayer(surface);
+ }
+
+ @Override
+ synchronized public void onFrameAvailable(SurfaceTexture surface) {
+ mUpdateSurface = true;
+ }
+
+ @Override
+ public void releaseAll() {
+
+ }
+
+ /**
+ * 设置滤镜效果
+ *
+ * @param shaderEffect
+ */
+ @Override
+ public void setEffect(GSYVideoGLView.ShaderInterface shaderEffect) {
+ if (shaderEffect != null) {
+ mEffect = shaderEffect;
+ }
+ mChangeProgram = true;
+ mChangeProgramSupportError = true;
+ }
+
+ @Override
+ public GSYVideoGLView.ShaderInterface getEffect() {
+ return mEffect;
+ }
+
+ protected void initDrawFrame() {
+ if (mChangeProgram) {
+ mProgram = createProgram(getVertexShader(), getFragmentShader());
+ mChangeProgram = false;
+ }
+ GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT
+ | GLES20.GL_COLOR_BUFFER_BIT);
+
+ GLES20.glUseProgram(mProgram);
+ checkGlError("glUseProgram");
+ }
+
+
+ protected void bindDrawFrameTexture() {
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID[0]);
+ }
+
+
+ protected void takeBitmap(GL10 glUnused) {
+ if (mTakeShotPic) {
+ mTakeShotPic = false;
+ if (mGSYVideoShotListener != null) {
+ Bitmap bitmap = createBitmapFromGLSurface(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight(), glUnused);
+ mGSYVideoShotListener.getBitmap(bitmap);
+ }
+ }
+ }
+
+
+ protected void initPointerAndDraw() {
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT,
+ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES,
+ mTriangleVertices);
+ checkGlError("glVertexAttribPointer maPosition");
+ GLES20.glEnableVertexAttribArray(maPositionHandle);
+ checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+ mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT,
+ false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES,
+ mTriangleVertices);
+ checkGlError("glVertexAttribPointer maTextureHandle");
+ GLES20.glEnableVertexAttribArray(maTextureHandle);
+ checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix,
+ 0);
+ GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+ checkGlError("glDrawArrays");
+
+ }
+
+ public int getProgram() {
+ return mProgram;
+ }
+
+ public int getMuMVPMatrixHandle() {
+ return muMVPMatrixHandle;
+ }
+
+ public int getMuSTMatrixHandle() {
+ return muSTMatrixHandle;
+ }
+
+ public int getMaPositionHandle() {
+ return maPositionHandle;
+ }
+
+ public int getMaTextureHandle() {
+ return maTextureHandle;
+ }
+
+ public float[] getSTMatrix() {
+ return mSTMatrix;
+ }
+
+ public int[] getTextureID() {
+ return mTextureID;
+ }
+
+ protected String getVertexShader() {
+ return mVertexShader;
+ }
+
+ protected String getFragmentShader() {
+ return mEffect.getShader(mSurfaceView);
+ }
+
+ /**
+ * 打开截图
+ */
+ public void takeShotPic() {
+ mTakeShotPic = true;
+ }
+
+ /**
+ * 截图监听
+ */
+ public void setGSYVideoShotListener(GSYVideoShotListener listener, boolean high) {
+ this.mGSYVideoShotListener = listener;
+ this.mHighShot = high;
+ }
+}
+
+
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYSurfaceView.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYSurfaceView.java
new file mode 100644
index 000000000..dc60c507d
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYSurfaceView.java
@@ -0,0 +1,236 @@
+package com.shuyu.gsyvideoplayer.render.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotSaveListener;
+import com.shuyu.gsyvideoplayer.render.GSYRenderView;
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewBaseRender;
+import com.shuyu.gsyvideoplayer.render.view.listener.IGSYSurfaceListener;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.utils.MeasureHelper;
+
+import java.io.File;
+
+/**
+ * SurfaceView
+ * Created by guoshuyu on 2017/8/26.
+ */
+
+public class GSYSurfaceView extends SurfaceView implements SurfaceHolder.Callback2, IGSYRenderView, MeasureHelper.MeasureFormVideoParamsListener {
+
+ private IGSYSurfaceListener mIGSYSurfaceListener;
+
+ private MeasureHelper.MeasureFormVideoParamsListener mVideoParamsListener;
+
+ private MeasureHelper measureHelper;
+
+ public GSYSurfaceView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GSYSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ measureHelper = new MeasureHelper(this, this);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureHelper.prepareMeasure(widthMeasureSpec, heightMeasureSpec, (int) getRotation());
+ setMeasuredDimension(measureHelper.getMeasuredWidth(), measureHelper.getMeasuredHeight());
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceAvailable(holder.getSurface());
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceSizeChanged(holder.getSurface(), width, height);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ //清空释放
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceDestroyed(holder.getSurface());
+ }
+ }
+
+ @Override
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ }
+
+ @Override
+ public IGSYSurfaceListener getIGSYSurfaceListener() {
+ return mIGSYSurfaceListener;
+ }
+
+ @Override
+ public void setIGSYSurfaceListener(IGSYSurfaceListener surfaceListener) {
+ getHolder().addCallback(this);
+ this.mIGSYSurfaceListener = surfaceListener;
+ }
+
+ @Override
+ public int getSizeH() {
+ return getHeight();
+ }
+
+ @Override
+ public int getSizeW() {
+ return getWidth();
+ }
+
+ @Override
+ public Bitmap initCover() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support initCover now");
+ return null;
+ }
+
+ @Override
+ public Bitmap initCoverHigh() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support initCoverHigh now");
+ return null;
+ }
+
+ /**
+ * 获取截图
+ *
+ * @param shotHigh 是否需要高清的
+ */
+ public void taskShotPic(GSYVideoShotListener gsyVideoShotListener, boolean shotHigh) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support taskShotPic now");
+ }
+
+ /**
+ * 保存截图
+ *
+ * @param high 是否需要高清的
+ */
+ public void saveFrame(final File file, final boolean high, final GSYVideoShotSaveListener gsyVideoShotSaveListener) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support saveFrame now");
+ }
+
+ @Override
+ public View getRenderView() {
+ return this;
+ }
+
+ @Override
+ public void onRenderResume() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support onRenderResume now");
+ }
+
+ @Override
+ public void onRenderPause() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support onRenderPause now");
+ }
+
+ @Override
+ public void releaseRenderAll() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support releaseRenderAll now");
+ }
+
+ @Override
+ public void setRenderMode(int mode) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setRenderMode now");
+ }
+
+
+ @Override
+ public void setRenderTransform(Matrix transform) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setRenderTransform now");
+ }
+
+ @Override
+ public void setGLRenderer(GSYVideoGLViewBaseRender renderer) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLRenderer now");
+ }
+
+ @Override
+ public void setGLMVPMatrix(float[] MVPMatrix) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLMVPMatrix now");
+ }
+
+ /**
+ * 设置滤镜效果
+ */
+ @Override
+ public void setGLEffectFilter(GSYVideoGLView.ShaderInterface effectFilter) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLEffectFilter now");
+ }
+
+
+ @Override
+ public void setVideoParamsListener(MeasureHelper.MeasureFormVideoParamsListener listener) {
+ mVideoParamsListener = listener;
+ }
+
+ @Override
+ public int getCurrentVideoWidth() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoWidth();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getCurrentVideoHeight() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoHeight();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarNum() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarNum();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarDen() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarDen();
+ }
+ return 0;
+ }
+
+ /**
+ * 添加播放的view
+ */
+ public static GSYSurfaceView addSurfaceView(Context context, ViewGroup textureViewContainer, int rotate,
+ final IGSYSurfaceListener gsySurfaceListener,
+ final MeasureHelper.MeasureFormVideoParamsListener videoParamsListener) {
+ if (textureViewContainer.getChildCount() > 0) {
+ textureViewContainer.removeAllViews();
+ }
+ GSYSurfaceView showSurfaceView = new GSYSurfaceView(context);
+ showSurfaceView.setIGSYSurfaceListener(gsySurfaceListener);
+ showSurfaceView.setVideoParamsListener(videoParamsListener);
+ showSurfaceView.setRotation(rotate);
+ GSYRenderView.addToParent(textureViewContainer, showSurfaceView);
+ return showSurfaceView;
+ }
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYTextureView.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYTextureView.java
new file mode 100644
index 000000000..1366a292a
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYTextureView.java
@@ -0,0 +1,298 @@
+package com.shuyu.gsyvideoplayer.render.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotSaveListener;
+import com.shuyu.gsyvideoplayer.render.GSYRenderView;
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewBaseRender;
+import com.shuyu.gsyvideoplayer.render.view.listener.IGSYSurfaceListener;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.utils.FileUtils;
+import com.shuyu.gsyvideoplayer.utils.GSYVideoType;
+import com.shuyu.gsyvideoplayer.utils.MeasureHelper;
+
+import java.io.File;
+
+/**
+ * 用于显示video的,做了横屏与竖屏的匹配,还有需要rotation需求的
+ * Created by shuyu on 2016/11/11.
+ */
+
+public class GSYTextureView extends TextureView implements TextureView.SurfaceTextureListener, IGSYRenderView, MeasureHelper.MeasureFormVideoParamsListener {
+
+ private IGSYSurfaceListener mIGSYSurfaceListener;
+
+ private MeasureHelper.MeasureFormVideoParamsListener mVideoParamsListener;
+
+ private MeasureHelper measureHelper;
+
+ private SurfaceTexture mSaveTexture;
+ private Surface mSurface;
+
+ public GSYTextureView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GSYTextureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ measureHelper = new MeasureHelper(this, this);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureHelper.prepareMeasure(widthMeasureSpec, heightMeasureSpec, (int) getRotation());
+ setMeasuredDimension(measureHelper.getMeasuredWidth(), measureHelper.getMeasuredHeight());
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ if (GSYVideoType.isMediaCodecTexture()) {
+ if (mSaveTexture == null) {
+ mSaveTexture = surface;
+ mSurface = new Surface(surface);
+ } else {
+ setSurfaceTexture(mSaveTexture);
+ }
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceAvailable(mSurface);
+ }
+ } else {
+ mSurface = new Surface(surface);
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceAvailable(mSurface);
+ }
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceSizeChanged(mSurface, width, height);
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+
+ //清空释放
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceDestroyed(mSurface);
+ }
+ if (GSYVideoType.isMediaCodecTexture()) {
+ return (mSaveTexture == null);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ //如果播放的是暂停全屏了
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceUpdated(mSurface);
+ }
+ }
+
+ @Override
+ public IGSYSurfaceListener getIGSYSurfaceListener() {
+ return mIGSYSurfaceListener;
+ }
+
+ @Override
+ public void setIGSYSurfaceListener(IGSYSurfaceListener surfaceListener) {
+ setSurfaceTextureListener(this);
+ mIGSYSurfaceListener = surfaceListener;
+ }
+
+ @Override
+ public int getSizeH() {
+ return getHeight();
+ }
+
+ @Override
+ public int getSizeW() {
+ return getWidth();
+ }
+
+ /**
+ * 暂停时初始化位图
+ */
+ @Override
+ public Bitmap initCover() {
+ Bitmap bitmap = Bitmap.createBitmap(
+ getSizeW(), getSizeH(), Bitmap.Config.RGB_565);
+ return getBitmap(bitmap);
+
+ }
+
+ /**
+ * 暂停时初始化位图
+ */
+ @Override
+ public Bitmap initCoverHigh() {
+ Bitmap bitmap = Bitmap.createBitmap(
+ getSizeW(), getSizeH(), Bitmap.Config.ARGB_8888);
+ return getBitmap(bitmap);
+
+ }
+
+
+ /**
+ * 获取截图
+ *
+ * @param shotHigh 是否需要高清的
+ */
+ @Override
+ public void taskShotPic(GSYVideoShotListener gsyVideoShotListener, boolean shotHigh) {
+ if (shotHigh) {
+ gsyVideoShotListener.getBitmap(initCoverHigh());
+ } else {
+ gsyVideoShotListener.getBitmap(initCover());
+ }
+ }
+
+ /**
+ * 保存截图
+ *
+ * @param high 是否需要高清的
+ */
+ @Override
+ public void saveFrame(final File file, final boolean high, final GSYVideoShotSaveListener gsyVideoShotSaveListener) {
+ GSYVideoShotListener gsyVideoShotListener = new GSYVideoShotListener() {
+ @Override
+ public void getBitmap(Bitmap bitmap) {
+ if (bitmap == null) {
+ gsyVideoShotSaveListener.result(false, file);
+ } else {
+ FileUtils.saveBitmap(bitmap, file);
+ gsyVideoShotSaveListener.result(true, file);
+ }
+ }
+ };
+ if (high) {
+ gsyVideoShotListener.getBitmap(initCoverHigh());
+ } else {
+ gsyVideoShotListener.getBitmap(initCover());
+ }
+
+ }
+
+
+ @Override
+ public View getRenderView() {
+ return this;
+ }
+
+ @Override
+ public void onRenderResume() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support onRenderResume now");
+ }
+
+ @Override
+ public void onRenderPause() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support onRenderPause now");
+ }
+
+ @Override
+ public void releaseRenderAll() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support releaseRenderAll now");
+ }
+
+ @Override
+ public void setRenderMode(int mode) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setRenderMode now");
+ }
+
+ @Override
+ public void setRenderTransform(Matrix transform) {
+ setTransform(transform);
+ }
+
+ @Override
+ public void setGLRenderer(GSYVideoGLViewBaseRender renderer) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLRenderer now");
+ }
+
+ @Override
+ public void setGLMVPMatrix(float[] MVPMatrix) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLMVPMatrix now");
+ }
+
+ /**
+ * 设置滤镜效果
+ */
+ @Override
+ public void setGLEffectFilter(GSYVideoGLView.ShaderInterface effectFilter) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setGLEffectFilter now");
+ }
+
+
+ @Override
+ public void setVideoParamsListener(MeasureHelper.MeasureFormVideoParamsListener listener) {
+ mVideoParamsListener = listener;
+ }
+
+ @Override
+ public int getCurrentVideoWidth() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoWidth();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getCurrentVideoHeight() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoHeight();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarNum() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarNum();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarDen() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarDen();
+ }
+ return 0;
+ }
+
+
+ /**
+ * 添加播放的view
+ */
+ public static GSYTextureView addTextureView(Context context, ViewGroup textureViewContainer, int rotate,
+ final IGSYSurfaceListener gsySurfaceListener,
+ final MeasureHelper.MeasureFormVideoParamsListener videoParamsListener) {
+ if (textureViewContainer.getChildCount() > 0) {
+ textureViewContainer.removeAllViews();
+ }
+ GSYTextureView gsyTextureView = new GSYTextureView(context);
+ gsyTextureView.setIGSYSurfaceListener(gsySurfaceListener);
+ gsyTextureView.setVideoParamsListener(videoParamsListener);
+ gsyTextureView.setRotation(rotate);
+ GSYRenderView.addToParent(textureViewContainer, gsyTextureView);
+ return gsyTextureView;
+ }
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYVideoGLView.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYVideoGLView.java
new file mode 100644
index 000000000..a581900a6
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/GSYVideoGLView.java
@@ -0,0 +1,416 @@
+package com.shuyu.gsyvideoplayer.render.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotSaveListener;
+import com.shuyu.gsyvideoplayer.render.GSYRenderView;
+import com.shuyu.gsyvideoplayer.render.view.listener.GSYVideoGLRenderErrorListener;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewBaseRender;
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewSimpleRender;
+import com.shuyu.gsyvideoplayer.render.view.listener.GLSurfaceListener;
+import com.shuyu.gsyvideoplayer.render.view.listener.IGSYSurfaceListener;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+import com.shuyu.gsyvideoplayer.utils.FileUtils;
+import com.shuyu.gsyvideoplayer.utils.MeasureHelper;
+import com.shuyu.gsyvideoplayer.render.effect.NoEffect;
+
+import java.io.File;
+
+
+/**
+ * 在videffects的基础上调整的
+ *
+ * 原 @author sheraz.khilji
+ */
+@SuppressLint("ViewConstructor")
+public class GSYVideoGLView extends GLSurfaceView implements GLSurfaceListener, IGSYRenderView, MeasureHelper.MeasureFormVideoParamsListener {
+
+ private static final String TAG = GSYVideoGLView.class.getName();
+ /**
+ * 利用布局计算大小
+ */
+ public static final int MODE_LAYOUT_SIZE = 0;
+ /**
+ * 利用Render计算大小
+ */
+ public static final int MODE_RENDER_SIZE = 1;
+
+ private GSYVideoGLViewBaseRender mRenderer;
+
+ private Context mContext;
+
+ private ShaderInterface mEffect = new NoEffect();
+
+ private MeasureHelper.MeasureFormVideoParamsListener mVideoParamsListener;
+
+ private MeasureHelper measureHelper;
+
+ private GLSurfaceListener mOnGSYSurfaceListener;
+
+ private IGSYSurfaceListener mIGSYSurfaceListener;
+
+ private float[] mMVPMatrix;
+
+ private int mMode = MODE_LAYOUT_SIZE;
+
+ public interface ShaderInterface {
+ String getShader(GLSurfaceView mGlSurfaceView);
+ }
+
+ public GSYVideoGLView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public GSYVideoGLView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mContext = context;
+ setEGLContextClientVersion(2);
+ mRenderer = new GSYVideoGLViewSimpleRender();
+ measureHelper = new MeasureHelper(this, this);
+ mRenderer.setSurfaceView(GSYVideoGLView.this);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mRenderer != null) {
+ mRenderer.initRenderSize();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mMode == MODE_RENDER_SIZE) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ measureHelper.prepareMeasure(widthMeasureSpec, heightMeasureSpec, (int) getRotation());
+ initRenderMeasure();
+ } else {
+ measureHelper.prepareMeasure(widthMeasureSpec, heightMeasureSpec, (int) getRotation());
+ setMeasuredDimension(measureHelper.getMeasuredWidth(), measureHelper.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public IGSYSurfaceListener getIGSYSurfaceListener() {
+ return mIGSYSurfaceListener;
+ }
+
+ @Override
+ public void setIGSYSurfaceListener(IGSYSurfaceListener surfaceListener) {
+ setOnGSYSurfaceListener(this);
+ mIGSYSurfaceListener = surfaceListener;
+ }
+
+ @Override
+ public void onSurfaceAvailable(Surface surface) {
+ if (mIGSYSurfaceListener != null) {
+ mIGSYSurfaceListener.onSurfaceAvailable(surface);
+ }
+ }
+
+ @Override
+ public int getSizeH() {
+ return getHeight();
+ }
+
+ @Override
+ public int getSizeW() {
+ return getWidth();
+ }
+
+ @Override
+ public Bitmap initCover() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support initCover now");
+ return null;
+ }
+
+ @Override
+ public Bitmap initCoverHigh() {
+ Debuger.printfLog(getClass().getSimpleName() + " not support initCoverHigh now");
+ return null;
+ }
+
+ /**
+ * 获取截图
+ *
+ * @param shotHigh 是否需要高清的
+ */
+ @Override
+ public void taskShotPic(GSYVideoShotListener gsyVideoShotListener, boolean shotHigh) {
+ if (gsyVideoShotListener != null) {
+ setGSYVideoShotListener(gsyVideoShotListener, shotHigh);
+ takeShotPic();
+
+ }
+ }
+
+ /**
+ * 保存截图
+ *
+ * @param high 是否需要高清的
+ */
+ @Override
+ public void saveFrame(final File file, final boolean high, final GSYVideoShotSaveListener gsyVideoShotSaveListener) {
+ GSYVideoShotListener gsyVideoShotListener = new GSYVideoShotListener() {
+ @Override
+ public void getBitmap(Bitmap bitmap) {
+ if (bitmap == null) {
+ gsyVideoShotSaveListener.result(false, file);
+ } else {
+ FileUtils.saveBitmap(bitmap, file);
+ gsyVideoShotSaveListener.result(true, file);
+ }
+ }
+ };
+ setGSYVideoShotListener(gsyVideoShotListener, high);
+ takeShotPic();
+ }
+
+ @Override
+ public View getRenderView() {
+ return this;
+ }
+
+
+ @Override
+ public void onRenderResume() {
+ requestLayout();
+ onResume();
+ }
+
+ @Override
+ public void onRenderPause() {
+ requestLayout();
+ onPause();
+
+ }
+
+ @Override
+ public void releaseRenderAll() {
+ requestLayout();
+ releaseAll();
+
+ }
+
+ @Override
+ public void setRenderMode(int mode) {
+ setMode(mode);
+ }
+
+
+ @Override
+ public void setRenderTransform(Matrix transform) {
+ Debuger.printfLog(getClass().getSimpleName() + " not support setRenderTransform now");
+ }
+
+ @Override
+ public void setGLRenderer(GSYVideoGLViewBaseRender renderer) {
+ setCustomRenderer(renderer);
+ }
+
+ @Override
+ public void setGLMVPMatrix(float[] MVPMatrix) {
+ setMVPMatrix(MVPMatrix);
+ }
+
+ /**
+ * 设置滤镜效果
+ */
+ @Override
+ public void setGLEffectFilter(GSYVideoGLView.ShaderInterface effectFilter) {
+ setEffect(effectFilter);
+ }
+
+
+ @Override
+ public void setVideoParamsListener(MeasureHelper.MeasureFormVideoParamsListener listener) {
+ mVideoParamsListener = listener;
+ }
+
+ @Override
+ public int getCurrentVideoWidth() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoWidth();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getCurrentVideoHeight() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getCurrentVideoHeight();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarNum() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarNum();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getVideoSarDen() {
+ if (mVideoParamsListener != null) {
+ return mVideoParamsListener.getVideoSarDen();
+ }
+ return 0;
+ }
+
+ protected void initRenderMeasure() {
+ if (mVideoParamsListener != null && mMode == MODE_RENDER_SIZE) {
+ try {
+ int videoWidth = mVideoParamsListener.getCurrentVideoWidth();
+ int videoHeight = mVideoParamsListener.getCurrentVideoHeight();
+ if (this.mRenderer != null) {
+ this.mRenderer.setCurrentViewWidth(measureHelper.getMeasuredWidth());
+ this.mRenderer.setCurrentViewHeight(measureHelper.getMeasuredHeight());
+ this.mRenderer.setCurrentVideoWidth(videoWidth);
+ this.mRenderer.setCurrentVideoHeight(videoHeight);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ public void initRender() {
+ setRenderer(mRenderer);
+ }
+
+
+ public void setGSYVideoGLRenderErrorListener(GSYVideoGLRenderErrorListener videoGLRenderErrorListener) {
+ this.mRenderer.setGSYVideoGLRenderErrorListener(videoGLRenderErrorListener);
+ }
+
+ /**
+ * 设置自定义的render,其他自定义设置会被取消,需要重新设置
+ * 在initRender() 前设置才会生效
+ *
+ * @param CustomRender
+ */
+ public void setCustomRenderer(GSYVideoGLViewBaseRender CustomRender) {
+ this.mRenderer = CustomRender;
+ mRenderer.setSurfaceView(GSYVideoGLView.this);
+ initRenderMeasure();
+ }
+
+ public void setOnGSYSurfaceListener(GLSurfaceListener mGSYSurfaceListener) {
+ this.mOnGSYSurfaceListener = mGSYSurfaceListener;
+ mRenderer.setGSYSurfaceListener(this.mOnGSYSurfaceListener);
+ }
+
+ public void setEffect(ShaderInterface shaderEffect) {
+ if (shaderEffect != null) {
+ mEffect = shaderEffect;
+ mRenderer.setEffect(mEffect);
+ }
+ }
+
+ public void setMVPMatrix(float[] MVPMatrix) {
+ if (MVPMatrix != null) {
+ mMVPMatrix = MVPMatrix;
+ mRenderer.setMVPMatrix(MVPMatrix);
+ }
+ }
+
+ public void takeShotPic() {
+ mRenderer.takeShotPic();
+ }
+
+
+ public void setGSYVideoShotListener(GSYVideoShotListener listener, boolean high) {
+ this.mRenderer.setGSYVideoShotListener(listener, high);
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * @param mode MODE_LAYOUT_SIZE = 0, MODE_RENDER_SIZE = 1
+ */
+ public void setMode(int mode) {
+ this.mMode = mode;
+ }
+
+ public void releaseAll() {
+ if (mRenderer != null) {
+ mRenderer.releaseAll();
+ }
+ }
+
+ public GSYVideoGLViewBaseRender getRenderer() {
+ return mRenderer;
+ }
+
+ public ShaderInterface getEffect() {
+ return mEffect;
+ }
+
+ public float[] getMVPMatrix() {
+ return mMVPMatrix;
+ }
+
+ /**
+ * 添加播放的view
+ */
+ public static GSYVideoGLView addGLView(final Context context, final ViewGroup textureViewContainer, final int rotate,
+ final IGSYSurfaceListener gsySurfaceListener,
+ final MeasureHelper.MeasureFormVideoParamsListener videoParamsListener,
+ final GSYVideoGLView.ShaderInterface effect, final float[] transform,
+ final GSYVideoGLViewBaseRender customRender, final int renderMode) {
+ if (textureViewContainer.getChildCount() > 0) {
+ textureViewContainer.removeAllViews();
+ }
+ final GSYVideoGLView gsyVideoGLView = new GSYVideoGLView(context);
+ if (customRender != null) {
+ gsyVideoGLView.setCustomRenderer(customRender);
+ }
+ gsyVideoGLView.setEffect(effect);
+ gsyVideoGLView.setVideoParamsListener(videoParamsListener);
+ gsyVideoGLView.setRenderMode(renderMode);
+ gsyVideoGLView.setIGSYSurfaceListener(gsySurfaceListener);
+ gsyVideoGLView.setRotation(rotate);
+ gsyVideoGLView.initRender();
+ gsyVideoGLView.setGSYVideoGLRenderErrorListener(new GSYVideoGLRenderErrorListener() {
+ @Override
+ public void onError(GSYVideoGLViewBaseRender render, String Error, int code, boolean byChangedRenderError) {
+ if (byChangedRenderError)
+ addGLView(context,
+ textureViewContainer,
+ rotate,
+ gsySurfaceListener,
+ videoParamsListener,
+ render.getEffect(),
+ render.getMVPMatrix(),
+ render, renderMode);
+
+ }
+ });
+ if (transform != null && transform.length == 16) {
+ gsyVideoGLView.setMVPMatrix(transform);
+ }
+ GSYRenderView.addToParent(textureViewContainer, gsyVideoGLView);
+ return gsyVideoGLView;
+ }
+
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/IGSYRenderView.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/IGSYRenderView.java
new file mode 100644
index 000000000..7bcaa9fac
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/IGSYRenderView.java
@@ -0,0 +1,85 @@
+package com.shuyu.gsyvideoplayer.render.view;
+
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.view.View;
+
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotSaveListener;
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewBaseRender;
+import com.shuyu.gsyvideoplayer.render.view.listener.IGSYSurfaceListener;
+import com.shuyu.gsyvideoplayer.utils.MeasureHelper;
+
+import java.io.File;
+
+/**
+ * Created by guoshuyu on 2018/1/29.
+ */
+
+public interface IGSYRenderView {
+
+ IGSYSurfaceListener getIGSYSurfaceListener();
+
+ /**
+ * Surface变化监听,必须
+ */
+ void setIGSYSurfaceListener(IGSYSurfaceListener surfaceListener);
+
+ /**
+ * 当前view高度,必须
+ */
+ int getSizeH();
+
+ /**
+ * 当前view宽度,必须
+ */
+ int getSizeW();
+
+ /**
+ * 实现该接口的view,必须
+ */
+ View getRenderView();
+
+ /**
+ * 渲染view通过MeasureFormVideoParamsListener获取视频的相关参数,必须
+ */
+ void setVideoParamsListener(MeasureHelper.MeasureFormVideoParamsListener listener);
+
+ /**
+ * 截图
+ */
+ void taskShotPic(GSYVideoShotListener gsyVideoShotListener, boolean shotHigh);
+
+ /**
+ * 保存当前帧
+ */
+ void saveFrame(final File file, final boolean high, final GSYVideoShotSaveListener gsyVideoShotSaveListener);
+
+ /**
+ * 获取当前画面的bitmap,没有返回空
+ */
+ Bitmap initCover();
+
+ /**
+ * 获取当前画面的高质量bitmap,没有返回空
+ */
+ Bitmap initCoverHigh();
+
+ void onRenderResume();
+
+ void onRenderPause();
+
+ void releaseRenderAll();
+
+ void setRenderMode(int mode);
+
+ void setRenderTransform(Matrix transform);
+
+ void setGLRenderer(GSYVideoGLViewBaseRender renderer);
+
+ void setGLMVPMatrix(float[] MVPMatrix);
+
+ void setGLEffectFilter(GSYVideoGLView.ShaderInterface effectFilter);
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GLSurfaceListener.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GLSurfaceListener.java
new file mode 100644
index 000000000..b68cf7acb
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GLSurfaceListener.java
@@ -0,0 +1,11 @@
+package com.shuyu.gsyvideoplayer.render.view.listener;
+
+import android.view.Surface;
+
+/**
+ * GLSurfaceView surface 状态变化回调
+ * Created by guoshuyu on 2018/1/29.
+ */
+public interface GLSurfaceListener {
+ void onSurfaceAvailable(Surface surface);
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GSYVideoGLRenderErrorListener.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GSYVideoGLRenderErrorListener.java
new file mode 100644
index 000000000..8f9747dd6
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/GSYVideoGLRenderErrorListener.java
@@ -0,0 +1,18 @@
+package com.shuyu.gsyvideoplayer.render.view.listener;
+
+import com.shuyu.gsyvideoplayer.render.glrender.GSYVideoGLViewBaseRender;
+
+/**
+ * GL渲染错误
+ * Created by guoshuyu on 2018/1/14.
+ */
+public interface GSYVideoGLRenderErrorListener {
+ /**
+ *
+ * @param render
+ * @param Error 错误文本
+ * @param code 错误代码
+ * @param byChangedRenderError 错误是因为切换effect导致的
+ */
+ void onError(GSYVideoGLViewBaseRender render, String Error, int code, boolean byChangedRenderError);
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/IGSYSurfaceListener.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/IGSYSurfaceListener.java
new file mode 100644
index 000000000..9d8dac6bd
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/view/listener/IGSYSurfaceListener.java
@@ -0,0 +1,18 @@
+package com.shuyu.gsyvideoplayer.render.view.listener;
+
+import android.view.Surface;
+
+/**
+ * Surface 状态变化回调
+ * Created by guoshuyu on 2018/1/29.
+ */
+
+public interface IGSYSurfaceListener {
+ void onSurfaceAvailable(Surface surface);
+
+ void onSurfaceSizeChanged(Surface surface, int width, int height);
+
+ boolean onSurfaceDestroyed(Surface surface);
+
+ void onSurfaceUpdated(Surface surface);
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/AnimatedGifEncoder.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/AnimatedGifEncoder.java
new file mode 100644
index 000000000..b073d2437
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/AnimatedGifEncoder.java
@@ -0,0 +1,1273 @@
+package com.shuyu.gsyvideoplayer.utils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+public class AnimatedGifEncoder {
+
+ protected int width; // image size
+
+ protected int height;
+
+ protected int x = 0;
+
+ protected int y = 0;
+
+ protected int transparent = -1; // transparent color if given
+
+ protected int transIndex; // transparent index in color table
+
+ protected int repeat = -1; // no repeat
+
+ protected int delay = 0; // frame delay (hundredths)
+
+ protected boolean started = false; // ready to output frames
+
+ protected OutputStream out;
+
+ protected Bitmap image; // current frame
+
+ protected byte[] pixels; // BGR byte array from frame
+
+ protected byte[] indexedPixels; // converted frame indexed to palette
+
+ protected int colorDepth; // number of bit planes
+
+ protected byte[] colorTab; // RGB palette
+
+ protected boolean[] usedEntry = new boolean[256]; // active palette entries
+
+ protected int palSize = 7; // color table size (bits-1)
+
+ protected int dispose = -1; // disposal code (-1 = use default)
+
+ protected boolean closeStream = false; // close stream when finished
+
+ protected boolean firstFrame = true;
+
+ protected boolean sizeSet = false; // if false, get size from first frame
+
+ protected int sample = 10; // default sample interval for quantizer
+
+ /**
+ * Sets the delay time between each frame, or changes it for subsequent frames
+ * (applies to last frame added).
+ *
+ * @param ms int delay time in milliseconds
+ */
+ public void setDelay(int ms) {
+ delay = ms / 10;
+ }
+
+ /**
+ * Sets the GIF frame disposal code for the last added frame and any
+ * subsequent frames. Default is 0 if no transparent color has been set,
+ * otherwise 2.
+ *
+ * @param code int disposal code.
+ */
+ public void setDispose(int code) {
+ if (code >= 0) {
+ dispose = code;
+ }
+ }
+
+ /**
+ * Sets the number of times the set of GIF frames should be played. Default is
+ * 1; 0 means play indefinitely. Must be invoked before the first image is
+ * added.
+ *
+ * @param iter int number of iterations.
+ *
+ */
+ public void setRepeat(int iter) {
+ if (iter >= 0) {
+ repeat = iter;
+ }
+ }
+
+ /**
+ * Sets the transparent color for the last added frame and any subsequent
+ * frames. Since all colors are subject to modification in the quantization
+ * process, the color in the final palette for each frame closest to the given
+ * color becomes the transparent color for that frame. May be set to null to
+ * indicate no transparent color.
+ *
+ * @param c Color to be treated as transparent on display.
+ */
+ public void setTransparent(int c) {
+ transparent = c;
+ }
+
+ /**
+ * Adds next GIF frame. The frame is not written immediately, but is actually
+ * deferred until the next frame is received so that timing data can be
+ * inserted. Invoking
+ * If file for this url is fully cached (it means method {@link #isCached(String)} returns {@code true})
+ * then file:// uri to cached file will be returned.
+ *
+ * Calling this method has same effect as calling {@link #getProxyUrl(String, boolean)} with 2nd parameter set to {@code true}.
+ *
+ * @param url a url to file that should be cached.
+ * @return a wrapped by proxy url if file is not fully cached or url pointed to cache file otherwise.
+ */
+ public String getProxyUrl(String url) {
+ return getProxyUrl(url, true);
+ }
+
+ /**
+ * Returns url that wrap original url and should be used for client (MediaPlayer, ExoPlayer, etc).
+ *
+ * If parameter {@code allowCachedFileUri} is {@code true} and file for this url is fully cached
+ * (it means method {@link #isCached(String)} returns {@code true}) then file:// uri to cached file will be returned.
+ *
+ * @param url a url to file that should be cached.
+ * @param allowCachedFileUri {@code true} if allow to return file:// uri if url is fully cached
+ * @return a wrapped by proxy url if file is not fully cached or url pointed to cache file otherwise (if {@code allowCachedFileUri} is {@code true}).
+ */
+ public String getProxyUrl(String url, boolean allowCachedFileUri) {
+ if (allowCachedFileUri && isCached(url)) {
+ File cacheFile = getCacheFile(url);
+ touchFileSafely(cacheFile);
+ return Uri.fromFile(cacheFile).toString();
+ }
+ return isAlive() ? appendToProxyUrl(url) : url;
+ }
+
+ public void registerCacheListener(CacheListener cacheListener, String url) {
+ checkAllNotNull(cacheListener, url);
+ synchronized (clientsLock) {
+ try {
+ getClients(url).registerCacheListener(cacheListener);
+ } catch (ProxyCacheException e) {
+ HttpProxyCacheDebuger.printfWarning("Error registering cache listener", e.getMessage());
+ }
+ }
+ }
+
+ public void unregisterCacheListener(CacheListener cacheListener, String url) {
+ checkAllNotNull(cacheListener, url);
+ synchronized (clientsLock) {
+ try {
+ getClients(url).unregisterCacheListener(cacheListener);
+ } catch (ProxyCacheException e) {
+ HttpProxyCacheDebuger.printfWarning("Error registering cache listener", e.getMessage());
+ }
+ }
+ }
+
+ public void unregisterCacheListener(CacheListener cacheListener) {
+ checkNotNull(cacheListener);
+ synchronized (clientsLock) {
+ for (HttpProxyCacheServerClients clients : clientsMap.values()) {
+ clients.unregisterCacheListener(cacheListener);
+ }
+ }
+ }
+
+ /**
+ * Checks is cache contains fully cached file for particular url.
+ *
+ * @param url an url cache file will be checked for.
+ * @return {@code true} if cache contains fully cached file for passed in parameters url.
+ */
+ public boolean isCached(String url) {
+ checkNotNull(url, "Url can't be null!");
+ return getCacheFile(url).exists();
+ }
+
+ public void shutdown() {
+ HttpProxyCacheDebuger.printfLog("Shutdown proxy server");
+
+ shutdownClients();
+
+ config.sourceInfoStorage.release();
+
+ waitConnectionThread.interrupt();
+ try {
+ if (!serverSocket.isClosed()) {
+ serverSocket.close();
+ }
+ } catch (IOException e) {
+ onError(new ProxyCacheException("Error shutting down proxy server", e));
+ }
+ }
+
+ private boolean isAlive() {
+ return pinger.ping(3, 70); // 70+140+280=max~500ms
+ }
+
+ private String appendToProxyUrl(String url) {
+ return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
+ }
+
+ private File getCacheFile(String url) {
+ File cacheDir = config.cacheRoot;
+ String fileName = config.fileNameGenerator.generate(url);
+ return new File(cacheDir, fileName);
+ }
+
+ private void touchFileSafely(File cacheFile) {
+ try {
+ config.diskUsage.touch(cacheFile);
+ } catch (IOException e) {
+ HttpProxyCacheDebuger.printfError("Error touching file " + cacheFile, e);
+ }
+ }
+
+ private void shutdownClients() {
+ synchronized (clientsLock) {
+ for (HttpProxyCacheServerClients clients : clientsMap.values()) {
+ clients.shutdown();
+ }
+ clientsMap.clear();
+ }
+ }
+
+ private void waitForRequest() {
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ Socket socket = serverSocket.accept();
+ socketProcessor.submit(new SocketProcessorRunnable(socket));
+ }
+ } catch (IOException e) {
+ onError(new ProxyCacheException("Error during waiting connection", e));
+ }
+ }
+
+ private void processSocket(Socket socket) {
+ try {
+ GetRequest request = GetRequest.read(socket.getInputStream());
+ String url = ProxyCacheUtils.decode(request.uri);
+ if (pinger.isPingRequest(url)) {
+ pinger.responseToPing(socket);
+ } else {
+ HttpProxyCacheServerClients clients = getClients(url);
+ clients.processRequest(request, socket);
+ }
+ } catch (SocketException e) {
+ // There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
+ // So just to prevent log flooding don't log stacktrace
+ } catch (ProxyCacheException | IOException e) {
+ onError(new ProxyCacheException("Error processing request", e));
+ } finally {
+ releaseSocket(socket);
+ HttpProxyCacheDebuger.printfLog("Opened connections: " + getClientsCount());
+ }
+ }
+
+ private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheException {
+ synchronized (clientsLock) {
+ HttpProxyCacheServerClients clients = clientsMap.get(url);
+ if (clients == null) {
+ clients = new HttpProxyCacheServerClients(url, config);
+ clientsMap.put(url, clients);
+ }
+ return clients;
+ }
+ }
+
+ private int getClientsCount() {
+ synchronized (clientsLock) {
+ int count = 0;
+ for (HttpProxyCacheServerClients clients : clientsMap.values()) {
+ count += clients.getClientsCount();
+ }
+ return count;
+ }
+ }
+
+ private void releaseSocket(Socket socket) {
+ closeSocketInput(socket);
+ closeSocketOutput(socket);
+ closeSocket(socket);
+ }
+
+ private void closeSocketInput(Socket socket) {
+ try {
+ if (!socket.isInputShutdown()) {
+ socket.shutdownInput();
+ }
+ } catch (SocketException e) {
+ // There is no way to determine that client closed connection http://stackoverflow.com/a/10241044/999458
+ // So just to prevent log flooding don't log stacktrace
+ } catch (IOException e) {
+ // onError(new ProxyCacheException("Error closing socket input stream", e));
+ }
+ }
+
+ private void closeSocketOutput(Socket socket) {
+ try {
+ if (!socket.isOutputShutdown()) {
+ socket.shutdownOutput();
+ }
+ } catch (IOException e) {
+ HttpProxyCacheDebuger.printfWarning("Failed to close socket on proxy side: {}. It seems client have already closed connection.", e.getMessage());
+ }
+ }
+
+ private void closeSocket(Socket socket) {
+ try {
+ if (!socket.isClosed()) {
+ socket.close();
+ }
+ } catch (IOException e) {
+ //onError(new ProxyCacheException("Error closing socket", e));
+ }
+ }
+
+ private void onError(Throwable e) {
+ HttpProxyCacheDebuger.printfError("HttpProxyCacheServer error", e.getMessage());
+ }
+
+ private final class WaitRequestsRunnable implements Runnable {
+
+ private final CountDownLatch startSignal;
+
+ public WaitRequestsRunnable(CountDownLatch startSignal) {
+ this.startSignal = startSignal;
+ }
+
+ @Override
+ public void run() {
+ startSignal.countDown();
+ waitForRequest();
+ }
+ }
+
+ private final class SocketProcessorRunnable implements Runnable {
+
+ private final Socket socket;
+
+ public SocketProcessorRunnable(Socket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ processSocket(socket);
+ }
+ }
+
+ /**
+ * Builder for {@link HttpProxyCacheServer}.
+ */
+ public static final class Builder {
+
+ private static final long DEFAULT_MAX_SIZE = 512 * 1024 * 1024;
+
+ private File cacheRoot;
+ private FileNameGenerator fileNameGenerator;
+ private DiskUsage diskUsage;
+ private SourceInfoStorage sourceInfoStorage;
+ private HeaderInjector headerInjector;
+
+ public Builder(Context context) {
+ this.sourceInfoStorage = SourceInfoStorageFactory.newSourceInfoStorage(context);
+ this.cacheRoot = StorageUtils.getIndividualCacheDirectory(context);
+ this.diskUsage = new TotalSizeLruDiskUsage(DEFAULT_MAX_SIZE);
+ this.fileNameGenerator = new Md5FileNameGenerator();
+ this.headerInjector = new EmptyHeadersInjector();
+ }
+
+ /**
+ * Overrides default cache folder to be used for caching files.
+ *
+ * By default AndroidVideoCache uses
+ * '/Android/data/[app_package_name]/cache/video-cache/' if card is mounted and app has appropriate permission
+ * or 'video-cache' subdirectory in default application's cache directory otherwise.
+ *
+ * All files that exceeds limit will be deleted using LRU strategy.
+ * Default value is 512 Mb.
+ *
+ * It is important to ignore system proxy for localhost connection.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+class IgnoreHostProxySelector extends ProxySelector {
+
+ private static final List
+ *
+ * @param hueDegrees Range of value should be between 0 to 360 degrees as described in the image above
+ */
+ public HueEffect(float hueDegrees) {
+// manipulating input value so that we can map it on 360 degree circle
+ hueValue = ((hueDegrees - 45) / 45f + 0.5f) * -1;
+
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+
+ + "varying vec2 vTextureCoord;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + "float hue=" + hueValue + ";\n"
+
+ + "void main() {\n"
+
+ + "vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n"
+ + "vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n"
+ + "vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n"
+
+ + "vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n"
+ + "vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n"
+ + "vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n"
+
+
+ + "vec4 color = texture2D(sTexture, vTextureCoord);\n"
+
+ + "float YPrime = dot(color, kRGBToYPrime);\n"
+ + "float I = dot(color, kRGBToI);\n"
+ + "float Q = dot(color, kRGBToQ);\n"
+
+ + "float chroma = sqrt (I * I + Q * Q);\n"
+
+ + "Q = chroma * sin (hue);\n"
+
+ + "I = chroma * cos (hue);\n"
+
+ + "vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n"
+
+ + "color.r = dot (yIQ, kYIQToR);\n"
+ + "color.g = dot (yIQ, kYIQToG);\n"
+ + "color.b = dot (yIQ, kYIQToB);\n"
+ + "gl_FragColor = color;\n"
+
+ + "}\n";
+
+ return shader;
+ }
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/InvertColorsEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/InvertColorsEffect.java
new file mode 100644
index 000000000..f64ea6bc6
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/InvertColorsEffect.java
@@ -0,0 +1,37 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+
+/**
+ * Inverts the video colors. This can also be known as negative Effect.
+ *
+ * @author sheraz.khilji
+ */
+public class InvertColorsEffect implements ShaderInterface {
+ /**
+ * Initialize Effect
+ */
+ public InvertColorsEffect() {
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "uniform samplerExternalOES sTexture;\n" + "void main() {\n"
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " float colorR = (1.0 - color.r) / 1.0;\n"
+ + " float colorG = (1.0 - color.g) / 1.0;\n"
+ + " float colorB = (1.0 - color.b) / 1.0;\n"
+ + " gl_FragColor = vec4(colorR, colorG, colorB, color.a);\n"
+ + "}\n";
+
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/LamoishEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/LamoishEffect.java
new file mode 100644
index 000000000..c62468fb8
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/LamoishEffect.java
@@ -0,0 +1,160 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * Applies lomo-camera style effect to video.
+ *
+ * @author sheraz.khilji
+ */
+public class LamoishEffect implements ShaderInterface {
+ private int mWidth;
+ private int mHeight;
+ private Random mRandom;
+
+ /**
+ * Initialize Effect
+ */
+ public LamoishEffect() {
+
+ }
+
+ /**
+ * Init all values that will be used by this shader.
+ *
+ * @param mGlSurfaceView which is responsible for displaying your video
+ */
+ private void initValues(GLSurfaceView mGlSurfaceView) {
+ mWidth = mGlSurfaceView.getWidth();
+ mHeight = mGlSurfaceView.getHeight();
+ mRandom = new Random(new Date().getTime());
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ initValues(mGlSurfaceView);
+ float scale[] = new float[2];
+ if (mWidth > mHeight) {
+ scale[0] = 1f;
+ scale[1] = ((float) mHeight) / mWidth;
+ } else {
+ scale[0] = ((float) mWidth) / mHeight;
+ scale[1] = 1f;
+ }
+ float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1]
+ * scale[1])) * 0.5f;
+
+ float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()};
+
+ String scaleString[] = new String[2];
+ String seedString[] = new String[2];
+
+ scaleString[0] = "scale[0] = " + scale[0] + ";\n";
+ scaleString[1] = "scale[1] = " + scale[1] + ";\n";
+
+ seedString[0] = "seed[0] = " + seed[0] + ";\n";
+ seedString[1] = "seed[1] = " + seed[1] + ";\n";
+
+ String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n";
+ String stepsizeString = "stepsize = " + 1.0f / 255.0f + ";\n";
+ String stepsizeXString = "stepsizeX = " + 1.0f / mWidth + ";\n";
+ String stepsizeYString = "stepsizeY = " + 1.0f / mHeight + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + " vec2 seed;\n"
+ + " float stepsizeX;\n"
+ + " float stepsizeY;\n"
+ + " float stepsize;\n"
+ + " vec2 scale;\n"
+ + " float inv_max_dist;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "float rand(vec2 loc) {\n"
+ + " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ + " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ + " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
+ +
+ // keep value of part1 in range: (2^-14 to 2^14).
+ " float temp = mod(197.0 * value, 1.0) + value;\n"
+ + " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ + " float part2 = value * 0.5453;\n"
+ + " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ + " return fract(part1 + part2 + part3);\n" + "}\n"
+ + "void main() {\n"
+ // Parameters that were created above
+ + scaleString[0]
+ + scaleString[1]
+ + seedString[0]
+ + seedString[1]
+ + inv_max_distString
+ + stepsizeString
+ + stepsizeXString
+ + stepsizeYString
+ // sharpen
+ + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ + " vec2 coord;\n"
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ + " coord.y = vTextureCoord.y - stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x - stepsizeX;\n"
+ + " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x + stepsizeX;\n"
+ + " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x + stepsizeX;\n"
+ + " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n"
+ +
+ // cross process
+ " vec3 c_color = vec3(0.0, 0.0, 0.0);\n"
+ + " float value;\n"
+ + " if (s_color.r < 0.5) {\n"
+ + " value = s_color.r;\n"
+ + " } else {\n"
+ + " value = 1.0 - s_color.r;\n"
+ + " }\n"
+ + " float red = 4.0 * value * value * value;\n"
+ + " if (s_color.r < 0.5) {\n"
+ + " c_color.r = red;\n"
+ + " } else {\n"
+ + " c_color.r = 1.0 - red;\n"
+ + " }\n"
+ + " if (s_color.g < 0.5) {\n"
+ + " value = s_color.g;\n"
+ + " } else {\n"
+ + " value = 1.0 - s_color.g;\n"
+ + " }\n"
+ + " float green = 2.0 * value * value;\n"
+ + " if (s_color.g < 0.5) {\n"
+ + " c_color.g = green;\n"
+ + " } else {\n"
+ + " c_color.g = 1.0 - green;\n"
+ + " }\n"
+ + " c_color.b = s_color.b * 0.5 + 0.25;\n"
+ +
+ // blackwhite
+ " float dither = rand(vTextureCoord + seed);\n"
+ + " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n"
+ + " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n"
+ + " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n"
+ +
+ // vignette
+ " coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ + " float dist = length(coord * scale);\n"
+ + " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n"
+ + " gl_FragColor = vec4(bw_color * lumen, color.a);\n" + "}\n";
+ ;
+
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/NoEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/NoEffect.java
new file mode 100644
index 000000000..eb4c79bc9
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/NoEffect.java
@@ -0,0 +1,33 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+
+/**
+ * Displays the normal video without any effect.
+ *
+ * @author sheraz.khilji
+ */
+public class NoEffect implements ShaderInterface {
+ /**
+ * Initialize
+ */
+ public NoEffect() {
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "uniform samplerExternalOES sTexture;\n" + "void main() {\n"
+ + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n"
+ + "}\n";
+
+ return shader;
+
+ }
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/OverlayEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/OverlayEffect.java
new file mode 100644
index 000000000..d9d565b03
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/OverlayEffect.java
@@ -0,0 +1,43 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+
+/**
+ * 重叠
+ * Created by guoshuyu on 2017/9/17.
+ */
+
+public class OverlayEffect implements GSYVideoGLView.ShaderInterface {
+
+ float fstep = 0.0015f;
+
+ public OverlayEffect() {
+ super();
+ }
+
+ public OverlayEffect(float fstep) {
+ this.fstep = fstep;
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ return "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ " uniform samplerExternalOES sTexture; \n" +
+ " varying vec2 vTextureCoord; \n" +
+ " \n" +
+ " void main() \n" +
+ " { \n" +
+ " vec4 sample0,sample1,sample2,sample3; \n" +
+ " float fstep=" + fstep +"; \n" +
+ " sample0=texture2D(sTexture,vec2(vTextureCoord.x-fstep,vTextureCoord.y-fstep)); \n" +
+ " sample1=texture2D(sTexture,vec2(vTextureCoord.x+fstep,vTextureCoord.y-fstep)); \n" +
+ " sample2=texture2D(sTexture,vec2(vTextureCoord.x+fstep,vTextureCoord.y+fstep)); \n" +
+ " sample3=texture2D(sTexture,vec2(vTextureCoord.x-fstep,vTextureCoord.y+fstep)); \n" +
+ " vec4 color=(sample0+sample1+sample2+sample3) / 4.0; \n" +
+ " gl_FragColor=color; \n" +
+ " } ";
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/PosterizeEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/PosterizeEffect.java
new file mode 100644
index 000000000..1df58c8ef
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/PosterizeEffect.java
@@ -0,0 +1,36 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+
+/**
+ * Applies Posterization effect to video.
+ *
+ * @author sheraz.khilji
+ */
+public class PosterizeEffect implements ShaderInterface {
+ /**
+ * Initialize Effect
+ */
+ public PosterizeEffect() {
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " vec3 pcolor;\n"
+ + " pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n"
+ + " pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n"
+ + " pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n"
+ + " gl_FragColor = vec4(pcolor, color.a);\n" + "}\n";
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SampleBlurEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SampleBlurEffect.java
new file mode 100644
index 000000000..6db7fd626
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SampleBlurEffect.java
@@ -0,0 +1,52 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+
+/**
+ * 简单模糊
+ * Created by guoshuyu on 2017/9/17.
+ */
+
+public class SampleBlurEffect implements GSYVideoGLView.ShaderInterface {
+
+ float blur = 1f;
+
+ public SampleBlurEffect() {
+ super();
+ }
+
+ public SampleBlurEffect(float blur) {
+ this.blur = blur;
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ String s =
+ "#extension GL_OES_EGL_image_external : require\n" +
+ "precision mediump float;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "const float blurSize = "+ blur + "/800.0;\n" +
+ "const float weightSum = 70.0 + 2.0 * (1.0 + 8.0 + 28.0 + 56.0);\n" +
+ "\n" +
+ "void main(void)\n" +
+ "{\n" +
+ " vec4 sum = vec4(0.0);\n" +
+ "\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x - 4.0*blurSize, vTextureCoord.y)) * 1.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x - 3.0*blurSize, vTextureCoord.y)) * 8.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x - 2.0*blurSize, vTextureCoord.y)) * 28.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x - blurSize, vTextureCoord.y)) * 56.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x, vTextureCoord.y)) * 70.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x + blurSize, vTextureCoord.y)) * 56.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x + 2.0*blurSize, vTextureCoord.y)) * 28.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x + 3.0*blurSize, vTextureCoord.y)) * 8.0 / weightSum;\n" +
+ " sum += texture2D(sTexture, vec2(vTextureCoord.x + 4.0*blurSize, vTextureCoord.y)) * 1.0 / weightSum;\n" +
+ "\n" +
+ " gl_FragColor = sum;\n" +
+ "}";
+ return s;
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SaturationEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SaturationEffect.java
new file mode 100644
index 000000000..b898f524c
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SaturationEffect.java
@@ -0,0 +1,89 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+/**
+ * Adjusts color saturation of video. There is still some issue with this
+ * effect.
+ *
+ * @author sheraz.khilji
+ */
+public class SaturationEffect implements ShaderInterface {
+ private float scale = 0f;
+
+ /**
+ * Initialize Effect
+ *
+ * @param scale Float, between -1 and 1. 0 means no change, while -1 indicates
+ * full desaturation, i.e. grayscale.
+ */
+ public SaturationEffect(float scale) {
+
+ this.scale = scale;
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ float shift = 1.0f / 255.0f;
+ float weights[] = {2f / 8f, 5f / 8f, 1f / 8f};
+ float exponents[] = new float[3];
+
+ String weightsString[] = new String[3];
+ String exponentsString[] = new String[3];
+ exponentsString[0] = "";
+ exponentsString[1] = "";
+ exponentsString[2] = "";
+ String scaleString = "";
+
+ if (scale > 0.0f) {
+ exponents[0] = (0.9f * scale) + 1.0f;
+ exponents[1] = (2.1f * scale) + 1.0f;
+ exponents[2] = (2.7f * scale) + 1.0f;
+ exponentsString[0] = "exponents[0] = " + exponents[0] + ";\n";
+ exponentsString[1] = "exponents[1] = " + exponents[1] + ";\n";
+ exponentsString[2] = "exponents[2] = " + exponents[2] + ";\n";
+ } else
+ scaleString = "scale = " + (1.0f + scale) + ";\n";
+
+ weightsString[0] = "weights[0] = " + weights[0] + ";\n";
+ weightsString[1] = "weights[1] = " + weights[1] + ";\n";
+ weightsString[2] = "weights[2] = " + weights[2] + ";\n";
+ String shiftString = "shift = " + shift + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n" + " float scale;\n"
+ + " float shift;\n" + " vec3 weights;\n" + " vec3 exponents;\n"
+ + "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ // Parameters that were created above
+ + weightsString[0]
+ + weightsString[1]
+ + weightsString[2]
+ + shiftString
+ + scaleString
+ + " vec4 oldcolor = texture2D(sTexture, vTextureCoord);\n"
+ + " float kv = dot(oldcolor.rgb, weights) + shift;\n"
+ + " vec3 new_color = scale * oldcolor.rgb + (1.0 - scale) * kv;\n"
+ + " gl_FragColor= vec4(new_color, oldcolor.a);\n"
+ // Parameters that were created above
+ + weightsString[0]
+ + weightsString[1]
+ + weightsString[2]
+ + exponentsString[0]
+ + exponentsString[1]
+ + exponentsString[2]
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " float de = dot(color.rgb, weights);\n"
+ + " float inv_de = 1.0 / de;\n"
+ + " vec3 verynew_color = de * pow(color.rgb * inv_de, exponents);\n"
+ + " float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0);\n"
+ + " gl_FragColor = gl_FragColor+vec4(verynew_color / max_color, color.a);\n"
+ + "}\n";
+
+ return shader;
+
+ }
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SepiaEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SepiaEffect.java
new file mode 100644
index 000000000..c7c6fd9ca
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SepiaEffect.java
@@ -0,0 +1,51 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+/**
+ * Converts video to Sepia tone.
+ *
+ * @author sheraz.khilji
+ */
+public class SepiaEffect implements ShaderInterface {
+ /**
+ * Initialize Effect
+ */
+ public SepiaEffect() {
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ float weights[] = {805.0f / 2048.0f, 715.0f / 2048.0f,
+ 557.0f / 2048.0f, 1575.0f / 2048.0f, 1405.0f / 2048.0f,
+ 1097.0f / 2048.0f, 387.0f / 2048.0f, 344.0f / 2048.0f,
+ 268.0f / 2048.0f};
+ String matrixString[] = new String[9];
+
+ matrixString[0] = " matrix[0][0]=" + weights[0] + ";\n";
+ matrixString[1] = " matrix[0][1]=" + weights[1] + ";\n";
+ matrixString[2] = " matrix[0][2]=" + weights[2] + ";\n";
+ matrixString[3] = " matrix[1][0]=" + weights[3] + ";\n";
+ matrixString[4] = " matrix[1][1]=" + weights[4] + ";\n";
+ matrixString[5] = " matrix[1][2]=" + weights[5] + ";\n";
+ matrixString[6] = " matrix[2][0]=" + weights[6] + ";\n";
+ matrixString[7] = " matrix[2][1]=" + weights[7] + ";\n";
+ matrixString[8] = " matrix[2][2]=" + weights[8] + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n" + " mat3 matrix;\n"
+ + "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ + matrixString[0] + matrixString[1] + matrixString[2]
+ + matrixString[3] + matrixString[4] + matrixString[5]
+ + matrixString[6] + matrixString[7] + matrixString[8]
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " vec3 new_color = min(matrix * color.rgb, 1.0);\n"
+ + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
+
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SharpnessEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SharpnessEffect.java
new file mode 100644
index 000000000..d5b672d24
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/SharpnessEffect.java
@@ -0,0 +1,83 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+/**
+ * Sharpens the video.
+ *
+ * @author sheraz.khilji
+ */
+public class SharpnessEffect implements ShaderInterface {
+ private int mWidth;
+ private int mHeight;
+ private float scale = 0f;
+
+ /**
+ * Initialize Effect
+ *
+ * @param scale Float, between 0 and 1. 0 means no change.
+ */
+ public SharpnessEffect(float scale) {
+ if (scale < 0.0f)
+ scale = 0.0f;
+ if (scale > 1.0f)
+ scale = 1.0f;
+
+ this.scale = scale;
+ }
+
+ /**
+ * Init all values that will be used by this shader.
+ *
+ * @param mGlSurfaceView which is responsible for displaying your video
+ */
+ private void initValues(GLSurfaceView mGlSurfaceView) {
+ mWidth = mGlSurfaceView.getWidth();
+ mHeight = mGlSurfaceView.getHeight();
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ initValues(mGlSurfaceView);
+
+ String stepsizeXString = "stepsizeX = " + 1.0f / mWidth + ";\n";
+ String stepsizeYString = "stepsizeY = " + 1.0f / mHeight + ";\n";
+ String scaleString = "scale = " + scale + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + " float scale;\n"
+ + " float stepsizeX;\n"
+ + " float stepsizeY;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ // Parameters that were created above
+ + stepsizeXString
+ + stepsizeYString
+ + scaleString
+ + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ + " vec2 coord;\n"
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ + " coord.y = vTextureCoord.y - stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x - stepsizeX;\n"
+ + " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x + stepsizeX;\n"
+ + " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " coord.x = vTextureCoord.x + stepsizeX;\n"
+ + " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ + " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ + " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n"
+ + "}\n";
+
+ return shader;
+
+ }
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TemperatureEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TemperatureEffect.java
new file mode 100644
index 000000000..11bb51571
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TemperatureEffect.java
@@ -0,0 +1,57 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+/**
+ * Adjusts color temperature of the video.
+ *
+ * @author sheraz.khilji
+ */
+public class TemperatureEffect implements ShaderInterface {
+ private float scale = 0f;
+
+ /**
+ * Initialize Effect
+ *
+ * @param scale Float, between 0 and 1, with 0 indicating cool, and 1
+ * indicating warm. A value of of 0.5 indicates no change.
+ */
+ public TemperatureEffect(float scale) {
+ if (scale < 0.0f)
+ scale = 0.0f;
+ if (scale > 1.0f)
+ scale = 1.0f;
+ this.scale = scale;
+
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+
+ String scaleString = "scale = " + (2.0f * scale - 1.0f) + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + " float scale;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n" // Parameters that were created above
+ + scaleString
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " vec3 new_color = color.rgb;\n"
+ + " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n"
+ + " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n"
+ + " if (scale > 0.0) { \n"
+ + " new_color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n"
+ + " }\n"
+ + " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n"
+ + " if (max_value > 1.0) { \n"
+ + " new_color /= max_value;\n" + " } \n"
+ + " gl_FragColor = vec4(new_color, color.a);\n" + "}\n";
+
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TintEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TintEffect.java
new file mode 100644
index 000000000..ce15e3f49
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/TintEffect.java
@@ -0,0 +1,65 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.graphics.Color;
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+
+/**
+ * Tints the video with specified color..
+ *
+ * @author sheraz.khilji
+ */
+public class TintEffect implements ShaderInterface {
+ private int mTint = 0xFF0000FF;
+
+ /**
+ * Initialize Effect
+ *
+ * @param color Integer, representing an ARGB color with 8 bits per channel.
+ * May be created using Color class.
+ */
+ public TintEffect(int color) {
+ this.mTint = color;
+
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ float color_ratio[] = {0.21f, 0.71f, 0.07f};
+ String color_ratioString[] = new String[3];
+ color_ratioString[0] = "color_ratio[0] = " + color_ratio[0] + ";\n";
+ color_ratioString[1] = "color_ratio[1] = " + color_ratio[1] + ";\n";
+ color_ratioString[2] = "color_ratio[2] = " + color_ratio[2] + ";\n";
+
+ float tint_color[] = {Color.red(mTint) / 255f,
+ Color.green(mTint) / 255f, Color.blue(mTint) / 255f};
+
+ String tintString[] = new String[3];
+ tintString[0] = "tint[0] = " + tint_color[0] + ";\n";
+ tintString[1] = "tint[1] = " + tint_color[1] + ";\n";
+ tintString[2] = "tint[2] = " + tint_color[2] + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + " vec3 tint;\n"
+ + " vec3 color_ratio;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ // Parameters that were created above
+ + color_ratioString[0]
+ + color_ratioString[1]
+ + color_ratioString[2]
+ + tintString[0]
+ + tintString[1]
+ + tintString[2]
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " float avg_color = dot(color_ratio, color.rgb);\n"
+ + " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n"
+ + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/VignetteEffect.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/VignetteEffect.java
new file mode 100644
index 000000000..f8bd33a82
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/effect/VignetteEffect.java
@@ -0,0 +1,97 @@
+package com.shuyu.gsyvideoplayer.render.effect;
+
+import android.opengl.GLSurfaceView;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView.ShaderInterface;
+
+
+/**
+ * Applies lomo-camera style effect to video.
+ *
+ * @author sheraz.khilji
+ */
+public class VignetteEffect implements ShaderInterface {
+ private int mWidth = 0;
+ private int mHeight = 0;
+ private float mScale = 0f;
+ private final float mShade = 0.85f;
+
+ /**
+ * Initialize Effect
+ *
+ * @param scale Float, between 0 and 1. 0 means no change.
+ */
+ public VignetteEffect(float scale) {
+ if (scale < 0.0f)
+ scale = 0.0f;
+ if (scale > 1.0f)
+ scale = 1.0f;
+ this.mScale = scale;
+
+ }
+
+ /**
+ * Init all values that will be used by this shader.
+ *
+ * @param mGlSurfaceView which is responsible for displaying your video
+ */
+ private void initValues(GLSurfaceView mGlSurfaceView) {
+ mWidth = mGlSurfaceView.getWidth();
+ mHeight = mGlSurfaceView.getHeight();
+ }
+
+ @Override
+ public String getShader(GLSurfaceView mGlSurfaceView) {
+ initValues(mGlSurfaceView);
+ float scale[] = new float[2];
+ if (mWidth > mHeight) {
+ scale[0] = 1f;
+ scale[1] = ((float) mHeight) / mWidth;
+ } else {
+ scale[0] = ((float) mWidth) / mHeight;
+ scale[1] = 1f;
+ }
+ float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1]
+ * scale[1])) * 0.5f;
+
+ String scaleString[] = new String[2];
+
+ scaleString[0] = "scale[0] = " + scale[0] + ";\n";
+ scaleString[1] = "scale[1] = " + scale[1] + ";\n";
+ String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n";
+ String shadeString = "shade = " + mShade + ";\n";
+
+ // The 'range' is between 1.3 to 0.6. When scale is zero then range is
+ // 1.3
+ // which means no vignette at all because the luminousity difference is
+ // less than 1/256 and will cause nothing.
+ String rangeString = "range = "
+ + (1.30f - (float) Math.sqrt(mScale) * 0.7f) + ";\n";
+
+ String shader = "#extension GL_OES_EGL_image_external : require\n"
+ + "precision mediump float;\n"
+ + "uniform samplerExternalOES sTexture;\n"
+ + " float range;\n"
+ + " float inv_max_dist;\n"
+ + " float shade;\n"
+ + " vec2 scale;\n"
+ + "varying vec2 vTextureCoord;\n"
+ + "void main() {\n"
+ // Parameters that were created above
+ + scaleString[0]
+ + scaleString[1]
+ + inv_max_distString
+ + shadeString
+ + rangeString
+ + " const float slope = 20.0;\n"
+ + " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ + " float dist = length(coord * scale);\n"
+ + " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n"
+ + " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ + " gl_FragColor = vec4(color.rgb * lumen, color.a);\n"
+ + "}\n";
+
+ return shader;
+
+ }
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewBaseRender.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewBaseRender.java
new file mode 100644
index 000000000..cc0113031
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewBaseRender.java
@@ -0,0 +1,260 @@
+package com.shuyu.gsyvideoplayer.render.glrender;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES20;
+import android.opengl.GLException;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.os.Handler;
+import android.view.Surface;
+
+import com.shuyu.gsyvideoplayer.render.view.listener.GSYVideoGLRenderErrorListener;
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+import com.shuyu.gsyvideoplayer.render.view.listener.GLSurfaceListener;
+import com.shuyu.gsyvideoplayer.utils.Debuger;
+
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ *
+ */
+@SuppressLint("ViewConstructor")
+public abstract class GSYVideoGLViewBaseRender implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
+
+ //是否需要高清截图
+ protected boolean mHighShot = false;
+
+ protected GLSurfaceListener mGSYSurfaceListener;
+
+ protected GLSurfaceView mSurfaceView;
+
+ protected float[] mMVPMatrix = new float[16];
+
+ protected float[] mSTMatrix = new float[16];
+
+ protected int mCurrentViewWidth = 0;
+
+ protected int mCurrentViewHeight = 0;
+
+ protected int mCurrentVideoWidth = 0;
+
+ protected int mCurrentVideoHeight = 0;
+
+ protected boolean mChangeProgram = false;
+
+ protected boolean mChangeProgramSupportError = false;
+
+ protected GSYVideoGLRenderErrorListener mGSYVideoGLRenderErrorListener;
+
+ protected Handler mHandler = new Handler();
+
+ public abstract void releaseAll();
+
+ public void setSurfaceView(GLSurfaceView surfaceView) {
+ this.mSurfaceView = surfaceView;
+ }
+
+ public void sendSurfaceForPlayer(final Surface surface) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mGSYSurfaceListener != null) {
+ mGSYSurfaceListener.onSurfaceAvailable(surface);
+ }
+ }
+ });
+ }
+
+ protected int loadShader(int shaderType, String source) {
+ int shader = GLES20.glCreateShader(shaderType);
+ if (shader != 0) {
+ GLES20.glShaderSource(shader, source);
+ GLES20.glCompileShader(shader);
+ int[] compiled = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
+ compiled, 0);
+ if (compiled[0] == 0) {
+ Debuger.printfError("Could not compile shader " + shaderType + ":");
+ Debuger.printfError(GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ return shader;
+ }
+
+ protected int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) {
+ return 0;
+ }
+ int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+ fragmentSource);
+ if (pixelShader == 0) {
+ return 0;
+ }
+
+ int program = GLES20.glCreateProgram();
+ if (program != 0) {
+ GLES20.glAttachShader(program, vertexShader);
+ checkGlError("glAttachShader");
+ GLES20.glAttachShader(program, pixelShader);
+ checkGlError("glAttachShader");
+ GLES20.glLinkProgram(program);
+ int[] linkStatus = new int[1];
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS,
+ linkStatus, 0);
+ if (linkStatus[0] != GLES20.GL_TRUE) {
+ Debuger.printfError("Could not link program: ");
+ Debuger.printfError(GLES20.glGetProgramInfoLog(program));
+ GLES20.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ protected void checkGlError(final String op) {
+ final int error;
+ if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Debuger.printfError(op + ": glError " + error);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mGSYVideoGLRenderErrorListener != null) {
+ mGSYVideoGLRenderErrorListener.onError(GSYVideoGLViewBaseRender.this, op + ": glError " + error, error, mChangeProgramSupportError);
+ }
+ mChangeProgramSupportError = false;
+ }
+ });
+ //throw new RuntimeException(op + ": glError " + error);
+ }
+ }
+
+ /**
+ * 创建bitmap截图
+ */
+ protected Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl) {
+ int bitmapBuffer[] = new int[w * h];
+ int bitmapSource[] = new int[w * h];
+ IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
+ intBuffer.position(0);
+ try {
+ gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.
+ GL_UNSIGNED_BYTE,
+ intBuffer);
+ int offset1, offset2;
+ for (int i = 0; i < h; i++) {
+ offset1 = i * w;
+ offset2 = (h - i - 1) * w;
+ for (int j = 0; j < w; j++) {
+ int texturePixel = bitmapBuffer[offset1 + j];
+ int blue = (texturePixel >> 16) & 0xff;
+ int red = (texturePixel << 16) & 0x00ff0000;
+ int pixel = (texturePixel & 0xff00ff00) | red | blue;
+ bitmapSource[offset2 + j] = pixel;
+ }
+ }
+ } catch (GLException e) {
+ return null;
+ }
+ if (mHighShot) {
+ return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
+ } else {
+ return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.RGB_565);
+ }
+ }
+
+
+ public void setGSYSurfaceListener(GLSurfaceListener onSurfaceListener) {
+ this.mGSYSurfaceListener = onSurfaceListener;
+ }
+
+ public float[] getMVPMatrix() {
+ return mMVPMatrix;
+ }
+
+ /**
+ * 形变动画
+ */
+ public void setMVPMatrix(float[] MVPMatrix) {
+ this.mMVPMatrix = MVPMatrix;
+ }
+
+ /**
+ * 打开截图
+ */
+ public void takeShotPic() {
+ }
+
+ /**
+ * 截图监听
+ */
+ public void setGSYVideoShotListener(GSYVideoShotListener listener, boolean high) {
+ }
+
+ /**
+ * 设置滤镜效果
+ *
+ * @param shaderEffect
+ */
+ public void setEffect(GSYVideoGLView.ShaderInterface shaderEffect) {
+ }
+
+
+ public GSYVideoGLView.ShaderInterface getEffect() {
+ return null;
+ }
+
+
+ public int getCurrentViewWidth() {
+ return mCurrentViewWidth;
+ }
+
+ public void setCurrentViewWidth(int currentViewWidth) {
+ this.mCurrentViewWidth = currentViewWidth;
+ }
+
+ public int getCurrentViewHeight() {
+ return mCurrentViewHeight;
+ }
+
+ public void setCurrentViewHeight(int currentViewHeight) {
+ this.mCurrentViewHeight = currentViewHeight;
+ }
+
+ public int getCurrentVideoWidth() {
+ return mCurrentVideoWidth;
+ }
+
+ public void setCurrentVideoWidth(int currentVideoWidth) {
+ this.mCurrentVideoWidth = currentVideoWidth;
+ }
+
+ public int getCurrentVideoHeight() {
+ return mCurrentVideoHeight;
+ }
+
+ public void setCurrentVideoHeight(int currentVideoHeight) {
+ this.mCurrentVideoHeight = currentVideoHeight;
+ }
+
+ public void initRenderSize() {
+ if (mCurrentViewWidth != 0 && mCurrentViewHeight != 0) {
+ Matrix.scaleM(mMVPMatrix, 0, (float) mCurrentViewWidth / mSurfaceView.getWidth(),
+ (float) mCurrentViewHeight / mSurfaceView.getHeight(), 1);
+ }
+ }
+
+ public void setGSYVideoGLRenderErrorListener(GSYVideoGLRenderErrorListener videoGLRenderErrorListener) {
+ this.mGSYVideoGLRenderErrorListener = videoGLRenderErrorListener;
+ }
+}
+
+
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewSimpleRender.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewSimpleRender.java
new file mode 100644
index 000000000..1348b12f3
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/render/glrender/GSYVideoGLViewSimpleRender.java
@@ -0,0 +1,319 @@
+package com.shuyu.gsyvideoplayer.render.glrender;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.view.Surface;
+
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+import com.shuyu.gsyvideoplayer.render.effect.NoEffect;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoShotListener;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+
+/**
+ * 在videffects的基础上调整的
+ * finish() flushes all frames. If
+ * setSize was not invoked, the size of the first image is used
+ * for all subsequent frames.
+ *
+ * @param im BufferedImage containing frame to write.
+ * true if successful.
+ */
+ public boolean addFrame(Bitmap im) {
+ if ((im == null) || !started) {
+ return false;
+ }
+ boolean ok = true;
+ try {
+ if (!sizeSet) {
+ // use first frame's size
+ setSize(im.getWidth(), im.getHeight());
+ }
+ image = im;
+ getImagePixels(); // convert to correct format if necessary
+ analyzePixels(); // build color table & map pixels
+ if (firstFrame) {
+ writeLSD(); // logical screen descriptior
+ writePalette(); // global color table
+ if (repeat >= 0) {
+ // use NS app extension to indicate reps
+ writeNetscapeExt();
+ }
+ }
+ writeGraphicCtrlExt(); // write graphic control extension
+ writeImageDesc(); // image descriptor
+ if (!firstFrame) {
+ writePalette(); // local color table
+ }
+ writePixels(); // encode and write pixel data
+ firstFrame = false;
+ } catch (IOException e) {
+ ok = false;
+ }
+
+ return ok;
+ }
+
+ /**
+ * Flushes any pending data and closes output file. If writing to an
+ * OutputStream, the stream is not closed.
+ */
+ public boolean finish() {
+ if (!started)
+ return false;
+ boolean ok = true;
+ started = false;
+ try {
+ out.write(0x3b); // gif trailer
+ out.flush();
+ if (closeStream) {
+ out.close();
+ }
+ } catch (IOException e) {
+ ok = false;
+ }
+
+ // reset for subsequent use
+ transIndex = 0;
+ out = null;
+ image = null;
+ pixels = null;
+ indexedPixels = null;
+ colorTab = null;
+ closeStream = false;
+ firstFrame = true;
+
+ return ok;
+ }
+
+ /**
+ * Sets frame rate in frames per second. Equivalent to
+ * setDelay(1000/fps).
+ *
+ * @param fps float frame rate (frames per second)
+ */
+ public void setFrameRate(float fps) {
+ if (fps != 0f) {
+ delay = (int) (100 / fps);
+ }
+ }
+
+ /**
+ * Sets quality of color quantization (conversion of images to the maximum 256
+ * colors allowed by the GIF specification). Lower values (minimum = 1)
+ * produce better colors, but slow processing significantly. 10 is the
+ * default, and produces good color mapping at reasonable speeds. Values
+ * greater than 20 do not yield significant improvements in speed.
+ *
+ * @param quality int greater than 0.
+ *
+ */
+ public void setQuality(int quality) {
+ if (quality < 1)
+ quality = 1;
+ sample = quality;
+ }
+
+ /**
+ * Sets the GIF frame size. The default size is the size of the first frame
+ * added if this method is not invoked.
+ *
+ * @param w int frame width.
+ * @param h int frame width.
+ */
+ public void setSize(int w, int h) {
+ width = w;
+ height = h;
+ if (width < 1)
+ width = 320;
+ if (height < 1)
+ height = 240;
+ sizeSet = true;
+ }
+
+ /**
+ * Sets the GIF frame position. The position is 0,0 by default.
+ * Useful for only updating a section of the image
+ *
+ * @param x int frame width.
+ * @param y int frame width.
+ */
+ public void setPosition(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Initiates GIF file creation on the given stream. The stream is not closed
+ * automatically.
+ *
+ * @param os OutputStream on which GIF images are written.
+ * false if initial write failed.
+ */
+ public boolean start(OutputStream os) {
+ if (os == null)
+ return false;
+ boolean ok = true;
+ closeStream = false;
+ out = os;
+ try {
+ writeString("GIF89a"); // header
+ } catch (IOException e) {
+ ok = false;
+ }
+ return started = ok;
+ }
+
+ /**
+ * Analyzes image colors and creates color map.
+ */
+ protected void analyzePixels() {
+ int len = pixels.length;
+ int nPix = len / 3;
+ indexedPixels = new byte[nPix];
+ NeuQuant nq = new NeuQuant(pixels, len, sample);
+ // initialize quantizer
+ colorTab = nq.process(); // create reduced palette
+ // convert map from BGR to RGB
+ for (int i = 0; i < colorTab.length; i += 3) {
+ byte temp = colorTab[i];
+ colorTab[i] = colorTab[i + 2];
+ colorTab[i + 2] = temp;
+ usedEntry[i / 3] = false;
+ }
+ // map image pixels to new palette
+ int k = 0;
+ for (int i = 0; i < nPix; i++) {
+ int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
+ usedEntry[index] = true;
+ indexedPixels[i] = (byte) index;
+ }
+ pixels = null;
+ colorDepth = 8;
+ palSize = 7;
+ // get closest match to transparent color if specified
+ if (transparent != -1) {
+ transIndex = findClosest(transparent);
+ }
+ }
+
+ /**
+ * Returns index of palette color closest to c
+ */
+ protected int findClosest(int c) {
+ if (colorTab == null)
+ return -1;
+ int r = (c >> 16) & 0xff;
+ int g = (c >> 8) & 0xff;
+ int b = (c >> 0) & 0xff;
+ int minpos = 0;
+ int dmin = 256 * 256 * 256;
+ int len = colorTab.length;
+ for (int i = 0; i < len; ) {
+ int dr = r - (colorTab[i++] & 0xff);
+ int dg = g - (colorTab[i++] & 0xff);
+ int db = b - (colorTab[i] & 0xff);
+ int d = dr * dr + dg * dg + db * db;
+ int index = i / 3;
+ if (usedEntry[index] && (d < dmin)) {
+ dmin = d;
+ minpos = index;
+ }
+ i++;
+ }
+ return minpos;
+ }
+
+ /**
+ * Extracts image pixels into byte array "pixels"
+ */
+ protected void getImagePixels() {
+ int w = image.getWidth();
+ int h = image.getHeight();
+ if ((w != width) || (h != height)) {
+ // create new image with right size/format
+ Bitmap temp = Bitmap.createBitmap(width, height, Config.RGB_565);
+ Canvas g = new Canvas(temp);
+ g.drawBitmap(image, 0, 0, new Paint());
+ image = temp;
+ }
+ int[] data = getImageData(image);
+ pixels = new byte[data.length * 3];
+ for (int i = 0; i < data.length; i++) {
+ int td = data[i];
+ int tind = i * 3;
+ pixels[tind++] = (byte) ((td >> 0) & 0xFF);
+ pixels[tind++] = (byte) ((td >> 8) & 0xFF);
+ pixels[tind] = (byte) ((td >> 16) & 0xFF);
+ }
+ }
+
+ protected int[] getImageData(Bitmap img) {
+ int w = img.getWidth();
+ int h = img.getHeight();
+
+ int[] data = new int[w * h];
+ img.getPixels(data, 0, w, 0, 0, w, h);
+ return data;
+ }
+
+ /**
+ * Writes Graphic Control Extension
+ */
+ protected void writeGraphicCtrlExt() throws IOException {
+ out.write(0x21); // extension introducer
+ out.write(0xf9); // GCE label
+ out.write(4); // data block size
+ int transp, disp;
+ if (transparent == -1) {
+ transp = 0;
+ disp = 0; // dispose = no action
+ } else {
+ transp = 1;
+ disp = 2; // force clear if using transparent color
+ }
+ if (dispose >= 0) {
+ disp = dispose & 7; // user override
+ }
+ disp <<= 2;
+
+ // packed fields
+ out.write(0 | // 1:3 reserved
+ disp | // 4:6 disposal
+ 0 | // 7 user input - 0 = none
+ transp); // 8 transparency flag
+
+ writeShort(delay); // delay x 1/100 sec
+ out.write(transIndex); // transparent color index
+ out.write(0); // block terminator
+ }
+
+ /**
+ * Writes Image Descriptor
+ */
+ protected void writeImageDesc() throws IOException {
+ out.write(0x2c); // image separator
+ writeShort(x); // image position x,y = 0,0
+ writeShort(y);
+ writeShort(width); // image size
+ writeShort(height);
+ // packed fields
+ if (firstFrame) {
+ // no LCT - GCT is used for first (or only) frame
+ out.write(0);
+ } else {
+ // specify normal LCT
+ out.write(0x80 | // 1 local color table 1=yes
+ 0 | // 2 interlace - 0=no
+ 0 | // 3 sorted - 0=no
+ 0 | // 4-5 reserved
+ palSize); // 6-8 size of color table
+ }
+ }
+
+ /**
+ * Writes Logical Screen Descriptor
+ */
+ protected void writeLSD() throws IOException {
+ // logical screen size
+ writeShort(width);
+ writeShort(height);
+ // packed fields
+ out.write((0x80 | // 1 : global color table flag = 1 (gct used)
+ 0x70 | // 2-4 : color resolution = 7
+ 0x00 | // 5 : gct sort flag = 0
+ palSize)); // 6-8 : gct size
+
+ out.write(0); // background color index
+ out.write(0); // pixel aspect ratio - assume 1:1
+ }
+
+ /**
+ * Writes Netscape application extension to define repeat count.
+ */
+ protected void writeNetscapeExt() throws IOException {
+ out.write(0x21); // extension introducer
+ out.write(0xff); // app extension label
+ out.write(11); // block size
+ writeString("NETSCAPE" + "2.0"); // app id + auth code
+ out.write(3); // sub-block size
+ out.write(1); // loop sub-block id
+ writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
+ out.write(0); // block terminator
+ }
+
+ /**
+ * Writes color table
+ */
+ protected void writePalette() throws IOException {
+ out.write(colorTab, 0, colorTab.length);
+ int n = (3 * 256) - colorTab.length;
+ for (int i = 0; i < n; i++) {
+ out.write(0);
+ }
+ }
+
+ /**
+ * Encodes and writes pixel data
+ */
+ protected void writePixels() throws IOException {
+ LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
+ encoder.encode(out);
+ }
+
+ /**
+ * Write 16-bit value to output stream, LSB first
+ */
+ protected void writeShort(int value) throws IOException {
+ out.write(value & 0xff);
+ out.write((value >> 8) & 0xff);
+ }
+
+ /**
+ * Writes string to output stream
+ */
+ protected void writeString(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ out.write((byte) s.charAt(i));
+ }
+ }
+}
+
+ /*
+ * NeuQuant Neural-Net Quantization Algorithm
+ * ------------------------------------------
+ *
+ * Copyright (c) 1994 Anthony Dekker
+ *
+ * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
+ * "Kohonen neural networks for optimal colour quantization" in "Network:
+ * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
+ * the algorithm.
+ *
+ * Any party obtaining a copy of these files from the author, directly or
+ * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
+ * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
+ * this software and documentation files (the "Software"), including without
+ * limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons who
+ * receive copies from any such party to do so, with the only requirement being
+ * that this copyright notice remain intact.
+ */
+
+// Ported to Java 12/00 K Weiner
+class NeuQuant {
+
+ protected static final int netsize = 256; /* number of colours used */
+
+ /* four primes near 500 - assume no image has a length so large */
+ /* that it is divisible by all four primes */
+ protected static final int prime1 = 499;
+
+ protected static final int prime2 = 491;
+
+ protected static final int prime3 = 487;
+
+ protected static final int prime4 = 503;
+
+ protected static final int minpicturebytes = (3 * prime4);
+
+ /* minimum size for input image */
+
+ /*
+ * Program Skeleton ---------------- [select samplefac in range 1..30] [read
+ * image from input file] pic = (unsigned char*) malloc(3*width*height);
+ * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
+ * image header, using writecolourmap(f)] inxbuild(); write output image using
+ * inxsearch(b,g,r)
+ */
+
+ /*
+ * Network Definitions -------------------
+ */
+
+ protected static final int maxnetpos = (netsize - 1);
+
+ protected static final int netbiasshift = 4; /* bias for colour values */
+
+ protected static final int ncycles = 100; /* no. of learning cycles */
+
+ /* defs for freq and bias */
+ protected static final int intbiasshift = 16; /* bias for fractions */
+
+ protected static final int intbias = (((int) 1) << intbiasshift);
+
+ protected static final int gammashift = 10; /* gamma = 1024 */
+
+ protected static final int gamma = (((int) 1) << gammashift);
+
+ protected static final int betashift = 10;
+
+ protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
+
+ protected static final int betagamma = (intbias << (gammashift - betashift));
+
+ /* defs for decreasing radius factor */
+ protected static final int initrad = (netsize >> 3); /*
+ * for 256 cols, radius
+ * starts
+ */
+
+ protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
+
+ protected static final int radiusbias = (((int) 1) << radiusbiasshift);
+
+ protected static final int initradius = (initrad * radiusbias); /*
+ * and
+ * decreases
+ * by a
+ */
+
+ protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
+
+ /* defs for decreasing alpha factor */
+ protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
+
+ protected static final int initalpha = (((int) 1) << alphabiasshift);
+
+ protected int alphadec; /* biased by 10 bits */
+
+ /* radbias and alpharadbias used for radpower calculation */
+ protected static final int radbiasshift = 8;
+
+ protected static final int radbias = (((int) 1) << radbiasshift);
+
+ protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
+
+ protected static final int alpharadbias = (((int) 1) << alpharadbshift);
+
+ /*
+ * Types and Global Variables --------------------------
+ */
+
+ protected byte[] thepicture; /* the input image itself */
+
+ protected int lengthcount; /* lengthcount = H*W*3 */
+
+ protected int samplefac; /* sampling factor 1..30 */
+
+ // typedef int pixel[4]; /* BGRc */
+ protected int[][] network; /* the network itself - [netsize][4] */
+
+ protected int[] netindex = new int[256];
+
+ /* for network lookup - really 256 */
+
+ protected int[] bias = new int[netsize];
+
+ /* bias and freq arrays for learning */
+ protected int[] freq = new int[netsize];
+
+ protected int[] radpower = new int[initrad];
+
+ /* radpower for precomputation */
+
+ /*
+ * Initialise network in range (0,0,0) to (255,255,255) and set parameters
+ * -----------------------------------------------------------------------
+ */
+ public NeuQuant(byte[] thepic, int len, int sample) {
+
+ int i;
+ int[] p;
+
+ thepicture = thepic;
+ lengthcount = len;
+ samplefac = sample;
+
+ network = new int[netsize][];
+ for (i = 0; i < netsize; i++) {
+ network[i] = new int[4];
+ p = network[i];
+ p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
+ freq[i] = intbias / netsize; /* 1/netsize */
+ bias[i] = 0;
+ }
+ }
+
+ public byte[] colorMap() {
+ byte[] map = new byte[3 * netsize];
+ int[] index = new int[netsize];
+ for (int i = 0; i < netsize; i++)
+ index[network[i][3]] = i;
+ int k = 0;
+ for (int i = 0; i < netsize; i++) {
+ int j = index[i];
+ map[k++] = (byte) (network[j][0]);
+ map[k++] = (byte) (network[j][1]);
+ map[k++] = (byte) (network[j][2]);
+ }
+ return map;
+ }
+
+ /*
+ * Insertion sort of network and building of netindex[0..255] (to do after
+ * unbias)
+ * -------------------------------------------------------------------------------
+ */
+ public void inxbuild() {
+
+ int i, j, smallpos, smallval;
+ int[] p;
+ int[] q;
+ int previouscol, startpos;
+
+ previouscol = 0;
+ startpos = 0;
+ for (i = 0; i < netsize; i++) {
+ p = network[i];
+ smallpos = i;
+ smallval = p[1]; /* index on g */
+ /* find smallest in i..netsize-1 */
+ for (j = i + 1; j < netsize; j++) {
+ q = network[j];
+ if (q[1] < smallval) { /* index on g */
+ smallpos = j;
+ smallval = q[1]; /* index on g */
+ }
+ }
+ q = network[smallpos];
+ /* swap p (i) and q (smallpos) entries */
+ if (i != smallpos) {
+ j = q[0];
+ q[0] = p[0];
+ p[0] = j;
+ j = q[1];
+ q[1] = p[1];
+ p[1] = j;
+ j = q[2];
+ q[2] = p[2];
+ p[2] = j;
+ j = q[3];
+ q[3] = p[3];
+ p[3] = j;
+ }
+ /* smallval entry is now in position i */
+ if (smallval != previouscol) {
+ netindex[previouscol] = (startpos + i) >> 1;
+ for (j = previouscol + 1; j < smallval; j++)
+ netindex[j] = i;
+ previouscol = smallval;
+ startpos = i;
+ }
+ }
+ netindex[previouscol] = (startpos + maxnetpos) >> 1;
+ for (j = previouscol + 1; j < 256; j++)
+ netindex[j] = maxnetpos; /* really 256 */
+ }
+
+ /*
+ * Main Learning Loop ------------------
+ */
+ public void learn() {
+
+ int i, j, b, g, r;
+ int radius, rad, alpha, step, delta, samplepixels;
+ byte[] p;
+ int pix, lim;
+
+ if (lengthcount < minpicturebytes)
+ samplefac = 1;
+ alphadec = 30 + ((samplefac - 1) / 3);
+ p = thepicture;
+ pix = 0;
+ lim = lengthcount;
+ samplepixels = lengthcount / (3 * samplefac);
+ delta = samplepixels / ncycles;
+ alpha = initalpha;
+ radius = initradius;
+
+ rad = radius >> radiusbiasshift;
+ if (rad <= 1)
+ rad = 0;
+ for (i = 0; i < rad; i++)
+ radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
+
+ // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
+
+ if (lengthcount < minpicturebytes)
+ step = 3;
+ else if ((lengthcount % prime1) != 0)
+ step = 3 * prime1;
+ else {
+ if ((lengthcount % prime2) != 0)
+ step = 3 * prime2;
+ else {
+ if ((lengthcount % prime3) != 0)
+ step = 3 * prime3;
+ else
+ step = 3 * prime4;
+ }
+ }
+
+ i = 0;
+ while (i < samplepixels) {
+ b = (p[pix + 0] & 0xff) << netbiasshift;
+ g = (p[pix + 1] & 0xff) << netbiasshift;
+ r = (p[pix + 2] & 0xff) << netbiasshift;
+ j = contest(b, g, r);
+
+ altersingle(alpha, j, b, g, r);
+ if (rad != 0)
+ alterneigh(rad, j, b, g, r); /* alter neighbours */
+
+ pix += step;
+ if (pix >= lim)
+ pix -= lengthcount;
+
+ i++;
+ if (delta == 0)
+ delta = 1;
+ if (i % delta == 0) {
+ alpha -= alpha / alphadec;
+ radius -= radius / radiusdec;
+ rad = radius >> radiusbiasshift;
+ if (rad <= 1)
+ rad = 0;
+ for (j = 0; j < rad; j++)
+ radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
+ }
+ }
+ // fprintf(stderr,"finished 1D learning: final alpha=%f
+ // !\n",((float)alpha)/initalpha);
+ }
+
+ /*
+ * Search for BGR values 0..255 (after net is unbiased) and return colour
+ * index
+ * ----------------------------------------------------------------------------
+ */
+ public int map(int b, int g, int r) {
+
+ int i, j, dist, a, bestd;
+ int[] p;
+ int best;
+
+ bestd = 1000; /* biggest possible dist is 256*3 */
+ best = -1;
+ i = netindex[g]; /* index on g */
+ j = i - 1; /* start at netindex[g] and work outwards */
+
+ while ((i < netsize) || (j >= 0)) {
+ if (i < netsize) {
+ p = network[i];
+ dist = p[1] - g; /* inx key */
+ if (dist >= bestd)
+ i = netsize; /* stop iter */
+ else {
+ i++;
+ if (dist < 0)
+ dist = -dist;
+ a = p[0] - b;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ a = p[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ if (j >= 0) {
+ p = network[j];
+ dist = g - p[1]; /* inx key - reverse dif */
+ if (dist >= bestd)
+ j = -1; /* stop iter */
+ else {
+ j--;
+ if (dist < 0)
+ dist = -dist;
+ a = p[0] - b;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ a = p[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ best = p[3];
+ }
+ }
+ }
+ }
+ }
+ return (best);
+ }
+
+ public byte[] process() {
+ learn();
+ unbiasnet();
+ inxbuild();
+ return colorMap();
+ }
+
+ /*
+ * Unbias network to give byte values 0..255 and record position i to prepare
+ * for sort
+ * -----------------------------------------------------------------------------------
+ */
+ public void unbiasnet() {
+
+ int i;
+
+ for (i = 0; i < netsize; i++) {
+ network[i][0] >>= netbiasshift;
+ network[i][1] >>= netbiasshift;
+ network[i][2] >>= netbiasshift;
+ network[i][3] = i; /* record colour no */
+ }
+ }
+
+ /*
+ * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
+ * radpower[|i-j|]
+ * ---------------------------------------------------------------------------------
+ */
+ protected void alterneigh(int rad, int i, int b, int g, int r) {
+
+ int j, k, lo, hi, a, m;
+ int[] p;
+
+ lo = i - rad;
+ if (lo < -1)
+ lo = -1;
+ hi = i + rad;
+ if (hi > netsize)
+ hi = netsize;
+
+ j = i + 1;
+ k = i - 1;
+ m = 1;
+ while ((j < hi) || (k > lo)) {
+ a = radpower[m++];
+ if (j < hi) {
+ p = network[j++];
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (Exception e) {
+ } // prevents 1.3 miscompilation
+ }
+ if (k > lo) {
+ p = network[k--];
+ try {
+ p[0] -= (a * (p[0] - b)) / alpharadbias;
+ p[1] -= (a * (p[1] - g)) / alpharadbias;
+ p[2] -= (a * (p[2] - r)) / alpharadbias;
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ /*
+ * Move neuron i towards biased (b,g,r) by factor alpha
+ * ----------------------------------------------------
+ */
+ protected void altersingle(int alpha, int i, int b, int g, int r) {
+
+ /* alter hit neuron */
+ int[] n = network[i];
+ n[0] -= (alpha * (n[0] - b)) / initalpha;
+ n[1] -= (alpha * (n[1] - g)) / initalpha;
+ n[2] -= (alpha * (n[2] - r)) / initalpha;
+ }
+
+ /*
+ * Search for biased BGR values ----------------------------
+ */
+ protected int contest(int b, int g, int r) {
+
+ /* finds closest neuron (min dist) and updates freq */
+ /* finds best neuron (min dist-bias) and returns position */
+ /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
+ /* bias[i] = gamma*((1/netsize)-freq[i]) */
+
+ int i, dist, a, biasdist, betafreq;
+ int bestpos, bestbiaspos, bestd, bestbiasd;
+ int[] n;
+
+ bestd = ~(((int) 1) << 31);
+ bestbiasd = bestd;
+ bestpos = -1;
+ bestbiaspos = bestpos;
+
+ for (i = 0; i < netsize; i++) {
+ n = network[i];
+ dist = n[0] - b;
+ if (dist < 0)
+ dist = -dist;
+ a = n[1] - g;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ a = n[2] - r;
+ if (a < 0)
+ a = -a;
+ dist += a;
+ if (dist < bestd) {
+ bestd = dist;
+ bestpos = i;
+ }
+ biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
+ if (biasdist < bestbiasd) {
+ bestbiasd = biasdist;
+ bestbiaspos = i;
+ }
+ betafreq = (freq[i] >> betashift);
+ freq[i] -= betafreq;
+ bias[i] += (betafreq << gammashift);
+ }
+ freq[bestpos] += beta;
+ bias[bestpos] -= betagamma;
+ return (bestbiaspos);
+ }
+}
+
+// ==============================================================================
+// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
+// K Weiner 12/00
+
+class LZWEncoder {
+
+ private static final int EOF = -1;
+
+ private int imgW, imgH;
+
+ private byte[] pixAry;
+
+ private int initCodeSize;
+
+ private int remaining;
+
+ private int curPixel;
+
+ // GIFCOMPR.C - GIF Image compression routines
+ //
+ // Lempel-Ziv compression based on 'compress'. GIF modifications by
+ // David Rowley (mgardi@watdcsu.waterloo.edu)
+
+ // General DEFINEs
+
+ static final int BITS = 12;
+
+ static final int HSIZE = 5003; // 80% occupancy
+
+ // GIF Image compression - modified 'compress'
+ //
+ // Based on: compress.c - File compression ala IEEE Computer, June 1984.
+ //
+ // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
+ // Jim McKie (decvax!mcvax!jim)
+ // Steve Davies (decvax!vax135!petsd!peora!srd)
+ // Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ // James A. Woods (decvax!ihnp4!ames!jaw)
+ // Joe Orost (decvax!vax135!petsd!joe)
+
+ int n_bits; // number of bits/code
+
+ int maxbits = BITS; // user settable max # bits/code
+
+ int maxcode; // maximum code, given n_bits
+
+ int maxmaxcode = 1 << BITS; // should NEVER generate this code
+
+ int[] htab = new int[HSIZE];
+
+ int[] codetab = new int[HSIZE];
+
+ int hsize = HSIZE; // for dynamic table sizing
+
+ int free_ent = 0; // first unused entry
+
+ // block compression parameters -- after all codes are used up,
+ // and compression rate changes, start over.
+ boolean clear_flg = false;
+
+ // Algorithm: use open addressing double hashing (no chaining) on the
+ // prefix code / next character combination. We do a variant of Knuth's
+ // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ // secondary probe. Here, the modular division first probe is gives way
+ // to a faster exclusive-or manipulation. Also do block compression with
+ // an adaptive reset, whereby the code table is cleared when the compression
+ // ratio decreases, but after the table fills. The variable-length output
+ // codes are re-sized at this point, and a special CLEAR code is generated
+ // for the decompressor. Late addition: construct the table according to
+ // file size for noticeable speed improvement on small files. Please direct
+ // questions about this implementation to ames!jaw.
+
+ int g_init_bits;
+
+ int ClearCode;
+
+ int EOFCode;
+
+ // output
+ //
+ // Output the given code.
+ // Inputs:
+ // code: A n_bits-bit integer. If == -1, then EOF. This assumes
+ // that n_bits =< wordsize - 1.
+ // Outputs:
+ // Outputs code to the file.
+ // Assumptions:
+ // Chars are 8 bits long.
+ // Algorithm:
+ // Maintain a BITS character long buffer (so that 8 codes will
+ // fit in it exactly). Use the VAX insv instruction to insert each
+ // code in turn. When the buffer fills up empty it and start over.
+
+ int cur_accum = 0;
+
+ int cur_bits = 0;
+
+ int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
+ 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
+
+ // Number of characters so far in this 'packet'
+ int a_count;
+
+ // Define the storage for the packet accumulator
+ byte[] accum = new byte[256];
+
+ // ----------------------------------------------------------------------------
+ LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
+ imgW = width;
+ imgH = height;
+ pixAry = pixels;
+ initCodeSize = Math.max(2, color_depth);
+ }
+
+ // Add a character to the end of the current packet, and if it is 254
+ // characters, flush the packet to disk.
+ void char_out(byte c, OutputStream outs) throws IOException {
+ accum[a_count++] = c;
+ if (a_count >= 254)
+ flush_char(outs);
+ }
+
+ // Clear out the hash table
+
+ // table clear for block compress
+ void cl_block(OutputStream outs) throws IOException {
+ cl_hash(hsize);
+ free_ent = ClearCode + 2;
+ clear_flg = true;
+
+ output(ClearCode, outs);
+ }
+
+ // reset code table
+ void cl_hash(int hsize) {
+ for (int i = 0; i < hsize; ++i)
+ htab[i] = -1;
+ }
+
+ void compress(int init_bits, OutputStream outs) throws IOException {
+ int fcode;
+ int i /* = 0 */;
+ int c;
+ int ent;
+ int disp;
+ int hsize_reg;
+ int hshift;
+
+ // Set up the globals: g_init_bits - initial number of bits
+ g_init_bits = init_bits;
+
+ // Set up the necessary values
+ clear_flg = false;
+ n_bits = g_init_bits;
+ maxcode = MAXCODE(n_bits);
+
+ ClearCode = 1 << (init_bits - 1);
+ EOFCode = ClearCode + 1;
+ free_ent = ClearCode + 2;
+
+ a_count = 0; // clear packet
+
+ ent = nextPixel();
+
+ hshift = 0;
+ for (fcode = hsize; fcode < 65536; fcode *= 2)
+ ++hshift;
+ hshift = 8 - hshift; // set hash code range bound
+
+ hsize_reg = hsize;
+ cl_hash(hsize_reg); // clear hash table
+
+ output(ClearCode, outs);
+
+ outer_loop:
+ while ((c = nextPixel()) != EOF) {
+ fcode = (c << maxbits) + ent;
+ i = (c << hshift) ^ ent; // xor hashing
+
+ if (htab[i] == fcode) {
+ ent = codetab[i];
+ continue;
+ } else if (htab[i] >= 0) // non-empty slot
+ {
+ disp = hsize_reg - i; // secondary hash (after G. Knott)
+ if (i == 0)
+ disp = 1;
+ do {
+ if ((i -= disp) < 0)
+ i += hsize_reg;
+
+ if (htab[i] == fcode) {
+ ent = codetab[i];
+ continue outer_loop;
+ }
+ } while (htab[i] >= 0);
+ }
+ output(ent, outs);
+ ent = c;
+ if (free_ent < maxmaxcode) {
+ codetab[i] = free_ent++; // code -> hashtable
+ htab[i] = fcode;
+ } else
+ cl_block(outs);
+ }
+ // Put out the final code.
+ output(ent, outs);
+ output(EOFCode, outs);
+ }
+
+ // ----------------------------------------------------------------------------
+ void encode(OutputStream os) throws IOException {
+ os.write(initCodeSize); // write "initial code size" byte
+
+ remaining = imgW * imgH; // reset navigation variables
+ curPixel = 0;
+
+ compress(initCodeSize + 1, os); // compress and write the pixel data
+
+ os.write(0); // write block terminator
+ }
+
+ // Flush the packet to disk, and reset the accumulator
+ void flush_char(OutputStream outs) throws IOException {
+ if (a_count > 0) {
+ outs.write(a_count);
+ outs.write(accum, 0, a_count);
+ a_count = 0;
+ }
+ }
+
+ final int MAXCODE(int n_bits) {
+ return (1 << n_bits) - 1;
+ }
+
+ // ----------------------------------------------------------------------------
+ // Return the next pixel from the image
+ // ----------------------------------------------------------------------------
+ private int nextPixel() {
+ if (remaining == 0)
+ return EOF;
+
+ --remaining;
+
+ byte pix = pixAry[curPixel++];
+
+ return pix & 0xff;
+ }
+
+ void output(int code, OutputStream outs) throws IOException {
+ cur_accum &= masks[cur_bits];
+
+ if (cur_bits > 0)
+ cur_accum |= (code << cur_bits);
+ else
+ cur_accum = code;
+
+ cur_bits += n_bits;
+
+ while (cur_bits >= 8) {
+ char_out((byte) (cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+ }
+
+ // If the next entry is going to be too big for the code size,
+ // then increase it, if possible.
+ if (free_ent > maxcode || clear_flg) {
+ if (clear_flg) {
+ maxcode = MAXCODE(n_bits = g_init_bits);
+ clear_flg = false;
+ } else {
+ ++n_bits;
+ if (n_bits == maxbits)
+ maxcode = maxmaxcode;
+ else
+ maxcode = MAXCODE(n_bits);
+ }
+ }
+
+ if (code == EOFCode) {
+ // At EOF, write the rest of the buffer.
+ while (cur_bits > 0) {
+ char_out((byte) (cur_accum & 0xff), outs);
+ cur_accum >>= 8;
+ cur_bits -= 8;
+ }
+
+ flush_char(outs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java
similarity index 82%
rename from gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java
rename to gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java
index 22d32dab3..9685d5266 100644
--- a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/CommonUtil.java
@@ -1,25 +1,30 @@
package com.shuyu.gsyvideoplayer.utils;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
-import android.support.v4.app.FragmentActivity;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.internal.view.ContextThemeWrapper;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.appcompat.widget.TintContextWrapper;
+import androidx.fragment.app.FragmentActivity;
+
import android.util.DisplayMetrics;
import android.util.TypedValue;
+import android.view.Surface;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import java.io.File;
import java.util.Formatter;
import java.util.Locale;
+
/**
* 公共类
* Created by shuyu on 2016/11/11.
@@ -63,6 +68,8 @@ public static Activity scanForActivity(Context context) {
if (context instanceof Activity) {
return (Activity) context;
+ } else if (context instanceof TintContextWrapper) {
+ return scanForActivity(((TintContextWrapper) context).getBaseContext());
} else if (context instanceof ContextWrapper) {
return scanForActivity(((ContextWrapper) context).getBaseContext());
}
@@ -101,6 +108,7 @@ public static int getActionBarHeight(Activity activity) {
}
+ @SuppressLint("RestrictedApi")
public static void hideSupportActionBar(Context context, boolean actionBar, boolean statusBar) {
if (actionBar) {
AppCompatActivity appCompatActivity = CommonUtil.getAppCompActivity(context);
@@ -117,6 +125,10 @@ public static void hideSupportActionBar(Context context, boolean actionBar, bool
FragmentActivity fragmentActivity = (FragmentActivity) context;
fragmentActivity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else if (context instanceof Activity) {
+ Activity activity = (Activity) context;
+ activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
CommonUtil.getAppCompActivity(context).getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -124,6 +136,7 @@ public static void hideSupportActionBar(Context context, boolean actionBar, bool
}
}
+ @SuppressLint("RestrictedApi")
public static void showSupportActionBar(Context context, boolean actionBar, boolean statusBar) {
if (actionBar) {
AppCompatActivity appCompatActivity = CommonUtil.getAppCompActivity(context);
@@ -140,6 +153,9 @@ public static void showSupportActionBar(Context context, boolean actionBar, bool
if (context instanceof FragmentActivity) {
FragmentActivity fragmentActivity = (FragmentActivity) context;
fragmentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else if (context instanceof Activity) {
+ Activity activity = (Activity) context;
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
CommonUtil.getAppCompActivity(context).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@@ -147,7 +163,15 @@ public static void showSupportActionBar(Context context, boolean actionBar, bool
}
public static void hideNavKey(Context context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (Build.VERSION.SDK_INT >= 29) {
+ // 设置屏幕始终在前面,不然点击鼠标,重新出现虚拟按键
+ ((Activity) context).getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav
+ // bar
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置屏幕始终在前面,不然点击鼠标,重新出现虚拟按键
((Activity) context).getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -261,9 +285,18 @@ public static Activity getActivityContext(Context context) {
return null;
else if (context instanceof Activity)
return (Activity) context;
+ else if (context instanceof TintContextWrapper)
+ return scanForActivity(((TintContextWrapper) context).getBaseContext());
else if (context instanceof ContextWrapper)
return scanForActivity(((ContextWrapper) context).getBaseContext());
return null;
}
+
+
+ public static boolean getCurrentScreenLand(Activity context) {
+ return context.getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_90 ||
+ context.getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_270;
+
+ }
}
diff --git a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/Debuger.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/Debuger.java
similarity index 100%
rename from gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/Debuger.java
rename to gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/Debuger.java
diff --git a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java
similarity index 54%
rename from gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java
rename to gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java
index a519f68d3..b47e77975 100644
--- a/gsyVideoPlayer/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/FileUtils.java
@@ -1,8 +1,13 @@
package com.shuyu.gsyvideoplayer.utils;
+import android.graphics.Bitmap;
import android.os.Environment;
+import android.widget.Toast;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
public class FileUtils {
@@ -23,11 +28,21 @@ public static String getAppPath(String name) {
}
public static String getPath() {
- return getAppPath(NAME);
+ String path = getAppPath(NAME);
+ File file = new File(path);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return path;
}
public static String getTestPath() {
- return getAppPath(NAME_TEST);
+ String path = getAppPath(NAME_TEST);
+ File file = new File(path);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ return path;
}
public static void deleteFiles(File root) {
@@ -45,4 +60,17 @@ public static void deleteFiles(File root) {
}
}
+ public static void saveBitmap(Bitmap bitmap, File file) {
+ if (bitmap != null) {
+ OutputStream outputStream;
+ try {
+ outputStream = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+ bitmap.recycle();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/GSYVideoHelper.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/GSYVideoHelper.java
new file mode 100644
index 000000000..87ff2b5c2
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/utils/GSYVideoHelper.java
@@ -0,0 +1,743 @@
+package com.shuyu.gsyvideoplayer.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.FrameLayout;
+
+import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder;
+import com.shuyu.gsyvideoplayer.listener.GSYVideoProgressListener;
+import com.shuyu.gsyvideoplayer.listener.LockClickListener;
+import com.shuyu.gsyvideoplayer.listener.VideoAllCallBack;
+import com.shuyu.gsyvideoplayer.render.view.GSYVideoGLView;
+import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoPlayer;
+
+
+import java.io.File;
+import java.util.Map;
+
+import androidx.transition.TransitionManager;
+
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.getActionBarHeight;
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.getStatusBarHeight;
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.hideNavKey;
+import static com.shuyu.gsyvideoplayer.utils.CommonUtil.showNavKey;
+
+/**
+ * 视频帮助类,更加节省资源
+ * Created by guoshuyu on 2018/1/15.
+ */
+public class GSYVideoHelper {
+
+ /**
+ * 播放的标志
+ */
+ private String TAG = "NULL";
+ /**
+ * 播放器
+ */
+ private StandardGSYVideoPlayer mGsyVideoPlayer;
+ /**
+ * 全屏承载布局
+ */
+ private ViewGroup mFullViewContainer;
+ /**
+ * 全屏承载布局
+ */
+ private ViewGroup mWindowViewContainer;
+ /**
+ * 记录列表中item的父布局
+ */
+ private ViewGroup mParent;
+ /**
+ * 布局
+ */
+ private ViewGroup.LayoutParams mNormalParams;
+ /**
+ * 选择工具类
+ */
+ private OrientationUtils mOrientationUtils;
+ /**
+ * 播放配置
+ */
+ private GSYVideoHelperBuilder mVideoOptionBuilder;
+ /**
+ * 上下文
+ */
+ private Context mContext;
+ /**
+ * 播放的位置
+ */
+ private int mPlayPosition = -1;
+ /**
+ * 可视保存
+ */
+ private int mSystemUiVisibility;
+ /**
+ * 当前是否全屏
+ */
+ private boolean isFull;
+ /**
+ * 当前是否小屏
+ */
+ private boolean isSmall;
+ /**
+ * 当前item框的屏幕位置
+ */
+ private int[] mNormalItemRect;
+ /**
+ * 当前item的大小
+ */
+ private int[] mNormalItemSize;
+ /**
+ * handler
+ */
+ private Handler mHandler = new Handler();
+
+
+ public GSYVideoHelper(Context context) {
+ this(context, new StandardGSYVideoPlayer(context));
+ }
+
+ public GSYVideoHelper(Context context, StandardGSYVideoPlayer player) {
+ mGsyVideoPlayer = player;
+ this.mContext = context;
+ this.mWindowViewContainer = (ViewGroup) (CommonUtil.scanForActivity(context)).findViewById(Window.ID_ANDROID_CONTENT);
+
+ }
+
+ /**
+ * 处理全屏逻辑
+ */
+ private void resolveToFull() {
+ mSystemUiVisibility = ((Activity) mContext).getWindow().getDecorView().getSystemUiVisibility();
+ CommonUtil.hideSupportActionBar(mContext, mVideoOptionBuilder.isHideActionBar(), mVideoOptionBuilder.isHideStatusBar());
+ if (mVideoOptionBuilder.isHideKey()) {
+ hideNavKey(mContext);
+ }
+ isFull = true;
+ ViewGroup viewGroup = (ViewGroup) mGsyVideoPlayer.getParent();
+ mNormalParams = mGsyVideoPlayer.getLayoutParams();
+ if (viewGroup != null) {
+ mParent = viewGroup;
+ viewGroup.removeView(mGsyVideoPlayer);
+ }
+ mGsyVideoPlayer.setIfCurrentIsFullscreen(true);
+ mGsyVideoPlayer.getFullscreenButton().setImageResource(mGsyVideoPlayer.getShrinkImageRes());
+ mGsyVideoPlayer.getBackButton().setVisibility(View.VISIBLE);
+ //设置旋转
+ mOrientationUtils = new OrientationUtils((Activity) mContext, mGsyVideoPlayer);
+ mOrientationUtils.setEnable(mVideoOptionBuilder.isRotateViewAuto());
+ mGsyVideoPlayer.getBackButton().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resolveMaterialToNormal(mGsyVideoPlayer);
+ }
+ });
+ if (mVideoOptionBuilder.isShowFullAnimation()) {
+ if (mFullViewContainer instanceof FrameLayout) {
+ //目前只做了frameLoayout的判断
+ resolveMaterialAnimation();
+ } else {
+ resolveFullAdd();
+ }
+
+ } else {
+ resolveFullAdd();
+ }
+ }
+
+ /**
+ * 添加到全屏父布局里
+ */
+ private void resolveFullAdd() {
+ if (mVideoOptionBuilder.isShowFullAnimation()) {
+ if (mFullViewContainer != null) {
+ mFullViewContainer.setBackgroundColor(Color.BLACK);
+ }
+ }
+ resolveChangeFirstLogic(0);
+ if (mFullViewContainer != null) {
+ mFullViewContainer.addView(mGsyVideoPlayer);
+ } else {
+ mWindowViewContainer.addView(mGsyVideoPlayer);
+ }
+ }
+
+ /**
+ * 如果是5.0的动画开始位置
+ */
+ private void resolveMaterialAnimation() {
+ mNormalItemRect = new int[2];
+ mNormalItemSize = new int[2];
+ saveLocationStatus(mContext, mVideoOptionBuilder.isHideActionBar(), mVideoOptionBuilder.isHideStatusBar());
+ FrameLayout.LayoutParams lpParent = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ FrameLayout frameLayout = new FrameLayout(mContext);
+ frameLayout.setBackgroundColor(Color.BLACK);
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mNormalItemSize[0], mNormalItemSize[1]);
+ lp.setMargins(mNormalItemRect[0], mNormalItemRect[1], 0, 0);
+ frameLayout.addView(mGsyVideoPlayer, lp);
+ if (mFullViewContainer != null) {
+ mFullViewContainer.addView(frameLayout, lpParent);
+ } else {
+ mWindowViewContainer.addView(frameLayout, lpParent);
+ }
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ //开始动画
+ if (mFullViewContainer != null) {
+ TransitionManager.beginDelayedTransition(mFullViewContainer);
+ } else {
+ TransitionManager.beginDelayedTransition(mWindowViewContainer);
+ }
+ resolveMaterialFullVideoShow(mGsyVideoPlayer);
+ resolveChangeFirstLogic(600);
+ }
+ }, 300);
+ }
+
+ /**
+ * 如果是5.0的,要从原位置过度到全屏位置
+ */
+ private void resolveMaterialFullVideoShow(GSYBaseVideoPlayer gsyVideoPlayer) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) gsyVideoPlayer.getLayoutParams();
+ lp.setMargins(0, 0, 0, 0);
+ lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ lp.gravity = Gravity.CENTER;
+ gsyVideoPlayer.setLayoutParams(lp);
+ gsyVideoPlayer.setIfCurrentIsFullscreen(true);
+ }
+
+
+ /**
+ * 处理正常逻辑
+ */
+ private void resolveToNormal() {
+ int delay = mOrientationUtils.backToProtVideo();
+ if (!mVideoOptionBuilder.isShowFullAnimation()) {
+ delay = 0;
+ }
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ isFull = false;
+ removeWindowContainer();
+ if (mFullViewContainer != null) {
+ mFullViewContainer.removeAllViews();
+ }
+ if (mGsyVideoPlayer.getParent() != null) {
+ ((ViewGroup) mGsyVideoPlayer.getParent()).removeView(mGsyVideoPlayer);
+ }
+ mOrientationUtils.setEnable(false);
+ mGsyVideoPlayer.setIfCurrentIsFullscreen(false);
+ if (mFullViewContainer != null) {
+ mFullViewContainer.setBackgroundColor(Color.TRANSPARENT);
+ }
+ mParent.addView(mGsyVideoPlayer, mNormalParams);
+ mGsyVideoPlayer.getFullscreenButton().setImageResource(mGsyVideoPlayer.getEnlargeImageRes());
+ mGsyVideoPlayer.getBackButton().setVisibility(View.GONE);
+ mGsyVideoPlayer.setIfCurrentIsFullscreen(false);
+ mGsyVideoPlayer.restartTimerTask();
+ if (mVideoOptionBuilder.getVideoAllCallBack() != null) {
+ Debuger.printfLog("onQuitFullscreen");
+ mVideoOptionBuilder.getVideoAllCallBack().onQuitFullscreen(mVideoOptionBuilder.getUrl(), mVideoOptionBuilder.getVideoTitle(), mGsyVideoPlayer);
+ }
+ if (mVideoOptionBuilder.isHideKey()) {
+ showNavKey(mContext, mSystemUiVisibility);
+ }
+ CommonUtil.showSupportActionBar(mContext, mVideoOptionBuilder.isHideActionBar(), mVideoOptionBuilder.isHideStatusBar());
+ }
+ }, delay);
+ }
+
+
+ /**
+ * 动画回到正常效果
+ */
+ private void resolveMaterialToNormal(final GSYVideoPlayer gsyVideoPlayer) {
+ if (mVideoOptionBuilder.isShowFullAnimation() && mFullViewContainer instanceof FrameLayout) {
+ int delay = mOrientationUtils.backToProtVideo();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ TransitionManager.beginDelayedTransition(mFullViewContainer);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) gsyVideoPlayer.getLayoutParams();
+ lp.setMargins(mNormalItemRect[0], mNormalItemRect[1], 0, 0);
+ lp.width = mNormalItemSize[0];
+ lp.height = mNormalItemSize[1];
+ //注意配置回来,不然动画效果会不对
+ lp.gravity = Gravity.NO_GRAVITY;
+ gsyVideoPlayer.setLayoutParams(lp);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ resolveToNormal();
+ }
+ }, 400);
+ }
+ }, delay);
+ } else {
+ resolveToNormal();
+ }
+ }
+
+
+ /**
+ * 是否全屏一开始马上自动横屏
+ */
+ private void resolveChangeFirstLogic(int time) {
+ if (mVideoOptionBuilder.isLockLand()) {
+ if (time > 0) {
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mOrientationUtils.getIsLand() != 1) {
+ if (mFullViewContainer != null) {
+ mFullViewContainer.setBackgroundColor(Color.BLACK);
+ }
+ mOrientationUtils.resolveByClick();
+ }
+ }
+ }, time);
+ } else {
+ if (mOrientationUtils.getIsLand() != 1) {
+ if (mFullViewContainer != null) {
+ mFullViewContainer.setBackgroundColor(Color.BLACK);
+ }
+ mOrientationUtils.resolveByClick();
+ }
+ }
+ }
+ mGsyVideoPlayer.setIfCurrentIsFullscreen(true);
+ mGsyVideoPlayer.restartTimerTask();
+ if (mVideoOptionBuilder.getVideoAllCallBack() != null) {
+ Debuger.printfLog("onEnterFullscreen");
+ mVideoOptionBuilder.getVideoAllCallBack().onEnterFullscreen(mVideoOptionBuilder.getUrl(), mVideoOptionBuilder.getVideoTitle(), mGsyVideoPlayer);
+ }
+ }
+
+ /**
+ * 保存大小和状态
+ */
+ private void saveLocationStatus(Context context, boolean statusBar, boolean actionBar) {
+ mParent.getLocationOnScreen(mNormalItemRect);
+ int statusBarH = getStatusBarHeight(context);
+ int actionBerH = getActionBarHeight((Activity) context);
+ if (statusBar) {
+ mNormalItemRect[1] = mNormalItemRect[1] - statusBarH;
+ }
+ if (actionBar) {
+ mNormalItemRect[1] = mNormalItemRect[1] - actionBerH;
+ }
+ mNormalItemSize[0] = mParent.getWidth();
+ mNormalItemSize[1] = mParent.getHeight();
+ }
+
+
+ /**
+ * 是否当前播放
+ */
+ private boolean isPlayView(int position, String tag) {
+ return mPlayPosition == position && TAG.equals(tag);
+ }
+
+ private boolean isCurrentViewPlaying(int position, String tag) {
+ return isPlayView(position, tag);
+ }
+
+ private boolean removeWindowContainer() {
+ if (mWindowViewContainer != null && mWindowViewContainer.indexOfChild(mGsyVideoPlayer) != -1) {
+ mWindowViewContainer.removeView(mGsyVideoPlayer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 动态添加视频播放
+ *
+ * @param position 位置
+ * @param imgView 封面
+ * @param tag TAG类型
+ * @param container player的容器
+ * @param playBtn 播放按键
+ */
+ public void addVideoPlayer(final int position, View imgView, String tag,
+ ViewGroup container, View playBtn) {
+ container.removeAllViews();
+ if (isCurrentViewPlaying(position, tag)) {
+ if (!isFull) {
+ ViewGroup viewGroup = (ViewGroup) mGsyVideoPlayer.getParent();
+ if (viewGroup != null)
+ viewGroup.removeAllViews();
+ container.addView(mGsyVideoPlayer);
+ playBtn.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ playBtn.setVisibility(View.VISIBLE);
+ container.removeAllViews(); //增加封面
+ container.addView(imgView);
+ }
+ }
+
+ /**
+ * 设置列表播放中的位置和TAG,防止错位,回复播放位置
+ *
+ * @param playPosition 列表中的播放位置
+ * @param tag 播放的是哪个列表的tag
+ */
+ public void setPlayPositionAndTag(int playPosition, String tag) {
+ this.mPlayPosition = playPosition;
+ this.TAG = tag;
+ }
+
+ /**
+ * 开始播放
+ */
+ public void startPlay() {
+
+ if (isSmall()) {
+ smallVideoToNormal();
+ }
+
+ mGsyVideoPlayer.release();
+
+
+ if (mVideoOptionBuilder == null) {
+ throw new NullPointerException("mVideoOptionBuilder can't be null");
+ }
+
+ mVideoOptionBuilder.build(mGsyVideoPlayer);
+
+ //增加title
+ if (mGsyVideoPlayer.getTitleTextView() != null) {
+ mGsyVideoPlayer.getTitleTextView().setVisibility(View.GONE);
+ }
+
+ //设置返回键
+ if (mGsyVideoPlayer.getBackButton() != null) {
+ mGsyVideoPlayer.getBackButton().setVisibility(View.GONE);
+ }
+
+ //设置全屏按键功能
+ if (mGsyVideoPlayer.getFullscreenButton() != null) {
+ mGsyVideoPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ doFullBtnLogic();
+ }
+ });
+ }
+ mGsyVideoPlayer.startPlayLogic();
+ }
+
+ /**
+ * 全屏按键逻辑
+ */
+ public void doFullBtnLogic() {
+ if (!isFull) {
+ resolveToFull();
+ } else {
+ resolveMaterialToNormal(mGsyVideoPlayer);
+ }
+ }
+
+
+ /**
+ * 处理返回正常逻辑
+ */
+ public boolean backFromFull() {
+ boolean isFull = false;
+ if (mFullViewContainer != null && mFullViewContainer.getChildCount() > 0) {
+ isFull = true;
+ resolveMaterialToNormal(mGsyVideoPlayer);
+ } else if (mWindowViewContainer != null && mWindowViewContainer.indexOfChild(mGsyVideoPlayer) != -1) {
+ isFull = true;
+ resolveMaterialToNormal(mGsyVideoPlayer);
+ }
+ return isFull;
+ }
+
+ /**
+ * 释放持有的视频
+ */
+ public void releaseVideoPlayer() {
+ removeWindowContainer();
+ ViewGroup viewGroup = (ViewGroup) mGsyVideoPlayer.getParent();
+ if (viewGroup != null)
+ viewGroup.removeAllViews();
+ mPlayPosition = -1;
+ TAG = "NULL";
+ if (mOrientationUtils != null)
+ mOrientationUtils.releaseListener();
+
+ }
+
+ /**
+ * 显示小屏幕效果
+ *
+ * @param size 小视频的大小
+ * @param actionBar 是否有actionBar
+ * @param statusBar 是否有状态栏
+ */
+ public void showSmallVideo(Point size, final boolean actionBar, final boolean statusBar) {
+ if (mGsyVideoPlayer.getCurrentState() == GSYVideoPlayer.CURRENT_STATE_PLAYING) {
+ mGsyVideoPlayer.showSmallVideo(size, actionBar, statusBar);
+ isSmall = true;
+ }
+ }
+
+
+ /**
+ * 恢复小屏幕效果
+ */
+ public void smallVideoToNormal() {
+ isSmall = false;
+ mGsyVideoPlayer.hideSmallVideo();
+ }
+
+
+ /**
+ * 设置全屏显示的viewGroup
+ * 如果不设置即使用默认的 mWindowViewContainer
+ *
+ * @param fullViewContainer viewGroup
+ */
+ public void setFullViewContainer(ViewGroup fullViewContainer) {
+ this.mFullViewContainer = fullViewContainer;
+ }
+
+ /**
+ * 是否全屏
+ */
+ public boolean isFull() {
+ return isFull;
+ }
+
+ /**
+ * 设置配置
+ */
+ public void setGsyVideoOptionBuilder(GSYVideoHelperBuilder mVideoOptionBuilder) {
+ this.mVideoOptionBuilder = mVideoOptionBuilder;
+ }
+
+ public GSYVideoOptionBuilder getGsyVideoOptionBuilder() {
+ return mVideoOptionBuilder;
+ }
+
+ public int getPlayPosition() {
+ return mPlayPosition;
+ }
+
+ public String getPlayTAG() {
+ return TAG;
+ }
+
+ public boolean isSmall() {
+ return isSmall;
+ }
+
+ /**
+ * 获取播放器,直接拿播放器,根据需要自定义配置
+ */
+ public StandardGSYVideoPlayer getGsyVideoPlayer() {
+ return mGsyVideoPlayer;
+ }
+
+ /**
+ * 配置
+ */
+ public static class GSYVideoHelperBuilder extends GSYVideoOptionBuilder {
+
+ protected boolean mHideActionBar;
+
+ protected boolean mHideStatusBar;
+
+ public boolean isHideActionBar() {
+ return mHideActionBar;
+ }
+
+ public GSYVideoHelperBuilder setHideActionBar(boolean hideActionBar) {
+ this.mHideActionBar = hideActionBar;
+ return this;
+ }
+
+ public boolean isHideStatusBar() {
+ return mHideStatusBar;
+ }
+
+ public GSYVideoHelperBuilder setHideStatusBar(boolean hideStatusBar) {
+ this.mHideStatusBar = hideStatusBar;
+ return this;
+ }
+
+ public int getShrinkImageRes() {
+ return mShrinkImageRes;
+ }
+
+ public int getEnlargeImageRes() {
+ return mEnlargeImageRes;
+ }
+
+ public int getPlayPosition() {
+ return mPlayPosition;
+ }
+
+ public int getDialogProgressHighLightColor() {
+ return mDialogProgressHighLightColor;
+ }
+
+ public int getDialogProgressNormalColor() {
+ return mDialogProgressNormalColor;
+ }
+
+ public int getDismissControlTime() {
+ return mDismissControlTime;
+ }
+
+ public long getSeekOnStart() {
+ return mSeekOnStart;
+ }
+
+ public float getSeekRatio() {
+ return mSeekRatio;
+ }
+
+ public float getSpeed() {
+ return mSpeed;
+ }
+
+ public boolean isHideKey() {
+ return mHideKey;
+ }
+
+ public boolean isShowFullAnimation() {
+ return mShowFullAnimation;
+ }
+
+ public boolean isNeedShowWifiTip() {
+ return mNeedShowWifiTip;
+ }
+
+ public boolean isRotateViewAuto() {
+ return mRotateViewAuto;
+ }
+
+ public boolean isLockLand() {
+ return mLockLand;
+ }
+
+ public boolean isLooping() {
+ return mLooping;
+ }
+
+ public boolean isIsTouchWiget() {
+ return mIsTouchWiget;
+ }
+
+ public boolean isIsTouchWigetFull() {
+ return mIsTouchWigetFull;
+ }
+
+ public boolean isShowPauseCover() {
+ return mShowPauseCover;
+ }
+
+ public boolean isRotateWithSystem() {
+ return mRotateWithSystem;
+ }
+
+ public boolean isCacheWithPlay() {
+ return mCacheWithPlay;
+ }
+
+ public boolean isNeedLockFull() {
+ return mNeedLockFull;
+ }
+
+ public boolean isThumbPlay() {
+ return mThumbPlay;
+ }
+
+ public boolean isSounchTouch() {
+ return mSounchTouch;
+ }
+
+ public boolean isSetUpLazy() {
+ return mSetUpLazy;
+ }
+
+ public String getPlayTag() {
+ return mPlayTag;
+ }
+
+ public String getUrl() {
+ return mUrl;
+ }
+
+ public String getVideoTitle() {
+ return mVideoTitle;
+ }
+
+ public File getCachePath() {
+ return mCachePath;
+ }
+
+ public Map
* NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
- * {@link android.content.Context#getCacheDir() Context.getCacheDir()} returns null).
+ * {@link Context#getCacheDir() Context.getCacheDir()} returns null).
*/
private static File getCacheDirectory(Context context, boolean preferExternal) {
File appCacheDir = null;
@@ -58,7 +54,6 @@ private static File getCacheDirectory(Context context, boolean preferExternal) {
}
if (appCacheDir == null) {
String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
- LOG.warn("Can't define system cache directory! '" + cacheDirPath + "%s' will be used.");
appCacheDir = new File(cacheDirPath);
}
return appCacheDir;
@@ -69,7 +64,6 @@ private static File getExternalCacheDir(Context context) {
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
if (!appCacheDir.exists()) {
if (!appCacheDir.mkdirs()) {
- LOG.warn("Unable to create external cache directory");
return null;
}
}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYADVideoPlayer.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYADVideoPlayer.java
new file mode 100644
index 000000000..e820e11e3
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYADVideoPlayer.java
@@ -0,0 +1,250 @@
+package com.shuyu.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.shuyu.gsyvideoplayer.GSYVideoADManager;
+import com.shuyu.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.utils.CommonUtil;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge;
+
+
+/**
+ * Created by guoshuyu on 2018/2/1.
+ */
+
+public class GSYADVideoPlayer extends StandardGSYVideoPlayer {
+
+ protected View mJumpAd;
+
+ protected TextView mADTime;
+
+ protected boolean isFirstPrepared;
+
+ public GSYADVideoPlayer(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public GSYADVideoPlayer(Context context) {
+ super(context);
+ }
+
+ public GSYADVideoPlayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ mJumpAd = findViewById(R.id.jump_ad);
+ mADTime = (TextView) findViewById(R.id.ad_time);
+ if (mJumpAd != null) {
+ mJumpAd.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getGSYVideoManager().listener() != null) {
+ getGSYVideoManager().listener().onAutoCompletion();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.video_layout_ad;
+ }
+
+ @Override
+ public GSYVideoViewBridge getGSYVideoManager() {
+ GSYVideoADManager.instance().initContext(getContext().getApplicationContext());
+ return GSYVideoADManager.instance();
+ }
+
+ @Override
+ protected boolean backFromFull(Context context) {
+ return GSYVideoADManager.backFromWindowFull(context);
+ }
+
+ @Override
+ protected void releaseVideos() {
+ GSYVideoADManager.releaseAllVideos();
+ }
+
+ @Override
+ protected int getFullId() {
+ return GSYVideoADManager.FULLSCREEN_ID;
+ }
+
+ @Override
+ protected int getSmallId() {
+ return GSYVideoADManager.SMALL_ID;
+ }
+
+ @Override
+ public void onPrepared() {
+ super.onPrepared();
+ isFirstPrepared = true;
+ changeAdUIState();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.start) {
+ if (mCurrentState == CURRENT_STATE_ERROR) {
+ clickStartIcon();
+ }
+ } else {
+ super.onClick(v);
+ }
+ }
+
+ @Override
+ protected void updateStartImage() {
+ if (mStartButton != null) {
+ if (mStartButton instanceof ImageView) {
+ ImageView imageView = (ImageView) mStartButton;
+ if (mCurrentState == CURRENT_STATE_PLAYING) {
+ imageView.setImageResource(R.drawable.empty_drawable);
+ } else if (mCurrentState == CURRENT_STATE_ERROR) {
+ imageView.setImageResource(R.drawable.video_click_error_selector);
+ } else {
+ imageView.setImageResource(R.drawable.empty_drawable);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 广告期间不需要双击
+ */
+ @Override
+ protected void touchDoubleUp() {
+ }
+
+ /**
+ * 广告期间不需要触摸
+ */
+ @Override
+ protected void touchSurfaceMove(float deltaX, float deltaY, float y) {
+ if (mChangePosition) {
+ } else {
+ super.touchSurfaceMove(deltaX, deltaY, y);
+ }
+ }
+
+ /**
+ * 广告期间不需要触摸
+ */
+ @Override
+ protected void touchSurfaceMoveFullLogic(float absDeltaX, float absDeltaY) {
+ if ((absDeltaX > mThreshold || absDeltaY > mThreshold)) {
+ int screenWidth = CommonUtil.getScreenWidth(getContext());
+ if (absDeltaX >= mThreshold && Math.abs(screenWidth - mDownX) > mSeekEndOffset) {
+ //防止全屏虚拟按键
+ mChangePosition = true;
+ mDownPosition = getCurrentPositionWhenPlaying();
+ } else {
+ super.touchSurfaceMoveFullLogic(absDeltaX, absDeltaY);
+ }
+ }
+ }
+
+ /**
+ * 广告期间不需要触摸
+ */
+ @Override
+ protected void touchSurfaceUp() {
+ if (mChangePosition) {
+ return;
+ }
+ super.touchSurfaceUp();
+
+ }
+
+
+ @Override
+ protected void hideAllWidget() {
+ if (isFirstPrepared) {
+ return;
+ }
+ super.hideAllWidget();
+ }
+
+ @Override
+ protected void setProgressAndTime(int progress, int secProgress, int currentTime, int totalTime, boolean forceChange) {
+ super.setProgressAndTime(progress, secProgress, currentTime, totalTime, forceChange);
+ if (mADTime != null && currentTime > 0) {
+ int totalSeconds = totalTime / 1000;
+ int currentSeconds = currentTime / 1000;
+ mADTime.setText("" + (totalSeconds - currentSeconds));
+ }
+ }
+
+ @Override
+ protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
+ super.cloneParams(from, to);
+ GSYADVideoPlayer sf = (GSYADVideoPlayer) from;
+ GSYADVideoPlayer st = (GSYADVideoPlayer) to;
+ st.isFirstPrepared = sf.isFirstPrepared;
+ st.changeAdUIState();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ if (mADTime != null) {
+ mADTime.setVisibility(GONE);
+ }
+ }
+
+ /**
+ * 根据是否广告url修改ui显示状态
+ */
+ protected void changeAdUIState() {
+ if (mJumpAd != null) {
+ mJumpAd.setVisibility((isFirstPrepared) ? VISIBLE : GONE);
+ }
+ if (mADTime != null) {
+ mADTime.setVisibility((isFirstPrepared) ? VISIBLE : GONE);
+ }
+ if (mBottomContainer != null) {
+ int color = (isFirstPrepared) ? Color.TRANSPARENT : getContext().getResources().getColor(R.color.bottom_container_bg);
+ mBottomContainer.setBackgroundColor(color);
+ }
+ if (mCurrentTimeTextView != null) {
+ mCurrentTimeTextView.setVisibility((isFirstPrepared) ? INVISIBLE : VISIBLE);
+ }
+ if (mTotalTimeTextView != null) {
+ mTotalTimeTextView.setVisibility((isFirstPrepared) ? INVISIBLE : VISIBLE);
+ }
+ if (mProgressBar != null) {
+ mProgressBar.setVisibility((isFirstPrepared) ? INVISIBLE : VISIBLE);
+ mProgressBar.setEnabled(!(isFirstPrepared));
+ }
+ }
+
+ /**
+ * 移除广告播放的全屏
+ */
+ public void removeFullWindowViewOnly() {
+ ViewGroup vp = (ViewGroup) (CommonUtil.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
+ View old = vp.findViewById(getFullId());
+ if (old != null) {
+ if (old.getParent() != null) {
+ ViewGroup viewGroup = (ViewGroup) old.getParent();
+ vp.removeView(viewGroup);
+ }
+ }
+ mIfCurrentIsFullscreen = false;
+ }
+
+}
diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYSampleADVideoPlayer.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYSampleADVideoPlayer.java
new file mode 100644
index 000000000..3f71915bf
--- /dev/null
+++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/video/GSYSampleADVideoPlayer.java
@@ -0,0 +1,375 @@
+package com.shuyu.gsyvideoplayer.video;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.shuyu.gsyvideoplayer.R;
+import com.shuyu.gsyvideoplayer.model.GSYVideoModel;
+import com.shuyu.gsyvideoplayer.utils.CommonUtil;
+import com.shuyu.gsyvideoplayer.video.base.GSYBaseVideoPlayer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 只支持每个片头广告播放的类
+ * 其实就是根据实体,判断播放列表中哪个是广告,哪个不是,从而处理不同的UI显示效果
+ * Created by guoshuyu on 2018/1/26.
+ */
+
+public class GSYSampleADVideoPlayer extends ListGSYVideoPlayer {
+
+ protected View mJumpAd;
+
+ protected ViewGroup mWidgetContainer;
+
+ protected TextView mADTime;
+
+ protected boolean isAdModel = false;
+
+ protected boolean isFirstPrepared = false;
+
+ public GSYSampleADVideoPlayer(Context context, Boolean fullFlag) {
+ super(context, fullFlag);
+ }
+
+ public GSYSampleADVideoPlayer(Context context) {
+ super(context);
+ }
+
+ public GSYSampleADVideoPlayer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+ mJumpAd = findViewById(R.id.jump_ad);
+ mADTime = (TextView) findViewById(R.id.ad_time);
+ mWidgetContainer = (ViewGroup) findViewById(R.id.widget_container);
+ if (mJumpAd != null) {
+ mJumpAd.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ playNext();
+ }
+ });
+ }
+
+ }
+
+ @Override
+ public int getLayoutId() {
+ return R.layout.video_layout_sample_ad;
+ }
+
+ /**
+ * 如果需要片头广告的,请用setAdUp
+ *
+ * @param url 播放url
+ * @param cacheWithPlay 是否边播边缓存
+ * @param position 需要播放的位置
+ * @return
+ */
+ @Override
+ public boolean setUp(List
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public class HttpProxyCacheServer {
+
+ private static final String PROXY_HOST = "127.0.0.1";
+
+ private final Object clientsLock = new Object();
+ private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
+ private final Map
+ * public onCreate(Bundle state) {
+ * super.onCreate(state);
+ *
+ * HttpProxyCacheServer proxy = getProxy();
+ * String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
+ * videoView.setVideoPath(proxyUrl);
+ * }
+ *
+ * private HttpProxyCacheServer getProxy() {
+ * // should return single instance of HttpProxyCacheServer shared for whole app.
+ * }
+ *
+ * NOTE: Can be null in some unpredictable cases (if SD card is unmounted and
+ * {@link Context#getCacheDir() Context.getCacheDir()} returns null).
+ */
+ private static File getCacheDirectory(Context context, boolean preferExternal) {
+ File appCacheDir = null;
+ String externalStorageState;
+ try {
+ externalStorageState = Environment.getExternalStorageState();
+ } catch (NullPointerException e) { // (sh)it happens
+ externalStorageState = "";
+ }
+ if (preferExternal && MEDIA_MOUNTED.equals(externalStorageState)) {
+ appCacheDir = getExternalCacheDir(context);
+ }
+ if (appCacheDir == null) {
+ appCacheDir = context.getCacheDir();
+ }
+ if (appCacheDir == null) {
+ String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
+ HttpProxyCacheDebuger.printfWarning("Can't define system cache directory! '" + cacheDirPath + "%s' will be used.");
+ appCacheDir = new File(cacheDirPath);
+ }
+ return appCacheDir;
+ }
+
+ private static File getExternalCacheDir(Context context) {
+ File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
+ File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
+ if (!appCacheDir.exists()) {
+ if (!appCacheDir.mkdirs()) {
+ HttpProxyCacheDebuger.printfWarning("Unable to create external cache directory");
+ return null;
+ }
+ }
+ return appCacheDir;
+ }
+}
diff --git a/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/DiskUsage.java b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/DiskUsage.java
new file mode 100644
index 000000000..1a436943a
--- /dev/null
+++ b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/DiskUsage.java
@@ -0,0 +1,15 @@
+package com.danikula.videocache.file;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Declares how {@link FileCache} will use disc space.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public interface DiskUsage {
+
+ void touch(File file) throws IOException;
+
+}
diff --git a/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileCache.java b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileCache.java
new file mode 100644
index 000000000..bcb651bdd
--- /dev/null
+++ b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileCache.java
@@ -0,0 +1,127 @@
+package com.danikula.videocache.file;
+
+import com.danikula.videocache.Cache;
+import com.danikula.videocache.ProxyCacheException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * {@link Cache} that uses file for storing data.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public class FileCache implements Cache {
+
+ private static final String TEMP_POSTFIX = ".download";
+
+ private final DiskUsage diskUsage;
+ public File file;
+ private RandomAccessFile dataFile;
+
+ public FileCache(File file) throws ProxyCacheException {
+ this(file, new UnlimitedDiskUsage());
+ }
+
+ public FileCache(File file, DiskUsage diskUsage) throws ProxyCacheException {
+ try {
+ if (diskUsage == null) {
+ throw new NullPointerException();
+ }
+ this.diskUsage = diskUsage;
+ File directory = file.getParentFile();
+ Files.makeDir(directory);
+ boolean completed = file.exists();
+ this.file = completed ? file : new File(file.getParentFile(), file.getName() + TEMP_POSTFIX);
+ this.dataFile = new RandomAccessFile(this.file, completed ? "r" : "rw");
+ } catch (IOException e) {
+ throw new ProxyCacheException("Error using file " + file + " as disc cache", e);
+ }
+ }
+
+ @Override
+ public synchronized long available() throws ProxyCacheException {
+ try {
+ return (int) dataFile.length();
+ } catch (IOException e) {
+ throw new ProxyCacheException("Error reading length of file " + file, e);
+ }
+ }
+
+ @Override
+ public synchronized int read(byte[] buffer, long offset, int length) throws ProxyCacheException {
+ try {
+ dataFile.seek(offset);
+ return dataFile.read(buffer, 0, length);
+ } catch (IOException e) {
+ String format = "Error reading %d bytes with offset %d from file[%d bytes] to buffer[%d bytes]";
+ throw new ProxyCacheException(String.format(format, length, offset, available(), buffer.length), e);
+ }
+ }
+
+ @Override
+ public synchronized void append(byte[] data, int length) throws ProxyCacheException {
+ try {
+ if (isCompleted()) {
+ throw new ProxyCacheException("Error append cache: cache file " + file + " is completed!");
+ }
+ dataFile.seek(available());
+ dataFile.write(data, 0, length);
+ } catch (IOException e) {
+ String format = "Error writing %d bytes to %s from buffer with size %d";
+ throw new ProxyCacheException(String.format(format, length, dataFile, data.length), e);
+ }
+ }
+
+ @Override
+ public synchronized void close() throws ProxyCacheException {
+ try {
+ dataFile.close();
+ diskUsage.touch(file);
+ } catch (IOException e) {
+ throw new ProxyCacheException("Error closing file " + file, e);
+ }
+ }
+
+ @Override
+ public synchronized void complete() throws ProxyCacheException {
+ if (isCompleted()) {
+ return;
+ }
+
+ close();
+ String fileName = file.getName().substring(0, file.getName().length() - TEMP_POSTFIX.length());
+ File completedFile = new File(file.getParentFile(), fileName);
+ boolean renamed = file.renameTo(completedFile);
+ if (!renamed) {
+ throw new ProxyCacheException("Error renaming file " + file + " to " + completedFile + " for completion!");
+ }
+ file = completedFile;
+ try {
+ dataFile = new RandomAccessFile(file, "r");
+ diskUsage.touch(file);
+ } catch (IOException e) {
+ throw new ProxyCacheException("Error opening " + file + " as disc cache", e);
+ }
+ }
+
+ @Override
+ public synchronized boolean isCompleted() {
+ return !isTempFile(file);
+ }
+
+ /**
+ * Returns file to be used fo caching. It may as original file passed in constructor as some temp file for not completed cache.
+ *
+ * @return file for caching.
+ */
+ public File getFile() {
+ return file;
+ }
+
+ private boolean isTempFile(File file) {
+ return file.getName().endsWith(TEMP_POSTFIX);
+ }
+
+}
diff --git a/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileNameGenerator.java b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
new file mode 100644
index 000000000..cd8b3445c
--- /dev/null
+++ b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/FileNameGenerator.java
@@ -0,0 +1,12 @@
+package com.danikula.videocache.file;
+
+/**
+ * Generator for files to be used for caching.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+public interface FileNameGenerator {
+
+ String generate(String url);
+
+}
diff --git a/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/Files.java b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/Files.java
new file mode 100644
index 000000000..0f95c8b2c
--- /dev/null
+++ b/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/Files.java
@@ -0,0 +1,94 @@
+package com.danikula.videocache.file;
+
+import com.danikula.videocache.HttpProxyCacheDebuger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Utils for work with files.
+ *
+ * @author Alexey Danilov (danikula@gmail.com).
+ */
+class Files {
+
+
+ static void makeDir(File directory) throws IOException {
+ if (directory.exists()) {
+ if (!directory.isDirectory()) {
+ throw new IOException("File " + directory + " is not directory!");
+ }
+ } else {
+ boolean isCreated = directory.mkdirs();
+ if (!isCreated) {
+ throw new IOException(String.format("Directory %s can't be created", directory.getAbsolutePath()));
+ }
+ }
+ }
+
+ static List