From d21f30105a2fdb0819029aa20fbe0fff1da88f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=BF=97=E8=B0=A6?= Date: Thu, 15 Nov 2018 14:17:40 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=8E=BB=E9=99=A4CacheManagingDrawTask?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E5=BC=B9=E5=B9=95=E7=BC=93=E5=AD=98=E7=AD=89?= =?UTF-8?q?=E5=BE=8530=E6=AF=AB=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../danmaku/controller/CacheManagingDrawTask.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java index 7d599713..6fb47063 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java @@ -403,16 +403,6 @@ public int accept(BaseDanmaku val) { } //else 回收尺寸过大的cache } - if (!mEndFlag) { - synchronized (mDrawingNotify) { - try { - mDrawingNotify.wait(30); - } catch (InterruptedException e) { - e.printStackTrace(); - return ACTION_BREAK; - } - } - } entryRemoved(false, val, null); return ACTION_REMOVE; } else { From 5b8e705f3af9e6ac1126430fd027c38e12d3c9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=BF=97=E8=B0=A6?= Date: Mon, 26 Nov 2018 18:31:20 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E7=BB=98=E5=88=B6=E5=89=8D=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E7=BC=93=E5=AD=98=E7=8A=B6=E6=80=81,=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E7=BC=93=E5=AD=98=E5=88=99=E5=85=88=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E7=BC=93=E5=AD=98=E5=86=8D=E7=BB=98=E5=88=B6?= =?UTF-8?q?=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CacheManagingDrawTask.java | 118 +++++++++++------- .../danmaku/danmaku/model/ICacheManager.java | 6 + .../renderer/android/DanmakuRenderer.java | 6 + 3 files changed, 85 insertions(+), 45 deletions(-) diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java index 6fb47063..41408734 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java @@ -197,7 +197,7 @@ public class CacheManager implements ICacheManager { public HandlerThread mThread; - Danmakus mCaches = new Danmakus(); + final Danmakus mCaches = new Danmakus(); DrawingCachePoolManager mCachePoolManager = new DrawingCachePoolManager(); @@ -241,6 +241,18 @@ public void addDanmaku(BaseDanmaku danmaku) { } } + @Override + public void buildDanmakuCache(final BaseDanmaku danmaku) { + if (danmaku == null) { + return; + } + CacheHandler handler = mHandler; + if (handler != null) { + //直接构建缓存 + handler.addDanmakuAndBuildCache(danmaku); + } + } + public void invalidateDanmaku(BaseDanmaku danmaku, boolean remeasure) { if (mHandler != null) { mHandler.requestCancelCaching(); @@ -381,12 +393,14 @@ private void clearCachePool() { private boolean push(BaseDanmaku item, int itemSize, boolean forcePush) { int size = itemSize; //sizeOf(item); - if (size > 0) { - clearTimeOutAndFilteredCaches(size, forcePush); - // may be a risk of OOM if (mRealSize + size) is still larger than mMaxSize + synchronized (this.mCaches) { + if (size > 0) { + clearTimeOutAndFilteredCaches(size, forcePush); + // may be a risk of OOM if (mRealSize + size) is still larger than mMaxSize + } + this.mCaches.addItem(item); + mRealSize += size; } - this.mCaches.addItem(item); - mRealSize += size; //Log.i("DFM CACHE", "realsize:"+mRealSize + ",size" + size); return true; } @@ -549,23 +563,25 @@ public void handleMessage(Message msg) { case REBUILD_CACHE: BaseDanmaku cacheitem = (BaseDanmaku) msg.obj; if (cacheitem != null) { - IDrawingCache cache = cacheitem.getDrawingCache(); - boolean requestRemeasure = 0 != (cacheitem.requestFlags & BaseDanmaku.FLAG_REQUEST_REMEASURE); - if (!requestRemeasure && cache != null && cache.get() !=null && !cache.hasReferences()) { - cache = DanmakuUtils.buildDanmakuDrawingCache(cacheitem, mDisp, (DrawingCache) cacheitem.cache, mContext.cachingPolicy.bitsPerPixelOfCache); - cacheitem.cache = cache; - push(cacheitem, 0, true); - return; - } - if (cacheitem.isLive) { - clearCache(cacheitem); - createCache(cacheitem); - } else { - if (cache != null && cache.hasReferences()) { - cache.destroy(); + synchronized (cacheitem) { + IDrawingCache cache = cacheitem.getDrawingCache(); + boolean requestRemeasure = 0 != (cacheitem.requestFlags & BaseDanmaku.FLAG_REQUEST_REMEASURE); + if (!requestRemeasure && cache != null && cache.get() != null && !cache.hasReferences()) { + cache = DanmakuUtils.buildDanmakuDrawingCache(cacheitem, mDisp, (DrawingCache) cacheitem.cache, mContext.cachingPolicy.bitsPerPixelOfCache); + cacheitem.cache = cache; + push(cacheitem, 0, true); + return; + } + if (cacheitem.isLive) { + clearCache(cacheitem); + createCache(cacheitem); + } else { + if (cache != null && cache.hasReferences()) { + cache.destroy(); + } + entryRemoved(true, cacheitem, null); + addDanmakuAndBuildCache(cacheitem); } - entryRemoved(true, cacheitem, null); - addDanmakuAndBuildCache(cacheitem); } } break; @@ -806,7 +822,12 @@ public int accept(BaseDanmaku item) { } // build cache - buildCache(item, false); + synchronized (item) { + cache = item.getDrawingCache(); + if (cache == null || cache.get() == null) { + buildCache(item, false); + } + } if (!repositioned) { long consumingTime = SystemClock.uptimeMillis() - startTime; if (consumingTime >= mContext.mDanmakuFactory.COMMON_DANMAKU_DURATION * mScreenSize) { @@ -828,31 +849,33 @@ public int accept(BaseDanmaku item) { } public boolean createCache(BaseDanmaku item) { - // measure - if (!item.isMeasured()) { - item.measure(mDisp, true); - } - DrawingCache cache = null; - try { - cache = mCachePool.acquire(); - cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); - item.cache = cache; - } catch (OutOfMemoryError e) { -//Log.e("cache", "break at error: oom"); - if (cache != null) { - mCachePool.release(cache); + synchronized (item) { + // measure + if (!item.isMeasured()) { + item.measure(mDisp, true); } - item.cache = null; - return false; - } catch (Exception e) { + DrawingCache cache = null; + try { + cache = mCachePool.acquire(); + cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); + item.cache = cache; + } catch (OutOfMemoryError e) { +//Log.e("cache", "break at error: oom"); + if (cache != null) { + mCachePool.release(cache); + } + item.cache = null; + return false; + } catch (Exception e) { //Log.e("cache", "break at exception:" + e.getMessage()); - if (cache != null) { - mCachePool.release(cache); + if (cache != null) { + mCachePool.release(cache); + } + item.cache = null; + return false; } - item.cache = null; - return false; + return true; } - return true; } private byte buildCache(BaseDanmaku item, boolean forceInsert) { @@ -933,7 +956,12 @@ private final void addDanmakuAndBuildCache(BaseDanmaku danmaku) { } IDrawingCache cache = danmaku.getDrawingCache(); if (cache == null || cache.get() == null) { - buildCache(danmaku, true); + synchronized (danmaku) { + cache = danmaku.getDrawingCache(); + if (cache == null || cache.get() == null) { + buildCache(danmaku, true); + } + } } } diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/ICacheManager.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/ICacheManager.java index 600699ab..caffb857 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/ICacheManager.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/ICacheManager.java @@ -21,4 +21,10 @@ */ public interface ICacheManager { void addDanmaku(BaseDanmaku danmaku); + + /** + * 直接在当前线程构建弹幕缓存 + * @param danmaku 弹幕 + */ + void buildDanmakuCache(BaseDanmaku danmaku); } diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakuRenderer.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakuRenderer.java index 5a5b28ae..5d76ec83 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakuRenderer.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakuRenderer.java @@ -86,6 +86,12 @@ public int accept(BaseDanmaku drawItem) { if (drawItem.lines == null && drawItem.getBottom() > disp.getHeight()) { return ACTION_CONTINUE; // skip bottom outside danmaku } + if (drawItem.getDrawingCache() == null || drawItem.getDrawingCache().get() == null) { + //没有缓存,本线程构建缓存 + if (mCacheManager != null) { + mCacheManager.buildDanmakuCache(drawItem); + } + } int renderingType = drawItem.draw(disp); if (renderingType == IRenderer.CACHE_RENDERING) { renderingState.cacheHitCount++; From 437866bf082065708c44034750dcf9b5278f00f0 Mon Sep 17 00:00:00 2001 From: yangzhiqian Date: Sat, 2 Nov 2019 16:40:02 +0800 Subject: [PATCH 3/4] merge gl_danmaku --- DanmakuFlameMaster/build.gradle | 8 +- .../src/main/assets/gl/glview.frag | 10 + .../src/main/assets/gl/glview.vert | 15 + .../controller/CacheManagingDrawTask.java | 39 +- .../flame/danmaku/controller/DrawHandler.java | 5 +- .../flame/danmaku/controller/DrawTask.java | 1032 ++++++------ .../danmaku/danmaku/model/BaseDanmaku.java | 780 ++++----- .../model/android/AndroidDisplayer.java | 1294 +++++++-------- .../danmaku/model/android/CachingPolicy.java | 16 + .../danmaku/model/android/DanmakuContext.java | 4 +- .../model/android/SimpleTextCacheStuffer.java | 5 +- .../danmaku/parser/BaseDanmakuParser.java | 233 +-- .../danmaku/danmaku/util/DanmakuUtils.java | 361 +++-- .../flame/danmaku/gl/AndroidGLDisplayer.java | 53 + .../master/flame/danmaku/gl/Constants.java | 25 + .../danmaku/gl/DanmakuGLSurfaceView.java | 651 ++++++++ .../master/flame/danmaku/gl/GLDrawTask.java | 653 ++++++++ .../flame/danmaku/gl/glview/GLProgram.java | 60 + .../flame/danmaku/gl/glview/GLUtils.java | 203 +++ .../flame/danmaku/gl/glview/MatrixInfo.java | 14 + .../glview/controller/GLDanmakuHandler.java | 175 ++ .../TextureGLSurfaceViewRenderer.java | 135 ++ .../danmaku/gl/glview/view/GLShader.java | 114 ++ .../gl/glview/view/GLTextureImgProvider.java | 270 ++++ .../flame/danmaku/gl/glview/view/GLView.java | 155 ++ .../danmaku/gl/glview/view/GLViewGroup.java | 415 +++++ .../view/GLViewGroupMatrixProvider.java | 89 ++ .../view/provider/GLDanmakuProvider.java | 129 ++ .../danmaku/gl/utils/SpeedsMeasurement.java | 55 + .../gl/wedget/GLHandlerSurfaceView.java | 1423 +++++++++++++++++ .../flame/danmaku/gl/wedget/GLShareable.java | 116 ++ Sample/src/main/AndroidManifest.xml | 16 +- .../java/com/sample/BiliMainActivity.java | 440 +++++ .../main/java/com/sample/MainActivity.java | 478 +----- .../java/com/sample/gl/DanmakuActivity.java | 276 ++++ Sample/src/main/res/layout/activity_main.xml | 2 +- Sample/src/main/res/layout/layout_danmaku.xml | 38 + .../src/main/res/layout/layout_entrance.xml | 30 + 38 files changed, 7521 insertions(+), 2296 deletions(-) create mode 100644 DanmakuFlameMaster/src/main/assets/gl/glview.frag create mode 100644 DanmakuFlameMaster/src/main/assets/gl/glview.vert create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/AndroidGLDisplayer.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/Constants.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/DanmakuGLSurfaceView.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/GLDrawTask.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLProgram.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLUtils.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/MatrixInfo.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/GLDanmakuHandler.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/TextureGLSurfaceViewRenderer.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLShader.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLTextureImgProvider.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLView.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroup.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroupMatrixProvider.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/provider/GLDanmakuProvider.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/utils/SpeedsMeasurement.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLHandlerSurfaceView.java create mode 100644 DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLShareable.java create mode 100644 Sample/src/main/java/com/sample/BiliMainActivity.java create mode 100644 Sample/src/main/java/com/sample/gl/DanmakuActivity.java create mode 100644 Sample/src/main/res/layout/layout_danmaku.xml create mode 100644 Sample/src/main/res/layout/layout_entrance.xml diff --git a/DanmakuFlameMaster/build.gradle b/DanmakuFlameMaster/build.gradle index feff7eac..2765417e 100644 --- a/DanmakuFlameMaster/build.gradle +++ b/DanmakuFlameMaster/build.gradle @@ -19,8 +19,8 @@ apply plugin: 'com.android.library' def SourcePath = 'src/main/' android { - compileSdkVersion 25 - buildToolsVersion "26.0.2" + compileSdkVersion 27 + buildToolsVersion "27.0.3" defaultConfig { minSdkVersion 9 @@ -42,6 +42,10 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } } + +dependencies { + implementation 'com.android.support:support-annotations:28.0.0' +} if (rootProject.file('gradle/gradle-mvn-push.gradle').exists()) { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } diff --git a/DanmakuFlameMaster/src/main/assets/gl/glview.frag b/DanmakuFlameMaster/src/main/assets/gl/glview.frag new file mode 100644 index 00000000..9f9e3c80 --- /dev/null +++ b/DanmakuFlameMaster/src/main/assets/gl/glview.frag @@ -0,0 +1,10 @@ +precision mediump float; +uniform sampler2D vTexture; +uniform float alpha; +varying vec2 aCoordinate; + +void main() +{ + gl_FragColor = texture2D(vTexture,aCoordinate); + gl_FragColor.a = gl_FragColor.a * alpha; +} \ No newline at end of file diff --git a/DanmakuFlameMaster/src/main/assets/gl/glview.vert b/DanmakuFlameMaster/src/main/assets/gl/glview.vert new file mode 100644 index 00000000..019b898b --- /dev/null +++ b/DanmakuFlameMaster/src/main/assets/gl/glview.vert @@ -0,0 +1,15 @@ +attribute vec4 vPosition; +attribute vec2 vCoordinate; +precision highp float; + +uniform mat4 vProject; +uniform mat4 vView; +uniform mat4 vModel; + +varying vec2 aCoordinate; + +void main() +{ + gl_Position=vProject*vView*vModel*vPosition; + aCoordinate=vCoordinate; +} \ No newline at end of file diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java index 41408734..04b172dd 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java @@ -249,7 +249,7 @@ public void buildDanmakuCache(final BaseDanmaku danmaku) { CacheHandler handler = mHandler; if (handler != null) { //直接构建缓存 - handler.addDanmakuAndBuildCache(danmaku); + handler.addDanmakuAndBuildCache(danmaku, false); } } @@ -257,8 +257,6 @@ public void invalidateDanmaku(BaseDanmaku danmaku, boolean remeasure) { if (mHandler != null) { mHandler.requestCancelCaching(); mHandler.obtainMessage(CacheHandler.REBUILD_CACHE, danmaku).sendToTarget(); - mHandler.sendEmptyMessage(CacheHandler.DISABLE_CANCEL_FLAG); - requestBuild(0); } } @@ -279,7 +277,6 @@ public void end() { mDrawingNotify.notifyAll(); } if (mHandler != null) { - mHandler.removeCallbacksAndMessages(null); mHandler.pause(); mHandler = null; } @@ -558,7 +555,7 @@ public void handleMessage(Message msg) { break; case ADD_DANMAKU: BaseDanmaku item = (BaseDanmaku) msg.obj; - addDanmakuAndBuildCache(item); + addDanmakuAndBuildCache(item, false); break; case REBUILD_CACHE: BaseDanmaku cacheitem = (BaseDanmaku) msg.obj; @@ -580,7 +577,7 @@ public void handleMessage(Message msg) { cache.destroy(); } entryRemoved(true, cacheitem, null); - addDanmakuAndBuildCache(cacheitem); + addDanmakuAndBuildCache(cacheitem, true); } } } @@ -825,7 +822,7 @@ public int accept(BaseDanmaku item) { synchronized (item) { cache = item.getDrawingCache(); if (cache == null || cache.get() == null) { - buildCache(item, false); + buildCache(item, false, false); } } if (!repositioned) { @@ -878,7 +875,7 @@ public boolean createCache(BaseDanmaku item) { } } - private byte buildCache(BaseDanmaku item, boolean forceInsert) { + private byte buildCache(BaseDanmaku item, boolean forceInsert, boolean rebuild) { // measure if (!item.isMeasured()) { @@ -909,6 +906,7 @@ private byte buildCache(BaseDanmaku item, boolean forceInsert) { danmaku.cache = null; //Log.e("cache", danmaku.text + "DrawingCache hit!!:" + item.paintWidth + "," + danmaku.paintWidth); cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); //redraw + delayDanmakuIfNeed(item, rebuild); item.cache = cache; mCacheManager.push(item, 0, forceInsert); return RESULT_SUCCESS; @@ -928,6 +926,7 @@ private byte buildCache(BaseDanmaku item, boolean forceInsert) { cache = mCachePool.acquire(); cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); + delayDanmakuIfNeed(item, rebuild); item.cache = cache; boolean pushed = mCacheManager.push(item, sizeOf(item), forceInsert); if (!pushed) { @@ -947,7 +946,7 @@ private byte buildCache(BaseDanmaku item, boolean forceInsert) { } } - private final void addDanmakuAndBuildCache(BaseDanmaku danmaku) { + private final void addDanmakuAndBuildCache(BaseDanmaku danmaku, boolean rebuild) { if (danmaku.isTimeOut() || (danmaku.getActualTime() > mCacheTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION && !danmaku.isLive)) { return; } @@ -959,12 +958,30 @@ private final void addDanmakuAndBuildCache(BaseDanmaku danmaku) { synchronized (danmaku) { cache = danmaku.getDrawingCache(); if (cache == null || cache.get() == null) { - buildCache(danmaku, true); + buildCache(danmaku, true, rebuild); } } } } + private void delayDanmakuIfNeed(BaseDanmaku item, boolean rebuild) { + if (!rebuild && !item.isOutside() && mContext.cachingPolicy.mAllowDelayInCacheModel) { + //该弹幕已经迟到了,将显示时间延后 + /*该处必须比mTimer.currMillisecond小 + * 原因参考DanmakuUtils#willHitInDuration方法判断两个弹幕碰撞 + * 因为item.isOutside()此时等于false,大概率已经被layout了,并且在某一行的最后面 + * 此时如果直接设置mTimer.currMillisecond,后面item.isOutside()会变成true, + * 碰撞结果变成false,后面很多弹幕被别添加到同一行,导致弹幕堆积在某一行,而下方 + * 留下大量空白 + * */ + item.setTime(mTimer.currMillisecond - 1); + //重新计算位置 + if (item.isShown()) { + item.layout(mContext.getDisplayer(), item.getLeft(), item.getTop()); + } + } + } + public void begin() { sendEmptyMessage(PREPARE); sendEmptyMessageDelayed(CLEAR_TIMEOUT_CACHES, mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); @@ -1009,7 +1026,7 @@ public int accept(BaseDanmaku oldValue) { return IDanmakus.Consumer.ACTION_BREAK; } if (mRealSize + fexpectedFreeSize > mMaxSize) { - if (oldValue.isTimeOut() || oldValue.isFiltered()) { + if (oldValue.isTimeOut() || (oldValue.priority == 0 && oldValue.isFiltered())) { entryRemoved(false, oldValue, null); return IDanmakus.Consumer.ACTION_REMOVE; } else if (forcePush) { diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHandler.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHandler.java index 6ac51b78..5b54591f 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHandler.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHandler.java @@ -38,6 +38,8 @@ import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.SystemClock; +import master.flame.danmaku.gl.AndroidGLDisplayer; +import master.flame.danmaku.gl.GLDrawTask; import tv.cjump.jni.DeviceUtils; public class DrawHandler extends Handler { @@ -625,7 +627,8 @@ private IDrawTask createDrawTask(boolean useDrwaingCache, DanmakuTimer timer, displayMetrics.scaledDensity); mDisp.resetSlopPixel(mContext.scaleTextSize); mDisp.setHardwareAccelerated(isHardwareAccelerated); - IDrawTask task = useDrwaingCache ? + mContext.cachingPolicy.mCacheDrawEnabled = useDrwaingCache || mDisp instanceof AndroidGLDisplayer; + IDrawTask task = mDisp instanceof AndroidGLDisplayer ? new GLDrawTask(timer, mContext, taskListener) : useDrwaingCache ? new CacheManagingDrawTask(timer, mContext, taskListener) : new DrawTask(timer, mContext, taskListener); task.setParser(mParser); diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawTask.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawTask.java index a460c410..117f00e4 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawTask.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawTask.java @@ -1,516 +1,516 @@ -/* - * Copyright (C) 2013 Chen Hui - * - * 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 master.flame.danmaku.controller; - -import android.graphics.Canvas; - -import master.flame.danmaku.danmaku.model.AbsDisplayer; -import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.model.DanmakuTimer; -import master.flame.danmaku.danmaku.model.IDanmakus; -import master.flame.danmaku.danmaku.model.android.DanmakuContext; -import master.flame.danmaku.danmaku.model.android.DanmakuContext.ConfigChangedCallback; -import master.flame.danmaku.danmaku.model.android.DanmakuContext.DanmakuConfigTag; -import master.flame.danmaku.danmaku.model.android.Danmakus; -import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; -import master.flame.danmaku.danmaku.renderer.IRenderer; -import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState; -import master.flame.danmaku.danmaku.renderer.android.DanmakuRenderer; -import master.flame.danmaku.danmaku.util.SystemClock; - -public class DrawTask implements IDrawTask { - - protected final DanmakuContext mContext; - - protected final AbsDisplayer mDisp; - - protected IDanmakus danmakuList; - - protected BaseDanmakuParser mParser; - - TaskListener mTaskListener; - - final IRenderer mRenderer; - - DanmakuTimer mTimer; - - private IDanmakus danmakus = new Danmakus(Danmakus.ST_BY_LIST); - - protected boolean clearRetainerFlag; - - private long mStartRenderTime = 0; - - private final RenderingState mRenderingState = new RenderingState(); - - protected boolean mReadyState; - - private long mLastBeginMills; - - private long mLastEndMills; - - protected int mPlayState; - - private boolean mIsHidden; - - private BaseDanmaku mLastDanmaku; - - private Danmakus mLiveDanmakus = new Danmakus(Danmakus.ST_BY_LIST); - - private IDanmakus mRunningDanmakus; - - private boolean mRequestRender; - - private ConfigChangedCallback mConfigChangedCallback = new ConfigChangedCallback() { - @Override - public boolean onDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, Object... values) { - return DrawTask.this.onDanmakuConfigChanged(config, tag, values); - } - }; - - public DrawTask(DanmakuTimer timer, DanmakuContext context, - TaskListener taskListener) { - if (context == null) { - throw new IllegalArgumentException("context is null"); - } - mContext = context; - mDisp = context.getDisplayer(); - mTaskListener = taskListener; - mRenderer = new DanmakuRenderer(context); - mRenderer.setOnDanmakuShownListener(new IRenderer.OnDanmakuShownListener() { - - @Override - public void onDanmakuShown(BaseDanmaku danmaku) { - if (mTaskListener != null) { - mTaskListener.onDanmakuShown(danmaku); - } - } - }); - mRenderer.setVerifierEnabled(mContext.isPreventOverlappingEnabled() || mContext.isMaxLinesLimited()); - initTimer(timer); - Boolean enable = mContext.isDuplicateMergingEnabled(); - if (enable != null) { - if(enable) { - mContext.mDanmakuFilters.registerFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); - } else { - mContext.mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); - } - } - } - - protected void initTimer(DanmakuTimer timer) { - mTimer = timer; - } - - @Override - public synchronized void addDanmaku(BaseDanmaku item) { - if (danmakuList == null) - return; - if (item.isLive) { - mLiveDanmakus.addItem(item); - removeUnusedLiveDanmakusIn(10); - } - item.index = danmakuList.size(); - boolean subAdded = true; - if (mLastBeginMills <= item.getActualTime() && item.getActualTime() <= mLastEndMills) { - synchronized (danmakus) { - subAdded = danmakus.addItem(item); - } - } else if (item.isLive) { - subAdded = false; - } - boolean added = false; - synchronized (danmakuList) { - added = danmakuList.addItem(item); - } - if (!subAdded || !added) { - mLastBeginMills = mLastEndMills = 0; - } - if (added && mTaskListener != null) { - mTaskListener.onDanmakuAdd(item); - } - if (mLastDanmaku == null || (item != null && mLastDanmaku != null && item.getActualTime() > mLastDanmaku.getActualTime())) { - mLastDanmaku = item; - } - } - - @Override - public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { - mContext.getDisplayer().getCacheStuffer().clearCache(item); - item.requestFlags |= BaseDanmaku.FLAG_REQUEST_INVALIDATE; - if (remeasure) { - item.paintWidth = -1; - item.paintHeight = -1; - item.requestFlags |= BaseDanmaku.FLAG_REQUEST_REMEASURE; - item.measureResetFlag++; - } - } - - @Override - public synchronized void removeAllDanmakus(boolean isClearDanmakusOnScreen) { - if (danmakuList == null || danmakuList.isEmpty()) - return; - synchronized (danmakuList) { - if (!isClearDanmakusOnScreen) { - long beginMills = mTimer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; - long endMills = mTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; - IDanmakus tempDanmakus = danmakuList.subnew(beginMills, endMills); - if (tempDanmakus != null) - danmakus = tempDanmakus; - } - danmakuList.clear(); - } - } - - protected void onDanmakuRemoved(BaseDanmaku danmaku) { - // override by CacheManagingDrawTask - } - - @Override - public synchronized void removeAllLiveDanmakus() { - if (danmakus == null || danmakus.isEmpty()) - return; - synchronized (danmakus) { - danmakus.forEachSync(new IDanmakus.DefaultConsumer() { - @Override - public int accept(BaseDanmaku danmaku) { - if (danmaku.isLive) { - onDanmakuRemoved(danmaku); - return ACTION_REMOVE; - } - return ACTION_CONTINUE; - } - }); - } - } - - protected synchronized void removeUnusedLiveDanmakusIn(final int msec) { - if (danmakuList == null || danmakuList.isEmpty() || mLiveDanmakus.isEmpty()) - return; - mLiveDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { - long startTime = SystemClock.uptimeMillis(); - - @Override - public int accept(BaseDanmaku danmaku) { - boolean isTimeout = danmaku.isTimeOut(); - if (SystemClock.uptimeMillis() - startTime > msec) { - return ACTION_BREAK; - } - if (isTimeout) { - danmakuList.removeItem(danmaku); - onDanmakuRemoved(danmaku); - return ACTION_REMOVE; - } else { - return ACTION_BREAK; - } - - } - }); - } - - @Override - public IDanmakus getVisibleDanmakusOnTime(long time) { - long beginMills = time - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; - long endMills = time + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; - IDanmakus subDanmakus = null; - int i = 0; - while (i++ < 3) { //avoid ConcurrentModificationException - try { - subDanmakus = danmakuList.subnew(beginMills, endMills); - break; - } catch (Exception e) { - - } - } - final IDanmakus visibleDanmakus = new Danmakus(); - if (null != subDanmakus && !subDanmakus.isEmpty()) { - subDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { - @Override - public int accept(BaseDanmaku danmaku) { - if (danmaku.isShown() && !danmaku.isOutside()) { - visibleDanmakus.addItem(danmaku); - } - return ACTION_CONTINUE; - } - }); - } - - return visibleDanmakus; - } - - @Override - public synchronized RenderingState draw(AbsDisplayer displayer) { - return drawDanmakus(displayer,mTimer); - } - - @Override - public void reset() { - if (danmakus != null) - danmakus = new Danmakus(); - if (mRenderer != null) - mRenderer.clear(); - } - - @Override - public void seek(long mills) { - reset(); - mContext.mGlobalFlagValues.updateVisibleFlag(); - mContext.mGlobalFlagValues.updateFirstShownFlag(); - mContext.mGlobalFlagValues.updateSyncOffsetTimeFlag(); - mContext.mGlobalFlagValues.updatePrepareFlag(); - mRunningDanmakus = new Danmakus(Danmakus.ST_BY_LIST); - mStartRenderTime = mills < 1000 ? 0 : mills; - mRenderingState.reset(); - mRenderingState.endTime = mStartRenderTime; - mLastBeginMills = mLastEndMills = 0; - - if (danmakuList != null) { - BaseDanmaku last = danmakuList.last(); - if (last != null && !last.isTimeOut()) { - mLastDanmaku = last; - } - } - } - - @Override - public void clearDanmakusOnScreen(long currMillis) { - reset(); - mContext.mGlobalFlagValues.updateVisibleFlag(); - mContext.mGlobalFlagValues.updateFirstShownFlag(); - mStartRenderTime = currMillis; - } - - @Override - public void start() { - mContext.registerConfigChangedCallback(mConfigChangedCallback); - } - - @Override - public void quit() { - mContext.unregisterAllConfigChangedCallbacks(); - if (mRenderer != null) - mRenderer.release(); - } - - public void prepare() { - if (mParser == null) { - return; - } - loadDanmakus(mParser); - mLastBeginMills = mLastEndMills = 0; - if (mTaskListener != null) { - mTaskListener.ready(); - mReadyState = true; - } - } - - @Override - public void onPlayStateChanged(int state) { - mPlayState = state; - } - - protected void loadDanmakus(BaseDanmakuParser parser) { - danmakuList = parser.setConfig(mContext).setDisplayer(mDisp).setTimer(mTimer).setListener(new BaseDanmakuParser.Listener() { - @Override - public void onDanmakuAdd(BaseDanmaku danmaku) { - if (mTaskListener != null) { - mTaskListener.onDanmakuAdd(danmaku); - } - } - }).getDanmakus(); - mContext.mGlobalFlagValues.resetAll(); - if(danmakuList != null) { - mLastDanmaku = danmakuList.last(); - } - } - - public void setParser(BaseDanmakuParser parser) { - mParser = parser; - mReadyState = false; - } - - protected RenderingState drawDanmakus(AbsDisplayer disp, DanmakuTimer timer) { - if (clearRetainerFlag) { - mRenderer.clearRetainer(); - clearRetainerFlag = false; - } - if (danmakuList != null) { - Canvas canvas = (Canvas) disp.getExtraData(); - DrawHelper.clearCanvas(canvas); - if (mIsHidden && !mRequestRender) { - return mRenderingState; - } - - mRequestRender = false; - RenderingState renderingState = mRenderingState; - // prepare screenDanmakus - long beginMills = timer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; - long endMills = timer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; - IDanmakus screenDanmakus = danmakus; - if(mLastBeginMills > beginMills || timer.currMillisecond > mLastEndMills) { - screenDanmakus = danmakuList.sub(beginMills, endMills); - if (screenDanmakus != null) { - danmakus = screenDanmakus; - } - mLastBeginMills = beginMills; - mLastEndMills = endMills; - } else { - beginMills = mLastBeginMills; - endMills = mLastEndMills; - } - - // prepare runningDanmakus to draw (in sync-mode) - IDanmakus runningDanmakus = mRunningDanmakus; - beginTracing(renderingState, runningDanmakus, screenDanmakus); - if (runningDanmakus != null && !runningDanmakus.isEmpty()) { - mRenderingState.isRunningDanmakus = true; - mRenderer.draw(disp, runningDanmakus, 0, mRenderingState); - } - - // draw screenDanmakus - mRenderingState.isRunningDanmakus = false; - if (screenDanmakus != null && !screenDanmakus.isEmpty()) { - mRenderer.draw(mDisp, screenDanmakus, mStartRenderTime, renderingState); - endTracing(renderingState); - if (renderingState.nothingRendered) { - if(mLastDanmaku != null && mLastDanmaku.isTimeOut()) { - mLastDanmaku = null; - if (mTaskListener != null) { - mTaskListener.onDanmakusDrawingFinished(); - } - } - if (renderingState.beginTime == RenderingState.UNKNOWN_TIME) { - renderingState.beginTime = beginMills; - } - if (renderingState.endTime == RenderingState.UNKNOWN_TIME) { - renderingState.endTime = endMills; - } - } - return renderingState; - } else { - renderingState.nothingRendered = true; - renderingState.beginTime = beginMills; - renderingState.endTime = endMills; - return renderingState; - } - } - return null; - } - - @Override - public void requestClear() { - mLastBeginMills = mLastEndMills = 0; - mIsHidden = false; - } - - @Override - public void requestClearRetainer() { - clearRetainerFlag = true; - } - - @Override - public void requestSync(long fromTimeMills, long toTimeMills, final long offsetMills) { - // obtain the running-danmakus which was drawn on screen - IDanmakus runningDanmakus = mRenderingState.obtainRunningDanmakus(); - mRunningDanmakus = runningDanmakus; - // set offset time for each running-danmakus - runningDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { - @Override - public int accept(BaseDanmaku danmaku) { - if (danmaku.isOutside()) { - return ACTION_REMOVE; - } - danmaku.setTimeOffset(offsetMills + danmaku.timeOffset); - if (danmaku.timeOffset == 0) { - return ACTION_REMOVE; - } - return ACTION_CONTINUE; - } - }); - mStartRenderTime = toTimeMills; - } - - public boolean onDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, - Object... values) { - boolean handled = handleOnDanmakuConfigChanged(config, tag, values); - if (mTaskListener != null) { - mTaskListener.onDanmakuConfigChanged(); - } - return handled; - } - - protected boolean handleOnDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, Object[] values) { - boolean handled = false; - if (tag == null || DanmakuConfigTag.MAXIMUM_NUMS_IN_SCREEN.equals(tag)) { - handled = true; - } else if (DanmakuConfigTag.DUPLICATE_MERGING_ENABLED.equals(tag)) { - Boolean enable = (Boolean) values[0]; - if (enable != null) { - if (enable) { - mContext.mDanmakuFilters.registerFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); - } else { - mContext.mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); - } - handled = true; - } - } else if (DanmakuConfigTag.SCALE_TEXTSIZE.equals(tag) || DanmakuConfigTag.SCROLL_SPEED_FACTOR.equals(tag) || DanmakuConfigTag.DANMAKU_MARGIN.equals(tag)) { - requestClearRetainer(); - handled = false; - } else if (DanmakuConfigTag.MAXIMUN_LINES.equals(tag) || DanmakuConfigTag.OVERLAPPING_ENABLE.equals(tag)) { - if (mRenderer != null) { - mRenderer.setVerifierEnabled(mContext.isPreventOverlappingEnabled() || mContext.isMaxLinesLimited()); - } - handled = true; - } else if (DanmakuConfigTag.ALIGN_BOTTOM.equals(tag)) { - Boolean enable = (Boolean) values[0]; - if (enable != null) { - if (mRenderer != null) { - mRenderer.alignBottom(enable); - } - handled = true; - } - } - return handled; - } - - @Override - public void requestHide() { - mIsHidden = true; - } - - @Override - public void requestRender() { - this.mRequestRender = true; - } - - private void beginTracing(RenderingState renderingState, IDanmakus runningDanmakus, IDanmakus screenDanmakus) { - renderingState.reset(); - renderingState.timer.update(SystemClock.uptimeMillis()); - renderingState.indexInScreen = 0; - renderingState.totalSizeInScreen = (runningDanmakus != null ? runningDanmakus.size() : 0) + (screenDanmakus != null ? screenDanmakus.size() : 0); - } - - private void endTracing(RenderingState renderingState) { - renderingState.nothingRendered = (renderingState.totalDanmakuCount == 0); - if (renderingState.nothingRendered) { - renderingState.beginTime = RenderingState.UNKNOWN_TIME; - } - BaseDanmaku lastDanmaku = renderingState.lastDanmaku; - renderingState.lastDanmaku = null; - renderingState.endTime = lastDanmaku != null ? lastDanmaku.getActualTime() : RenderingState.UNKNOWN_TIME; - renderingState.consumingTime = renderingState.timer.update(SystemClock.uptimeMillis()); - } -} +/* + * Copyright (C) 2013 Chen Hui + * + * 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 master.flame.danmaku.controller; + +import android.graphics.Canvas; + +import master.flame.danmaku.danmaku.model.AbsDisplayer; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.model.android.DanmakuContext.ConfigChangedCallback; +import master.flame.danmaku.danmaku.model.android.DanmakuContext.DanmakuConfigTag; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.renderer.IRenderer; +import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState; +import master.flame.danmaku.danmaku.renderer.android.DanmakuRenderer; +import master.flame.danmaku.danmaku.util.SystemClock; + +public class DrawTask implements IDrawTask { + + protected final DanmakuContext mContext; + + protected final AbsDisplayer mDisp; + + protected IDanmakus danmakuList; + + protected BaseDanmakuParser mParser; + + TaskListener mTaskListener; + + protected final IRenderer mRenderer; + + protected DanmakuTimer mTimer; + + private IDanmakus danmakus = new Danmakus(Danmakus.ST_BY_LIST); + + protected boolean clearRetainerFlag; + + private long mStartRenderTime = 0; + + private final RenderingState mRenderingState = new RenderingState(); + + protected boolean mReadyState; + + private long mLastBeginMills; + + private long mLastEndMills; + + protected int mPlayState; + + private boolean mIsHidden; + + private BaseDanmaku mLastDanmaku; + + private Danmakus mLiveDanmakus = new Danmakus(Danmakus.ST_BY_LIST); + + private IDanmakus mRunningDanmakus; + + private boolean mRequestRender; + + private ConfigChangedCallback mConfigChangedCallback = new ConfigChangedCallback() { + @Override + public boolean onDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, Object... values) { + return DrawTask.this.onDanmakuConfigChanged(config, tag, values); + } + }; + + public DrawTask(DanmakuTimer timer, DanmakuContext context, + TaskListener taskListener) { + if (context == null) { + throw new IllegalArgumentException("context is null"); + } + mContext = context; + mDisp = context.getDisplayer(); + mTaskListener = taskListener; + mRenderer = new DanmakuRenderer(context); + mRenderer.setOnDanmakuShownListener(new IRenderer.OnDanmakuShownListener() { + + @Override + public void onDanmakuShown(BaseDanmaku danmaku) { + if (mTaskListener != null) { + mTaskListener.onDanmakuShown(danmaku); + } + } + }); + mRenderer.setVerifierEnabled(mContext.isPreventOverlappingEnabled() || mContext.isMaxLinesLimited()); + initTimer(timer); + Boolean enable = mContext.isDuplicateMergingEnabled(); + if (enable != null) { + if(enable) { + mContext.mDanmakuFilters.registerFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); + } else { + mContext.mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); + } + } + } + + protected void initTimer(DanmakuTimer timer) { + mTimer = timer; + } + + @Override + public synchronized void addDanmaku(BaseDanmaku item) { + if (danmakuList == null) + return; + if (item.isLive) { + mLiveDanmakus.addItem(item); + removeUnusedLiveDanmakusIn(10); + } + item.index = danmakuList.size(); + boolean subAdded = true; + if (mLastBeginMills <= item.getActualTime() && item.getActualTime() <= mLastEndMills) { + synchronized (danmakus) { + subAdded = danmakus.addItem(item); + } + } else if (item.isLive) { + subAdded = false; + } + boolean added = false; + synchronized (danmakuList) { + added = danmakuList.addItem(item); + } + if (!subAdded || !added) { + mLastBeginMills = mLastEndMills = 0; + } + if (added && mTaskListener != null) { + mTaskListener.onDanmakuAdd(item); + } + if (mLastDanmaku == null || (item != null && mLastDanmaku != null && item.getActualTime() > mLastDanmaku.getActualTime())) { + mLastDanmaku = item; + } + } + + @Override + public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { + mContext.getDisplayer().getCacheStuffer().clearCache(item); + item.requestFlags |= BaseDanmaku.FLAG_REQUEST_INVALIDATE; + if (remeasure) { + item.paintWidth = -1; + item.paintHeight = -1; + item.requestFlags |= BaseDanmaku.FLAG_REQUEST_REMEASURE; + item.measureResetFlag++; + } + } + + @Override + public synchronized void removeAllDanmakus(boolean isClearDanmakusOnScreen) { + if (danmakuList == null || danmakuList.isEmpty()) + return; + synchronized (danmakuList) { + if (!isClearDanmakusOnScreen) { + long beginMills = mTimer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; + long endMills = mTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; + IDanmakus tempDanmakus = danmakuList.subnew(beginMills, endMills); + if (tempDanmakus != null) + danmakus = tempDanmakus; + } + danmakuList.clear(); + } + } + + protected void onDanmakuRemoved(BaseDanmaku danmaku) { + // override by CacheManagingDrawTask + } + + @Override + public synchronized void removeAllLiveDanmakus() { + if (danmakus == null || danmakus.isEmpty()) + return; + synchronized (danmakus) { + danmakus.forEachSync(new IDanmakus.DefaultConsumer() { + @Override + public int accept(BaseDanmaku danmaku) { + if (danmaku.isLive) { + onDanmakuRemoved(danmaku); + return ACTION_REMOVE; + } + return ACTION_CONTINUE; + } + }); + } + } + + protected synchronized void removeUnusedLiveDanmakusIn(final int msec) { + if (danmakuList == null || danmakuList.isEmpty() || mLiveDanmakus.isEmpty()) + return; + mLiveDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { + long startTime = SystemClock.uptimeMillis(); + + @Override + public int accept(BaseDanmaku danmaku) { + boolean isTimeout = danmaku.isTimeOut(); + if (SystemClock.uptimeMillis() - startTime > msec) { + return ACTION_BREAK; + } + if (isTimeout) { + danmakuList.removeItem(danmaku); + onDanmakuRemoved(danmaku); + return ACTION_REMOVE; + } else { + return ACTION_BREAK; + } + + } + }); + } + + @Override + public IDanmakus getVisibleDanmakusOnTime(long time) { + long beginMills = time - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; + long endMills = time + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; + IDanmakus subDanmakus = null; + int i = 0; + while (i++ < 3) { //avoid ConcurrentModificationException + try { + subDanmakus = danmakuList.subnew(beginMills, endMills); + break; + } catch (Exception e) { + + } + } + final IDanmakus visibleDanmakus = new Danmakus(); + if (null != subDanmakus && !subDanmakus.isEmpty()) { + subDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { + @Override + public int accept(BaseDanmaku danmaku) { + if (danmaku.isShown() && !danmaku.isOutside()) { + visibleDanmakus.addItem(danmaku); + } + return ACTION_CONTINUE; + } + }); + } + + return visibleDanmakus; + } + + @Override + public synchronized RenderingState draw(AbsDisplayer displayer) { + return drawDanmakus(displayer,mTimer); + } + + @Override + public void reset() { + if (danmakus != null) + danmakus = new Danmakus(); + if (mRenderer != null) + mRenderer.clear(); + } + + @Override + public void seek(long mills) { + reset(); + mContext.mGlobalFlagValues.updateVisibleFlag(); + mContext.mGlobalFlagValues.updateFirstShownFlag(); + mContext.mGlobalFlagValues.updateSyncOffsetTimeFlag(); + mContext.mGlobalFlagValues.updatePrepareFlag(); + mRunningDanmakus = new Danmakus(Danmakus.ST_BY_LIST); + mStartRenderTime = mills < 1000 ? 0 : mills; + mRenderingState.reset(); + mRenderingState.endTime = mStartRenderTime; + mLastBeginMills = mLastEndMills = 0; + + if (danmakuList != null) { + BaseDanmaku last = danmakuList.last(); + if (last != null && !last.isTimeOut()) { + mLastDanmaku = last; + } + } + } + + @Override + public void clearDanmakusOnScreen(long currMillis) { + reset(); + mContext.mGlobalFlagValues.updateVisibleFlag(); + mContext.mGlobalFlagValues.updateFirstShownFlag(); + mStartRenderTime = currMillis; + } + + @Override + public void start() { + mContext.registerConfigChangedCallback(mConfigChangedCallback); + } + + @Override + public void quit() { + mContext.unregisterAllConfigChangedCallbacks(); + if (mRenderer != null) + mRenderer.release(); + } + + public void prepare() { + if (mParser == null) { + return; + } + loadDanmakus(mParser); + mLastBeginMills = mLastEndMills = 0; + if (mTaskListener != null) { + mTaskListener.ready(); + mReadyState = true; + } + } + + @Override + public void onPlayStateChanged(int state) { + mPlayState = state; + } + + protected void loadDanmakus(BaseDanmakuParser parser) { + danmakuList = parser.setConfig(mContext).setDisplayer(mDisp).setTimer(mTimer).setListener(new BaseDanmakuParser.Listener() { + @Override + public void onDanmakuAdd(BaseDanmaku danmaku) { + if (mTaskListener != null) { + mTaskListener.onDanmakuAdd(danmaku); + } + } + }).getDanmakus(); + mContext.mGlobalFlagValues.resetAll(); + if(danmakuList != null) { + mLastDanmaku = danmakuList.last(); + } + } + + public void setParser(BaseDanmakuParser parser) { + mParser = parser; + mReadyState = false; + } + + protected RenderingState drawDanmakus(AbsDisplayer disp, DanmakuTimer timer) { + if (clearRetainerFlag) { + mRenderer.clearRetainer(); + clearRetainerFlag = false; + } + if (danmakuList != null) { + Canvas canvas = (Canvas) disp.getExtraData(); + DrawHelper.clearCanvas(canvas); + if (mIsHidden && !mRequestRender) { + return mRenderingState; + } + + mRequestRender = false; + RenderingState renderingState = mRenderingState; + // prepare screenDanmakus + long beginMills = timer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION - 100; + long endMills = timer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; + IDanmakus screenDanmakus = danmakus; + if(mLastBeginMills > beginMills || timer.currMillisecond > mLastEndMills) { + screenDanmakus = danmakuList.sub(beginMills, endMills); + if (screenDanmakus != null) { + danmakus = screenDanmakus; + } + mLastBeginMills = beginMills; + mLastEndMills = endMills; + } else { + beginMills = mLastBeginMills; + endMills = mLastEndMills; + } + + // prepare runningDanmakus to draw (in sync-mode) + IDanmakus runningDanmakus = mRunningDanmakus; + beginTracing(renderingState, runningDanmakus, screenDanmakus); + if (runningDanmakus != null && !runningDanmakus.isEmpty()) { + mRenderingState.isRunningDanmakus = true; + mRenderer.draw(disp, runningDanmakus, 0, mRenderingState); + } + + // draw screenDanmakus + mRenderingState.isRunningDanmakus = false; + if (screenDanmakus != null && !screenDanmakus.isEmpty()) { + mRenderer.draw(mDisp, screenDanmakus, mStartRenderTime, renderingState); + endTracing(renderingState); + if (renderingState.nothingRendered) { + if(mLastDanmaku != null && mLastDanmaku.isTimeOut()) { + mLastDanmaku = null; + if (mTaskListener != null) { + mTaskListener.onDanmakusDrawingFinished(); + } + } + if (renderingState.beginTime == RenderingState.UNKNOWN_TIME) { + renderingState.beginTime = beginMills; + } + if (renderingState.endTime == RenderingState.UNKNOWN_TIME) { + renderingState.endTime = endMills; + } + } + return renderingState; + } else { + renderingState.nothingRendered = true; + renderingState.beginTime = beginMills; + renderingState.endTime = endMills; + return renderingState; + } + } + return null; + } + + @Override + public void requestClear() { + mLastBeginMills = mLastEndMills = 0; + mIsHidden = false; + } + + @Override + public void requestClearRetainer() { + clearRetainerFlag = true; + } + + @Override + public void requestSync(long fromTimeMills, long toTimeMills, final long offsetMills) { + // obtain the running-danmakus which was drawn on screen + IDanmakus runningDanmakus = mRenderingState.obtainRunningDanmakus(); + mRunningDanmakus = runningDanmakus; + // set offset time for each running-danmakus + runningDanmakus.forEachSync(new IDanmakus.DefaultConsumer() { + @Override + public int accept(BaseDanmaku danmaku) { + if (danmaku.isOutside()) { + return ACTION_REMOVE; + } + danmaku.setTimeOffset(offsetMills + danmaku.timeOffset); + if (danmaku.timeOffset == 0) { + return ACTION_REMOVE; + } + return ACTION_CONTINUE; + } + }); + mStartRenderTime = toTimeMills; + } + + public boolean onDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, + Object... values) { + boolean handled = handleOnDanmakuConfigChanged(config, tag, values); + if (mTaskListener != null) { + mTaskListener.onDanmakuConfigChanged(); + } + return handled; + } + + protected boolean handleOnDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, Object[] values) { + boolean handled = false; + if (tag == null || DanmakuConfigTag.MAXIMUM_NUMS_IN_SCREEN.equals(tag)) { + handled = true; + } else if (DanmakuConfigTag.DUPLICATE_MERGING_ENABLED.equals(tag)) { + Boolean enable = (Boolean) values[0]; + if (enable != null) { + if (enable) { + mContext.mDanmakuFilters.registerFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); + } else { + mContext.mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_DUPLICATE_FILTER); + } + handled = true; + } + } else if (DanmakuConfigTag.SCALE_TEXTSIZE.equals(tag) || DanmakuConfigTag.SCROLL_SPEED_FACTOR.equals(tag) || DanmakuConfigTag.DANMAKU_MARGIN.equals(tag)) { + requestClearRetainer(); + handled = false; + } else if (DanmakuConfigTag.MAXIMUN_LINES.equals(tag) || DanmakuConfigTag.OVERLAPPING_ENABLE.equals(tag)) { + if (mRenderer != null) { + mRenderer.setVerifierEnabled(mContext.isPreventOverlappingEnabled() || mContext.isMaxLinesLimited()); + } + handled = true; + } else if (DanmakuConfigTag.ALIGN_BOTTOM.equals(tag)) { + Boolean enable = (Boolean) values[0]; + if (enable != null) { + if (mRenderer != null) { + mRenderer.alignBottom(enable); + } + handled = true; + } + } + return handled; + } + + @Override + public void requestHide() { + mIsHidden = true; + } + + @Override + public void requestRender() { + this.mRequestRender = true; + } + + private void beginTracing(RenderingState renderingState, IDanmakus runningDanmakus, IDanmakus screenDanmakus) { + renderingState.reset(); + renderingState.timer.update(SystemClock.uptimeMillis()); + renderingState.indexInScreen = 0; + renderingState.totalSizeInScreen = (runningDanmakus != null ? runningDanmakus.size() : 0) + (screenDanmakus != null ? screenDanmakus.size() : 0); + } + + private void endTracing(RenderingState renderingState) { + renderingState.nothingRendered = (renderingState.totalDanmakuCount == 0); + if (renderingState.nothingRendered) { + renderingState.beginTime = RenderingState.UNKNOWN_TIME; + } + BaseDanmaku lastDanmaku = renderingState.lastDanmaku; + renderingState.lastDanmaku = null; + renderingState.endTime = lastDanmaku != null ? lastDanmaku.getActualTime() : RenderingState.UNKNOWN_TIME; + renderingState.consumingTime = renderingState.timer.update(SystemClock.uptimeMillis()); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/BaseDanmaku.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/BaseDanmaku.java index 932eacfc..383dfca4 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/BaseDanmaku.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/BaseDanmaku.java @@ -1,382 +1,398 @@ -/* - * Copyright (C) 2013 Chen Hui - * - * 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 master.flame.danmaku.danmaku.model; - -import android.util.SparseArray; - -public abstract class BaseDanmaku { - - public final static String DANMAKU_BR_CHAR = "/n"; - - public final static int TYPE_SCROLL_RL = 1; - - public final static int TYPE_SCROLL_LR = 6; - - public final static int TYPE_FIX_TOP = 5; - - public final static int TYPE_FIX_BOTTOM = 4; - - public final static int TYPE_SPECIAL = 7; - - public final static int TYPE_MOVEABLE_XXX = 0; // TODO: add more type - - public final static int INVISIBLE = 0; - - public final static int VISIBLE = 1; - - public final static int FLAG_REQUEST_REMEASURE = 0x1; - public final static int FLAG_REQUEST_INVALIDATE = 0x2; - - /** - * 显示时间(毫秒) - */ - private long time; - - /** - * 偏移时间 - */ - public long timeOffset; - - /** - * 文本 - */ - public CharSequence text; - - /** - * 多行文本: 如果有包含换行符需事先拆分到lines - */ - public String[] lines; - - /** - * 保存一些数据的引用(库内部使用, 外部使用请用tag) - */ - public Object obj; - - /** - * 可保存一些自定义数据的引用(外部使用). - * 除非主动set null,否则不会自动释放引用. - * 确定你会主动set null, 否则不要使用这个字段引用大内存的对象实例. - */ - public Object tag; - - /** - * 文本颜色 - */ - public int textColor; - - /** - * Z轴角度 - */ - public float rotationZ; - - /** - * Y轴角度 - */ - public float rotationY; - - /** - * 阴影/描边颜色 - */ - public int textShadowColor; - - /** - * 下划线颜色,0表示无下划线 - */ - public int underlineColor = 0; - - /** - * 字体大小 - */ - public float textSize = -1; - - /** - * 框的颜色,0表示无框 - */ - public int borderColor = 0; - - /** - * 内边距(像素) - */ - public int padding = 0; - - /** - * 弹幕优先级,0为低优先级,>0为高优先级不会被过滤器过滤 - */ - public byte priority = 0; - - /** - * 占位宽度 - */ - public float paintWidth = -1; - - /** - * 占位高度 - */ - public float paintHeight = -1; - - /** - * 存活时间(毫秒) - */ - public Duration duration; - - /** - * 索引/编号 - */ - public int index; - - /** - * 是否可见 - */ - public int visibility; - - /** - * 重置位 visible - */ - private int visibleResetFlag = 0; - - /** - * 重置位 measure - */ - public int measureResetFlag = 0; - - /** - * 重置位 offset time - */ - public int syncTimeOffsetResetFlag = 0; - - /** - * 重置位 prepare - */ - public int prepareResetFlag = -1; - - /** - * 绘制用缓存 - */ - public IDrawingCache cache; - - /** - * 是否是直播弹幕 - */ - public boolean isLive; - - /** - * 临时, 是否在同线程创建缓存 - */ - public boolean forceBuildCacheInSameThread; - - /** - * 弹幕发布者id, 0表示游客 - */ - public int userId = 0; - - /** - * 弹幕发布者id - */ - public String userHash; - - /** - * 是否游客 - */ - public boolean isGuest; - - /** - * 计时 - */ - protected DanmakuTimer mTimer; - - /** - * 透明度 - */ - protected int alpha = AlphaValue.MAX; - - public int mFilterParam = 0; - - public int filterResetFlag = -1; - - public GlobalFlagValues flags = null; - - public int requestFlags = 0; - - /** - * 标记是否首次显示,首次显示后将置为FIRST_SHOWN_RESET_FLAG - */ - public int firstShownFlag = -1; - - private SparseArray mTags = new SparseArray<>(); - - public long getDuration() { - return duration.value; - } - - public void setDuration(Duration duration) { - this.duration = duration; - } - - public int draw(IDisplayer displayer) { - return displayer.draw(this); - } - - public boolean isMeasured() { - return paintWidth > -1 && paintHeight > -1 - && measureResetFlag == flags.MEASURE_RESET_FLAG; - } - - public void measure(IDisplayer displayer, boolean fromWorkerThread) { - displayer.measure(this, fromWorkerThread); - this.measureResetFlag = flags.MEASURE_RESET_FLAG; - } - - public boolean isPrepared() { - return this.prepareResetFlag == flags.PREPARE_RESET_FLAG; - } - - public void prepare(IDisplayer displayer, boolean fromWorkerThread) { - displayer.prepare(this, fromWorkerThread); - this.prepareResetFlag = flags.PREPARE_RESET_FLAG; - } - - public IDrawingCache getDrawingCache() { - return cache; - } - - public boolean isShown() { - return this.visibility == VISIBLE - && visibleResetFlag == flags.VISIBLE_RESET_FLAG; - } - - public boolean isTimeOut() { - return mTimer == null || isTimeOut(mTimer.currMillisecond); - } - - public boolean isTimeOut(long ctime) { - return ctime - getActualTime() >= duration.value; - } - - public boolean isOutside() { - return mTimer == null || isOutside(mTimer.currMillisecond); - } - - public boolean isOutside(long ctime) { - long dtime = ctime - getActualTime(); - return dtime <= 0 || dtime >= duration.value; - } - - public boolean isLate() { - return mTimer == null || mTimer.currMillisecond < getActualTime(); - } - - public boolean hasPassedFilter() { - if (filterResetFlag != flags.FILTER_RESET_FLAG) { - mFilterParam = 0; - return false; - } - return true; - } - - public boolean isFiltered() { - return filterResetFlag == flags.FILTER_RESET_FLAG && mFilterParam != 0; - } - - public boolean isFilteredBy(int flag) { - return filterResetFlag == flags.FILTER_RESET_FLAG && (mFilterParam & flag) == flag; - } - - public void setVisibility(boolean b) { - if (b) { - this.visibleResetFlag = flags.VISIBLE_RESET_FLAG; - this.visibility = VISIBLE; - } else - this.visibility = INVISIBLE; - } - - public abstract void layout(IDisplayer displayer, float x, float y); - - public abstract float[] getRectAtTime(IDisplayer displayer, long currTime); - - public abstract float getLeft(); - - public abstract float getTop(); - - public abstract float getRight(); - - public abstract float getBottom(); - - /** - * return the type of Danmaku - * - * @return TYPE_SCROLL_RL = 0 TYPE_SCROLL_RL = 1 TYPE_SCROLL_LR = 2 - * TYPE_FIX_TOP = 3; TYPE_FIX_BOTTOM = 4; - */ - public abstract int getType(); - - public DanmakuTimer getTimer() { - return mTimer; - } - - public void setTimer(DanmakuTimer timer) { - mTimer = timer; - } - - public int getAlpha() { - return alpha; - } - - public void setTag(Object tag) { - this.tag = tag; - } - - public void setTag(int key, Object tag) { - this.mTags.put(key, tag); - } - - public Object getTag(int key) { - if (mTags == null) { - return null; - } - return mTags.get(key); - } - - public void setTimeOffset(long timeOffset) { - this.timeOffset = timeOffset; - this.syncTimeOffsetResetFlag = flags.SYNC_TIME_OFFSET_RESET_FLAG; - } - - public void setTime(long time) { - this.time = time; - this.timeOffset = 0; - } - - public long getTime() { - return time; - } - - public long getActualTime() { - if (flags == null || flags.SYNC_TIME_OFFSET_RESET_FLAG != this.syncTimeOffsetResetFlag) { - this.timeOffset = 0; - return time; - } - return time + timeOffset; - } - - public boolean isOffset() { - if (flags == null || flags.SYNC_TIME_OFFSET_RESET_FLAG != this.syncTimeOffsetResetFlag) { - this.timeOffset = 0; - return false; - } - return timeOffset != 0; - } -} +/* + * Copyright (C) 2013 Chen Hui + * + * 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 master.flame.danmaku.danmaku.model; + +import android.util.SparseArray; + +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class BaseDanmaku { + + public final static String DANMAKU_BR_CHAR = "/n"; + + public final static int TYPE_SCROLL_RL = 1; + + public final static int TYPE_SCROLL_LR = 6; + + public final static int TYPE_FIX_TOP = 5; + + public final static int TYPE_FIX_BOTTOM = 4; + + public final static int TYPE_SPECIAL = 7; + + public final static int TYPE_MOVEABLE_XXX = 0; // TODO: add more type + + public final static int INVISIBLE = 0; + + public final static int VISIBLE = 1; + + public final static int FLAG_REQUEST_REMEASURE = 0x1; + public final static int FLAG_REQUEST_INVALIDATE = 0x2; + + private static final AtomicInteger sIdincrement = new AtomicInteger(0); + /** + * 弹幕id,每一个实例都是不一样的 + */ + public final int id = sIdincrement.incrementAndGet(); + + /** + * 显示时间(毫秒) + */ + public long time; + + /** + * 偏移时间 + */ + public long timeOffset; + + /** + * 文本 + */ + public CharSequence text; + + /** + * 多行文本: 如果有包含换行符需事先拆分到lines + */ + public String[] lines; + + /** + * 保存一些数据的引用(库内部使用, 外部使用请用tag) + */ + public Object obj; + + /** + * 可保存一些自定义数据的引用(外部使用). + * 除非主动set null,否则不会自动释放引用. + * 确定你会主动set null, 否则不要使用这个字段引用大内存的对象实例. + */ + public Object tag; + + /** + * 文本颜色 + */ + public int textColor; + + /** + * Z轴角度 + */ + public float rotationZ; + + /** + * Y轴角度 + */ + public float rotationY; + + /** + * 阴影/描边颜色 + */ + public int textShadowColor; + + /** + * 下划线颜色,0表示无下划线 + */ + public int underlineColor = 0; + + /** + * 字体大小 + */ + public float textSize = -1; + + /** + * 框的颜色,0表示无框 + */ + public int borderColor = 0; + + /** + * 内边距(像素) + */ + public int padding = 0; + + /** + * 弹幕优先级,0为低优先级,>0为高优先级不会被过滤器过滤 + */ + public byte priority = 0; + + /** + * 占位宽度 + */ + public float paintWidth = -1; + + /** + * 占位高度 + */ + public float paintHeight = -1; + + /** + * 存活时间(毫秒) + */ + public Duration duration; + + /** + * 索引/编号 + */ + public int index; + + /** + * 是否可见 + */ + public int visibility; + + /** + * 重置位 visible + */ + private int visibleResetFlag = 0; + + /** + * 重置位 measure + */ + public int measureResetFlag = 0; + + /** + * 重置位 offset time + */ + public int syncTimeOffsetResetFlag = 0; + + /** + * 重置位 prepare + */ + public int prepareResetFlag = -1; + + /** + * 绘制用缓存 + */ + public IDrawingCache cache; + + + /** + * gl 绘制时的纹理id,如果没有加载则为0,销毁后也为0,纹理设置为无效也为0 + */ + public int mGLTextureId; + public float mTextureWidth = 0; + public float mTextureHeight = 0; + + /** + * 是否是直播弹幕 + */ + public boolean isLive; + + /** + * 临时, 是否在同线程创建缓存 + */ + public boolean forceBuildCacheInSameThread; + + /** + * 弹幕发布者id, 0表示游客 + */ + public int userId = 0; + + /** + * 弹幕发布者id + */ + public String userHash; + + /** + * 是否游客 + */ + public boolean isGuest; + + /** + * 计时 + */ + protected DanmakuTimer mTimer; + + /** + * 透明度 + */ + protected int alpha = AlphaValue.MAX; + + public int mFilterParam = 0; + + public int filterResetFlag = -1; + + public GlobalFlagValues flags = null; + + public int requestFlags = 0; + + /** + * 标记是否首次显示,首次显示后将置为FIRST_SHOWN_RESET_FLAG + */ + public int firstShownFlag = -1; + + private SparseArray mTags = new SparseArray<>(); + + public long getDuration() { + return duration.value; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public int draw(IDisplayer displayer) { + return displayer.draw(this); + } + + public boolean isMeasured() { + return paintWidth > -1 && paintHeight > -1 + && measureResetFlag == flags.MEASURE_RESET_FLAG; + } + + public void measure(IDisplayer displayer, boolean fromWorkerThread) { + displayer.measure(this, fromWorkerThread); + this.measureResetFlag = flags.MEASURE_RESET_FLAG; + } + + public boolean isPrepared() { + return this.prepareResetFlag == flags.PREPARE_RESET_FLAG; + } + + public void prepare(IDisplayer displayer, boolean fromWorkerThread) { + displayer.prepare(this, fromWorkerThread); + this.prepareResetFlag = flags.PREPARE_RESET_FLAG; + } + + public IDrawingCache getDrawingCache() { + return cache; + } + + public boolean isShown() { + return this.visibility == VISIBLE + && visibleResetFlag == flags.VISIBLE_RESET_FLAG; + } + + public boolean isTimeOut() { + return mTimer == null || isTimeOut(mTimer.currMillisecond); + } + + public boolean isTimeOut(long ctime) { + return ctime - getActualTime() >= duration.value; + } + + public boolean isOutside() { + return mTimer == null || isOutside(mTimer.currMillisecond); + } + + public boolean isOutside(long ctime) { + long dtime = ctime - getActualTime(); + return dtime <= 0 || dtime >= duration.value; + } + + public boolean isLate() { + return mTimer == null || mTimer.currMillisecond < getActualTime(); + } + + public boolean hasPassedFilter() { + if (filterResetFlag != flags.FILTER_RESET_FLAG) { + mFilterParam = 0; + return false; + } + return true; + } + + public boolean isFiltered() { + return filterResetFlag == flags.FILTER_RESET_FLAG && mFilterParam != 0; + } + + public boolean isFilteredBy(int flag) { + return filterResetFlag == flags.FILTER_RESET_FLAG && (mFilterParam & flag) == flag; + } + + public void setVisibility(boolean b) { + if (b) { + this.visibleResetFlag = flags.VISIBLE_RESET_FLAG; + this.visibility = VISIBLE; + } else + this.visibility = INVISIBLE; + } + + public abstract void layout(IDisplayer displayer, float x, float y); + + public abstract float[] getRectAtTime(IDisplayer displayer, long currTime); + + public abstract float getLeft(); + + public abstract float getTop(); + + public abstract float getRight(); + + public abstract float getBottom(); + + /** + * return the type of Danmaku + * + * @return TYPE_SCROLL_RL = 0 TYPE_SCROLL_RL = 1 TYPE_SCROLL_LR = 2 + * TYPE_FIX_TOP = 3; TYPE_FIX_BOTTOM = 4; + */ + public abstract int getType(); + + public DanmakuTimer getTimer() { + return mTimer; + } + + public void setTimer(DanmakuTimer timer) { + mTimer = timer; + } + + public int getAlpha() { + return alpha; + } + + public void setTag(Object tag) { + this.tag = tag; + } + + public void setTag(int key, Object tag) { + this.mTags.put(key, tag); + } + + public Object getTag(int key) { + if (mTags == null) { + return null; + } + return mTags.get(key); + } + + public void setTimeOffset(long timeOffset) { + this.timeOffset = timeOffset; + this.syncTimeOffsetResetFlag = flags.SYNC_TIME_OFFSET_RESET_FLAG; + } + + public void setTime(long time) { + this.time = time; + this.timeOffset = 0; + } + + public long getTime() { + return time; + } + + public long getActualTime() { + if (flags == null || flags.SYNC_TIME_OFFSET_RESET_FLAG != this.syncTimeOffsetResetFlag) { + this.timeOffset = 0; + return time; + } + return time + timeOffset; + } + + public boolean isOffset() { + if (flags == null || flags.SYNC_TIME_OFFSET_RESET_FLAG != this.syncTimeOffsetResetFlag) { + this.timeOffset = 0; + return false; + } + return timeOffset != 0; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/AndroidDisplayer.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/AndroidDisplayer.java index 24d7e714..2cef7070 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/AndroidDisplayer.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/AndroidDisplayer.java @@ -1,646 +1,648 @@ -/* - * Copyright (C) 2013 Chen Hui - * - * 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 master.flame.danmaku.danmaku.model.android; - -import android.annotation.SuppressLint; -import android.graphics.Camera; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.graphics.Typeface; -import android.os.Build; -import android.text.TextPaint; - -import java.util.HashMap; -import java.util.Map; - -import master.flame.danmaku.danmaku.model.AbsDisplayer; -import master.flame.danmaku.danmaku.model.AlphaValue; -import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.renderer.IRenderer; - -public class AndroidDisplayer extends AbsDisplayer { - - public static class DisplayerConfig { - private float sLastScaleTextSize; - private final Map sCachedScaleSize = new HashMap<>(10); - - public final TextPaint PAINT, PAINT_DUPLICATE; - - private Paint ALPHA_PAINT; - - private Paint UNDERLINE_PAINT; - - private Paint BORDER_PAINT; - - /** - * 下划线高度 - */ - public int UNDERLINE_HEIGHT = 4; - - /** - * 边框厚度 - */ - public static final int BORDER_WIDTH = 4; - - /** - * 阴影半径 - */ - private float SHADOW_RADIUS = 4.0f; - - /** - * 描边宽度 - */ - private float STROKE_WIDTH = 3.5f; - - /** - * 投影参数 - */ - public float sProjectionOffsetX = 1.0f; - public float sProjectionOffsetY = 1.0f; - private int sProjectionAlpha = 0xCC; - - /** - * 开启阴影,可动态改变 - */ - public boolean CONFIG_HAS_SHADOW = false; - private boolean HAS_SHADOW = CONFIG_HAS_SHADOW; - - /** - * 开启描边,可动态改变 - */ - public boolean CONFIG_HAS_STROKE = true; - private boolean HAS_STROKE = CONFIG_HAS_STROKE; - - /** - * 开启投影,可动态改变 - */ - public boolean CONFIG_HAS_PROJECTION = false; - public boolean HAS_PROJECTION = CONFIG_HAS_PROJECTION; - - /** - * 开启抗锯齿,可动态改变 - */ - public boolean CONFIG_ANTI_ALIAS = true; - private boolean ANTI_ALIAS = CONFIG_ANTI_ALIAS; - private boolean isTranslucent; - private int transparency = AlphaValue.MAX; - private float scaleTextSize = 1.0f; - private boolean isTextScaled = false; - private int margin = 0; - private int allMarginTop = 0; - - public DisplayerConfig() { - PAINT = new TextPaint(); - PAINT.setStrokeWidth(STROKE_WIDTH); - PAINT_DUPLICATE = new TextPaint(PAINT); - ALPHA_PAINT = new Paint(); - UNDERLINE_PAINT = new Paint(); - UNDERLINE_PAINT.setStrokeWidth(UNDERLINE_HEIGHT); - UNDERLINE_PAINT.setStyle(Style.STROKE); - BORDER_PAINT = new Paint(); - BORDER_PAINT.setStyle(Style.STROKE); - BORDER_PAINT.setStrokeWidth(BORDER_WIDTH); - } - - public void setTypeface(Typeface typeface) { - this.PAINT.setTypeface(typeface); - } - - public void setShadowRadius(float shadowRadius) { - SHADOW_RADIUS = shadowRadius; - } - - public void setStrokeWidth(float s) { - PAINT.setStrokeWidth(s); - STROKE_WIDTH = s; - } - - public void setProjectionConfig(float offsetX, float offsetY, int alpha) { - if (sProjectionOffsetX != offsetX || sProjectionOffsetY != offsetY || sProjectionAlpha != alpha) { - sProjectionOffsetX = (offsetX > 1.0f) ? offsetX : 1.0f; - sProjectionOffsetY = (offsetY > 1.0f) ? offsetY : 1.0f; - sProjectionAlpha = (alpha < 0) ? 0 : ((alpha > 255) ? 255 : alpha); - } - } - - public void setFakeBoldText(boolean fakeBoldText) { - PAINT.setFakeBoldText(fakeBoldText); - } - - public void setTransparency(int newTransparency) { - isTranslucent = (newTransparency != AlphaValue.MAX); - transparency = newTransparency; - } - - public void setScaleTextSizeFactor(float factor) { - isTextScaled = (factor != 1f); - scaleTextSize = factor; - } - - private void applyTextScaleConfig(BaseDanmaku danmaku, Paint paint) { - if (!isTextScaled) { - return; - } - Float size = sCachedScaleSize.get(danmaku.textSize); - if (size == null || sLastScaleTextSize != scaleTextSize) { - sLastScaleTextSize = scaleTextSize; - size = danmaku.textSize * scaleTextSize; - sCachedScaleSize.put(danmaku.textSize, size); - } - paint.setTextSize(size); - } - - public boolean hasStroke(BaseDanmaku danmaku) { - return (HAS_STROKE || HAS_PROJECTION) && STROKE_WIDTH > 0 && danmaku.textShadowColor != 0; - } - - public Paint getBorderPaint(BaseDanmaku danmaku) { - BORDER_PAINT.setColor(danmaku.borderColor); - return BORDER_PAINT; - } - - public Paint getUnderlinePaint(BaseDanmaku danmaku) { - UNDERLINE_PAINT.setColor(danmaku.underlineColor); - return UNDERLINE_PAINT; - } - - public TextPaint getPaint(BaseDanmaku danmaku, boolean fromWorkerThread) { - TextPaint paint; - if (fromWorkerThread) { - paint = PAINT; - } else { - paint = PAINT_DUPLICATE; - paint.set(PAINT); - } - paint.setTextSize(danmaku.textSize); - applyTextScaleConfig(danmaku, paint); - - //ignore the transparent textShadowColor - if (!HAS_SHADOW || SHADOW_RADIUS <= 0 || danmaku.textShadowColor == 0) { - paint.clearShadowLayer(); - } else { - paint.setShadowLayer(SHADOW_RADIUS, 0, 0, danmaku.textShadowColor); - } - paint.setAntiAlias(ANTI_ALIAS); - return paint; - } - - public void applyPaintConfig(BaseDanmaku danmaku, Paint paint, boolean stroke) { - - if (isTranslucent) { - if (stroke) { - paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.FILL_AND_STROKE); - paint.setColor(danmaku.textShadowColor & 0x00FFFFFF); - int alpha = HAS_PROJECTION ? (int) (sProjectionAlpha * ((float) transparency / AlphaValue.MAX)) - : transparency; - paint.setAlpha(alpha); - } else { - paint.setStyle(Style.FILL); - paint.setColor(danmaku.textColor & 0x00FFFFFF); - paint.setAlpha(transparency); - } - } else { - if (stroke) { - paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.FILL_AND_STROKE); - paint.setColor(danmaku.textShadowColor & 0x00FFFFFF); - int alpha = HAS_PROJECTION ? sProjectionAlpha : AlphaValue.MAX; - paint.setAlpha(alpha); - } else { - paint.setStyle(Style.FILL); - paint.setColor(danmaku.textColor & 0x00FFFFFF); - paint.setAlpha(AlphaValue.MAX); - } - } - - if (danmaku.getType() == BaseDanmaku.TYPE_SPECIAL) { - paint.setAlpha(danmaku.getAlpha()); - } - - } - - public void clearTextHeightCache() { - sCachedScaleSize.clear(); - } - - public float getStrokeWidth() { - if (HAS_SHADOW && HAS_STROKE) { - return Math.max(SHADOW_RADIUS, STROKE_WIDTH); - } - if (HAS_SHADOW) { - return SHADOW_RADIUS; - } - if (HAS_STROKE) { - return STROKE_WIDTH; - } - return 0f; - } - - public void definePaintParams(boolean fromWorkerThread) { - HAS_STROKE = CONFIG_HAS_STROKE; - HAS_SHADOW = CONFIG_HAS_SHADOW; - HAS_PROJECTION = CONFIG_HAS_PROJECTION; - ANTI_ALIAS = CONFIG_ANTI_ALIAS; - } - } - - private Camera camera = new Camera(); - - private Matrix matrix = new Matrix(); - - private final DisplayerConfig mDisplayConfig = new DisplayerConfig(); - - private BaseCacheStuffer sStuffer = new SimpleTextCacheStuffer(); - - public AndroidDisplayer() { - - } - - @SuppressLint("NewApi") - private static final int getMaximumBitmapWidth(Canvas c) { - if (Build.VERSION.SDK_INT >= 14) { - return c.getMaximumBitmapWidth(); - } else { - return c.getWidth(); - } - } - - @SuppressLint("NewApi") - private static final int getMaximumBitmapHeight(Canvas c) { - if (Build.VERSION.SDK_INT >= 14) { - return c.getMaximumBitmapHeight(); - } else { - return c.getHeight(); - } - } - - public void setTypeFace(Typeface font) { - mDisplayConfig.setTypeface(font); - } - - public void setShadowRadius(float s) { - mDisplayConfig.setShadowRadius(s); - } - - public void setPaintStorkeWidth(float s) { - mDisplayConfig.setStrokeWidth(s); - } - - public void setProjectionConfig(float offsetX, float offsetY, int alpha) { - mDisplayConfig.setProjectionConfig(offsetX, offsetY, alpha); - } - - public void setFakeBoldText(boolean fakeBoldText) { - mDisplayConfig.setFakeBoldText(fakeBoldText); - } - - @Override - public void setTransparency(int newTransparency) { - mDisplayConfig.setTransparency(newTransparency); - } - - @Override - public void setScaleTextSizeFactor(float factor) { - mDisplayConfig.setScaleTextSizeFactor(factor); - } - - @Override - public void setCacheStuffer(BaseCacheStuffer cacheStuffer) { - if (cacheStuffer != sStuffer) { - sStuffer = cacheStuffer; - } - } - - @Override - public BaseCacheStuffer getCacheStuffer() { - return sStuffer; - } - - @Override - public void setMargin(int m) { - mDisplayConfig.margin = m; - } - - @Override - public int getMargin() { - return mDisplayConfig.margin; - } - - @Override - public void setAllMarginTop(int m) { - mDisplayConfig.allMarginTop = m; - } - - @Override - public int getAllMarginTop() { - return mDisplayConfig.allMarginTop; - } - - public Canvas canvas; - - private int width; - - private int height; - - private float locationZ; - - private float density = 1; - - private int densityDpi = 160; - - private float scaledDensity = 1; - - private int mSlopPixel = 0; - - private boolean mIsHardwareAccelerated = true; - - private int mMaximumBitmapWidth = 2048; - - private int mMaximumBitmapHeight = 2048; - - private void update(Canvas c) { - canvas = c; - if (c != null) { - width = c.getWidth(); - height = c.getHeight(); - if (mIsHardwareAccelerated) { - mMaximumBitmapWidth = getMaximumBitmapWidth(c); - mMaximumBitmapHeight = getMaximumBitmapHeight(c); - } - } - } - - @Override - public int getWidth() { - return width; - } - - @Override - public int getHeight() { - return height; - } - - @Override - public float getDensity() { - return density; - } - - @Override - public int getDensityDpi() { - return densityDpi; - } - - @Override - public int draw(BaseDanmaku danmaku) { - float top = danmaku.getTop(); - float left = danmaku.getLeft(); - if (canvas != null) { - - Paint alphaPaint = null; - boolean needRestore = false; - if (danmaku.getType() == BaseDanmaku.TYPE_SPECIAL) { - if (danmaku.getAlpha() == AlphaValue.TRANSPARENT) { - return IRenderer.NOTHING_RENDERING; - } - if (danmaku.rotationZ != 0 || danmaku.rotationY != 0) { - saveCanvas(danmaku, canvas, left, top); - needRestore = true; - } - - int alpha = danmaku.getAlpha(); - if (alpha != AlphaValue.MAX) { - alphaPaint = mDisplayConfig.ALPHA_PAINT; - alphaPaint.setAlpha(danmaku.getAlpha()); - } - } - - // skip drawing when danmaku is transparent - if (alphaPaint != null && alphaPaint.getAlpha() == AlphaValue.TRANSPARENT) { - return IRenderer.NOTHING_RENDERING; - } - - // drawing cache - boolean cacheDrawn = sStuffer.drawCache(danmaku, canvas, left, top, alphaPaint, mDisplayConfig.PAINT); - int result = IRenderer.CACHE_RENDERING; - if (!cacheDrawn) { - if (alphaPaint != null) { - mDisplayConfig.PAINT.setAlpha(alphaPaint.getAlpha()); - mDisplayConfig.PAINT_DUPLICATE.setAlpha(alphaPaint.getAlpha()); - } else { - resetPaintAlpha(mDisplayConfig.PAINT); - } - drawDanmaku(danmaku, canvas, left, top, false); - result = IRenderer.TEXT_RENDERING; - } - - if (needRestore) { - restoreCanvas(canvas); - } - - return result; - } - - return IRenderer.NOTHING_RENDERING; - } - - @Override - public void recycle(BaseDanmaku danmaku) { - if (sStuffer != null) { - sStuffer.releaseResource(danmaku); - } - } - - private void resetPaintAlpha(Paint paint) { - if (paint.getAlpha() != AlphaValue.MAX) { - paint.setAlpha(AlphaValue.MAX); - } - } - - private void restoreCanvas(Canvas canvas) { - canvas.restore(); - } - - private int saveCanvas(BaseDanmaku danmaku, Canvas canvas, float left, float top) { - camera.save(); - if (locationZ !=0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { - camera.setLocation(0, 0, locationZ); - } - camera.rotateY(-danmaku.rotationY); - camera.rotateZ(-danmaku.rotationZ); - camera.getMatrix(matrix); - matrix.preTranslate(-left, -top); - matrix.postTranslate(left , top); - camera.restore(); - int count = canvas.save(); - canvas.concat(matrix); - return count; - } - - @Override - public synchronized void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, - boolean fromWorkerThread) { - if (sStuffer != null) { - sStuffer.drawDanmaku(danmaku, canvas, left, top, fromWorkerThread, mDisplayConfig); - } - } - - private synchronized TextPaint getPaint(BaseDanmaku danmaku, boolean fromWorkerThread) { - return mDisplayConfig.getPaint(danmaku, fromWorkerThread); - } - - @Override - public void prepare(BaseDanmaku danmaku, boolean fromWorkerThread) { - if (sStuffer != null) { - sStuffer.prepare(danmaku, fromWorkerThread); - } - } - - @Override - public void measure(BaseDanmaku danmaku, boolean fromWorkerThread) { - TextPaint paint = getPaint(danmaku, fromWorkerThread); - if (mDisplayConfig.HAS_STROKE) { - mDisplayConfig.applyPaintConfig(danmaku, paint, true); - } - calcPaintWH(danmaku, paint, fromWorkerThread); - if (mDisplayConfig.HAS_STROKE) { - mDisplayConfig.applyPaintConfig(danmaku, paint, false); - } - } - - private void calcPaintWH(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { - sStuffer.measure(danmaku, paint, fromWorkerThread); - setDanmakuPaintWidthAndHeight(danmaku, danmaku.paintWidth, danmaku.paintHeight); - } - - private void setDanmakuPaintWidthAndHeight(BaseDanmaku danmaku, float w, float h) { - float pw = w + 2 * danmaku.padding; - float ph = h + 2 * danmaku.padding; - if (danmaku.borderColor != 0) { - pw += 2 * mDisplayConfig.BORDER_WIDTH; - ph += 2 * mDisplayConfig.BORDER_WIDTH; - } - danmaku.paintWidth = pw + getStrokeWidth(); - danmaku.paintHeight = ph; - } - - @Override - public void clearTextHeightCache() { - sStuffer.clearCaches(); - mDisplayConfig.clearTextHeightCache(); - } - - @Override - public float getScaledDensity() { - return scaledDensity; - } - - @Override - public void resetSlopPixel(float factor) { - float d = Math.max(factor, getWidth() / DanmakuFactory.BILI_PLAYER_WIDTH); //correct for low density and high resolution - float slop = d * DanmakuFactory.DANMAKU_MEDIUM_TEXTSIZE; - mSlopPixel = (int) slop; - if (factor > 1f) - mSlopPixel = (int) (slop * factor); - } - - @Override - public int getSlopPixel() { - return mSlopPixel; - } - - @Override - public void setDensities(float density, int densityDpi, float scaledDensity) { - this.density = density; - this.densityDpi = densityDpi; - this.scaledDensity = scaledDensity; - } - - @Override - public void setSize(int width, int height) { - this.width = width; - this.height = height; - this.locationZ = (float) (width / 2f / Math.tan((Math.PI / 180) * (55f / 2f))); - } - - @Override - public void setDanmakuStyle(int style, float[] values) { - switch (style) { - case DANMAKU_STYLE_NONE: - mDisplayConfig.CONFIG_HAS_SHADOW = false; - mDisplayConfig.CONFIG_HAS_STROKE = false; - mDisplayConfig.CONFIG_HAS_PROJECTION = false; - break; - case DANMAKU_STYLE_SHADOW: - mDisplayConfig.CONFIG_HAS_SHADOW = true; - mDisplayConfig.CONFIG_HAS_STROKE = false; - mDisplayConfig.CONFIG_HAS_PROJECTION = false; - setShadowRadius(values[0]); - break; - case DANMAKU_STYLE_DEFAULT: - case DANMAKU_STYLE_STROKEN: - mDisplayConfig.CONFIG_HAS_SHADOW = false; - mDisplayConfig.CONFIG_HAS_STROKE = true; - mDisplayConfig.CONFIG_HAS_PROJECTION = false; - setPaintStorkeWidth(values[0]); - break; - case DANMAKU_STYLE_PROJECTION: - mDisplayConfig.CONFIG_HAS_SHADOW = false; - mDisplayConfig.CONFIG_HAS_STROKE = false; - mDisplayConfig.CONFIG_HAS_PROJECTION = true; - setProjectionConfig(values[0], values[1], (int) values[2]); - break; - } - } - - @Override - public void setExtraData(Canvas data) { - update(data); - } - - @Override - public Canvas getExtraData() { - return this.canvas; - } - - @Override - public float getStrokeWidth() { - return mDisplayConfig.getStrokeWidth(); - } - - @Override - public void setHardwareAccelerated(boolean enable) { - mIsHardwareAccelerated = enable; - } - - @Override - public boolean isHardwareAccelerated() { - return mIsHardwareAccelerated; - } - - @Override - public int getMaximumCacheWidth() { - return mMaximumBitmapWidth; - } - - @Override - public int getMaximumCacheHeight() { - return mMaximumBitmapHeight; - } - - -} +/* + * Copyright (C) 2013 Chen Hui + * + * 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 master.flame.danmaku.danmaku.model.android; + +import android.annotation.SuppressLint; +import android.graphics.Camera; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Typeface; +import android.os.Build; +import android.text.TextPaint; + +import java.util.HashMap; +import java.util.Map; + +import master.flame.danmaku.danmaku.model.AbsDisplayer; +import master.flame.danmaku.danmaku.model.AlphaValue; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.renderer.IRenderer; + +public class AndroidDisplayer extends AbsDisplayer { + + public static class DisplayerConfig { + private float sLastScaleTextSize; + private final Map sCachedScaleSize = new HashMap<>(10); + + public final TextPaint PAINT, PAINT_DUPLICATE; + + private Paint ALPHA_PAINT; + + private Paint UNDERLINE_PAINT; + + private Paint BORDER_PAINT; + + /** + * 下划线高度 + */ + public int UNDERLINE_HEIGHT = 4; + + /** + * 边框厚度 + */ + public static final int BORDER_WIDTH = 4; + + /** + * 阴影半径 + */ + private float SHADOW_RADIUS = 4.0f; + + /** + * 描边宽度 + */ + private float STROKE_WIDTH = 3.5f; + + /** + * 投影参数 + */ + public float sProjectionOffsetX = 1.0f; + public float sProjectionOffsetY = 1.0f; + private int sProjectionAlpha = 0xCC; + + /** + * 开启阴影,可动态改变 + */ + public boolean CONFIG_HAS_SHADOW = false; + private boolean HAS_SHADOW = CONFIG_HAS_SHADOW; + + /** + * 开启描边,可动态改变 + */ + public boolean CONFIG_HAS_STROKE = true; + private boolean HAS_STROKE = CONFIG_HAS_STROKE; + + /** + * 开启投影,可动态改变 + */ + public boolean CONFIG_HAS_PROJECTION = false; + public boolean HAS_PROJECTION = CONFIG_HAS_PROJECTION; + + /** + * 开启抗锯齿,可动态改变 + */ + public boolean CONFIG_ANTI_ALIAS = true; + private boolean ANTI_ALIAS = CONFIG_ANTI_ALIAS; + private boolean isTranslucent; + private int transparency = AlphaValue.MAX; + private float scaleTextSize = 1.0f; + private boolean isTextScaled = false; + private int margin = 0; + private int allMarginTop = 0; + + public DisplayerConfig() { + PAINT = new TextPaint(); + PAINT.setStrokeWidth(STROKE_WIDTH); + PAINT_DUPLICATE = new TextPaint(PAINT); + ALPHA_PAINT = new Paint(); + UNDERLINE_PAINT = new Paint(); + UNDERLINE_PAINT.setStrokeWidth(UNDERLINE_HEIGHT); + UNDERLINE_PAINT.setStyle(Style.STROKE); + BORDER_PAINT = new Paint(); + BORDER_PAINT.setStyle(Style.STROKE); + BORDER_PAINT.setStrokeWidth(BORDER_WIDTH); + } + + public void setTypeface(Typeface typeface) { + this.PAINT.setTypeface(typeface); + } + + public void setShadowRadius(float shadowRadius) { + SHADOW_RADIUS = shadowRadius; + } + + public void setStrokeWidth(float s) { + PAINT.setStrokeWidth(s); + STROKE_WIDTH = s; + } + + public void setProjectionConfig(float offsetX, float offsetY, int alpha) { + if (sProjectionOffsetX != offsetX || sProjectionOffsetY != offsetY || sProjectionAlpha != alpha) { + sProjectionOffsetX = (offsetX > 1.0f) ? offsetX : 1.0f; + sProjectionOffsetY = (offsetY > 1.0f) ? offsetY : 1.0f; + sProjectionAlpha = (alpha < 0) ? 0 : ((alpha > 255) ? 255 : alpha); + } + } + + public void setFakeBoldText(boolean fakeBoldText) { + PAINT.setFakeBoldText(fakeBoldText); + } + + public void setTransparency(int newTransparency) { + isTranslucent = (newTransparency != AlphaValue.MAX); + transparency = newTransparency; + } + + public void setScaleTextSizeFactor(float factor) { + isTextScaled = (factor != 1f); + scaleTextSize = factor; + } + + private void applyTextScaleConfig(BaseDanmaku danmaku, Paint paint) { + if (!isTextScaled) { + return; + } + Float size = sCachedScaleSize.get(danmaku.textSize); + if (size == null || sLastScaleTextSize != scaleTextSize) { + sLastScaleTextSize = scaleTextSize; + size = danmaku.textSize * scaleTextSize; + sCachedScaleSize.put(danmaku.textSize, size); + } + paint.setTextSize(size); + } + + public boolean hasStroke(BaseDanmaku danmaku) { + return (HAS_STROKE || HAS_PROJECTION) && STROKE_WIDTH > 0 && danmaku.textShadowColor != 0; + } + + public Paint getBorderPaint(BaseDanmaku danmaku) { + BORDER_PAINT.setColor(danmaku.borderColor); + return BORDER_PAINT; + } + + public Paint getUnderlinePaint(BaseDanmaku danmaku) { + UNDERLINE_PAINT.setColor(danmaku.underlineColor); + return UNDERLINE_PAINT; + } + + public TextPaint getPaint(BaseDanmaku danmaku, boolean fromWorkerThread) { + TextPaint paint; + if (fromWorkerThread) { + paint = PAINT; + } else { + paint = PAINT_DUPLICATE; + paint.set(PAINT); + } + paint.setTextSize(danmaku.textSize); + applyTextScaleConfig(danmaku, paint); + + //ignore the transparent textShadowColor + if (!HAS_SHADOW || SHADOW_RADIUS <= 0 || danmaku.textShadowColor == 0) { + paint.clearShadowLayer(); + } else { + paint.setShadowLayer(SHADOW_RADIUS, 0, 0, danmaku.textShadowColor); + } + paint.setAntiAlias(ANTI_ALIAS); + return paint; + } + + public void applyPaintConfig(BaseDanmaku danmaku, Paint paint, boolean stroke) { + + if (isTranslucent) { + if (stroke) { + paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.FILL_AND_STROKE); + paint.setColor(danmaku.textShadowColor & 0x00FFFFFF); + int alpha = HAS_PROJECTION ? (int) (sProjectionAlpha * ((float) transparency / AlphaValue.MAX)) + : transparency; + paint.setAlpha(alpha); + } else { + paint.setStyle(Style.FILL); + paint.setColor(danmaku.textColor & 0x00FFFFFF); + paint.setAlpha(transparency); + } + } else { + if (stroke) { + paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.FILL_AND_STROKE); + paint.setColor(danmaku.textShadowColor & 0x00FFFFFF); + int alpha = HAS_PROJECTION ? sProjectionAlpha : AlphaValue.MAX; + paint.setAlpha(alpha); + } else { + paint.setStyle(Style.FILL); + paint.setColor(danmaku.textColor & 0x00FFFFFF); + paint.setAlpha(AlphaValue.MAX); + } + } + + if (danmaku.getType() == BaseDanmaku.TYPE_SPECIAL) { + paint.setAlpha(danmaku.getAlpha()); + } + + } + + public void clearTextHeightCache() { + sCachedScaleSize.clear(); + } + + public float getStrokeWidth() { + if (HAS_SHADOW && HAS_STROKE) { + return Math.max(SHADOW_RADIUS, STROKE_WIDTH); + } + if (HAS_SHADOW) { + return SHADOW_RADIUS; + } + if (HAS_STROKE) { + return STROKE_WIDTH; + } + return 0f; + } + + public void definePaintParams(boolean fromWorkerThread) { + HAS_STROKE = CONFIG_HAS_STROKE; + HAS_SHADOW = CONFIG_HAS_SHADOW; + HAS_PROJECTION = CONFIG_HAS_PROJECTION; + ANTI_ALIAS = CONFIG_ANTI_ALIAS; + } + } + + private Camera camera = new Camera(); + + private Matrix matrix = new Matrix(); + + private final DisplayerConfig mDisplayConfig = new DisplayerConfig(); + + private BaseCacheStuffer sStuffer = new SimpleTextCacheStuffer(); + + private DanmakuContext mDanmakuContext; + + public AndroidDisplayer(DanmakuContext context) { + mDanmakuContext = context; + } + + @SuppressLint("NewApi") + private static final int getMaximumBitmapWidth(Canvas c) { + if (Build.VERSION.SDK_INT >= 14) { + return c.getMaximumBitmapWidth(); + } else { + return c.getWidth(); + } + } + + @SuppressLint("NewApi") + private static final int getMaximumBitmapHeight(Canvas c) { + if (Build.VERSION.SDK_INT >= 14) { + return c.getMaximumBitmapHeight(); + } else { + return c.getHeight(); + } + } + + public void setTypeFace(Typeface font) { + mDisplayConfig.setTypeface(font); + } + + public void setShadowRadius(float s) { + mDisplayConfig.setShadowRadius(s); + } + + public void setPaintStorkeWidth(float s) { + mDisplayConfig.setStrokeWidth(s); + } + + public void setProjectionConfig(float offsetX, float offsetY, int alpha) { + mDisplayConfig.setProjectionConfig(offsetX, offsetY, alpha); + } + + public void setFakeBoldText(boolean fakeBoldText) { + mDisplayConfig.setFakeBoldText(fakeBoldText); + } + + @Override + public void setTransparency(int newTransparency) { + mDisplayConfig.setTransparency(newTransparency); + } + + @Override + public void setScaleTextSizeFactor(float factor) { + mDisplayConfig.setScaleTextSizeFactor(factor); + } + + @Override + public void setCacheStuffer(BaseCacheStuffer cacheStuffer) { + if (cacheStuffer != sStuffer) { + sStuffer = cacheStuffer; + } + } + + @Override + public BaseCacheStuffer getCacheStuffer() { + return sStuffer; + } + + @Override + public void setMargin(int m) { + mDisplayConfig.margin = m; + } + + @Override + public int getMargin() { + return mDisplayConfig.margin; + } + + @Override + public void setAllMarginTop(int m) { + mDisplayConfig.allMarginTop = m; + } + + @Override + public int getAllMarginTop() { + return mDisplayConfig.allMarginTop; + } + + public Canvas canvas; + + private int width; + + private int height; + + private float locationZ; + + private float density = 1; + + private int densityDpi = 160; + + private float scaledDensity = 1; + + private int mSlopPixel = 0; + + private boolean mIsHardwareAccelerated = true; + + private int mMaximumBitmapWidth = 2048; + + private int mMaximumBitmapHeight = 2048; + + private void update(Canvas c) { + canvas = c; + if (c != null) { + width = c.getWidth(); + height = c.getHeight(); + if (mIsHardwareAccelerated) { + mMaximumBitmapWidth = getMaximumBitmapWidth(c); + mMaximumBitmapHeight = getMaximumBitmapHeight(c); + } + } + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public float getDensity() { + return density; + } + + @Override + public int getDensityDpi() { + return densityDpi; + } + + @Override + public int draw(BaseDanmaku danmaku) { + float top = danmaku.getTop(); + float left = danmaku.getLeft(); + if (canvas != null) { + + Paint alphaPaint = null; + boolean needRestore = false; + if (danmaku.getType() == BaseDanmaku.TYPE_SPECIAL) { + if (danmaku.getAlpha() == AlphaValue.TRANSPARENT) { + return IRenderer.NOTHING_RENDERING; + } + if (danmaku.rotationZ != 0 || danmaku.rotationY != 0) { + saveCanvas(danmaku, canvas, left, top); + needRestore = true; + } + + int alpha = danmaku.getAlpha(); + if (alpha != AlphaValue.MAX) { + alphaPaint = mDisplayConfig.ALPHA_PAINT; + alphaPaint.setAlpha(danmaku.getAlpha()); + } + } + + // skip drawing when danmaku is transparent + if (alphaPaint != null && alphaPaint.getAlpha() == AlphaValue.TRANSPARENT) { + return IRenderer.NOTHING_RENDERING; + } + + // drawing cache + boolean cacheDrawn = sStuffer.drawCache(danmaku, canvas, left, top, alphaPaint, mDisplayConfig.PAINT); + int result = IRenderer.CACHE_RENDERING; + if (!cacheDrawn && !(mDanmakuContext.cachingPolicy.mCacheDrawEnabled && mDanmakuContext.cachingPolicy.mAllowDelayInCacheModel)) { + if (alphaPaint != null) { + mDisplayConfig.PAINT.setAlpha(alphaPaint.getAlpha()); + mDisplayConfig.PAINT_DUPLICATE.setAlpha(alphaPaint.getAlpha()); + } else { + resetPaintAlpha(mDisplayConfig.PAINT); + } + drawDanmaku(danmaku, canvas, left, top, false); + result = IRenderer.TEXT_RENDERING; + } + + if (needRestore) { + restoreCanvas(canvas); + } + + return result; + } + + return IRenderer.NOTHING_RENDERING; + } + + @Override + public void recycle(BaseDanmaku danmaku) { + if (sStuffer != null) { + sStuffer.releaseResource(danmaku); + } + } + + private void resetPaintAlpha(Paint paint) { + if (paint.getAlpha() != AlphaValue.MAX) { + paint.setAlpha(AlphaValue.MAX); + } + } + + private void restoreCanvas(Canvas canvas) { + canvas.restore(); + } + + private int saveCanvas(BaseDanmaku danmaku, Canvas canvas, float left, float top) { + camera.save(); + if (locationZ !=0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { + camera.setLocation(0, 0, locationZ); + } + camera.rotateY(-danmaku.rotationY); + camera.rotateZ(-danmaku.rotationZ); + camera.getMatrix(matrix); + matrix.preTranslate(-left, -top); + matrix.postTranslate(left , top); + camera.restore(); + int count = canvas.save(); + canvas.concat(matrix); + return count; + } + + @Override + public synchronized void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, + boolean fromWorkerThread) { + if (sStuffer != null) { + sStuffer.drawDanmaku(danmaku, canvas, left, top, fromWorkerThread, mDisplayConfig); + } + } + + private synchronized TextPaint getPaint(BaseDanmaku danmaku, boolean fromWorkerThread) { + return mDisplayConfig.getPaint(danmaku, fromWorkerThread); + } + + @Override + public void prepare(BaseDanmaku danmaku, boolean fromWorkerThread) { + if (sStuffer != null) { + sStuffer.prepare(danmaku, fromWorkerThread); + } + } + + @Override + public void measure(BaseDanmaku danmaku, boolean fromWorkerThread) { + TextPaint paint = getPaint(danmaku, fromWorkerThread); + if (mDisplayConfig.HAS_STROKE) { + mDisplayConfig.applyPaintConfig(danmaku, paint, true); + } + calcPaintWH(danmaku, paint, fromWorkerThread); + if (mDisplayConfig.HAS_STROKE) { + mDisplayConfig.applyPaintConfig(danmaku, paint, false); + } + } + + private void calcPaintWH(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { + sStuffer.measure(danmaku, paint, fromWorkerThread); + setDanmakuPaintWidthAndHeight(danmaku, danmaku.paintWidth, danmaku.paintHeight); + } + + private void setDanmakuPaintWidthAndHeight(BaseDanmaku danmaku, float w, float h) { + float pw = w + 2 * danmaku.padding; + float ph = h + 2 * danmaku.padding; + if (danmaku.borderColor != 0) { + pw += 2 * mDisplayConfig.BORDER_WIDTH; + ph += 2 * mDisplayConfig.BORDER_WIDTH; + } + danmaku.paintWidth = pw + getStrokeWidth(); + danmaku.paintHeight = ph; + } + + @Override + public void clearTextHeightCache() { + sStuffer.clearCaches(); + mDisplayConfig.clearTextHeightCache(); + } + + @Override + public float getScaledDensity() { + return scaledDensity; + } + + @Override + public void resetSlopPixel(float factor) { + float d = Math.max(factor, getWidth() / DanmakuFactory.BILI_PLAYER_WIDTH); //correct for low density and high resolution + float slop = d * DanmakuFactory.DANMAKU_MEDIUM_TEXTSIZE; + mSlopPixel = (int) slop; + if (factor > 1f) + mSlopPixel = (int) (slop * factor); + } + + @Override + public int getSlopPixel() { + return mSlopPixel; + } + + @Override + public void setDensities(float density, int densityDpi, float scaledDensity) { + this.density = density; + this.densityDpi = densityDpi; + this.scaledDensity = scaledDensity; + } + + @Override + public void setSize(int width, int height) { + this.width = width; + this.height = height; + this.locationZ = (float) (width / 2f / Math.tan((Math.PI / 180) * (55f / 2f))); + } + + @Override + public void setDanmakuStyle(int style, float[] values) { + switch (style) { + case DANMAKU_STYLE_NONE: + mDisplayConfig.CONFIG_HAS_SHADOW = false; + mDisplayConfig.CONFIG_HAS_STROKE = false; + mDisplayConfig.CONFIG_HAS_PROJECTION = false; + break; + case DANMAKU_STYLE_SHADOW: + mDisplayConfig.CONFIG_HAS_SHADOW = true; + mDisplayConfig.CONFIG_HAS_STROKE = false; + mDisplayConfig.CONFIG_HAS_PROJECTION = false; + setShadowRadius(values[0]); + break; + case DANMAKU_STYLE_DEFAULT: + case DANMAKU_STYLE_STROKEN: + mDisplayConfig.CONFIG_HAS_SHADOW = false; + mDisplayConfig.CONFIG_HAS_STROKE = true; + mDisplayConfig.CONFIG_HAS_PROJECTION = false; + setPaintStorkeWidth(values[0]); + break; + case DANMAKU_STYLE_PROJECTION: + mDisplayConfig.CONFIG_HAS_SHADOW = false; + mDisplayConfig.CONFIG_HAS_STROKE = false; + mDisplayConfig.CONFIG_HAS_PROJECTION = true; + setProjectionConfig(values[0], values[1], (int) values[2]); + break; + } + } + + @Override + public void setExtraData(Canvas data) { + update(data); + } + + @Override + public Canvas getExtraData() { + return this.canvas; + } + + @Override + public float getStrokeWidth() { + return mDisplayConfig.getStrokeWidth(); + } + + @Override + public void setHardwareAccelerated(boolean enable) { + mIsHardwareAccelerated = enable; + } + + @Override + public boolean isHardwareAccelerated() { + return mIsHardwareAccelerated; + } + + @Override + public int getMaximumCacheWidth() { + return mMaximumBitmapWidth; + } + + @Override + public int getMaximumCacheHeight() { + return mMaximumBitmapHeight; + } + + +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/CachingPolicy.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/CachingPolicy.java index 4ee460be..74fac329 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/CachingPolicy.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/CachingPolicy.java @@ -76,4 +76,20 @@ public CachingPolicy(int bitsPerPixelOfCache, float maxCachePoolSizeFactorPercen public int maxTimesOfReusableFinds = 150; + + /** + * 是否开启了弹幕缓存模式 + */ + public boolean mCacheDrawEnabled = false; + /** + * 是否允许弹幕在使用缓存的状态下延迟显示 + * 对于使用缓存的弹幕,直接绘制弹幕缓存的速度比绘制弹幕速度快得多 + * 在某些极端的情况下(弹幕密度大或则预留创建缓存的时间短),弹幕缓存无法及时提供,所以此标识表明是否允许在没有 + * 准备好弹幕缓存的情况下延迟弹幕显示,直到弹幕缓存创建好。 + * 该变量在绘制{@link AndroidDisplayer}和 + * {@link master.flame.danmaku.controller.CacheManagingDrawTask}中使用到 + * 默认不延迟。 + * 该值只有在{@link #mCacheDrawEnabled}状态为true才有效 + */ + public boolean mAllowDelayInCacheModel = false; } diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java index 809e1abe..390cbf26 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java @@ -96,7 +96,7 @@ public boolean isVisibilityRelatedTag() { private boolean mIsPreventOverlappingEnabled; - public AbsDisplayer mDisplayer = new AndroidDisplayer(); + public AbsDisplayer mDisplayer = new AndroidDisplayer(this); public GlobalFlagValues mGlobalFlagValues = new GlobalFlagValues(); @@ -701,7 +701,7 @@ public DanmakuContext unregisterFilter(DanmakuFilters.BaseDanmakuFilter filter) } public DanmakuContext resetContext() { - mDisplayer = new AndroidDisplayer(); + mDisplayer = new AndroidDisplayer(this); mGlobalFlagValues = new GlobalFlagValues(); // mDanmakuFilters = new DanmakuFilters(); mDanmakuFilters.clear(); diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SimpleTextCacheStuffer.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SimpleTextCacheStuffer.java index 55e12fa3..303651f5 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SimpleTextCacheStuffer.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SimpleTextCacheStuffer.java @@ -8,7 +8,7 @@ import java.util.Map; import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.model.SpecialDanmaku; +import master.flame.danmaku.danmaku.model.IDrawingCache; /** * Created by ch on 15-7-16. @@ -63,9 +63,6 @@ protected void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, f } protected void drawText(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, TextPaint paint, boolean fromWorkerThread) { - if (fromWorkerThread && danmaku instanceof SpecialDanmaku) { - paint.setAlpha(255); - } if (lineText != null) { canvas.drawText(lineText, left, top, paint); } else { diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java index 47c0aaac..00da7cec 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java @@ -1,116 +1,117 @@ -/* - * Copyright (C) 2013 Chen Hui - * - * 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 master.flame.danmaku.danmaku.parser; - -import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.model.DanmakuTimer; -import master.flame.danmaku.danmaku.model.IDanmakus; -import master.flame.danmaku.danmaku.model.IDisplayer; -import master.flame.danmaku.danmaku.model.android.DanmakuContext; - -/** - * - */ -public abstract class BaseDanmakuParser { - - public interface Listener { - void onDanmakuAdd(BaseDanmaku danmaku); - } - - protected IDataSource mDataSource; - - protected DanmakuTimer mTimer; - protected int mDispWidth; - protected int mDispHeight; - protected float mDispDensity; - protected float mScaledDensity; - - private IDanmakus mDanmakus; - - protected IDisplayer mDisp; - protected DanmakuContext mContext; - protected Listener mListener; - - public BaseDanmakuParser setDisplayer(IDisplayer disp){ - mDisp = disp; - mDispWidth = disp.getWidth(); - mDispHeight = disp.getHeight(); - mDispDensity = disp.getDensity(); - mScaledDensity = disp.getScaledDensity(); - mContext.mDanmakuFactory.updateViewportState(mDispWidth, mDispHeight, getViewportSizeFactor()); - mContext.mDanmakuFactory.updateMaxDanmakuDuration(); - return this; - } - - public IDisplayer getDisplayer(){ - return mDisp; - } - - public BaseDanmakuParser setListener(Listener listener) { - mListener = listener; - return this; - } - - /** - * decide the speed of scroll-danmakus - * @return - */ - protected float getViewportSizeFactor() { - return 1 / (mDispDensity - 0.6f); - } - - public BaseDanmakuParser load(IDataSource source) { - mDataSource = source; - return this; - } - - public BaseDanmakuParser setTimer(DanmakuTimer timer) { - mTimer = timer; - return this; - } - - public DanmakuTimer getTimer() { - return mTimer; - } - - public IDanmakus getDanmakus() { - if (mDanmakus != null) - return mDanmakus; - mContext.mDanmakuFactory.resetDurationsData(); - mDanmakus = parse(); - releaseDataSource(); - mContext.mDanmakuFactory.updateMaxDanmakuDuration(); - return mDanmakus; - } - - protected void releaseDataSource() { - if(mDataSource!=null) - mDataSource.release(); - mDataSource = null; - } - - protected abstract IDanmakus parse(); - - public void release() { - releaseDataSource(); - } - - public BaseDanmakuParser setConfig(DanmakuContext config) { - mContext = config; - return this; - } -} +/* + * Copyright (C) 2013 Chen Hui + * + * 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 master.flame.danmaku.danmaku.parser; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; + +/** + * + */ +public abstract class BaseDanmakuParser { + + public interface Listener { + void onDanmakuAdd(BaseDanmaku danmaku); + } + + protected IDataSource mDataSource; + + protected DanmakuTimer mTimer; + protected int mDispWidth; + protected int mDispHeight; + protected float mDispDensity; + protected float mScaledDensity; + + private IDanmakus mDanmakus; + + protected IDisplayer mDisp; + protected DanmakuContext mContext; + protected Listener mListener; + + public BaseDanmakuParser setDisplayer(IDisplayer disp){ + mDisp = disp; + mDispWidth = disp.getWidth(); + mDispHeight = disp.getHeight(); + mDispDensity = disp.getDensity(); + mScaledDensity = disp.getScaledDensity(); + mContext.mDanmakuFactory.updateViewportState(mDispWidth, mDispHeight, getViewportSizeFactor()); + mContext.mDanmakuFactory.updateMaxDanmakuDuration(); + return this; + } + + public IDisplayer getDisplayer(){ + return mDisp; + } + + public BaseDanmakuParser setListener(Listener listener) { + mListener = listener; + return this; + } + + /** + * decide the speed of scroll-danmakus + * @return + */ + protected float getViewportSizeFactor() { +// return 1 / (mDispDensity - 0.6f); + return 2.0f; + } + + public BaseDanmakuParser load(IDataSource source) { + mDataSource = source; + return this; + } + + public BaseDanmakuParser setTimer(DanmakuTimer timer) { + mTimer = timer; + return this; + } + + public DanmakuTimer getTimer() { + return mTimer; + } + + public IDanmakus getDanmakus() { + if (mDanmakus != null) + return mDanmakus; + mContext.mDanmakuFactory.resetDurationsData(); + mDanmakus = parse(); + releaseDataSource(); + mContext.mDanmakuFactory.updateMaxDanmakuDuration(); + return mDanmakus; + } + + protected void releaseDataSource() { + if(mDataSource!=null) + mDataSource.release(); + mDataSource = null; + } + + protected abstract IDanmakus parse(); + + public void release() { + releaseDataSource(); + } + + public BaseDanmakuParser setConfig(DanmakuContext config) { + mContext = config; + return this; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/DanmakuUtils.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/DanmakuUtils.java index e534f36e..134e4533 100644 --- a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/DanmakuUtils.java +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/DanmakuUtils.java @@ -1,171 +1,190 @@ -/* - * Copyright (C) 2013 Chen Hui - * - * 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 master.flame.danmaku.danmaku.util; - -import android.text.TextUtils; - -import master.flame.danmaku.danmaku.model.AbsDisplayer; -import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.model.IDisplayer; -import master.flame.danmaku.danmaku.model.android.DrawingCache; -import master.flame.danmaku.danmaku.model.android.DrawingCacheHolder; - -public class DanmakuUtils { - - /** - * 检测两个弹幕是否会碰撞 - * 允许不同类型弹幕的碰撞 - * @param d1 - * @param d2 - * @return - */ - public static boolean willHitInDuration(IDisplayer disp, BaseDanmaku d1, BaseDanmaku d2, - long duration, long currTime) { - final int type1 = d1.getType(); - final int type2 = d2.getType(); - // allow hit if different type - if(type1 != type2) - return false; - - if(d1.isOutside()){ - return false; - } - long dTime = d2.getActualTime() - d1.getActualTime(); - if (dTime <= 0) - return true; - if (Math.abs(dTime) >= duration || d1.isTimeOut() || d2.isTimeOut()) { - return false; - } - - if (type1 == BaseDanmaku.TYPE_FIX_TOP || type1 == BaseDanmaku.TYPE_FIX_BOTTOM) { - return true; - } - - return checkHitAtTime(disp, d1, d2, currTime) - || checkHitAtTime(disp, d1, d2, d1.getActualTime() + d1.getDuration()); - } - - private static boolean checkHitAtTime(IDisplayer disp, BaseDanmaku d1, BaseDanmaku d2, long time){ - final float[] rectArr1 = d1.getRectAtTime(disp, time); - final float[] rectArr2 = d2.getRectAtTime(disp, time); - if (rectArr1 == null || rectArr2 == null) - return false; - return checkHit(d1.getType(), d2.getType(), rectArr1, rectArr2); - } - - private static boolean checkHit(int type1, int type2, float[] rectArr1, - float[] rectArr2) { - if(type1 != type2) - return false; - if (type1 == BaseDanmaku.TYPE_SCROLL_RL) { - // hit if left2 < right1 - return rectArr2[0] < rectArr1[2]; - } - - if (type1 == BaseDanmaku.TYPE_SCROLL_LR){ - // hit if right2 > left1 - return rectArr2[2] > rectArr1[0]; - } - - return false; - } - - public static DrawingCache buildDanmakuDrawingCache(BaseDanmaku danmaku, IDisplayer disp, - DrawingCache cache, int bitsPerPixel) { - if (cache == null) - cache = new DrawingCache(); - - cache.build((int) Math.ceil(danmaku.paintWidth), (int) Math.ceil(danmaku.paintHeight), disp.getDensityDpi(), false, bitsPerPixel); - DrawingCacheHolder holder = cache.get(); - if (holder != null) { - ((AbsDisplayer) disp).drawDanmaku(danmaku, holder.canvas, 0, 0, true); - if(disp.isHardwareAccelerated()) { - holder.splitWith(disp.getWidth(), disp.getHeight(), disp.getMaximumCacheWidth(), - disp.getMaximumCacheHeight()); - } - } - return cache; - } - - public static int getCacheSize(int w, int h, int bytesPerPixel) { - return (w) * (h) * bytesPerPixel; - } - - public final static boolean isDuplicate(BaseDanmaku obj1, BaseDanmaku obj2) { - if(obj1 == obj2) { - return false; - } -// if(obj1.isTimeOut() || obj2.isTimeOut()) { -// return false; -// } -// long dtime = Math.abs(obj1.time - obj2.time); -// if(dtime > obj1.getDuration()) { -// return false; -// } - if (obj1.text == obj2.text) { - return true; - } - if (obj1.text != null && obj1.text.equals(obj2.text)) { - return true; - } - return false; - } - - public final static int compare(BaseDanmaku obj1, BaseDanmaku obj2) { - - if (obj1 == obj2) { - return 0; - } - - if (obj1 == null) { - return -1; - } - - if (obj2 == null) { - return 1; - } - - long val = obj1.getTime() - obj2.getTime(); - if (val > 0) { - return 1; - } else if (val < 0) { - return -1; - } - int r = obj1.index - obj2.index; - if (r != 0) - return r < 0 ? -1 : 1; - - r = obj1.hashCode() - obj1.hashCode(); - return r; - } - - public final static boolean isOverSize(IDisplayer disp, BaseDanmaku item) { - return disp.isHardwareAccelerated() && (item.paintWidth > disp.getMaximumCacheWidth() || item.paintHeight > disp.getMaximumCacheHeight()); - } - - public static void fillText(BaseDanmaku danmaku, CharSequence text) { - danmaku.text = text; - if (TextUtils.isEmpty(text) || !text.toString().contains(BaseDanmaku.DANMAKU_BR_CHAR)) { - return; - } - - String[] lines = String.valueOf(danmaku.text).split(BaseDanmaku.DANMAKU_BR_CHAR, -1); - if (lines.length > 1) { - danmaku.lines = lines; - } - } -} +/* + * Copyright (C) 2013 Chen Hui + * + * 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 master.flame.danmaku.danmaku.util; + +import android.text.TextUtils; + +import master.flame.danmaku.danmaku.model.AbsDisplayer; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.IDrawingCache; +import master.flame.danmaku.danmaku.model.android.DrawingCache; +import master.flame.danmaku.danmaku.model.android.DrawingCacheHolder; + +public class DanmakuUtils { + + /** + * 检测两个弹幕是否会碰撞 + * 允许不同类型弹幕的碰撞 + * @param d1 + * @param d2 + * @return + */ + public static boolean willHitInDuration(IDisplayer disp, BaseDanmaku d1, BaseDanmaku d2, + long duration, long currTime) { + final int type1 = d1.getType(); + final int type2 = d2.getType(); + // allow hit if different type + if(type1 != type2) + return false; + + if(d1.isOutside()){ + return false; + } + long dTime = d2.getActualTime() - d1.getActualTime(); + if (dTime <= 0) + return true; + if (Math.abs(dTime) >= duration || d1.isTimeOut() || d2.isTimeOut()) { + return false; + } + + if (type1 == BaseDanmaku.TYPE_FIX_TOP || type1 == BaseDanmaku.TYPE_FIX_BOTTOM) { + return true; + } + + return checkHitAtTime(disp, d1, d2, currTime) + || checkHitAtTime(disp, d1, d2, d1.getActualTime() + d1.getDuration()); + } + + private static boolean checkHitAtTime(IDisplayer disp, BaseDanmaku d1, BaseDanmaku d2, long time){ + final float[] rectArr1 = d1.getRectAtTime(disp, time); + final float[] rectArr2 = d2.getRectAtTime(disp, time); + if (rectArr1 == null || rectArr2 == null) + return false; + return checkHit(d1.getType(), d2.getType(), rectArr1, rectArr2); + } + + private static boolean checkHit(int type1, int type2, float[] rectArr1, + float[] rectArr2) { + if(type1 != type2) + return false; + if (type1 == BaseDanmaku.TYPE_SCROLL_RL) { + // hit if left2 < right1 + return rectArr2[0] < rectArr1[2]; + } + + if (type1 == BaseDanmaku.TYPE_SCROLL_LR){ + // hit if right2 > left1 + return rectArr2[2] > rectArr1[0]; + } + + return false; + } + + public static DrawingCache buildDanmakuDrawingCache(BaseDanmaku danmaku, IDisplayer disp, + DrawingCache cache, int bitsPerPixel) { + if (cache == null) + cache = new DrawingCache(); + + cache.build((int) Math.ceil(danmaku.paintWidth), (int) Math.ceil(danmaku.paintHeight), disp.getDensityDpi(), false, bitsPerPixel); + DrawingCacheHolder holder = cache.get(); + if (holder != null) { + ((AbsDisplayer) disp).drawDanmaku(danmaku, holder.canvas, 0, 0, true); + if(disp.isHardwareAccelerated()) { + holder.splitWith(disp.getWidth(), disp.getHeight(), disp.getMaximumCacheWidth(), + disp.getMaximumCacheHeight()); + } + } + return cache; + } + + public static boolean isCacheOk(BaseDanmaku danmaku) { + if (danmaku == null) { + return false; + } + IDrawingCache cache = danmaku.cache; + if (cache == null) { + return false; + } + DrawingCacheHolder holder = (DrawingCacheHolder) cache.get(); + if (holder == null) { + return false; + } + if (holder.bitmap == null || holder.bitmap.isRecycled()) { + return false; + } + return true; + } + + public static int getCacheSize(int w, int h, int bytesPerPixel) { + return (w) * (h) * bytesPerPixel; + } + + public final static boolean isDuplicate(BaseDanmaku obj1, BaseDanmaku obj2) { + if(obj1 == obj2) { + return false; + } +// if(obj1.isTimeOut() || obj2.isTimeOut()) { +// return false; +// } +// long dtime = Math.abs(obj1.time - obj2.time); +// if(dtime > obj1.getDuration()) { +// return false; +// } + if (obj1.text == obj2.text) { + return true; + } + if (obj1.text != null && obj1.text.equals(obj2.text)) { + return true; + } + return false; + } + + public final static int compare(BaseDanmaku obj1, BaseDanmaku obj2) { + + if (obj1 == obj2) { + return 0; + } + + if (obj1 == null) { + return -1; + } + + if (obj2 == null) { + return 1; + } + + long val = obj1.getTime() - obj2.getTime(); + if (val > 0) { + return 1; + } else if (val < 0) { + return -1; + } + int r = obj1.index - obj2.index; + if (r != 0) + return r < 0 ? -1 : 1; + + r = obj1.hashCode() - obj1.hashCode(); + return r; + } + + public final static boolean isOverSize(IDisplayer disp, BaseDanmaku item) { + return disp.isHardwareAccelerated() && (item.paintWidth > disp.getMaximumCacheWidth() || item.paintHeight > disp.getMaximumCacheHeight()); + } + + public static void fillText(BaseDanmaku danmaku, CharSequence text) { + danmaku.text = text; + if (TextUtils.isEmpty(text) || !text.toString().contains(BaseDanmaku.DANMAKU_BR_CHAR)) { + return; + } + + String[] lines = String.valueOf(danmaku.text).split(BaseDanmaku.DANMAKU_BR_CHAR, -1); + if (lines.length > 1) { + danmaku.lines = lines; + } + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/AndroidGLDisplayer.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/AndroidGLDisplayer.java new file mode 100644 index 00000000..da09d794 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/AndroidGLDisplayer.java @@ -0,0 +1,53 @@ +package master.flame.danmaku.gl; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.android.AndroidDisplayer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.renderer.IRenderer; +import master.flame.danmaku.danmaku.util.DanmakuUtils; +import master.flame.danmaku.gl.glview.controller.TextureGLSurfaceViewRenderer; + +/** + * 创建人:yangzhiqian + * 创建时间:2018/7/11 15:12 + * 备注: + */ +public class AndroidGLDisplayer extends AndroidDisplayer { + + private TextureGLSurfaceViewRenderer mRenderer; + + public AndroidGLDisplayer(DanmakuContext context) { + super(context); + } + + @Override + public int draw(BaseDanmaku danmaku) { + if (danmaku == null || danmaku.isTimeOut()) { + return IRenderer.NOTHING_RENDERING; + } + if (danmaku.mGLTextureId != 0) { + //有纹理id表示一定添加过 + return IRenderer.CACHE_RENDERING; + } + //没有cache,有两种情况: + // bitmpa还没准备好 + // 已经有bitmap但没有加载到纹理中 + if (!DanmakuUtils.isCacheOk(danmaku)) { + //其实没有绘制,但返回TEXT_RENDERING会通知GLDrawTask构建bitmap + //暂时不添加到gl,等待bitmap创建成功后再添加 + return IRenderer.TEXT_RENDERING; + } else { + //添加到gl +// mRenderer.getGLDanmakuHandler().addDanmaku(danmaku); + return IRenderer.CACHE_RENDERING; + } + } + + public void setRenderer(TextureGLSurfaceViewRenderer mRenderer) { + this.mRenderer = mRenderer; + } + + public TextureGLSurfaceViewRenderer getRenderer() { + return mRenderer; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/Constants.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/Constants.java new file mode 100644 index 00000000..d477fa44 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/Constants.java @@ -0,0 +1,25 @@ +package master.flame.danmaku.gl; + +public class Constants { + public static final boolean DEBUG_DANMAKUGLSURFACEVIEW = false; + public static final boolean DEBUG_DANMAKUGLSURFACEVIEW_DRAW_STATUS = false; + + public static final boolean DEBUG_GLDANMAKUHANDLER = false; + + public static final boolean DEBUG_GLDRAWTASK = false; + public static final boolean DEBUG_GLCACHEMANAGER = false; + public static final boolean DEBUG_GLCACHEDRAWHANDLER = false; + + public static final boolean DEBUG_GLVIEWGROUP = false; + public static final boolean DEBUG_GLVIEWGROUP_DRAW = false; + public static final boolean DEBUG_GLVIEWGROUP_INIT = false; + public static final boolean DEBUG_GLVIEWGROUP_RELEASE = false; + public static final boolean DEBUG_GLVIEWGROUP_ADD = false; + + public static final boolean DEBUG_GLVIEW = false; + public static final boolean DEBUG_GLVIEW_DRAW = false; + public static final boolean DEBUG_GLVIEW_CREATE_TEXTURE = false; + public static final boolean DEBUG_GLVIEW_RELEASE_TEXTURE = false; + + public static final boolean DEBUG_GLHANDLERSURFACEVIEW = false; +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/DanmakuGLSurfaceView.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/DanmakuGLSurfaceView.java new file mode 100644 index 00000000..0336c096 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/DanmakuGLSurfaceView.java @@ -0,0 +1,651 @@ +package master.flame.danmaku.gl; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.HandlerThread; +import android.os.Looper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import java.util.LinkedList; + +import master.flame.danmaku.controller.DrawHandler; +import master.flame.danmaku.controller.IDanmakuView; +import master.flame.danmaku.controller.IDanmakuViewController; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.util.SystemClock; +import master.flame.danmaku.gl.glview.controller.TextureGLSurfaceViewRenderer; +import master.flame.danmaku.gl.wedget.GLHandlerSurfaceView; +import master.flame.danmaku.ui.widget.DanmakuTouchHelper; + +public class DanmakuGLSurfaceView extends GLHandlerSurfaceView implements IDanmakuView, IDanmakuViewController { + private static final String TAG = "DanmakuGLSurfaceView"; + private static final boolean DEBUG = Constants.DEBUG_DANMAKUGLSURFACEVIEW; + private static final boolean DEBUG_DRAW = Constants.DEBUG_DANMAKUGLSURFACEVIEW_DRAW_STATUS; + + private final TextureGLSurfaceViewRenderer mRenderer = new TextureGLSurfaceViewRenderer(this); + private boolean mPause = true; + + public DanmakuGLSurfaceView(Context context) { + super(context); + init(); + } + + public DanmakuGLSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + //配置2.0 + setEGLContextClientVersion(2); + /*不能完全避免OpenglContent丢失,但可是避免绝大部分短暂的离开app再回到app时的Content重新构建过程 + https://stackoverflow.com/questions/11067881/android-when-is-opengl-context-destroyed + */ + setPreserveEGLContextOnPause(true); + //配置颜色配置、深度缓存、模板缓冲 + setEGLConfigChooser(8, 8, 8, 8, 16, 0); + + //设置层级关系 + setZOrderMediaOverlay(true); + setWillNotCacheDrawing(true); + setDrawingCacheEnabled(false); + setWillNotDraw(true); + + //设置透明模式,否则底部的播放器会被遮盖 + getHolder().setFormat(PixelFormat.TRANSLUCENT); + + mUiThreadId = Thread.currentThread().getId(); + setBackgroundColor(Color.TRANSPARENT); + setDrawingCacheBackgroundColor(Color.TRANSPARENT); + //因为绘制mCanvas上不会被显示,所以暂不配置,否则可能影响到其他的弹幕控件(b站弹幕库问题) +// DrawHelper.useDrawColorToClearCanvas(true, false); + mTouchHelper = DanmakuTouchHelper.instance(this); + } + + @Override + public void onPause() { + if (!mPause) { + super.onPause(); + mRenderer.onPause(); + } + mPause = true; + } + + @Override + public void onResume() { + if (mPause) { + super.onResume(); + mRenderer.onResume(); + } + mPause = false; + } + + @Override + public void setAlpha(float alpha) { + mRenderer.getGLDanmakuHandler().setAlpha(alpha); + } + + @Override + public float getAlpha() { + return mRenderer.getGLDanmakuHandler().getAlpha(); + } + + //==================================以下和弹幕库相关====================================== + private DrawHandler.Callback mCallback; + + private HandlerThread mHandlerThread; + + protected volatile DrawHandler handler; + + private boolean isSurfaceCreated; + + private OnDanmakuClickListener mOnDanmakuClickListener; + + private float mXOff; + + private float mYOff; + + private View.OnClickListener mOnClickListener; + + private DanmakuTouchHelper mTouchHelper; + + private boolean mShowFps; + + private boolean mDanmakuVisible = true; + + protected int mDrawingThreadType = THREAD_TYPE_NORMAL_PRIORITY; + + protected boolean mRequestRender = false; + + private long mUiThreadId; + + private static final int MAX_RECORD_SIZE = 50; + private static final int ONE_SECOND = 1000; + private LinkedList mDrawTimes; + + + /** + * 该mBitmap,mCanvas用来作为原先绘制方式的画布位图,大小等于view的大小 + * 因为DrawTask需要操作一个Canvas,所以构造了一个和原来相同的画布 + * 绘制内容并不会显示 + */ + private Bitmap mBitmap; + private Canvas mCanvas; + + public void addDanmaku(BaseDanmaku item) { + if (handler != null) { + handler.addDanmaku(item); + } + } + + @Override + public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { + if (handler != null) { + handler.invalidateDanmaku(item, remeasure); + } + } + + @Override + public void removeAllDanmakus(boolean isClearDanmakusOnScreen) { + if (handler != null) { + handler.removeAllDanmakus(isClearDanmakusOnScreen); + } + } + + @Override + public void removeAllLiveDanmakus() { + if (handler != null) { + handler.removeAllLiveDanmakus(); + } + } + + @Override + public IDanmakus getCurrentVisibleDanmakus() { + if (handler != null) { + return handler.getCurrentVisibleDanmakus(); + } + return null; + } + + @Override + public void setCallback(DrawHandler.Callback callback) { + mCallback = callback; + if (handler != null) { + handler.setCallback(callback); + } + } + + @Override + public void release() { + if (DEBUG) { + Log.d(TAG, "release"); + } + stop(); + if (mDrawTimes != null) { + mDrawTimes.clear(); + } + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + mBitmap = null; + } + + @Override + public void stop() { + if (DEBUG) { + Log.d(TAG, "stopDraw start"); + } + stopDraw(); + if (DEBUG) { + Log.d(TAG, "stopDraw finish"); + } + } + + private synchronized void stopDraw() { + if (this.handler == null) { + return; + } + DrawHandler handler = this.handler; + this.handler = null; + //opengl退出 + super.exit(); + if (handler != null) { + handler.quit(); + } + //退出handle线程 + HandlerThread handlerThread = this.mHandlerThread; + mHandlerThread = null; + if (handlerThread != null) { + try { + handlerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + handlerThread.quit(); + } + } + + protected synchronized Looper getLooper(int type) { + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread = null; + } + + int priority; + switch (type) { + case THREAD_TYPE_MAIN_THREAD: + return Looper.getMainLooper(); + case THREAD_TYPE_HIGH_PRIORITY: + priority = android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY; + break; + case THREAD_TYPE_LOW_PRIORITY: + priority = android.os.Process.THREAD_PRIORITY_LOWEST; + break; + case THREAD_TYPE_NORMAL_PRIORITY: + default: + priority = android.os.Process.THREAD_PRIORITY_DEFAULT; + break; + } + String threadName = "DFM Handler Thread #" + priority; + mHandlerThread = new HandlerThread(threadName, priority); + mHandlerThread.start(); + if (DEBUG) { + Log.d(TAG, "start a new looper,type=" + type); + } + return mHandlerThread.getLooper(); + } + + private void prepare() { + if (handler == null) { + Looper looper = getLooper(mDrawingThreadType); + setRenderer(mRenderer, looper); + setRenderMode(GLHandlerSurfaceView.RENDERMODE_WHEN_DIRTY); + handler = new DrawHandler(looper, this, mDanmakuVisible); + if (DEBUG) { + Log.d(TAG, "prepare"); + } + } + } + + @Override + public void prepare(BaseDanmakuParser parser, DanmakuContext config) { + //此处mDisplayer必须是AndroidGLDisplayer + ((AndroidGLDisplayer) config.mDisplayer).setRenderer(mRenderer); + prepare(); + handler.setConfig(config); + handler.setParser(parser); + handler.setCallback(mCallback); + handler.prepare(); + } + + @Override + public boolean isPrepared() { + return handler != null && handler.isPrepared(); + } + + @Override + public DanmakuContext getConfig() { + if (handler == null) { + return null; + } + return handler.getConfig(); + } + + @Override + public void showFPS(boolean show) { + mShowFps = show; + } + + private float fps() { + if (mDrawTimes == null) { + mDrawTimes = new LinkedList<>(); + } + long lastTime = SystemClock.uptimeMillis(); + mDrawTimes.addLast(lastTime); + Long first = mDrawTimes.peekFirst(); + if (first == null) { + return 0.0f; + } + float dtime = lastTime - first; + int frames = mDrawTimes.size(); + if (frames > MAX_RECORD_SIZE) { + mDrawTimes.removeFirst(); + } + return dtime > 0 ? mDrawTimes.size() * ONE_SECOND / dtime : 0.0f; + } + + @Override + public long drawDanmakus() { + if (DEBUG_DRAW) { + Log.d(TAG, "drawDanmakus"); + } + if (!isSurfaceCreated) + return 0; + if (!isShown()) + return -1; + long stime = SystemClock.uptimeMillis(); + if (mCanvas == null) { + initTempCanvas(getWidth(), getHeight()); + } + if (mCanvas != null) { + handler.draw(mCanvas); + } + mRenderer.requestRender(); + if (mShowFps) { + //todo 暂时fps,暂时没有想到解决方案,但可以通过打印log方式 + Log.i(TAG, String.format("danmuku fps = %.2f", fps())); + } + return SystemClock.uptimeMillis() - stime; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (DEBUG) { + Log.d(TAG, "onLayout"); + } + initTempCanvas(right - left, bottom - top); + if (handler != null) { + handler.notifyDispSizeChanged(right - left, bottom - top); + } + isSurfaceCreated = true; + } + + private void initTempCanvas(int width, int height) { + if (width <= 0 || height <= 0) { + return; + } + if (mBitmap == null || mBitmap.getWidth() != width || mBitmap.getHeight() != height || mBitmap.isRecycled()) { + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); + if (mCanvas == null) { + mCanvas = new Canvas(); + } + mCanvas.setBitmap(mBitmap); + if (DEBUG) { + Log.d(TAG, "create a new temp bitmap,width=" + width + ",height=" + height); + } + } + } + + @Override + public void toggle() { + if (isSurfaceCreated) { + if (handler == null) + start(); + else if (handler.isStop()) { + resume(); + } else { + pause(); + } + } + } + + @Override + public void pause() { + if (DEBUG) { + Log.d(TAG, "pause"); + } + onPause(); + if (handler != null) { + handler.removeCallbacks(mResumeRunnable); + handler.pause(); + } + } + + private int mResumeTryCount = 0; + + private Runnable mResumeRunnable = new Runnable() { + @Override + public void run() { + DrawHandler drawHandler = handler; + if (drawHandler == null) { + return; + } + mResumeTryCount++; + if (mResumeTryCount > 4 || DanmakuGLSurfaceView.this.isShown()) { + drawHandler.resume(); + } else { + drawHandler.postDelayed(this, 100 * mResumeTryCount); + } + } + }; + + @Override + public void resume() { + if (DEBUG) { + Log.d(TAG, "resume"); + } + onResume(); + if (handler != null && handler.isPrepared()) { + mResumeTryCount = 0; + handler.post(mResumeRunnable); + } else if (handler == null) { + restart(); + } + } + + @Override + public boolean isPaused() { + if (handler != null) { + return handler.isStop(); + } + return false; + } + + public void restart() { + if (DEBUG) { + Log.d(TAG, "restart"); + } + stop(); + start(); + } + + @Override + public void start() { + if (DEBUG) { + Log.d(TAG, "start"); + } + start(0); + } + + @Override + public void start(long position) { + if (DEBUG) { + Log.d(TAG, "start position=" + position); + } + if (handler == null) { + prepare(); + } else { + handler.removeCallbacksAndMessages(null); + } + handler.obtainMessage(DrawHandler.START, position).sendToTarget(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return mTouchHelper.onTouchEvent(event) || super.onTouchEvent(event); + } + + @Override + public void seekTo(Long ms) { + if (DEBUG) { + Log.d(TAG, "seekTo " + ms); + } + if (handler != null) { + handler.seekTo(ms); + } + } + + + @Override + public void enableDanmakuDrawingCache(boolean enable) { + //opengl必须使用缓存,然后放到opengl中 + } + + @Override + public boolean isDanmakuDrawingCacheEnabled() { + //此处必须为true + return true; + } + + @Override + public boolean isViewReady() { + return isSurfaceCreated; + } + + @Override + public int getViewWidth() { + return super.getWidth(); + } + + @Override + public int getViewHeight() { + return super.getHeight(); + } + + @Override + public View getView() { + return this; + } + + @Override + public void show() { + if (DEBUG) { + Log.d(TAG, "show "); + } + showAndResumeDrawTask(null); + } + + @Override + public void showAndResumeDrawTask(Long position) { + if (DEBUG) { + Log.d(TAG, "show position = " + position); + } + mDanmakuVisible = true; + mRenderer.show(); + if (handler == null) { + return; + } + handler.showDanmakus(position); + } + + @Override + public void hide() { + if (DEBUG) { + Log.d(TAG, "hide "); + } + mDanmakuVisible = false; + mRenderer.hide(); + if (handler == null) { + return; + } + handler.hideDanmakus(false); + } + + @Override + public long hideAndPauseDrawTask() { + mDanmakuVisible = false; + mRenderer.hide(); + if (handler == null) { + return 0; + } + return handler.hideDanmakus(true); + } + + @Override + public void clear() { + if (DEBUG) { + Log.d(TAG, "clear "); + } + if (!isViewReady()) { + return; + } + mRenderer.clearNextFrame(); + } + + @Override + public boolean isShown() { + return mDanmakuVisible && super.isShown(); + } + + @Override + public void setDrawingThreadType(int type) { + mDrawingThreadType = type; + } + + @Override + public long getCurrentTime() { + if (handler != null) { + return handler.getCurrentTime(); + } + return 0; + } + + @Override + @SuppressLint("NewApi") + public boolean isHardwareAccelerated() { + //isHardwareAccelerated为true会触发cache分片,opengl实现不需要 + return false; + } + + @Override + public void clearDanmakusOnScreen() { + if (DEBUG) { + Log.d(TAG, "clearDanmakusOnScreen "); + } + if (handler != null) { + handler.clearDanmakusOnScreen(); + } + } + + @Override + public void setOnDanmakuClickListener(OnDanmakuClickListener listener) { + mOnDanmakuClickListener = listener; + } + + @Override + public void setOnDanmakuClickListener(OnDanmakuClickListener listener, float xOff, + float yOff) { + mOnDanmakuClickListener = listener; + mXOff = xOff; + mYOff = yOff; + } + + @Override + public OnDanmakuClickListener getOnDanmakuClickListener() { + return mOnDanmakuClickListener; + } + + @Override + public float getXOff() { + return mXOff; + } + + @Override + public float getYOff() { + return mYOff; + } + + @Override + public void forceRender() { + if (DEBUG) { + Log.d(TAG, "forceRender "); + } + mRequestRender = true; + handler.forceRender(); + mRenderer.requestRender(); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/GLDrawTask.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/GLDrawTask.java new file mode 100644 index 00000000..666c47bc --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/GLDrawTask.java @@ -0,0 +1,653 @@ +package master.flame.danmaku.gl; + +import android.opengl.GLES20; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.Pair; + +import java.util.Comparator; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +import master.flame.danmaku.controller.DrawTask; +import master.flame.danmaku.controller.IDrawTask; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.ICacheManager; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.IDrawingCache; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.model.android.DrawingCacheHolder; +import master.flame.danmaku.danmaku.util.DanmakuUtils; +import master.flame.danmaku.gl.glview.GLUtils; +import master.flame.danmaku.gl.wedget.GLShareable; +import tv.cjump.jni.NativeBitmapFactory; + +import static master.flame.danmaku.danmaku.model.IDanmakus.ST_BY_LIST; + +/** + * 创建人:yangzhiqian + * 创建时间:2018/7/11 14:20 + * 备注: + */ +public class GLDrawTask extends DrawTask { + private static final boolean DEBUG = Constants.DEBUG_GLDRAWTASK; + private static final String TAG = "GLDrawTask"; + private GLCacheManager mCacheManager; + private AndroidGLDisplayer mDiplayer; + private Looper mLooper; + + public GLDrawTask(DanmakuTimer timer, DanmakuContext context, TaskListener taskListener) { + this(null, timer, context, taskListener); + } + + public GLDrawTask(Looper lopper, DanmakuTimer timer, DanmakuContext context, TaskListener taskListener) { + super(timer, context, taskListener); + NativeBitmapFactory.loadLibs(); + mLooper = lopper; + mCacheManager = new GLCacheManager(); + mRenderer.setCacheManager(mCacheManager); + //此GLDrawTask必须配合AndroidGLDisplayer使用 + mDiplayer = (AndroidGLDisplayer) context.mDisplayer; + } + + @Override + public void addDanmaku(BaseDanmaku danmaku) { + super.addDanmaku(danmaku); + if (mCacheManager != null) { + if (DEBUG) { + Log.i(TAG, "addDanmaku id = " + danmaku.id); + } + mCacheManager.addDanmaku(danmaku); + } + } + + @Override + public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { + super.invalidateDanmaku(item, remeasure); + if (mCacheManager != null) { + if (DEBUG) { + Log.i(TAG, "invalidateDanmaku id = " + item.id); + } + mCacheManager.rebuildDanmaku(item); + } + } + + @Override + public void removeAllDanmakus(boolean isClearDanmakusOnScreen) { + IDanmakus subnew = subnew(0, Long.MAX_VALUE); + super.removeAllDanmakus(isClearDanmakusOnScreen); + if (DEBUG) { + Log.i(TAG, "removeAllDanmakus isClearDanmakusOnScreen = " + isClearDanmakusOnScreen + "\ttotal size = " + (subnew == null ? 0 : subnew.size())); + } + if (isClearDanmakusOnScreen) { + mDiplayer.getRenderer().getGLDanmakuHandler().removeAllDanmaku(); + } + if (mCacheManager != null) { + mCacheManager.removeAllCachedDanmaku(); + } else if (subnew != null && !subnew.isEmpty()) { + //此处不应该被调用到 + Log.w(TAG, "此处不应该被调用到,请检查一下代码逻辑"); + subnew.forEach(new IDanmakus.Consumer() { + @Override + public int accept(BaseDanmaku danmaku) { + IDrawingCache cache = danmaku.getDrawingCache(); + if (cache != null) { + cache.destroy(); + danmaku.cache = null; + } + return ACTION_CONTINUE; + } + }); + } + } + + private IDanmakus subnew(long start, long end) { + IDanmakus subnew; + int exceptionTimes = 0; + while (exceptionTimes < 3) { + try { + //subnew调用不安全,可能会抛异常 + subnew = danmakuList.subnew(start, end); + return subnew; + } catch (Exception e) { + exceptionTimes++; + } + } + return null; + } + + @Override + protected void onDanmakuRemoved(BaseDanmaku danmaku) { + super.onDanmakuRemoved(danmaku); + if (danmaku == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "onDanmakuRemoved id = " + danmaku.id); + } + mDiplayer.getRenderer().getGLDanmakuHandler().removeDamaku(danmaku); + if (mCacheManager != null) { + mCacheManager.removeDanmaku(danmaku); + } else { + //此处不应该被调用到 + Log.w(TAG, "此处不应该被调用到,请检查一下代码逻辑"); + IDrawingCache cache = danmaku.getDrawingCache(); + if (cache != null) { + cache.destroy(); + danmaku.cache = null; + } + } + } + + @Override + public void start() { + super.start(); + if (DEBUG) { + Log.i(TAG, "GLDrawTask start"); + } + NativeBitmapFactory.loadLibs(); + if (mCacheManager == null) { + mCacheManager = new GLCacheManager(); + mRenderer.setCacheManager(mCacheManager); + } + mCacheManager.start(); + } + + @Override + public void onPlayStateChanged(int state) { + super.onPlayStateChanged(state); + if (DEBUG) { + Log.i(TAG, "onPlayStateChanged state = " + state); + } + if (mCacheManager != null) { + mCacheManager.onPlayStateChanged(state); + } + } + + @Override + public void quit() { + super.quit(); + if (DEBUG) { + Log.i(TAG, "GLDrawTask quit"); + } + long startTime = System.nanoTime(); + mRenderer.setCacheManager(null); + if (mCacheManager != null) { + mCacheManager.quit(); + mCacheManager = null; + } + NativeBitmapFactory.releaseLibs(); + if (DEBUG) { + Log.i(TAG, "GLDrawTask quit time = " + (System.nanoTime() - startTime)); + } + } + + + public class GLCacheManager implements ICacheManager { + private static final String TAG = "GLCacheManager"; + private static final boolean DEBUG = Constants.DEBUG_GLCACHEMANAGER; + + private HandlerThread mThread; + private GLCacheDrawHandler mHandler; + private final Object mMonitor = new Object(); + private boolean mExited = false; + + private final TreeSet> mCacheTasks = new TreeSet<>(new Comparator>() { + @Override + public int compare(Pair o1, Pair o2) { + if (o2.first == o1.first) { + return 0; + } + return DanmakuUtils.compare(o1.first, o2.first); + } + }); + + /** + * 存储已经缓存过的弹幕,里面包含bitmap和纹理,所以必须保证这些弹幕以后做一次释放操作,否则会有内存泄漏 + */ + private final Danmakus mCachedDanmakus = new Danmakus(ST_BY_LIST); + + @Override + public void addDanmaku(BaseDanmaku danmaku) { + if (mHandler != null && danmaku != null) { + if (DEBUG) { + Log.i(TAG, "addDanmaku id = " + danmaku.id); + } + synchronized (mCacheTasks) { + mCacheTasks.add(new Pair<>(danmaku, GLCacheDrawHandler.ADD_DANMAKU)); + mHandler.sendEmptyMessage(GLCacheDrawHandler.HANDLE_DANMAKU); + } + } + } + + @Override + public void buildDanmakuCache(BaseDanmaku danmaku) { + //不需要做任何操作,等待异步处理完成 + } + + void rebuildDanmaku(BaseDanmaku danmaku) { + if (mHandler != null && danmaku != null) { + if (DEBUG) { + Log.i(TAG, "rebuildDanmaku id = " + danmaku.id); + } + synchronized (mCacheTasks) { + mCacheTasks.add(new Pair<>(danmaku, GLCacheDrawHandler.REBUILD_DANMAKU)); + mHandler.sendEmptyMessage(GLCacheDrawHandler.HANDLE_DANMAKU); + } + } + } + + void removeDanmaku(BaseDanmaku danmaku) { + if (mHandler != null) { + if (DEBUG) { + Log.i(TAG, "removeCachedDanmaku id = " + danmaku.id); + } + synchronized (mCacheTasks) { + mCacheTasks.add(new Pair<>(danmaku, GLCacheDrawHandler.REMOVE_DANMAKU)); + mHandler.sendEmptyMessage(GLCacheDrawHandler.HANDLE_DANMAKU); + } + } + } + + void removeAllCachedDanmaku() { + if (mHandler != null) { + if (DEBUG) { + Log.i(TAG, "removeAllCachedDanmaku"); + } + synchronized (mCacheTasks) { + mCacheTasks.clear(); + mHandler.obtainMessage(GLCacheDrawHandler.REMOVE_ALL_CACHED_DANMAKU).sendToTarget(); + } + } + } + + public void start() { + if (DEBUG) { + Log.i(TAG, "start"); + } + Looper workLooper = mLooper; + if (workLooper == null) { + //开启handler + if (mThread == null) { + mThread = new HandlerThread("GLCacheManager Cache-Building Thread"); + mThread.start(); + } + workLooper = mThread.getLooper(); + } + if (mHandler == null) { + mHandler = new GLCacheDrawHandler(workLooper); + } + mExited = false; + mHandler.start(); + } + + public void pause() { + if (DEBUG) { + Log.i(TAG, "pause"); + } + if (mHandler != null) { + mHandler.pause(); + } + } + + void onPlayStateChanged(int state) { + if (DEBUG) { + Log.i(TAG, "onPlayStateChanged state = " + state); + } + if (state == IDrawTask.PLAY_STATE_PAUSE) { + pause(); + } else if (state == IDrawTask.PLAY_STATE_PLAYING) { + start(); + } + } + + public void quit() { + if (DEBUG) { + Log.i(TAG, "quit "); + } + synchronized (mCacheTasks) { + mCacheTasks.clear(); + } + long startTime = System.nanoTime(); + if (mHandler != null) { + mHandler.pause(); + mHandler.removeCallbacksAndMessages(null); + mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(GLCacheDrawHandler.QUIT)); + mHandler = null; + } + if (mThread != null) { + try { + mThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mThread.quit(); + mThread = null; + } else { + synchronized (mMonitor) { + while (!mExited) { + try { + mMonitor.wait(); + } catch (InterruptedException ignore) { + } + } + } + } + if (DEBUG) { + Log.i(TAG, "quit time = " + (System.nanoTime() - startTime)); + } + } + + private class GLCacheDrawHandler extends Handler { + private static final String TAG = "GLCacheDrawHandler"; + private static final boolean DEBUG = Constants.DEBUG_GLCACHEDRAWHANDLER; + private static final int HANDLE_DANMAKU = 10000; + private static final int ADD_DANMAKU = 0x1; + private static final int REBUILD_DANMAKU = 0x2; + private static final int REMOVE_DANMAKU = 0x3; + + private static final int REMOVE_ALL_CACHED_DANMAKU = 0x4; + /** + * 定时清理弹幕缓存和构建将来的弹幕 + */ + private static final int DISPATCH_ACTIONS = 0x5; + private static final int QUIT = 0x6; + + private GLShareable.GLShareHelper mGLShareHelper; + private boolean mPause = true; + + /** + * 构建将来缓存的参数 + */ + private long mFutureBeginOffsetBegin = -mContext.mDanmakuFactory.MAX_DANMAKU_DURATION; + private long mFutureBeginOffsetEnd = 1000; + private long mHandleTime = 100000000;//每次最多构建100ms + private long mDispatchActionsTimeGap = 1000;//1秒中轮询一次缓存和构建 + + GLCacheDrawHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + int what = msg.what; + if (DEBUG) { + Log.i(TAG, "handleMessage what = " + what); + } + if (mPause && what != QUIT) { + //对于停止状态,只处理QUIT操作 + return; + } + if (mGLShareHelper == null && + what != QUIT) { + //共享渲染线程的glcontext + int tryTimes = 0; + //尝试三次 + while (tryTimes++ < 3 && (mGLShareHelper = GLShareable.GLShareHelper.makeSharedGlContext(mDiplayer.getRenderer())) == null) { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (mGLShareHelper == null) { + return; + } + } + switch (what) { + case DISPATCH_ACTIONS: + clearAllCachedDanmakus(false); + //todo 构建将来需要显示弹幕缓存,当前问题是,如果购将这些缓存会耗时比较大 + //buildFutureDanmaku(); + removeMessages(DISPATCH_ACTIONS); + sendEmptyMessageDelayed(DISPATCH_ACTIONS, mDispatchActionsTimeGap); + break; + case HANDLE_DANMAKU: + mHandler.removeMessages(GLCacheDrawHandler.HANDLE_DANMAKU); + int size = 0; + long startTime = System.nanoTime(); + while ((System.nanoTime() - startTime < mHandleTime) && (size = handleDanmaku()) > 0) { + if (DEBUG) { + Log.d(TAG, "remain size=" + size); + } + } + if (size > 0) { + //还有 + mHandler.sendEmptyMessage(GLCacheDrawHandler.HANDLE_DANMAKU); + } + break; + case REMOVE_ALL_CACHED_DANMAKU: + clearAllCachedDanmakus(true); + break; + case QUIT: + removeCallbacksAndMessages(null); + clearAllCachedDanmakus(true); + GLShareable.GLShareHelper.release(); + mGLShareHelper = null; + if (mThread != null) { + this.getLooper().quit(); + } else { + synchronized (mMonitor) { + mExited = true; + mMonitor.notifyAll(); + } + } + break; + } + } + + public void start() { + if (DEBUG) { + Log.i(TAG, "start"); + } + mPause = false; + removeMessages(DISPATCH_ACTIONS); + //开启缓存操作 + obtainMessage(DISPATCH_ACTIONS).sendToTarget(); + } + + public void pause() { + if (DEBUG) { + Log.i(TAG, "pause"); + } + mPause = true; + //pause默认没有glcontext + mGLShareHelper = null; + } + + private int handleDanmaku() { + int remainSize; + Pair cacheTask; + synchronized (mCacheTasks) { + cacheTask = mCacheTasks.pollFirst(); + remainSize = mCacheTasks.size(); + } + if (cacheTask != null) { + switch (cacheTask.second) { + case ADD_DANMAKU: + if (buildDanmakuCache(cacheTask.first, false)) { + //通知gl bitmap准备好了 + mCachedDanmakus.addItem(cacheTask.first); + mDiplayer.getRenderer().getGLDanmakuHandler().addDanmaku((cacheTask.first)); + } + break; + case REBUILD_DANMAKU: + if (buildDanmakuCache(cacheTask.first, true)) { + mCachedDanmakus.addItem((cacheTask.first)); + mDiplayer.getRenderer().getGLDanmakuHandler().addDanmaku(cacheTask.first); + } + break; + case REMOVE_DANMAKU: + if (destroyCache(cacheTask.first, true)) { + mCachedDanmakus.removeItem(cacheTask.first); + } + break; + } + } + return remainSize; + } + + private boolean buildDanmakuCache(BaseDanmaku item, boolean force) { + if (mPause) { + return false; + } + if (!force) { + if (item.mGLTextureId != 0) { + return false; + } + if (DanmakuUtils.isCacheOk(item)) { + //已经被映射到纹理了或者缓存有效 + return createTexture(item); + } + } + //先销毁先前的缓存,防止内存泄漏 + if (destroyCache(item, true)) { + mCachedDanmakus.removeItem(item); + } + // measure + if (!item.isMeasured()) { + item.measure(mDisp, true); + } + if (!item.isPrepared()) { + item.prepare(mDisp, true); + } + if (DEBUG) { + Log.i(TAG, "buildDanmakuCache id = " + item.id); + } + //构建缓存 + item.cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, null, mContext.cachingPolicy.bitsPerPixelOfCache); + return createTexture(item); + } + + private boolean destroyCache(BaseDanmaku item, boolean includeTexture) { + if (item == null) { + return false; + } + if (includeTexture) { + //销毁之前的纹理 + destroyTexture(item); + } + IDrawingCache cache = item.getDrawingCache(); + if (cache == null) { + return false; + } + if (DEBUG) { + Log.i(TAG, "destroyCache id = " + item.id); + } + cache.destroy(); + item.cache = null; + return true; + } + + private boolean createTexture(BaseDanmaku item) { + if (item == null) { + return false; + } + destroyTexture(item); + //更新对应的纹理id为失效状态 + IDrawingCache drawingCache = item.cache; + if (drawingCache == null || drawingCache.get() == null) { + return false; + } + DrawingCacheHolder holder = (DrawingCacheHolder) drawingCache.get(); + if (holder == null || holder.bitmap == null || holder.bitmap.isRecycled()) { + return false; + } + item.mGLTextureId = GLUtils.createBitmapTexture2D(holder.bitmap); + item.mTextureWidth = holder.bitmap.getWidth(); + item.mTextureHeight = holder.bitmap.getHeight(); + if (item.mGLTextureId != 0) { + //已经成功创建了纹理,可以删除bitmap缓存了 + destroyCache(item, false); + } + if (DEBUG) { + Log.d(TAG, "createTexture textid=" + item.mGLTextureId); + } + return true; + } + + private void destroyTexture(BaseDanmaku item) { + if (item == null) { + return; + } + if (item.mGLTextureId != 0) { + //销毁之前的纹理 + if (DEBUG) { + Log.d(TAG, "destroyTexture textid=" + item.mGLTextureId); + } + GLES20.glDeleteTextures(1, new int[]{item.mGLTextureId}, 0); + item.mGLTextureId = 0; + } + } + + private void buildFutureDanmaku() { + long begin = mTimer.currMillisecond + mFutureBeginOffsetBegin; + long end = mTimer.currMillisecond + mFutureBeginOffsetEnd; + //拉取构建时间内的弹幕 + IDanmakus danmakus = subnew(begin, end); + if (danmakus == null || danmakus.isEmpty()) { + return; + } + final AtomicInteger validBuildSize = new AtomicInteger(0); + final AtomicInteger succeedBuildSize = new AtomicInteger(0); + final int sizeInScreen = danmakus.size(); + danmakus.forEach(new IDanmakus.DefaultConsumer() { + int orderInScreen = 0; + int currScreenIndex = 0; + + @Override + public int accept(BaseDanmaku item) { + if (mPause) { + return ACTION_BREAK; + } + if (!item.hasPassedFilter()) { + mContext.mDanmakuFilters.filter(item, orderInScreen, sizeInScreen, null, true, mContext); + } + if (item.priority == 0 && item.isFiltered()) { + return ACTION_CONTINUE; + } + if (item.getType() == BaseDanmaku.TYPE_SCROLL_RL) { + // 同屏弹幕密度只对滚动弹幕有效 + int screenIndex = (int) ((item.getActualTime() - mTimer.currMillisecond) / mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); + if (currScreenIndex == screenIndex) + orderInScreen++; + else { + orderInScreen = 0; + currScreenIndex = screenIndex; + } + } + validBuildSize.incrementAndGet(); + if (buildDanmakuCache(item, false)) { + succeedBuildSize.incrementAndGet(); + mCachedDanmakus.addItem(item); + mDiplayer.getRenderer().getGLDanmakuHandler().addDanmaku(item); + } + return ACTION_CONTINUE; + } + }); + if (DEBUG) { + Log.i(TAG, "buildFutureDanmaku validBuildSize = " + validBuildSize.get() + "\t succeedBuildSize = " + succeedBuildSize.get()); + } + } + + private void clearAllCachedDanmakus(final boolean force) { + if (DEBUG) { + Log.i(TAG, "clearAllCachedDanmakus force = " + force + "\t size = " + mCachedDanmakus.size()); + } + mCachedDanmakus.forEach(new IDanmakus.DefaultConsumer() { + @Override + public int accept(BaseDanmaku item) { + boolean releaseTexture = item.isTimeOut() || force; + destroyCache(item, releaseTexture); + return releaseTexture ? ACTION_REMOVE : ACTION_CONTINUE; + } + }); + } + } + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLProgram.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLProgram.java new file mode 100644 index 00000000..6f8d8886 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLProgram.java @@ -0,0 +1,60 @@ +package master.flame.danmaku.gl.glview; + +import android.content.Context; + +import master.flame.danmaku.gl.glview.view.GLShader; + +public class GLProgram { + private Context mAppContext; + public GLShader mShader; + public int glHPosition; + public int glHCoordinate; + public int glHTexture; + public int glHProject; + public int glHView; + public int glHModel; + public int glHRotate; + public int glAlpha; + + public GLProgram(Context context) { + mAppContext = context.getApplicationContext(); + } + + /** + * gl线程 + */ + public void load() { + mShader = new GLShader("gl/glview.vert", "gl/glview.frag", mAppContext); + mShader.create(); + + glHPosition = mShader.getAttributeLocation("vPosition"); + glHCoordinate = mShader.getAttributeLocation("vCoordinate"); + glHTexture = mShader.getUniformLocation("vTexture"); + glHProject = mShader.getUniformLocation("vProject"); + glHView = mShader.getUniformLocation("vView"); + glHModel = mShader.getUniformLocation("vModel"); + glHRotate = mShader.getUniformLocation("vRotate"); + glAlpha = mShader.getUniformLocation("alpha"); + } + + /** + * gl线程 + */ + public void unload() { + mShader.onDestroy(); + } + + /** + * gl线程 + */ + public void use() { + mShader.use(); + } + + /** + * gl线程 + */ + public void unuse() { + mShader.unuse(); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLUtils.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLUtils.java new file mode 100644 index 00000000..d545fa18 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/GLUtils.java @@ -0,0 +1,203 @@ +package master.flame.danmaku.gl.glview; + +import android.content.Context; +import android.graphics.Bitmap; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; + +import javax.microedition.khronos.opengles.GL10; + +import static android.opengl.GLES20.GL_TEXTURE_2D; + +public class GLUtils { + + public static int createOESTextureObject() { + int[] tex = new int[1]; + GLES20.glGenTextures(1, tex, 0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, + GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); + return tex[0]; + } + + + public static int createTexture2D(int width, int height, int type, ByteBuffer byteBuffer) { + int[] tex = new int[1]; + GLES20.glGenTextures(1, tex, 0); + GLES20.glBindTexture(GL_TEXTURE_2D, tex[0]); + GLES20.glTexParameterf(GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + byteBuffer.position(0); + GLES20.glTexImage2D(GL_TEXTURE_2D, 0, type, width, height, 0, + type, GLES20.GL_UNSIGNED_BYTE, byteBuffer); + GLES20.glGenerateMipmap(GL_TEXTURE_2D); + return tex[0]; + } + + public static int createBitmapTexture2D(Bitmap bitmap) { + int[] texture = new int[1]; + if (bitmap != null && !bitmap.isRecycled()) { + //生成纹理 + GLES20.glGenTextures(1, texture, 0); + //生成纹理 + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]); + //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色 + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色 + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合 + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合 + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + //根据以上指定的参数,生成一个2D纹理 + android.opengl.GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + return texture[0]; + } + return 0; + } + + /** + * 3为向量叉乘,结果为两个向量的向量
+ * u x v
+ * 注意,叉乘不满足交换律 + * + * @param u u + * @param uOffset uOffset + * @param v v + * @param vOffset vOffset + * @return 叉积 + */ + public static float[] vectorProduct(float[] u, int uOffset, float[] v, int vOffset) { + if (u.length - uOffset < 3 || v.length - vOffset > 3 || uOffset < 0 || vOffset < 0) { + throw new IndexOutOfBoundsException(); + } + float[] result = new float[4]; + result[0] = u[uOffset + 1] * v[vOffset + 2] - u[uOffset + 2] * v[vOffset + 1]; + result[1] = u[uOffset + 2] * v[vOffset] - u[uOffset] * v[vOffset + 2]; + result[2] = u[uOffset] * v[vOffset + 1] - u[uOffset + 1] * v[vOffset]; + result[3] = 1; + return result; + } + + /** + * 单位化一个向量 + * + * @param vector 向量 + * @param offset 偏移 + * @return 新的单位向量 + */ + public static float[] vectorIdentity(float[] vector, int offset) { + if (offset < 0 || vector.length - offset < 3) { + throw new IndexOutOfBoundsException(); + } + float scale = (float) Math.sqrt(Math.pow(vector[offset], 2) + Math.pow(vector[offset + 1], 2) + Math.pow(vector[offset + 2], 2)); + return new float[]{vector[offset] / scale, vector[offset + 1] / scale, vector[offset + 2] / scale, 1}; + } + + + public static int linkProgram(int verShader, int fragShader) { + int program = GLES20.glCreateProgram(); + if (program == 0) { + throw new RuntimeException("Create Program Failed!" + GLES20.glGetError()); + } + GLES20.glAttachShader(program, verShader); + GLES20.glAttachShader(program, fragShader); + GLES20.glLinkProgram(program); + + GLES20.glUseProgram(program); + return program; + } + + + public static int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + + 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) { + GLES20.glDeleteProgram(program); + program = 0; + } + } + return program; + } + + public static 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) { + GLES20.glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + public static String readShaderFromResource(Context context, int resourceId) { + StringBuilder builder = new StringBuilder(); + InputStream is = null; + InputStreamReader isr = null; + BufferedReader br = null; + try { + is = context.getResources().openRawResource(resourceId); + isr = new InputStreamReader(is); + br = new BufferedReader(isr); + String line; + while ((line = br.readLine()) != null) { + builder.append(line + "\n"); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + is = null; + } + if (isr != null) { + isr.close(); + isr = null; + } + if (br != null) { + br.close(); + br = null; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/MatrixInfo.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/MatrixInfo.java new file mode 100644 index 00000000..4dd3d293 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/MatrixInfo.java @@ -0,0 +1,14 @@ +package master.flame.danmaku.gl.glview; + +public class MatrixInfo { + public float mTransX = 0; + public float mTransY = 0; + public float mTransZ = 0; + + public float mRotateX = 0; + public float mRotateY = 0; + public float mRotateZ = 0; + + public float mScaleX = 1; + public float mScaleY = 1; +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/GLDanmakuHandler.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/GLDanmakuHandler.java new file mode 100644 index 00000000..b5aba5e1 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/GLDanmakuHandler.java @@ -0,0 +1,175 @@ +package master.flame.danmaku.gl.glview.controller; + +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.gl.Constants; +import master.flame.danmaku.gl.glview.view.GLViewGroup; +import master.flame.danmaku.gl.wedget.GLHandlerSurfaceView; + +/** + * 创建人:yangzhiqian + * 创建时间:2018/7/12 14:12 + * 备注: 负责传递弹幕信息和控制gl线程空闲时的任务调度 + */ +public class GLDanmakuHandler implements Runnable { + private static final String TAG = "GLDanmakuHandler"; + private static final boolean DEBUG = Constants.DEBUG_GLDANMAKUHANDLER; + private GLHandlerSurfaceView mGLSurfaceView; + private final GLViewGroup mGLViewGroup; + private boolean mPaused = true; + private volatile boolean mPrepareRender = false; + private final AtomicBoolean mPostRunning = new AtomicBoolean(false); + private boolean mHasGLContext = false; + + public GLDanmakuHandler(GLHandlerSurfaceView surfaceView) { + if (surfaceView == null) { + throw new RuntimeException("surfaceView 不能为空"); + } + this.mGLSurfaceView = surfaceView; + this.mGLViewGroup = new GLViewGroup(surfaceView.getContext()); + mGLViewGroup.setViewsReverseState(false, false); + } + + /** + * gl线程 + */ + public void onSurfaceCreate() { + if (DEBUG) { + Log.d(TAG, "onSurfaceCreate"); + } + mHasGLContext = true; + mGLViewGroup.onGLSurfaceViewCreate(); + postRun(); + } + + /** + * gl线程 + */ + public void onSurfaceSizeChanged(int width, int height) { + if (DEBUG) { + Log.d(TAG, "onSurfaceSizeChanged width=" + width + "\theight=" + height); + } + mHasGLContext = true; + mGLViewGroup.onDisplaySizeChanged(width, height); + } + + /** + * gl线程 + */ + public void onGLDrawFrame() { + if (DEBUG) { + Log.d(TAG, "onGLDrawFrame"); + } + mHasGLContext = true; + if (!mPaused) { + mGLViewGroup.onGLDrawFrame(); + } + //渲染完成 + mPrepareRender = false; + //此时渲染线程空闲,重新开启辅助任务 + postRun(); + } + + public void onResume() { + if (DEBUG) { + Log.d(TAG, "onResume"); + } + mPaused = false; + } + + public void onPause() { + if (DEBUG) { + Log.d(TAG, "onResume"); + } + //GLSurface在onPause时会造成GLContext丢失,即使调用了setPreserveEGLContextOnPause也存在丢失的情况 + //此处在onPause是默认GLContext丢失,当opengl生命周期启动时GLContext一定存在 + mHasGLContext = false; + mPaused = true; + } + + public void prepareRender() { + if (DEBUG) { + Log.d(TAG, "prepareRender"); + } + //上层调用让GLSurfaceView渲染,此时对于空闲时执行的任务可以暂时停止,优先渲染 + mPrepareRender = true; + } + + public void addDanmaku(BaseDanmaku danmaku) { + if (DEBUG) { + Log.d(TAG, "addDanmaku"); + } + if (danmaku == null) { + return; + } + mGLViewGroup.addDanmu(danmaku); + postRun(); + } + + public void removeDamaku(BaseDanmaku danmaku) { + if (DEBUG) { + Log.d(TAG, "removeDamaku"); + } + if (danmaku == null) { + return; + } + mGLViewGroup.removeView(danmaku); + postRun(); + } + + public void removeAllDanmaku() { + if (DEBUG) { + Log.d(TAG, "removeAllDanmaku"); + } + mGLViewGroup.removeAll(); + postRun(); + } + + public void setAlpha(float alpha) { + mGLViewGroup.setAlpha(alpha); + } + + public float getAlpha() { + return mGLViewGroup.getAlpha(); + } + + protected boolean postRun() { + //在弹幕密度比较高的情况下,由于queueEvent方法需要进行同步,该处锁机制同步消耗较大,所以暂时去除该辅助任务 + //具体代价可以在高密度弹幕情况下对mGLSurfaceView.queueEvent(this);进行耗时检测 +// if (mPostRunning.compareAndSet(false, true)) { +// mGLSurfaceView.queueEvent(this); +// if (DEBUG) { +// Log.d(TAG, "postRun succeed"); +// } +// return true; +// } + return false; + } + + /** + * gl线程 + */ + @Override + public void run() { + if (DEBUG) { + Log.d(TAG, "postRun start"); + } + int size = 0; + while (mHasGLContext && + !mPrepareRender && + (mGLViewGroup.initFirst() || + mGLViewGroup.releaseFirst() || + mPostRunning.compareAndSet(true, false)) + ) { + size++; + } + //此处忽略线程安全(辅助任务) + mPostRunning.set(false); + if (DEBUG) { + Log.d(TAG, "postRun end ,run task size " + size); + } + } +} \ No newline at end of file diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/TextureGLSurfaceViewRenderer.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/TextureGLSurfaceViewRenderer.java new file mode 100644 index 00000000..4d396934 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/controller/TextureGLSurfaceViewRenderer.java @@ -0,0 +1,135 @@ +package master.flame.danmaku.gl.glview.controller; + +import android.opengl.GLES20; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.opengles.GL10; + +import master.flame.danmaku.gl.wedget.GLHandlerSurfaceView; +import master.flame.danmaku.gl.wedget.GLShareable; + + +public class TextureGLSurfaceViewRenderer implements GLHandlerSurfaceView.Renderer, GLShareable { + private static final String TAG = "SurfaceViewRenderer"; + private GLHandlerSurfaceView mGLSurfaceView; + private GLDanmakuHandler mGLDanmakuHandler; + private boolean mCreated = false; + private boolean mPaused = true; + private boolean mDrawFinished = false; + private boolean mHide = true; + private boolean mClearFlag = false; + + public TextureGLSurfaceViewRenderer(GLHandlerSurfaceView surfaceView) { + mGLSurfaceView = surfaceView; + mGLDanmakuHandler = new GLDanmakuHandler(surfaceView); + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + mCreated = true; + mGLDanmakuHandler.onSurfaceCreate(); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + mGLDanmakuHandler.onSurfaceSizeChanged(width, height); + } + + @Override + public void onDrawFrame(GL10 gl) { + GLES20.glClearColor(0, 0, 0, 0); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + if (!mPaused && !mHide && !mClearFlag) { + mGLDanmakuHandler.onGLDrawFrame(); + } + mClearFlag = false; + synchronized (this) { + mDrawFinished = true; + notifyAll(); + } + } + + public void onResume() { + mPaused = false; + mGLDanmakuHandler.onResume(); + } + + public void onPause() { + mPaused = true; + mGLDanmakuHandler.onPause(); + } + + public void hide() { + mHide = true; + if (mCreated) { + mGLSurfaceView.requestRender(); + } + } + + public void show() { + mHide = false; + if (mCreated) { + mGLSurfaceView.requestRender(); + } + } + + public void clearNextFrame() { + mClearFlag = true; + if (mCreated) { + mGLSurfaceView.requestRender(); + } + } + + public void requestRender() { + if (mCreated) { + mGLDanmakuHandler.prepareRender(); + mGLSurfaceView.requestRender(); + } + } + + public void requestRenderSync() { + if (mCreated) { + mGLDanmakuHandler.prepareRender(); + mGLSurfaceView.requestRender(); + synchronized (this) { + while (!mDrawFinished && !mPaused) { + try { + wait(200); + } catch (InterruptedException ignore) { + } + } + mDrawFinished = false; + } + } + } + + public GLDanmakuHandler getGLDanmakuHandler() { + return mGLDanmakuHandler; + } + + @Override + public EGLContext getSharedContext() { + return mGLSurfaceView.getSharedContext(); + } + + @Override + public EGLConfig getSharedConfig() { + return mGLSurfaceView.getSharedConfig(); + } + + @Override + public int getEGLContextClientVersion() { + return mGLSurfaceView.getEGLContextClientVersion(); + } + + @Override + public int getSurfaceWidth() { + return mGLSurfaceView.getSurfaceWidth(); + } + + @Override + public int getSurfaceHeight() { + return mGLSurfaceView.getSurfaceWidth(); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLShader.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLShader.java new file mode 100644 index 00000000..1f894532 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLShader.java @@ -0,0 +1,114 @@ +package master.flame.danmaku.gl.glview.view; + +import android.content.Context; +import android.opengl.GLES20; +import android.support.annotation.NonNull; + +import java.io.InputStream; + +public final class GLShader { + private String mVertexShaderProgram; + private String mFragmentShaderProgram; + + private int mShaderProgram; + private int mVertexShader; + private int mFragmentShader; + + public GLShader(@NonNull String vertexShaderFileName, @NonNull String fragmentShaderFileName, Context context) { + this(loadFromAssetsFile(vertexShaderFileName, context), loadFromAssetsFile(fragmentShaderFileName, context)); + } + + public GLShader(@NonNull String vertexShader, @NonNull String fragmentShader) { + this.mVertexShaderProgram = vertexShader; + this.mFragmentShaderProgram = fragmentShader; + } + + public void create() { + mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, mVertexShaderProgram); + mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, mFragmentShaderProgram); + + int program = GLES20.glCreateProgram(); + if (program != 0) { + GLES20.glAttachShader(program, mVertexShader); + checkGlError("glAttachShader mVertexShader"); + GLES20.glAttachShader(program, mFragmentShader); + checkGlError("glAttachShader mFragmentShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + GLES20.glDeleteProgram(program); + program = 0; + } + } + mShaderProgram = program; + } + + + public void use() { + GLES20.glUseProgram(mShaderProgram); + checkGlError("glUseProgram " + mShaderProgram); + } + + public void unuse() { + GLES20.glUseProgram(0); + } + + public int getProgram() { + return mShaderProgram; + } + + public int getAttributeLocation(String name) { + return GLES20.glGetAttribLocation(mShaderProgram, name); + } + + public int getUniformLocation(String name) { + return GLES20.glGetUniformLocation(mShaderProgram, name); + } + + public void onDestroy() { + GLES20.glDeleteShader(mVertexShader); + GLES20.glDeleteShader(mFragmentShader); + GLES20.glDeleteProgram(mShaderProgram); + } + + + private void checkGlError(String op) { + int error = GLES20.glGetError(); + if (error != GLES20.GL_NO_ERROR) { + String msg = op + ": glError 0x" + Integer.toHexString(error); + throw new RuntimeException(msg); + } + } + + private 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) { + GLES20.glDeleteShader(shader); + shader = 0; + } + } + return shader; + } + + private static String loadFromAssetsFile(String name, Context context) { + StringBuilder result = new StringBuilder(); + try { + InputStream is = context.getResources().getAssets().open(name); + int ch; + byte[] buffer = new byte[1024]; + while (-1 != (ch = is.read(buffer))) { + result.append(new String(buffer, 0, ch)); + } + is.close(); + } catch (Exception e) { + return ""; + } + return result.toString().replaceAll("\\r\\n", "\n"); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLTextureImgProvider.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLTextureImgProvider.java new file mode 100644 index 00000000..dc781786 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLTextureImgProvider.java @@ -0,0 +1,270 @@ +package master.flame.danmaku.gl.glview.view; + +import android.graphics.Bitmap; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.os.SystemClock; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +import master.flame.danmaku.gl.Constants; +import master.flame.danmaku.gl.glview.GLUtils; +import master.flame.danmaku.gl.glview.MatrixInfo; + + +public abstract class GLTextureImgProvider { + private static final String TAG = "GLTextureImgProvider"; + private static final boolean DEBUG_CREATE_TEXTURE = Constants.DEBUG_GLVIEW_CREATE_TEXTURE; + private static final boolean DEBUG_RELEASE_TEXTURE = Constants.DEBUG_GLVIEW_RELEASE_TEXTURE; + protected GLView mGlView; + private D mData; + /** + * 是否获取到纹理图片,该值可以控制是否需要刷新content + */ + protected boolean mHaveTextureImgFetched = false; + private AtomicBoolean mImgFetching = new AtomicBoolean(false); + + /** + * 是否自动更新view的位置、大小和转转变化,也就对应着opengl中模型矩阵 + */ + private boolean mAutoFreshMatrix = true; + + /** + * 自动更新view的位置、大小和转转变化的频率,只有{@link #mAutoFreshMatrix}为true才有效
+ * 如果模型矩阵计算比较复杂,则可能会影响帧率,这个参数控制刷新模型矩阵的频率
+ * 自动刷新模型矩阵时两次刷新之间的间隔 + */ + private long mFreshMatrixGapTime = 0; + /** + * 当前状态是否需要刷新模型矩阵,只有{@link #mAutoFreshMatrix}为true才有效 + */ + private boolean mNeedFreshMatrix = true; + /** + * 上次刷新模型矩阵的时间 + */ + private long mLashFreshMatrixTime; + + /** + * 获取到纹理数据后该值存在,映射到纹理后该值为null + * 如果该值不为null,则需要加到到纹理 + */ + private Bitmap mViewBitmap; + /** + * 加载到纹理后的纹理id,没有则为0 + */ + private int mViewTextureId; + + //空间参数 + private float mTextureWidth, mTextureHeight; + protected float mDisplayWidth = -1, mDisplayHeight = -1; + /** + * view的矩阵信息 + */ + private MatrixInfo mMatrixInfo = new MatrixInfo(); + /** + * 模型矩阵 + */ + private float[] mModelMatrix = new float[16]; + + public GLTextureImgProvider() { + this(null); + } + + public GLTextureImgProvider(D data) { + setData(data); + Matrix.setIdentityM(mModelMatrix, 0); + } + + void onDisplaySizeChanged(int displayWidth, int displayHeight) { + if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) { + return; + } + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + //显示的区域大小变化了,需要重新构建模型矩阵 + mModelMatrix = resolveModelMatrix(getCachedMatrixInfo()); + } + + public void onGLDrawFrame() { + if (isTextureOutOfData()) { + freshTextureContent(); + } + } + + protected float[] resolveModelMatrix(MatrixInfo matrixInfo) { + if (matrixInfo == null) { + return mModelMatrix; + } + //此时正在坐标原点 + //缩放 + float[] scaleM = new float[16]; + Matrix.setIdentityM(scaleM, 0); + Matrix.scaleM(scaleM, 0, matrixInfo.mScaleX * mTextureWidth, matrixInfo.mScaleY * mTextureHeight, 1); + + //旋转+镜像旋转 + float[] rotateM = new float[16]; + Matrix.setIdentityM(rotateM, 0); + Matrix.rotateM(rotateM, 0, matrixInfo.mRotateX, 1, 0, 0); + Matrix.rotateM(rotateM, 0, matrixInfo.mRotateY, 0, 1, 0); + Matrix.rotateM(rotateM, 0, matrixInfo.mRotateZ, 0, 0, 1); + + //位移+镜像位移 + float[] transT = new float[16]; + Matrix.setIdentityM(transT, 0); + float transX = mTextureWidth / 2 + matrixInfo.mTransX - mDisplayWidth / 2; + float transY = -mTextureHeight / 2 - matrixInfo.mTransY + mDisplayHeight / 2; + Matrix.translateM(transT, 0, transX, transY, matrixInfo.mTransZ); + + //混合变换 + float[] resultM = new float[16]; + Matrix.setIdentityM(resultM, 0); + Matrix.multiplyMM(resultM, 0, scaleM, 0, resultM, 0); + Matrix.multiplyMM(resultM, 0, rotateM, 0, resultM, 0); + Matrix.multiplyMM(resultM, 0, transT, 0, resultM, 0); + return resultM; + } + + public final MatrixInfo getCachedMatrixInfo() { + return mMatrixInfo; + } + + public float[] getModelMatrix() { + long currentTimeMillis = SystemClock.uptimeMillis(); + if (mNeedFreshMatrix || (isAutoFreshMatrix() && currentTimeMillis - mLashFreshMatrixTime >= mFreshMatrixGapTime)) { + mMatrixInfo = getMatrixInfo(); + mNeedFreshMatrix = false; + mLashFreshMatrixTime = currentTimeMillis; + mModelMatrix = resolveModelMatrix(mMatrixInfo); + } + return mModelMatrix; + } + + public boolean isAutoFreshMatrix() { + return mAutoFreshMatrix; + } + + public void setIsAutoFreshMatrix(boolean autoFresh) { + mAutoFreshMatrix = autoFresh; + } + + /** + * 设置模型矩阵刷新的频率 + * <=0表示没错都刷新 + * 默认为0 + * + * @param fps 刷新的帧率 + */ + public void setFreshMatrixhFps(int fps) { + if (fps <= 0) { + mFreshMatrixGapTime = 0; + } else { + mFreshMatrixGapTime = 1000 / fps; + } + } + + public void setGLView(GLView glView) { + this.mGlView = glView; + } + + public void setData(D data) { + this.mData = data; + } + + public D getData() { + return this.mData; + } + + public void freshTextureContent() { + if (mImgFetching.compareAndSet(false, true)) { + try { + Bitmap textureBitmap = getTextureBitmap(); + if (textureBitmap == null || textureBitmap.isRecycled()) { + return; + } + //纹理显示的大小就是位图的大小 + // TODO: 2018/8/11 纹理压缩提高性能 + mTextureWidth = textureBitmap.getWidth(); + mTextureHeight = textureBitmap.getHeight(); + mViewBitmap = textureBitmap; + mViewTextureId = createTexture(); + mNeedFreshMatrix = true; + mHaveTextureImgFetched = true; + } finally { + mImgFetching.set(false); + } + } + } + + public int getTextureId() { + return mViewTextureId; + } + + protected int createTexture() { + int textureId = 0; + final Bitmap bitmap = mViewBitmap; + if (bitmap != null && !bitmap.isRecycled()) { + //删除之前的texture + onDestroy(); + long start = System.nanoTime(); + //位图有效 + mViewBitmap = null; + try { + textureId = GLUtils.createBitmapTexture2D(bitmap); + } catch (Exception ignore) { + return 0; + } + if (DEBUG_CREATE_TEXTURE) { + Log.i(TAG, "createViewTexture texid=" + mViewTextureId + "\t time=" + (System.nanoTime() - start)); + } + } + return textureId; + } + + protected void destroyTexture() { + if (mViewTextureId != 0) { + GLES20.glDeleteTextures(1, new int[]{mViewTextureId}, 0); + if (DEBUG_RELEASE_TEXTURE) { + Log.i(TAG, "destroyTexture texid=" + mViewTextureId); + } + mViewTextureId = 0; + } + } + + public void onDestroy() { + destroyTexture(); + } + + public float getViewElevation() { + return getMatrixInfo().mTransZ; + } + + public boolean isVisibable() { + return true; + } + + public boolean isRecyclered() { + return false; + } + + protected boolean isTextureOutOfData() { + return !mHaveTextureImgFetched; + } + + protected void onGLContextLost() { + mHaveTextureImgFetched = false; + } + + public float getTextureWidth() { + return mTextureWidth; + } + + public float getTextureHeight() { + return mTextureHeight; + } + + protected abstract Bitmap getTextureBitmap(); + + protected abstract MatrixInfo getMatrixInfo(); + +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLView.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLView.java new file mode 100644 index 00000000..72f75aa4 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLView.java @@ -0,0 +1,155 @@ +package master.flame.danmaku.gl.glview.view; + +import android.opengl.GLES20; +import android.util.Log; + +import master.flame.danmaku.gl.Constants; + +import static android.opengl.GLES20.GL_TEXTURE_2D; + +public class GLView { + + private static final String TAG = "GLView"; + private static final boolean DEBUG = Constants.DEBUG_GLVIEW; + private static final boolean DEBUG_DRAW = Constants.DEBUG_GLVIEW_DRAW; + + public GLViewGroup mGLViewGroup; + + /** + * 提供模型矩阵和纹理数据 + */ + private GLTextureImgProvider mImgProvider; + + /** + * 标记当前view是否已经被回收 + */ + private boolean mRecyclered = false; + + public GLView(GLViewGroup viewGroup) { + if (viewGroup == null) { + throw new NullPointerException("GLViewGroup 不能为空"); + } + this.mGLViewGroup = viewGroup; + } + + /** + * gl线程 + */ + public void onGLCreate() { + if (DEBUG) { + Log.i(TAG, "onGLCreate"); + } + //GLSurfaceView在pause后,大概率会造成GLContext丢失,此时需要重新初始化链接glsl并创建glview纹理 + //先销毁原先所有的gl数据 + onDestroy(); + mImgProvider.onGLContextLost(); + } + + /** + * 显示器的大小,GLSurfaceView全屏模式下,数值为手机的分辨率 + * + * @param w 宽度 + * @param h 高度 + */ + public void onDisplaySizeChanged(int w, int h) { + if (DEBUG) { + Log.i(TAG, "onDisplaySizeChanged widht=" + w + "\t height=" + h); + } + mImgProvider.onDisplaySizeChanged(w, h); + freshView(); + } + + /** + * 刷新view的内容,该方法会让view的内容提供者刷新位图
+ * 该方法一般比较耗时
+ */ + public void freshView() { + mImgProvider.freshTextureContent(); + } + + /** + * 每次渲染时实时获取view的变化情况
+ * 该方法并不会造成耗时影响
+ * + * @param auto true表示会正常刷新view的位置和变换,默认为true + */ + public void freshViewMatrixAuto(boolean auto) { + mImgProvider.setIsAutoFreshMatrix(auto); + } + + /** + * 是否自动刷新view变换 + * + * @return true表示自动刷新 + * @see #freshViewMatrixAuto(boolean) + */ + public boolean isFreshViewMatrixAuto() { + return mImgProvider.isAutoFreshMatrix(); + } + + /** + * view的海拔,也是view的z轴值 + * + * @return iew的海拔 + */ + public float getViewElevation() { + return mImgProvider.getViewElevation(); + } + + /** + * 获取纹理位图提供者 + * + * @return GLTextureImgProvider + */ + public GLTextureImgProvider getImgProvider() { + return this.mImgProvider; + } + + public void setImgProvider(GLTextureImgProvider provider) { + this.mImgProvider = provider; + } + + /** + * 绘制view + * gl线程 + */ + public void onDrawFrame(int glHModel) { + long start = System.nanoTime(); + boolean succeed = false; + mImgProvider.onGLDrawFrame(); + int textureId = mImgProvider.getTextureId(); + if (GLES20.glIsTexture(textureId)) { + succeed = true; + GLES20.glUniformMatrix4fv(glHModel, 1, false, mImgProvider.getModelMatrix(), 0); + GLES20.glBindTexture(GL_TEXTURE_2D, textureId); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + } + if (DEBUG_DRAW) { + Log.i(TAG, "onDrawFrame succeed = " + succeed + "\t time=" + (System.nanoTime() - start)); + } + } + + public boolean isVisibale() { + return mImgProvider.isVisibable(); + } + + public void setRecyclered(boolean recyclered) { + this.mRecyclered = recyclered; + } + + public boolean isRecyclered() { + return mRecyclered || mImgProvider.isRecyclered(); + } + + public void onDestroy() { + mImgProvider.onDestroy(); + } + + public float getViewWidth() { + return mImgProvider.getTextureWidth(); + } + + public float getViewHeight() { + return mImgProvider.getTextureHeight(); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroup.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroup.java new file mode 100644 index 00000000..c7e73908 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroup.java @@ -0,0 +1,415 @@ +package master.flame.danmaku.gl.glview.view; + +import android.content.Context; +import android.opengl.GLES20; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Queue; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentLinkedQueue; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.gl.Constants; +import master.flame.danmaku.gl.glview.GLProgram; +import master.flame.danmaku.gl.glview.view.provider.GLDanmakuProvider; +import master.flame.danmaku.gl.utils.SpeedsMeasurement; + +import static android.opengl.GLES20.GL_BLEND; +import static android.opengl.GLES20.GL_CULL_FACE; +import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA; +import static android.opengl.GLES20.GL_SRC_ALPHA; +import static android.opengl.GLES20.GL_TEXTURE0; +import static android.opengl.GLES20.GL_TEXTURE_2D; + +public class GLViewGroup { + private static final String TAG = "GLViewGroup"; + private static final boolean DEBUG = Constants.DEBUG_GLVIEWGROUP; + private static final boolean DEBUG_DRAW = Constants.DEBUG_GLVIEWGROUP_DRAW; + private static final boolean DEBUG_INIT = Constants.DEBUG_GLVIEWGROUP_INIT; + private static final boolean DEBUG_RELEASE = Constants.DEBUG_GLVIEWGROUP_RELEASE; + private static final boolean DEBUG_ADD = Constants.DEBUG_GLVIEWGROUP_ADD; + private SpeedsMeasurement mCreateSpeeds = new SpeedsMeasurement("mCreateSpeeds"); + private SpeedsMeasurement mDrawSpeeds = new SpeedsMeasurement("mDrawSpeeds"); + private SpeedsMeasurement mReleaseSpeeds = new SpeedsMeasurement("mReleaseSpeeds"); + + //纹理映射 + private static final float[] sPosition = { + -0.5f, 0.5f, + -0.5f, -0.5f, + 0.5f, 0.5f, + 0.5f, -0.5f + }; + + private static final float[] sCoordinate = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + }; + + private static final FloatBuffer sPositionBuffer; + private static final FloatBuffer sCoordBuffer; + + static { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(sPosition.length * 4); + byteBuffer.order(ByteOrder.nativeOrder()); + sPositionBuffer = byteBuffer.asFloatBuffer(); + sPositionBuffer.put(sPosition); + sPositionBuffer.position(0); + + byteBuffer = ByteBuffer.allocateDirect(sCoordinate.length * 4); + byteBuffer.order(ByteOrder.nativeOrder()); + sCoordBuffer = byteBuffer.asFloatBuffer(); + sCoordBuffer.put(sCoordinate); + sCoordBuffer.position(0); + } + + /** + * 观察矩阵和投影矩阵等整体变换 + */ + private GLViewGroupMatrixProvider mMatrixProvider; + /** + * glsl管理 + */ + private GLProgram mGlProgram; + + private float mAlpha = 1f; + private int mDisplayWidth, mDisplayHeight; + /** + * 如果不开启深度测试,则需要通过弹幕排序来实现层级关系 + */ + private Comparator zComparator = new Comparator() { + @Override + public int compare(GLView o1, GLView o2) { + if (o1.getImgProvider().getData() == o2.getImgProvider().getData()) { + //同一个弹幕,此代码快不应该出现,否则逻辑可能有问题Override + Log.w(TAG, "zComparator compare the same damaku override"); + return 0; + } + return o1.getViewElevation() >= o2.getViewElevation() ? 1 : -1; + } + }; + + /** + * BaseDanmaku add->mNewDanmaku initFirst-> mRunningViews timeout->mRemovingViews releaseFirst->mRemovedViews + * 刚添加的弹幕集合,集合中的弹幕经过变换成gl中的纹理后会放入到mRunningViews集合中显示 + * 此处特意没有使用{@link master.flame.danmaku.danmaku.model.Danmaku}管理 + */ + private final Queue mNewDanmaku = new ConcurrentLinkedQueue<>(); + /** + * 需要渲染的弹幕库,按照zComparator的排序绘制 + */ + private final Collection mRunningViews = new TreeSet<>(zComparator); + /** + * 已经被删除了的弹幕库,绝大部分因为弹幕库超过了屏幕区域 + * 在此集合中的弹幕库的纹理还没有被销毁,需要在随后被销毁调,否则有内存溢出的可能 + */ + private final Queue mRemovingViews = new ConcurrentLinkedQueue<>(); + /** + * 已经被删除了的弹幕库,mRemovingViews中的弹幕库在纹理被销毁后放入次集合,后面再重复利用放入到mRunningViews集合中显示 + */ + private final Queue mRemovedViews = new ConcurrentLinkedQueue<>(); + + public GLViewGroup(Context context) { + mGlProgram = new GLProgram(context); + mMatrixProvider = new GLViewGroupMatrixProvider(); + } + + /** + * gl线程 + * Called when the surface is created or recreated. + * Called when the rendering thread starts and whenever the EGL context is lost. The EGL context will typically be lost when the Android device awakes after going to sleep. + */ + public void onGLSurfaceViewCreate() { + if (DEBUG) { + Log.i(TAG, "onGLSurfaceViewCreate"); + } + //opengl create 或者 recreate 时 glcontext 很容易丢失, + //重新加载一次glsl + mGlProgram.load(); + for (GLView glView : mRunningViews) { + glView.onGLCreate(); + } + } + + /** + * gl线程 + */ + public void onDisplaySizeChanged(int width, int height) { + if (DEBUG) { + Log.i(TAG, "onDisplaySizeChanged width=" + width + "\t height=" + height); + } + GLES20.glViewport(0, 0, width, height); + mDisplayWidth = width; + mDisplayHeight = height; + mMatrixProvider.onDisplaySizeChanged(width, height); + for (GLView glView : mRunningViews) { + glView.onDisplaySizeChanged(width, height); + } + } + + /** + * gl线程 + */ + public void onGLDrawFrame() { + if (DEBUG_DRAW) { + //新增 + mCreateSpeeds.taskStart(); + int addSize = initNew(); + mCreateSpeeds.taskEnd(); + //绘制 + mDrawSpeeds.taskStart(); + int drawSize = drawRunning(); + mDrawSpeeds.taskEnd(); + //移除 + mReleaseSpeeds.taskStart(); + int removeSize = releaseRemoved(); + mReleaseSpeeds.taskEnd(); + + Log.i(TAG, "onGLDrawFrame " + + "new=" + mNewDanmaku.size() + " " + + "run=" + mRunningViews.size() + " " + + "rmv=" + mRemovingViews.size() + " " + + "as=" + addSize + " " + + "ds=" + drawSize + " " + + "rs=" + removeSize + " "); + } else { + initNew(); + drawRunning(); + releaseRemoved(); + } + } + + /** + * gl线程 + * + * @return 添加的个数 + */ + private int initNew() { + int addSize = 0; + while (initFirst()) { + addSize++; + } + return addSize; + } + + /** + * gl线程 + * + * @return 是否成功 + */ + public boolean initFirst() { + int loopTime = 0; + boolean reuseGlView = true; + BaseDanmaku danmaku; + while ((danmaku = mNewDanmaku.poll()) != null && danmaku.isTimeOut()) { + loopTime++; + } + if (danmaku == null) { + return false; + } + GLView recyclerView = mRemovedViews.poll(); + if (recyclerView == null) { + reuseGlView = false; + recyclerView = new GLView(this); + } + recyclerView.setRecyclered(false); + GLTextureImgProvider imgProvider = recyclerView.getImgProvider(); + if (imgProvider == null || !(imgProvider instanceof GLDanmakuProvider)) { + imgProvider = new GLDanmakuProvider(); + } + imgProvider.setData(danmaku); + imgProvider.setGLView(recyclerView); + recyclerView.setImgProvider(imgProvider); + + //重新走生命周期 + recyclerView.onGLCreate(); + recyclerView.onDisplaySizeChanged(mDisplayWidth, mDisplayHeight); + //添加到需要渲染的弹幕的集合中,等待下一次渲染 + mRunningViews.add(recyclerView); + + if (DEBUG_INIT) { + Log.i(TAG, "init texture id=" + danmaku.id + "\tloopTime=" + loopTime + "\t reuseGlView=" + reuseGlView); + } + return true; + } + + /** + * gl线程 + * + * @return 绘制的个数 + */ + private int drawRunning() { + if (mRunningViews.isEmpty()) { + return 0; + } + GLES20.glEnable(GL_BLEND); + GLES20.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GLES20.glEnable(GL_CULL_FACE); + int drawSize = 0; + Iterator iterator = mRunningViews.iterator(); + mGlProgram.use(); + boolean isFirst = true; + while (iterator.hasNext()) { + GLView next = iterator.next(); + if (next.isRecyclered()) { + //已经标记为移除 + mRemovingViews.add(next); + iterator.remove(); + continue; + } + if (!next.isVisibale()) { + continue; + } + if (isFirst) { + isFirst = false; + GLES20.glUniformMatrix4fv(mGlProgram.glHProject, 1, false, mMatrixProvider.getProjectMatrix(), 0); + GLES20.glUniformMatrix4fv(mGlProgram.glHView, 1, false, mMatrixProvider.getViewMatrix(), 0); + GLES20.glUniform1f(mGlProgram.glAlpha, mAlpha); + GLES20.glEnableVertexAttribArray(mGlProgram.glHPosition); + GLES20.glEnableVertexAttribArray(mGlProgram.glHCoordinate); + GLES20.glVertexAttribPointer(mGlProgram.glHPosition, 2, GLES20.GL_FLOAT, false, 0, sPositionBuffer); + GLES20.glVertexAttribPointer(mGlProgram.glHCoordinate, 2, GLES20.GL_FLOAT, false, 0, sCoordBuffer); + GLES20.glActiveTexture(GL_TEXTURE0); + GLES20.glUniform1i(mGlProgram.glHTexture, 0); + } + next.onDrawFrame(mGlProgram.glHModel); + drawSize++; + } + GLES20.glBindTexture(GL_TEXTURE_2D, 0); + GLES20.glDisable(GL_BLEND); + GLES20.glDisable(GL_CULL_FACE); + mGlProgram.unuse(); + return drawSize; + } + + /** + * gl线程 + * + * @return 释放的个数 + */ + private int releaseRemoved() { + int removeSize = 0; + while (releaseFirst()) { + removeSize++; + } + return removeSize; + } + + /** + * gl线程 + * + * @return 成功移除了第一个 + */ + public boolean releaseFirst() { + GLView first = mRemovingViews.poll(); + if (first == null) { + return false; + } + first.onDestroy(); + if (DEBUG_RELEASE) { + BaseDanmaku data = (BaseDanmaku) first.getImgProvider().getData(); + Log.i(TAG, "release id=" + data.id); + } + mRemovedViews.offer(first); + return true; + } + + public boolean isEmpty() { + return mRunningViews.isEmpty() && mRemovingViews.isEmpty() && mNewDanmaku.isEmpty(); + } + + public void addDanmu(BaseDanmaku danmaku) { + if (danmaku == null) { + return; + } + if (DEBUG_ADD) { + Log.i(TAG, "addDanmu id=" + danmaku.id); + } + mNewDanmaku.offer(danmaku); + } + + public void removeView(Object view) { + if (view == null) { + return; + } + + if (view instanceof GLView) { + //gl线程 + if (DEBUG) { + BaseDanmaku data = (BaseDanmaku) ((GLView) view).getImgProvider().getData(); + Log.i(TAG, "removeView id=" + data.id); + } + //设置回收状态,在下一次绘制时会被移除 + ((GLView) view).setRecyclered(true); + } + if (view instanceof BaseDanmaku) { + if (DEBUG) { + Log.i(TAG, "removeView id=" + ((BaseDanmaku) view).id); + } + if (!mNewDanmaku.remove(view)) { + for (GLView glView : new ArrayList<>(mRunningViews)) { + if (glView.getImgProvider().getData() == view) { + glView.setRecyclered(true); + return; + } + } + } + } + } + + public void removeAll() { + if (DEBUG) { + Log.i(TAG, "removeAll"); + } + mNewDanmaku.clear(); + for (GLView glView : new ArrayList<>(mRunningViews)) { + glView.setRecyclered(true); + } + } + + public void freshView(Object view) { + if (view == null) { + return; + } + if (view instanceof GLView) { + //gl线程 + ((GLView) view).freshView(); + return; + } + + for (GLView next : new ArrayList<>(mRunningViews)) { + if (next.getImgProvider().getData() == view) { + next.freshView(); + return; + } + } + } + + public void setViewsReverseState(boolean reverseHorizontal, boolean reverseVertical) { + mMatrixProvider.setViewsReverseState(reverseHorizontal, reverseVertical); + } + + public void setAlpha(float alpha) { + this.mAlpha = alpha; + } + + public float getAlpha() { + return mAlpha; + } + + public float getDisplayWidth() { + return mDisplayWidth; + } + + public float getDisplayHeight() { + return mDisplayHeight; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroupMatrixProvider.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroupMatrixProvider.java new file mode 100644 index 00000000..9709a1a5 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/GLViewGroupMatrixProvider.java @@ -0,0 +1,89 @@ +package master.flame.danmaku.gl.glview.view; + +import android.opengl.Matrix; + +public class GLViewGroupMatrixProvider { + /** + * 透视矩阵中的视野角度 + */ + private static float DEFAULT_VIEW_FOVY = 45; + private static float DEFAULT_NEAR_DISTANCE = 0.1f; + private static float DEFAULT_FAR_DISTANCE = (float) (360f / Math.tan(Math.PI * DEFAULT_VIEW_FOVY / 360)); + + + private int mDisplayWidth, mDisplayHeight; + //镜像操作 + private boolean mReverseHorizontal, mReverseVertical; + + /** + * 观察矩阵 + */ + private float[] mViewMatrix = new float[16]; + /** + * 投影矩阵 + */ + private float[] mProjectMatrix = new float[16]; + /** + * 透视投影的参数 + */ + private float mViewFOVY = DEFAULT_VIEW_FOVY; + private float mViewDistance = DEFAULT_FAR_DISTANCE; + private float mViewNearDistance = DEFAULT_NEAR_DISTANCE; + + + public GLViewGroupMatrixProvider() { + Matrix.setIdentityM(mViewMatrix, 0); + Matrix.setIdentityM(mProjectMatrix, 0); + } + + + public void onDisplaySizeChanged(int width, int height) { + mDisplayWidth = width; + mDisplayHeight = height; + mViewDistance = (float) mDisplayHeight / (2f * (float) Math.tan(Math.PI * mViewFOVY / 360)); + mViewMatrix = resolveViewMatrix(); + mProjectMatrix = resolveProjectMatrix(); + } + + private float[] resolveViewMatrix() { + //设置相机位置,放在中间 + // TODO: 2018/8/10 翻转操作 + float[] viewMatrix = new float[16]; + Matrix.setIdentityM(viewMatrix, 0); + Matrix.setLookAtM(viewMatrix, 0, 0, 0, mViewDistance, 0, 0, 0f, 0f, 1.0f, 0.0f); + return viewMatrix; + } + + private float[] resolveProjectMatrix() { + //投影矩阵 + float[] projectMatrix = new float[16]; + Matrix.setIdentityM(projectMatrix, 0); + Matrix.perspectiveM(projectMatrix, 0, mViewFOVY, (float) mDisplayWidth / (float) mDisplayHeight, mViewNearDistance, mViewDistance * 1.01f); + return projectMatrix; + } + + public float[] getViewMatrix() { + return mViewMatrix; + } + + public float[] getProjectMatrix() { + return mProjectMatrix; + } + + public void setViewsReverseState(boolean reverseHorizontal, boolean reverseVertical) { + if (reverseHorizontal == mReverseHorizontal && reverseVertical == mReverseVertical) { + return; + } + mReverseHorizontal = reverseHorizontal; + mReverseVertical = reverseVertical; + mViewMatrix = resolveViewMatrix(); + } + + public boolean isReverseHorizontal() { + return mReverseHorizontal; + } + + public boolean isReverseVertical() { + return mReverseVertical; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/provider/GLDanmakuProvider.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/provider/GLDanmakuProvider.java new file mode 100644 index 00000000..189c9f51 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/glview/view/provider/GLDanmakuProvider.java @@ -0,0 +1,129 @@ +package master.flame.danmaku.gl.glview.view.provider; + +import android.graphics.Bitmap; + +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.gl.glview.MatrixInfo; +import master.flame.danmaku.gl.glview.view.GLTextureImgProvider; +import master.flame.danmaku.gl.glview.view.GLView; + +/** + * 创建人:yangzhiqian + * 创建时间:2018/7/11 15:57 + * 备注: + */ +public class GLDanmakuProvider extends GLTextureImgProvider { + + @Override + public void setGLView(GLView glView) { + super.setGLView(glView); + } + + @Override + protected Bitmap getTextureBitmap() { + //不需要上层创建纹理 + return null; + } + + @Override + protected float[] resolveModelMatrix(MatrixInfo matrixInfo) { + // TODO: 2018/8/11 rotate + float sx = matrixInfo.mScaleX * getTextureWidth(); + float sy = matrixInfo.mScaleY * getTextureHeight(); + float sz = 1; + float tx = getTextureWidth() / 2 + matrixInfo.mTransX - mDisplayWidth / 2; + float ty = -getTextureHeight() / 2 - matrixInfo.mTransY + mDisplayHeight / 2; + float tz = matrixInfo.mTransZ; + float[] resultM = new float[16]; + /** + * 平移矩阵 缩放矩阵 + * 1 0 0 tx sx 0 0 0 + * 0 1 0 ty 0 sy 0 0 + * 0 0 1 tz 0 0 sz 0 + * 0 0 0 1 0 0 0 1 + * + * + * 缩放+平移 + * sx 0 0 tx + * 0 sy 0 ty + * 0 0 sz tz + * 0 0 0 1 + */ + resultM[0] = sx; + resultM[1] = 0; + resultM[2] = 0; + resultM[3] = 0; + + resultM[4] = 0; + resultM[5] = sy; + resultM[6] = 0; + resultM[7] = 0; + + resultM[8] = 0; + resultM[9] = 0; + resultM[10] = sz; + resultM[11] = 0; + + resultM[12] = tx; + resultM[13] = ty; + resultM[14] = tz; + resultM[15] = 1; + + return resultM; + + } + + @Override + protected MatrixInfo getMatrixInfo() { + MatrixInfo matrixInfo = new MatrixInfo(); + BaseDanmaku data = getData(); + matrixInfo.mTransX = data.getLeft(); + matrixInfo.mTransY = data.getTop(); + return matrixInfo; + } + + @Override + public boolean isVisibable() { + return getData() != null && getData().isShown(); + } + + @Override + public boolean isRecyclered() { + return getData() == null || getData().isTimeOut(); + } + + @Override + public int getTextureId() { + BaseDanmaku data = getData(); + if (data == null || data.mGLTextureId == 0) { + return super.getTextureId(); + } + return data.mGLTextureId; + } + + @Override + public float getTextureWidth() { + BaseDanmaku data = getData(); + return data == null ? 0 : data.mTextureWidth; + } + + @Override + public float getTextureHeight() { + BaseDanmaku data = getData(); + return data == null ? 0 : data.mTextureHeight; + } + + @Override + protected int createTexture() { + return 0; + } + + @Override + protected void destroyTexture() { + } + + @Override + protected boolean isTextureOutOfData() { + return false; + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/utils/SpeedsMeasurement.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/utils/SpeedsMeasurement.java new file mode 100644 index 00000000..24fd6f2f --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/utils/SpeedsMeasurement.java @@ -0,0 +1,55 @@ +package master.flame.danmaku.gl.utils; + +import android.util.Log; + +public class SpeedsMeasurement { + private String mTag; + private int index = 0; + private int lastIndex = 0; + private long lastTime; + private int seconds = 0; + + public SpeedsMeasurement(String tag) { + mTag = tag; + } + + public void dot() { + long curr = System.currentTimeMillis(); + if (curr - lastTime > 1000) { + lastTime = curr; + int speed = index - lastIndex; + lastIndex = index; + Log.d(mTag, + "sec=" + seconds + + "\tspeed=" + speed + + "\t avg=" + index / (seconds == 0 ? 1 : seconds) + + "\tindex=" + index); + seconds++; + } + index++; + } + + private static final int MAX_RECORDER = 100; + private final long[] mTimes = new long[MAX_RECORDER]; + private int mTaskIndex = 0; + private long mLastStart = 0; + + public void taskStart() { + mLastStart = System.nanoTime(); + } + + public void taskEnd() { + long current = System.nanoTime(); + long spends = current - mLastStart; + mTimes[mTaskIndex % MAX_RECORDER] = spends; + mTaskIndex++; + if (mTaskIndex % MAX_RECORDER == 0) { + long totalTimes = 0; + for (long mTime : mTimes) { + totalTimes += mTime; + } + Log.d(mTag, "avg=" + totalTimes / MAX_RECORDER + + "\ttotalIndex=" + mTaskIndex); + } + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLHandlerSurfaceView.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLHandlerSurfaceView.java new file mode 100644 index 00000000..9f79a295 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLHandlerSurfaceView.java @@ -0,0 +1,1423 @@ + +package master.flame.danmaku.gl.wedget; + +import android.content.Context; +import android.opengl.EGL14; +import android.opengl.EGLExt; +import android.opengl.GLDebugHelper; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import java.io.Writer; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +import master.flame.danmaku.gl.Constants; + +public class GLHandlerSurfaceView extends SurfaceView implements SurfaceHolder.Callback2, GLShareable { + private final static String TAG = "GLHandlerSurfaceView"; + public int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private final static long NORMAL_WAIT_TIME = 20; + private final static boolean DEBUG = Constants.DEBUG_GLHANDLERSURFACEVIEW; + private final static boolean LOG_ATTACH_DETACH = false; + private final static boolean LOG_THREADS = false; + private final static boolean LOG_PAUSE_RESUME = false; + private final static boolean LOG_SURFACE = false; + private final static boolean LOG_RENDERER = false; + private final static boolean LOG_RENDERER_DRAW_FRAME = false; + private final static boolean LOG_EGL = false; + + public final static int RENDERMODE_WHEN_DIRTY = 0; + public final static int RENDERMODE_CONTINUOUSLY = 1; + public final static int DEFAULT_RENDERMODE = RENDERMODE_CONTINUOUSLY; + + + public final static int DEBUG_CHECK_GL_ERROR = 1; + public final static int DEBUG_LOG_GL_CALLS = 2; + + private static final int MSG_RUN = 1; + private static final int MSG_SCHEDULE = 2; + private final Object mMonitor = new Object(); + + private GLHandler mHandler; + private Looper mWorkLooper; + private HandlerThread mHandlerThread; + private int mThreadPriority = Thread.NORM_PRIORITY; + private boolean mAllowMainThreadLooper = false; + + private final WeakReference mThisWeakRef = + new WeakReference<>(this); + private boolean mDetached; + private Renderer mRenderer; + private EGLConfigChooser mEGLConfigChooser; + private EGLContextFactory mEGLContextFactory; + private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + private GLWrapper mGLWrapper; + private int mDebugFlags; + private int mEGLContextClientVersion; + private boolean mPreserveEGLContextOnPause; + + public GLHandlerSurfaceView(Context context) { + super(context); + init(); + } + + public GLHandlerSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + } + + @Override + protected void finalize() throws Throwable { + if (DEBUG) { + Log.i(TAG, "finalize"); + } + try { + exit(); + } finally { + super.finalize(); + } + } + + public void exit() { + boolean exitSucceed = false; + long start = System.nanoTime(); + if (mHandler != null) { + mHandler.requestExitAndWait(); + if (mHandlerThread != null) { + mHandlerThread.quit(); + mWorkLooper = null; + } + mHandler = null; + exitSucceed = true; + } + if (DEBUG) { + Log.i(TAG, "exit state=" + exitSucceed + "\t time=" + (System.nanoTime() - start)); + } + } + + public void setGLWrapper(GLWrapper glWrapper) { + mGLWrapper = glWrapper; + } + + public void setDebugFlags(int debugFlags) { + mDebugFlags = debugFlags; + } + + public int getDebugFlags() { + return mDebugFlags; + } + + public void setPreserveEGLContextOnPause(boolean preserveOnPause) { + mPreserveEGLContextOnPause = preserveOnPause; + } + + public boolean getPreserveEGLContextOnPause() { + return mPreserveEGLContextOnPause; + } + + public void setAllowMainThreadLooper(boolean allow) { + this.mAllowMainThreadLooper = allow; + } + + public boolean isAllowMainThreadLooper() { + return this.mAllowMainThreadLooper; + } + + public void setThreadPriority(int priority) { + checkRenderThreadState(); + this.mThreadPriority = priority; + } + + public int getThreadPriority() { + return this.mThreadPriority; + } + + public void setRenderer(Renderer renderer) { + setRenderer(renderer, null); + } + + public void setRenderer(Renderer renderer, Looper workLooper) { + checkRenderThreadState(); + if (DEBUG) { + Log.i(TAG, "setRenderer workLooper = " + workLooper); + } + if (mEGLConfigChooser == null) { + mEGLConfigChooser = new SimpleEGLConfigChooser(true); + } + if (mEGLContextFactory == null) { + mEGLContextFactory = new DefaultContextFactory(); + } + if (mEGLWindowSurfaceFactory == null) { + mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); + } + mRenderer = renderer; + if (workLooper == null) { + mHandlerThread = new HandlerThread(getClass().getSimpleName(), mThreadPriority); + mHandlerThread.start(); + workLooper = mHandlerThread.getLooper(); + } + if (!mAllowMainThreadLooper && workLooper == Looper.getMainLooper()) { + throw new IllegalArgumentException("main looper is not allowed to be gl thread"); + } + mWorkLooper = workLooper; + mHandler = new GLHandler(mWorkLooper, mThisWeakRef); + mHandler.obtainMessage(MSG_RUN).sendToTarget(); + } + + public void setEGLContextFactory(EGLContextFactory factory) { + checkRenderThreadState(); + mEGLContextFactory = factory; + } + + public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { + checkRenderThreadState(); + mEGLWindowSurfaceFactory = factory; + } + + public void setEGLConfigChooser(EGLConfigChooser configChooser) { + checkRenderThreadState(); + mEGLConfigChooser = configChooser; + } + + public void setEGLConfigChooser(boolean needDepth) { + setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); + } + + public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, + blueSize, alphaSize, depthSize, stencilSize)); + } + + public void setEGLContextClientVersion(int version) { + checkRenderThreadState(); + mEGLContextClientVersion = version; + } + + public void setRenderMode(int renderMode) { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer before call setRenderMode"); + return; + } + if (DEBUG) { + Log.i(TAG, "setRenderMode renderMode = " + renderMode); + } + mHandler.setRenderMode(renderMode); + } + + public int getRenderMode() { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer before call getRenderMode"); + return DEFAULT_RENDERMODE; + } + return mHandler.getRenderMode(); + } + + public void requestRender() { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer before call requestRender"); + return; + } + if (DEBUG) { + Log.i(TAG, "requestRender"); + } + mHandler.requestRender(); + } + + private void checkRenderThreadState() { + if (mHandler != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + if (mHandler == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "surfaceCreated"); + } + mHandler.surfaceCreated(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (mHandler == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "surfaceDestroyed"); + } + mHandler.surfaceDestroyed(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + if (mHandler == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "surfaceChanged"); + } + mHandler.onWindowResize(w, h); + } + + @Override + public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { + if (mHandler == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "surfaceRedrawNeededAsync"); + } + mHandler.requestRenderAndNotify(finishDrawing); + } + + @Deprecated + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + + } + + public void onPause() { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer"); + return; + } + if (DEBUG) { + Log.i(TAG, "onPause"); + } + mHandler.onPause(); + } + + public void onResume() { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer"); + return; + } + if (DEBUG) { + Log.i(TAG, "onResume"); + } + mHandler.onResume(); + } + + public void queueEvent(Runnable r) { + if (mHandler == null) { + Log.w(TAG, "did you have called the setRenderer before call queueEvent"); + return; + } + if (DEBUG) { + Log.i(TAG, "queueEvent"); + } + mHandler.queueEvent(r); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); + } + + if (mDetached && (mRenderer != null)) { + int renderMode = DEFAULT_RENDERMODE; + if (mHandler != null) { + renderMode = mHandler.getRenderMode(); + } + setRenderer(mRenderer, mWorkLooper); + setRenderMode(renderMode); + } + mDetached = false; + } + + @Override + protected void onDetachedFromWindow() { + if (LOG_ATTACH_DETACH) { + Log.d(TAG, "onDetachedFromWindow"); + } + exit(); + mDetached = true; + super.onDetachedFromWindow(); + } + + @Override + public EGLContext getSharedContext() { + GLHandler handler = mHandler; + if (handler == null) { + return null; + } + return handler.mEglHelper.mEglContext; + } + + @Override + public EGLConfig getSharedConfig() { + GLHandler handler = mHandler; + if (handler == null) { + return null; + } + return handler.mEglHelper.mEglConfig; + } + + @Override + public int getEGLContextClientVersion() { + return mEGLContextClientVersion; + } + + @Override + public int getSurfaceWidth() { + return getWidth(); + } + + @Override + public int getSurfaceHeight() { + return getHeight(); + } + + //==================================inner classes or ininterfaces========================================================== + + static class GLHandler extends Handler { + WeakReference mWeakReference; + private EglHelper mEglHelper; + + private boolean mShouldExit; + private boolean mExited; + private boolean mRequestPaused; + private boolean mPaused; + private boolean mHasSurface; + private boolean mSurfaceIsBad; + private boolean mWaitingForSurface; + private boolean mHaveEglContext; + private boolean mHaveEglSurface; + private boolean mFinishedCreatingEglSurface; + private boolean mShouldReleaseEglContext; + private int mWidth; + private int mHeight; + private int mRenderMode; + private boolean mRequestRender; + private boolean mWantRenderNotification; + private boolean mRenderComplete; + private ArrayList mEventQueue = new ArrayList(); + private boolean mSizeChanged = true; + private Runnable mFinishDrawingRunnable = null; + + GL10 gl = null; + boolean createEglContext = false; + boolean createEglSurface = false; + boolean createGlInterface = false; + boolean lostEglContext = false; + boolean sizeChanged = false; + boolean wantRenderNotification = false; + boolean doRenderNotification = false; + boolean askedToReleaseEglContext = false; + int w = 0; + int h = 0; + Runnable finishDrawingRunnable = null; + + private long mLastRun = 0; + + GLHandler(Looper looper, WeakReference glSurfaceViewWeakRef) { + super(looper); + mWeakReference = glSurfaceViewWeakRef; + mEglHelper = new EglHelper(mWeakReference); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + if (glHandlerSurfaceView == null) { + //view 被销毁了 + exit(); + return; + } + + try { + while (true) { + synchronized (glHandlerSurfaceView.mMonitor) { + while (true) { + if (mShouldExit) { + exit(); + return; + } + + if (!mEventQueue.isEmpty()) { + Runnable remove = mEventQueue.remove(0); + if (remove != null) { + remove.run(); + } + continue; + } + + // Update the pause state. + boolean pausing = false; + if (mPaused != mRequestPaused) { + pausing = mRequestPaused; + mPaused = mRequestPaused; + notifyMonitor(); + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + Thread.currentThread().getId()); + } + } + + // Do we need to give up the EGL context? + if (mShouldReleaseEglContext) { + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because asked to tid=" + Thread.currentThread().getId()); + } + stopEglSurfaceLocked(); + stopEglContextLocked(); + mShouldReleaseEglContext = false; + askedToReleaseEglContext = true; + } + + // Have we lost the EGL context? + if (lostEglContext) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + lostEglContext = false; + } + + // When pausing, release the EGL surface: + if (pausing && mHaveEglSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL surface because paused tid=" + Thread.currentThread().getId()); + } + stopEglSurfaceLocked(); + } + + // When pausing, optionally release the EGL Context: + if (pausing && mHaveEglContext) { + if (!glHandlerSurfaceView.mPreserveEGLContextOnPause) { + stopEglContextLocked(); + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because paused tid=" + Thread.currentThread().getId()); + } + } + } + + // Have we lost the SurfaceView surface? + if ((!mHasSurface) && (!mWaitingForSurface)) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface lost tid=" + Thread.currentThread().getId()); + } + if (mHaveEglSurface) { + stopEglSurfaceLocked(); + } + mWaitingForSurface = true; + mSurfaceIsBad = false; + notifyMonitor(); + } + + // Have we acquired the surface view surface? + if (mHasSurface && mWaitingForSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface acquired tid=" + Thread.currentThread().getId()); + } + mWaitingForSurface = false; + notifyMonitor(); + } + + if (doRenderNotification) { + if (LOG_SURFACE) { + Log.i("GLThread", "sending render notification tid=" + Thread.currentThread().getId()); + } + mWantRenderNotification = false; + doRenderNotification = false; + mRenderComplete = true; + notifyMonitor(); + } + + if (mFinishDrawingRunnable != null) { + finishDrawingRunnable = mFinishDrawingRunnable; + mFinishDrawingRunnable = null; + } + + // Ready to draw? + if (readyToDraw()) { + + // If we don't have an EGL context, try to acquire one. + if (!mHaveEglContext) { + if (askedToReleaseEglContext) { + askedToReleaseEglContext = false; + } else { + try { + mEglHelper.start(); + } catch (RuntimeException t) { + notifyMonitor(); + throw t; + } + mHaveEglContext = true; + createEglContext = true; + + notifyMonitor(); + } + } + + if (mHaveEglContext && !mHaveEglSurface) { + mHaveEglSurface = true; + createEglSurface = true; + createGlInterface = true; + sizeChanged = true; + } + + if (mHaveEglSurface) { + if (mSizeChanged) { + sizeChanged = true; + w = mWidth; + h = mHeight; + mWantRenderNotification = true; + if (LOG_SURFACE) { + Log.i("GLThread", + "noticing that we want render notification tid=" + + Thread.currentThread().getId()); + } + + // Destroy and recreate the EGL surface. + createEglSurface = true; + + mSizeChanged = false; + } + mRequestRender = false; + notifyMonitor(); + if (mWantRenderNotification) { + wantRenderNotification = true; + } + scheduleNextRun(); + break; + } + } else { + if (finishDrawingRunnable != null) { + Log.w(TAG, "Warning, !readyToDraw() but waiting for " + + "draw finished! Early reporting draw finished."); + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } + // By design, this is the only place in a GLThread thread where we wait(). + if (LOG_THREADS) { + Log.i("GLThread", "waiting tid=" + Thread.currentThread().getId() + + " mHaveEglContext: " + mHaveEglContext + + " mHaveEglSurface: " + mHaveEglSurface + + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface + + " mPaused: " + mPaused + + " mHasSurface: " + mHasSurface + + " mSurfaceIsBad: " + mSurfaceIsBad + + " mWaitingForSurface: " + mWaitingForSurface + + " mWidth: " + mWidth + + " mHeight: " + mHeight + + " mRequestRender: " + mRequestRender + + " mRenderMode: " + mRenderMode); + } + return; + } + } // end of synchronized(sGLThreadManager) + + + if (createEglSurface) { + if (LOG_SURFACE) { + Log.w("GLThread", "egl createSurface"); + } + if (mEglHelper.createSurface()) { + synchronized (glHandlerSurfaceView.mMonitor) { + mFinishedCreatingEglSurface = true; + glHandlerSurfaceView.mMonitor.notifyAll(); + } + } else { + synchronized (glHandlerSurfaceView.mMonitor) { + mFinishedCreatingEglSurface = true; + mSurfaceIsBad = true; + glHandlerSurfaceView.mMonitor.notifyAll(); + } + continue; + } + createEglSurface = false; + } + + if (createGlInterface) { + gl = (GL10) mEglHelper.createGL(); + + createGlInterface = false; + } + + if (createEglContext) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceCreated"); + } + glHandlerSurfaceView.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + createEglContext = false; + } + + if (sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); + } + glHandlerSurfaceView.mRenderer.onSurfaceChanged(gl, w, h); + sizeChanged = false; + } + + if (LOG_RENDERER_DRAW_FRAME) { + Log.w("GLThread", "onDrawFrame tid=" + Thread.currentThread().getId()); + } + glHandlerSurfaceView.mRenderer.onDrawFrame(gl); + if (finishDrawingRunnable != null) { + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + int swapError = mEglHelper.swap(); + switch (swapError) { + case EGL10.EGL_SUCCESS: + break; + case EGL11.EGL_CONTEXT_LOST: + if (LOG_SURFACE) { + Log.i("GLThread", "egl context lost tid=" + Thread.currentThread().getId()); + } + lostEglContext = true; + break; + default: + EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); + synchronized (glHandlerSurfaceView.mMonitor) { + mSurfaceIsBad = true; + glHandlerSurfaceView.mMonitor.notifyAll(); + } + break; + } + + if (wantRenderNotification) { + doRenderNotification = true; + wantRenderNotification = false; + } + return; + } + } catch (Throwable throwable) { + exit(); + throw throwable; + } finally { + synchronized (glHandlerSurfaceView.mMonitor) { + glHandlerSurfaceView.mMonitor.notifyAll(); + } + } + } + + private void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; + mEglHelper.destroySurface(); + } + } + + private void stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + notifyMonitor(); + } + } + + private void exit() { + try { + stopEglSurfaceLocked(); + stopEglContextLocked(); + } finally { + mExited = true; + } + } + + private void scheduleNextRun() { + if (mRenderMode == GLHandlerSurfaceView.RENDERMODE_CONTINUOUSLY) { + long curr = SystemClock.uptimeMillis(); + long gap = curr - mLastRun; + if (gap > 16) { + gap = 0; + } else { + gap = 16 - gap; + } + mLastRun = curr; + removeMessages(MSG_SCHEDULE); + sendEmptyMessageDelayed(MSG_SCHEDULE, gap); + } + } + + private void notifyMonitor() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + if (glHandlerSurfaceView != null) { + synchronized (glHandlerSurfaceView.mMonitor) { + glHandlerSurfaceView.mMonitor.notifyAll(); + } + } + } + + private boolean isLooperThread() { + return Looper.myLooper() == getLooper(); + } + + public boolean ableToDraw() { + return mHaveEglContext && mHaveEglSurface && readyToDraw(); + } + + private boolean readyToDraw() { + return (!mPaused) && mHasSurface && (!mSurfaceIsBad) + && (mWidth > 0) && (mHeight > 0) + && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); + } + + public void setRenderMode(int renderMode) { + if (!((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY))) { + throw new IllegalArgumentException("renderMode"); + } + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mRenderMode = renderMode; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + } + } + } + + public int getRenderMode() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + return mRenderMode; + } + } + + public void requestRender() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mRequestRender = true; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + } + } + } + + public void requestRenderAndNotify(Runnable finishDrawing) { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mWantRenderNotification = true; + mRequestRender = true; + mRenderComplete = false; + mFinishDrawingRunnable = finishDrawing; + + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + } + } + } + + public void surfaceCreated() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceCreated tid=" + getLooper().getThread().getId()); + } + mHasSurface = true; + mFinishedCreatingEglSurface = false; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while (mWaitingForSurface + && !mFinishedCreatingEglSurface + && !mExited) { + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + } + } + + public void surfaceDestroyed() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceDestroyed tid=" + getLooper().getThread().getId()); + } + mHasSurface = false; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while ((!mWaitingForSurface) && (!mExited)) { + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + } + } + + public void onPause() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "onPause tid=" + getLooper().getThread().getId()); + } + mRequestPaused = true; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while ((!mExited) && (!mPaused)) { + if (LOG_PAUSE_RESUME) { + Log.i("Main thread", "onPause waiting for mPaused."); + } + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + } + } + + public void onResume() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + if (LOG_PAUSE_RESUME) { + Log.i("GLThread", "onResume tid=" + getLooper().getThread().getId()); + } + mRequestPaused = false; + mRequestRender = true; + mRenderComplete = false; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while ((!mExited) && mPaused && (!mRenderComplete)) { + if (LOG_PAUSE_RESUME) { + Log.i("Main thread", "onResume waiting for !mPaused."); + } + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + } + } + + public void onWindowResize(int w, int h) { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mWidth = w; + mHeight = h; + mSizeChanged = true; + mRequestRender = true; + mRenderComplete = false; + + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while (!mExited && !mPaused && !mRenderComplete + && ableToDraw()) { + if (LOG_SURFACE) { + Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getLooper().getThread().getId()); + } + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + + } + } + + public void requestExitAndWait() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mShouldExit = true; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + while (!mExited) { + try { + glHandlerSurfaceView.mMonitor.wait(NORMAL_WAIT_TIME); + } catch (InterruptedException ignore) { + } + } + } + } + } + + public void requestReleaseEglContextLocked() { + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mShouldReleaseEglContext = true; + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + } + } + } + + public void queueEvent(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("r must not be null"); + } + GLHandlerSurfaceView glHandlerSurfaceView = mWeakReference.get(); + synchronized (glHandlerSurfaceView.mMonitor) { + mEventQueue.add(r); + removeMessages(MSG_RUN); + if (isLooperThread()) { + handleMessage(obtainMessage(MSG_RUN)); + } else { + obtainMessage(MSG_RUN).sendToTarget(); + } + } + } + } + + public interface GLWrapper { + GL wrap(GL gl); + } + + public interface Renderer { + void onSurfaceCreated(GL10 gl, EGLConfig config); + + void onSurfaceChanged(GL10 gl, int width, int height); + + void onDrawFrame(GL10 gl); + } + + public interface EGLContextFactory { + EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); + + void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); + } + + private class DefaultContextFactory implements EGLContextFactory { + + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, + EGL10.EGL_NONE}; + + return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, + mEGLContextClientVersion != 0 ? attrib_list : null); + } + + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + if (!egl.eglDestroyContext(display, context)) { + Log.e("DefaultContextFactory", "display:" + display + " context: " + context); + if (LOG_THREADS) { + Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); + } + EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); + } + } + } + + public interface EGLWindowSurfaceFactory { + + EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow); + + void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); + } + + private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { + + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, + EGLConfig config, Object nativeWindow) { + EGLSurface result = null; + try { + result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); + } catch (IllegalArgumentException e) { + Log.e(TAG, "eglCreateWindowSurface", e); + } + return result; + } + + public void destroySurface(EGL10 egl, EGLDisplay display, + EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } + } + + public interface EGLConfigChooser { + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); + } + + private abstract class BaseConfigChooser + implements EGLConfigChooser { + public BaseConfigChooser(int[] configSpec) { + mConfigSpec = filterConfigSpec(configSpec); + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } + + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + + protected int[] mConfigSpec; + + private int[] filterConfigSpec(int[] configSpec) { + if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) { + return configSpec; + } + int len = configSpec.length; + int[] newConfigSpec = new int[len + 2]; + System.arraycopy(configSpec, 0, newConfigSpec, 0, len - 1); + newConfigSpec[len - 1] = EGL10.EGL_RENDERABLE_TYPE; + if (mEGLContextClientVersion == 2) { + newConfigSpec[len] = EGL14.EGL_OPENGL_ES2_BIT; /* EGL_OPENGL_ES2_BIT */ + } else { + newConfigSpec[len] = EGLExt.EGL_OPENGL_ES3_BIT_KHR; /* EGL_OPENGL_ES3_BIT_KHR */ + } + newConfigSpec[len + 1] = EGL10.EGL_NONE; + return newConfigSpec; + } + } + + private class ComponentSizeChooser extends BaseConfigChooser { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(new int[]{ + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + for (EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if ((d >= mDepthSize) && (s >= mStencilSize)) { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + if ((r == mRedSize) && (g == mGreenSize) + && (b == mBlueSize) && (a == mAlphaSize)) { + return config; + } + } + } + return null; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + private int[] mValue; + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + } + + private class SimpleEGLConfigChooser extends ComponentSizeChooser { + public SimpleEGLConfigChooser(boolean withDepthBuffer) { + super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); + } + } + + private static class EglHelper { + private WeakReference mGLSurfaceViewWeakRef; + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + EGLContext mEglContext; + + public EglHelper(WeakReference glSurfaceViewWeakRef) { + mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; + } + + public void start() { + if (LOG_EGL) { + Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); + } + + mEgl = (EGL10) EGLContext.getEGL(); + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + + int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } + GLHandlerSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view == null) { + mEglConfig = null; + mEglContext = null; + } else { + mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); + mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); + } + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + mEglContext = null; + throwEglException("createContext"); + } + if (LOG_EGL) { + Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); + } + + mEglSurface = null; + } + + public boolean createSurface() { + if (LOG_EGL) { + Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); + } + if (mEgl == null) { + throw new RuntimeException("egl not initialized"); + } + if (mEglDisplay == null) { + throw new RuntimeException("eglDisplay not initialized"); + } + if (mEglConfig == null) { + throw new RuntimeException("mEglConfig not initialized"); + } + + destroySurfaceImp(); + + GLHandlerSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, + mEglDisplay, mEglConfig, view.getHolder()); + } else { + mEglSurface = null; + } + + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); + return false; + } + + return true; + } + + GL createGL() { + GL gl = mEglContext.getGL(); + GLHandlerSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + if (view.mGLWrapper != null) { + gl = view.mGLWrapper.wrap(gl); + } + + if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { + int configFlags = 0; + Writer log = null; + if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { + configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; + } + if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { + log = new LogWriter(); + } + gl = GLDebugHelper.wrap(gl, configFlags, log); + } + } + return gl; + } + + public int swap() { + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + return mEgl.eglGetError(); + } + return EGL10.EGL_SUCCESS; + } + + public void destroySurface() { + if (LOG_EGL) { + Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); + } + destroySurfaceImp(); + } + + private void destroySurfaceImp() { + if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + GLHandlerSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); + } + mEglSurface = null; + } + } + + public void finish() { + if (LOG_EGL) { + Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); + } + if (mEglContext != null) { + GLHandlerSurfaceView view = mGLSurfaceViewWeakRef.get(); + if (view != null) { + view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); + } + mEglContext = null; + } + if (mEglDisplay != null) { + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + } + } + + private void throwEglException(String function) { + throwEglException(function, mEgl.eglGetError()); + } + + public static void throwEglException(String function, int error) { + String message = formatEglError(function, error); + if (LOG_THREADS) { + Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + + message); + } + throw new RuntimeException(message); + } + + public static void logEglErrorAsWarning(String tag, String function, int error) { + Log.w(tag, formatEglError(function, error)); + } + + public static String formatEglError(String function, int error) { + return function + " failed: " + "0x" + Integer.toHexString(error); + } + + } + + static class LogWriter extends Writer { + + @Override + public void close() { + flushBuilder(); + } + + @Override + public void flush() { + flushBuilder(); + } + + @Override + public void write(char[] buf, int offset, int count) { + for (int i = 0; i < count; i++) { + char c = buf[offset + i]; + if (c == '\n') { + flushBuilder(); + } else { + mBuilder.append(c); + } + } + } + + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.v("GLSurfaceView", mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } + + private StringBuilder mBuilder = new StringBuilder(); + } +} diff --git a/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLShareable.java b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLShareable.java new file mode 100644 index 00000000..e6a740d7 --- /dev/null +++ b/DanmakuFlameMaster/src/main/java/master/flame/danmaku/gl/wedget/GLShareable.java @@ -0,0 +1,116 @@ +package master.flame.danmaku.gl.wedget; + +import android.opengl.GLES20; +import android.util.Log; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + +/** + * 创建人:yangzhiqian + * 创建时间:2018/8/16 15:06 + */ +public interface GLShareable { + EGLContext getSharedContext(); + + EGLConfig getSharedConfig(); + + int getEGLContextClientVersion(); + + int getSurfaceWidth(); + + int getSurfaceHeight(); + + final class GLShareHelper { + private EGL10 mEGL10; + private EGLContext mSharedContext; + private EGLConfig mSharedConfig; + private EGLDisplay mCurrentDisplay; + private EGLContext mCurrentContext; + private EGLSurface mCurrentSurface; + + private static final ThreadLocal sLocalGLShareHelper = new ThreadLocal<>(); + + private GLShareHelper() { + } + + public static GLShareHelper makeSharedGlContext(final GLShareable shareable) { + EGL10 egl = (EGL10) EGLContext.getEGL(); + EGLContext currentEglContext = egl.eglGetCurrentContext(); + if (sLocalGLShareHelper.get() != null && currentEglContext != null && currentEglContext != EGL10.EGL_NO_CONTEXT) { + //已经调用过makeSharedGlContext了 + return sLocalGLShareHelper.get(); + } + if (currentEglContext != null && currentEglContext != EGL10.EGL_NO_CONTEXT) { + //当前线程已经存在glcontext + Log.w("GLShareHelper", "当前线程已经存在gl环境,请不要在此线程再次初始化opengl环境"); + return null; + } + if (shareable == null) { + return null; + } + EGLContext sharedContext = shareable.getSharedContext(); + if (sharedContext == null || sharedContext == EGL10.EGL_NO_CONTEXT) { + return null; + } + EGLConfig sharedConfig = shareable.getSharedConfig(); + if (sharedConfig == null) { + return null; + } + int eglContextClientVersion = shareable.getEGLContextClientVersion(); + int[] attrib_list = {0x3098, eglContextClientVersion, + EGL10.EGL_NONE}; + + final EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (display == EGL10.EGL_NO_DISPLAY) { + return null; + } + currentEglContext = egl.eglCreateContext(display, sharedConfig, sharedContext, + eglContextClientVersion != 0 ? attrib_list : null); + if (currentEglContext == EGL10.EGL_NO_CONTEXT) { + return null; + } + + int[] surfaceAttribList = { + EGL10.EGL_WIDTH, shareable.getSurfaceWidth(), + EGL10.EGL_HEIGHT, shareable.getSurfaceHeight(), + EGL10.EGL_NONE + }; + EGLSurface eglSurface = egl.eglCreatePbufferSurface(display, sharedConfig, surfaceAttribList); + if (eglSurface == EGL10.EGL_NO_SURFACE) { + return null; + } + if (!egl.eglMakeCurrent(display, eglSurface, eglSurface, currentEglContext)) { + egl.eglDestroySurface(display, eglSurface); + return null; + } + GLES20.glFlush(); + GLShareHelper shareHelper = new GLShareHelper(); + shareHelper.mEGL10 = egl; + shareHelper.mSharedContext = sharedContext; + shareHelper.mSharedConfig = sharedConfig; + shareHelper.mCurrentContext = currentEglContext; + shareHelper.mCurrentDisplay = display; + shareHelper.mCurrentSurface = eglSurface; + sLocalGLShareHelper.set(shareHelper); + return shareHelper; + } + + public static void release() { + GLShareHelper shareHelper = sLocalGLShareHelper.get(); + if (shareHelper == null) { + return; + } + shareHelper.mEGL10.eglDestroySurface(shareHelper.mCurrentDisplay, shareHelper.mCurrentSurface); + if (!shareHelper.mEGL10.eglDestroyContext(shareHelper.mCurrentDisplay, shareHelper.mCurrentContext)) { + Log.e("GLShareHelper", "display:" + shareHelper.mCurrentDisplay + " context: " + shareHelper.mCurrentContext); + } + //todo releae mCurrentDisplay +// shareHelper.mEGL10.eglTerminate(shareHelper.mCurrentDisplay); + sLocalGLShareHelper.set(null); + } + } +} \ No newline at end of file diff --git a/Sample/src/main/AndroidManifest.xml b/Sample/src/main/AndroidManifest.xml index c2307beb..27a2803c 100644 --- a/Sample/src/main/AndroidManifest.xml +++ b/Sample/src/main/AndroidManifest.xml @@ -2,13 +2,13 @@ + android:versionName="1.0"> - + + android:theme="@style/AppTheme"> + android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> + + \ No newline at end of file diff --git a/Sample/src/main/java/com/sample/BiliMainActivity.java b/Sample/src/main/java/com/sample/BiliMainActivity.java new file mode 100644 index 00000000..69724e78 --- /dev/null +++ b/Sample/src/main/java/com/sample/BiliMainActivity.java @@ -0,0 +1,440 @@ + +package com.sample; + +import android.app.Activity; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.os.Environment; +import master.flame.danmaku.danmaku.util.SystemClock; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; +import android.util.Log; +import android.view.Menu; +import android.view.View; +import android.widget.Button; +import android.widget.PopupWindow; +import android.widget.VideoView; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; + +import master.flame.danmaku.controller.IDanmakuView; +import master.flame.danmaku.danmaku.loader.ILoader; +import master.flame.danmaku.danmaku.loader.IllegalDataException; +import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.android.BaseCacheStuffer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.parser.IDataSource; +import master.flame.danmaku.danmaku.util.IOUtils; + +public class BiliMainActivity extends Activity implements View.OnClickListener { + + private IDanmakuView mDanmakuView; + + private View mMediaController; + + public PopupWindow mPopupWindow; + + private Button mBtnRotate; + + private Button mBtnHideDanmaku; + + private Button mBtnShowDanmaku; + + private BaseDanmakuParser mParser; + + private Button mBtnPauseDanmaku; + + private Button mBtnResumeDanmaku; + + private Button mBtnSendDanmaku; + + private Button mBtnSendDanmakuTextAndImage; + + private Button mBtnSendDanmakus; + private DanmakuContext mContext; + private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() { + + private Drawable mDrawable; + + @Override + public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) { + if (danmaku.text instanceof Spanned) { // 根据你的条件检查是否需要需要更新弹幕 + // FIXME 这里只是简单启个线程来加载远程url图片,请使用你自己的异步线程池,最好加上你的缓存池 + new Thread() { + + @Override + public void run() { + String url = "http://www.bilibili.com/favicon.ico"; + InputStream inputStream = null; + Drawable drawable = mDrawable; + if(drawable == null) { + try { + URLConnection urlConnection = new URL(url).openConnection(); + inputStream = urlConnection.getInputStream(); + drawable = BitmapDrawable.createFromStream(inputStream, "bitmap"); + mDrawable = drawable; + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + if (drawable != null) { + drawable.setBounds(0, 0, 100, 100); + SpannableStringBuilder spannable = createSpannable(drawable); + danmaku.text = spannable; + if(mDanmakuView != null) { + mDanmakuView.invalidateDanmaku(danmaku, false); + } + return; + } + } + }.start(); + } + } + + @Override + public void releaseResource(BaseDanmaku danmaku) { + // TODO 重要:清理含有ImageSpan的text中的一些占用内存的资源 例如drawable + } + }; + + /** + * 绘制背景(自定义弹幕样式) + */ + private static class BackgroundCacheStuffer extends SpannedCacheStuffer { + // 通过扩展SimpleTextCacheStuffer或SpannedCacheStuffer个性化你的弹幕样式 + final Paint paint = new Paint(); + + @Override + public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { + danmaku.padding = 10; // 在背景绘制模式下增加padding + super.measure(danmaku, paint, fromWorkerThread); + } + + @Override + public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { + paint.setColor(0x8125309b); + canvas.drawRect(left + 2, top + 2, left + danmaku.paintWidth - 2, top + danmaku.paintHeight - 2, paint); + } + + @Override + public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { + // 禁用描边绘制 + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + findViews(); + } + + private BaseDanmakuParser createParser(InputStream stream) { + + if (stream == null) { + return new BaseDanmakuParser() { + + @Override + protected Danmakus parse() { + return new Danmakus(); + } + }; + } + + ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); + + try { + loader.load(stream); + } catch (IllegalDataException e) { + e.printStackTrace(); + } + BaseDanmakuParser parser = new BiliDanmukuParser(); + IDataSource dataSource = loader.getDataSource(); + parser.load(dataSource); + return parser; + + } + + private void findViews() { + + mMediaController = findViewById(R.id.media_controller); + mBtnRotate = (Button) findViewById(R.id.rotate); + mBtnHideDanmaku = (Button) findViewById(R.id.btn_hide); + mBtnShowDanmaku = (Button) findViewById(R.id.btn_show); + mBtnPauseDanmaku = (Button) findViewById(R.id.btn_pause); + mBtnResumeDanmaku = (Button) findViewById(R.id.btn_resume); + mBtnSendDanmaku = (Button) findViewById(R.id.btn_send); + mBtnSendDanmakuTextAndImage = (Button) findViewById(R.id.btn_send_image_text); + mBtnSendDanmakus = (Button) findViewById(R.id.btn_send_danmakus); + mBtnRotate.setOnClickListener(this); + mBtnHideDanmaku.setOnClickListener(this); + mMediaController.setOnClickListener(this); + mBtnShowDanmaku.setOnClickListener(this); + mBtnPauseDanmaku.setOnClickListener(this); + mBtnResumeDanmaku.setOnClickListener(this); + mBtnSendDanmaku.setOnClickListener(this); + mBtnSendDanmakuTextAndImage.setOnClickListener(this); + mBtnSendDanmakus.setOnClickListener(this); + + // VideoView + VideoView mVideoView = (VideoView) findViewById(R.id.videoview); + // DanmakuView + + // 设置最大显示行数 + HashMap maxLinesPair = new HashMap(); + maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); // 滚动弹幕最大显示5行 + // 设置是否禁止重叠 + HashMap overlappingEnablePair = new HashMap(); + overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true); + overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true); + + mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku); + mContext = DanmakuContext.create(); + mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3).setDuplicateMergingEnabled(false).setScrollSpeedFactor(1.2f).setScaleTextSize(1.2f) + .setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer +// .setCacheStuffer(new BackgroundCacheStuffer()) // 绘制背景使用BackgroundCacheStuffer + .setMaximumLines(maxLinesPair) + .preventOverlapping(overlappingEnablePair).setDanmakuMargin(40); + if (mDanmakuView != null) { + mParser = createParser(this.getResources().openRawResource(R.raw.comments)); + mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { + @Override + public void updateTimer(DanmakuTimer timer) { + } + + @Override + public void drawingFinished() { + + } + + @Override + public void danmakuShown(BaseDanmaku danmaku) { +// Log.d("DFM", "danmakuShown(): text=" + danmaku.text); + } + + @Override + public void prepared() { + mDanmakuView.start(); + } + }); + mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() { + + @Override + public boolean onDanmakuClick(IDanmakus danmakus) { + Log.d("DFM", "onDanmakuClick: danmakus size:" + danmakus.size()); + BaseDanmaku latest = danmakus.last(); + if (null != latest) { + Log.d("DFM", "onDanmakuClick: text of latest danmaku:" + latest.text); + return true; + } + return false; + } + + @Override + public boolean onDanmakuLongClick(IDanmakus danmakus) { + return false; + } + + @Override + public boolean onViewClick(IDanmakuView view) { + mMediaController.setVisibility(View.VISIBLE); + return false; + } + }); + mDanmakuView.prepare(mParser, mContext); + mDanmakuView.showFPS(true); + mDanmakuView.enableDanmakuDrawingCache(true); + } + + if (mVideoView != null) { + mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + mediaPlayer.start(); + } + }); + mVideoView.setVideoPath(Environment.getExternalStorageDirectory() + "/1.flv"); + } + + } + + @Override + protected void onPause() { + super.onPause(); + if (mDanmakuView != null && mDanmakuView.isPrepared()) { + mDanmakuView.pause(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { + mDanmakuView.resume(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDanmakuView != null) { + // dont forget release! + mDanmakuView.release(); + mDanmakuView = null; + } + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + if (mDanmakuView != null) { + // dont forget release! + mDanmakuView.release(); + mDanmakuView = null; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public void onClick(View v) { + if (v == mMediaController) { + mMediaController.setVisibility(View.GONE); + } + if (mDanmakuView == null || !mDanmakuView.isPrepared()) + return; + if (v == mBtnRotate) { + setRequestedOrientation(getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else if (v == mBtnHideDanmaku) { + mDanmakuView.hide(); + // mPausedPosition = mDanmakuView.hideAndPauseDrawTask(); + } else if (v == mBtnShowDanmaku) { + mDanmakuView.show(); + // mDanmakuView.showAndResumeDrawTask(mPausedPosition); // sync to the video time in your practice + } else if (v == mBtnPauseDanmaku) { + mDanmakuView.pause(); + } else if (v == mBtnResumeDanmaku) { + mDanmakuView.resume(); + } else if (v == mBtnSendDanmaku) { + addDanmaku(false); + } else if (v == mBtnSendDanmakuTextAndImage) { + addDanmaKuShowTextAndImage(false); + } else if (v == mBtnSendDanmakus) { + Boolean b = (Boolean) mBtnSendDanmakus.getTag(); + timer.cancel(); + if (b == null || !b) { + mBtnSendDanmakus.setText(R.string.cancel_sending_danmakus); + timer = new Timer(); + timer.schedule(new AsyncAddTask(), 0, 1000); + mBtnSendDanmakus.setTag(true); + } else { + mBtnSendDanmakus.setText(R.string.send_danmakus); + mBtnSendDanmakus.setTag(false); + } + } + } + + Timer timer = new Timer(); + + class AsyncAddTask extends TimerTask { + + @Override + public void run() { + for (int i = 0; i < 20; i++) { + addDanmaku(true); + SystemClock.sleep(20); + } + } + }; + + private void addDanmaku(boolean islive) { + BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); + if (danmaku == null || mDanmakuView == null) { + return; + } + // for(int i=0;i<100;i++){ + // } + danmaku.text = "这是一条弹幕" + System.nanoTime(); + danmaku.padding = 5; + danmaku.priority = 0; // 可能会被各种过滤器过滤并隐藏显示 + danmaku.isLive = islive; + danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); + danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); + danmaku.textColor = Color.RED; + danmaku.textShadowColor = Color.WHITE; + // danmaku.underlineColor = Color.GREEN; + danmaku.borderColor = Color.GREEN; + mDanmakuView.addDanmaku(danmaku); + + } + + private void addDanmaKuShowTextAndImage(boolean islive) { + BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); + Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher); + drawable.setBounds(0, 0, 100, 100); + SpannableStringBuilder spannable = createSpannable(drawable); + danmaku.text = spannable; + danmaku.padding = 5; + danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕 + danmaku.isLive = islive; + danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); + danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); + danmaku.textColor = Color.RED; + danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低 + danmaku.underlineColor = Color.GREEN; + mDanmakuView.addDanmaku(danmaku); + } + + private SpannableStringBuilder createSpannable(Drawable drawable) { + String text = "bitmap"; + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); + ImageSpan span = new ImageSpan(drawable);//ImageSpan.ALIGN_BOTTOM); + spannableStringBuilder.setSpan(span, 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + spannableStringBuilder.append("图文混排"); + spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("#8A2233B1")), 0, spannableStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + return spannableStringBuilder; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + mDanmakuView.getConfig().setDanmakuMargin(20); + } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + mDanmakuView.getConfig().setDanmakuMargin(40); + } + } +} diff --git a/Sample/src/main/java/com/sample/MainActivity.java b/Sample/src/main/java/com/sample/MainActivity.java index ec07f71b..80f81973 100644 --- a/Sample/src/main/java/com/sample/MainActivity.java +++ b/Sample/src/main/java/com/sample/MainActivity.java @@ -1,440 +1,38 @@ - -package com.sample; - -import android.app.Activity; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.MediaPlayer; -import android.os.Bundle; -import android.os.Environment; -import master.flame.danmaku.danmaku.util.SystemClock; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.style.BackgroundColorSpan; -import android.text.style.ImageSpan; -import android.util.Log; -import android.view.Menu; -import android.view.View; -import android.widget.Button; -import android.widget.PopupWindow; -import android.widget.VideoView; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.util.HashMap; -import java.util.Timer; -import java.util.TimerTask; - -import master.flame.danmaku.controller.IDanmakuView; -import master.flame.danmaku.danmaku.loader.ILoader; -import master.flame.danmaku.danmaku.loader.IllegalDataException; -import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; -import master.flame.danmaku.danmaku.model.BaseDanmaku; -import master.flame.danmaku.danmaku.model.DanmakuTimer; -import master.flame.danmaku.danmaku.model.IDanmakus; -import master.flame.danmaku.danmaku.model.IDisplayer; -import master.flame.danmaku.danmaku.model.android.BaseCacheStuffer; -import master.flame.danmaku.danmaku.model.android.DanmakuContext; -import master.flame.danmaku.danmaku.model.android.Danmakus; -import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; -import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; -import master.flame.danmaku.danmaku.parser.IDataSource; -import master.flame.danmaku.danmaku.util.IOUtils; - -public class MainActivity extends Activity implements View.OnClickListener { - - private IDanmakuView mDanmakuView; - - private View mMediaController; - - public PopupWindow mPopupWindow; - - private Button mBtnRotate; - - private Button mBtnHideDanmaku; - - private Button mBtnShowDanmaku; - - private BaseDanmakuParser mParser; - - private Button mBtnPauseDanmaku; - - private Button mBtnResumeDanmaku; - - private Button mBtnSendDanmaku; - - private Button mBtnSendDanmakuTextAndImage; - - private Button mBtnSendDanmakus; - private DanmakuContext mContext; - private BaseCacheStuffer.Proxy mCacheStufferAdapter = new BaseCacheStuffer.Proxy() { - - private Drawable mDrawable; - - @Override - public void prepareDrawing(final BaseDanmaku danmaku, boolean fromWorkerThread) { - if (danmaku.text instanceof Spanned) { // 根据你的条件检查是否需要需要更新弹幕 - // FIXME 这里只是简单启个线程来加载远程url图片,请使用你自己的异步线程池,最好加上你的缓存池 - new Thread() { - - @Override - public void run() { - String url = "http://www.bilibili.com/favicon.ico"; - InputStream inputStream = null; - Drawable drawable = mDrawable; - if(drawable == null) { - try { - URLConnection urlConnection = new URL(url).openConnection(); - inputStream = urlConnection.getInputStream(); - drawable = BitmapDrawable.createFromStream(inputStream, "bitmap"); - mDrawable = drawable; - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - if (drawable != null) { - drawable.setBounds(0, 0, 100, 100); - SpannableStringBuilder spannable = createSpannable(drawable); - danmaku.text = spannable; - if(mDanmakuView != null) { - mDanmakuView.invalidateDanmaku(danmaku, false); - } - return; - } - } - }.start(); - } - } - - @Override - public void releaseResource(BaseDanmaku danmaku) { - // TODO 重要:清理含有ImageSpan的text中的一些占用内存的资源 例如drawable - } - }; - - /** - * 绘制背景(自定义弹幕样式) - */ - private static class BackgroundCacheStuffer extends SpannedCacheStuffer { - // 通过扩展SimpleTextCacheStuffer或SpannedCacheStuffer个性化你的弹幕样式 - final Paint paint = new Paint(); - - @Override - public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { - danmaku.padding = 10; // 在背景绘制模式下增加padding - super.measure(danmaku, paint, fromWorkerThread); - } - - @Override - public void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { - paint.setColor(0x8125309b); - canvas.drawRect(left + 2, top + 2, left + danmaku.paintWidth - 2, top + danmaku.paintHeight - 2, paint); - } - - @Override - public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { - // 禁用描边绘制 - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - findViews(); - } - - private BaseDanmakuParser createParser(InputStream stream) { - - if (stream == null) { - return new BaseDanmakuParser() { - - @Override - protected Danmakus parse() { - return new Danmakus(); - } - }; - } - - ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); - - try { - loader.load(stream); - } catch (IllegalDataException e) { - e.printStackTrace(); - } - BaseDanmakuParser parser = new BiliDanmukuParser(); - IDataSource dataSource = loader.getDataSource(); - parser.load(dataSource); - return parser; - - } - - private void findViews() { - - mMediaController = findViewById(R.id.media_controller); - mBtnRotate = (Button) findViewById(R.id.rotate); - mBtnHideDanmaku = (Button) findViewById(R.id.btn_hide); - mBtnShowDanmaku = (Button) findViewById(R.id.btn_show); - mBtnPauseDanmaku = (Button) findViewById(R.id.btn_pause); - mBtnResumeDanmaku = (Button) findViewById(R.id.btn_resume); - mBtnSendDanmaku = (Button) findViewById(R.id.btn_send); - mBtnSendDanmakuTextAndImage = (Button) findViewById(R.id.btn_send_image_text); - mBtnSendDanmakus = (Button) findViewById(R.id.btn_send_danmakus); - mBtnRotate.setOnClickListener(this); - mBtnHideDanmaku.setOnClickListener(this); - mMediaController.setOnClickListener(this); - mBtnShowDanmaku.setOnClickListener(this); - mBtnPauseDanmaku.setOnClickListener(this); - mBtnResumeDanmaku.setOnClickListener(this); - mBtnSendDanmaku.setOnClickListener(this); - mBtnSendDanmakuTextAndImage.setOnClickListener(this); - mBtnSendDanmakus.setOnClickListener(this); - - // VideoView - VideoView mVideoView = (VideoView) findViewById(R.id.videoview); - // DanmakuView - - // 设置最大显示行数 - HashMap maxLinesPair = new HashMap(); - maxLinesPair.put(BaseDanmaku.TYPE_SCROLL_RL, 5); // 滚动弹幕最大显示5行 - // 设置是否禁止重叠 - HashMap overlappingEnablePair = new HashMap(); - overlappingEnablePair.put(BaseDanmaku.TYPE_SCROLL_RL, true); - overlappingEnablePair.put(BaseDanmaku.TYPE_FIX_TOP, true); - - mDanmakuView = (IDanmakuView) findViewById(R.id.sv_danmaku); - mContext = DanmakuContext.create(); - mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3).setDuplicateMergingEnabled(false).setScrollSpeedFactor(1.2f).setScaleTextSize(1.2f) - .setCacheStuffer(new SpannedCacheStuffer(), mCacheStufferAdapter) // 图文混排使用SpannedCacheStuffer -// .setCacheStuffer(new BackgroundCacheStuffer()) // 绘制背景使用BackgroundCacheStuffer - .setMaximumLines(maxLinesPair) - .preventOverlapping(overlappingEnablePair).setDanmakuMargin(40); - if (mDanmakuView != null) { - mParser = createParser(this.getResources().openRawResource(R.raw.comments)); - mDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { - @Override - public void updateTimer(DanmakuTimer timer) { - } - - @Override - public void drawingFinished() { - - } - - @Override - public void danmakuShown(BaseDanmaku danmaku) { -// Log.d("DFM", "danmakuShown(): text=" + danmaku.text); - } - - @Override - public void prepared() { - mDanmakuView.start(); - } - }); - mDanmakuView.setOnDanmakuClickListener(new IDanmakuView.OnDanmakuClickListener() { - - @Override - public boolean onDanmakuClick(IDanmakus danmakus) { - Log.d("DFM", "onDanmakuClick: danmakus size:" + danmakus.size()); - BaseDanmaku latest = danmakus.last(); - if (null != latest) { - Log.d("DFM", "onDanmakuClick: text of latest danmaku:" + latest.text); - return true; - } - return false; - } - - @Override - public boolean onDanmakuLongClick(IDanmakus danmakus) { - return false; - } - - @Override - public boolean onViewClick(IDanmakuView view) { - mMediaController.setVisibility(View.VISIBLE); - return false; - } - }); - mDanmakuView.prepare(mParser, mContext); - mDanmakuView.showFPS(true); - mDanmakuView.enableDanmakuDrawingCache(true); - } - - if (mVideoView != null) { - mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mediaPlayer) { - mediaPlayer.start(); - } - }); - mVideoView.setVideoPath(Environment.getExternalStorageDirectory() + "/1.flv"); - } - - } - - @Override - protected void onPause() { - super.onPause(); - if (mDanmakuView != null && mDanmakuView.isPrepared()) { - mDanmakuView.pause(); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mDanmakuView != null && mDanmakuView.isPrepared() && mDanmakuView.isPaused()) { - mDanmakuView.resume(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mDanmakuView != null) { - // dont forget release! - mDanmakuView.release(); - mDanmakuView = null; - } - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - if (mDanmakuView != null) { - // dont forget release! - mDanmakuView.release(); - mDanmakuView = null; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - - @Override - public void onClick(View v) { - if (v == mMediaController) { - mMediaController.setVisibility(View.GONE); - } - if (mDanmakuView == null || !mDanmakuView.isPrepared()) - return; - if (v == mBtnRotate) { - setRequestedOrientation(getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } else if (v == mBtnHideDanmaku) { - mDanmakuView.hide(); - // mPausedPosition = mDanmakuView.hideAndPauseDrawTask(); - } else if (v == mBtnShowDanmaku) { - mDanmakuView.show(); - // mDanmakuView.showAndResumeDrawTask(mPausedPosition); // sync to the video time in your practice - } else if (v == mBtnPauseDanmaku) { - mDanmakuView.pause(); - } else if (v == mBtnResumeDanmaku) { - mDanmakuView.resume(); - } else if (v == mBtnSendDanmaku) { - addDanmaku(false); - } else if (v == mBtnSendDanmakuTextAndImage) { - addDanmaKuShowTextAndImage(false); - } else if (v == mBtnSendDanmakus) { - Boolean b = (Boolean) mBtnSendDanmakus.getTag(); - timer.cancel(); - if (b == null || !b) { - mBtnSendDanmakus.setText(R.string.cancel_sending_danmakus); - timer = new Timer(); - timer.schedule(new AsyncAddTask(), 0, 1000); - mBtnSendDanmakus.setTag(true); - } else { - mBtnSendDanmakus.setText(R.string.send_danmakus); - mBtnSendDanmakus.setTag(false); - } - } - } - - Timer timer = new Timer(); - - class AsyncAddTask extends TimerTask { - - @Override - public void run() { - for (int i = 0; i < 20; i++) { - addDanmaku(true); - SystemClock.sleep(20); - } - } - }; - - private void addDanmaku(boolean islive) { - BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); - if (danmaku == null || mDanmakuView == null) { - return; - } - // for(int i=0;i<100;i++){ - // } - danmaku.text = "这是一条弹幕" + System.nanoTime(); - danmaku.padding = 5; - danmaku.priority = 0; // 可能会被各种过滤器过滤并隐藏显示 - danmaku.isLive = islive; - danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); - danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); - danmaku.textColor = Color.RED; - danmaku.textShadowColor = Color.WHITE; - // danmaku.underlineColor = Color.GREEN; - danmaku.borderColor = Color.GREEN; - mDanmakuView.addDanmaku(danmaku); - - } - - private void addDanmaKuShowTextAndImage(boolean islive) { - BaseDanmaku danmaku = mContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); - Drawable drawable = getResources().getDrawable(R.drawable.ic_launcher); - drawable.setBounds(0, 0, 100, 100); - SpannableStringBuilder spannable = createSpannable(drawable); - danmaku.text = spannable; - danmaku.padding = 5; - danmaku.priority = 1; // 一定会显示, 一般用于本机发送的弹幕 - danmaku.isLive = islive; - danmaku.setTime(mDanmakuView.getCurrentTime() + 1200); - danmaku.textSize = 25f * (mParser.getDisplayer().getDensity() - 0.6f); - danmaku.textColor = Color.RED; - danmaku.textShadowColor = 0; // 重要:如果有图文混排,最好不要设置描边(设textShadowColor=0),否则会进行两次复杂的绘制导致运行效率降低 - danmaku.underlineColor = Color.GREEN; - mDanmakuView.addDanmaku(danmaku); - } - - private SpannableStringBuilder createSpannable(Drawable drawable) { - String text = "bitmap"; - SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text); - ImageSpan span = new ImageSpan(drawable);//ImageSpan.ALIGN_BOTTOM); - spannableStringBuilder.setSpan(span, 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - spannableStringBuilder.append("图文混排"); - spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("#8A2233B1")), 0, spannableStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); - return spannableStringBuilder; - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - mDanmakuView.getConfig().setDanmakuMargin(20); - } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { - mDanmakuView.getConfig().setDanmakuMargin(40); - } - } -} +package com.sample; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import com.sample.gl.DanmakuActivity; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.layout_entrance); + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.btn_enter_glsurfaceview: + Intent intent = new Intent(); + intent.setClass(this, DanmakuActivity.class); + intent.putExtra(DanmakuActivity.TYPE, DanmakuActivity.TYPE_DANMAKU_GL_VIEW); + startActivity(intent); + break; + case R.id.btn_enter_view: + intent = new Intent(); + intent.setClass(this, DanmakuActivity.class); + intent.putExtra(DanmakuActivity.TYPE, DanmakuActivity.TYPE_DANMAKU_VIEW); + startActivity(intent); + break; + case R.id.btn_bili_enter_view: + intent = new Intent(); + intent.setClass(this, BiliMainActivity.class); + startActivity(intent); + break; + } + } +} diff --git a/Sample/src/main/java/com/sample/gl/DanmakuActivity.java b/Sample/src/main/java/com/sample/gl/DanmakuActivity.java new file mode 100644 index 00000000..05b4fb8c --- /dev/null +++ b/Sample/src/main/java/com/sample/gl/DanmakuActivity.java @@ -0,0 +1,276 @@ +package com.sample.gl; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.sample.BiliDanmukuParser; +import com.sample.R; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Random; + +import master.flame.danmaku.controller.IDanmakuView; +import master.flame.danmaku.danmaku.loader.ILoader; +import master.flame.danmaku.danmaku.loader.IllegalDataException; +import master.flame.danmaku.danmaku.loader.android.DanmakuLoaderFactory; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.Duration; +import master.flame.danmaku.danmaku.model.IDisplayer; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.model.android.SpannedCacheStuffer; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.danmaku.parser.IDataSource; +import master.flame.danmaku.gl.AndroidGLDisplayer; +import master.flame.danmaku.gl.utils.SpeedsMeasurement; + +public class DanmakuActivity extends Activity { + public static final String TYPE = "danmaku_type"; + public static final int TYPE_DANMAKU_VIEW = 1; + public static final int TYPE_DANMAKU_GL_VIEW = 2; + private static final String XML_PARSE_LOAD = "800"; + + protected IDanmakuView mNormalDanmakuView; + private DanmakuContext mNormalDanmakuContext; + private SpannedCacheStuffer mSpannedCacheStuffer = new SpannedCacheStuffer(); + private BaseDanmakuParser mParser; + int mDanmakuType; + EditText time; + FrequencyDanmakuSender frequencyDanmakuSender; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDanmakuType = getIntent().getIntExtra(TYPE, TYPE_DANMAKU_GL_VIEW); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + setContentView(R.layout.layout_danmaku); + + if (mDanmakuType == TYPE_DANMAKU_GL_VIEW) { + mNormalDanmakuView = (IDanmakuView) findViewById(R.id.danmaku_gl); + } else { + mNormalDanmakuView = (IDanmakuView) findViewById(R.id.danmaku_view); + } + ((View) mNormalDanmakuView).setVisibility(View.VISIBLE); + time = (EditText) findViewById(R.id.et_time); + frequencyDanmakuSender = new FrequencyDanmakuSender(); + frequencyDanmakuSender.init(mNormalDanmakuView, time); + initNormalDanmuView(); + + } + + + @Override + public void onResume() { + super.onResume(); + mNormalDanmakuView.resume(); + frequencyDanmakuSender.resume(); + } + + @Override + public void onPause() { + super.onPause(); + mNormalDanmakuView.pause(); + frequencyDanmakuSender.stop(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mNormalDanmakuView.release(); + } + + + private void initNormalDanmuView() { + mNormalDanmakuContext = DanmakuContext.create(); + mNormalDanmakuContext.cachingPolicy.mAllowDelayInCacheModel = true; + if (mDanmakuType == TYPE_DANMAKU_GL_VIEW) { + mNormalDanmakuContext.mDisplayer = new AndroidGLDisplayer(mNormalDanmakuContext); + } + mNormalDanmakuContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_SHADOW, 10) + .setMaximumVisibleSizeInScreen(200) + // 图文混排使用SpannedCacheStuffer; + .setCacheStuffer(mSpannedCacheStuffer, null); + + mNormalDanmakuView.setCallback(new master.flame.danmaku.controller.DrawHandler.Callback() { + @Override + public void updateTimer(DanmakuTimer timer) { + } + + @Override + public void danmakuShown(BaseDanmaku danmaku) { + } + + @Override + public void drawingFinished() { + } + + @Override + public void prepared() { + mNormalDanmakuView.start(); + } + }); + + mNormalDanmakuView.enableDanmakuDrawingCache(true); + + try { + mParser = createParser(string2InputStream(XML_PARSE_LOAD)); + } catch (Exception e) { + } + + if (mParser != null) { + mNormalDanmakuView.prepare(mParser, mNormalDanmakuContext); + } + mNormalDanmakuView.show(); + } + + ///Danmaku test + private BaseDanmakuParser createParser(InputStream stream) { + if (stream == null) { + return new BaseDanmakuParser() { + @Override + protected Danmakus parse() { + return new Danmakus(); + } + }; + } + ILoader loader = DanmakuLoaderFactory.create(DanmakuLoaderFactory.TAG_BILI); + try { + loader.load(stream); + } catch (IllegalDataException e) { + e.printStackTrace(); + } + BaseDanmakuParser parser = new BiliDanmukuParser(); + IDataSource dataSource = loader.getDataSource(); + parser.load(dataSource); + return parser; + } + + public static InputStream string2InputStream(String in) throws Exception { + ByteArrayInputStream is = new ByteArrayInputStream(in.getBytes("UTF-8")); + return is; + } + private static class FrequencyDanmakuSender implements TextWatcher, Runnable { + private int mSpeed = 0; + private boolean mRunning = false; + private Handler mHandler = new Handler(); + protected IDanmakuView mNormalDanmakuView; + private Random rand = new Random(); + private Duration mDuration = new Duration(6000); + private HandlerThread handlerThread; + private SpeedsMeasurement speedsMeasurement = new SpeedsMeasurement("MainSenderSpeed"); + + FrequencyDanmakuSender() { + handlerThread = new HandlerThread("sender"); + handlerThread.start(); + mHandler = new Handler(handlerThread.getLooper()); + } + + public void init(IDanmakuView danmakuView, TextView textView) { + + this.mNormalDanmakuView = danmakuView; + textView.addTextChangedListener(this); + try { + mSpeed = Integer.parseInt(textView.getText().toString()); + mHandler.removeCallbacksAndMessages(null); + if (mRunning && mSpeed > 0) { + mHandler.post(this); + } + } catch (Exception ignore) { + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + try { + mSpeed = Integer.parseInt(s.toString()); + mHandler.removeCallbacksAndMessages(null); + if (mRunning && mSpeed > 0) { + mHandler.post(this); + } + } catch (Exception ignore) { + } + } + + @Override + public void run() { + if (!mNormalDanmakuView.isPaused()) { + // 根据position创建弹幕展示的类型 + int danmaType = BaseDanmaku.TYPE_SCROLL_RL; + String position = String.valueOf((1 + rand.nextInt(4))); + if ("2".equals(position)) { + danmaType = BaseDanmaku.TYPE_FIX_TOP; + } else if ("3".equals(position)) { + danmaType = BaseDanmaku.TYPE_FIX_BOTTOM; + } else if ("4".equals(position)) { + danmaType = BaseDanmaku.TYPE_SCROLL_LR; + } + BaseDanmaku danmaku = mNormalDanmakuView.getConfig().mDanmakuFactory.createDanmaku(danmaType); + if (danmaku == null) { + return; + } + + danmaku.isLive = false; + + danmaku.text = getRandomString(Math.abs(rand.nextInt() % 20) + 5); + + danmaku.priority = 1; + danmaku.padding = 5; + danmaku.setTime(mNormalDanmakuView.getCurrentTime() + 20 + rand.nextInt(20)); + danmaku.textSize = 30 + rand.nextInt(30); + danmaku.textColor = rand.nextInt() | 0xff000000; + danmaku.setDuration(mDuration); + mNormalDanmakuView.addDanmaku(danmaku); + speedsMeasurement.dot(); + } + if (mRunning && mSpeed > 0) { + mHandler.postDelayed(this, 1000 / mSpeed); + } + } + + public void resume() { + mHandler.removeCallbacksAndMessages(null); + mRunning = true; + if (mSpeed > 0) { + mHandler.post(this); + } + } + + public void stop() { + mHandler.removeCallbacksAndMessages(null); + mRunning = false; + } + + public static String getRandomString(int length) { + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?$$$%%%###@@@!!!&&&***^^^???"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < length; i++) { + int number = random.nextInt(str.length()); + sb.append(str.charAt(number)); + } + return sb.toString(); + } + } +} diff --git a/Sample/src/main/res/layout/activity_main.xml b/Sample/src/main/res/layout/activity_main.xml index 65c0f456..8bb46767 100644 --- a/Sample/src/main/res/layout/activity_main.xml +++ b/Sample/src/main/res/layout/activity_main.xml @@ -22,7 +22,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" - tools:context=".MainActivity" > + tools:context=".BiliMainActivity" > diff --git a/Sample/src/main/res/layout/layout_danmaku.xml b/Sample/src/main/res/layout/layout_danmaku.xml new file mode 100644 index 00000000..d253c2bf --- /dev/null +++ b/Sample/src/main/res/layout/layout_danmaku.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/Sample/src/main/res/layout/layout_entrance.xml b/Sample/src/main/res/layout/layout_entrance.xml new file mode 100644 index 00000000..585e7d01 --- /dev/null +++ b/Sample/src/main/res/layout/layout_entrance.xml @@ -0,0 +1,30 @@ + + + +