From 9e85d19671e3393c350074801f8442d2b84bbcb8 Mon Sep 17 00:00:00 2001 From: FongMi Date: Wed, 13 Sep 2023 01:27:39 +0800 Subject: [PATCH] Support danmu - part 5 --- .../android/tv/player/danmu/Parser.java | 6 +- .../android/tv/ui/activity/VideoActivity.java | 23 +++- .../controller/CacheManagingDrawTask.java | 2 - .../flame/danmaku/controller/DrawHandler.java | 16 +-- .../danmaku/model/android/DanmakuContext.java | 2 +- .../danmaku/parser/BaseDanmakuParser.java | 24 ++-- .../flame/danmaku/ui/widget/DanmakuView.java | 12 +- .../tv/cjump/jni/NativeBitmapFactory.java | 103 +----------------- .../main/jniLibs/arm64-v8a/libndkbitmap.so | Bin 8160 -> 0 bytes .../main/jniLibs/armeabi-v7a/libndkbitmap.so | Bin 5940 -> 0 bytes 10 files changed, 49 insertions(+), 139 deletions(-) delete mode 100644 danmaku/src/main/jniLibs/arm64-v8a/libndkbitmap.so delete mode 100644 danmaku/src/main/jniLibs/armeabi-v7a/libndkbitmap.so diff --git a/app/src/main/java/com/fongmi/android/tv/player/danmu/Parser.java b/app/src/main/java/com/fongmi/android/tv/player/danmu/Parser.java index 2eeda4441..2c2a9b7a1 100644 --- a/app/src/main/java/com/fongmi/android/tv/player/danmu/Parser.java +++ b/app/src/main/java/com/fongmi/android/tv/player/danmu/Parser.java @@ -49,15 +49,15 @@ public class Parser extends BaseDanmakuParser { @Override public BaseDanmakuParser setDisplay(IDisplay display) { super.setDisplay(display); - scaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH; - scaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; + scaleX = mDisplayWidth / DanmakuFactory.BILI_PLAYER_WIDTH; + scaleY = mDisplayHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; return this; } private void setParam(String[] values) { int type = Integer.parseInt(values[1]); long time = (long) (Float.parseFloat(values[0]) * 1000); - float size = Float.parseFloat(values[2]) * (mDispDensity - 0.6f); + float size = Float.parseFloat(values[2]) * (mDisplayDensity - 0.6f); int color = (int) ((0x00000000ff000000L | Long.parseLong(values[3])) & 0x00000000ffffffffL); item = mContext.mDanmakuFactory.createDanmaku(type, mContext); item.setTime(time); diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java index edcc8bbc3..33de2c67a 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java @@ -61,6 +61,7 @@ import com.fongmi.android.tv.model.SiteViewModel; import com.fongmi.android.tv.player.ExoUtil; import com.fongmi.android.tv.player.Players; import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.player.danmu.Parser; import com.fongmi.android.tv.service.PlaybackService; import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; import com.fongmi.android.tv.ui.adapter.FlagAdapter; @@ -101,7 +102,8 @@ import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.IDisplay; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; import tv.danmaku.ijk.media.player.ui.IjkVideoView; public class VideoActivity extends BaseActivity implements Clock.Callback, CustomKeyDownVod.Listener, CastDialog.Listener, TrackDialog.Listener, ControlDialog.Listener, FlagAdapter.OnClickListener, EpisodeAdapter.OnClickListener, QualityAdapter.OnClickListener, QuickAdapter.OnClickListener, ParseAdapter.OnClickListener, SubtitleCallback { @@ -111,6 +113,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo private Observer mObserveDetail; private Observer mObservePlayer; private Observer mObserveSearch; + private DanmakuContext mDanmakuContext; private EpisodeAdapter mEpisodeAdapter; private QualityAdapter mQualityAdapter; private ControlDialog mControlDialog; @@ -262,7 +265,8 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - if (TextUtils.isEmpty(intent.getStringExtra("id"))) return; + String id = intent.getStringExtra("id"); + if (TextUtils.isEmpty(id) || id.equals(getId())) return; mBinding.swipeLayout.setRefreshing(true); getIntent().putExtras(intent); stopSearch(); @@ -274,6 +278,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo protected void initView(Bundle savedInstanceState) { mKeyDown = CustomKeyDownVod.create(this, mBinding.video); mFrameParams = mBinding.video.getLayoutParams(); + mDanmakuContext = DanmakuContext.create(); mBinding.progressLayout.showProgress(); mBinding.swipeLayout.setEnabled(false); mPlayers = new Players().init(this); @@ -292,6 +297,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo setForeground(true); setRecyclerView(); setVideoView(); + setDanmuView(); setViewModel(); showProgress(); checkId(); @@ -383,6 +389,11 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo setSubtitle(14); } + private void setDanmuView() { + mPlayers.setDanmuView(mBinding.danmu); + mDanmakuContext.setDanmakuStyle(IDisplay.DANMAKU_STYLE_STROKEN, 3).setDanmakuMargin(ResUtil.dp2px(24)); + } + @Override public void setSubtitle(int size) { getExo().getSubtitleView().setFixedTextSize(Dimension.SP, size); @@ -509,11 +520,13 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo mBinding.quality.setVisibility(result.getUrl().isOnly() ? View.GONE : View.VISIBLE); mBinding.swipeLayout.setRefreshing(false); mQualityAdapter.addAll(result); - loadDanmu(result.getDanmakus()); + checkDanmu(result.getDanmaku()); } - private void loadDanmu(IDanmakus danmakus) { - + private void checkDanmu(String danmu) { + mBinding.danmu.release(); + mBinding.danmu.setVisibility(danmu.isEmpty() ? View.GONE : View.VISIBLE); + if (danmu.length() > 0) mBinding.danmu.prepare(new Parser(danmu), mDanmakuContext); } @Override diff --git a/danmaku/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java b/danmaku/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java index 4bdb870f3..fa32280ca 100644 --- a/danmaku/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java +++ b/danmaku/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java @@ -51,7 +51,6 @@ public class CacheManagingDrawTask extends DrawTask { public CacheManagingDrawTask(DanmakuTimer timer, DanmakuContext config, TaskListener taskListener) { super(timer, config, taskListener); - NativeBitmapFactory.loadLibs(); mMaxCacheSize = (int) Math.max(1024 * 1024 * 4, Runtime.getRuntime().maxMemory() * config.cachingPolicy.maxCachePoolSizeFactorPercentage); mCacheManager = new CacheManager(mMaxCacheSize, MAX_CACHE_SCREEN_SIZE); mRenderer.setCacheManager(mCacheManager); @@ -150,7 +149,6 @@ public class CacheManagingDrawTask extends DrawTask { mCacheManager.end(); mCacheManager = null; } - NativeBitmapFactory.releaseLibs(); } @Override diff --git a/danmaku/src/main/java/master/flame/danmaku/controller/DrawHandler.java b/danmaku/src/main/java/master/flame/danmaku/controller/DrawHandler.java index 677cd0995..314314abf 100644 --- a/danmaku/src/main/java/master/flame/danmaku/controller/DrawHandler.java +++ b/danmaku/src/main/java/master/flame/danmaku/controller/DrawHandler.java @@ -409,14 +409,14 @@ public class DrawHandler extends Handler { long time = startMS - mTimeBase; if (mNonBlockModeEnable) { if (mCallback != null) { - mCallback.updateTimer(timer); + //mCallback.updateTimer(timer); d = timer.lastInterval(); } } else if (!mDanmakusVisible || mRenderingState.nothingRendered || mInWaitingState) { timer.update(time); mRemainingTime = 0; if (mCallback != null) { - mCallback.updateTimer(timer); + //mCallback.updateTimer(timer); } } else { long gapTime = time - timer.currMillisecond; @@ -438,7 +438,7 @@ public class DrawHandler extends Handler { mRemainingTime = gapTime; timer.add(d); if (mCallback != null) { - mCallback.updateTimer(timer); + //mCallback.updateTimer(timer); } } mInSyncAction = false; @@ -483,12 +483,12 @@ public class DrawHandler extends Handler { @Override public void onDanmakuShown(BaseDanmaku danmaku) { - if (mCallback != null) mCallback.danmakuShown(danmaku); + //if (mCallback != null) mCallback.danmakuShown(danmaku); } @Override public void onDanmakusDrawingFinished() { - if (mCallback != null) mCallback.drawingFinished(); + //if (mCallback != null) mCallback.drawingFinished(); } @Override @@ -752,12 +752,6 @@ public class DrawHandler extends Handler { public interface Callback { void prepared(); - - void updateTimer(DanmakuTimer timer); - - void danmakuShown(BaseDanmaku danmaku); - - void drawingFinished(); } private class FrameCallback implements Choreographer.FrameCallback { diff --git a/danmaku/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java b/danmaku/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java index 98217ba23..8c70b882e 100644 --- a/danmaku/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java +++ b/danmaku/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java @@ -45,7 +45,7 @@ public class DanmakuContext implements Cloneable { List mUserHashBlackList = new ArrayList<>(); private List> mCallbackList; private boolean mBlockGuestDanmaku = false; - private boolean mDuplicateMergingEnable = false; + private boolean mDuplicateMergingEnable = true; private boolean mIsAlignBottom = false; private BaseCacheStuffer mCacheStuffer; private boolean mIsMaxLinesLimited; diff --git a/danmaku/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java b/danmaku/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java index 1e4c234b4..6bbc128f2 100644 --- a/danmaku/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java +++ b/danmaku/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java @@ -23,14 +23,15 @@ import master.flame.danmaku.danmaku.model.android.DanmakuContext; public abstract class BaseDanmakuParser { - protected DanmakuTimer mTimer; - protected int mDispWidth; - protected int mDispHeight; - protected float mDispDensity; - protected float mScaledDensity; - protected IDisplay mDisp; protected DanmakuContext mContext; + protected DanmakuTimer mTimer; private IDanmakus mDanmakus; + private IDisplay mDisp; + + protected float mDisplayDensity; + protected float mScaledDensity; + protected int mDisplayHeight; + protected int mDisplayWidth; public IDisplay getDisplay() { return mDisp; @@ -38,21 +39,20 @@ public abstract class BaseDanmakuParser { public BaseDanmakuParser setDisplay(IDisplay disp) { mDisp = disp; - mDispWidth = disp.getWidth(); - mDispHeight = disp.getHeight(); - mDispDensity = disp.getDensity(); + mDisplayWidth = disp.getWidth(); + mDisplayHeight = disp.getHeight(); + mDisplayDensity = disp.getDensity(); mScaledDensity = disp.getScaledDensity(); - mContext.mDanmakuFactory.updateViewportState(mDispWidth, mDispHeight, getViewportSizeFactor()); + mContext.mDanmakuFactory.updateViewportState(mDisplayWidth, mDisplayHeight, getViewportSizeFactor()); mContext.mDanmakuFactory.updateMaxDanmakuDuration(); return this; } /** * decide the speed of scroll-danmakus - * */ protected float getViewportSizeFactor() { - return 1 / (mDispDensity - 0.6f); + return 1 / (mDisplayDensity - 0.6f); } public DanmakuTimer getTimer() { diff --git a/danmaku/src/main/java/master/flame/danmaku/ui/widget/DanmakuView.java b/danmaku/src/main/java/master/flame/danmaku/ui/widget/DanmakuView.java index 3c2505dbc..c6b3d71df 100644 --- a/danmaku/src/main/java/master/flame/danmaku/ui/widget/DanmakuView.java +++ b/danmaku/src/main/java/master/flame/danmaku/ui/widget/DanmakuView.java @@ -47,13 +47,14 @@ public class DanmakuView extends View implements IDanmakuView, IDanmakuViewContr private static final int MAX_RECORD_SIZE = 50; private static final int ONE_SECOND = 1000; - private Callback mCallback; - private Object mDrawMonitor; private OnDanmakuClickListener mOnDanmakuClickListener; private DanmakuTouchHelper mTouchHelper; private HandlerThread mHandlerThread; - private LinkedList mDrawTimes; private volatile DrawHandler handler; + private LinkedList mDrawTimes; + private Object mDrawMonitor; + private Callback mCallback; + private boolean mEnableDrawingCache; private boolean isSurfaceCreated; private boolean mDanmakuVisible; @@ -137,6 +138,11 @@ public class DanmakuView extends View implements IDanmakuView, IDanmakuViewContr mCallback = callback; } + public void setSpeed(float speed) { + if (getConfig() == null) return; + getConfig().setSpeed(speed); + } + @Override public void release() { stop(); diff --git a/danmaku/src/main/java/tv/cjump/jni/NativeBitmapFactory.java b/danmaku/src/main/java/tv/cjump/jni/NativeBitmapFactory.java index dd5f14027..579232aa5 100644 --- a/danmaku/src/main/java/tv/cjump/jni/NativeBitmapFactory.java +++ b/danmaku/src/main/java/tv/cjump/jni/NativeBitmapFactory.java @@ -1,115 +1,14 @@ package tv.cjump.jni; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.os.Build; -import android.util.Log; - -import java.lang.reflect.Field; public class NativeBitmapFactory { - static Field nativeIntField = null; - - static { - System.loadLibrary("ndkbitmap"); - } - - public static void loadLibs() { - boolean libInit = init(); - if (!libInit) { - release(); - } else { - initField(); - if (!testLib()) release(); - } - } - - public static synchronized void releaseLibs() { - nativeIntField = null; - release(); - } - - static void initField() { - try { - nativeIntField = Bitmap.Config.class.getDeclaredField("nativeInt"); - nativeIntField.setAccessible(true); - } catch (NoSuchFieldException e) { - nativeIntField = null; - e.printStackTrace(); - } - } - - private static boolean testLib() { - if (nativeIntField == null) return false; - Bitmap bitmap = null; - Canvas canvas; - try { - bitmap = createNativeBitmap(2, 2, Bitmap.Config.ARGB_8888, true); - boolean result = (bitmap != null && bitmap.getWidth() == 2 && bitmap.getHeight() == 2); - if (result) { - if (Build.VERSION.SDK_INT >= 17 && !bitmap.isPremultiplied()) { - bitmap.setPremultiplied(true); - } - canvas = new Canvas(bitmap); - Paint paint = new Paint(); - paint.setColor(Color.RED); - paint.setTextSize(20f); - canvas.drawRect(0f, 0f, (float) bitmap.getWidth(), (float) bitmap.getHeight(), paint); - canvas.drawText("TestLib", 0, 0, paint); - if (Build.VERSION.SDK_INT >= 17) { - result = bitmap.isPremultiplied(); - } - } - return result; - } catch (Exception e) { - Log.e("NativeBitmapFactory", "exception:" + e.toString()); - return false; - } catch (Error e) { - return false; - } finally { - if (bitmap != null) { - bitmap.recycle(); - } - } - } - - public static int getNativeConfig(Bitmap.Config config) { - try { - if (nativeIntField == null) return 0; - return nativeIntField.getInt(config); - } catch (IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - return 0; - } - } - public static Bitmap createBitmap(int width, int height, Bitmap.Config config) { return createBitmap(width, height, config, config.equals(Bitmap.Config.ARGB_4444) || config.equals(Bitmap.Config.ARGB_8888)); } - public static void recycle(Bitmap bitmap) { - bitmap.recycle(); - } - public static synchronized Bitmap createBitmap(int width, int height, Bitmap.Config config, boolean hasAlpha) { - if (nativeIntField == null) return Bitmap.createBitmap(width, height, config); - return createNativeBitmap(width, height, config, hasAlpha); + return Bitmap.createBitmap(width, height, config, hasAlpha); } - - private static Bitmap createNativeBitmap(int width, int height, Config config, boolean hasAlpha) { - int nativeConfig = getNativeConfig(config); - return Build.VERSION.SDK_INT == 19 ? createBitmap19(width, height, nativeConfig, hasAlpha) : createBitmap(width, height, nativeConfig, hasAlpha); - } - - private static native boolean init(); - - private static native boolean release(); - - private static native Bitmap createBitmap(int width, int height, int nativeConfig, boolean hasAlpha); - - private static native Bitmap createBitmap19(int width, int height, int nativeConfig, boolean hasAlpha); } diff --git a/danmaku/src/main/jniLibs/arm64-v8a/libndkbitmap.so b/danmaku/src/main/jniLibs/arm64-v8a/libndkbitmap.so deleted file mode 100644 index caafaea2dd9fc8c476d77d917dc62c881f95781a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8160 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 YlL^4thudF4+^5;@hTj(^05|>q4U?s~AOHXW