Repository: bilibili/DanmakuFlameMaster Branch: master Commit: e2846461a09e Files: 135 Total size: 663.3 KB Directory structure: gitextract_ewo3zqa2/ ├── .gitignore ├── .gitmodules ├── .travis.yml ├── DanmakuFlameMaster/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── jni/ │ │ └── NativeBitmapFactoryTest.java │ └── main/ │ ├── .classpath │ ├── .project │ ├── .settings/ │ │ ├── org.eclipse.jdt.core.prefs │ │ └── org.eclipse.jdt.ui.prefs │ ├── AndroidManifest.xml │ ├── build.xml │ ├── java/ │ │ ├── master/ │ │ │ └── flame/ │ │ │ └── danmaku/ │ │ │ ├── controller/ │ │ │ │ ├── CacheManagingDrawTask.java │ │ │ │ ├── DanmakuFilters.java │ │ │ │ ├── DrawHandler.java │ │ │ │ ├── DrawHelper.java │ │ │ │ ├── DrawTask.java │ │ │ │ ├── IDanmakuView.java │ │ │ │ ├── IDanmakuViewController.java │ │ │ │ ├── IDrawTask.java │ │ │ │ └── UpdateThread.java │ │ │ ├── danmaku/ │ │ │ │ ├── loader/ │ │ │ │ │ ├── ILoader.java │ │ │ │ │ ├── IllegalDataException.java │ │ │ │ │ └── android/ │ │ │ │ │ ├── AcFunDanmakuLoader.java │ │ │ │ │ ├── BiliDanmakuLoader.java │ │ │ │ │ └── DanmakuLoaderFactory.java │ │ │ │ ├── model/ │ │ │ │ │ ├── AbsDanmakuSync.java │ │ │ │ │ ├── AbsDisplayer.java │ │ │ │ │ ├── AlphaValue.java │ │ │ │ │ ├── BaseDanmaku.java │ │ │ │ │ ├── Danmaku.java │ │ │ │ │ ├── DanmakuTimer.java │ │ │ │ │ ├── Duration.java │ │ │ │ │ ├── FBDanmaku.java │ │ │ │ │ ├── FTDanmaku.java │ │ │ │ │ ├── GlobalFlagValues.java │ │ │ │ │ ├── ICacheManager.java │ │ │ │ │ ├── IDanmakuIterator.java │ │ │ │ │ ├── IDanmakus.java │ │ │ │ │ ├── IDisplayer.java │ │ │ │ │ ├── IDrawingCache.java │ │ │ │ │ ├── L2RDanmaku.java │ │ │ │ │ ├── R2LDanmaku.java │ │ │ │ │ ├── SpecialDanmaku.java │ │ │ │ │ ├── android/ │ │ │ │ │ │ ├── AndroidDisplayer.java │ │ │ │ │ │ ├── BaseCacheStuffer.java │ │ │ │ │ │ ├── CachingPolicy.java │ │ │ │ │ │ ├── DanmakuContext.java │ │ │ │ │ │ ├── DanmakuFactory.java │ │ │ │ │ │ ├── Danmakus.java │ │ │ │ │ │ ├── DrawingCache.java │ │ │ │ │ │ ├── DrawingCacheHolder.java │ │ │ │ │ │ ├── DrawingCachePoolManager.java │ │ │ │ │ │ ├── SimpleTextCacheStuffer.java │ │ │ │ │ │ ├── SpannedCacheStuffer.java │ │ │ │ │ │ └── ViewCacheStuffer.java │ │ │ │ │ └── objectpool/ │ │ │ │ │ ├── FinitePool.java │ │ │ │ │ ├── Pool.java │ │ │ │ │ ├── Poolable.java │ │ │ │ │ ├── PoolableManager.java │ │ │ │ │ ├── Pools.java │ │ │ │ │ └── SynchronizedPool.java │ │ │ │ ├── parser/ │ │ │ │ │ ├── BaseDanmakuParser.java │ │ │ │ │ ├── IDataSource.java │ │ │ │ │ └── android/ │ │ │ │ │ ├── AndroidFileSource.java │ │ │ │ │ └── JSONSource.java │ │ │ │ ├── renderer/ │ │ │ │ │ ├── IRenderer.java │ │ │ │ │ ├── Renderer.java │ │ │ │ │ └── android/ │ │ │ │ │ ├── DanmakuRenderer.java │ │ │ │ │ └── DanmakusRetainer.java │ │ │ │ └── util/ │ │ │ │ ├── DanmakuUtils.java │ │ │ │ ├── IOUtils.java │ │ │ │ └── SystemClock.java │ │ │ └── ui/ │ │ │ └── widget/ │ │ │ ├── DanmakuSurfaceView.java │ │ │ ├── DanmakuTextureView.java │ │ │ ├── DanmakuTouchHelper.java │ │ │ ├── DanmakuView.java │ │ │ └── FakeDanmakuView.java │ │ └── tv/ │ │ └── cjump/ │ │ └── jni/ │ │ ├── DeviceUtils.java │ │ └── NativeBitmapFactory.java │ ├── lint.xml │ ├── proguard-project.txt │ └── project.properties ├── LICENSE ├── README.md ├── Sample/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── .classpath │ ├── .project │ ├── .settings/ │ │ └── org.eclipse.jdt.core.prefs │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── sample/ │ │ ├── BiliDanmukuParser.java │ │ ├── MainActivity.java │ │ └── UglyViewCacheStufferSampleActivity.java │ ├── project.properties │ └── res/ │ ├── layout/ │ │ ├── activity_main.xml │ │ ├── layout_view_cache.xml │ │ └── media_controller.xml │ ├── menu/ │ │ └── main.xml │ ├── raw/ │ │ └── comments.xml │ └── values/ │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle/ │ ├── gradle-bintray-upload.gradle │ ├── gradle-mvn-push.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ndkbitmap-armv5/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_armv5/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_armv5/ │ │ └── Pragma.java │ └── res/ │ └── values/ │ └── strings.xml ├── ndkbitmap-armv7a/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_armv7a/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_armv7a/ │ │ └── Pragma.java │ └── res/ │ └── values/ │ └── strings.xml ├── ndkbitmap-x86/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_x86/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── tv/ │ │ └── cjump/ │ │ └── ndkbitmap_x86/ │ │ └── Pragma.java │ └── res/ │ └── values/ │ └── strings.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # built application files *.apk *.ap_ *.com *.class *.dll *.exe *.o # files for the dex VM *.dex # Java class files *.class # generated files bin/ gen/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Intellij project files *.iml *.ipr *.iws .idea/ .DS_Store .idea/ .gradle build/ *.iml out/ ================================================ FILE: .gitmodules ================================================ ================================================ FILE: .travis.yml ================================================ language: android android: components: - platform-tools - tools - build-tools-26.0.2 - android-25 - extra-android-support - extra-android-m2repository script: - ./gradlew check -i - ./gradlew assemble after_failure: - cat /home/travis/build/Bilibili/DanmakuFlameMaster/DanmakuFlameMaster/build/outputs/lint-results.html - cat /home/travis/build/Bilibili/DanmakuFlameMaster/DanmakuFlameMaster/build/outputs/lint-results.xml ================================================ FILE: DanmakuFlameMaster/build.gradle ================================================ /* * 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. */ apply plugin: 'com.android.library' def SourcePath = 'src/main/' android { compileSdkVersion 25 buildToolsVersion "26.0.2" defaultConfig { minSdkVersion 9 targetSdkVersion 25 versionName VERSION_NAME versionCode Integer.parseInt(VERSION_CODE) } sourceSets { main { manifest.srcFile "${SourcePath}AndroidManifest.xml" java.srcDirs = ["${SourcePath}java"] jniLibs.srcDirs = ["${SourcePath}libs"] } androidTest.setRoot("${SourcePath}../androidTest") } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } } if (rootProject.file('gradle/gradle-mvn-push.gradle').exists()) { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } if (rootProject.file('gradle/gradle-bintray-upload.gradle').exists()) { apply from: rootProject.file('gradle/gradle-bintray-upload.gradle') } ================================================ FILE: DanmakuFlameMaster/gradle.properties ================================================ POM_NAME=DanmakuFlameMaster BINTRAY_POM_NAME=dfm POM_ARTIFACT_ID=dfm POM_PACKAGING=aar ================================================ FILE: DanmakuFlameMaster/src/androidTest/java/tv/cjump/jni/NativeBitmapFactoryTest.java ================================================ package tv.cjump.jni; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Debug; import master.flame.danmaku.danmaku.util.SystemClock; import android.test.InstrumentationTestCase; import android.util.Log; import junit.framework.Assert; import junit.framework.TestCase; import java.util.ArrayList; import java.util.Locale; /** * Created by ch on 15-6-12. */ public class NativeBitmapFactoryTest extends InstrumentationTestCase { private static final int DEFAULT_MESSAGE_SIZE = 1024; private static final int BYTES_IN_MEGABYTE = 1024 * 1024; private static final int BITMAP_WIDTH = 200; private static final int BITMAP_HEIGHT = 200; private static final String TAG = NativeBitmapFactoryTest.class.getSimpleName(); public void testLoadLibs() { NativeBitmapFactory.loadLibs(); boolean isInNativeAlloc = NativeBitmapFactory.isInNativeAlloc(); Assert.assertTrue("NativeBitmapFactory is not supported on your OS", isInNativeAlloc); } public void testNativeBitmap() { Bitmap bitmap = NativeBitmapFactory.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); accessBitmap(bitmap); bitmap.recycle(); gcAndWait(); } public void testDalvikBitmap() { Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); accessBitmap(bitmap); bitmap.recycle(); gcAndWait(); } public void testNativeBitmaps() { StringBuilder sb = new StringBuilder(DEFAULT_MESSAGE_SIZE); appendValue(sb, "\n\n", "===== before create 50 NativeBitmap", "\n\n"); updateHeapValue(sb); final String message = sb.toString(); Log.i(TAG, message); sb = new StringBuilder(); ArrayList bitmaps = new ArrayList<>(); for (int i = 0; i < 150; i++) { Bitmap bitmap = NativeBitmapFactory.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); accessBitmap(bitmap); bitmaps.add(bitmap); } updateHeapValue(sb); Log.d(TAG, sb.toString()); for (Bitmap bitmap : bitmaps) { bitmap.recycle(); } gcAndWait(); } public Context getContext(){ return getInstrumentation().getTargetContext(); } private void updateHeapValue(StringBuilder sb) { ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); ActivityManager activityManager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(mi); long availableMegs = mi.availMem; final Runtime runtime = Runtime.getRuntime(); final long heapMemory = runtime.totalMemory() - runtime.freeMemory(); // When changing format of output below, make sure to sync "scripts/test_runner.py" as well. appendSize(sb, "System availMem: ", availableMegs, "\n"); appendSize(sb, "Java heap size: ", heapMemory, "\n"); appendSize(sb, "Native heap size: ", Debug.getNativeHeapSize(), "\n"); } public void testDalvikBitmaps() { StringBuilder sb = new StringBuilder(DEFAULT_MESSAGE_SIZE); appendValue(sb, "\n\n", "===== before create 50 DalvikBitmap", "\n\n"); updateHeapValue(sb); final String message = sb.toString(); Log.i(TAG, message); sb = new StringBuilder(); ArrayList bitmaps = new ArrayList<>(); for (int i = 0; i < 150; i++) { Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); accessBitmap(bitmap); bitmaps.add(bitmap); } updateHeapValue(sb); Log.d(TAG, sb.toString()); for (Bitmap bitmap : bitmaps) { bitmap.recycle(); } gcAndWait(); } public void testReleaseLibs() { NativeBitmapFactory.releaseLibs(); } private void accessBitmap(Bitmap bitmap) { boolean result = (bitmap != null && bitmap.getWidth() == BITMAP_WIDTH && bitmap.getHeight() == BITMAP_HEIGHT); if (result) { if (android.os.Build.VERSION.SDK_INT >= 17 && !bitmap.isPremultiplied()) { bitmap.setPremultiplied(true); } Canvas 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); } } private void gcAndWait() { System.gc(); SystemClock.sleep(10000); // try { // Debug.dumpHprofData("/sdcard/nbf_test_dump.hprof"); // } catch (IOException e) { // e.printStackTrace(); // } } private static void appendSize(StringBuilder sb, String prefix, long bytes, String suffix) { String value = String.format(Locale.getDefault(), "%.2f", (float) bytes / BYTES_IN_MEGABYTE); appendValue(sb, prefix, value + " MB", suffix); } private static void appendTime(StringBuilder sb, String prefix, long timeMs, String suffix) { appendValue(sb, prefix, timeMs + " ms", suffix); } private static void appendNumber(StringBuilder sb, String prefix, long number, String suffix) { appendValue(sb, prefix, number + "", suffix); } private static void appendValue(StringBuilder sb, String prefix, String value, String suffix) { sb.append(prefix).append(value).append(suffix); } } ================================================ FILE: DanmakuFlameMaster/src/main/.classpath ================================================ ================================================ FILE: DanmakuFlameMaster/src/main/.project ================================================ DanmakuFlameMaster com.android.ide.eclipse.adt.ResourceManagerBuilder com.android.ide.eclipse.adt.PreCompilerBuilder org.eclipse.jdt.core.javabuilder com.android.ide.eclipse.adt.ApkBuilder com.android.ide.eclipse.adt.AndroidNature org.eclipse.jdt.core.javanature ================================================ FILE: DanmakuFlameMaster/src/main/.settings/org.eclipse.jdt.core.prefs ================================================ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.source=1.6 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_assignment=0 org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.blank_lines_before_field=0 org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 org.eclipse.jdt.core.formatter.blank_lines_before_method=1 org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 org.eclipse.jdt.core.formatter.blank_lines_before_package=1 org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true org.eclipse.jdt.core.formatter.comment.format_block_comments=true org.eclipse.jdt.core.formatter.comment.format_header=false org.eclipse.jdt.core.formatter.comment.format_html=true org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true org.eclipse.jdt.core.formatter.comment.format_line_comments=true org.eclipse.jdt.core.formatter.comment.format_source_code=true org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true org.eclipse.jdt.core.formatter.comment.indent_root_tags=true org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert org.eclipse.jdt.core.formatter.comment.line_length=80 org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false org.eclipse.jdt.core.formatter.compact_else_if=true org.eclipse.jdt.core.formatter.continuation_indentation=2 org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_empty_lines=false org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true org.eclipse.jdt.core.formatter.indentation.size=4 org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.join_lines_in_comments=true org.eclipse.jdt.core.formatter.join_wrapped_lines=true org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.lineSplit=300 org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true org.eclipse.jdt.core.formatter.tabulation.char=space org.eclipse.jdt.core.formatter.tabulation.size=4 org.eclipse.jdt.core.formatter.use_on_off_tags=false org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true ================================================ FILE: DanmakuFlameMaster/src/main/.settings/org.eclipse.jdt.ui.prefs ================================================ eclipse.preferences.version=1 formatter_profile=_AndroidCodeStyle formatter_settings_version=12 org.eclipse.jdt.ui.ignorelowercasenames=true org.eclipse.jdt.ui.importorder=android;com;dalvik;gov;junit;libcore;net;org;java;javax; org.eclipse.jdt.ui.ondemandthreshold=99 org.eclipse.jdt.ui.staticondemandthreshold=99 ================================================ FILE: DanmakuFlameMaster/src/main/AndroidManifest.xml ================================================ ================================================ FILE: DanmakuFlameMaster/src/main/build.xml ================================================ ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/CacheManagingDrawTask.java ================================================ /* * 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.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.ICacheManager; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.model.IDrawingCache; import master.flame.danmaku.danmaku.model.android.CachingPolicy; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.model.android.DanmakuContext.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.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.DanmakuUtils; import master.flame.danmaku.danmaku.util.SystemClock; 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(); private int mRemaininCacheCount; 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); } @Override protected void initTimer(DanmakuTimer timer) { mTimer = timer; mCacheTimer = new DanmakuTimer(); mCacheTimer.update(timer.currMillisecond); } @Override public void addDanmaku(BaseDanmaku danmaku) { super.addDanmaku(danmaku); if (mCacheManager == null) return; mCacheManager.addDanmaku(danmaku); } @Override public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { super.invalidateDanmaku(item, remeasure); if (mCacheManager == null) { return; } mCacheManager.invalidateDanmaku(item, remeasure); } @Override public void removeAllDanmakus(boolean isClearDanmakusOnScreen) { super.removeAllDanmakus(isClearDanmakusOnScreen); if (mCacheManager != null) { mCacheManager.requestClearAll(); } } @Override protected void onDanmakuRemoved(BaseDanmaku danmaku) { super.onDanmakuRemoved(danmaku); if (mCacheManager != null) { if (++mRemaininCacheCount > 5) { // control frequency (it does not require very precise mCacheManager.requestClearTimeout(); mRemaininCacheCount = 0; } } else { IDrawingCache cache = danmaku.getDrawingCache(); if (cache != null) { if (cache.hasReferences()) { cache.decreaseReference(); } else { cache.destroy(); } danmaku.cache = null; } } } @Override public RenderingState draw(AbsDisplayer displayer) { RenderingState result = super.draw(displayer); synchronized (mDrawingNotify) { mDrawingNotify.notify(); } if (result != null && mCacheManager != null) { if (result.totalDanmakuCount - result.lastTotalDanmakuCount < -20) { mCacheManager.requestClearTimeout(); mCacheManager.requestBuild(-mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); } } return result; } @Override public void seek(long mills) { super.seek(mills); if (mCacheManager == null) { start(); } mCacheManager.seek(mills); } @Override public void start() { super.start(); NativeBitmapFactory.loadLibs(); if (mCacheManager == null) { mCacheManager = new CacheManager(mMaxCacheSize, MAX_CACHE_SCREEN_SIZE); mCacheManager.begin(); mRenderer.setCacheManager(mCacheManager); } else { mCacheManager.resume(); } } @Override public void quit() { super.quit(); reset(); mRenderer.setCacheManager(null); if (mCacheManager != null) { mCacheManager.end(); mCacheManager = null; } NativeBitmapFactory.releaseLibs(); } @Override public void prepare() { if (mParser == null) { return; } loadDanmakus(mParser); mCacheManager.begin(); } @Override public void onPlayStateChanged(int state) { super.onPlayStateChanged(state); if (mCacheManager != null) { mCacheManager.onPlayStateChanged(state); } } @Override public void requestSync(long fromTimeMills, long toTimeMills, long offsetMills) { super.requestSync(fromTimeMills, toTimeMills, offsetMills); if (mCacheManager != null) { mCacheManager.seek(toTimeMills); } } public class CacheManager implements ICacheManager { @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(); DrawingCachePoolManager mCachePoolManager = new DrawingCachePoolManager(); Pool 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(); } @Override public void addDanmaku(BaseDanmaku danmaku) { if (mHandler != null) { if (danmaku.isLive && danmaku.forceBuildCacheInSameThread) { if (!danmaku.isTimeOut()) { mHandler.createCache(danmaku); } return; } mHandler.obtainMessage(CacheHandler.ADD_DANMAKU, danmaku).sendToTarget(); } } 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); } } public void begin() { mEndFlag = false; 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.removeCallbacksAndMessages(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 void onPlayStateChanged(int state) { if (mHandler != null) { mHandler.onPlayStateChanged(state == IDrawTask.PLAY_STATE_PLAYING); } } 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) { mCaches.forEach(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku danmaku) { entryRemoved(true, danmaku, null); return ACTION_CONTINUE; } }); mCaches.clear(); } mRealSize = 0; } private void evictAllNotInScreen() { if (mCaches != null) { mCaches.forEach(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku danmaku) { if (danmaku.isOutside()) { entryRemoved(true, danmaku, null); return ACTION_REMOVE; } return ACTION_CONTINUE; } }); } } protected void entryRemoved(boolean evicted, BaseDanmaku oldValue, BaseDanmaku newValue) { IDrawingCache cache = oldValue.getDrawingCache(); if (cache != null) { long releasedSize = clearCache(oldValue); if (oldValue.isTimeOut()) { mContext.getDisplayer().getCacheStuffer().releaseResource(oldValue); } if (releasedSize <= 0) return; mRealSize -= releasedSize; mCachePool.release((DrawingCache) cache); } } private long clearCache(BaseDanmaku oldValue) { IDrawingCache cache = oldValue.cache; if (cache == null) { return 0; } if (cache.hasReferences()) { cache.decreaseReference(); oldValue.cache = null; return 0; } long size = sizeOf(oldValue); cache.destroy(); oldValue.cache = null; return size; } 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, 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 } this.mCaches.addItem(item); mRealSize += size; //Log.i("DFM CACHE", "realsize:"+mRealSize + ",size" + size); return true; } private void clearTimeOutCaches() { mCaches.forEach(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku val) { if (val.isTimeOut()) { IDrawingCache cache = val.cache; if (mContext.cachingPolicy.periodOfRecycle == CachingPolicy.CACHE_PERIOD_NOT_RECYCLE && cache != null && !cache.hasReferences()) { if (cache.size() / (float) mMaxCacheSize < mContext.cachingPolicy.forceRecyleThreshold) { return ACTION_CONTINUE; } //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 { return ACTION_BREAK; } } }); } private BaseDanmaku findReusableCache(final BaseDanmaku refDanmaku, final boolean strictMode, final int maximumTimes) { int slopPixel = 0; if (!strictMode) { slopPixel = mDisp.getSlopPixel() * 2; } final int finalSlopPixel = slopPixel + mContext.cachingPolicy.reusableOffsetPixel; IDanmakus.Consumer consumer = new IDanmakus.Consumer() { int count = 0; BaseDanmaku mResult; @Override public BaseDanmaku result() { return mResult; } @Override public int accept(BaseDanmaku danmaku) { if (count++ >= maximumTimes) { return ACTION_BREAK; } IDrawingCache cache = danmaku.getDrawingCache(); if (cache == null || cache.get() == null) { return ACTION_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) && danmaku.tag == refDanmaku.tag) { mResult = danmaku; return ACTION_BREAK; } if (strictMode) { return ACTION_CONTINUE; } if (!danmaku.isTimeOut()) { return ACTION_BREAK; } if (cache.hasReferences()) { return ACTION_CONTINUE; } float widthGap = cache.width() - refDanmaku.paintWidth; float heightGap = cache.height() - refDanmaku.paintHeight; if (widthGap >= 0 && widthGap <= finalSlopPixel && heightGap >= 0 && heightGap <= finalSlopPixel) { mResult = danmaku; return ACTION_BREAK; } return ACTION_CONTINUE; } }; mCaches.forEach(consumer); return consumer.result(); } public class CacheHandler extends Handler { private static final int PREPARE = 0x1; public static final int ADD_DANMAKU = 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; public static final int REBUILD_CACHE = 0x11; public static final int DISABLE_CANCEL_FLAG = 0x12; private boolean mPause; private boolean mIsPlayerPause; 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 = mContext.mDanmakuFactory.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_DANMAKU: BaseDanmaku item = (BaseDanmaku) msg.obj; addDanmakuAndBuildCache(item); break; 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(); } entryRemoved(true, cacheitem, null); addDanmakuAndBuildCache(cacheitem); } } break; case CLEAR_TIMEOUT_CACHES: clearTimeOutCaches(); break; case SEEK: Long seekMills = (Long) msg.obj; if (seekMills != null) { long seekCacheTime = seekMills.longValue(); long oldCacheTime = mCacheTimer.currMillisecond; mCacheTimer.update(seekCacheTime); mSeekedFlag = true; long firstCacheTime = getFirstCacheTime(); if (seekCacheTime > oldCacheTime || firstCacheTime - seekCacheTime > mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) { evictAllNotInScreen(); } else { clearTimeOutCaches(); } prepareCaches(true); resume(); } break; case QUIT: removeCallbacksAndMessages(null); mPause = true; evictAll(); clearCachePool(); this.getLooper().quit(); break; case CLEAR_ALL_CACHES: evictAll(); mCacheTimer.update(mTimer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); mSeekedFlag = true; break; case CLEAR_OUTSIDE_CACHES: evictAllNotInScreen(); mCacheTimer.update(mTimer.currMillisecond); break; case CLEAR_OUTSIDE_CACHES_AND_RESET: evictAllNotInScreen(); mCacheTimer.update(mTimer.currMillisecond); requestClear(); break; case DISABLE_CANCEL_FLAG: mCancelFlag = false; break; } } private long dispatchAction() { if (mCacheTimer.currMillisecond <= mTimer.currMillisecond - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) { if (mContext.cachingPolicy.periodOfRecycle != CachingPolicy.CACHE_PERIOD_NOT_RECYCLE) { evictAllNotInScreen(); } mCacheTimer.update(mTimer.currMillisecond); sendEmptyMessage(BUILD_CACHES); return 0; } float level = getPoolPercent(); BaseDanmaku firstCache = mCaches.first(); //TODO 如果firstcache大于当前时间超过半屏并且水位在0.5f以下, long gapTime = firstCache != null ? firstCache.getActualTime() - mTimer.currMillisecond : 0; long doubleScreenDuration = mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * 2; if (level < 0.6f && gapTime > mContext.mDanmakuFactory.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 (firstCache != null && firstCache.isTimeOut() && deltaTime < -mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) { 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(); //fixme: consider hasReferences? mCachePool.release(cache); } private void preMeasure() { // pre measure IDanmakus danmakus = null; try { long begin = mTimer.currMillisecond; long end = begin + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * 2; danmakus = danmakuList.subnew(begin - mContext.mDanmakuFactory.MAX_DANMAKU_DURATION, end); } catch (Exception e) { } if (danmakus == null || danmakus.isEmpty()) { return; } danmakus.forEach(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku item) { if (mPause || mCancelFlag) { return ACTION_BREAK; } if (!item.hasPassedFilter()) { mContext.mDanmakuFilters.filter(item, 0, 0, null, true, mContext); } if (item.isFiltered()) { return ACTION_CONTINUE; } if (!item.isMeasured()) { item.measure(mDisp, true); } if (!item.isPrepared()) { item.prepare(mDisp, true); } return ACTION_CONTINUE; } }); } private long prepareCaches(final boolean repositioned) { preMeasure(); final long curr = mCacheTimer.currMillisecond - 30; final long end = curr + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * mScreenSize; if (end < mTimer.currMillisecond) { return 0; } final long startTime = SystemClock.uptimeMillis(); IDanmakus danmakus = null; int tryCount = 0; boolean hasException = false; do { try { danmakus = danmakuList.subnew(curr, end); } catch (Exception e) { hasException = true; SystemClock.sleep(10); } } while (++tryCount < 3 && danmakus == null && hasException); if (danmakus == null) { mCacheTimer.update(end); return 0; } final BaseDanmaku first = danmakus.first(); final BaseDanmaku last = danmakus.last(); if (first == null || last == null) { mCacheTimer.update(end); return 0; } long deltaTime = first.getActualTime() - mTimer.currMillisecond; long sleepTime = (deltaTime < 0 ? 30 : 30 + 10 * deltaTime / mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); sleepTime = Math.min(100, sleepTime); if (repositioned) { sleepTime = 0; } final long finalSleepTime = sleepTime; BaseDanmaku item = null; long consumingTime = 0; int orderInScreen = 0; int currScreenIndex = 0; final int sizeInScreen = danmakus.size(); // String message = ""; danmakus.forEach(new IDanmakus.DefaultConsumer() { int orderInScreen = 0; int currScreenIndex = 0; @Override public int accept(BaseDanmaku item) { if (mPause || mCancelFlag) { return ACTION_BREAK; } if (last.getActualTime() < mTimer.currMillisecond) { return ACTION_BREAK; } IDrawingCache cache = item.getDrawingCache(); if (cache != null && cache.get() != null) { return ACTION_CONTINUE; } if (repositioned == false && (item.isTimeOut() || !item.isOutside())) { return ACTION_CONTINUE; } if (!item.hasPassedFilter()) { mContext.mDanmakuFilters.filter(item, orderInScreen, sizeInScreen, null, true, mContext); } //Log.e("prepareCache", currScreenIndex+","+indexInScreen+"," + item.time+"skip:"+skip); if (item.priority == 0 && item.isFiltered()) { return ACTION_CONTINUE; } if (item.getType() == BaseDanmaku.TYPE_SCROLL_RL) { // 同屏弹幕密度只对滚动弹幕有效 int screenIndex = (int) ((item.getActualTime() - curr) / mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); if (currScreenIndex == screenIndex) orderInScreen++; else { orderInScreen = 0; currScreenIndex = screenIndex; } } if (!repositioned && !mIsPlayerPause) { try { synchronized (mDrawingNotify) { mDrawingNotify.wait(finalSleepTime); } } catch (InterruptedException e) { e.printStackTrace(); return ACTION_BREAK; } } // build cache buildCache(item, false); if (!repositioned) { long consumingTime = SystemClock.uptimeMillis() - startTime; if (consumingTime >= mContext.mDanmakuFactory.COMMON_DANMAKU_DURATION * mScreenSize) { // message = "break at consumingTime out:" + consumingTime; return ACTION_BREAK; } } return ACTION_CONTINUE; } }); consumingTime = SystemClock.uptimeMillis() - startTime; if (item != null) { mCacheTimer.update(item.getTime()); //Log.i("cache","stop at :"+item.time+","+count+",size:"+danmakus.size()+","+message); } else { mCacheTimer.update(end); } return consumingTime; } 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); } item.cache = null; return false; } catch (Exception e) { //Log.e("cache", "break at exception:" + e.getMessage()); if (cache != null) { mCachePool.release(cache); } item.cache = null; return false; } return true; } private byte buildCache(BaseDanmaku item, boolean forceInsert) { // measure if (!item.isMeasured()) { item.measure(mDisp, true); } DrawingCache cache = null; try { // try to find reuseable cache BaseDanmaku danmaku = findReusableCache(item, true, mContext.cachingPolicy.maxTimesOfStrictReusableFinds); if (danmaku != null) { cache = (DrawingCache) danmaku.cache; } if (cache != null) { cache.increaseReference(); item.cache = cache; //Log.w("cache", danmaku.text + "DrawingCache hit!!:" + item.paintWidth + "," + danmaku.paintWidth); mCacheManager.push(item, 0, forceInsert); return RESULT_SUCCESS; } // try to find reuseable cache from timeout || no-refrerence caches danmaku = findReusableCache(item, false, mContext.cachingPolicy.maxTimesOfReusableFinds); 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, mContext.cachingPolicy.bitsPerPixelOfCache); //redraw item.cache = cache; mCacheManager.push(item, 0, forceInsert); return RESULT_SUCCESS; } // guess cache size int cacheSize = DanmakuUtils.getCacheSize((int) item.paintWidth, (int) item.paintHeight, mContext.cachingPolicy.bitsPerPixelOfCache / 8); if (cacheSize * 2 > mMaxCacheSize) { // block large-size cache // Log.d("cache", "cache is too large:"+cacheSize); return RESULT_FAILED; } if (!forceInsert && (mRealSize + cacheSize > mMaxSize)) { // Log.d("cache", "break at MaxSize:"+mMaxSize); mCacheManager.clearTimeOutAndFilteredCaches(cacheSize, false); return RESULT_FAILED; } cache = mCachePool.acquire(); cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); item.cache = cache; boolean pushed = mCacheManager.push(item, sizeOf(item), forceInsert); 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; } } private final void addDanmakuAndBuildCache(BaseDanmaku danmaku) { if (danmaku.isTimeOut() || (danmaku.getActualTime() > mCacheTimer.currMillisecond + mContext.mDanmakuFactory.MAX_DANMAKU_DURATION && !danmaku.isLive)) { return; } if (danmaku.priority == 0 && danmaku.isFiltered()) { return; } IDrawingCache cache = danmaku.getDrawingCache(); if (cache == null || cache.get() == null) { buildCache(danmaku, true); } } public void begin() { sendEmptyMessage(PREPARE); sendEmptyMessageDelayed(CLEAR_TIMEOUT_CACHES, mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); } public void pause() { mPause = true; sendEmptyMessage(QUIT); } public void resume() { sendEmptyMessage(DISABLE_CANCEL_FLAG); mPause = false; removeMessages(DISPATCH_ACTIONS); sendEmptyMessage(DISPATCH_ACTIONS); sendEmptyMessageDelayed(CLEAR_TIMEOUT_CACHES, mContext.mDanmakuFactory.MAX_DANMAKU_DURATION); } public boolean isPause() { return mPause; } public void requestBuildCacheAndDraw(long correctionTime) { removeMessages(CacheHandler.BUILD_CACHES); mSeekedFlag = true; sendEmptyMessage(DISABLE_CANCEL_FLAG); mCacheTimer.update(mTimer.currMillisecond + correctionTime); sendEmptyMessage(CacheHandler.BUILD_CACHES); } public void onPlayStateChanged(boolean isPlaying) { mIsPlayerPause = !isPlaying; } } private void clearTimeOutAndFilteredCaches(int expectedFreeSize, final boolean forcePush) { final int fexpectedFreeSize = expectedFreeSize; mCaches.forEach(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku oldValue) { if (mEndFlag) { return IDanmakus.Consumer.ACTION_BREAK; } if (mRealSize + fexpectedFreeSize > mMaxSize) { if (oldValue.isTimeOut() || oldValue.isFiltered()) { entryRemoved(false, oldValue, null); return IDanmakus.Consumer.ACTION_REMOVE; } else if (forcePush) { return IDanmakus.Consumer.ACTION_BREAK; } } else { return IDanmakus.Consumer.ACTION_BREAK; } return IDanmakus.Consumer.ACTION_CONTINUE; } }); } public long getFirstCacheTime() { if (mCaches != null && mCaches.size() > 0) { BaseDanmaku firstItem = mCaches.first(); if (firstItem == null) return 0; return firstItem.getActualTime(); } 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.removeMessages(CacheHandler.DISABLE_CANCEL_FLAG); 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(DanmakuContext config, DanmakuConfigTag tag, Object... values) { if (super.handleOnDanmakuConfigChanged(config, tag, values)) { // do nothing } else if (DanmakuConfigTag.SCROLL_SPEED_FACTOR.equals(tag)) { mDisp.resetSlopPixel(mContext.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(mContext.scaleTextSize); } if (mCacheManager != null) { mCacheManager.requestClearAll(); mCacheManager.requestBuild(-mContext.mDanmakuFactory.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; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DanmakuFilters.java ================================================ package master.flame.danmaku.controller; import master.flame.danmaku.danmaku.model.Duration; import master.flame.danmaku.danmaku.util.SystemClock; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; 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.Danmakus; public class DanmakuFilters { public static final int FILTER_TYPE_TYPE = 1; public static final int FILYER_TYPE_QUANTITY = 2; public static final int FILTER_TYPE_ELAPSED_TIME = 4; public static final int FILTER_TYPE_TEXTCOLOR = 8; public static final int FILTER_TYPE_USER_ID = 16; public static final int FILTER_TYPE_USER_HASH = 32; public static final int FILTER_TYPE_USER_GUEST = 64; public static final int FILTER_TYPE_DUPLICATE_MERGE = 128; public static final int FILTER_TYPE_MAXIMUM_LINES = 256; public static final int FILTER_TYPE_OVERLAPPING = 512; public interface IDanmakuFilter { /* * 是否过滤 */ boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config); void setData(T data); void reset(); void clear(); } public static abstract class BaseDanmakuFilter implements IDanmakuFilter { @Override public void clear() { } } /** * 根据弹幕类型过滤 * * @author ch */ public static class TypeDanmakuFilter extends BaseDanmakuFilter> { final List mFilterTypes = Collections.synchronizedList(new ArrayList()); public void enableType(Integer type) { if (!mFilterTypes.contains(type)) mFilterTypes.add(type); } public void disableType(Integer type) { if (mFilterTypes.contains(type)) mFilterTypes.remove(type); } @Override public boolean filter(BaseDanmaku danmaku, int orderInScreen, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = danmaku != null && mFilterTypes.contains(danmaku.getType()); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_TYPE; } return filtered; } @Override public void setData(List data) { reset(); if (data != null) { for (Integer i : data) { enableType(i); } } } @Override public void reset() { mFilterTypes.clear(); } } /** * 根据同屏数量过滤弹幕 * * @author ch */ public static class QuantityDanmakuFilter extends BaseDanmakuFilter { protected int mMaximumSize = -1; protected BaseDanmaku mLastSkipped = null; private float mFilterFactor = 1f; private boolean needFilter(BaseDanmaku danmaku, int orderInScreen, int totalSizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext context) { if (mMaximumSize <= 0 || danmaku.getType() != BaseDanmaku.TYPE_SCROLL_RL) { return false; } if (mLastSkipped == null || mLastSkipped.isTimeOut()) { mLastSkipped = danmaku; return false; } long gapTime = danmaku.getActualTime() - mLastSkipped.getActualTime(); Duration maximumScrollDuration = context.mDanmakuFactory.MAX_Duration_Scroll_Danmaku; if (gapTime >= 0 && maximumScrollDuration != null && gapTime < (maximumScrollDuration.value * mFilterFactor)) { return true; } if (orderInScreen > mMaximumSize) { return true; } mLastSkipped = danmaku; return false; } @Override public synchronized boolean filter(BaseDanmaku danmaku, int orderInScreen, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = needFilter(danmaku, orderInScreen, totalsizeInScreen, timer, fromCachingTask, config); if (filtered) { danmaku.mFilterParam |= FILYER_TYPE_QUANTITY; } return filtered; } @Override public void setData(Integer data) { reset(); if (data == null) return; if (data != mMaximumSize) { mMaximumSize = data + data / 5; mFilterFactor = 1f / (float) mMaximumSize; } } @Override public synchronized void reset() { mLastSkipped = null; } @Override public void clear() { reset(); } } /** * 根据绘制耗时过滤弹幕 * * @author ch */ public static class ElapsedTimeFilter extends BaseDanmakuFilter { long mMaxTime = 20; // 绘制超过20ms就跳过 ,默认保持接近50fps private synchronized boolean needFilter(BaseDanmaku danmaku, int orderInScreen, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask) { if (timer == null || !danmaku.isOutside()) { return false; } long elapsedTime = SystemClock.uptimeMillis() - timer.currMillisecond; if (elapsedTime >= mMaxTime) { return true; } return false; } @Override public boolean filter(BaseDanmaku danmaku, int orderInScreen, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = needFilter(danmaku, orderInScreen, totalsizeInScreen, timer, fromCachingTask); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_ELAPSED_TIME; } return filtered; } @Override public void setData(Object data) { reset(); } @Override public synchronized void reset() { } @Override public void clear() { reset(); } } /** * 根据文本颜色白名单过滤 * * @author ch */ public static class TextColorFilter extends BaseDanmakuFilter> { public List mWhiteList = new ArrayList(); private void addToWhiteList(Integer color) { if (!mWhiteList.contains(color)) { mWhiteList.add(color); } } @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = danmaku != null && !mWhiteList.contains(danmaku.textColor); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_TEXTCOLOR; } return filtered; } @Override public void setData(List data) { reset(); if (data != null) { for (Integer i : data) { addToWhiteList(i); } } } @Override public void reset() { mWhiteList.clear(); } } /** * 根据用户标识黑名单过滤 * * @author ch */ public static abstract class UserFilter extends BaseDanmakuFilter> { public List mBlackList = new ArrayList(); private void addToBlackList(T id) { if (!mBlackList.contains(id)) { mBlackList.add(id); } } @Override public abstract boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config); @Override public void setData(List data) { reset(); if (data != null) { for (T i : data) { addToBlackList(i); } } } @Override public void reset() { mBlackList.clear(); } } /** * 根据用户Id黑名单过滤 * * @author ch */ public static class UserIdFilter extends UserFilter { @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = danmaku != null && mBlackList.contains(danmaku.userId); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_USER_ID; } return filtered; } } /** * 根据用户hash黑名单过滤 * * @author ch */ public static class UserHashFilter extends UserFilter { @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = danmaku != null && mBlackList.contains(danmaku.userHash); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_USER_HASH; } return filtered; } } /** * 屏蔽游客弹幕 * * @author ch */ public static class GuestFilter extends BaseDanmakuFilter { private Boolean mBlock = false; @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = mBlock && danmaku.isGuest; if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_USER_GUEST; } return filtered; } @Override public void setData(Boolean data) { mBlock = data; } @Override public void reset() { mBlock = false; } } public static class DuplicateMergingFilter extends BaseDanmakuFilter { protected final IDanmakus blockedDanmakus = new Danmakus(Danmakus.ST_BY_LIST); protected final LinkedHashMap currentDanmakus = new LinkedHashMap(); private final IDanmakus passedDanmakus = new Danmakus(Danmakus.ST_BY_LIST); private final void removeTimeoutDanmakus(final IDanmakus danmakus, final long limitTime) { danmakus.forEachSync(new IDanmakus.DefaultConsumer() { long startTime = SystemClock.uptimeMillis(); @Override public int accept(BaseDanmaku item) { try { if (SystemClock.uptimeMillis() - startTime > limitTime) { return ACTION_BREAK; } if (item.isTimeOut()) { return ACTION_REMOVE; } else { return ACTION_BREAK; } } catch (Exception e) { return ACTION_BREAK; } } }); } private void removeTimeoutDanmakus(LinkedHashMap danmakus, int limitTime) { Iterator> it = danmakus.entrySet().iterator(); long startTime = SystemClock.uptimeMillis(); while (it.hasNext()) { try { Entry entry = it.next(); BaseDanmaku item = entry.getValue(); if (item.isTimeOut()) { it.remove(); } else { break; } } catch (Exception e) { break; } if (SystemClock.uptimeMillis() - startTime > limitTime) { break; } } } public synchronized boolean needFilter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask) { removeTimeoutDanmakus(blockedDanmakus, 2); removeTimeoutDanmakus(passedDanmakus, 2); removeTimeoutDanmakus(currentDanmakus, 3); if (blockedDanmakus.contains(danmaku) && !danmaku.isOutside()) { return true; } if (passedDanmakus.contains(danmaku)) { return false; } if (currentDanmakus.containsKey(danmaku.text)) { currentDanmakus.put(String.valueOf(danmaku.text), danmaku); blockedDanmakus.removeItem(danmaku); blockedDanmakus.addItem(danmaku); return true; } else { currentDanmakus.put(String.valueOf(danmaku.text), danmaku); passedDanmakus.addItem(danmaku); return false; } } @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext config) { boolean filtered = needFilter(danmaku, index, totalsizeInScreen, timer, fromCachingTask); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_DUPLICATE_MERGE; } return filtered; } @Override public void setData(Void data) { } @Override public synchronized void reset() { passedDanmakus.clear(); blockedDanmakus.clear(); currentDanmakus.clear(); } @Override public void clear() { reset(); } } public static class MaximumLinesFilter extends BaseDanmakuFilter> { private Map mMaximumLinesPairs; @Override public boolean filter(BaseDanmaku danmaku, int lines, int totalsizeInScreen, DanmakuTimer timer, boolean willHit, DanmakuContext config) { boolean filtered = false; if (mMaximumLinesPairs != null) { Integer maxLines = mMaximumLinesPairs.get(danmaku.getType()); filtered = (maxLines != null && lines >= maxLines); if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_MAXIMUM_LINES; } } return filtered; } @Override public void setData(Map data) { mMaximumLinesPairs = data; } @Override public void reset() { mMaximumLinesPairs = null; } } public static class OverlappingFilter extends BaseDanmakuFilter> { private Map mEnabledPairs; @Override public boolean filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean willHit, DanmakuContext config) { boolean filtered = false; if (mEnabledPairs != null) { Boolean enabledValue = mEnabledPairs.get(danmaku.getType()); filtered = enabledValue != null && enabledValue && willHit; if (filtered) { danmaku.mFilterParam |= FILTER_TYPE_OVERLAPPING; } } return filtered; } @Override public void setData(Map data) { mEnabledPairs = data; } @Override public void reset() { mEnabledPairs = null; } } public final static String TAG_TYPE_DANMAKU_FILTER = "1010_Filter"; public final static String TAG_QUANTITY_DANMAKU_FILTER = "1011_Filter"; public final static String TAG_ELAPSED_TIME_FILTER = "1012_Filter"; public final static String TAG_TEXT_COLOR_DANMAKU_FILTER = "1013_Filter"; public final static String TAG_USER_ID_FILTER = "1014_Filter"; public final static String TAG_USER_HASH_FILTER = "1015_Filter"; public final static String TAG_GUEST_FILTER = "1016_Filter"; public final static String TAG_DUPLICATE_FILTER = "1017_Filter"; public final static String TAG_MAXIMUN_LINES_FILTER = "1018_Filter"; public final static String TAG_OVERLAPPING_FILTER = "1019_Filter"; public final static String TAG_PRIMARY_CUSTOM_FILTER = "2000_Primary_Custom_Filter"; public final Exception filterException = new Exception("not suuport this filter tag"); public void filter(BaseDanmaku danmaku, int index, int totalsizeInScreen, DanmakuTimer timer, boolean fromCachingTask, DanmakuContext context) { for (IDanmakuFilter f : mFilterArray) { if (f != null) { boolean filtered = f.filter(danmaku, index, totalsizeInScreen, timer, fromCachingTask, context); danmaku.filterResetFlag = context.mGlobalFlagValues.FILTER_RESET_FLAG; if (filtered) { break; } } } } public boolean filterSecondary(BaseDanmaku danmaku, int lines, int totalsizeInScreen, DanmakuTimer timer, boolean willHit, DanmakuContext context) { for (IDanmakuFilter f : mFilterArraySecondary) { if (f != null) { boolean filtered = f.filter(danmaku, lines, totalsizeInScreen, timer, willHit, context); danmaku.filterResetFlag = context.mGlobalFlagValues.FILTER_RESET_FLAG; if (filtered) { return true; } } } return false; } private final Map> filters = Collections .synchronizedSortedMap(new TreeMap>()); private final Map> filtersSecondary = Collections .synchronizedSortedMap(new TreeMap>()); IDanmakuFilter[] mFilterArray = new IDanmakuFilter[0]; IDanmakuFilter[] mFilterArraySecondary = new IDanmakuFilter[0]; public IDanmakuFilter get(String tag) { return get(tag, true); } public IDanmakuFilter get(String tag, boolean primary) { IDanmakuFilter f = primary ? filters.get(tag) : filtersSecondary.get(tag); if (f == null) { f = registerFilter(tag, primary); } return f; } public IDanmakuFilter registerFilter(String tag) { return registerFilter(tag, true); } public IDanmakuFilter registerFilter(String tag, boolean primary) { if (tag == null) { throwFilterException(); return null; } IDanmakuFilter filter = filters.get(tag); if (filter == null) { if (TAG_TYPE_DANMAKU_FILTER.equals(tag)) { filter = new TypeDanmakuFilter(); } else if (TAG_QUANTITY_DANMAKU_FILTER.equals(tag)) { filter = new QuantityDanmakuFilter(); } else if (TAG_ELAPSED_TIME_FILTER.equals(tag)) { filter = new ElapsedTimeFilter(); } else if (TAG_TEXT_COLOR_DANMAKU_FILTER.equals(tag)) { filter = new TextColorFilter(); } else if (TAG_USER_ID_FILTER.equals(tag)) { filter = new UserIdFilter(); } else if (TAG_USER_HASH_FILTER.equals(tag)) { filter = new UserHashFilter(); } else if (TAG_GUEST_FILTER.equals(tag)) { filter = new GuestFilter(); } else if (TAG_DUPLICATE_FILTER.equals(tag)) { filter = new DuplicateMergingFilter(); } else if (TAG_MAXIMUN_LINES_FILTER.equals(tag)) { filter = new MaximumLinesFilter(); } else if (TAG_OVERLAPPING_FILTER.equals(tag)) { filter = new OverlappingFilter(); } } if (filter == null) { throwFilterException(); return null; } filter.setData(null); if (primary) { filters.put(tag, filter); mFilterArray = filters.values().toArray(mFilterArray); } else { filtersSecondary.put(tag, filter); mFilterArraySecondary = filtersSecondary.values().toArray(mFilterArraySecondary); } return filter; } public void registerFilter(BaseDanmakuFilter filter) { filters.put(TAG_PRIMARY_CUSTOM_FILTER + "_" + filter.hashCode(), filter); mFilterArray = filters.values().toArray(mFilterArray); } public void unregisterFilter(String tag) { unregisterFilter(tag, true); } public void unregisterFilter(String tag, boolean primary) { IDanmakuFilter f = primary ? filters.remove(tag) : filtersSecondary.remove(tag); if (f != null) { f.clear(); if (primary) { mFilterArray = filters.values().toArray(mFilterArray); } else { mFilterArraySecondary = filtersSecondary.values().toArray(mFilterArraySecondary); } } } public void unregisterFilter(BaseDanmakuFilter filter) { filters.remove(TAG_PRIMARY_CUSTOM_FILTER + "_" + filter.hashCode()); mFilterArray = filters.values().toArray(mFilterArray); } public void clear() { for (IDanmakuFilter f : mFilterArray) { if (f != null) f.clear(); } for (IDanmakuFilter f : mFilterArraySecondary) { if (f != null) f.clear(); } } public void reset() { for (IDanmakuFilter f : mFilterArray) { if (f != null) f.reset(); } for (IDanmakuFilter f : mFilterArraySecondary) { if (f != null) f.reset(); } } public void release() { clear(); filters.clear(); mFilterArray = new IDanmakuFilter[0]; filtersSecondary.clear(); mFilterArraySecondary = new IDanmakuFilter[0]; } private void throwFilterException() { try { throw filterException; } catch (Exception e) { } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHandler.java ================================================ /* * 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.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.DisplayMetrics; import android.view.Choreographer; import java.util.LinkedList; import master.flame.danmaku.danmaku.model.AbsDanmakuSync; 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.IDisplayer; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.SystemClock; import tv.cjump.jni.DeviceUtils; public class DrawHandler extends Handler { private DanmakuContext mContext; private FrameCallback mFrameCallback; public interface Callback { public void prepared(); public void updateTimer(DanmakuTimer timer); public void danmakuShown(BaseDanmaku danmaku); public void drawingFinished(); } 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 int FORCE_RENDER = 14; 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 mDisp; private final RenderingState mRenderingState = new RenderingState(); private static final int MAX_RECORD_SIZE = 500; private LinkedList mDrawTimes = new LinkedList<>(); private UpdateThread mThread; private boolean mUpdateInSeparateThread; private long mCordonTime = 30; private long mCordonTime2 = 60; private long mFrameUpdateRate = 16; @SuppressWarnings("unused") private long mThresholdTime; private long mLastDeltaTime; private boolean mInSeekingAction; private long mDesireSeekingTime; private long mRemainingTime; private boolean mInSyncAction; private boolean mInWaitingState; private boolean mIdleSleep; private boolean mNonBlockModeEnable; public DrawHandler(Looper looper, IDanmakuViewController view, boolean danmakuVisibile) { super(looper); mIdleSleep = !DeviceUtils.isProblemBoxDevice(); bindView(view); if (danmakuVisibile) { showDanmakus(null); } else { hideDanmakus(false); } mDanmakusVisible = danmakuVisibile; } private void bindView(IDanmakuViewController view) { this.mDanmakuView = view; } public void setIdleSleep(boolean enable) { mIdleSleep = enable; } public void enableNonBlockMode(boolean enable) { mNonBlockModeEnable = enable; } public void setConfig(DanmakuContext config) { mContext = config; } public void setParser(BaseDanmakuParser parser) { mParser = parser; DanmakuTimer timer = parser.getTimer(); if (timer != null) { this.timer = timer; } } public void setCallback(Callback cb) { mCallback = cb; } public void quit() { quitFlag = true; sendEmptyMessage(QUIT); } public boolean isStop() { return quitFlag; } @Override public void handleMessage(Message msg) { int what = msg.what; switch (what) { case PREPARE: mTimeBase = SystemClock.uptimeMillis(); if (mParser == null || !mDanmakuView.isViewReady()) { sendEmptyMessageDelayed(PREPARE, 100); } else { prepare(new Runnable() { @Override public void run() { pausedPosition = 0; mReady = true; if (mCallback != null) { mCallback.prepared(); } } }); } break; case SHOW_DANMAKUS: mDanmakusVisible = true; Long start = (Long) msg.obj; boolean resume = false; if (drawTask != null) { if (start == null) { timer.update(getCurrentTime()); drawTask.requestClear(); } else { drawTask.start(); drawTask.seek(start); drawTask.requestClear(); resume = true; } } if (quitFlag && mDanmakuView != null) { mDanmakuView.drawDanmakus(); } notifyRendering(); if (!resume) { break; } case START: Long startTime = (Long) msg.obj; if (startTime != null) { pausedPosition = startTime; } else { pausedPosition = 0; } case SEEK_POS: if (what == SEEK_POS) { quitFlag = true; quitUpdateThread(); Long position = (Long) msg.obj; long deltaMs = position - timer.currMillisecond; mTimeBase -= deltaMs; timer.update(position); mContext.mGlobalFlagValues.updateMeasureFlag(); if (drawTask != null) drawTask.seek(position); pausedPosition = position; } case RESUME: removeMessages(DrawHandler.PAUSE); quitFlag = false; if (mReady) { mRenderingState.reset(); mDrawTimes.clear(); mTimeBase = SystemClock.uptimeMillis() - pausedPosition; timer.update(pausedPosition); removeMessages(RESUME); sendEmptyMessage(UPDATE); drawTask.start(); notifyRendering(); mInSeekingAction = false; if (drawTask != null) { drawTask.onPlayStateChanged(IDrawTask.PLAY_STATE_PLAYING); } } else { sendEmptyMessageDelayed(RESUME, 100); } break; case UPDATE: if (mContext.updateMethod == 0) { updateInChoreographer(); } else if (mContext.updateMethod == 1) { updateInNewThread(); } else if (mContext.updateMethod == 2) { updateInCurrentThread(); } break; case NOTIFY_DISP_SIZE_CHANGED: mContext.mDanmakuFactory.notifyDispSizeChanged(mContext); Boolean updateFlag = (Boolean) msg.obj; if (updateFlag != null && updateFlag) { mContext.mGlobalFlagValues.updateMeasureFlag(); mContext.mGlobalFlagValues.updateVisibleFlag(); drawTask.requestClearRetainer(); } 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(DrawHandler.RESUME); removeMessages(UPDATE); if (drawTask != null) { drawTask.onPlayStateChanged(IDrawTask.PLAY_STATE_PAUSE); } case QUIT: if (what == QUIT) { removeCallbacksAndMessages(null); } quitFlag = true; syncTimerIfNeeded(); pausedPosition = timer.currMillisecond; if (mUpdateInSeparateThread) { notifyRendering(); quitUpdateThread(); } if (mFrameCallback != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { Choreographer.getInstance().removeFrameCallback(mFrameCallback); } } 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; case FORCE_RENDER: if (drawTask != null) { drawTask.requestRender(); } break; } } private synchronized void quitUpdateThread() { UpdateThread thread = mThread; mThread = null; if (thread != null) { synchronized (drawTask) { drawTask.notifyAll(); } thread.quit(); try { thread.join(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } private void updateInCurrentThread() { if (quitFlag) { return; } long startMS = SystemClock.uptimeMillis(); long d = syncTimer(startMS); if (d < 0 && !mNonBlockModeEnable) { removeMessages(UPDATE); sendEmptyMessageDelayed(UPDATE, 60 - d); return; } d = mDanmakuView.drawDanmakus(); removeMessages(UPDATE); if (d > mCordonTime2) { // this situation may be cuased by ui-thread waiting of DanmakuView, so we sync-timer at once timer.add(d); mDrawTimes.clear(); } if (!mDanmakusVisible) { waitRendering(INDEFINITE_TIME); return; } else if (mRenderingState.nothingRendered && mIdleSleep) { long dTime = mRenderingState.endTime - timer.currMillisecond; if (dTime > 500) { waitRendering(dTime - 10); 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 = SystemClock.uptimeMillis(); long dTime = 0; while (!isQuited() && !quitFlag) { long startMS = SystemClock.uptimeMillis(); dTime = SystemClock.uptimeMillis() - lastTime; long diffTime = mFrameUpdateRate - dTime; if (diffTime > 1 && !mNonBlockModeEnable) { SystemClock.sleep(1); continue; } lastTime = startMS; long d = syncTimer(startMS); if (d < 0 && !mNonBlockModeEnable) { SystemClock.sleep(60 - d); continue; } d = mDanmakuView.drawDanmakus(); if (d > mCordonTime2) { // this situation may be cuased by ui-thread waiting of DanmakuView, so we sync-timer at once timer.add(d); mDrawTimes.clear(); } if (!mDanmakusVisible) { waitRendering(INDEFINITE_TIME); } else if (mRenderingState.nothingRendered && mIdleSleep) { dTime = mRenderingState.endTime - timer.currMillisecond; if (dTime > 500) { notifyRendering(); waitRendering(dTime - 10); } } } } }; mThread.start(); } @TargetApi(16) private class FrameCallback implements Choreographer.FrameCallback { @Override public void doFrame(long frameTimeNanos) { sendEmptyMessage(UPDATE); } }; @TargetApi(16) private void updateInChoreographer() { if (quitFlag) { return; } Choreographer.getInstance().postFrameCallback(mFrameCallback); long startMS = SystemClock.uptimeMillis(); long d = syncTimer(startMS); if (d < 0) { removeMessages(UPDATE); return; } d = mDanmakuView.drawDanmakus(); removeMessages(UPDATE); if (d > mCordonTime2) { // this situation may be cuased by ui-thread waiting of DanmakuView, so we sync-timer at once timer.add(d); mDrawTimes.clear(); } if (!mDanmakusVisible) { waitRendering(INDEFINITE_TIME); return; } else if (mRenderingState.nothingRendered && mIdleSleep) { long dTime = mRenderingState.endTime - timer.currMillisecond; if (dTime > 500) { waitRendering(dTime - 10); return; } } } private final long syncTimer(long startMS) { if (mInSeekingAction || mInSyncAction) { return 0; } mInSyncAction = true; long d = 0; long time = startMS - mTimeBase; if (mNonBlockModeEnable) { if (mCallback != null) { mCallback.updateTimer(timer); d = timer.lastInterval(); } } else if (!mDanmakusVisible || mRenderingState.nothingRendered || mInWaitingState) { timer.update(time); mRemainingTime = 0; if (mCallback != null) { mCallback.updateTimer(timer); } } 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 (a > 3 && a < 8 && mLastDeltaTime >= mFrameUpdateRate && mLastDeltaTime <= mCordonTime) { d = mLastDeltaTime; } gapTime -= d; mLastDeltaTime = d; } mRemainingTime = gapTime; timer.add(d); if (mCallback != null) { mCallback.updateTimer(timer); } // Log.e("DrawHandler", time+"|d:" + d + "RemaingTime:" + mRemainingTime + ",gapTime:" + gapTime + ",rtim:" + mRenderingState.consumingTime + ",average:" + averageTime); } mInSyncAction = false; return d; } private void syncTimerIfNeeded() { if (mInWaitingState) { syncTimer(SystemClock.uptimeMillis()); } } private void initRenderingConfigs() { long averageFrameConsumingTime = 16; mCordonTime = Math.max(33, (long) (averageFrameConsumingTime * 2.5f)); mCordonTime2 = (long) (mCordonTime * 2.5f); mFrameUpdateRate = Math.max(16, averageFrameConsumingTime / 15 * 15); 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.getViewWidth(), mDanmakuView.getViewHeight(), mDanmakuView.isHardwareAccelerated(), new IDrawTask.TaskListener() { @Override public void ready() { initRenderingConfigs(); runnable.run(); } @Override public void onDanmakuAdd(BaseDanmaku danmaku) { if (danmaku.isTimeOut()) { return; } long delay = danmaku.getActualTime() - getCurrentTime(); if (delay < mContext.mDanmakuFactory.MAX_DANMAKU_DURATION && (mInWaitingState || mRenderingState.nothingRendered)) { notifyRendering(); } else if (delay > 0 && delay <= mContext.mDanmakuFactory.MAX_DANMAKU_DURATION) { sendEmptyMessageDelayed(NOTIFY_RENDERING, delay); } } @Override public void onDanmakuShown(BaseDanmaku danmaku) { if (mCallback != null) { mCallback.danmakuShown(danmaku); } } @Override public void onDanmakusDrawingFinished() { if (mCallback != null) { mCallback.drawingFinished(); } } @Override public void onDanmakuConfigChanged() { redrawIfNeeded(); } }); } 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 = mContext.getDisplayer(); mDisp.setSize(width, height); DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); mDisp.setDensities(displayMetrics.density, displayMetrics.densityDpi, displayMetrics.scaledDensity); mDisp.resetSlopPixel(mContext.scaleTextSize); mDisp.setHardwareAccelerated(isHardwareAccelerated); IDrawTask task = useDrwaingCache ? new CacheManagingDrawTask(timer, mContext, taskListener) : new DrawTask(timer, mContext, taskListener); task.setParser(mParser); task.prepare(); obtainMessage(NOTIFY_DISP_SIZE_CHANGED, false).sendToTarget(); return task; } public void seekTo(Long ms) { mInSeekingAction = true; mDesireSeekingTime = ms; 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.flags = mContext.mGlobalFlagValues; item.setTimer(timer); drawTask.addDanmaku(item); obtainMessage(NOTIFY_RENDERING).sendToTarget(); } } public void invalidateDanmaku(BaseDanmaku item, boolean remeasure) { if (drawTask != null && item != null) { drawTask.invalidateDanmaku(item, remeasure); } redrawIfNeeded(); } public void resume() { removeMessages(DrawHandler.PAUSE); sendEmptyMessage(DrawHandler.RESUME); } public void prepare() { mReady = false; if (Build.VERSION.SDK_INT < 16 && mContext.updateMethod == 0) { mContext.updateMethod = 2; } if (mContext.updateMethod == 0) { mFrameCallback = new FrameCallback(); } mUpdateInSeparateThread = (mContext.updateMethod == 1); sendEmptyMessage(DrawHandler.PREPARE); } public void pause() { removeMessages(DrawHandler.RESUME); syncTimerIfNeeded(); sendEmptyMessage(DrawHandler.PAUSE); } public void showDanmakus(Long position) { if (mDanmakusVisible) return; mDanmakusVisible = true; removeMessages(SHOW_DANMAKUS); removeMessages(HIDE_DANMAKUS); obtainMessage(SHOW_DANMAKUS, position).sendToTarget(); } public long hideDanmakus(boolean quitDrawTask) { if (!mDanmakusVisible) return timer.currMillisecond; mDanmakusVisible = false; removeMessages(SHOW_DANMAKUS); removeMessages(HIDE_DANMAKUS); obtainMessage(HIDE_DANMAKUS, quitDrawTask).sendToTarget(); return timer.currMillisecond; } public void forceRender() { removeMessages(FORCE_RENDER); obtainMessage(FORCE_RENDER).sendToTarget(); } public boolean getVisibility() { return mDanmakusVisible; } public RenderingState draw(Canvas canvas) { if (drawTask == null) return mRenderingState; if (!mInWaitingState) { AbsDanmakuSync danmakuSync = mContext.danmakuSync; if (danmakuSync != null) { do { boolean isSyncPlayingState = danmakuSync.isSyncPlayingState(); if (!isSyncPlayingState && quitFlag) { break; } int syncState = danmakuSync.getSyncState(); if (syncState == AbsDanmakuSync.SYNC_STATE_PLAYING) { long fromTime = timer.currMillisecond; long toTime = danmakuSync.getUptimeMillis(); long offset = toTime - fromTime; if (Math.abs(offset) > danmakuSync.getThresholdTimeMills()) { if (isSyncPlayingState && quitFlag) { resume(); } drawTask.requestSync(fromTime, toTime, offset); timer.update(toTime); mTimeBase -= offset; mRemainingTime = 0; } } else if (syncState == AbsDanmakuSync.SYNC_STATE_HALT) { if (isSyncPlayingState && !quitFlag) { pause(); } } } while (false); } } mDisp.setExtraData(canvas); mRenderingState.set(drawTask.draw(mDisp)); recordRenderingTime(); return mRenderingState; } private void redrawIfNeeded() { if (quitFlag && mDanmakusVisible) { removeMessages(UPDATE_WHEN_PAUSED); sendEmptyMessageDelayed(UPDATE_WHEN_PAUSED, 100); } } private void notifyRendering() { if (!mInWaitingState) { return; } if(drawTask != null) { drawTask.requestClear(); } if (mUpdateInSeparateThread) { synchronized (this) { mDrawTimes.clear(); } synchronized (drawTask) { drawTask.notifyAll(); } } else { mDrawTimes.clear(); removeMessages(UPDATE); sendEmptyMessage(UPDATE); } mInWaitingState = false; } private void waitRendering(long dTime) { if (isStop() || !isPrepared() || mInSeekingAction) { return; } mRenderingState.sysTime = SystemClock.uptimeMillis(); mInWaitingState = true; if (mUpdateInSeparateThread) { if (mThread == null) { return; } 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 first = mDrawTimes.peekFirst(); Long last = mDrawTimes.peekLast(); if (first == null || last == null) { return 0; } long dtime = last - first; return dtime / frames; } private synchronized void recordRenderingTime() { long lastTime = SystemClock.uptimeMillis(); 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(boolean isClearDanmakusOnScreen) { if (drawTask != null) { drawTask.removeAllDanmakus(isClearDanmakusOnScreen); } } public void removeAllLiveDanmakus() { if (drawTask != null) { drawTask.removeAllLiveDanmakus(); } } public IDanmakus getCurrentVisibleDanmakus() { if (drawTask != null) { return drawTask.getVisibleDanmakusOnTime(getCurrentTime()); } return null; } public long getCurrentTime() { if (!mReady) { return 0; } if (mInSeekingAction) { return mDesireSeekingTime; } if (quitFlag || !mInWaitingState) { return timer.currMillisecond - mRemainingTime; } return SystemClock.uptimeMillis() - mTimeBase; } public void clearDanmakusOnScreen() { obtainMessage(CLEAR_DANMAKUS_ON_SCREEN).sendToTarget(); } public DanmakuContext getConfig() { return mContext; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawHelper.java ================================================ /* * 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 android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; public class DrawHelper { public static Paint PAINT, PAINT_FPS; public static RectF RECT; private static boolean USE_DRAWCOLOR_TO_CLEAR_CANVAS = true; private static boolean USE_DRAWCOLOR_MODE_CLEAR = true; static { PAINT = new Paint(); PAINT.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); PAINT.setColor(Color.TRANSPARENT); RECT = new RectF(); } public static void useDrawColorToClearCanvas(boolean use, boolean useClearMode) { USE_DRAWCOLOR_TO_CLEAR_CANVAS = use; USE_DRAWCOLOR_MODE_CLEAR = useClearMode; } public static void drawFPS(Canvas canvas, String text) { if (PAINT_FPS == null) { PAINT_FPS = new Paint(); PAINT_FPS.setColor(Color.RED); PAINT_FPS.setTextSize(30); } int top = canvas.getHeight() - 50; clearCanvas(canvas, 10, top - 50, (int) (PAINT_FPS.measureText(text) + 20), canvas.getHeight()); canvas.drawText(text, 10, top, PAINT_FPS); } public static void clearCanvas(Canvas canvas) { if (USE_DRAWCOLOR_TO_CLEAR_CANVAS) { if(USE_DRAWCOLOR_MODE_CLEAR) { canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); } else { canvas.drawColor(Color.TRANSPARENT); } } else { RECT.set(0, 0, canvas.getWidth(), canvas.getHeight()); clearCanvas(canvas, RECT); } } public static void fillTransparent(Canvas canvas){ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); } public static void clearCanvas(Canvas canvas, float left, float top, float right, float bottom) { RECT.set(left, top, right, bottom); clearCanvas(canvas, RECT); } private static void clearCanvas(Canvas canvas, RectF rect) { if (rect.width() <= 0 || rect.height() <= 0) { return; } canvas.drawRect(rect, PAINT); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/DrawTask.java ================================================ /* * 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()); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/IDanmakuView.java ================================================ package master.flame.danmaku.controller; import android.view.View; import master.flame.danmaku.controller.DrawHandler.Callback; 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; public interface IDanmakuView { public final static int THREAD_TYPE_NORMAL_PRIORITY = 0x0; public final static int THREAD_TYPE_MAIN_THREAD = 0x1; public final static int THREAD_TYPE_HIGH_PRIORITY = 0x2; public final static int THREAD_TYPE_LOW_PRIORITY = 0x3; public boolean isPrepared(); public boolean isPaused(); public boolean isHardwareAccelerated(); /** * * @param type One of THREAD_TYPE_MAIN_THREAD, THREAD_TYPE_HIGH_PRIORITY, THREAD_TYPE_NORMAL_PRIORITY, or THREAD_TYPE_LOW_PRIORITY. */ public void setDrawingThreadType(int type); public void enableDanmakuDrawingCache(boolean enable); public boolean isDanmakuDrawingCacheEnabled(); public void showFPS(boolean show); /** * danmaku.isLive == true的情况下,请在非UI线程中使用此方法,避免可能卡住主线程 * @param item */ public void addDanmaku(BaseDanmaku item); public void invalidateDanmaku(BaseDanmaku item, boolean remeasure); public void removeAllDanmakus(boolean isClearDanmakusOnScreen); public void removeAllLiveDanmakus(); public IDanmakus getCurrentVisibleDanmakus(); public void setCallback(Callback callback); /** * for getting the accurate play-time. use this method intead of parser.getTimer().currMillisecond * @return */ public long getCurrentTime(); public DanmakuContext getConfig(); // ------------- Android View方法 -------------------- public View getView(); public int getWidth(); public int getHeight(); public void setVisibility(int visibility); public boolean isShown(); // ------------- 播放控制 ------------------- public void prepare(BaseDanmakuParser parser, DanmakuContext config); public void seekTo(Long ms); public void start(); public void start(long postion); public void stop(); public void pause(); public void resume(); public void release(); public void toggle(); public void show(); public void hide(); /** * show the danmakuview again if you called hideAndPauseDrawTask() * @param position The position you want to resume * @see #hideAndPauseDrawTask */ public void showAndResumeDrawTask(Long position); /** * hide the danmakuview and pause the drawtask * @return the paused position * @see #showAndResumeDrawTask */ public long hideAndPauseDrawTask(); public void clearDanmakusOnScreen(); // ------------- Click Listener ------------------- public interface OnDanmakuClickListener { /** * @param danmakus all to be clicked, this value may be empty; * danmakus.last() is the latest danmaku which may be null; * @return True if the event was handled, false otherwise. */ boolean onDanmakuClick(IDanmakus danmakus); boolean onDanmakuLongClick(IDanmakus danmakus); boolean onViewClick(IDanmakuView view); } public void setOnDanmakuClickListener(OnDanmakuClickListener listener); public void setOnDanmakuClickListener(OnDanmakuClickListener listener, float xOff, float yOff); public OnDanmakuClickListener getOnDanmakuClickListener(); public float getXOff(); public float getYOff(); void forceRender(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/IDanmakuViewController.java ================================================ package master.flame.danmaku.controller; import android.content.Context; /** * For internal control. DO NOT ACCESS this interface. */ public interface IDanmakuViewController { public boolean isViewReady(); public int getViewWidth(); public int getViewHeight(); public Context getContext(); public long drawDanmakus(); public void clear(); public boolean isHardwareAccelerated(); public boolean isDanmakuDrawingCacheEnabled(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/IDrawTask.java ================================================ /* * 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 master.flame.danmaku.danmaku.model.AbsDisplayer; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.renderer.IRenderer.RenderingState; public interface IDrawTask { public static final int PLAY_STATE_PLAYING = 1; public static final int PLAY_STATE_PAUSE = 2; public void addDanmaku(BaseDanmaku item); public void removeAllDanmakus(boolean isClearDanmakusOnScreen); public void removeAllLiveDanmakus(); public void clearDanmakusOnScreen(long currMillis); public IDanmakus getVisibleDanmakusOnTime(long time); public RenderingState draw(AbsDisplayer displayer); public void reset(); public void seek(long mills); public void start(); public void quit(); public void prepare(); public void onPlayStateChanged(int state); public void requestClear(); void requestClearRetainer(); void requestSync(long fromTimeMills, long toTimeMills, long offsetMills); public void setParser(BaseDanmakuParser parser); void invalidateDanmaku(BaseDanmaku item, boolean remeasure); public interface TaskListener { public void ready(); public void onDanmakuAdd(BaseDanmaku danmaku); public void onDanmakuShown(BaseDanmaku danmaku); public void onDanmakuConfigChanged(); public void onDanmakusDrawingFinished(); } public void requestHide(); void requestRender(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/controller/UpdateThread.java ================================================ package master.flame.danmaku.controller; public class UpdateThread extends Thread { public UpdateThread(String name) { super(name); } volatile boolean mIsQuited; public void quit() { mIsQuited = true; } public boolean isQuited() { return mIsQuited; } @Override public void run() { if (mIsQuited) return; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/loader/ILoader.java ================================================ /* * 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.loader; import java.io.InputStream; import master.flame.danmaku.danmaku.parser.IDataSource; public interface ILoader { /** * @return data source */ IDataSource getDataSource(); /** * @param uri 弹幕文件地址(http:// file://) */ void load(String uri) throws IllegalDataException; /** * * @param in stream from Internet or local file */ void load(InputStream in) throws IllegalDataException; } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/loader/IllegalDataException.java ================================================ package master.flame.danmaku.danmaku.loader; /** * Thrown when data is loading which can not be reasonably deal with. * @author yrom * */ public class IllegalDataException extends Exception { private static final long serialVersionUID = 10441759254L; public IllegalDataException() { super(); } public IllegalDataException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } public IllegalDataException(String detailMessage) { super(detailMessage); } public IllegalDataException(Throwable throwable) { super(throwable); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/loader/android/AcFunDanmakuLoader.java ================================================ package master.flame.danmaku.danmaku.loader.android; import java.io.InputStream; import master.flame.danmaku.danmaku.loader.ILoader; import master.flame.danmaku.danmaku.loader.IllegalDataException; import master.flame.danmaku.danmaku.parser.android.JSONSource; import android.net.Uri; /** * Ac danmaku loader * @author yrom * */ public class AcFunDanmakuLoader implements ILoader{ private AcFunDanmakuLoader(){} private static volatile AcFunDanmakuLoader instance; private JSONSource dataSource; public static ILoader instance() { if(instance == null){ synchronized (AcFunDanmakuLoader.class){ if(instance == null) instance = new AcFunDanmakuLoader(); } } return instance; } @Override public JSONSource getDataSource() { return dataSource; } @Override public void load(String uri) throws IllegalDataException { try { dataSource = new JSONSource(Uri.parse(uri)); } catch (Exception e) { throw new IllegalDataException(e); } } @Override public void load(InputStream in) throws IllegalDataException { try { dataSource = new JSONSource(in); } catch (Exception e) { throw new IllegalDataException(e); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/loader/android/BiliDanmakuLoader.java ================================================ /* * 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.loader.android; import java.io.InputStream; import master.flame.danmaku.danmaku.loader.ILoader; import master.flame.danmaku.danmaku.loader.IllegalDataException; import master.flame.danmaku.danmaku.parser.android.AndroidFileSource; public class BiliDanmakuLoader implements ILoader { private static BiliDanmakuLoader _instance; private AndroidFileSource dataSource; private BiliDanmakuLoader() { } public static BiliDanmakuLoader instance() { if (_instance == null) { _instance = new BiliDanmakuLoader(); } return _instance; } public void load(String uri) throws IllegalDataException { try { dataSource = new AndroidFileSource(uri); } catch (Exception e) { throw new IllegalDataException(e); } } public void load(InputStream stream) { dataSource = new AndroidFileSource(stream); } @Override public AndroidFileSource getDataSource() { return dataSource; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/loader/android/DanmakuLoaderFactory.java ================================================ /* * 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.loader.android; import master.flame.danmaku.danmaku.loader.ILoader; public class DanmakuLoaderFactory { public static String TAG_BILI = "bili"; public static String TAG_ACFUN = "acfun"; public static ILoader create(String tag) { if (TAG_BILI.equalsIgnoreCase(tag)) { return BiliDanmakuLoader.instance(); } else if(TAG_ACFUN.equalsIgnoreCase(tag)) return AcFunDanmakuLoader.instance(); return null; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/AbsDanmakuSync.java ================================================ package master.flame.danmaku.danmaku.model; public abstract class AbsDanmakuSync { public static final int SYNC_STATE_HALT = 1; public static final int SYNC_STATE_PLAYING = 2; /** * Get the uptime of timer synchronization * * @return */ public abstract long getUptimeMillis(); /** * Get the state of timer synchronization * * @return SYNC_STATE_HALT or SYNC_STATE_PLAYING */ public abstract int getSyncState(); /** * Get the threshold-time of timer synchronization * This value should be greater than or equal to 1000L * * @return */ public long getThresholdTimeMills() { return 1500L; } /** * synchronize pause/resume state with outside playback * @return */ public boolean isSyncPlayingState() { return false; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/AbsDisplayer.java ================================================ package master.flame.danmaku.danmaku.model; import master.flame.danmaku.danmaku.model.android.BaseCacheStuffer; public abstract class AbsDisplayer implements IDisplayer { public abstract T getExtraData(); public abstract void setExtraData(T data); @Override public boolean isHardwareAccelerated() { return false; } public abstract void drawDanmaku(BaseDanmaku danmaku, T canvas, float left, float top, boolean fromWorkerThread); public abstract void clearTextHeightCache(); public abstract void setTypeFace(F font); public abstract void setFakeBoldText(boolean bold); public abstract void setTransparency(int newTransparency); public abstract void setScaleTextSizeFactor(float factor); public abstract void setCacheStuffer(BaseCacheStuffer cacheStuffer); public abstract BaseCacheStuffer getCacheStuffer(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/AlphaValue.java ================================================ package master.flame.danmaku.danmaku.model; public class AlphaValue { public static int MAX = 255; public static int TRANSPARENT = 0; } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/BaseDanmaku.java ================================================ /* * 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; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/Danmaku.java ================================================ /* * 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 master.flame.danmaku.danmaku.util.DanmakuUtils; public class Danmaku extends BaseDanmaku { public Danmaku(CharSequence text) { DanmakuUtils.fillText(this, text); } @Override public boolean isShown() { return false; } @Override public void layout(IDisplayer displayer, float x, float y) { } @Override public float[] getRectAtTime(IDisplayer displayer, long time) { return null; } @Override public float getLeft() { return 0; } @Override public float getTop() { return 0; } @Override public float getRight() { return 0; } @Override public float getBottom() { return 0; } @Override public int getType() { return 0; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/DanmakuTimer.java ================================================ /* * 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; public class DanmakuTimer { public long currMillisecond; private long lastInterval; public DanmakuTimer() { } public DanmakuTimer(long curr) { update(curr); } public long update(long curr) { lastInterval = curr - currMillisecond; currMillisecond = curr; return lastInterval; } public long add(long mills) { return update(currMillisecond + mills); } public long lastInterval() { return lastInterval; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/Duration.java ================================================ package master.flame.danmaku.danmaku.model; public class Duration implements Cloneable { private long mInitialDuration; private float factor = 1.0f; public long value; public Duration(long initialDuration) { mInitialDuration = initialDuration; value = initialDuration; } public void setValue(long initialDuration) { mInitialDuration = initialDuration; value = (long) (mInitialDuration * factor); } public void setFactor(float f) { if (factor != f) { factor = f; value = (long) (mInitialDuration * f); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/FBDanmaku.java ================================================ /* * 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; public class FBDanmaku extends FTDanmaku { public FBDanmaku(Duration duration) { super(duration); } @Override public int getType() { return TYPE_FIX_BOTTOM; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/FTDanmaku.java ================================================ /* * 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; /** * 顶部固定弹幕 */ public class FTDanmaku extends BaseDanmaku { private float x = 0; protected float y = -1; private float[] RECT = null; private float mLastLeft; private float mLastPaintWidth; private int mLastDispWidth; public FTDanmaku(Duration duration) { this.duration = duration; } @Override public void layout(IDisplayer displayer, float x, float y) { if (mTimer != null) { long deltaDuration = mTimer.currMillisecond - getActualTime(); if (deltaDuration > 0 && deltaDuration < duration.value) { if (!this.isShown()) { this.x = getLeft(displayer); this.y = y; this.setVisibility(true); } return; } this.setVisibility(false); this.y = -1; this.x = displayer.getWidth(); } } protected float getLeft(IDisplayer displayer) { if (mLastDispWidth == displayer.getWidth() && mLastPaintWidth == paintWidth) { return mLastLeft; } float left = (displayer.getWidth() - paintWidth) / 2; mLastDispWidth = displayer.getWidth(); mLastPaintWidth = paintWidth; mLastLeft = left; return left; } @Override public float[] getRectAtTime(IDisplayer displayer, long time) { if (!isMeasured()) return null; float left = getLeft(displayer); if (RECT == null) { RECT = new float[4]; } RECT[0] = left; RECT[1] = y; RECT[2] = left + paintWidth; RECT[3] = y + paintHeight; return RECT; } @Override public float getLeft() { return x; } @Override public float getTop() { return y; } @Override public float getRight() { return x + paintWidth; } @Override public float getBottom() { return y + paintHeight; } @Override public int getType() { return TYPE_FIX_TOP; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/GlobalFlagValues.java ================================================ package master.flame.danmaku.danmaku.model; public class GlobalFlagValues { public int MEASURE_RESET_FLAG = 0; public int VISIBLE_RESET_FLAG = 0; public int FILTER_RESET_FLAG = 0; public int FIRST_SHOWN_RESET_FLAG = 0; public int SYNC_TIME_OFFSET_RESET_FLAG = 0; public int PREPARE_RESET_FLAG = 0; public void resetAll() { VISIBLE_RESET_FLAG = 0; MEASURE_RESET_FLAG = 0; FILTER_RESET_FLAG = 0; FIRST_SHOWN_RESET_FLAG = 0; SYNC_TIME_OFFSET_RESET_FLAG = 0; PREPARE_RESET_FLAG = 0; } public void updateAll() { VISIBLE_RESET_FLAG++; MEASURE_RESET_FLAG++; FILTER_RESET_FLAG++; FIRST_SHOWN_RESET_FLAG++; SYNC_TIME_OFFSET_RESET_FLAG++; PREPARE_RESET_FLAG++; } public void updateVisibleFlag() { VISIBLE_RESET_FLAG++; } public void updateMeasureFlag() { MEASURE_RESET_FLAG++; } public void updateFilterFlag() { FILTER_RESET_FLAG++; } public void updateFirstShownFlag() { FIRST_SHOWN_RESET_FLAG++; } public void updateSyncOffsetTimeFlag() { SYNC_TIME_OFFSET_RESET_FLAG++; } public void updatePrepareFlag() { PREPARE_RESET_FLAG++; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/ICacheManager.java ================================================ /* * 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; /** * Created by ch on 15-11-15. */ public interface ICacheManager { void addDanmaku(BaseDanmaku danmaku); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/IDanmakuIterator.java ================================================ package master.flame.danmaku.danmaku.model; public interface IDanmakuIterator { public BaseDanmaku next(); public boolean hasNext(); public void reset(); public void remove(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/IDanmakus.java ================================================ /* * 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 java.util.Collection; import java.util.Comparator; import master.flame.danmaku.danmaku.util.DanmakuUtils; public interface IDanmakus { abstract class Consumer { public static final int ACTION_CONTINUE = 0; public static final int ACTION_BREAK = 1; public static final int ACTION_REMOVE = 2; public static final int ACTION_REMOVE_AND_BREAK = 3; /** * Performs this operation on the given argument. * * @param t the input argument * @return next action of the loop * * @see #ACTION_CONTINUE * @see #ACTION_BREAK * @see #ACTION_REMOVE */ public abstract int accept(Progress t); public void before() { } public void after() { } public Result result() { return null; } } abstract class DefaultConsumer extends Consumer { } int ST_BY_TIME = 0; int ST_BY_YPOS = 1; int ST_BY_YPOS_DESC = 2; /** * this type is used to iterate/remove/insert elements, not support sub/subnew */ int ST_BY_LIST = 4; boolean addItem(BaseDanmaku item); boolean removeItem(BaseDanmaku item); IDanmakus subnew(long startTime, long endTime); IDanmakus sub(long startTime, long endTime); int size(); void clear(); BaseDanmaku first(); BaseDanmaku last(); boolean contains(BaseDanmaku item); boolean isEmpty(); void setSubItemsDuplicateMergingEnabled(boolean enable); Collection getCollection(); void forEachSync(Consumer consumer); void forEach(Consumer consumer); Object obtainSynchronizer(); class BaseComparator implements Comparator { protected boolean mDuplicateMergingEnable; public BaseComparator(boolean duplicateMergingEnabled) { setDuplicateMergingEnabled(duplicateMergingEnabled); } public void setDuplicateMergingEnabled(boolean enable) { mDuplicateMergingEnable = enable; } @Override public int compare(BaseDanmaku obj1, BaseDanmaku obj2) { if (mDuplicateMergingEnable && DanmakuUtils.isDuplicate(obj1, obj2)) { return 0; } return DanmakuUtils.compare(obj1, obj2); } } class TimeComparator extends BaseComparator { public TimeComparator(boolean duplicateMergingEnabled) { super(duplicateMergingEnabled); } @Override public int compare(BaseDanmaku obj1, BaseDanmaku obj2) { return super.compare(obj1, obj2); } } class YPosComparator extends BaseComparator { public YPosComparator(boolean duplicateMergingEnabled) { super(duplicateMergingEnabled); } @Override public int compare(BaseDanmaku obj1, BaseDanmaku obj2) { if (mDuplicateMergingEnable && DanmakuUtils.isDuplicate(obj1, obj2)) { return 0; } return Float.compare(obj1.getTop(), obj2.getTop()); } } class YPosDescComparator extends BaseComparator { public YPosDescComparator(boolean duplicateMergingEnabled) { super(duplicateMergingEnabled); } @Override public int compare(BaseDanmaku obj1, BaseDanmaku obj2) { if (mDuplicateMergingEnable && DanmakuUtils.isDuplicate(obj1, obj2)) { return 0; } return Float.compare(obj2.getTop(), obj1.getTop()); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/IDisplayer.java ================================================ /* * 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; public interface IDisplayer { int DANMAKU_STYLE_DEFAULT = -1; // 自动 int DANMAKU_STYLE_NONE = 0; // 无 int DANMAKU_STYLE_SHADOW = 1; // 阴影 int DANMAKU_STYLE_STROKEN = 2; // 描边 int DANMAKU_STYLE_PROJECTION = 3; // 投影 int getWidth(); int getHeight(); float getDensity(); int getDensityDpi(); int draw(BaseDanmaku danmaku); void recycle(BaseDanmaku danmaku); float getScaledDensity(); int getSlopPixel(); void prepare(BaseDanmaku danmaku, boolean fromWorkerThread); void measure(BaseDanmaku danmaku, boolean fromWorkerThread); float getStrokeWidth(); void setHardwareAccelerated(boolean enable); boolean isHardwareAccelerated(); int getMaximumCacheWidth(); int getMaximumCacheHeight(); ////////////////// setter /////////////////////////// void resetSlopPixel(float factor); void setDensities(float density, int densityDpi, float scaledDensity); void setSize(int width, int height); void setDanmakuStyle(int style, float[] data); void setMargin(int m); int getMargin(); void setAllMarginTop(int m); int getAllMarginTop(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/IDrawingCache.java ================================================ package master.flame.danmaku.danmaku.model; public interface IDrawingCache { public void build(int w, int h, int density, boolean checkSizeEquals, int bitsPerPixel); public void erase(); public T get(); public void destroy(); public int size(); public int width(); public int height(); public boolean hasReferences(); public void increaseReference(); public void decreaseReference(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/L2RDanmaku.java ================================================ /* * 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; public class L2RDanmaku extends R2LDanmaku { public L2RDanmaku(Duration duration) { super(duration); } @Override public void layout(IDisplayer displayer, float x, float y) { if (mTimer != null) { long currMS = mTimer.currMillisecond; long deltaDuration = currMS - getActualTime(); if (deltaDuration > 0 && deltaDuration < duration.value) { this.x = getAccurateLeft(displayer, currMS); if (!this.isShown()) { this.y = y; this.setVisibility(true); } mLastTime = currMS; return; } mLastTime = currMS; } this.setVisibility(false); } @Override public float[] getRectAtTime(IDisplayer displayer, long time) { if (!isMeasured()) return null; float left = getAccurateLeft(displayer, time); if (RECT == null) { RECT = new float[4]; } RECT[0] = left; RECT[1] = y; RECT[2] = left + paintWidth; RECT[3] = y + paintHeight; return RECT; } protected float getAccurateLeft(IDisplayer displayer, long currTime) { long elapsedTime = currTime - getActualTime(); if (elapsedTime >= duration.value) { return displayer.getWidth(); } return mStepX * elapsedTime - paintWidth; } @Override public float getLeft() { return x; } @Override public float getTop() { return y; } @Override public float getRight() { return x + paintWidth; } @Override public float getBottom() { return y + paintHeight; } @Override public int getType() { return TYPE_SCROLL_LR; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/R2LDanmaku.java ================================================ /* * 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; public class R2LDanmaku extends BaseDanmaku { protected static final long MAX_RENDERING_TIME = 100; protected static final long CORDON_RENDERING_TIME = 40; protected float x = 0; protected float y = -1; protected int mDistance; protected float[] RECT = null; protected float mStepX; protected long mLastTime; public R2LDanmaku(Duration duration) { this.duration = duration; } @Override public void layout(IDisplayer displayer, float x, float y) { if (mTimer != null) { long currMS = mTimer.currMillisecond; long deltaDuration = currMS - getActualTime(); if (deltaDuration > 0 && deltaDuration < duration.value) { this.x = getAccurateLeft(displayer, currMS); if (!this.isShown()) { this.y = y; this.setVisibility(true); } mLastTime = currMS; return; } mLastTime = currMS; } this.setVisibility(false); } protected float getAccurateLeft(IDisplayer displayer, long currTime) { long elapsedTime = currTime - getActualTime(); if (elapsedTime >= duration.value) { return -paintWidth; } return displayer.getWidth() - elapsedTime * mStepX; } @Override public float[] getRectAtTime(IDisplayer displayer, long time) { if (!isMeasured()) return null; float left = getAccurateLeft(displayer, time); if (RECT == null) { RECT = new float[4]; } RECT[0] = left; RECT[1] = y; RECT[2] = left + paintWidth; RECT[3] = y + paintHeight; return RECT; } @Override public float getLeft() { return x; } @Override public float getTop() { return y; } @Override public float getRight() { return x + paintWidth; } @Override public float getBottom() { return y + paintHeight; } @Override public int getType() { return TYPE_SCROLL_RL; } @Override public void measure(IDisplayer displayer, boolean fromWorkerThread) { super.measure(displayer, fromWorkerThread); mDistance = (int) (displayer.getWidth() + paintWidth); mStepX = mDistance / (float) duration.value; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/SpecialDanmaku.java ================================================ /* * 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; public class SpecialDanmaku extends BaseDanmaku { public static class ScaleFactor { int flag = 0; float scaleX; float scaleY; int width; int height; public ScaleFactor(int width, int height, float scaleX, float scaleY) { update(width, height, scaleX, scaleY); } public void update(int width, int height, float scaleX, float scaleY) { if (Float.compare(this.scaleX, scaleX) != 0 || Float.compare(this.scaleY, scaleY) != 0) { flag++; } this.width = width; this.height = height; this.scaleX = scaleX; this.scaleY = scaleY; } public boolean isUpdated(int flag, int width, int height) { return this.flag != flag && (this.width != width || this.height != height); } } private class Point { float x, y; public Point(float x, float y) { this.x = x; this.y = y; } public float getDistance(Point p) { float _x = Math.abs(this.x - p.x); float _y = Math.abs(this.y - p.y); return (float) Math.sqrt(_x * _x + _y * _y); } } public class LinePath { Point pBegin, pEnd; public long duration, beginTime, endTime; float delatX, deltaY; public void setPoints(Point pBegin, Point pEnd) { this.pBegin = pBegin; this.pEnd = pEnd; this.delatX = pEnd.x - pBegin.x; this.deltaY = pEnd.y - pBegin.y; } public float getDistance() { return pEnd.getDistance(pBegin); } public float[] getBeginPoint() { return new float[]{ pBegin.x, pBegin.y }; } public float[] getEndPoint() { return new float[]{ pEnd.x, pEnd.y }; } } public float beginX, beginY; public float endX, endY; public float deltaX, deltaY; public long translationDuration; public long translationStartDelay; private ScaleFactor mScaleFactor; private int mScaleFactorChangedFlag; private int mCurrentWidth = 0; private int mCurrentHeight = 0; /** * Linear.easeIn or Quadratic.easeOut */ public boolean isQuadraticEaseOut = false; public int beginAlpha; public int endAlpha; public int deltaAlpha; public long alphaDuration; public float rotateX, rotateZ; public float pivotX, pivotY; private float[] currStateValues = new float[4]; public LinePath[] linePaths; @Override public void measure(IDisplayer displayer, boolean fromWorkerThread) { super.measure(displayer, fromWorkerThread); if (mCurrentWidth == 0 || mCurrentHeight == 0) { mCurrentWidth = displayer.getWidth(); mCurrentHeight = displayer.getHeight(); } } @Override public void layout(IDisplayer displayer, float x, float y) { getRectAtTime(displayer, mTimer.currMillisecond); } @Override public float[] getRectAtTime(IDisplayer displayer, long currTime) { if (!isMeasured()) return null; if (mScaleFactor.isUpdated(this.mScaleFactorChangedFlag, mCurrentWidth, mCurrentHeight)) { float scaleX = mScaleFactor.scaleX; float scaleY = mScaleFactor.scaleY; setTranslationData(beginX * scaleX, beginY * scaleY, endX * scaleX, endY * scaleY, translationDuration, translationStartDelay); if (linePaths != null && linePaths.length > 0) { int length = linePaths.length; float[][] points = new float[length + 1][2]; for (int j = 0; j < length; j++) { points[j] = linePaths[j].getBeginPoint(); points[j + 1] = linePaths[j].getEndPoint(); } for (int i = 0; i < points.length; i++) { points[i][0] *= scaleX; points[i][1] *= scaleY; } setLinePathData(points); } this.mScaleFactorChangedFlag = mScaleFactor.flag; this.mCurrentWidth = mScaleFactor.width; this.mCurrentHeight = mScaleFactor.height; } long deltaTime = currTime - getActualTime(); // caculate alpha if (alphaDuration > 0 && deltaAlpha != 0) { if (deltaTime >= alphaDuration) { alpha = endAlpha; } else { float alphaProgress = deltaTime / (float) alphaDuration; int vectorAlpha = (int) (deltaAlpha * alphaProgress); alpha = beginAlpha + vectorAlpha; } } // caculate x y float currX = beginX; float currY = beginY; long dtime = deltaTime - translationStartDelay; if (translationDuration > 0 && dtime >= 0 && dtime <= translationDuration) { float tranalationProgress = 0f; if (linePaths != null) { LinePath currentLinePath = null; for (LinePath line : linePaths) { if (dtime >= line.beginTime && dtime < line.endTime) { currentLinePath = line; break; } else { currX = line.pEnd.x; currY = line.pEnd.y; } } if (currentLinePath != null) { float deltaX = currentLinePath.delatX; float deltaY = currentLinePath.deltaY; tranalationProgress = (deltaTime - currentLinePath.beginTime) / (float) currentLinePath.duration; float beginX = currentLinePath.pBegin.x; float beginY = currentLinePath.pBegin.y; if (deltaX != 0) { float vectorX = deltaX * tranalationProgress; currX = beginX + vectorX; } if (deltaY != 0) { float vectorY = deltaY * tranalationProgress; currY = beginY + vectorY; } } } else { tranalationProgress = isQuadraticEaseOut ? getQuadEaseOutProgress(dtime, translationDuration) : dtime / (float) translationDuration; if (deltaX != 0) { float vectorX = deltaX * tranalationProgress; currX = beginX + vectorX; } if (deltaY != 0) { float vectorY = deltaY * tranalationProgress; currY = beginY + vectorY; } } } else if (dtime > translationDuration) { currX = endX; currY = endY; } currStateValues[0] = currX; currStateValues[1] = currY; currStateValues[2] = currX + paintWidth; currStateValues[3] = currY + paintHeight; this.setVisibility(!isOutside()); return currStateValues; } private final static float getQuadEaseOutProgress(long ctime, long duration) { // Math.easeOutQuad = function (t, b, c, d) { // t /= d; // return -c * t*(t-2) + b; // }; float t = ctime; // float b = 0f; float c = 1.0f; float d = duration; return -c * (t /= d) * (t - 2); // + b; } @Override public float getLeft() { return currStateValues[0]; } @Override public float getTop() { return currStateValues[1]; } @Override public float getRight() { return currStateValues[2]; } @Override public float getBottom() { return currStateValues[3]; } @Override public int getType() { return TYPE_SPECIAL; } public void setTranslationData(float beginX, float beginY, float endX, float endY, long translationDuration, long translationStartDelay) { this.beginX = beginX; this.beginY = beginY; this.endX = endX; this.endY = endY; this.deltaX = endX - beginX; this.deltaY = endY - beginY; this.translationDuration = translationDuration; this.translationStartDelay = translationStartDelay; } public void setAlphaData(int beginAlpha, int endAlpha, long alphaDuration) { this.beginAlpha = beginAlpha; this.endAlpha = endAlpha; this.deltaAlpha = endAlpha - beginAlpha; this.alphaDuration = alphaDuration; if (beginAlpha != AlphaValue.MAX) { alpha = beginAlpha; } } public void setLinePathData(float[][] points) { if (points != null) { int length = points.length; this.beginX = points[0][0]; this.beginY = points[0][1]; this.endX = points[length - 1][0]; this.endY = points[length - 1][1]; if (points.length > 1) { linePaths = new LinePath[points.length - 1]; for (int i = 0; i < linePaths.length; i++) { linePaths[i] = new LinePath(); linePaths[i].setPoints(new Point(points[i][0], points[i][1]), new Point( points[i + 1][0], points[i + 1][1])); } float totalDistance = 0; for (LinePath line : linePaths) { totalDistance += line.getDistance(); } LinePath lastLine = null; for (LinePath line : linePaths) { line.duration = (long) ((line.getDistance() / totalDistance) * translationDuration); line.beginTime = (lastLine == null ? 0 : lastLine.endTime); line.endTime = line.beginTime + line.duration; lastLine = line; } } } } public void setScaleFactor(ScaleFactor scaleFactor) { this.mScaleFactor = scaleFactor; this.mScaleFactorChangedFlag = scaleFactor.flag; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/AndroidDisplayer.java ================================================ /* * 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; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/BaseCacheStuffer.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.graphics.Canvas; import android.graphics.Paint; import android.text.TextPaint; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.IDrawingCache; /** * Created by ch on 15-7-16. */ public abstract class BaseCacheStuffer { public static abstract class Proxy { /** * 在弹幕显示前使用新的text,使用新的text * @param danmaku * @param fromWorkerThread 是否在工作(非UI)线程,在true的情况下可以做一些耗时操作(例如更新Span的drawblae或者其他IO操作) * @return 如果不需重置,直接返回danmaku.text */ public abstract void prepareDrawing(BaseDanmaku danmaku, boolean fromWorkerThread); public abstract void releaseResource(BaseDanmaku danmaku); } protected Proxy mProxy; public void prepare(BaseDanmaku danmaku, boolean fromWorkerThread) { if (mProxy != null) { mProxy.prepareDrawing(danmaku, fromWorkerThread); } } /** * set paintWidth, paintHeight to danmaku * @param danmaku * @param fromWorkerThread */ public abstract void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread); /** * clear caches which created by this stuffer */ public abstract void clearCaches(); public abstract void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, boolean fromWorkerThread, AndroidDisplayer.DisplayerConfig displayerConfig); public boolean drawCache(BaseDanmaku danmaku, Canvas canvas, float left, float top, Paint alphaPaint, TextPaint paint) { IDrawingCache cache = danmaku.getDrawingCache(); if (cache != null) { DrawingCacheHolder holder = (DrawingCacheHolder) cache.get(); if (holder != null) { return holder.draw(canvas, left, top, alphaPaint); } } return false; } public void clearCache(BaseDanmaku danmaku) { } public void setProxy(Proxy adapter) { mProxy = adapter; } public void releaseResource(BaseDanmaku danmaku) { if (mProxy != null) { mProxy.releaseResource(danmaku); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/CachingPolicy.java ================================================ package master.flame.danmaku.danmaku.model.android; /** * Created by ch on 17/4/28.
* The cacing policy apply to {@link master.flame.danmaku.controller.CacheManagingDrawTask} * 提供缓存相关的策略设置:
* 1.缓存格式 ARGB_4444 ARGB_8888
* 2.缓存池总容量大小百分比系数(0.0~1.0)
* 3.过期缓存回收频率
* 4.缓存回收条件内存占比阈值
* 5.可复用缓存尺寸调节 */ public class CachingPolicy { public final static int BMP_BPP_ARGB_4444 = 16; public final static int BMP_BPP_ARGB_8888 = 32; public final static int CACHE_PERIOD_AUTO = 0; public final static int CACHE_PERIOD_NOT_RECYCLE = -1; public final static CachingPolicy POLICY_LAZY = new CachingPolicy(BMP_BPP_ARGB_4444, 0.3f, CACHE_PERIOD_AUTO, 50, 0.01f); public final static CachingPolicy POLICY_GREEDY = new CachingPolicy(BMP_BPP_ARGB_4444, 0.5f, CACHE_PERIOD_NOT_RECYCLE, 50, 0.005f); public final static CachingPolicy POLICY_DEFAULT = POLICY_LAZY; public CachingPolicy(int bitsPerPixelOfCache, float maxCachePoolSizeFactorPercentage, long periodOfRecycle, int reusableOffsetPixel, float forceRecyleThreshold) { this.bitsPerPixelOfCache = bitsPerPixelOfCache; /* Note: as of {@link android.os.Build.VERSION_CODES#KITKAT}, * any bitmap created with this configuration will be created * using {@link #ARGB_8888} instead.*/ if (android.os.Build.VERSION.SDK_INT >= 19) { this.bitsPerPixelOfCache = BMP_BPP_ARGB_8888; } this.maxCachePoolSizeFactorPercentage = maxCachePoolSizeFactorPercentage; this.periodOfRecycle = periodOfRecycle; this.reusableOffsetPixel = reusableOffsetPixel; this.forceRecyleThreshold = forceRecyleThreshold; } /** * 缓存bitmap的格式, ARGB_4444 = 16 ARGB_8888 = 32 * use BMP_BPP_ARGB_4444 or BMP_BPP_ARGB_8888 * * Note: as of {@link android.os.Build.VERSION_CODES#KITKAT}, * any bitmap created with this configuration will be created * using {@link #ARGB_8888} instead. */ public int bitsPerPixelOfCache = BMP_BPP_ARGB_4444; /** * 0.0 ~ 1.0, 超过0.5的话有OOM风险 */ public float maxCachePoolSizeFactorPercentage = 0.3f; /** * 回收周期 * * @see CACHE_PERIOD_AUTO 0: 默认 * @see CACHE_PERIOD_NOT_RECYCLE -1: 不回收 */ public long periodOfRecycle = CACHE_PERIOD_AUTO; // public DanmakuTimer recyleTimer = new DanmakuTimer(SystemClock.uptimeMillis()); /** * 内存占用大小超过总容量一定比例值(forceRecyleThreshold值)的缓存,在回收时进行主动回收,忽略CACHE_PERIOD_NOT_RECYCLE */ public float forceRecyleThreshold = 0.01f; /** * @see master.flame.danmaku.controller.CacheManagingDrawTask.CacheManager#findReusableCache */ public int reusableOffsetPixel = 0; public int maxTimesOfStrictReusableFinds = 20; public int maxTimesOfReusableFinds = 150; } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuContext.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.graphics.Typeface; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import master.flame.danmaku.controller.DanmakuFilters; import master.flame.danmaku.controller.DanmakuFilters.IDanmakuFilter; import master.flame.danmaku.danmaku.model.AbsDanmakuSync; 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.model.GlobalFlagValues; import master.flame.danmaku.danmaku.model.IDanmakus; public class DanmakuContext implements Cloneable { public static DanmakuContext create() { return new DanmakuContext(); } public enum DanmakuConfigTag { FT_DANMAKU_VISIBILITY, FB_DANMAKU_VISIBILITY, L2R_DANMAKU_VISIBILITY, R2L_DANMAKU_VISIBILIY, SPECIAL_DANMAKU_VISIBILITY, TYPEFACE, TRANSPARENCY, SCALE_TEXTSIZE, MAXIMUM_NUMS_IN_SCREEN, DANMAKU_STYLE, DANMAKU_BOLD, COLOR_VALUE_WHITE_LIST, USER_ID_BLACK_LIST, USER_HASH_BLACK_LIST, SCROLL_SPEED_FACTOR, BLOCK_GUEST_DANMAKU, DUPLICATE_MERGING_ENABLED, MAXIMUN_LINES, OVERLAPPING_ENABLE, ALIGN_BOTTOM, DANMAKU_MARGIN, DANMAKU_SYNC; public boolean isVisibilityRelatedTag() { return this.equals(FT_DANMAKU_VISIBILITY) || this.equals(FB_DANMAKU_VISIBILITY) || this.equals(L2R_DANMAKU_VISIBILITY) || this.equals(R2L_DANMAKU_VISIBILIY) || this.equals(SPECIAL_DANMAKU_VISIBILITY) || this.equals(COLOR_VALUE_WHITE_LIST) || this.equals(USER_ID_BLACK_LIST); } } /** * 默认字体 */ public Typeface mFont = null; /** * paint alpha:0-255 */ public int transparency = AlphaValue.MAX; public float scaleTextSize = 1.0f; public int margin = 0; /** * 弹幕显示隐藏设置 */ public boolean FTDanmakuVisibility = true; public boolean FBDanmakuVisibility = true; public boolean L2RDanmakuVisibility = true; public boolean R2LDanmakuVisibility = true; public boolean SpecialDanmakuVisibility = true; List mFilterTypes = new ArrayList(); /** * 同屏弹幕数量 -1 按绘制效率自动调整 0 无限制 n 同屏最大显示n个弹幕 */ public int maximumNumsInScreen = -1; /** * 默认滚动速度系数 */ public float scrollSpeedFactor = 1.0f; public AbsDanmakuSync danmakuSync; List mColorValueWhiteList = new ArrayList(); List mUserIdBlackList = new ArrayList(); List mUserHashBlackList = new ArrayList(); private List> mCallbackList; private boolean mBlockGuestDanmaku = false; private boolean mDuplicateMergingEnable = false; private boolean mIsAlignBottom = false; private BaseCacheStuffer mCacheStuffer; private boolean mIsMaxLinesLimited; private boolean mIsPreventOverlappingEnabled; public AbsDisplayer mDisplayer = new AndroidDisplayer(); public GlobalFlagValues mGlobalFlagValues = new GlobalFlagValues(); public DanmakuFilters mDanmakuFilters = new DanmakuFilters(); public DanmakuFactory mDanmakuFactory = DanmakuFactory.create(); public CachingPolicy cachingPolicy = CachingPolicy.POLICY_DEFAULT; private IDanmakus.BaseComparator mBaseComparator; public IDanmakus.BaseComparator getBaseComparator() { return mBaseComparator; } public void setBaseComparator(IDanmakus.BaseComparator baseComparator) { this.mBaseComparator = baseComparator; } public AbsDisplayer getDisplayer() { return mDisplayer; } /** * 0 默认 Choreographer驱动DrawHandler线程刷新
* 1 "DFM Update"单独线程刷新
* 2 DrawHandler线程自驱动刷新 * * Note: 在系统{@link android.os.Build.VERSION_CODES#JELLY_BEAN}以下, 0方式会被2方式代替 */ public byte updateMethod = 0; /** * set typeface * * @param font */ public DanmakuContext setTypeface(Typeface font) { if (mFont != font) { mFont = font; mDisplayer.clearTextHeightCache(); mDisplayer.setTypeFace(font); notifyConfigureChanged(DanmakuConfigTag.TYPEFACE); } return this; } public DanmakuContext setDanmakuTransparency(float p) { int newTransparency = (int) (p * AlphaValue.MAX); if (newTransparency != transparency) { transparency = newTransparency; mDisplayer.setTransparency(newTransparency); notifyConfigureChanged(DanmakuConfigTag.TRANSPARENCY, p); } return this; } public DanmakuContext setScaleTextSize(float p) { if (scaleTextSize != p) { scaleTextSize = p; mDisplayer.clearTextHeightCache(); mDisplayer.setScaleTextSizeFactor(p); mGlobalFlagValues.updateMeasureFlag(); mGlobalFlagValues.updateVisibleFlag(); notifyConfigureChanged(DanmakuConfigTag.SCALE_TEXTSIZE, p); } return this; } public DanmakuContext setDanmakuMargin(int m) { if (margin != m) { margin = m; mDisplayer.setMargin(m); mGlobalFlagValues.updateFilterFlag(); mGlobalFlagValues.updateVisibleFlag(); notifyConfigureChanged(DanmakuConfigTag.DANMAKU_MARGIN, m); } return this; } public DanmakuContext setMarginTop(int m) { mDisplayer.setAllMarginTop(m); return this; } /** * @return 是否显示顶部弹幕 */ public boolean getFTDanmakuVisibility() { return FTDanmakuVisibility; } /** * 设置是否显示顶部弹幕 * * @param visible */ public DanmakuContext setFTDanmakuVisibility(boolean visible) { setDanmakuVisible(visible, BaseDanmaku.TYPE_FIX_TOP); setFilterData(DanmakuFilters.TAG_TYPE_DANMAKU_FILTER, mFilterTypes); mGlobalFlagValues.updateFilterFlag(); if (FTDanmakuVisibility != visible) { FTDanmakuVisibility = visible; notifyConfigureChanged(DanmakuConfigTag.FT_DANMAKU_VISIBILITY, visible); } return this; } private void setFilterData(String tag, T data) { setFilterData(tag, data, true); } private void setFilterData(String tag, T data, boolean primary) { @SuppressWarnings("unchecked") IDanmakuFilter filter = (IDanmakuFilter) mDanmakuFilters.get(tag, primary); filter.setData(data); } private void setDanmakuVisible(boolean visible, int type) { if (visible) { mFilterTypes.remove(Integer.valueOf(type)); } else if (!mFilterTypes.contains(Integer.valueOf(type))) { mFilterTypes.add(type); } } /** * @return 是否显示底部弹幕 */ public boolean getFBDanmakuVisibility() { return FBDanmakuVisibility; } /** * 设置是否显示底部弹幕 * * @param visible */ public DanmakuContext setFBDanmakuVisibility(boolean visible) { setDanmakuVisible(visible, BaseDanmaku.TYPE_FIX_BOTTOM); setFilterData(DanmakuFilters.TAG_TYPE_DANMAKU_FILTER, mFilterTypes); mGlobalFlagValues.updateFilterFlag(); if (FBDanmakuVisibility != visible) { FBDanmakuVisibility = visible; notifyConfigureChanged(DanmakuConfigTag.FB_DANMAKU_VISIBILITY, visible); } return this; } /** * @return 是否显示左右滚动弹幕 */ public boolean getL2RDanmakuVisibility() { return L2RDanmakuVisibility; } /** * 设置是否显示左右滚动弹幕 * * @param visible */ public DanmakuContext setL2RDanmakuVisibility(boolean visible) { setDanmakuVisible(visible, BaseDanmaku.TYPE_SCROLL_LR); setFilterData(DanmakuFilters.TAG_TYPE_DANMAKU_FILTER, mFilterTypes); mGlobalFlagValues.updateFilterFlag(); if(L2RDanmakuVisibility != visible){ L2RDanmakuVisibility = visible; notifyConfigureChanged(DanmakuConfigTag.L2R_DANMAKU_VISIBILITY, visible); } return this; } /** * @return 是否显示右左滚动弹幕 */ public boolean getR2LDanmakuVisibility() { return R2LDanmakuVisibility; } /** * 设置是否显示右左滚动弹幕 * * @param visible */ public DanmakuContext setR2LDanmakuVisibility(boolean visible) { setDanmakuVisible(visible, BaseDanmaku.TYPE_SCROLL_RL); setFilterData(DanmakuFilters.TAG_TYPE_DANMAKU_FILTER, mFilterTypes); mGlobalFlagValues.updateFilterFlag(); if (R2LDanmakuVisibility != visible) { R2LDanmakuVisibility = visible; notifyConfigureChanged(DanmakuConfigTag.R2L_DANMAKU_VISIBILIY, visible); } return this; } /** * @return 是否显示特殊弹幕 */ public boolean getSpecialDanmakuVisibility() { return SpecialDanmakuVisibility; } /** * 设置是否显示特殊弹幕 * * @param visible */ public DanmakuContext setSpecialDanmakuVisibility(boolean visible) { setDanmakuVisible(visible, BaseDanmaku.TYPE_SPECIAL); setFilterData(DanmakuFilters.TAG_TYPE_DANMAKU_FILTER, mFilterTypes); mGlobalFlagValues.updateFilterFlag(); if (SpecialDanmakuVisibility != visible) { SpecialDanmakuVisibility = visible; notifyConfigureChanged(DanmakuConfigTag.SPECIAL_DANMAKU_VISIBILITY, visible); } return this; } /** * 设置同屏弹幕密度 -1自动 0无限制 * * @param maxSize * @return */ public DanmakuContext setMaximumVisibleSizeInScreen(int maxSize) { maximumNumsInScreen = maxSize; // 无限制 if (maxSize == 0) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_QUANTITY_DANMAKU_FILTER); mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_ELAPSED_TIME_FILTER); notifyConfigureChanged(DanmakuConfigTag.MAXIMUM_NUMS_IN_SCREEN, maxSize); return this; } // 自动调整 if (maxSize == -1) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_QUANTITY_DANMAKU_FILTER); mDanmakuFilters.registerFilter(DanmakuFilters.TAG_ELAPSED_TIME_FILTER); notifyConfigureChanged(DanmakuConfigTag.MAXIMUM_NUMS_IN_SCREEN, maxSize); return this; } setFilterData(DanmakuFilters.TAG_QUANTITY_DANMAKU_FILTER, maxSize); mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.MAXIMUM_NUMS_IN_SCREEN, maxSize); return this; } /** * 设置描边样式 * * @param style DANMAKU_STYLE_NONE DANMAKU_STYLE_SHADOW or * DANMAKU_STYLE_STROKEN or DANMAKU_STYLE_PROJECTION * @param values * DANMAKU_STYLE_SHADOW 阴影模式下,values传入阴影半径 * DANMAKU_STYLE_STROKEN 描边模式下,values传入描边宽度 * DANMAKU_STYLE_PROJECTION * 投影模式下,values传入offsetX, offsetY, alpha * offsetX/offsetY: x/y 方向上的偏移量 * alpha: 投影透明度 [0...255] * @return */ public DanmakuContext setDanmakuStyle(int style, float... values) { mDisplayer.setDanmakuStyle(style, values); notifyConfigureChanged(DanmakuConfigTag.DANMAKU_STYLE, style, values); return this; } /** * 设置是否粗体显示,对某些字体无效 * * @param bold * @return */ public DanmakuContext setDanmakuBold(boolean bold) { mDisplayer.setFakeBoldText(bold); notifyConfigureChanged(DanmakuConfigTag.DANMAKU_BOLD, bold); return this; } /** * 设置色彩过滤弹幕白名单 * @param colors * @return */ public DanmakuContext setColorValueWhiteList(Integer... colors) { mColorValueWhiteList.clear(); if (colors == null || colors.length == 0) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_TEXT_COLOR_DANMAKU_FILTER); } else { Collections.addAll(mColorValueWhiteList, colors); setFilterData(DanmakuFilters.TAG_TEXT_COLOR_DANMAKU_FILTER, mColorValueWhiteList); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.COLOR_VALUE_WHITE_LIST, mColorValueWhiteList); return this; } public List getColorValueWhiteList(){ return mColorValueWhiteList; } /** * 设置屏蔽弹幕用户hash * @param hashes * @return */ public DanmakuContext setUserHashBlackList(String... hashes) { mUserHashBlackList.clear(); if (hashes == null || hashes.length == 0) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_USER_HASH_FILTER); } else { Collections.addAll(mUserHashBlackList, hashes); setFilterData(DanmakuFilters.TAG_USER_HASH_FILTER, mUserHashBlackList); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_HASH_BLACK_LIST, mUserHashBlackList); return this; } public DanmakuContext removeUserHashBlackList(String... hashes){ if(hashes == null || hashes.length == 0) { return this; } for (String hash : hashes) { mUserHashBlackList.remove(hash); } setFilterData(DanmakuFilters.TAG_USER_HASH_FILTER, mUserHashBlackList); mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_HASH_BLACK_LIST, mUserHashBlackList); return this; } /** * 添加屏蔽用户 * @param hashes * @return */ public DanmakuContext addUserHashBlackList(String... hashes){ if(hashes == null || hashes.length == 0) { return this; } Collections.addAll(mUserHashBlackList, hashes); setFilterData(DanmakuFilters.TAG_USER_HASH_FILTER, mUserHashBlackList); mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_HASH_BLACK_LIST, mUserHashBlackList); return this; } public List getUserHashBlackList(){ return mUserHashBlackList; } /** * 设置屏蔽弹幕用户id , 0 表示游客弹幕 * @param ids * @return */ public DanmakuContext setUserIdBlackList(Integer... ids) { mUserIdBlackList.clear(); if (ids == null || ids.length == 0) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_USER_ID_FILTER); } else { Collections.addAll(mUserIdBlackList, ids); setFilterData(DanmakuFilters.TAG_USER_ID_FILTER, mUserIdBlackList); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_ID_BLACK_LIST, mUserIdBlackList); return this; } public DanmakuContext removeUserIdBlackList(Integer... ids){ if(ids == null || ids.length == 0) { return this; } for (Integer id : ids) { mUserIdBlackList.remove(id); } setFilterData(DanmakuFilters.TAG_USER_ID_FILTER, mUserIdBlackList); mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_ID_BLACK_LIST, mUserIdBlackList); return this; } /** * 添加屏蔽用户 * @param ids * @return */ public DanmakuContext addUserIdBlackList(Integer... ids){ if(ids == null || ids.length == 0) { return this; } Collections.addAll(mUserIdBlackList, ids); setFilterData(DanmakuFilters.TAG_USER_ID_FILTER, mUserIdBlackList); mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.USER_ID_BLACK_LIST, mUserIdBlackList); return this; } public List getUserIdBlackList(){ return mUserIdBlackList; } /** * 设置是否屏蔽游客弹幕 * @param block true屏蔽,false不屏蔽 * @return */ public DanmakuContext blockGuestDanmaku(boolean block) { if (mBlockGuestDanmaku != block) { mBlockGuestDanmaku = block; if (block) { setFilterData(DanmakuFilters.TAG_GUEST_FILTER, block); } else { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_GUEST_FILTER); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.BLOCK_GUEST_DANMAKU, block); } return this; } /** * 设置弹幕滚动速度系数,只对滚动弹幕有效 * @param p * @return */ public DanmakuContext setScrollSpeedFactor(float p){ if (scrollSpeedFactor != p) { scrollSpeedFactor = p; mDanmakuFactory.updateDurationFactor(p); mGlobalFlagValues.updateMeasureFlag(); mGlobalFlagValues.updateVisibleFlag(); notifyConfigureChanged(DanmakuConfigTag.SCROLL_SPEED_FACTOR, p); } return this; } /** * 设置是否启用合并重复弹幕 * @param enable * @return */ public DanmakuContext setDuplicateMergingEnabled(boolean enable) { if (mDuplicateMergingEnable != enable) { mDuplicateMergingEnable = enable; mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.DUPLICATE_MERGING_ENABLED, enable); } return this; } public boolean isDuplicateMergingEnabled() { return mDuplicateMergingEnable; } public DanmakuContext alignBottom(boolean enable) { if (mIsAlignBottom != enable) { mIsAlignBottom = enable; notifyConfigureChanged(DanmakuConfigTag.ALIGN_BOTTOM, enable); mGlobalFlagValues.updateVisibleFlag(); } return this; } public boolean isAlignBottom() { return mIsAlignBottom; } /** * 设置最大显示行数 * @param pairs map 设置null取消行数限制 * K = (BaseDanmaku.TYPE_SCROLL_RL|BaseDanmaku.TYPE_SCROLL_LR|BaseDanmaku.TYPE_FIX_TOP|BaseDanmaku.TYPE_FIX_BOTTOM) * V = 最大行数 * @return */ public DanmakuContext setMaximumLines(Map pairs) { mIsMaxLinesLimited = (pairs != null); if (pairs == null) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_MAXIMUN_LINES_FILTER, false); } else { setFilterData(DanmakuFilters.TAG_MAXIMUN_LINES_FILTER, pairs, false); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.MAXIMUN_LINES, pairs); return this; } @Deprecated public DanmakuContext setOverlapping(Map pairs) { return preventOverlapping(pairs); } /** * 设置防弹幕重叠 * @param pairs map 设置null恢复默认设置,默认为允许重叠 * K = (BaseDanmaku.TYPE_SCROLL_RL|BaseDanmaku.TYPE_SCROLL_LR|BaseDanmaku.TYPE_FIX_TOP|BaseDanmaku.TYPE_FIX_BOTTOM) * V = true|false 是否重叠 * @return */ public DanmakuContext preventOverlapping(Map pairs) { mIsPreventOverlappingEnabled = (pairs != null); if (pairs == null) { mDanmakuFilters.unregisterFilter(DanmakuFilters.TAG_OVERLAPPING_FILTER, false); } else { setFilterData(DanmakuFilters.TAG_OVERLAPPING_FILTER, pairs, false); } mGlobalFlagValues.updateFilterFlag(); notifyConfigureChanged(DanmakuConfigTag.OVERLAPPING_ENABLE, pairs); return this; } public boolean isMaxLinesLimited() { return mIsMaxLinesLimited; } public boolean isPreventOverlappingEnabled() { return mIsPreventOverlappingEnabled; } /** * 设置缓存绘制填充器,默认使用{@link SimpleTextCacheStuffer}只支持纯文字显示, 如果需要图文混排请设置{@link SpannedCacheStuffer} * 如果需要定制其他样式请扩展{@link SimpleTextCacheStuffer}|{@link SpannedCacheStuffer} * @param cacheStuffer * @param cacheStufferAdapter */ public DanmakuContext setCacheStuffer(BaseCacheStuffer cacheStuffer, BaseCacheStuffer.Proxy cacheStufferAdapter) { this.mCacheStuffer = cacheStuffer; if (this.mCacheStuffer != null) { this.mCacheStuffer.setProxy(cacheStufferAdapter); mDisplayer.setCacheStuffer(this.mCacheStuffer); } return this; } public DanmakuContext setDanmakuSync(AbsDanmakuSync danmakuSync) { this.danmakuSync = danmakuSync; return this; } public DanmakuContext setCachingPolicy(CachingPolicy cachingPolicy) { this.cachingPolicy = cachingPolicy; return this; } public interface ConfigChangedCallback { public boolean onDanmakuConfigChanged(DanmakuContext config, DanmakuConfigTag tag, Object... value); } public void registerConfigChangedCallback(ConfigChangedCallback listener) { if (listener == null || mCallbackList == null) { mCallbackList = Collections.synchronizedList(new ArrayList>()); } for (WeakReference configReferer : mCallbackList) { if (listener.equals(configReferer.get())) { return; } } mCallbackList.add(new WeakReference(listener)); } public void unregisterConfigChangedCallback(ConfigChangedCallback listener) { if (listener == null || mCallbackList == null) return; for (WeakReference configReferer : mCallbackList) { if (listener.equals(configReferer.get())) { mCallbackList.remove(listener); return; } } } public void unregisterAllConfigChangedCallbacks() { if (mCallbackList != null) { mCallbackList.clear(); mCallbackList = null; } } private void notifyConfigureChanged(DanmakuConfigTag tag, Object... values) { if (mCallbackList != null) { for (WeakReference configReferer : mCallbackList) { ConfigChangedCallback cb = configReferer.get(); if (cb != null) { cb.onDanmakuConfigChanged(this, tag, values); } } } } public DanmakuContext registerFilter(DanmakuFilters.BaseDanmakuFilter filter) { mDanmakuFilters.registerFilter(filter); mGlobalFlagValues.updateFilterFlag(); return this; } public DanmakuContext unregisterFilter(DanmakuFilters.BaseDanmakuFilter filter) { mDanmakuFilters.unregisterFilter(filter); mGlobalFlagValues.updateFilterFlag(); return this; } public DanmakuContext resetContext() { mDisplayer = new AndroidDisplayer(); mGlobalFlagValues = new GlobalFlagValues(); // mDanmakuFilters = new DanmakuFilters(); mDanmakuFilters.clear(); mDanmakuFactory = DanmakuFactory.create(); return this; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DanmakuFactory.java ================================================ /* * 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.util.Log; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.Duration; import master.flame.danmaku.danmaku.model.FBDanmaku; import master.flame.danmaku.danmaku.model.FTDanmaku; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.L2RDanmaku; import master.flame.danmaku.danmaku.model.R2LDanmaku; import master.flame.danmaku.danmaku.model.SpecialDanmaku; public class DanmakuFactory { public final static float OLD_BILI_PLAYER_WIDTH = 539; public final static float BILI_PLAYER_WIDTH = 682; public final static float OLD_BILI_PLAYER_HEIGHT = 385; public final static float BILI_PLAYER_HEIGHT = 438; public final static long COMMON_DANMAKU_DURATION = 3800; // B站原始分辨率下弹幕存活时间 public final static int DANMAKU_MEDIUM_TEXTSIZE = 25; public final static long MIN_DANMAKU_DURATION = 4000; public final static long MAX_DANMAKU_DURATION_HIGH_DENSITY = 9000; public int CURRENT_DISP_WIDTH = 0, CURRENT_DISP_HEIGHT = 0; private SpecialDanmaku.ScaleFactor mScaleFactor = null; private float CURRENT_DISP_SIZE_FACTOR = 1.0f; public long REAL_DANMAKU_DURATION = COMMON_DANMAKU_DURATION; public long MAX_DANMAKU_DURATION = MIN_DANMAKU_DURATION; public Duration MAX_Duration_Scroll_Danmaku; public Duration MAX_Duration_Fix_Danmaku; public Duration MAX_Duration_Special_Danmaku; public IDisplayer sLastDisp; private DanmakuContext sLastConfig; public static DanmakuFactory create() { return new DanmakuFactory(); } protected DanmakuFactory() { } public void resetDurationsData() { sLastDisp = null; CURRENT_DISP_WIDTH = CURRENT_DISP_HEIGHT = 0; MAX_Duration_Scroll_Danmaku = null; MAX_Duration_Fix_Danmaku = null; MAX_Duration_Special_Danmaku = null; MAX_DANMAKU_DURATION = MIN_DANMAKU_DURATION; } public void notifyDispSizeChanged(DanmakuContext context) { sLastConfig = context; sLastDisp = context.getDisplayer(); createDanmaku(BaseDanmaku.TYPE_SCROLL_RL, context); } public BaseDanmaku createDanmaku(int type) { return createDanmaku(type, sLastConfig); } public BaseDanmaku createDanmaku(int type, DanmakuContext context) { if (context == null) return null; sLastConfig = context; sLastDisp = context.getDisplayer(); return createDanmaku(type, sLastDisp.getWidth(), sLastDisp.getHeight(), CURRENT_DISP_SIZE_FACTOR, context.scrollSpeedFactor); } public BaseDanmaku createDanmaku(int type, IDisplayer disp, float viewportScale, float scrollSpeedFactor) { if (disp == null) return null; sLastDisp = disp; return createDanmaku(type, disp.getWidth(), disp.getHeight(), viewportScale, scrollSpeedFactor); } /** * 创建弹幕数据请尽量使用此方法,参考BiliDanmakuParser或AcfunDanmakuParser * * @param type 弹幕类型 * @param viewportWidth danmakuview宽度,会影响滚动弹幕的存活时间(duration) * @param viewportHeight danmakuview高度 * @param viewportScale 缩放比例,会影响滚动弹幕的存活时间(duration) * @return */ public BaseDanmaku createDanmaku(int type, int viewportWidth, int viewportHeight, float viewportScale, float scrollSpeedFactor) { return createDanmaku(type, (float) viewportWidth, (float) viewportHeight, viewportScale, scrollSpeedFactor); } /** * 创建弹幕数据请尽量使用此方法,参考BiliDanmakuParser或AcfunDanmakuParser * * @param type 弹幕类型 * @param viewportWidth danmakuview宽度,会影响滚动弹幕的存活时间(duration) * @param viewportHeight danmakuview高度 * @param viewportSizeFactor 会影响滚动弹幕的速度/存活时间(duration) * @return */ public BaseDanmaku createDanmaku(int type, float viewportWidth, float viewportHeight, float viewportSizeFactor, float scrollSpeedFactor) { int oldDispWidth = CURRENT_DISP_WIDTH; int oldDispHeight = CURRENT_DISP_HEIGHT; boolean sizeChanged = updateViewportState(viewportWidth, viewportHeight, viewportSizeFactor); if (MAX_Duration_Scroll_Danmaku == null) { MAX_Duration_Scroll_Danmaku = new Duration(REAL_DANMAKU_DURATION); MAX_Duration_Scroll_Danmaku.setFactor(scrollSpeedFactor); } else if (sizeChanged) { MAX_Duration_Scroll_Danmaku.setValue(REAL_DANMAKU_DURATION); } if (MAX_Duration_Fix_Danmaku == null) { MAX_Duration_Fix_Danmaku = new Duration(COMMON_DANMAKU_DURATION); } float scaleX = 1f; float scaleY = 1f; if (sizeChanged && viewportWidth > 0) { updateMaxDanmakuDuration(); if (oldDispWidth > 0 && oldDispHeight > 0) { scaleX = viewportWidth / (float) oldDispWidth; scaleY = viewportHeight / (float) oldDispHeight; } updateScaleFactor((int) viewportWidth, (int) viewportHeight, scaleX, scaleY); if (viewportHeight > 0) { updateSpecialDanmakusDate((int) viewportWidth, (int) viewportHeight, scaleX, scaleY); } } BaseDanmaku instance = null; switch (type) { case 1: // 从右往左滚动 instance = new R2LDanmaku(MAX_Duration_Scroll_Danmaku); break; case 4: // 底端固定 instance = new FBDanmaku(MAX_Duration_Fix_Danmaku); break; case 5: // 顶端固定 instance = new FTDanmaku(MAX_Duration_Fix_Danmaku); break; case 6: // 从左往右滚动 instance = new L2RDanmaku(MAX_Duration_Scroll_Danmaku); break; case 7: // 特殊弹幕 instance = new SpecialDanmaku(); updateScaleFactor((int) viewportWidth, (int) viewportHeight, scaleX, scaleY); ((SpecialDanmaku) instance).setScaleFactor(mScaleFactor); break; } return instance; } private void updateScaleFactor(int width, int height, float scaleX, float scaleY) { if (mScaleFactor == null) { mScaleFactor = new SpecialDanmaku.ScaleFactor(width, height, scaleX, scaleY); } mScaleFactor.update(width, height, scaleX, scaleY); } public boolean updateViewportState(float viewportWidth, float viewportHeight, float viewportSizeFactor) { boolean sizeChanged = false; if (CURRENT_DISP_WIDTH != (int) viewportWidth || CURRENT_DISP_HEIGHT != (int) viewportHeight || CURRENT_DISP_SIZE_FACTOR != viewportSizeFactor) { sizeChanged = true; REAL_DANMAKU_DURATION = (long) (COMMON_DANMAKU_DURATION * (viewportSizeFactor * viewportWidth / BILI_PLAYER_WIDTH)); REAL_DANMAKU_DURATION = Math.min(MAX_DANMAKU_DURATION_HIGH_DENSITY, REAL_DANMAKU_DURATION); REAL_DANMAKU_DURATION = Math.max(MIN_DANMAKU_DURATION, REAL_DANMAKU_DURATION); CURRENT_DISP_WIDTH = (int) viewportWidth; CURRENT_DISP_HEIGHT = (int) viewportHeight; CURRENT_DISP_SIZE_FACTOR = viewportSizeFactor; } return sizeChanged; } private synchronized void updateSpecialDanmakusDate(int width, int height, float scaleX, float scaleY) { if (mScaleFactor != null) { mScaleFactor.update(width, height, scaleX, scaleY); } } public void updateMaxDanmakuDuration() { long maxScrollDuration = (MAX_Duration_Scroll_Danmaku == null ? 0 : MAX_Duration_Scroll_Danmaku.value), maxFixDuration = (MAX_Duration_Fix_Danmaku == null ? 0 : MAX_Duration_Fix_Danmaku.value), maxSpecialDuration = (MAX_Duration_Special_Danmaku == null ? 0 : MAX_Duration_Special_Danmaku.value); MAX_DANMAKU_DURATION = Math.max(maxScrollDuration, maxFixDuration); MAX_DANMAKU_DURATION = Math.max(MAX_DANMAKU_DURATION, maxSpecialDuration); MAX_DANMAKU_DURATION = Math.max(COMMON_DANMAKU_DURATION, MAX_DANMAKU_DURATION); MAX_DANMAKU_DURATION = Math.max(REAL_DANMAKU_DURATION, MAX_DANMAKU_DURATION); } public void updateDurationFactor(float f) { if (MAX_Duration_Scroll_Danmaku == null || MAX_Duration_Fix_Danmaku == null) return; MAX_Duration_Scroll_Danmaku.setFactor(f); updateMaxDanmakuDuration(); } /** * Initial translation data of the special danmaku * * @param item * @param beginX * @param beginX * @param beginY * @param endX * @param endY * @param translationDuration * @param translationStartDelay */ public void fillTranslationData(BaseDanmaku item, float beginX, float beginY, float endX, float endY, long translationDuration, long translationStartDelay, float scaleX, float scaleY) { if (item.getType() != BaseDanmaku.TYPE_SPECIAL) return; ((SpecialDanmaku) item).setTranslationData(beginX * scaleX, beginY * scaleY, endX * scaleX, endY * scaleY, translationDuration, translationStartDelay); updateSpecicalDanmakuDuration(item); } public static void fillLinePathData(BaseDanmaku item, float[][] points, float scaleX, float scaleY) { if (item.getType() != BaseDanmaku.TYPE_SPECIAL || points.length == 0 || points[0].length != 2) return; for (int i = 0; i < points.length; i++) { points[i][0] *= scaleX; points[i][1] *= scaleY; } ((SpecialDanmaku) item).setLinePathData(points); } /** * Initial alpha data of the special danmaku * * @param item * @param beginAlpha * @param endAlpha * @param alphaDuraion */ public void fillAlphaData(BaseDanmaku item, int beginAlpha, int endAlpha, long alphaDuraion) { if (item.getType() != BaseDanmaku.TYPE_SPECIAL) return; ((SpecialDanmaku) item).setAlphaData(beginAlpha, endAlpha, alphaDuraion); updateSpecicalDanmakuDuration(item); } private void updateSpecicalDanmakuDuration(BaseDanmaku item) { if (MAX_Duration_Special_Danmaku == null || (item.duration != null && item.duration.value > MAX_Duration_Special_Danmaku.value)) { MAX_Duration_Special_Danmaku = item.duration; updateMaxDanmakuDuration(); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/Danmakus.java ================================================ /* * 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 java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.Danmaku; import master.flame.danmaku.danmaku.model.IDanmakus; public class Danmakus implements IDanmakus { public Collection items; private Danmakus subItems; private BaseDanmaku startItem, endItem; private BaseDanmaku endSubItem; private BaseDanmaku startSubItem; private volatile AtomicInteger mSize = new AtomicInteger(0); private int mSortType = ST_BY_TIME; private BaseComparator mComparator; private boolean mDuplicateMergingEnabled; private Object mLockObject = new Object(); public Danmakus() { this(ST_BY_TIME, false); } public Danmakus(int sortType) { this(sortType, false); } public Danmakus(int sortType, boolean duplicateMergingEnabled) { this(sortType, duplicateMergingEnabled, null); } public Danmakus(int sortType, boolean duplicateMergingEnabled, BaseComparator baseComparator) { BaseComparator comparator = null; if (sortType == ST_BY_TIME) { comparator = baseComparator == null ? new TimeComparator(duplicateMergingEnabled) : baseComparator; } else if (sortType == ST_BY_YPOS) { comparator = new YPosComparator(duplicateMergingEnabled); } else if (sortType == ST_BY_YPOS_DESC) { comparator = new YPosDescComparator(duplicateMergingEnabled); } if (sortType == ST_BY_LIST) { items = new LinkedList<>(); } else { mDuplicateMergingEnabled = duplicateMergingEnabled; comparator.setDuplicateMergingEnabled(duplicateMergingEnabled); items = new TreeSet<>(comparator); mComparator = comparator; } mSortType = sortType; mSize.set(0); } public Danmakus(Collection items) { setItems(items); } public Danmakus(boolean duplicateMergingEnabled) { this(ST_BY_TIME, duplicateMergingEnabled); } public void setItems(Collection items) { if (mDuplicateMergingEnabled && mSortType != ST_BY_LIST) { synchronized (this.mLockObject) { this.items.clear(); this.items.addAll(items); items = this.items; } } else { this.items = items; } if (items instanceof List) { mSortType = ST_BY_LIST; } mSize.set(items == null ? 0 : items.size()); } @Override public boolean addItem(BaseDanmaku item) { synchronized (this.mLockObject) { if (items != null) { try { if (items.add(item)) { mSize.incrementAndGet(); return true; } } catch (Exception e) { e.printStackTrace(); } } } return false; } @Override public boolean removeItem(BaseDanmaku item) { if (item == null) { return false; } if (item.isOutside()) { item.setVisibility(false); } synchronized (this.mLockObject) { if (items.remove(item)) { mSize.decrementAndGet(); return true; } } return false; } private Collection subset(long startTime, long endTime) { if (mSortType == ST_BY_LIST || items == null || items.size() == 0) { return null; } if (subItems == null) { subItems = new Danmakus(mDuplicateMergingEnabled); subItems.mLockObject = this.mLockObject; } if (startSubItem == null) { startSubItem = createItem("start"); } if (endSubItem == null) { endSubItem = createItem("end"); } startSubItem.setTime(startTime); endSubItem.setTime(endTime); return ((SortedSet) items).subSet(startSubItem, endSubItem); } @Override public IDanmakus subnew(long startTime, long endTime) { Collection subset = subset(startTime, endTime); if (subset == null || subset.isEmpty()) { return null; } LinkedList newSet = new LinkedList(subset); return new Danmakus(newSet); } @Override public IDanmakus sub(long startTime, long endTime) { if (items == null || items.size() == 0) { return null; } if (subItems == null) { if(mSortType == ST_BY_LIST) { subItems = new Danmakus(Danmakus.ST_BY_LIST); subItems.mLockObject = this.mLockObject; synchronized (this.mLockObject) { subItems.setItems(items); } } else { subItems = new Danmakus(mDuplicateMergingEnabled); subItems.mLockObject = this.mLockObject; } } if (mSortType == ST_BY_LIST) { return subItems; } if (startItem == null) { startItem = createItem("start"); } if (endItem == null) { endItem = createItem("end"); } if (subItems != null) { long dtime = startTime - startItem.getActualTime(); if (dtime >= 0 && endTime <= endItem.getActualTime()) { return subItems; } } startItem.setTime(startTime); endItem.setTime(endTime); synchronized (this.mLockObject) { subItems.setItems(((SortedSet) items).subSet(startItem, endItem)); } return subItems; } private BaseDanmaku createItem(String text) { return new Danmaku(text); } public int size() { return mSize.get(); } @Override public void clear() { synchronized (this.mLockObject) { if (items != null) { items.clear(); mSize.set(0); } } if (subItems != null) { subItems = null; startItem = createItem("start"); endItem = createItem("end"); } } @Override public BaseDanmaku first() { if (items != null && !items.isEmpty()) { if (mSortType == ST_BY_LIST) { return ((LinkedList) items).peek(); } return ((SortedSet) items).first(); } return null; } @Override public BaseDanmaku last() { if (items != null && !items.isEmpty()) { if (mSortType == ST_BY_LIST) { return ((LinkedList) items).peekLast(); } return ((SortedSet) items).last(); } return null; } @Override public boolean contains(BaseDanmaku item) { return this.items != null && this.items.contains(item); } @Override public boolean isEmpty() { return this.items == null || this.items.isEmpty(); } private void setDuplicateMergingEnabled(boolean enable) { mComparator.setDuplicateMergingEnabled(enable); mDuplicateMergingEnabled = enable; } @Override public void setSubItemsDuplicateMergingEnabled(boolean enable) { mDuplicateMergingEnabled = enable; startItem = endItem = null; if (subItems == null) { subItems = new Danmakus(enable); subItems.mLockObject = this.mLockObject; } subItems.setDuplicateMergingEnabled(enable); } @Override public Collection getCollection() { return this.items; } @Override public void forEachSync(Consumer consumer) { synchronized (this.mLockObject) { forEach(consumer); } } @Override public void forEach(Consumer consumer) { consumer.before(); Iterator it = items.iterator(); while (it.hasNext()) { BaseDanmaku next = it.next(); if (next == null) { continue; } int action = consumer.accept(next); if (action == DefaultConsumer.ACTION_BREAK) { break; } else if (action == DefaultConsumer.ACTION_REMOVE) { it.remove(); mSize.decrementAndGet(); } else if (action == DefaultConsumer.ACTION_REMOVE_AND_BREAK) { it.remove(); mSize.decrementAndGet(); break; } } consumer.after(); } @Override public Object obtainSynchronizer() { return mLockObject; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DrawingCache.java ================================================ package master.flame.danmaku.danmaku.model.android; import master.flame.danmaku.danmaku.model.IDrawingCache; import master.flame.danmaku.danmaku.model.objectpool.Poolable; public class DrawingCache implements IDrawingCache, Poolable { private final DrawingCacheHolder mHolder; private int mSize = 0; private DrawingCache mNextElement; private boolean mIsPooled; private int referenceCount = 0; public DrawingCache() { mHolder = new DrawingCacheHolder(); } @Override public void build(int w, int h, int density, boolean checkSizeEquals, int bitsPerPixel) { final DrawingCacheHolder holder = mHolder; holder.buildCache(w, h, density, checkSizeEquals, bitsPerPixel); mSize = mHolder.bitmap.getRowBytes() * mHolder.bitmap.getHeight(); } @Override public void erase() { mHolder.erase(); } @Override public DrawingCacheHolder get() { final DrawingCacheHolder holder = mHolder; if (holder.bitmap == null) { return null; } return mHolder; } @Override public void destroy() { if (mHolder != null) { mHolder.recycle(); } mSize = 0; referenceCount = 0; } @Override public int size() { return mSize; } @Override public void setNextPoolable(DrawingCache element) { mNextElement = element; } @Override public DrawingCache getNextPoolable() { return mNextElement; } @Override public boolean isPooled() { return mIsPooled; } @Override public void setPooled(boolean isPooled) { mIsPooled = isPooled; } @Override public synchronized boolean hasReferences() { return referenceCount > 0; } @Override public synchronized void increaseReference() { referenceCount++; } @Override public synchronized void decreaseReference() { referenceCount--; } @Override public int width() { return mHolder.width; } @Override public int height() { return mHolder.height; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DrawingCacheHolder.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import tv.cjump.jni.NativeBitmapFactory; public class DrawingCacheHolder { public Canvas canvas; public Bitmap bitmap; public Bitmap[][] bitmapArray; public Object extra; public int width; public int height; public boolean drawn; private int mDensity; public DrawingCacheHolder() { } public void buildCache(int w, int h, int density, boolean checkSizeEquals, int bitsPerPixel) { boolean reuse = checkSizeEquals ? (w == width && h == height) : (w <= width && h <= height); if (reuse && bitmap != null) { // canvas.drawColor(Color.TRANSPARENT); // canvas.setBitmap(null); bitmap.eraseColor(Color.TRANSPARENT); canvas.setBitmap(bitmap); recycleBitmapArray(); return; } if (bitmap != null) { recycle(); } width = w; height = h; Bitmap.Config config = Bitmap.Config.ARGB_4444; if (bitsPerPixel == 32) { config = Bitmap.Config.ARGB_8888; } bitmap = NativeBitmapFactory.createBitmap(w, h, config); if (density > 0) { mDensity = density; bitmap.setDensity(density); } if (canvas == null){ canvas = new Canvas(bitmap); canvas.setDensity(density); }else canvas.setBitmap(bitmap); } public void erase() { eraseBitmap(bitmap); eraseBitmapArray(); } public synchronized void recycle() { Bitmap bitmapReserve = bitmap; bitmap = null; width = height = 0; if (bitmapReserve != null) { bitmapReserve.recycle(); } recycleBitmapArray(); extra = null; } @SuppressLint("NewApi") public void splitWith(int dispWidth, int dispHeight, int maximumCacheWidth, int maximumCacheHeight) { recycleBitmapArray(); if (width <= 0 || height <= 0 || bitmap == null) { return; } if (width <= maximumCacheWidth && height <= maximumCacheHeight) { return; } maximumCacheWidth = Math.min(maximumCacheWidth, dispWidth); maximumCacheHeight = Math.min(maximumCacheHeight, dispHeight); int xCount = width / maximumCacheWidth + (width % maximumCacheWidth == 0 ? 0 : 1); int yCount = height / maximumCacheHeight + (height % maximumCacheHeight == 0 ? 0 : 1); int averageWidth = width / xCount; int averageHeight = height / yCount; final Bitmap[][] bmpArray = new Bitmap[yCount][xCount]; if (canvas == null){ canvas = new Canvas(); if (mDensity > 0) { canvas.setDensity(mDensity); } } Rect rectSrc = new Rect(); Rect rectDst = new Rect(); for (int yIndex = 0; yIndex < yCount; yIndex++) { for (int xIndex = 0; xIndex < xCount; xIndex++) { Bitmap bmp = bmpArray[yIndex][xIndex] = NativeBitmapFactory.createBitmap( averageWidth, averageHeight, Bitmap.Config.ARGB_8888); if (mDensity > 0) { bmp.setDensity(mDensity); } canvas.setBitmap(bmp); int left = xIndex * averageWidth, top = yIndex * averageHeight; rectSrc.set(left, top, left + averageWidth, top + averageHeight); rectDst.set(0, 0, bmp.getWidth(), bmp.getHeight()); canvas.drawBitmap(bitmap, rectSrc, rectDst, null); } } canvas.setBitmap(bitmap); bitmapArray = bmpArray; } private void eraseBitmap(Bitmap bmp) { if (bmp != null) { bmp.eraseColor(Color.TRANSPARENT); } } private void eraseBitmapArray() { if (bitmapArray != null) { for (int i = 0; i < bitmapArray.length; i++) { for (int j = 0; j < bitmapArray[i].length; j++) { eraseBitmap(bitmapArray[i][j]); } } } } private void recycleBitmapArray() { Bitmap[][] bitmapArrayReserve = bitmapArray; bitmapArray = null; if (bitmapArrayReserve != null) { for (int i = 0; i < bitmapArrayReserve.length; i++) { for (int j = 0; j < bitmapArrayReserve[i].length; j++) { if (bitmapArrayReserve[i][j] != null) { bitmapArrayReserve[i][j].recycle(); bitmapArrayReserve[i][j] = null; } } } } } public final synchronized boolean draw(Canvas canvas, float left, float top, Paint paint) { if (bitmapArray != null) { for (int i = 0; i < bitmapArray.length; i++) { for (int j = 0; j < bitmapArray[i].length; j++) { Bitmap bmp = bitmapArray[i][j]; if (bmp != null) { float dleft = left + j * bmp.getWidth(); if (dleft > canvas.getWidth() || dleft + bmp.getWidth() < 0) { continue; } float dtop = top + i * bmp.getHeight(); if (dtop > canvas.getHeight() || dtop + bmp.getHeight() < 0) { continue; } canvas.drawBitmap(bmp, dleft, dtop, paint); } } } return true; } else if (bitmap != null) { canvas.drawBitmap(bitmap, left, top, paint); return true; } return false; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/DrawingCachePoolManager.java ================================================ package master.flame.danmaku.danmaku.model.android; import master.flame.danmaku.danmaku.model.objectpool.PoolableManager; public class DrawingCachePoolManager implements PoolableManager { @Override public DrawingCache newInstance() { return null; } @Override public void onAcquired(DrawingCache element) { } @Override public void onReleased(DrawingCache element) { } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SimpleTextCacheStuffer.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.graphics.Canvas; import android.graphics.Paint; import android.text.TextPaint; import java.util.HashMap; import java.util.Map; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.SpecialDanmaku; /** * Created by ch on 15-7-16. */ public class SimpleTextCacheStuffer extends BaseCacheStuffer { private final static Map sTextHeightCache = new HashMap(); protected Float getCacheHeight(BaseDanmaku danmaku, Paint paint) { Float textSize = paint.getTextSize(); Float textHeight = sTextHeightCache.get(textSize); if (textHeight == null) { Paint.FontMetrics fontMetrics = paint.getFontMetrics(); textHeight = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading; sTextHeightCache.put(textSize, textHeight); } return textHeight; } @Override public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { float w = 0; Float textHeight = 0f; if (danmaku.lines == null) { if (danmaku.text == null) { w = 0; } else { w = paint.measureText(danmaku.text.toString()); textHeight = getCacheHeight(danmaku, paint); } danmaku.paintWidth = w; danmaku.paintHeight = textHeight; } else { textHeight = getCacheHeight(danmaku, paint); for (String tempStr : danmaku.lines) { if (tempStr.length() > 0) { float tr = paint.measureText(tempStr); w = Math.max(tr, w); } } danmaku.paintWidth = w; danmaku.paintHeight = danmaku.lines.length * textHeight; } } protected void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { if (lineText != null) { canvas.drawText(lineText, left, top, paint); } else { canvas.drawText(danmaku.text.toString(), left, top, paint); } } 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 { canvas.drawText(danmaku.text.toString(), left, top, paint); } } @Override public void clearCaches() { sTextHeightCache.clear(); } protected void drawBackground(BaseDanmaku danmaku, Canvas canvas, float left, float top) { } @Override public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, boolean fromWorkerThread, AndroidDisplayer.DisplayerConfig displayerConfig) { float _left = left; float _top = top; left += danmaku.padding; top += danmaku.padding; if (danmaku.borderColor != 0) { left += displayerConfig.BORDER_WIDTH; top += displayerConfig.BORDER_WIDTH; } displayerConfig.definePaintParams(fromWorkerThread); TextPaint paint = displayerConfig.getPaint(danmaku, fromWorkerThread); drawBackground(danmaku, canvas, _left, _top); if (danmaku.lines != null) { String[] lines = danmaku.lines; if (lines.length == 1) { if (displayerConfig.hasStroke(danmaku)) { displayerConfig.applyPaintConfig(danmaku, paint, true); float strokeLeft = left; float strokeTop = top - paint.ascent(); if (displayerConfig.HAS_PROJECTION) { strokeLeft += displayerConfig.sProjectionOffsetX; strokeTop += displayerConfig.sProjectionOffsetY; } drawStroke(danmaku, lines[0], canvas, strokeLeft, strokeTop, paint); } displayerConfig.applyPaintConfig(danmaku, paint, false); drawText(danmaku, lines[0], canvas, left, top - paint.ascent(), paint, fromWorkerThread); } else { float textHeight = (danmaku.paintHeight - 2 * danmaku.padding) / lines.length; for (int t = 0; t < lines.length; t++) { if (lines[t] == null || lines[t].length() == 0) { continue; } if (displayerConfig.hasStroke(danmaku)) { displayerConfig.applyPaintConfig(danmaku, paint, true); float strokeLeft = left; float strokeTop = t * textHeight + top - paint.ascent(); if (displayerConfig.HAS_PROJECTION) { strokeLeft += displayerConfig.sProjectionOffsetX; strokeTop += displayerConfig.sProjectionOffsetY; } drawStroke(danmaku, lines[t], canvas, strokeLeft, strokeTop, paint); } displayerConfig.applyPaintConfig(danmaku, paint, false); drawText(danmaku, lines[t], canvas, left, t * textHeight + top - paint.ascent(), paint, fromWorkerThread); } } } else { if (displayerConfig.hasStroke(danmaku)) { displayerConfig.applyPaintConfig(danmaku, paint, true); float strokeLeft = left; float strokeTop = top - paint.ascent(); if (displayerConfig.HAS_PROJECTION) { strokeLeft += displayerConfig.sProjectionOffsetX; strokeTop += displayerConfig.sProjectionOffsetY; } drawStroke(danmaku, null, canvas, strokeLeft, strokeTop, paint); } displayerConfig.applyPaintConfig(danmaku, paint, false); drawText(danmaku, null, canvas, left, top - paint.ascent(), paint, fromWorkerThread); } // draw underline if (danmaku.underlineColor != 0) { Paint linePaint = displayerConfig.getUnderlinePaint(danmaku); float bottom = _top + danmaku.paintHeight - displayerConfig.UNDERLINE_HEIGHT; canvas.drawLine(_left, bottom, _left + danmaku.paintWidth, bottom, linePaint); } //draw border if (danmaku.borderColor != 0) { Paint borderPaint = displayerConfig.getBorderPaint(danmaku); canvas.drawRect(_left, _top, _left + danmaku.paintWidth, _top + danmaku.paintHeight, borderPaint); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/SpannedCacheStuffer.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import java.lang.ref.SoftReference; import master.flame.danmaku.danmaku.model.BaseDanmaku; /** * Created by ch on 15-7-16. */ public class SpannedCacheStuffer extends SimpleTextCacheStuffer { @Override public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { if (danmaku.text instanceof Spanned) { CharSequence text = danmaku.text; if (text != null) { StaticLayout staticLayout = new StaticLayout(text, paint, (int) Math.ceil(StaticLayout.getDesiredWidth(danmaku.text, paint)), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); danmaku.paintWidth = staticLayout.getWidth(); danmaku.paintHeight = staticLayout.getHeight(); danmaku.obj = new SoftReference<>(staticLayout); return; } } super.measure(danmaku, paint, fromWorkerThread); } @Override public void drawStroke(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, Paint paint) { if (danmaku.obj == null) { super.drawStroke(danmaku, lineText, canvas, left, top, paint); } } @Override public void drawText(BaseDanmaku danmaku, String lineText, Canvas canvas, float left, float top, TextPaint paint, boolean fromWorkerThread) { if (danmaku.obj == null) { super.drawText(danmaku, lineText, canvas, left, top, paint, fromWorkerThread); return; } SoftReference reference = (SoftReference) danmaku.obj; StaticLayout staticLayout = reference.get(); boolean requestRemeasure = 0 != (danmaku.requestFlags & BaseDanmaku.FLAG_REQUEST_REMEASURE); boolean requestInvalidate = 0 != (danmaku.requestFlags & BaseDanmaku.FLAG_REQUEST_INVALIDATE); if (requestInvalidate || staticLayout == null) { if (requestInvalidate) { danmaku.requestFlags &= ~BaseDanmaku.FLAG_REQUEST_INVALIDATE; } CharSequence text = danmaku.text; if (text != null) { if (requestRemeasure) { staticLayout = new StaticLayout(text, paint, (int) Math.ceil(StaticLayout.getDesiredWidth(danmaku.text, paint)), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); danmaku.paintWidth = staticLayout.getWidth(); danmaku.paintHeight = staticLayout.getHeight(); danmaku.requestFlags &= ~BaseDanmaku.FLAG_REQUEST_REMEASURE; } else { staticLayout = new StaticLayout(text, paint, (int) danmaku.paintWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true); } danmaku.obj = new SoftReference<>(staticLayout); } else { return; } } boolean needRestore = false; if (left != 0 && top != 0) { canvas.save(); canvas.translate(left, top + paint.ascent()); needRestore = true; } staticLayout.draw(canvas); if (needRestore) { canvas.restore(); } } @Override public void clearCaches() { super.clearCaches(); System.gc(); } @Override public void clearCache(BaseDanmaku danmaku) { super.clearCache(danmaku); if (danmaku.obj instanceof SoftReference) { ((SoftReference) danmaku.obj).clear(); } } @Override public void releaseResource(BaseDanmaku danmaku) { clearCache(danmaku); super.releaseResource(danmaku); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/android/ViewCacheStuffer.java ================================================ package master.flame.danmaku.danmaku.model.android; import android.graphics.Canvas; import android.graphics.Paint; import android.text.TextPaint; import android.util.SparseArray; import android.view.View; import java.util.ArrayList; import java.util.List; import master.flame.danmaku.danmaku.model.BaseDanmaku; public abstract class ViewCacheStuffer extends BaseCacheStuffer { public static abstract class ViewHolder { protected final View itemView; public ViewHolder(View itemView) { if (itemView == null) { throw new IllegalArgumentException("itemView may not be null"); } this.itemView = itemView; } public void measure(int widthMeasureSpec, int heightMeasureSpec) { this.itemView.measure(widthMeasureSpec, heightMeasureSpec); } public int getMeasureWidth() { return this.itemView.getMeasuredWidth(); } public int getMeasureHeight() { return this.itemView.getMeasuredHeight(); } public void layout(int l, int t, int r, int b) { this.itemView.layout(l, t, r, b); } public void draw(Canvas canvas, AndroidDisplayer.DisplayerConfig displayerConfig) { this.itemView.draw(canvas); //TODO: apply displayerConfig } } public static final int INVALID_TYPE = -1; public static final int MEASURE_VIEW_TYPE = -2; public static final int DRAW_VIEW_TYPE = -3; public static final int CACHE_VIEW_TYPE = -3; private final int mMaximumWidthPixels; private final int mMaximumHeightPixels; private SparseArray> mViewHolderArray = new SparseArray(); public abstract VH onCreateViewHolder(int viewType); public abstract void onBindViewHolder(int viewType, VH viewHolder, BaseDanmaku danmaku, AndroidDisplayer.DisplayerConfig displayerConfig, TextPaint paint); public int getItemViewType(int position, BaseDanmaku danmaku) { return 0; } public ViewCacheStuffer() { mMaximumWidthPixels = -1; // FIXME: get maximum of canvas mMaximumHeightPixels = -1; } @Override public void measure(BaseDanmaku danmaku, TextPaint paint, boolean fromWorkerThread) { int viewType = getItemViewType(danmaku.index, danmaku); List viewHolders = mViewHolderArray.get(viewType); if (viewHolders == null) { viewHolders = new ArrayList<>(); viewHolders.add(onCreateViewHolder(viewType)); viewHolders.add(onCreateViewHolder(viewType)); viewHolders.add(onCreateViewHolder(viewType)); mViewHolderArray.put(viewType, viewHolders); } VH viewHolder = viewHolders.get(0); // TODO displayerConfig or TextPaint onBindViewHolder(viewType, viewHolder, danmaku, null, paint); viewHolder.measure(View.MeasureSpec.makeMeasureSpec(mMaximumWidthPixels, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(mMaximumHeightPixels, View.MeasureSpec.AT_MOST)); viewHolder.layout(0, 0, viewHolder.getMeasureWidth(), viewHolder.getMeasureHeight()); danmaku.paintWidth = viewHolder.getMeasureWidth(); danmaku.paintHeight = viewHolder.getMeasureHeight(); } @Override public void clearCaches() { } @Override public void releaseResource(BaseDanmaku danmaku) { super.releaseResource(danmaku); danmaku.tag = null; } @Override public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top, boolean fromWorkerThread, AndroidDisplayer.DisplayerConfig displayerConfig) { int viewType = getItemViewType(danmaku.index, danmaku); List viewHolders = mViewHolderArray.get(viewType); VH viewHolder = null; if (viewHolders != null) { viewHolder = viewHolders.get(fromWorkerThread ? 1 : 2); } if (viewHolder == null) { return; } //ignore danmaku.padding, apply it onBindViewHolder displayerConfig.definePaintParams(fromWorkerThread); TextPaint paint = displayerConfig.getPaint(danmaku, fromWorkerThread); displayerConfig.applyPaintConfig(danmaku, paint, false); onBindViewHolder(viewType, viewHolder, danmaku, displayerConfig, paint); viewHolder.measure(View.MeasureSpec.makeMeasureSpec(Math.round(danmaku.paintWidth), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(Math.round(danmaku.paintHeight), View.MeasureSpec.EXACTLY)); boolean needRestore = false; if (!fromWorkerThread) { canvas.save(); canvas.translate(left, top); needRestore = true; } // draw underline if (danmaku.underlineColor != 0) { Paint linePaint = displayerConfig.getUnderlinePaint(danmaku); float bottom = top + danmaku.paintHeight - displayerConfig.UNDERLINE_HEIGHT; canvas.drawLine(left, bottom, left + danmaku.paintWidth, bottom, linePaint); } //draw border if (danmaku.borderColor != 0) { Paint borderPaint = displayerConfig.getBorderPaint(danmaku); canvas.drawRect(left, top, left + danmaku.paintWidth, top + danmaku.paintHeight, borderPaint); } //draw danmaku viewHolder.layout(0, 0, (int) danmaku.paintWidth, (int) danmaku.paintHeight); viewHolder.draw(canvas, displayerConfig); //FIXME: handle canvas.getMaximumBitmapWidth() //TODO: stroke handle displayerConfig if (needRestore) { canvas.restore(); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/FinitePool.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; class FinitePool> implements Pool { /** Factory used to create new pool objects */ private final PoolableManager mManager; /** Maximum number of objects in the pool */ private final int mLimit; /** If true, mLimit is ignored */ private final boolean mInfinite; /** Next object to acquire */ private T mRoot; /** Number of objects in the pool */ private int mPoolCount; FinitePool(PoolableManager manager) { mManager = manager; mLimit = 0; mInfinite = true; } FinitePool(PoolableManager manager, int limit) { if (limit <= 0) { throw new IllegalArgumentException("The pool limit must be > 0"); } mManager = manager; mLimit = limit; mInfinite = false; } public T acquire() { T element; if (mRoot != null) { element = mRoot; mRoot = element.getNextPoolable(); mPoolCount--; } else { element = mManager.newInstance(); } if (element != null) { element.setNextPoolable(null); element.setPooled(false); mManager.onAcquired(element); } return element; } public void release(T element) { if (!element.isPooled()) { if (mInfinite || mPoolCount < mLimit) { mPoolCount++; element.setNextPoolable(mRoot); element.setPooled(true); mRoot = element; } mManager.onReleased(element); } else { System.out.print("[FinitePool] Element is already in pool: " + element); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/Pool.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; public interface Pool> { T acquire(); void release(T element); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/Poolable.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; public interface Poolable { void setNextPoolable(T element); T getNextPoolable(); boolean isPooled(); void setPooled(boolean isPooled); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/PoolableManager.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; public interface PoolableManager> { T newInstance(); void onAcquired(T element); void onReleased(T element); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/Pools.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; public class Pools { private Pools() { } public static > Pool simplePool(PoolableManager manager) { return new FinitePool(manager); } public static > Pool finitePool(PoolableManager manager, int limit) { return new FinitePool(manager, limit); } public static > Pool synchronizedPool(Pool pool) { return new SynchronizedPool(pool); } public static > Pool synchronizedPool(Pool pool, Object lock) { return new SynchronizedPool(pool, lock); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/model/objectpool/SynchronizedPool.java ================================================ /* * Copyright (C) 2009 The Android Open Source Project * * 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.objectpool; class SynchronizedPool> implements Pool { private final Pool mPool; private final Object mLock; public SynchronizedPool(Pool pool) { mPool = pool; mLock = this; } public SynchronizedPool(Pool pool, Object lock) { mPool = pool; mLock = lock; } public T acquire() { synchronized (mLock) { return mPool.acquire(); } } public void release(T element) { synchronized (mLock) { mPool.release(element); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/BaseDanmakuParser.java ================================================ /* * 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; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/IDataSource.java ================================================ /* * 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; public interface IDataSource { String SCHEME_HTTP_TAG = "http"; String SCHEME_HTTPS_TAG = "https"; String SCHEME_FILE_TAG = "file"; public T data(); public void release(); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/android/AndroidFileSource.java ================================================ /* * 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.android; import master.flame.danmaku.danmaku.parser.IDataSource; import master.flame.danmaku.danmaku.util.IOUtils; import android.net.Uri; import java.io.*; import java.net.MalformedURLException; import java.net.URL; public class AndroidFileSource implements IDataSource { private InputStream inStream; public AndroidFileSource(String filepath) { fillStreamFromFile(new File(filepath)); } public AndroidFileSource(Uri uri) { fillStreamFromUri(uri); } public AndroidFileSource(File file) { fillStreamFromFile(file); } public AndroidFileSource(InputStream stream) { this.inStream = stream; } public void fillStreamFromFile(File file) { try { inStream = new BufferedInputStream(new FileInputStream(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } } public void fillStreamFromUri(Uri uri) { String scheme = uri.getScheme(); if (SCHEME_HTTP_TAG.equalsIgnoreCase(scheme) || SCHEME_HTTPS_TAG.equalsIgnoreCase(scheme)) { fillStreamFromHttpFile(uri); } else if (SCHEME_FILE_TAG.equalsIgnoreCase(scheme)) { fillStreamFromFile(new File(uri.getPath())); } } public void fillStreamFromHttpFile(Uri uri) { try { URL url = new URL(uri.getPath()); url.openConnection(); inStream = new BufferedInputStream(url.openStream()); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Override public void release() { IOUtils.closeQuietly(inStream); inStream = null; } @Override public InputStream data() { return inStream; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/parser/android/JSONSource.java ================================================ package master.flame.danmaku.danmaku.parser.android; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import master.flame.danmaku.danmaku.parser.IDataSource; import master.flame.danmaku.danmaku.util.IOUtils; import org.json.JSONArray; import org.json.JSONException; import android.net.Uri; import android.text.TextUtils; /** * a json file source * @author yrom */ public class JSONSource implements IDataSource{ private JSONArray mJSONArray; private InputStream mInput; public JSONSource(String json) throws JSONException{ init(json); } public JSONSource(InputStream in) throws JSONException{ init(in); } private void init(InputStream in) throws JSONException { if(in == null) throw new NullPointerException("input stream cannot be null!"); mInput = in; String json = IOUtils.getString(mInput); init(json); } public JSONSource(URL url) throws JSONException, IOException{ this(url.openStream()); } public JSONSource(File file) throws FileNotFoundException, JSONException{ init(new FileInputStream(file)); } public JSONSource(Uri uri) throws IOException, JSONException { String scheme = uri.getScheme(); if (SCHEME_HTTP_TAG.equalsIgnoreCase(scheme) || SCHEME_HTTPS_TAG.equalsIgnoreCase(scheme)) { init(new URL(uri.getPath()).openStream()); } else if (SCHEME_FILE_TAG.equalsIgnoreCase(scheme)) { init(new FileInputStream(uri.getPath())); } } private void init(String json) throws JSONException { if(!TextUtils.isEmpty(json)){ mJSONArray = new JSONArray(json); } } public JSONArray data(){ return mJSONArray; } @Override public void release() { IOUtils.closeQuietly(mInput); mInput = null; mJSONArray = null; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/IRenderer.java ================================================ /* * 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.renderer; import master.flame.danmaku.danmaku.model.DanmakuTimer; import master.flame.danmaku.danmaku.model.ICacheManager; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.android.Danmakus; public interface IRenderer { int NOTHING_RENDERING = 0; int CACHE_RENDERING = 1; int TEXT_RENDERING = 2; interface OnDanmakuShownListener { void onDanmakuShown(BaseDanmaku danmaku); } class Area { public final float[] mRefreshRect = new float[4]; private int mMaxHeight; private int mMaxWidth; public void setEdge(int maxWidth, int maxHeight) { mMaxWidth = maxWidth; mMaxHeight = maxHeight; } public void reset() { set(mMaxWidth, mMaxHeight, 0, 0); } public void resizeToMax() { set(0, 0, mMaxWidth, mMaxHeight); } public void set(float left, float top, float right, float bottom) { mRefreshRect[0] = left; mRefreshRect[1] = top; mRefreshRect[2] = right; mRefreshRect[3] = bottom; } } public class RenderingState { public final static int UNKNOWN_TIME = -1; public boolean isRunningDanmakus; public DanmakuTimer timer = new DanmakuTimer(); public int indexInScreen; public int totalSizeInScreen; public BaseDanmaku lastDanmaku; public int r2lDanmakuCount; public int l2rDanmakuCount; public int ftDanmakuCount; public int fbDanmakuCount; public int specialDanmakuCount; public int totalDanmakuCount; public int lastTotalDanmakuCount; public long consumingTime; public long beginTime; public long endTime; public boolean nothingRendered; public long sysTime; public long cacheHitCount; public long cacheMissCount; private IDanmakus runningDanmakus = new Danmakus(Danmakus.ST_BY_LIST); private boolean mIsObtaining; public int addTotalCount(int count) { totalDanmakuCount += count; return totalDanmakuCount; } public int addCount(int type, int count) { switch (type) { case BaseDanmaku.TYPE_SCROLL_RL: r2lDanmakuCount += count; return r2lDanmakuCount; case BaseDanmaku.TYPE_SCROLL_LR: l2rDanmakuCount += count; return l2rDanmakuCount; case BaseDanmaku.TYPE_FIX_TOP: ftDanmakuCount += count; return ftDanmakuCount; case BaseDanmaku.TYPE_FIX_BOTTOM: fbDanmakuCount += count; return fbDanmakuCount; case BaseDanmaku.TYPE_SPECIAL: specialDanmakuCount += count; return specialDanmakuCount; } return 0; } public void reset() { lastTotalDanmakuCount = totalDanmakuCount; r2lDanmakuCount = l2rDanmakuCount = ftDanmakuCount = fbDanmakuCount = specialDanmakuCount = totalDanmakuCount = 0; sysTime = beginTime = endTime = consumingTime = 0; nothingRendered = false; synchronized (this) { runningDanmakus.clear(); } } public void set(RenderingState other) { if(other == null) return; lastTotalDanmakuCount = other.lastTotalDanmakuCount; r2lDanmakuCount = other.r2lDanmakuCount; l2rDanmakuCount = other.l2rDanmakuCount; ftDanmakuCount = other.ftDanmakuCount; fbDanmakuCount = other.fbDanmakuCount; specialDanmakuCount = other.specialDanmakuCount; totalDanmakuCount = other.totalDanmakuCount; consumingTime = other.consumingTime; beginTime = other.beginTime; endTime = other.endTime; nothingRendered = other.nothingRendered; sysTime = other.sysTime; cacheHitCount = other.cacheHitCount; cacheMissCount = other.cacheMissCount; } public void appendToRunningDanmakus(BaseDanmaku danmaku) { if (!mIsObtaining) { runningDanmakus.addItem(danmaku); } } public IDanmakus obtainRunningDanmakus() { mIsObtaining = true; IDanmakus danmakus; synchronized (this) { danmakus = runningDanmakus; runningDanmakus = new Danmakus(Danmakus.ST_BY_LIST); } mIsObtaining = false; return danmakus; } } void draw(IDisplayer disp, IDanmakus danmakus, long startRenderTime, RenderingState renderingState); void clear(); void clearRetainer(); void release(); void setVerifierEnabled(boolean enabled); void setCacheManager(ICacheManager cacheManager); void setOnDanmakuShownListener(OnDanmakuShownListener onDanmakuShownListener); void removeOnDanmakuShownListener(); void alignBottom(boolean enable); } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/Renderer.java ================================================ /* * 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.renderer; public abstract class Renderer implements IRenderer { } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakuRenderer.java ================================================ /* * 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.renderer.android; 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.IDisplayer; import master.flame.danmaku.danmaku.model.IDrawingCache; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.renderer.IRenderer; import master.flame.danmaku.danmaku.renderer.Renderer; public class DanmakuRenderer extends Renderer { private class Consumer extends IDanmakus.DefaultConsumer { private BaseDanmaku lastItem; public IDisplayer disp; public RenderingState renderingState; public long startRenderTime; @Override public int accept(BaseDanmaku drawItem) { lastItem = drawItem; if (drawItem.isTimeOut()) { disp.recycle(drawItem); return renderingState.isRunningDanmakus ? ACTION_REMOVE : ACTION_CONTINUE; } if (!renderingState.isRunningDanmakus && drawItem.isOffset()) { return ACTION_CONTINUE; } if (!drawItem.hasPassedFilter()) { mContext.mDanmakuFilters.filter(drawItem, renderingState.indexInScreen, renderingState.totalSizeInScreen, renderingState.timer, false, mContext); } if (drawItem.getActualTime() < startRenderTime || (drawItem.priority == 0 && drawItem.isFiltered())) { return ACTION_CONTINUE; } if (drawItem.isLate()) { IDrawingCache cache = drawItem.getDrawingCache(); if (mCacheManager != null && (cache == null || cache.get() == null)) { mCacheManager.addDanmaku(drawItem); } return ACTION_BREAK; } if (drawItem.getType() == BaseDanmaku.TYPE_SCROLL_RL) { // 同屏弹幕密度只对滚动弹幕有效 renderingState.indexInScreen++; } // measure if (!drawItem.isMeasured()) { drawItem.measure(disp, false); } // notify prepare drawing if (!drawItem.isPrepared()) { drawItem.prepare(disp, false); } // layout mDanmakusRetainer.fix(drawItem, disp, mVerifier); // draw if (drawItem.isShown()) { if (drawItem.lines == null && drawItem.getBottom() > disp.getHeight()) { return ACTION_CONTINUE; // skip bottom outside danmaku } int renderingType = drawItem.draw(disp); if (renderingType == IRenderer.CACHE_RENDERING) { renderingState.cacheHitCount++; } else if (renderingType == IRenderer.TEXT_RENDERING) { renderingState.cacheMissCount++; if (mCacheManager != null) { mCacheManager.addDanmaku(drawItem); } } renderingState.addCount(drawItem.getType(), 1); renderingState.addTotalCount(1); renderingState.appendToRunningDanmakus(drawItem); if (mOnDanmakuShownListener != null && drawItem.firstShownFlag != mContext.mGlobalFlagValues.FIRST_SHOWN_RESET_FLAG) { drawItem.firstShownFlag = mContext.mGlobalFlagValues.FIRST_SHOWN_RESET_FLAG; mOnDanmakuShownListener.onDanmakuShown(drawItem); } } return ACTION_CONTINUE; } @Override public void after() { renderingState.lastDanmaku = lastItem; super.after(); } } private DanmakuTimer mStartTimer; private final DanmakuContext mContext; private DanmakusRetainer.Verifier mVerifier; private final DanmakusRetainer.Verifier verifier = new DanmakusRetainer.Verifier() { @Override public boolean skipLayout(BaseDanmaku danmaku, float fixedTop, int lines, boolean willHit) { if (danmaku.priority == 0 && mContext.mDanmakuFilters.filterSecondary(danmaku, lines, 0, mStartTimer, willHit, mContext)) { danmaku.setVisibility(false); return true; } return false; } }; private final DanmakusRetainer mDanmakusRetainer; private ICacheManager mCacheManager; private OnDanmakuShownListener mOnDanmakuShownListener; private Consumer mConsumer = new Consumer(); public DanmakuRenderer(DanmakuContext config) { mContext = config; mDanmakusRetainer = new DanmakusRetainer(config.isAlignBottom()); } @Override public void clear() { clearRetainer(); mContext.mDanmakuFilters.clear(); } @Override public void clearRetainer() { mDanmakusRetainer.clear(); } @Override public void release() { mDanmakusRetainer.release(); mContext.mDanmakuFilters.clear(); } @Override public void setVerifierEnabled(boolean enabled) { mVerifier = (enabled ? verifier : null); } @Override public void draw(final IDisplayer disp, IDanmakus danmakus, long startRenderTime, final RenderingState renderingState) { mStartTimer = renderingState.timer; mConsumer.disp = disp; mConsumer.renderingState = renderingState; mConsumer.startRenderTime = startRenderTime; danmakus.forEachSync(mConsumer); } public void setCacheManager(ICacheManager cacheManager) { mCacheManager = cacheManager; } @Override public void setOnDanmakuShownListener(OnDanmakuShownListener onDanmakuShownListener) { mOnDanmakuShownListener = onDanmakuShownListener; } @Override public void removeOnDanmakuShownListener() { mOnDanmakuShownListener = null; } public void alignBottom(boolean enable) { if (mDanmakusRetainer != null) { mDanmakusRetainer.alignBottom(enable); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/renderer/android/DanmakusRetainer.java ================================================ /* * 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.renderer.android; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.android.Danmakus; import master.flame.danmaku.danmaku.util.DanmakuUtils; public class DanmakusRetainer { private IDanmakusRetainer rldrInstance = null; private IDanmakusRetainer lrdrInstance = null; private IDanmakusRetainer ftdrInstance = null; private IDanmakusRetainer fbdrInstance = null; public DanmakusRetainer(boolean alignBottom) { alignBottom(alignBottom); } public void alignBottom(boolean alignBottom) { rldrInstance = alignBottom ? new AlignBottomRetainer() : new AlignTopRetainer(); lrdrInstance = alignBottom ? new AlignBottomRetainer() : new AlignTopRetainer(); if (ftdrInstance == null) { ftdrInstance = new FTDanmakusRetainer(); } if (fbdrInstance == null) { fbdrInstance = new AlignBottomRetainer(); } } public void fix(BaseDanmaku danmaku, IDisplayer disp, Verifier verifier) { int type = danmaku.getType(); switch (type) { case BaseDanmaku.TYPE_SCROLL_RL: rldrInstance.fix(danmaku, disp, verifier); break; case BaseDanmaku.TYPE_SCROLL_LR: lrdrInstance.fix(danmaku, disp, verifier); break; case BaseDanmaku.TYPE_FIX_TOP: ftdrInstance.fix(danmaku, disp, verifier); break; case BaseDanmaku.TYPE_FIX_BOTTOM: fbdrInstance.fix(danmaku, disp, verifier); break; case BaseDanmaku.TYPE_SPECIAL: danmaku.layout(disp, 0, 0); break; } } public void clear() { if (rldrInstance != null) { rldrInstance.clear(); } if (lrdrInstance != null) { lrdrInstance.clear(); } if (ftdrInstance != null) { ftdrInstance.clear(); } if (fbdrInstance != null) { fbdrInstance.clear(); } } public void release(){ clear(); } public interface Verifier { public boolean skipLayout(BaseDanmaku danmaku, float fixedTop, int lines, boolean willHit); } public interface IDanmakusRetainer { public void fix(BaseDanmaku drawItem, IDisplayer disp, Verifier verifier); public void clear(); } private static class RetainerState { public int lines = 0; public BaseDanmaku insertItem = null, firstItem = null, lastItem = null, minRightRow = null, removeItem = null; public boolean overwriteInsert = false; public boolean shown = false; public boolean willHit = false; } private static class AlignTopRetainer implements IDanmakusRetainer { protected class RetainerConsumer extends IDanmakus.Consumer { public IDisplayer disp; int lines = 0; public BaseDanmaku insertItem = null, firstItem = null, lastItem = null, minRightRow = null, drawItem = null; boolean overwriteInsert = false; boolean shown = false; boolean willHit = false; @Override public void before() { lines = 0; insertItem = firstItem = lastItem = minRightRow = null; overwriteInsert = shown = willHit = false; } @Override public int accept(BaseDanmaku item) { if (mCancelFixingFlag) { return ACTION_BREAK; } lines++; if(item == drawItem){ insertItem = item; lastItem = null; shown = true; willHit = false; return ACTION_BREAK; } if (firstItem == null) firstItem = item; if (drawItem.paintHeight + item.getTop() > disp.getHeight()) { overwriteInsert = true; return ACTION_BREAK; } if (minRightRow == null) { minRightRow = item; } else { if (minRightRow.getRight() >= item.getRight()) { minRightRow = item; } } // 检查碰撞 willHit = DanmakuUtils.willHitInDuration(disp, item, drawItem, drawItem.getDuration(), drawItem.getTimer().currMillisecond); if (!willHit) { insertItem = item; return ACTION_BREAK; } lastItem = item; return ACTION_CONTINUE; } @Override public RetainerState result() { RetainerState retainerState = new RetainerState(); retainerState.lines = this.lines; retainerState.firstItem = this.firstItem; retainerState.insertItem = this.insertItem; retainerState.lastItem = this.lastItem; retainerState.minRightRow = this.minRightRow; retainerState.overwriteInsert = this.overwriteInsert; retainerState.shown = this.shown; retainerState.willHit = this.willHit; return retainerState; } } protected Danmakus mVisibleDanmakus = new Danmakus(Danmakus.ST_BY_YPOS); protected boolean mCancelFixingFlag = false; protected RetainerConsumer mConsumer = new RetainerConsumer(); @Override public void fix(BaseDanmaku drawItem, IDisplayer disp, Verifier verifier) { if (drawItem.isOutside()) return; float topPos = disp.getAllMarginTop(); int lines = 0; boolean shown = drawItem.isShown(); boolean willHit = !shown && !mVisibleDanmakus.isEmpty(); boolean isOutOfVertialEdge = false; BaseDanmaku removeItem = null; int margin = disp.getMargin(); if (!shown) { mCancelFixingFlag = false; // 确定弹幕位置 BaseDanmaku insertItem = null, firstItem = null, lastItem = null, minRightRow = null; boolean overwriteInsert = false; mConsumer.disp = disp; mConsumer.drawItem = drawItem; mVisibleDanmakus.forEachSync(mConsumer); RetainerState retainerState = mConsumer.result(); if (retainerState != null) { lines = retainerState.lines; insertItem = retainerState.insertItem; firstItem = retainerState.firstItem; lastItem = retainerState.lastItem; minRightRow = retainerState.minRightRow; overwriteInsert = retainerState.overwriteInsert; shown = retainerState.shown; willHit = retainerState.willHit; } boolean checkEdge = true; if (insertItem != null) { if (lastItem != null) topPos = lastItem.getBottom() + margin; else topPos = insertItem.getTop(); if (insertItem != drawItem){ removeItem = insertItem; shown = false; } } else if (overwriteInsert && minRightRow != null) { topPos = minRightRow.getTop(); checkEdge = false; shown = false; } else if (lastItem != null) { topPos = lastItem.getBottom() + margin; willHit = false; } else if (firstItem != null) { topPos = firstItem.getTop(); removeItem = firstItem; shown = false; } else { topPos = disp.getAllMarginTop(); } if (checkEdge) { isOutOfVertialEdge = isOutVerticalEdge(overwriteInsert, drawItem, disp, topPos, firstItem, lastItem); } if (isOutOfVertialEdge) { topPos = disp.getAllMarginTop(); willHit = true; lines = 1; } else if (removeItem != null) { lines--; } if (topPos == disp.getAllMarginTop()) { shown = false; } } if (verifier != null && verifier.skipLayout(drawItem, topPos, lines, willHit)) { return; } if (isOutOfVertialEdge) { clear(); } drawItem.layout(disp, drawItem.getLeft(), topPos); if (!shown) { mVisibleDanmakus.removeItem(removeItem); mVisibleDanmakus.addItem(drawItem); } } protected boolean isOutVerticalEdge(boolean overwriteInsert, BaseDanmaku drawItem, IDisplayer disp, float topPos, BaseDanmaku firstItem, BaseDanmaku lastItem) { if (topPos < disp.getAllMarginTop() || (firstItem != null && firstItem.getTop() > 0) || topPos + drawItem.paintHeight > disp.getHeight()) { return true; } return false; } @Override public void clear() { mCancelFixingFlag = true; mVisibleDanmakus.clear(); } } private static class FTDanmakusRetainer extends AlignTopRetainer { @Override protected boolean isOutVerticalEdge(boolean overwriteInsert, BaseDanmaku drawItem, IDisplayer disp, float topPos, BaseDanmaku firstItem, BaseDanmaku lastItem) { if (topPos + drawItem.paintHeight > disp.getHeight()) { return true; } return false; } } private static class AlignBottomRetainer extends FTDanmakusRetainer { protected class RetainerConsumer extends IDanmakus.Consumer { public IDisplayer disp; int lines = 0; public BaseDanmaku removeItem = null, firstItem = null, drawItem = null; boolean willHit = false; float topPos; @Override public void before() { lines = 0; removeItem = firstItem = null; willHit = false; } @Override public int accept(BaseDanmaku item) { if (mCancelFixingFlag) { return ACTION_BREAK; } lines++; if (item == drawItem) { removeItem = null; willHit = false; return ACTION_BREAK; } if (firstItem == null) { firstItem = item; if (firstItem.getBottom() != disp.getHeight()) { return ACTION_BREAK; } } if (topPos < disp.getAllMarginTop()) { removeItem = null; return ACTION_BREAK; } // 检查碰撞 willHit = DanmakuUtils.willHitInDuration(disp, item, drawItem, drawItem.getDuration(), drawItem.getTimer().currMillisecond); if (!willHit) { removeItem = item; // topPos = item.getBottom() - drawItem.paintHeight; return ACTION_BREAK; } topPos = item.getTop() - disp.getMargin() - drawItem.paintHeight; return ACTION_CONTINUE; } @Override public RetainerState result() { RetainerState retainerState = new RetainerState(); retainerState.lines = this.lines; retainerState.firstItem = this.firstItem; retainerState.removeItem = this.removeItem; retainerState.willHit = this.willHit; return retainerState; } } protected RetainerConsumer mConsumer = new RetainerConsumer(); protected Danmakus mVisibleDanmakus = new Danmakus(Danmakus.ST_BY_YPOS_DESC); @Override public void fix(BaseDanmaku drawItem, IDisplayer disp, Verifier verifier) { if (drawItem.isOutside()) return; boolean shown = drawItem.isShown(); float topPos = shown ? drawItem.getTop() : -1; int lines = 0; boolean willHit = !shown && !mVisibleDanmakus.isEmpty(); boolean isOutOfVerticalEdge = false; if (topPos < disp.getAllMarginTop()) { topPos = disp.getHeight() - drawItem.paintHeight; } BaseDanmaku removeItem = null, firstItem = null; if (!shown) { mCancelFixingFlag = false; mConsumer.topPos = topPos; mConsumer.disp = disp; mConsumer.drawItem = drawItem; mVisibleDanmakus.forEachSync(mConsumer); RetainerState retainerState = mConsumer.result(); topPos = mConsumer.topPos; if (retainerState != null) { lines = retainerState.lines; firstItem = retainerState.firstItem; removeItem = retainerState.removeItem; shown = retainerState.shown; willHit = retainerState.willHit; } isOutOfVerticalEdge = isOutVerticalEdge(false, drawItem, disp, topPos, firstItem, null); if (isOutOfVerticalEdge) { topPos = disp.getHeight() - drawItem.paintHeight; willHit = true; lines = 1; } else { if (topPos >= disp.getAllMarginTop()) { willHit = false; } if (removeItem != null) { lines--; } } } if (verifier != null && verifier.skipLayout(drawItem, topPos, lines, willHit)) { return; } if (isOutOfVerticalEdge) { clear(); } drawItem.layout(disp, drawItem.getLeft(), topPos); if (!shown) { mVisibleDanmakus.removeItem(removeItem); mVisibleDanmakus.addItem(drawItem); } } protected boolean isOutVerticalEdge(boolean overwriteInsert, BaseDanmaku drawItem, IDisplayer disp, float topPos, BaseDanmaku firstItem, BaseDanmaku lastItem) { if (topPos < disp.getAllMarginTop() || (firstItem != null && firstItem.getBottom() != disp.getHeight())) { return true; } return false; } @Override public void clear() { mCancelFixingFlag = true; mVisibleDanmakus.clear(); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/DanmakuUtils.java ================================================ /* * 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; } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/IOUtils.java ================================================ package master.flame.danmaku.danmaku.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Created by MoiTempete. */ public class IOUtils { public static String getString(InputStream in){ byte[] data = getBytes(in); return data == null? null:new String(data); } public static byte[] getBytes(InputStream in){ try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int len = 0; while ((len = in.read(buffer)) != -1) baos.write(buffer, 0, len); in.close(); return baos.toByteArray(); } catch (IOException e) { return null; } } public static void closeQuietly(InputStream in){ try { if(in != null) in.close(); } catch (IOException ignore) {} } public static void closeQuietly(OutputStream out){ try { if(out != null) out.close(); } catch (IOException ignore) {} } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/danmaku/util/SystemClock.java ================================================ package master.flame.danmaku.danmaku.util; /** * Created by ch on 15-12-9. */ public class SystemClock { public static final long uptimeMillis() { return android.os.SystemClock.elapsedRealtime(); } public static final void sleep(long mills) { android.os.SystemClock.sleep(mills); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/ui/widget/DanmakuSurfaceView.java ================================================ /* * 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.ui.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.os.HandlerThread; import android.os.Looper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import java.util.LinkedList; import java.util.Locale; import master.flame.danmaku.controller.DrawHandler; import master.flame.danmaku.controller.DrawHandler.Callback; import master.flame.danmaku.controller.DrawHelper; 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.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.SystemClock; import static android.R.attr.x; public class DanmakuSurfaceView extends SurfaceView implements IDanmakuView, IDanmakuViewController, SurfaceHolder.Callback { public static final String TAG = "DanmakuSurfaceView"; private Callback mCallback; private SurfaceHolder mSurfaceHolder; private HandlerThread mHandlerThread; private DrawHandler handler; private boolean isSurfaceCreated; private boolean mEnableDanmakuDrwaingCache = true; private OnDanmakuClickListener mOnDanmakuClickListener; private float mXOff; private float mYOff; private DanmakuTouchHelper mTouchHelper; private boolean mShowFps; private boolean mDanmakuVisible = true; protected int mDrawingThreadType = THREAD_TYPE_NORMAL_PRIORITY; public DanmakuSurfaceView(Context context) { super(context); init(); } private void init() { setZOrderMediaOverlay(true); setWillNotCacheDrawing(true); setDrawingCacheEnabled(false); setWillNotDraw(true); mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT); DrawHelper.useDrawColorToClearCanvas(true, true); mTouchHelper = DanmakuTouchHelper.instance(this); } public DanmakuSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DanmakuSurfaceView(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 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; } public void setCallback(Callback callback) { mCallback = callback; if (handler != null) { handler.setCallback(callback); } } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { isSurfaceCreated = true; Canvas canvas = surfaceHolder.lockCanvas(); if (canvas != null) { DrawHelper.clearCanvas(canvas); surfaceHolder.unlockCanvasAndPost(canvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (handler != null) { handler.notifyDispSizeChanged(width, height); } } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { isSurfaceCreated = false; } @Override public void release() { stop(); if(mDrawTimes!= null) mDrawTimes.clear(); } @Override public void stop() { stopDraw(); } private synchronized void stopDraw() { if (handler != null) { handler.quit(); handler = null; } 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(); return mHandlerThread.getLooper(); } private void prepare() { if (handler == null) handler = new DrawHandler(getLooper(mDrawingThreadType), this, mDanmakuVisible); } @Override public void prepare(BaseDanmakuParser parser, DanmakuContext config) { 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 static final int MAX_RECORD_SIZE = 50; private static final int ONE_SECOND = 1000; private LinkedList mDrawTimes; private float fps() { 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 (!isSurfaceCreated) return 0; if (!isShown()) return -1; long stime = SystemClock.uptimeMillis(); long dtime = 0; Canvas canvas = mSurfaceHolder.lockCanvas(); if (canvas != null){ if (handler != null) { RenderingState rs = handler.draw(canvas); if (mShowFps) { if (mDrawTimes == null) mDrawTimes = new LinkedList(); dtime = SystemClock.uptimeMillis() - stime; 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); } } if (isSurfaceCreated) mSurfaceHolder.unlockCanvasAndPost(canvas); } dtime = SystemClock.uptimeMillis() - stime; return dtime; } 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 if (handler == null) { 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(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean isEventConsumed = mTouchHelper.onTouchEvent(event); if (!isEventConsumed) { return super.onTouchEvent(event); } return isEventConsumed; } 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 int getViewWidth() { return super.getWidth(); } @Override public int getViewHeight() { return super.getHeight(); } @Override public View getView() { return this; } @Override public void show() { showAndResumeDrawTask(null); } @Override public void showAndResumeDrawTask(Long position) { mDanmakuVisible = true; 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 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() { } @Override public void clear() { if (!isViewReady()) { return; } Canvas canvas = mSurfaceHolder.lockCanvas(); if (canvas != null) { DrawHelper.clearCanvas(canvas); mSurfaceHolder.unlockCanvasAndPost(canvas); } } @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 public boolean isHardwareAccelerated() { return false; } @Override public void clearDanmakusOnScreen() { if (handler != null) { handler.clearDanmakusOnScreen(); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/ui/widget/DanmakuTextureView.java ================================================ /* * 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.ui.widget; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.SurfaceTexture; import android.os.Build; import android.os.HandlerThread; import android.os.Looper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.TextureView; import android.view.View; import java.util.LinkedList; import java.util.Locale; import master.flame.danmaku.controller.DrawHandler; import master.flame.danmaku.controller.DrawHandler.Callback; import master.flame.danmaku.controller.DrawHelper; 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.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.SystemClock; /** * DanmakuTextureView 目前使用lockCanvas, 没有使用opengl硬件加速 * @author ch * */ @SuppressLint("NewApi") public class DanmakuTextureView extends TextureView implements IDanmakuView, IDanmakuViewController, TextureView.SurfaceTextureListener { public static final String TAG = "DanmakuTextureView"; private Callback mCallback; private HandlerThread mHandlerThread; private DrawHandler handler; private boolean isSurfaceCreated; private boolean mEnableDanmakuDrwaingCache = true; private OnDanmakuClickListener mOnDanmakuClickListener; private float mXOff; private float mYOff; private DanmakuTouchHelper mTouchHelper; private boolean mShowFps; private boolean mDanmakuVisible = true; protected int mDrawingThreadType = THREAD_TYPE_NORMAL_PRIORITY; public DanmakuTextureView(Context context) { super(context); init(); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void init() { setLayerType(View.LAYER_TYPE_HARDWARE, null); setOpaque(false); setWillNotCacheDrawing(true); setDrawingCacheEnabled(false); setWillNotDraw(true); setSurfaceTextureListener(this); DrawHelper.useDrawColorToClearCanvas(true, true); mTouchHelper = DanmakuTouchHelper.instance(this); } public DanmakuTextureView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DanmakuTextureView(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 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; } public void setCallback(Callback callback) { mCallback = callback; if (handler != null) { handler.setCallback(callback); } } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { isSurfaceCreated = true; } @Override public synchronized boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { isSurfaceCreated = false; return true; } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { if (handler != null) { handler.notifyDispSizeChanged(width, height); } } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } @Override public void release() { stop(); if(mDrawTimes!= null) mDrawTimes.clear(); } @Override public void stop() { stopDraw(); } private synchronized void stopDraw() { if (handler != null) { handler.quit(); handler = null; } 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(); return mHandlerThread.getLooper(); } private void prepare() { if (handler == null) handler = new DrawHandler(getLooper(mDrawingThreadType), this, mDanmakuVisible); } @Override public void prepare(BaseDanmakuParser parser, DanmakuContext config) { 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 static final int MAX_RECORD_SIZE = 50; private static final int ONE_SECOND = 1000; private LinkedList mDrawTimes; private float fps() { 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 synchronized long drawDanmakus() { if (!isSurfaceCreated) return 0; long stime = SystemClock.uptimeMillis(); if (!isShown()) return -1; long dtime = 0; Canvas canvas = lockCanvas(); if (canvas != null) { if (handler != null) { RenderingState rs = handler.draw(canvas); if (mShowFps) { if (mDrawTimes == null) mDrawTimes = new LinkedList(); dtime = SystemClock.uptimeMillis() - stime; 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); } } if (isSurfaceCreated) unlockCanvasAndPost(canvas); } dtime = SystemClock.uptimeMillis() - stime; return dtime; } 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 if (handler == null) { 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(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean isEventConsumed = mTouchHelper.onTouchEvent(event); if (!isEventConsumed) { return super.onTouchEvent(event); } return isEventConsumed; } 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 int getViewWidth() { return super.getWidth(); } @Override public int getViewHeight() { return super.getHeight(); } @Override public View getView() { return this; } @Override public void show() { showAndResumeDrawTask(null); } @Override public void showAndResumeDrawTask(Long position) { mDanmakuVisible = true; 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 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() { } @Override public synchronized void clear() { if (!isViewReady()) { return; } Canvas canvas = lockCanvas(); if (canvas != null) { DrawHelper.clearCanvas(canvas); unlockCanvasAndPost(canvas); } } @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 public boolean isHardwareAccelerated() { return false; } @Override public void clearDanmakusOnScreen() { if (handler != null) { handler.clearDanmakusOnScreen(); } } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/ui/widget/DanmakuTouchHelper.java ================================================ package master.flame.danmaku.ui.widget; import android.graphics.RectF; import android.nfc.Tag; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import master.flame.danmaku.controller.IDanmakuView; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.IDanmakus; import master.flame.danmaku.danmaku.model.android.Danmakus; /** * Created by kmfish on 2015/1/25. */ public class DanmakuTouchHelper { private final GestureDetector mTouchDelegate; private IDanmakuView danmakuView; private RectF mDanmakuBounds; private float mXOff; private float mYOff; private final android.view.GestureDetector.OnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent event) { if (danmakuView != null) { IDanmakuView.OnDanmakuClickListener onDanmakuClickListener = danmakuView.getOnDanmakuClickListener(); if (onDanmakuClickListener != null) { mXOff = danmakuView.getXOff(); mYOff = danmakuView.getYOff(); return true; } } return false; } @Override public boolean onSingleTapConfirmed(MotionEvent event) { IDanmakus clickDanmakus = touchHitDanmaku(event.getX(), event.getY()); boolean isEventConsumed = false; if (null != clickDanmakus && !clickDanmakus.isEmpty()) { isEventConsumed = performDanmakuClick(clickDanmakus, false); } if (!isEventConsumed) { isEventConsumed = performViewClick(); } return isEventConsumed; } @Override public void onLongPress(MotionEvent event) { IDanmakuView.OnDanmakuClickListener onDanmakuClickListener = danmakuView.getOnDanmakuClickListener(); if (onDanmakuClickListener == null) { return; } mXOff = danmakuView.getXOff(); mYOff = danmakuView.getYOff(); IDanmakus clickDanmakus = touchHitDanmaku(event.getX(), event.getY()); if (null != clickDanmakus && !clickDanmakus.isEmpty()) { performDanmakuClick(clickDanmakus, true); } } }; private DanmakuTouchHelper(IDanmakuView danmakuView) { this.danmakuView = danmakuView; this.mDanmakuBounds = new RectF(); this.mTouchDelegate = new GestureDetector(((View) danmakuView).getContext(), mOnGestureListener); } public static synchronized DanmakuTouchHelper instance(IDanmakuView danmakuView) { return new DanmakuTouchHelper(danmakuView); } public boolean onTouchEvent(MotionEvent event) { return mTouchDelegate.onTouchEvent(event); } private boolean performDanmakuClick(IDanmakus danmakus, boolean isLongClick) { IDanmakuView.OnDanmakuClickListener onDanmakuClickListener = danmakuView.getOnDanmakuClickListener(); if (onDanmakuClickListener != null) { if (isLongClick) { return onDanmakuClickListener.onDanmakuLongClick(danmakus); } else { return onDanmakuClickListener.onDanmakuClick(danmakus); } } return false; } private boolean performViewClick() { IDanmakuView.OnDanmakuClickListener onDanmakuClickListener = danmakuView.getOnDanmakuClickListener(); if (onDanmakuClickListener != null) { return onDanmakuClickListener.onViewClick(danmakuView); } return false; } private IDanmakus touchHitDanmaku(final float x, final float y) { final IDanmakus hitDanmakus = new Danmakus(); mDanmakuBounds.setEmpty(); IDanmakus danmakus = danmakuView.getCurrentVisibleDanmakus(); if (null != danmakus && !danmakus.isEmpty()) { danmakus.forEachSync(new IDanmakus.DefaultConsumer() { @Override public int accept(BaseDanmaku danmaku) { if (null != danmaku) { mDanmakuBounds.set(danmaku.getLeft(), danmaku.getTop(), danmaku.getRight(), danmaku.getBottom()); if (mDanmakuBounds.intersect(x - mXOff, y - mYOff, x + mXOff, y + mYOff)) { hitDanmakus.addItem(danmaku); } } return ACTION_CONTINUE; } }); } return hitDanmakus; } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/ui/widget/DanmakuView.java ================================================ /* * 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.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.Handler; import android.os.HandlerThread; import android.os.Looper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.util.LinkedList; import java.util.Locale; import master.flame.danmaku.controller.DrawHandler; import master.flame.danmaku.controller.DrawHandler.Callback; import master.flame.danmaku.controller.DrawHelper; 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.renderer.IRenderer.RenderingState; import master.flame.danmaku.danmaku.util.SystemClock; public class DanmakuView extends View implements IDanmakuView, IDanmakuViewController { public static final String TAG = "DanmakuView"; private Callback mCallback; private HandlerThread mHandlerThread; protected volatile DrawHandler handler; private boolean isSurfaceCreated; private boolean mEnableDanmakuDrwaingCache = true; private OnDanmakuClickListener mOnDanmakuClickListener; private float mXOff; private float mYOff; private OnClickListener mOnClickListener; private DanmakuTouchHelper mTouchHelper; private boolean mShowFps; private boolean mDanmakuVisible = true; protected int mDrawingThreadType = THREAD_TYPE_NORMAL_PRIORITY; private Object mDrawMonitor = new Object(); private boolean mDrawFinished = false; protected 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); mTouchHelper = DanmakuTouchHelper.instance(this); } 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 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; } 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 synchronized void stopDraw() { if (this.handler == null) { return; } DrawHandler handler = this.handler; this.handler = null; unlockCanvasAndPost(); if (handler != null) { handler.quit(); } 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(); return mHandlerThread.getLooper(); } private void prepare() { if (handler == null) handler = new DrawHandler(getLooper(mDrawingThreadType), this, mDanmakuVisible); } @Override public void prepare(BaseDanmakuParser parser, DanmakuContext config) { 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 static final int MAX_RECORD_SIZE = 50; private static final int ONE_SECOND = 1000; private LinkedList mDrawTimes; protected boolean mClearFlag; private float fps() { 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 (!isSurfaceCreated) return 0; if (!isShown()) return -1; long stime = SystemClock.uptimeMillis(); lockCanvas(); return SystemClock.uptimeMillis() - stime; } @SuppressLint("NewApi") private void postInvalidateCompat() { mRequestRender = true; if(Build.VERSION.SDK_INT >= 16) { this.postInvalidateOnAnimation(); } else { this.postInvalidate(); } } protected 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(); 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.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 || DanmakuView.super.isShown()) { drawHandler.resume(); } else { drawHandler.postDelayed(this, 100 * mResumeTryCount); } } }; @Override public void resume() { 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() { stop(); start(); } @Override public void start() { start(0); } @Override public void start(long position) { Handler handler = this.handler; if (handler == null) { prepare(); handler = this.handler; } else { handler.removeCallbacksAndMessages(null); } if (handler != null) { handler.obtainMessage(DrawHandler.START, position).sendToTarget(); } } @Override public boolean onTouchEvent(MotionEvent event) { boolean isEventConsumed = mTouchHelper.onTouchEvent(event); if (!isEventConsumed) { return super.onTouchEvent(event); } return isEventConsumed; } 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 int getViewWidth() { return super.getWidth(); } @Override public int getViewHeight() { return super.getHeight(); } @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(); } } @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; } public void forceRender() { mRequestRender = true; handler.forceRender(); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/master/flame/danmaku/ui/widget/FakeDanmakuView.java ================================================ package master.flame.danmaku.ui.widget; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import master.flame.danmaku.controller.DrawHandler; import master.flame.danmaku.controller.DrawHelper; import master.flame.danmaku.danmaku.model.AlphaValue; 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.IDanmakus; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.SpecialDanmaku; import master.flame.danmaku.danmaku.model.android.DanmakuContext; import master.flame.danmaku.danmaku.model.android.DanmakuFactory; import master.flame.danmaku.danmaku.model.android.Danmakus; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.util.DanmakuUtils; /** * Created by ch on 17/8/18. */ public class FakeDanmakuView extends DanmakuView implements DrawHandler.Callback { private class CustomParser extends BaseDanmakuParser { private final BaseDanmakuParser mBaseParser; private final long stTime; private final long edTime; private float mDispScaleX, mDispScaleY; private int mViewWidth; public CustomParser(BaseDanmakuParser baseParser, long stTime, long edTime) { this.mBaseParser = baseParser; this.stTime = stTime; this.edTime = edTime; } @Override protected IDanmakus parse() { final IDanmakus danmakus = new Danmakus(); IDanmakus subnew; try { subnew = this.mBaseParser.getDanmakus().subnew(this.stTime, this.edTime); } catch (Exception e) { subnew = this.mBaseParser.getDanmakus(); } if (subnew == null) { return danmakus; } subnew.forEach(new IDanmakus.Consumer() { @Override public int accept(BaseDanmaku danmaku) { long time = danmaku.getTime(); if (time < stTime) { return IDanmakus.Consumer.ACTION_CONTINUE; } else if (time > edTime) { return IDanmakus.Consumer.ACTION_BREAK; } BaseDanmaku item = mContext.mDanmakuFactory.createDanmaku(danmaku.getType(), mContext); if (item != null) { item.setTime(danmaku.getTime()); DanmakuUtils.fillText(item, danmaku.text); item.textSize = danmaku.textSize; item.textColor = danmaku.textColor; item.textShadowColor = danmaku.textShadowColor; if (danmaku instanceof SpecialDanmaku) { SpecialDanmaku sdanmaku = (SpecialDanmaku) danmaku; item.index = danmaku.index; item.duration = new Duration(sdanmaku.getDuration()); item.rotationZ = sdanmaku.rotateZ; item.rotationY = sdanmaku.rotationY; ((SpecialDanmaku) item).isQuadraticEaseOut = sdanmaku.isQuadraticEaseOut; mContext.mDanmakuFactory.fillTranslationData(item, sdanmaku.beginX, sdanmaku.beginY, sdanmaku.endX, sdanmaku.endY, sdanmaku.translationDuration, sdanmaku.translationStartDelay, mDispScaleX, mDispScaleY); mContext.mDanmakuFactory.fillAlphaData(item, sdanmaku.beginAlpha, sdanmaku.endAlpha, item.getDuration()); // mContext.mDanmakuFactory.fillLinePathData(item, points, mDispScaleX, // mDispScaleY); // FIXME return 0; // FIXME skip special danmakus } item.setTimer(mTimer); item.mFilterParam = danmaku.mFilterParam; item.filterResetFlag = danmaku.filterResetFlag; item.flags = mContext.mGlobalFlagValues; Object lock = danmakus.obtainSynchronizer(); synchronized (lock) { danmakus.addItem(item); } } return 0; } }); return danmakus; } @Override public BaseDanmakuParser setDisplayer(IDisplayer disp) { super.setDisplayer(disp); if (mBaseParser == null || mBaseParser.getDisplayer() == null) { return this; } mDispScaleX = mDispWidth / (float) mBaseParser.getDisplayer().getWidth(); mDispScaleY = mDispHeight / (float) mBaseParser.getDisplayer().getHeight(); if (mViewWidth <= 1) { mViewWidth = disp.getWidth(); } return this; } @Override protected float getViewportSizeFactor() { float scale = DanmakuFactory.COMMON_DANMAKU_DURATION * mViewWidth / DanmakuFactory.BILI_PLAYER_WIDTH; float factor = 1.1f; return mContext.mDanmakuFactory.MAX_DANMAKU_DURATION * factor / scale; } } private DanmakuTimer mTimer; public interface OnFrameAvailableListener { void onConfig(DanmakuContext config); void onFrameAvailable(long timeMills, Bitmap bitmap); void onFramesFinished(long timeMills); void onFailed(int errorCode, String msg); } private boolean mIsRelease; private OnFrameAvailableListener mOnFrameAvailableListener; private int mWidth = 0; private int mHeight = 0; private float mScale = 1f; private DanmakuTimer mOuterTimer; private long mBeginTimeMills; private long mFrameIntervalMills = 16L; private long mEndTimeMills; private Bitmap mBufferBitmap; private Canvas mBufferCanvas; private int mRetryCount = 0; private long mExpectBeginMills = 0; public FakeDanmakuView(Context context) { super(context); } public FakeDanmakuView(Context context, int width, int height, float scale) { super(context); mWidth = width; mHeight = height; mScale = scale; initBufferCanvas(width, height); } public void initBufferCanvas(int width, int height) { mBufferBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mBufferCanvas = new Canvas(mBufferBitmap); } @Override public long drawDanmakus() { if (mIsRelease) { return 0; } Canvas canvas = mBufferCanvas; if (canvas == null) { return 0; } Bitmap bufferBitmap = this.mBufferBitmap; if (bufferBitmap == null || bufferBitmap.isRecycled()) { return 0; } bufferBitmap.eraseColor(Color.TRANSPARENT); if (mClearFlag) { DrawHelper.clearCanvas(canvas); mClearFlag = false; } else { if (handler != null) { handler.draw(canvas); } } OnFrameAvailableListener onFrameAvailableListener = this.mOnFrameAvailableListener; if (onFrameAvailableListener != null) { long curr = mOuterTimer.currMillisecond; try { if (curr >= mExpectBeginMills - mFrameIntervalMills) { Bitmap bitmap; boolean recycle = false; if (mScale == 1f) { bitmap = bufferBitmap; } else { bitmap = Bitmap.createScaledBitmap(bufferBitmap, (int) (mWidth * mScale), (int) (mHeight * mScale), true); recycle = true; } onFrameAvailableListener.onFrameAvailable(curr, bitmap); if (recycle) { bitmap.recycle(); } } } catch (Exception e) { release(); onFrameAvailableListener.onFailed(101, e.getMessage()); } finally { if (curr >= mEndTimeMills) { release(); if (mTimer != null) { mTimer.update(mEndTimeMills); } onFrameAvailableListener.onFramesFinished(curr); } } } mRequestRender = false; return 2; // 固定频率 } @Override public void release() { mIsRelease = true; super.release(); mBufferBitmap = null; } @Override protected void onDraw(Canvas canvas) { } @Override public boolean isShown() { return true; } @Override public boolean isViewReady() { return true; } @Override public int getViewWidth() { return mWidth; } @Override public int getViewHeight() { return mHeight; } @Override public void prepare(BaseDanmakuParser parser, DanmakuContext config) { CustomParser newParser = new CustomParser(parser, mBeginTimeMills, mEndTimeMills); DanmakuContext configCopy; try { configCopy = (DanmakuContext) config.clone(); configCopy.resetContext(); configCopy.transparency = AlphaValue.MAX; configCopy.setDanmakuTransparency(config.transparency / (float) AlphaValue.MAX); configCopy.mGlobalFlagValues.FILTER_RESET_FLAG = config.mGlobalFlagValues.FILTER_RESET_FLAG; configCopy.setDanmakuSync(null); configCopy.unregisterAllConfigChangedCallbacks(); configCopy.mGlobalFlagValues.updateAll(); } catch (CloneNotSupportedException e) { e.printStackTrace(); configCopy = config; } configCopy.updateMethod = 1; if (mOnFrameAvailableListener != null) { mOnFrameAvailableListener.onConfig(configCopy); } super.prepare(newParser, configCopy); handler.setIdleSleep(false); handler.enableNonBlockMode(true); } public void setTimeRange(final long beginMills, final long endMills) { mExpectBeginMills = beginMills; mBeginTimeMills = Math.max(0, beginMills - 30000L); // FIXME: 17/8/23 magic code 30000L mEndTimeMills = endMills; } public void setOnFrameAvailableListener(OnFrameAvailableListener onFrameAvailableListener) { mOnFrameAvailableListener = onFrameAvailableListener; } public void getFrameAtTime(final int frameRate) { if (mRetryCount++ > 5) { release(); if (mOnFrameAvailableListener != null) { mOnFrameAvailableListener.onFailed(100, "not prepared"); } return; } if (!isPrepared()) { DrawHandler handler = this.handler; if (handler == null) { return; } handler.postDelayed(new Runnable() { @Override public void run() { getFrameAtTime(frameRate); } }, 1000L); return; } mFrameIntervalMills = 1000 / frameRate; setCallback(this); long beginMills = Math.max(0, mExpectBeginMills - getConfig().mDanmakuFactory.MAX_DANMAKU_DURATION * 3 / 2); mOuterTimer = new DanmakuTimer(beginMills); start(beginMills); } @Override public void prepared() { } @Override public void updateTimer(DanmakuTimer timer) { mTimer = timer; timer.update(mOuterTimer.currMillisecond); mOuterTimer.add(mFrameIntervalMills); timer.add(mFrameIntervalMills); } @Override public void danmakuShown(BaseDanmaku danmaku) { } @Override public void drawingFinished() { } } ================================================ FILE: DanmakuFlameMaster/src/main/java/tv/cjump/jni/DeviceUtils.java ================================================ package tv.cjump.jni; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; public class DeviceUtils { public static final String ABI_X86 = "x86"; public static final String ABI_MIPS = "mips"; public static enum ARCH { Unknown, ARM, X86, MIPS, ARM64, } private static ARCH sArch = ARCH.Unknown; // see include/​uapi/​linux/​elf-em.h private static final int EM_ARM = 40; private static final int EM_386 = 3; private static final int EM_MIPS = 8; private static final int EM_AARCH64 = 183; // /system/lib/libc.so // XXX: need a runtime check public static synchronized ARCH getMyCpuArch() { byte[] data = new byte[20]; File libc = new File(Environment.getRootDirectory(), "lib/libc.so"); if (libc.canRead()) { RandomAccessFile fp = null; try { fp = new RandomAccessFile(libc, "r"); fp.readFully(data); int machine = (data[19] << 8) | data[18]; switch (machine) { case EM_ARM: sArch = ARCH.ARM; break; case EM_386: sArch = ARCH.X86; break; case EM_MIPS: sArch = ARCH.MIPS; break; case EM_AARCH64: sArch = ARCH.ARM64; break; default: Log.e("NativeBitmapFactory", "libc.so is unknown arch: " + Integer.toHexString(machine)); break; } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fp != null) { try { fp.close(); } catch (IOException e) { e.printStackTrace(); } } } } return sArch; } public static String get_CPU_ABI() { return Build.CPU_ABI; } public static String get_CPU_ABI2() { try { Field field = Build.class.getDeclaredField("CPU_ABI2"); if (field == null) return null; Object fieldValue = field.get(null); if (!(fieldValue instanceof String)) { return null; } return (String) fieldValue; } catch (Exception e) { } return null; } public static boolean supportABI(String requestAbi) { String abi = get_CPU_ABI(); if (!TextUtils.isEmpty(abi) && abi.equalsIgnoreCase(requestAbi)) return true; String abi2 = get_CPU_ABI2(); return !TextUtils.isEmpty(abi2) && abi.equalsIgnoreCase(requestAbi); } public static boolean supportX86() { return supportABI(ABI_X86); } public static boolean supportMips() { return supportABI(ABI_MIPS); } public static boolean isARMSimulatedByX86() { ARCH arch = getMyCpuArch(); return !supportX86() && ARCH.X86.equals(arch); } public static boolean isMiBox2Device() { String manufacturer = Build.MANUFACTURER; String productName = Build.PRODUCT; return manufacturer.equalsIgnoreCase("Xiaomi") && productName.equalsIgnoreCase("dredd"); } public static boolean isMagicBoxDevice() { String manufacturer = Build.MANUFACTURER; String productName = Build.PRODUCT; return manufacturer.equalsIgnoreCase("MagicBox") && productName.equalsIgnoreCase("MagicBox"); } public static boolean isProblemBoxDevice() { return isMiBox2Device() || isMagicBoxDevice(); } public static boolean isRealARMArch() { ARCH arch = getMyCpuArch(); return (supportABI("armeabi-v7a") || supportABI("armeabi")) && ARCH.ARM.equals(arch); } public static boolean isRealX86Arch() { ARCH arch = getMyCpuArch(); return supportABI(ABI_X86) || ARCH.X86.equals(arch); } } ================================================ FILE: DanmakuFlameMaster/src/main/java/tv/cjump/jni/NativeBitmapFactory.java ================================================ package tv.cjump.jni; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.Log; import java.lang.reflect.Field; public class NativeBitmapFactory { static Field nativeIntField = null; static boolean nativeLibLoaded = false; static boolean notLoadAgain = false; public static boolean isInNativeAlloc() { return android.os.Build.VERSION.SDK_INT < 11 || (nativeLibLoaded && nativeIntField != null); } public static void loadLibs() { if (notLoadAgain) { return; } if (!(DeviceUtils.isRealARMArch() || DeviceUtils.isRealX86Arch())) { notLoadAgain = true; nativeLibLoaded = false; return; } if (nativeLibLoaded) { return; } try { if (android.os.Build.VERSION.SDK_INT >= 11 && android.os.Build.VERSION.SDK_INT < 23) { System.loadLibrary("ndkbitmap"); nativeLibLoaded = true; } else { notLoadAgain = true; nativeLibLoaded = false; } } catch (Exception e) { e.printStackTrace(); notLoadAgain = true; nativeLibLoaded = false; } catch (Error e) { e.printStackTrace(); notLoadAgain = true; nativeLibLoaded = false; } if (nativeLibLoaded) { boolean libInit = init(); if (!libInit) { release(); notLoadAgain = true; nativeLibLoaded = false; } else { initField(); boolean confirm = testLib(); if (!confirm) { // 测试so文件函数是否调用失败 release(); notLoadAgain = true; nativeLibLoaded = false; } } } Log.e("NativeBitmapFactory", "loaded" + nativeLibLoaded); } public static synchronized void releaseLibs() { boolean loaded = nativeLibLoaded; nativeIntField = null; nativeLibLoaded = false; if (loaded) { release(); } // Log.e("NativeBitmapFactory", "released"); } static void initField() { try { nativeIntField = Bitmap.Config.class.getDeclaredField("nativeInt"); nativeIntField.setAccessible(true); } catch (NoSuchFieldException e) { nativeIntField = null; e.printStackTrace(); } } @SuppressLint("NewApi") private static boolean testLib() { if (nativeIntField == null) { return false; } Bitmap bitmap = null; Canvas canvas = null; try { bitmap = createNativeBitmap(2, 2, Bitmap.Config.ARGB_8888, true); boolean result = (bitmap != null && bitmap.getWidth() == 2 && bitmap.getHeight() == 2); if (result) { if (android.os.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 (android.os.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(); bitmap = null; } } } public static int getNativeConfig(Bitmap.Config config) { try { if (nativeIntField == null) { return 0; } return nativeIntField.getInt(config); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (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 (!nativeLibLoaded || nativeIntField == null) { // Log.e("NativeBitmapFactory", "ndk bitmap create failed"); return Bitmap.createBitmap(width, height, config); } return createNativeBitmap(width, height, config, hasAlpha); } private static Bitmap createNativeBitmap(int width, int height, Config config, boolean hasAlpha) { int nativeConfig = getNativeConfig(config); // Log.e("NativeBitmapFactory", "nativeConfig:" + nativeConfig); // Log.e("NativeBitmapFactory", "create bitmap:" + bitmap); return android.os.Build.VERSION.SDK_INT == 19 ? createBitmap19(width, height, nativeConfig, hasAlpha) : createBitmap(width, height, nativeConfig, hasAlpha); } // ///////////native methods////////// 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); } ================================================ FILE: DanmakuFlameMaster/src/main/lint.xml ================================================ ================================================ FILE: DanmakuFlameMaster/src/main/proguard-project.txt ================================================ # To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: DanmakuFlameMaster/src/main/project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt # Project target. target=android-19 android.library=true source.dir=java ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: README.md ================================================ DanmakuFlameMaster ================== android上开源弹幕解析绘制引擎项目。[![Build Status](https://travis-ci.org/Bilibili/DanmakuFlameMaster.png?branch=master)](https://travis-ci.org/Bilibili/DanmakuFlameMaster) ### DFM Inside: [![bili](https://raw.github.com/ctiao/ctiao.github.io/master/images/apps/bili.png?raw=true)](https://play.google.com/store/apps/details?id=tv.danmaku.bili) - libndkbitmap.so(ndk)源码:https://github.com/Bilibili/NativeBitmapFactory - 开发交流群:314468823 (加入请注明DFM开发交流) ### Features - 使用多种方式(View/SurfaceView/TextureView)实现高效绘制 - B站xml弹幕格式解析 - 基础弹幕精确还原绘制 - 支持mode7特殊弹幕 - 多核机型优化,高效的预缓存机制 - 支持多种显示效果选项实时切换 - 实时弹幕显示支持 - 换行弹幕支持/运动弹幕支持 - 支持自定义字体 - 支持多种弹幕参数设置 - 支持多种方式的弹幕屏蔽 ### TODO: - 增加OpenGL ES绘制方式 ### Download Download the [latest version][1] or grab via Maven: ```xml com.github.ctiao dfm 0.9.25 ``` or Gradle: ```groovy repositories { jcenter() } dependencies { compile 'com.github.ctiao:DanmakuFlameMaster:0.9.25' compile 'com.github.ctiao:ndkbitmap-armv7a:0.9.21' # Other ABIs: optional compile 'com.github.ctiao:ndkbitmap-armv5:0.9.21' compile 'com.github.ctiao:ndkbitmap-x86:0.9.21' } ``` Snapshots of the development version are available in [Sonatype's snapshots repository][2]. ### License Copyright (C) 2013-2015 Chen Hui Licensed under the Apache License, Version 2.0 (the "License"); [1]:https://oss.sonatype.org/#nexus-search;gav~com.github.ctiao~dfm~~~ [2]:https://oss.sonatype.org/content/repositories/snapshots/ ================================================ FILE: Sample/build.gradle ================================================ /* * 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. */ apply plugin: 'com.android.application' dependencies { compile project(':ndkbitmap-armv5') compile project(':ndkbitmap-armv7a') compile project(':ndkbitmap-x86') compile project(':DanmakuFlameMaster') compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' // compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.6.2' } android { compileSdkVersion 25 buildToolsVersion "26.0.2" defaultConfig { minSdkVersion 9 targetSdkVersion 25 versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } } ================================================ FILE: Sample/src/main/.classpath ================================================ ================================================ FILE: Sample/src/main/.project ================================================ Sample com.android.ide.eclipse.adt.ResourceManagerBuilder com.android.ide.eclipse.adt.PreCompilerBuilder org.eclipse.jdt.core.javabuilder com.android.ide.eclipse.adt.ApkBuilder com.android.ide.eclipse.adt.AndroidNature org.eclipse.jdt.core.javanature ================================================ FILE: Sample/src/main/.settings/org.eclipse.jdt.core.prefs ================================================ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.source=1.6 ================================================ FILE: Sample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: Sample/src/main/java/com/sample/BiliDanmukuParser.java ================================================ /* * 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 com.sample; import android.graphics.Color; import android.text.TextUtils; import org.json.JSONArray; import org.json.JSONException; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; import java.io.IOException; import java.util.Locale; import master.flame.danmaku.danmaku.model.AlphaValue; import master.flame.danmaku.danmaku.model.BaseDanmaku; import master.flame.danmaku.danmaku.model.Duration; import master.flame.danmaku.danmaku.model.IDisplayer; import master.flame.danmaku.danmaku.model.SpecialDanmaku; import master.flame.danmaku.danmaku.model.android.Danmakus; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.model.android.DanmakuFactory; import master.flame.danmaku.danmaku.parser.android.AndroidFileSource; import master.flame.danmaku.danmaku.util.DanmakuUtils; import static master.flame.danmaku.danmaku.model.IDanmakus.ST_BY_TIME; public class BiliDanmukuParser extends BaseDanmakuParser { static { System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); } protected float mDispScaleX; protected float mDispScaleY; @Override public Danmakus parse() { if (mDataSource != null) { AndroidFileSource source = (AndroidFileSource) mDataSource; try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); XmlContentHandler contentHandler = new XmlContentHandler(); xmlReader.setContentHandler(contentHandler); xmlReader.parse(new InputSource(source.data())); return contentHandler.getResult(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return null; } public class XmlContentHandler extends DefaultHandler { private static final String TRUE_STRING = "true"; public Danmakus result; public BaseDanmaku item = null; public boolean completed = false; public int index = 0; public Danmakus getResult() { return result; } @Override public void startDocument() throws SAXException { result = new Danmakus(ST_BY_TIME, false, mContext.getBaseComparator()); } @Override public void endDocument() throws SAXException { completed = true; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { String tagName = localName.length() != 0 ? localName : qName; tagName = tagName.toLowerCase(Locale.getDefault()).trim(); if (tagName.equals("d")) { // 我从未见过如此厚颜无耻之猴 // 0:时间(弹幕出现时间) // 1:类型(1从右至左滚动弹幕|6从左至右滚动弹幕|5顶端固定弹幕|4底端固定弹幕|7高级弹幕|8脚本弹幕) // 2:字号 // 3:颜色 // 4:时间戳 ? // 5:弹幕池id // 6:用户hash // 7:弹幕id String pValue = attributes.getValue("p"); // parse p value to danmaku String[] values = pValue.split(","); if (values.length > 0) { long time = (long) (parseFloat(values[0]) * 1000); // 出现时间 int type = parseInteger(values[1]); // 弹幕类型 float textSize = parseFloat(values[2]); // 字体大小 int color = (int) ((0x00000000ff000000 | parseLong(values[3])) & 0x00000000ffffffff); // 颜色 // int poolType = parseInteger(values[5]); // 弹幕池类型(忽略 item = mContext.mDanmakuFactory.createDanmaku(type, mContext); if (item != null) { item.setTime(time); item.textSize = textSize * (mDispDensity - 0.6f); item.textColor = color; item.textShadowColor = color <= Color.BLACK ? Color.WHITE : Color.BLACK; } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (item != null && item.text != null) { if (item.duration != null) { String tagName = localName.length() != 0 ? localName : qName; if (tagName.equalsIgnoreCase("d")) { item.setTimer(mTimer); item.flags = mContext.mGlobalFlagValues; Object lock = result.obtainSynchronizer(); synchronized (lock) { result.addItem(item); } } } item = null; } } @Override public void characters(char[] ch, int start, int length) { if (item != null) { DanmakuUtils.fillText(item, decodeXmlString(new String(ch, start, length))); item.index = index++; // initial specail danmaku data String text = String.valueOf(item.text).trim(); if (item.getType() == BaseDanmaku.TYPE_SPECIAL && text.startsWith("[") && text.endsWith("]")) { //text = text.substring(1, text.length() - 1); String[] textArr = null;//text.split(",", -1); try { JSONArray jsonArray = new JSONArray(text); textArr = new String[jsonArray.length()]; for (int i = 0; i < textArr.length; i++) { textArr[i] = jsonArray.getString(i); } } catch (JSONException e) { e.printStackTrace(); } if (textArr == null || textArr.length < 5 || TextUtils.isEmpty(textArr[4])) { item = null; return; } DanmakuUtils.fillText(item, textArr[4]); float beginX = parseFloat(textArr[0]); float beginY = parseFloat(textArr[1]); float endX = beginX; float endY = beginY; String[] alphaArr = textArr[2].split("-"); int beginAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[0])); int endAlpha = beginAlpha; if (alphaArr.length > 1) { endAlpha = (int) (AlphaValue.MAX * parseFloat(alphaArr[1])); } long alphaDuraion = (long) (parseFloat(textArr[3]) * 1000); long translationDuration = alphaDuraion; long translationStartDelay = 0; float rotateY = 0, rotateZ = 0; if (textArr.length >= 7) { rotateZ = parseFloat(textArr[5]); rotateY = parseFloat(textArr[6]); } if (textArr.length >= 11) { endX = parseFloat(textArr[7]); endY = parseFloat(textArr[8]); if (!"".equals(textArr[9])) { translationDuration = parseInteger(textArr[9]); } if (!"".equals(textArr[10])) { translationStartDelay = (long) (parseFloat(textArr[10])); } } if (isPercentageNumber(textArr[0])) { beginX *= DanmakuFactory.BILI_PLAYER_WIDTH; } if (isPercentageNumber(textArr[1])) { beginY *= DanmakuFactory.BILI_PLAYER_HEIGHT; } if (textArr.length >= 8 && isPercentageNumber(textArr[7])) { endX *= DanmakuFactory.BILI_PLAYER_WIDTH; } if (textArr.length >= 9 && isPercentageNumber(textArr[8])) { endY *= DanmakuFactory.BILI_PLAYER_HEIGHT; } item.duration = new Duration(alphaDuraion); item.rotationZ = rotateZ; item.rotationY = rotateY; mContext.mDanmakuFactory.fillTranslationData(item, beginX, beginY, endX, endY, translationDuration, translationStartDelay, mDispScaleX, mDispScaleY); mContext.mDanmakuFactory.fillAlphaData(item, beginAlpha, endAlpha, alphaDuraion); if (textArr.length >= 12) { // 是否有描边 if (!TextUtils.isEmpty(textArr[11]) && TRUE_STRING.equalsIgnoreCase(textArr[11])) { item.textShadowColor = Color.TRANSPARENT; } } if (textArr.length >= 13) { //TODO 字体 textArr[12] } if (textArr.length >= 14) { // Linear.easeIn or Quadratic.easeOut ((SpecialDanmaku) item).isQuadraticEaseOut = ("0".equals(textArr[13])); } if (textArr.length >= 15) { // 路径数据 if (!"".equals(textArr[14])) { String motionPathString = textArr[14].substring(1); if (!TextUtils.isEmpty(motionPathString)) { String[] pointStrArray = motionPathString.split("L"); if (pointStrArray.length > 0) { float[][] points = new float[pointStrArray.length][2]; for (int i = 0; i < pointStrArray.length; i++) { String[] pointArray = pointStrArray[i].split(","); if (pointArray.length >= 2) { points[i][0] = parseFloat(pointArray[0]); points[i][1] = parseFloat(pointArray[1]); } } mContext.mDanmakuFactory.fillLinePathData(item, points, mDispScaleX, mDispScaleY); } } } } } } } private String decodeXmlString(String title) { if (title.contains("&")) { title = title.replace("&", "&"); } if (title.contains(""")) { title = title.replace(""", "\""); } if (title.contains(">")) { title = title.replace(">", ">"); } if (title.contains("<")) { title = title.replace("<", "<"); } return title; } } private boolean isPercentageNumber(String number) { //return number >= 0f && number <= 1f; return number != null && number.contains("."); } private float parseFloat(String floatStr) { try { return Float.parseFloat(floatStr); } catch (NumberFormatException e) { return 0.0f; } } private int parseInteger(String intStr) { try { return Integer.parseInt(intStr); } catch (NumberFormatException e) { return 0; } } private long parseLong(String longStr) { try { return Long.parseLong(longStr); } catch (NumberFormatException e) { return 0; } } @Override public BaseDanmakuParser setDisplayer(IDisplayer disp) { super.setDisplayer(disp); mDispScaleX = mDispWidth / DanmakuFactory.BILI_PLAYER_WIDTH; mDispScaleY = mDispHeight / DanmakuFactory.BILI_PLAYER_HEIGHT; return this; } } ================================================ FILE: Sample/src/main/java/com/sample/MainActivity.java ================================================ 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); } } } ================================================ FILE: Sample/src/main/java/com/sample/UglyViewCacheStufferSampleActivity.java ================================================ package com.sample; import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; 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 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.util.TypedValue; import android.view.Menu; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.PopupWindow; import android.widget.TextView; import android.widget.VideoView; import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.ImageSize; import com.nostra13.universalimageloader.core.assist.ViewScaleType; import com.nostra13.universalimageloader.core.imageaware.NonViewAware; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; 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.AndroidDisplayer; 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.model.android.ViewCacheStuffer; import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; import master.flame.danmaku.danmaku.parser.IDataSource; import master.flame.danmaku.danmaku.util.IOUtils; import master.flame.danmaku.danmaku.util.SystemClock; public class UglyViewCacheStufferSampleActivity 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 int mIconWidth; /** * 绘制背景(自定义弹幕样式) */ 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) { // 禁用描边绘制 } } public class MyViewHolder extends ViewCacheStuffer.ViewHolder { private final ImageView mIcon; private final TextView mText; public MyViewHolder(View itemView) { super(itemView); mIcon = (ImageView) itemView.findViewById(R.id.icon); mText = (TextView) itemView.findViewById(R.id.text); } } public static class MyImageWare extends NonViewAware { private long start; private int id; private WeakReference danmakuViewRef; private BaseDanmaku danmaku; private Bitmap bitmap; public MyImageWare(String imageUri, BaseDanmaku danmaku, int width, int height, IDanmakuView danmakuView) { this(imageUri, new ImageSize(width, height), ViewScaleType.FIT_INSIDE); if (danmaku == null) { throw new IllegalArgumentException("danmaku may not be null"); } this.danmaku = danmaku; this.id = danmaku.hashCode(); this.danmakuViewRef = new WeakReference<>(danmakuView); this.start = SystemClock.uptimeMillis(); } @Override public int getId() { return this.id; } public String getImageUri() { return this.imageUri; } private MyImageWare(ImageSize imageSize, ViewScaleType scaleType) { super(imageSize, scaleType); } private MyImageWare(String imageUri, ImageSize imageSize, ViewScaleType scaleType) { super(imageUri, imageSize, scaleType); } @Override public boolean setImageDrawable(Drawable drawable) { return super.setImageDrawable(drawable); } @Override public boolean setImageBitmap(Bitmap bitmap) { // if (this.danmaku.isTimeOut() || this.danmaku.isFiltered()) { // return true; // } if (this.danmaku.text.toString().contains("textview")) { Log.e("DFM", (SystemClock.uptimeMillis() - this.start) + "ms=====> inside" + danmaku.tag + ":" + danmaku.getActualTime() + ",url: bitmap" + (bitmap == null)); } this.bitmap = bitmap; IDanmakuView danmakuView = danmakuViewRef.get(); if (danmakuView != null) { danmakuView.invalidateDanmaku(danmaku, true); } return true; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create global configuration and initialize ImageLoader with this config ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).memoryCache(new LruMemoryCache(2 * 1024 * 1024)) .memoryCacheSize(2 * 1024 * 1024) .memoryCacheSizePercentage(13).build(); // default ImageLoader.getInstance().init(config); 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(); mIconWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, getResources().getDisplayMetrics()); mContext.setDanmakuBold(true); mContext.setDanmakuStyle(IDisplayer.DANMAKU_STYLE_STROKEN, 3).setDuplicateMergingEnabled(false).setScrollSpeedFactor(1.2f).setScaleTextSize(1.2f) .setCacheStuffer(new ViewCacheStuffer() { @Override public MyViewHolder onCreateViewHolder(int viewType) { Log.e("DFM", "onCreateViewHolder:" + viewType); return new MyViewHolder(View.inflate(getApplicationContext(), R.layout.layout_view_cache, null)); } @Override public void onBindViewHolder(int viewType, MyViewHolder viewHolder, BaseDanmaku danmaku, AndroidDisplayer.DisplayerConfig displayerConfig, TextPaint paint) { if (paint != null) viewHolder.mText.getPaint().set(paint); viewHolder.mText.setText(danmaku.text); viewHolder.mText.setTextColor(danmaku.textColor); viewHolder.mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, danmaku.textSize); Bitmap bitmap = null; MyImageWare imageWare = (MyImageWare) danmaku.tag; if (imageWare != null) { bitmap = imageWare.bitmap; if (danmaku.text.toString().contains("textview")) { Log.e("DFM", "onBindViewHolder======> bitmap:" + (bitmap == null) + " " + danmaku.tag + "url:" + imageWare.getImageUri()); } } if (bitmap != null) { viewHolder.mIcon.setImageBitmap(bitmap); if (danmaku.text.toString().contains("textview")) { Log.e("DFM", "onBindViewHolder======>" + danmaku.tag + "url:" + imageWare.getImageUri()); } } else { viewHolder.mIcon.setImageResource(R.drawable.ic_launcher); } } @Override public void releaseResource(BaseDanmaku danmaku) { MyImageWare imageWare = (MyImageWare) danmaku.tag; if (imageWare != null) { ImageLoader.getInstance().cancelDisplayTask(imageWare); } danmaku.setTag(null); Log.e("DFM", "releaseResource url:" + danmaku.text); } String[] avatars = { "http://i0.hdslb.com/bfs/face/e13fcb94342c325debb2d3a1d9e503ac4f083514.jpg@45w_45h.webp", "http://i0.hdslb.com/bfs/bangumi/2558e1341d2e934a7e06bb7d92551fef5c82c172.jpg@72w_72h.webp", "http://i0.hdslb.com/bfs/face/128edefeef7ce9cfc443a2489d8a1c7d44d88b80.jpg@72w_72h.webp"}; @Override public void prepare(BaseDanmaku danmaku, boolean fromWorkerThread) { if (danmaku.isTimeOut()) { return; } MyImageWare imageWare = (MyImageWare) danmaku.tag; if (imageWare == null) { String avatar = avatars[danmaku.index % avatars.length]; imageWare = new MyImageWare(avatar, danmaku, mIconWidth, mIconWidth, mDanmakuView); danmaku.setTag(imageWare); } if (danmaku.text.toString().contains("textview")) { Log.e("DFM", "onAsyncLoadResource======>" + danmaku.tag + "url:" + imageWare.getImageUri()); } ImageLoader.getInstance().displayImage(imageWare.getImageUri(), imageWare); } }, null) // 图文混排使用SpannedCacheStuffer // .setCacheStuffer(new BackgroundCacheStuffer()) // 绘制背景使用BackgroundCacheStuffer .setMaximumLines(maxLinesPair) .preventOverlapping(overlappingEnablePair); 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; } ImageLoader.getInstance().destroy(); } @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 = "这是文本,图片在textview左侧"; danmaku.setTag(new MyImageWare("http://i0.hdslb.com/bfs/face/084bd13eb5dc51a64674085bb28e958ecd5addd0.jpg@180w_180h.webp", danmaku, mIconWidth, mIconWidth, mDanmakuView)); 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; } } ================================================ FILE: Sample/src/main/project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt # Project target. target=android-17 android.library.reference.1=../../../DanmakuFlameMaster/src/main ================================================ FILE: Sample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: Sample/src/main/res/layout/layout_view_cache.xml ================================================ ================================================ FILE: Sample/src/main/res/layout/media_controller.xml ================================================