/*
* 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.graphics.Canvas;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import tv.cjump.jni.DeviceUtils;
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.GlobalFlagValues;
import master.flame.danmaku.danmaku.model.IDisplayer;
import master.flame.danmaku.danmaku.model.android.AndroidDisplayer;
import master.flame.danmaku.danmaku.model.android.DanmakuGlobalConfig;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.danmaku.parser.DanmakuFactory;
import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState;
import master.flame.danmaku.danmaku.util.AndroidUtils;
import java.util.LinkedList;
public class DrawHandler extends Handler {
public interface Callback {
public void prepared();
public void updateTimer(DanmakuTimer timer);
}
public static final int START = 1;
public static final int UPDATE = 2;
public static final int RESUME = 3;
public static final int SEEK_POS = 4;
public static final int PREPARE = 5;
private static final int QUIT = 6;
private static final int PAUSE = 7;
private static final int SHOW_DANMAKUS = 8;
private static final int HIDE_DANMAKUS = 9;
private static final int NOTIFY_DISP_SIZE_CHANGED = 10;
private static final int NOTIFY_RENDERING = 11;
private static final int UPDATE_WHEN_PAUSED = 12;
private static final int CLEAR_DANMAKUS_ON_SCREEN = 13;
private static final long INDEFINITE_TIME = 10000000;
private long pausedPosition = 0;
private boolean quitFlag = true;
private long mTimeBase;
private boolean mReady;
private Callback mCallback;
private DanmakuTimer timer = new DanmakuTimer();
private BaseDanmakuParser mParser;
public IDrawTask drawTask;
private IDanmakuViewController mDanmakuView;
private boolean mDanmakusVisible = true;
private AbsDisplayer<Canvas> mDisp;
private final RenderingState mRenderingState = new RenderingState();
private int mSkipFrames;
private static final int MAX_RECORD_SIZE = 500;
private LinkedList<Long> mDrawTimes = new LinkedList<Long>();
private UpdateThread mThread;
private final boolean mUpdateInNewThread;
private long mCordonTime = 30;
@SuppressWarnings("unused")
private long mCordonTime2 = 60;
private long mFrameUpdateRate = 16;
@SuppressWarnings("unused")
private long mThresholdTime;
private long mLastDeltaTime;
private boolean mInSeekingAction;
private long mRemainingTime;
private boolean mInSyncAction;
private boolean mInWaitingState;
private boolean mIdleSleep;
public DrawHandler(Looper looper, IDanmakuViewController view, boolean danmakuVisibile) {
super(looper);
mUpdateInNewThread = (Runtime.getRuntime().availableProcessors() > 3);
mIdleSleep = !DeviceUtils.isProblemBoxDevice();
bindView(view);
if(danmakuVisibile){
showDanmakus(null);
}else{
hideDanmakus(false);
}
mDanmakusVisible = danmakuVisibile;
}
private void bindView(IDanmakuViewController view) {
this.mDanmakuView = view;
}
public void setParser(BaseDanmakuParser parser) {
mParser = parser;
}
public void setCallback(Callback cb) {
mCallback = cb;
}
public void quit() {
sendEmptyMessage(QUIT);
}
public boolean isStop() {
return quitFlag;
}
@Override
public void handleMessage(Message msg) {
int what = msg.what;
switch (what) {
case PREPARE:
if (mParser == null || !mDanmakuView.isViewReady()) {
sendEmptyMessageDelayed(PREPARE, 100);
} else {
prepare(new Runnable() {
@Override
public void run() {
mReady = true;
if (mCallback != null) {
mCallback.prepared();
}
}
});
}
break;
case START:
Long startTime = (Long) msg.obj;
if (startTime != null) {
pausedPosition = startTime;
} else {
pausedPosition = 0;
}
case RESUME:
quitFlag = false;
if (mReady) {
mDrawTimes.clear();
mTimeBase = System.currentTimeMillis() - pausedPosition;
timer.update(pausedPosition);
removeMessages(RESUME);
sendEmptyMessage(UPDATE);
drawTask.start();
notifyRendering();
mInSeekingAction = false;
} else {
sendEmptyMessageDelayed(RESUME, 100);
}
break;
case SEEK_POS:
quitFlag = true;
quitUpdateThread();
Long position = (Long) msg.obj;
long deltaMs = position - timer.currMillisecond;
mTimeBase -= deltaMs;
timer.update(System.currentTimeMillis() - mTimeBase);
if (drawTask != null)
drawTask.seek(timer.currMillisecond);
pausedPosition = timer.currMillisecond;
removeMessages(RESUME);
sendEmptyMessage(RESUME);
break;
case UPDATE:
if (mUpdateInNewThread) {
updateInNewThread();
} else {
updateInCurrentThread();
}
break;
case NOTIFY_DISP_SIZE_CHANGED:
DanmakuFactory.notifyDispSizeChanged(mDisp);
Boolean updateFlag = (Boolean) msg.obj;
if(updateFlag != null && updateFlag){
GlobalFlagValues.updateMeasureFlag();
}
break;
case SHOW_DANMAKUS:
Long start = (Long) msg.obj;
if(drawTask != null) {
if (start == null) {
timer.update(getCurrentTime());
drawTask.requestClear();
} else {
drawTask.start();
drawTask.seek(start);
drawTask.requestClear();
obtainMessage(START, start).sendToTarget();
}
}
mDanmakusVisible = true;
if(quitFlag && mDanmakuView != null) {
mDanmakuView.drawDanmakus();
}
notifyRendering();
break;
case HIDE_DANMAKUS:
mDanmakusVisible = false;
if (mDanmakuView != null) {
mDanmakuView.clear();
}
if(this.drawTask != null) {
this.drawTask.requestClear();
this.drawTask.requestHide();
}
Boolean quitDrawTask = (Boolean) msg.obj;
if (quitDrawTask && this.drawTask != null) {
this.drawTask.quit();
}
if (!quitDrawTask) {
break;
}
case PAUSE:
removeMessages(UPDATE);
case QUIT:
if (what == QUIT) {
removeCallbacksAndMessages(null);
}
quitFlag = true;
syncTimerIfNeeded();
mSkipFrames = 0;
if (mThread != null) {
notifyRendering();
quitUpdateThread();
}
pausedPosition = timer.currMillisecond;
if (what == QUIT){
if (this.drawTask != null){
this.drawTask.quit();
}
if (mParser != null) {
mParser.release();
}
if (this.getLooper() != Looper.getMainLooper())
this.getLooper().quit();
}
break;
case NOTIFY_RENDERING:
notifyRendering();
break;
case UPDATE_WHEN_PAUSED:
if (quitFlag && mDanmakuView != null) {
drawTask.requestClear();
mDanmakuView.drawDanmakus();
notifyRendering();
}
break;
case CLEAR_DANMAKUS_ON_SCREEN:
if (drawTask != null) {
drawTask.clearDanmakusOnScreen(getCurrentTime());
}
break;
}
}
private void quitUpdateThread() {
if (mThread != null) {
synchronized (drawTask) {
drawTask.notifyAll();
}
mThread.quit();
try {
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mThread = null;
}
}
private void updateInCurrentThread() {
if (quitFlag) {
return;
}
long startMS = System.currentTimeMillis();
long d = syncTimer(startMS);
if (d < 0) {
removeMessages(UPDATE);
sendEmptyMessageDelayed(UPDATE, 60 - d);
return;
}
d = mDanmakuView.drawDanmakus();
removeMessages(UPDATE);
if (!mDanmakusVisible) {
waitRendering(INDEFINITE_TIME);
return;
} else if (mRenderingState.nothingRendered && mIdleSleep) {
long dTime = mRenderingState.endTime - timer.currMillisecond;
if (dTime > 500) {
waitRendering(dTime - 400);
return;
}
}
if (d < mFrameUpdateRate) {
sendEmptyMessageDelayed(UPDATE, mFrameUpdateRate - d);
return;
}
sendEmptyMessage(UPDATE);
}
private void updateInNewThread() {
if (mThread != null) {
return;
}
mThread = new UpdateThread("DFM Update") {
@Override
public void run() {
long lastTime = System.currentTimeMillis();
long dTime = 0;
while (!isQuited() && !quitFlag) {
long startMS = System.currentTimeMillis();
dTime = System.currentTimeMillis() - lastTime;
long diffTime = mFrameUpdateRate - dTime;
if (diffTime > 1) {
SystemClock.sleep(1);
continue;
}
lastTime = startMS;
long d = syncTimer(startMS);
if (d < 0) {
SystemClock.sleep(60 - d);
continue;
}
d = mDanmakuView.drawDanmakus();
if (!mDanmakusVisible) {
waitRendering(INDEFINITE_TIME);
} else if (mRenderingState.nothingRendered && mIdleSleep) {
dTime = mRenderingState.endTime - timer.currMillisecond;
if (dTime > 500) {
notifyRendering();
waitRendering(dTime - 400);
}
}
}
}
};
mThread.start();
}
private final long syncTimer(long startMS) {
if (mInSeekingAction || mInSyncAction) {
return 0;
}
mInSyncAction = true;
long d = 0;
long time = startMS - mTimeBase;
if (!mDanmakusVisible || mRenderingState.nothingRendered || mInWaitingState) {
timer.update(time);
mRemainingTime = 0;
} else {
long gapTime = time - timer.currMillisecond;
long averageTime = Math.max(mFrameUpdateRate, getAverageRenderingTime());
if (gapTime > 2000 || mRenderingState.consumingTime > mCordonTime || averageTime > mCordonTime) {
d = gapTime;
gapTime = 0;
} else {
d = averageTime + gapTime / mFrameUpdateRate;
d = Math.max(mFrameUpdateRate, d);
d = Math.min(mCordonTime, d);
long a = d - mLastDeltaTime;
if (Math.abs(a) < 4 && d > mFrameUpdateRate && mLastDeltaTime > mFrameUpdateRate) {
d = mLastDeltaTime;
}
gapTime -= d;
}
mLastDeltaTime = d;
mRemainingTime = gapTime;
timer.add(d);
// Log.e("DrawHandler", time+"|d:" + d + "RemaingTime:" + mRemainingTime + ",gapTime:" + gapTime + ",rtim:" + mRenderingState.consumingTime + ",average:" + averageTime);
}
if (mCallback != null) {
mCallback.updateTimer(timer);
}
mInSyncAction = false;
return d;
}
private void syncTimerIfNeeded() {
if (mInWaitingState) {
syncTimer(System.currentTimeMillis());
}
}
private void initRenderingConfigs() {
long averageFrameConsumingTime = 16;
mCordonTime = Math.max(33, (long) (averageFrameConsumingTime * 2.5f));
mCordonTime2 = mCordonTime * 2;
mFrameUpdateRate = Math.max(16, averageFrameConsumingTime / 15 * 15);
mLastDeltaTime = mFrameUpdateRate;
mThresholdTime = mFrameUpdateRate + 3;
// Log.i("DrawHandler", "initRenderingConfigs test-fps:" + averageFrameConsumingTime + "ms,mCordonTime:"
// + mCordonTime + ",mFrameRefreshingRate:" + mFrameUpdateRate);
}
private void prepare(final Runnable runnable) {
if (drawTask == null) {
drawTask = createDrawTask(mDanmakuView.isDanmakuDrawingCacheEnabled(), timer,
mDanmakuView.getContext(), mDanmakuView.getWidth(), mDanmakuView.getHeight(),
mDanmakuView.isHardwareAccelerated(), new IDrawTask.TaskListener() {
@Override
public void ready() {
initRenderingConfigs();
runnable.run();
}
@Override
public void onDanmakuAdd(BaseDanmaku danmaku) {
obtainMessage(NOTIFY_RENDERING).sendToTarget();
}
@Override
public void onDanmakuConfigChanged() {
if (quitFlag && mDanmakusVisible) {
obtainMessage(UPDATE_WHEN_PAUSED).sendToTarget();
}
}
});
} else {
runnable.run();
}
}
public boolean isPrepared() {
return mReady;
}
private IDrawTask createDrawTask(boolean useDrwaingCache, DanmakuTimer timer, Context context,
int width, int height, boolean isHardwareAccelerated,
IDrawTask.TaskListener taskListener) {
mDisp = new AndroidDisplayer();
mDisp.setSize(width, height);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
mDisp.setDensities(displayMetrics.density, displayMetrics.densityDpi,
displayMetrics.scaledDensity);
mDisp.resetSlopPixel(DanmakuGlobalConfig.DEFAULT.scaleTextSize);
mDisp.setHardwareAccelerated(isHardwareAccelerated);
obtainMessage(NOTIFY_DISP_SIZE_CHANGED, false).sendToTarget();
IDrawTask task = useDrwaingCache ? new CacheManagingDrawTask(timer, context, mDisp,
taskListener, 1024 * 1024 * AndroidUtils.getMemoryClass(context) / 3)
: new DrawTask(timer, context, mDisp, taskListener);
task.setParser(mParser);
task.prepare();
return task;
}
public void seekTo(Long ms) {
mInSeekingAction = true;
removeMessages(DrawHandler.UPDATE);
removeMessages(DrawHandler.RESUME);
removeMessages(DrawHandler.SEEK_POS);
obtainMessage(DrawHandler.SEEK_POS, ms).sendToTarget();
}
public void addDanmaku(BaseDanmaku item) {
if (drawTask != null) {
item.setTimer(timer);
drawTask.addDanmaku(item);
obtainMessage(NOTIFY_RENDERING).sendToTarget();
}
}
public void resume() {
sendEmptyMessage(DrawHandler.RESUME);
}
public void prepare() {
sendEmptyMessage(DrawHandler.PREPARE);
}
public void pause() {
syncTimerIfNeeded();
sendEmptyMessage(DrawHandler.PAUSE);
}
public void showDanmakus(Long position) {
if (mDanmakusVisible)
return;
removeMessages(SHOW_DANMAKUS);
removeMessages(HIDE_DANMAKUS);
obtainMessage(SHOW_DANMAKUS, position).sendToTarget();
}
public long hideDanmakus(boolean quitDrawTask) {
if (!mDanmakusVisible)
return timer.currMillisecond;
removeMessages(SHOW_DANMAKUS);
removeMessages(HIDE_DANMAKUS);
obtainMessage(HIDE_DANMAKUS, quitDrawTask).sendToTarget();
return timer.currMillisecond;
}
public boolean getVisibility() {
return mDanmakusVisible;
}
public RenderingState draw(Canvas canvas) {
if (drawTask == null)
return mRenderingState;
mDisp.setExtraData(canvas);
mRenderingState.set(drawTask.draw(mDisp));
recordRenderingTime();
return mRenderingState;
}
private void notifyRendering() {
if (!mInWaitingState) {
return;
}
if(drawTask != null) {
drawTask.requestClear();
}
mSkipFrames = 0;
if (mUpdateInNewThread) {
synchronized (this) {
mDrawTimes.clear();
}
synchronized (drawTask) {
drawTask.notifyAll();
}
} else {
mDrawTimes.clear();
removeMessages(UPDATE);
sendEmptyMessage(UPDATE);
}
mInWaitingState = false;
}
private void waitRendering(long dTime) {
mRenderingState.sysTime = System.currentTimeMillis();
mInWaitingState = true;
if (mUpdateInNewThread) {
try {
synchronized (drawTask) {
if (dTime == INDEFINITE_TIME) {
drawTask.wait();
} else {
drawTask.wait(dTime);
}
sendEmptyMessage(NOTIFY_RENDERING);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
if (dTime == INDEFINITE_TIME) {
removeMessages(NOTIFY_RENDERING);
removeMessages(UPDATE);
} else {
removeMessages(NOTIFY_RENDERING);
removeMessages(UPDATE);
sendEmptyMessageDelayed(NOTIFY_RENDERING, dTime);
}
}
}
private synchronized long getAverageRenderingTime() {
int frames = mDrawTimes.size();
if(frames <= 0)
return 0;
long dtime = mDrawTimes.getLast() - mDrawTimes.getFirst();
return dtime / frames;
}
private synchronized void recordRenderingTime() {
long lastTime = System.currentTimeMillis();
mDrawTimes.addLast(lastTime);
int frames = mDrawTimes.size();
if (frames > MAX_RECORD_SIZE) {
mDrawTimes.removeFirst();
frames = MAX_RECORD_SIZE;
}
}
public IDisplayer getDisplayer(){
return mDisp;
}
public void notifyDispSizeChanged(int width, int height) {
if (mDisp == null) {
return;
}
if (mDisp.getWidth() != width || mDisp.getHeight() != height) {
mDisp.setSize(width, height);
obtainMessage(NOTIFY_DISP_SIZE_CHANGED, true).sendToTarget();
}
}
public void removeAllDanmakus() {
if (drawTask != null) {
drawTask.removeAllDanmakus();
}
}
public void removeAllLiveDanmakus() {
if (drawTask != null) {
drawTask.removeAllLiveDanmakus();
}
}
public long getCurrentTime() {
if (quitFlag || !mInWaitingState) {
return timer.currMillisecond - mRemainingTime;
}
return System.currentTimeMillis() - mTimeBase;
}
public void clearDanmakusOnScreen() {
obtainMessage(CLEAR_DANMAKUS_ON_SCREEN).sendToTarget();
}
}