/*
* 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.ui.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import master.flame.danmaku.controller.DrawHandler;
import master.flame.danmaku.controller.IDanmakuView;
import master.flame.danmaku.controller.IDanmakuViewController;
import master.flame.danmaku.controller.DrawHandler.Callback;
import master.flame.danmaku.controller.DrawHelper;
import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.parser.BaseDanmakuParser;
import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState;
import java.util.LinkedList;
import java.util.Locale;
public class DanmakuView extends View implements IDanmakuView, IDanmakuViewController {
public static final String TAG = "DanmakuView";
private Callback mCallback;
private HandlerThread mHandlerThread;
private DrawHandler handler;
private boolean isSurfaceCreated;
private boolean mEnableDanmakuDrwaingCache = true;
private boolean mShowFps;
private boolean mDanmakuVisible = true;
protected int mDrawingThreadType = THREAD_TYPE_NORMAL_PRIORITY;
private Object mDrawMonitor = new Object();
private boolean mDrawFinished = false;
private boolean mRequestRender = false;
private long mUiThreadId;
public DanmakuView(Context context) {
super(context);
init();
}
private void init() {
mUiThreadId = Thread.currentThread().getId();
setBackgroundColor(Color.TRANSPARENT);
setDrawingCacheBackgroundColor(Color.TRANSPARENT);
DrawHelper.useDrawColorToClearCanvas(true, false);
}
public DanmakuView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DanmakuView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void addDanmaku(BaseDanmaku item) {
if (handler != null) {
handler.addDanmaku(item);
}
}
@Override
public void removeAllDanmakus() {
if (handler != null) {
handler.removeAllDanmakus();
}
}
@Override
public void removeAllLiveDanmakus() {
if (handler != null) {
handler.removeAllLiveDanmakus();
}
}
public void setCallback(Callback callback) {
mCallback = callback;
if (handler != null) {
handler.setCallback(callback);
}
}
@Override
public void release() {
stop();
if(mDrawTimes!= null) mDrawTimes.clear();
}
@Override
public void stop() {
stopDraw();
}
private void stopDraw() {
if (handler != null) {
handler.quit();
handler = null;
}
if (mHandlerThread != null) {
try {
mHandlerThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandlerThread.quit();
mHandlerThread = null;
}
}
protected 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();
return mHandlerThread.getLooper();
}
private void prepare() {
if (handler == null)
handler = new DrawHandler(getLooper(mDrawingThreadType), this, mDanmakuVisible);
}
@Override
public void prepare(BaseDanmakuParser parser) {
prepare();
handler.setParser(parser);
handler.setCallback(mCallback);
handler.prepare();
}
@Override
public boolean isPrepared() {
return handler != null && handler.isPrepared();
}
@Override
public void showFPS(boolean show){
mShowFps = show;
}
private static final int MAX_RECORD_SIZE = 50;
private static final int ONE_SECOND = 1000;
private LinkedList<Long> mDrawTimes;
private boolean mClearFlag;
private float fps() {
long lastTime = System.currentTimeMillis();
mDrawTimes.addLast(lastTime);
float dtime = lastTime - mDrawTimes.getFirst();
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 (!isSurfaceCreated)
return 0;
if (!isShown())
return -1;
long stime = System.currentTimeMillis();
lockCanvas();
return System.currentTimeMillis() - stime;
}
@SuppressLint("NewApi")
private void postInvalidateCompat() {
mRequestRender = true;
if(Build.VERSION.SDK_INT >= 16) {
this.postInvalidateOnAnimation();
} else {
this.postInvalidate();
}
}
private void lockCanvas() {
if(mDanmakuVisible == false) {
return;
}
postInvalidateCompat();
synchronized (mDrawMonitor) {
while ((!mDrawFinished) && (handler != null)) {
try {
mDrawMonitor.wait(200);
} catch (InterruptedException e) {
if (mDanmakuVisible == false || handler == null || handler.isStop()) {
break;
} else {
Thread.currentThread().interrupt();
}
}
}
mDrawFinished = false;
}
}
private void lockCanvasAndClear() {
mClearFlag = true;
lockCanvas();
}
private void unlockCanvasAndPost() {
synchronized (mDrawMonitor) {
mDrawFinished = true;
mDrawMonitor.notifyAll();
}
}
@Override
protected void onDraw(Canvas canvas) {
if ((!mDanmakuVisible) && (!mRequestRender)) {
super.onDraw(canvas);
return;
}
if (mClearFlag) {
DrawHelper.clearCanvas(canvas);
mClearFlag = false;
} else {
if (handler != null) {
RenderingState rs = handler.draw(canvas);
if (mShowFps) {
if (mDrawTimes == null)
mDrawTimes = new LinkedList<Long>();
String fps = String.format(Locale.getDefault(),
"fps %.2f,time:%d s,cache:%d,miss:%d", fps(), getCurrentTime() / 1000,
rs.cacheHitCount, rs.cacheMissCount);
DrawHelper.drawFPS(canvas, fps);
}
}
}
mRequestRender = false;
unlockCanvasAndPost();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (handler != null) {
handler.notifyDispSizeChanged(right - left, bottom - top);
}
isSurfaceCreated = true;
}
public void toggle() {
if (isSurfaceCreated) {
if (handler == null)
start();
else if (handler.isStop()) {
resume();
} else
pause();
}
}
@Override
public void pause() {
if (handler != null)
handler.pause();
}
@Override
public void resume() {
if (handler != null && handler.isPrepared())
handler.resume();
else {
restart();
}
}
@Override
public boolean isPaused() {
if(handler != null) {
return handler.isStop();
}
return false;
}
public void restart() {
stop();
start();
}
@Override
public void start() {
start(0);
}
@Override
public void start(long postion) {
if (handler == null) {
prepare();
}else{
handler.removeCallbacksAndMessages(null);
}
handler.obtainMessage(DrawHandler.START, postion).sendToTarget();
}
public void seekTo(Long ms) {
if(handler != null){
handler.seekTo(ms);
}
}
public void enableDanmakuDrawingCache(boolean enable) {
mEnableDanmakuDrwaingCache = enable;
}
@Override
public boolean isDanmakuDrawingCacheEnabled() {
return mEnableDanmakuDrwaingCache;
}
@Override
public boolean isViewReady() {
return isSurfaceCreated;
}
@Override
public View getView() {
return this;
}
@Override
public void show() {
showAndResumeDrawTask(null);
}
@Override
public void showAndResumeDrawTask(Long position) {
mDanmakuVisible = true;
mClearFlag = false;
if (handler == null) {
return;
}
handler.showDanmakus(position);
}
@Override
public void hide() {
mDanmakuVisible = false;
if (handler == null) {
return;
}
handler.hideDanmakus(false);
}
@Override
public long hideAndPauseDrawTask() {
mDanmakuVisible = false;
if (handler == null) {
return 0;
}
return handler.hideDanmakus(true);
}
@Override
public void clear() {
if (!isViewReady()) {
return;
}
if (!mDanmakuVisible || Thread.currentThread().getId() == mUiThreadId) {
mClearFlag = true;
postInvalidateCompat();
} else {
lockCanvasAndClear();
}
}
@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() {
// >= 3.0
if (Build.VERSION.SDK_INT >= 11) {
return super.isHardwareAccelerated();
} else {
return false;
}
}
@Override
public void clearDanmakusOnScreen() {
if (handler != null) {
handler.clearDanmakusOnScreen();
}
}
}