/*
* Copyright (C) 2013 Chen Hui <calmer91@gmail.com>
*
* 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.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
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.IDanmakuIterator;
import master.flame.danmaku.danmaku.model.IDanmakus;
import master.flame.danmaku.danmaku.model.IDrawingCache;
import master.flame.danmaku.danmaku.model.android.DanmakuGlobalConfig;
import master.flame.danmaku.danmaku.model.android.DanmakuGlobalConfig.DanmakuConfigTag;
import master.flame.danmaku.danmaku.model.android.Danmakus;
import master.flame.danmaku.danmaku.model.android.DrawingCache;
import master.flame.danmaku.danmaku.model.android.DrawingCachePoolManager;
import master.flame.danmaku.danmaku.model.objectpool.Pool;
import master.flame.danmaku.danmaku.model.objectpool.Pools;
import master.flame.danmaku.danmaku.parser.DanmakuFactory;
import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState;
import master.flame.danmaku.danmaku.util.DanmakuUtils;
import tv.cjump.jni.NativeBitmapFactory;
public class CacheManagingDrawTask extends DrawTask {
private static final int MAX_CACHE_SCREEN_SIZE = 3;
private int mMaxCacheSize = 2;
private CacheManager mCacheManager;
private DanmakuTimer mCacheTimer;
private final Object mDrawingNotify = new Object();
public CacheManagingDrawTask(DanmakuTimer timer, Context context, AbsDisplayer<?> disp,
TaskListener taskListener, int maxCacheSize) {
super(timer, context, disp, taskListener);
NativeBitmapFactory.loadLibs();
mMaxCacheSize = maxCacheSize;
if (NativeBitmapFactory.isInNativeAlloc()) {
mMaxCacheSize = maxCacheSize * 3;
}
mCacheManager = new CacheManager(maxCacheSize, MAX_CACHE_SCREEN_SIZE);
}
@Override
protected void initTimer(DanmakuTimer timer) {
mTimer = timer;
mCacheTimer = new DanmakuTimer();
mCacheTimer.update(timer.currMillisecond);
}
@Override
public void addDanmaku(BaseDanmaku danmaku) {
if (mCacheManager == null)
return;
mCacheManager.addDanmaku(danmaku);
}
@Override
public RenderingState draw(AbsDisplayer<?> displayer) {
RenderingState result = null;
synchronized (danmakuList) {
result = super.draw(displayer);
}
synchronized(mDrawingNotify){
mDrawingNotify.notify();
}
if(result != null && mCacheManager != null) {
if(result.incrementCount < -20) {
mCacheManager.requestClearTimeout();
mCacheManager.requestBuild(-DanmakuFactory.MAX_DANMAKU_DURATION);
}
}
return result;
}
@Override
public void reset() {
// mCacheTimer.update(mTimer.currMillisecond);
if (mRenderer != null)
mRenderer.clear();
}
@Override
public void seek(long mills) {
super.seek(mills);
mCacheManager.seek(mills);
}
@Override
public void start() {
super.start();
NativeBitmapFactory.loadLibs();
if (mCacheManager == null) {
mCacheManager = new CacheManager(mMaxCacheSize, MAX_CACHE_SCREEN_SIZE);
mCacheManager.begin();
} else {
mCacheManager.resume();
}
}
@Override
public void quit() {
super.quit();
reset();
if(mCacheManager!=null){
mCacheManager.end();
mCacheManager = null;
}
NativeBitmapFactory.releaseLibs();
}
@Override
public void prepare() {
assert (mParser != null);
loadDanmakus(mParser);
mCacheManager.begin();
}
public class CacheManager {
@SuppressWarnings("unused")
private static final String TAG = "CacheManager";
public static final byte RESULT_SUCCESS = 0;
public static final byte RESULT_FAILED = 1;
public static final byte RESULT_FAILED_OVERSIZE = 2;
public HandlerThread mThread;
Danmakus mCaches = new Danmakus(Danmakus.ST_BY_LIST);
DrawingCachePoolManager mCachePoolManager = new DrawingCachePoolManager();
Pool<DrawingCache> mCachePool = Pools.finitePool(mCachePoolManager, 800);
private int mMaxSize;
private int mRealSize;
private int mScreenSize = 3;
private CacheHandler mHandler;
private boolean mEndFlag;
public CacheManager(int maxSize, int screenSize) {
mEndFlag = false;
mRealSize = 0;
mMaxSize = maxSize;
mScreenSize = screenSize;
}
public void seek(long mills) {
if (mHandler == null)
return;
mHandler.requestCancelCaching();
mHandler.removeMessages(CacheHandler.BUILD_CACHES);
mHandler.obtainMessage(CacheHandler.SEEK, mills).sendToTarget();
}
public void addDanmaku(BaseDanmaku danmaku) {
if (mHandler != null) {
mHandler.obtainMessage(CacheHandler.ADD_DANMAKKU, danmaku).sendToTarget();
}
}
public void begin() {
if (mThread == null) {
mThread = new HandlerThread("DFM Cache-Building Thread");
mThread.start();
}
if (mHandler == null)
mHandler = new CacheHandler(mThread.getLooper());
mHandler.begin();
}
public void end() {
mEndFlag = true;
synchronized (mDrawingNotify) {
mDrawingNotify.notifyAll();
}
if (mHandler != null) {
mHandler.pause();
mHandler = null;
}
if (mThread != null) {
try {
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mThread.quit();
mThread = null;
}
}
public void resume() {
if (mHandler != null) {
mHandler.resume();
} else {
begin();
}
}
public float getPoolPercent(){
if(mMaxSize == 0){
return 0;
}
return mRealSize/(float)mMaxSize;
}
public boolean isPoolFull(){
return mRealSize + 5120 >= mMaxSize;
}
private void evictAll() {
if (mCaches != null) {
IDanmakuIterator it = mCaches.iterator();
while(it.hasNext()) {
BaseDanmaku danmaku = it.next();
entryRemoved(true, danmaku, null);
}
mCaches.clear();
}
mRealSize = 0;
}
private void evictAllNotInScreen() {
evictAllNotInScreen(false);
}
private void evictAllNotInScreen(boolean removeAllReferences) {
if (mCaches != null) {
IDanmakuIterator it = mCaches.iterator();
while (it.hasNext()) {
BaseDanmaku danmaku = it.next();
IDrawingCache<?> cache = danmaku.cache;
boolean hasReferences = cache != null && cache.hasReferences();
if (removeAllReferences && hasReferences) {
if (cache.get() != null) {
mRealSize -= cache.size();
cache.destroy();
}
entryRemoved(true, danmaku, null);
it.remove();
continue;
}
if (danmaku.hasDrawingCache() == false || danmaku.isOutside()) {
entryRemoved(true, danmaku, null);
it.remove();
}
}
// mCaches.clear();
}
mRealSize = 0;
}
protected void entryRemoved(boolean evicted, BaseDanmaku oldValue, BaseDanmaku newValue) {
if (oldValue.cache != null) {
if (oldValue.cache.hasReferences()) {
oldValue.cache.decreaseReference();
oldValue.cache = null;
return;
}
mRealSize -= sizeOf(oldValue);
oldValue.cache.destroy();
mCachePool.release((DrawingCache) oldValue.cache);
oldValue.cache = null;
}
}
protected int sizeOf(BaseDanmaku value) {
if (value.cache != null && !value.cache.hasReferences()) {
return value.cache.size();
}
return 0;
}
private void clearCachePool() {
DrawingCache item;
while ((item = mCachePool.acquire()) != null) {
item.destroy();
}
}
private boolean push(BaseDanmaku item, int itemSize) {
int size = itemSize; //sizeOf(item);
while (mRealSize + size > mMaxSize && mCaches.size() > 0) {
BaseDanmaku oldValue = mCaches.first();
if (oldValue.isTimeOut()) {
entryRemoved(false, oldValue, item);
mCaches.removeItem(oldValue);
} else {
return false;
}
}
this.mCaches.addItem(item);
mRealSize += size;
//Log.e("CACHE", "realsize:"+mRealSize + ",size" + size);
return true;
}
private void clearTimeOutCaches() {
clearTimeOutCaches(mTimer.currMillisecond);
}
private void clearTimeOutCaches(long time) {
IDanmakuIterator it = mCaches.iterator();
while (it.hasNext() && !mEndFlag) {
BaseDanmaku val = it.next();
if (val.isTimeOut()) {
synchronized (mDrawingNotify) {
try {
mDrawingNotify.wait(30);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
entryRemoved(false, val, null);
it.remove();
} else {
break;
}
}
}
private BaseDanmaku findReuseableCache(BaseDanmaku refDanmaku,
boolean strictMode) {
IDanmakuIterator it = mCaches.iterator();
int slopPixel = 0;
if (!strictMode) {
slopPixel = mDisp.getSlopPixel() * 2;
}
while (it.hasNext()) {
BaseDanmaku danmaku = it.next();
if (!danmaku.hasDrawingCache()) {
continue;
}
if (danmaku.paintWidth == refDanmaku.paintWidth
&& danmaku.paintHeight == refDanmaku.paintHeight
&& danmaku.underlineColor == refDanmaku.underlineColor
&& danmaku.borderColor == refDanmaku.borderColor
&& danmaku.textColor == refDanmaku.textColor
&& danmaku.text.equals(refDanmaku.text)) {
return danmaku;
}
if (strictMode) {
continue;
}
if (!danmaku.isTimeOut()) {
break;
}
if (danmaku.cache.hasReferences()) {
continue;
}
float widthGap = danmaku.cache.width() - refDanmaku.paintWidth;
float heightGap = danmaku.cache.height() - refDanmaku.paintHeight;
if (widthGap >= 0 && widthGap <= slopPixel &&
heightGap >= 0 && heightGap <= slopPixel) {
return danmaku;
}
}
return null;
}
int danmakuAddedCount = 0;
public class CacheHandler extends Handler {
private static final int PREPARE = 0x1;
public static final int ADD_DANMAKKU = 0x2;
public static final int BUILD_CACHES = 0x3;
public static final int CLEAR_TIMEOUT_CACHES = 0x4;
public static final int SEEK = 0x5;
public static final int QUIT = 0x6;
public static final int CLEAR_ALL_CACHES = 0x7;
public static final int CLEAR_OUTSIDE_CACHES = 0x8;
public static final int CLEAR_OUTSIDE_CACHES_AND_RESET = 0x9;
public static final int DISPATCH_ACTIONS = 0x10;
private boolean mPause;
private boolean mSeekedFlag;
private boolean mCancelFlag;
public CacheHandler(android.os.Looper looper) {
super(looper);
}
public void requestCancelCaching() {
mCancelFlag = true;
}
@Override
public void handleMessage(Message msg) {
int what = msg.what;
switch (what) {
case PREPARE:
evictAllNotInScreen();
for (int i = 0; i < 300; i++) {
mCachePool.release(new DrawingCache());
}
case DISPATCH_ACTIONS:
//Log.e(TAG,"dispatch_actions:"+mCacheTimer.currMillisecond+":"+mTimer.currMillisecond);
long delayed = dispatchAction();
if (delayed <= 0) {
delayed = DanmakuFactory.MAX_DANMAKU_DURATION / 2;
}
sendEmptyMessageDelayed(DISPATCH_ACTIONS, delayed);
break;
case BUILD_CACHES:
removeMessages(BUILD_CACHES);
boolean repositioned = ((mTaskListener != null && mReadyState == false) || mSeekedFlag);
prepareCaches(repositioned);
if (repositioned)
mSeekedFlag = false;
if (mTaskListener != null && mReadyState == false) {
mTaskListener.ready();
mReadyState = true;
}
// Log.i(TAG,"BUILD_CACHES:"+mCacheTimer.currMillisecond+":"+mTimer.currMillisecond);
break;
case ADD_DANMAKKU:
synchronized (danmakuList) {
BaseDanmaku item = (BaseDanmaku) msg.obj;
if(item.isTimeOut()) {
break;
}
if(!item.hasDrawingCache()) {
buildCache(item);
}
if (item.isLive) {
mCacheTimer.update(mTimer.currMillisecond
+ DanmakuFactory.MAX_DANMAKU_DURATION * mScreenSize);
}
CacheManagingDrawTask.super.addDanmaku(item);
}
break;
case CLEAR_TIMEOUT_CACHES:
clearTimeOutCaches();
break;
case SEEK:
Long seekMills = (Long) msg.obj;
if (seekMills != null) {
mCacheTimer.update(seekMills.longValue());
mSeekedFlag = true;
evictAllNotInScreen();
resume();
}
break;
case QUIT:
removeCallbacksAndMessages(null);
mPause = true;
evictAll();
clearCachePool();
this.getLooper().quit();
break;
case CLEAR_ALL_CACHES:
evictAll();
mCacheTimer.update(mTimer.currMillisecond - DanmakuFactory.MAX_DANMAKU_DURATION);
mSeekedFlag = true;
break;
case CLEAR_OUTSIDE_CACHES:
evictAllNotInScreen(true);
mCacheTimer.update(mTimer.currMillisecond);
break;
case CLEAR_OUTSIDE_CACHES_AND_RESET:
evictAllNotInScreen(true);
mCacheTimer.update(mTimer.currMillisecond);
requestClear();
break;
}
}
private long dispatchAction() {
float level = getPoolPercent();
BaseDanmaku firstCache = mCaches.first();
//TODO 如果firstcache大于当前时间超过半屏并且水位在0.5f以下,
long gapTime = firstCache != null ? firstCache.time - mTimer.currMillisecond : 0;
long doubleScreenDuration = DanmakuFactory.MAX_DANMAKU_DURATION * 2;
if (level < 0.6f && gapTime > DanmakuFactory.MAX_DANMAKU_DURATION) {
mCacheTimer.update(mTimer.currMillisecond);
removeMessages(BUILD_CACHES);
sendEmptyMessage(BUILD_CACHES);
return 0;
} else if (level > 0.4f && gapTime < -doubleScreenDuration) {
// clear timeout caches
removeMessages(CLEAR_TIMEOUT_CACHES);
sendEmptyMessage(CLEAR_TIMEOUT_CACHES);
return 0;
}
if (level >= 0.9f) {
return 0;
}
// check cache time
long deltaTime = mCacheTimer.currMillisecond - mTimer.currMillisecond;
if (deltaTime < 0) {
mCacheTimer.update(mTimer.currMillisecond);
sendEmptyMessage(CLEAR_OUTSIDE_CACHES);
sendEmptyMessage(BUILD_CACHES);
return 0;
} else if (deltaTime > doubleScreenDuration) {
return 0;
}
removeMessages(BUILD_CACHES);
sendEmptyMessage(BUILD_CACHES);
return 0;
}
private void releaseDanmakuCache(BaseDanmaku item, DrawingCache cache) {
if (cache == null) {
cache = (DrawingCache) item.cache;
}
item.cache = null;
if (cache == null) {
return;
}
cache.destroy();
mCachePool.release(cache);
}
private long prepareCaches(boolean repositioned) {
long curr = mCacheTimer.currMillisecond;
long end = curr + DanmakuFactory.MAX_DANMAKU_DURATION * mScreenSize * 3;
if (end < mTimer.currMillisecond) {
return 0;
}
long startTime = System.currentTimeMillis();
IDanmakus danmakus = danmakuList.subnew(curr, end);
if (danmakus == null || danmakus.isEmpty()) {
mCacheTimer.update(end);
return 0;
}
BaseDanmaku first = danmakus.first();
BaseDanmaku last = danmakus.last();
long deltaTime = first.time - mTimer.currMillisecond;
long sleepTime = 30 + 10 * deltaTime / DanmakuFactory.MAX_DANMAKU_DURATION;
sleepTime = Math.min(100, sleepTime);
if (repositioned) {
sleepTime = 0;
}
IDanmakuIterator itr = danmakus.iterator();
BaseDanmaku item = null;
long consumingTime = 0;
int count = 0;
int orderInScreen = 0;
int currScreenIndex = 0;
int sizeInScreen = danmakus.size();
// String message = "";
while (!mPause && !mCancelFlag) {
boolean hasNext = itr.hasNext();
if(!hasNext){
// message = "break at not hasNext";
break;
}
item = itr.next();
count++;
if (last.time < mTimer.currMillisecond) {
// message = "break at last.time < mTimer.currMillisecond";
break;
}
if(item.hasDrawingCache()){
continue;
}
if (repositioned == false && (item.isTimeOut() || !item.isOutside())) {
continue;
}
boolean skip = DanmakuFilters.getDefault().filter(item, orderInScreen,
sizeInScreen, null, true);
//Log.e("prepareCache", currScreenIndex+","+orderInScreen+"," + item.time+"skip:"+skip);
if (skip) {
continue;
}
if(item.getType() == BaseDanmaku.TYPE_SCROLL_RL){
// 同屏弹幕密度只对滚动弹幕有效
int screenIndex = (int) ((item.time - curr)/DanmakuFactory.MAX_DANMAKU_DURATION);
if(currScreenIndex == screenIndex)
orderInScreen++;
else{
orderInScreen = 0;
currScreenIndex = screenIndex;
}
}
if (!repositioned) {
try {
synchronized (mDrawingNotify) {
mDrawingNotify.wait(sleepTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
// build cache
if (buildCache(item) == RESULT_FAILED) {
// message = "break at build failed";
break;
}
if (!repositioned) {
consumingTime = System.currentTimeMillis() - startTime;
if (consumingTime >= DanmakuFactory.COMMON_DANMAKU_DURATION * mScreenSize) {
// message = "break at consumingTime out:" + consumingTime;
break;
}
}
}
consumingTime = System.currentTimeMillis() - startTime;
if (item != null) {
mCacheTimer.update(item.time);
//Log.i("cache","stop at :"+item.time+","+count+",size:"+danmakus.size()+","+message);
}else {
mCacheTimer.update(end);
}
return consumingTime;
}
private byte buildCache(BaseDanmaku item) {
// measure
if (!item.isMeasured()) {
item.measure(mDisp);
}
DrawingCache cache = null;
try {
// try to find reuseable cache
BaseDanmaku danmaku = findReuseableCache(item, true);
if (danmaku != null) {
cache = (DrawingCache) danmaku.cache;
}
if (cache != null) {
cache.increaseReference();
item.cache = cache;
mCacheManager.push(item, sizeOf(item));
return RESULT_SUCCESS;
}
// try to find reuseable cache from timeout && no-refrerence caches
danmaku = findReuseableCache(item, false);
if (danmaku != null) {
cache = (DrawingCache) danmaku.cache;
}
if (cache != null) {
danmaku.cache = null;
//Log.e("cache", danmaku.text+"DrawingCache hit!!:" + item.paintWidth + "," + danmaku.paintWidth);
cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache); //redraw
item.cache = cache;
mCacheManager.push(item, 0);
return RESULT_SUCCESS;
}
// guess cache size
int cacheSize = DanmakuUtils.getCacheSize((int) item.paintWidth,
(int) item.paintHeight);
if (mRealSize + cacheSize > mMaxSize) {
// Log.d("cache", "break at MaxSize:"+mMaxSize);
return RESULT_FAILED;
}
cache = mCachePool.acquire();
synchronized (danmakuList) {
cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache);
item.cache = cache;
boolean pushed = mCacheManager.push(item, sizeOf(item));
if (!pushed) {
releaseDanmakuCache(item, cache);
//Log.e("cache", "break at push failed:" + mMaxSize);
}
return pushed ? RESULT_SUCCESS : RESULT_FAILED;
}
} catch (OutOfMemoryError e) {
//Log.e("cache", "break at error: oom");
releaseDanmakuCache(item, cache);
return RESULT_FAILED;
} catch (Exception e) {
//Log.e("cache", "break at exception:" + e.getMessage());
releaseDanmakuCache(item, cache);
return RESULT_FAILED;
}
}
public void begin() {
sendEmptyMessage(PREPARE);
sendEmptyMessageDelayed(CLEAR_TIMEOUT_CACHES, DanmakuFactory.MAX_DANMAKU_DURATION);
}
public void pause() {
mPause = true;
removeCallbacksAndMessages(null);
sendEmptyMessage(QUIT);
}
public void resume() {
mCancelFlag = false;
mPause = false;
removeMessages(DISPATCH_ACTIONS);
sendEmptyMessage(DISPATCH_ACTIONS);
sendEmptyMessageDelayed(CLEAR_TIMEOUT_CACHES, DanmakuFactory.MAX_DANMAKU_DURATION);
}
public boolean isPause() {
return mPause;
}
public void requestBuildCacheAndDraw(long correctionTime) {
removeMessages(CacheHandler.BUILD_CACHES);
mSeekedFlag = true;
mCancelFlag = false;
mCacheTimer.update(mTimer.currMillisecond + correctionTime);
sendEmptyMessage(CacheHandler.BUILD_CACHES);
}
}
public long getFirstCacheTime() {
if (mCaches != null && mCaches.size() > 0) {
BaseDanmaku firstItem = mCaches.first();
if (firstItem == null)
return 0;
return firstItem.time;
}
return 0;
}
public void requestBuild(long correctionTime) {
if(mHandler != null) {
mHandler.requestBuildCacheAndDraw(correctionTime);
}
}
public void requestClearAll() {
if (mHandler == null) {
return;
}
mHandler.removeMessages(CacheHandler.BUILD_CACHES);
mHandler.requestCancelCaching();
mHandler.removeMessages(CacheHandler.CLEAR_ALL_CACHES);
mHandler.sendEmptyMessage(CacheHandler.CLEAR_ALL_CACHES);
}
public void requestClearUnused() {
if (mHandler == null) {
return;
}
mHandler.removeMessages(CacheHandler.CLEAR_OUTSIDE_CACHES_AND_RESET);
mHandler.sendEmptyMessage(CacheHandler.CLEAR_OUTSIDE_CACHES_AND_RESET);
}
public void requestClearTimeout() {
if (mHandler == null) {
return;
}
mHandler.removeMessages(CacheHandler.CLEAR_TIMEOUT_CACHES);
mHandler.sendEmptyMessage(CacheHandler.CLEAR_TIMEOUT_CACHES);
}
public void post(Runnable runnable) {
if (mHandler == null) {
return;
}
mHandler.post(runnable);
}
}
@Override
public boolean onDanmakuConfigChanged(DanmakuGlobalConfig config, DanmakuConfigTag tag,
Object... values) {
if (super.handleOnDanmakuConfigChanged(config, tag, values)) {
// do nothing
} else if (DanmakuConfigTag.SCROLL_SPEED_FACTOR.equals(tag)) {
mDisp.resetSlopPixel(DanmakuGlobalConfig.DEFAULT.scaleTextSize);
requestClear();
} else if (tag.isVisibilityRelatedTag()) {
if (values != null && values.length > 0) {
if (values[0] != null && ((values[0] instanceof Boolean) == false || ((Boolean) values[0]).booleanValue())) {
if (mCacheManager != null) {
mCacheManager.requestBuild(0l);
}
}
}
requestClear();
} else if (DanmakuConfigTag.TRANSPARENCY.equals(tag) || DanmakuConfigTag.SCALE_TEXTSIZE.equals(tag) || DanmakuConfigTag.DANMAKU_STYLE.equals(tag)) {
if (DanmakuConfigTag.SCALE_TEXTSIZE.equals(tag)) {
mDisp.resetSlopPixel(DanmakuGlobalConfig.DEFAULT.scaleTextSize);
}
if (mCacheManager != null) {
mCacheManager.requestClearAll();
mCacheManager.requestBuild(-DanmakuFactory.MAX_DANMAKU_DURATION);
}
} else {
if (mCacheManager != null) {
mCacheManager.requestClearUnused();
mCacheManager.requestBuild(0l);
}
}
if (mTaskListener != null && mCacheManager != null) {
mCacheManager.post(new Runnable() {
@Override
public void run() {
mTaskListener.onDanmakuConfigChanged();
}
});
}
return true;
}
}