diff --git a/app/src/main/java/io/github/peerless2012/ass/demo/MainActivity.kt b/app/src/main/java/io/github/peerless2012/ass/demo/MainActivity.kt index f82d244..1f7ebf0 100644 --- a/app/src/main/java/io/github/peerless2012/ass/demo/MainActivity.kt +++ b/app/src/main/java/io/github/peerless2012/ass/demo/MainActivity.kt @@ -27,7 +27,7 @@ import androidx.core.net.toUri class MainActivity : AppCompatActivity() { - private var url = "http://192.168.0.254:80/files/c.mkv" + private var url = "http://192.168.3.6:8080/files/c.mkv" private lateinit var player: ExoPlayer @@ -50,12 +50,12 @@ class MainActivity : AppCompatActivity() { player = ExoPlayer.Builder(this) .buildWithAssSupport( this, - AssRenderType.OVERLAY, + AssRenderType.OVERLAY_OPEN_GL, playerView.subtitleView ) playerView.player = player val enConfig = MediaItem.SubtitleConfiguration - .Builder(Uri.parse("http://192.168.0.254:80/files/e.ass")) + .Builder(Uri.parse("http://192.168.3.6:8080/files/e.ass")) .setMimeType(MimeTypes.TEXT_SSA) .setLanguage("en") .setLabel("External ass en") @@ -63,14 +63,14 @@ class MainActivity : AppCompatActivity() { .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .build() val jpConfig = MediaItem.SubtitleConfiguration - .Builder(Uri.parse("http://192.168.0.254:80/files/f-jp.ass")) + .Builder(Uri.parse("http://192.168.3.6:8080/files/f-jp.ass")) .setMimeType(MimeTypes.TEXT_SSA) .setLanguage("jp") .setLabel("External ass jp") .setId("130") .build() val zhConfig = MediaItem.SubtitleConfiguration - .Builder(Uri.parse("http://192.168.0.254:80/files/f-zh.ass")) + .Builder(Uri.parse("http://192.168.3.6:8080/files/f-zh.ass")) .setMimeType(MimeTypes.TEXT_SSA) .setLanguage("zh") .setLabel("External ass zh") diff --git a/gradle.properties b/gradle.properties index 9ec746b..cdb0858 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ android.nonTransitiveRClass=true # maven publish GROUP=io.github.peerless2012 -VERSION_NAME=0.3.0 +VERSION_NAME=0.4.0-alpha01 POM_URL=https://github.com/peerless2012/libass-android POM_INCEPTION_YEAR=2025 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 471fe2e..1a1982c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.0" +agp = "8.13.1" kotlin = "2.2.0" coreKtx = "1.16.0" annotation = "1.9.1" diff --git a/lib_ass_kt/src/main/cpp/AssKt.c b/lib_ass_kt/src/main/cpp/AssKt.c index 9b911cf..6110a16 100644 --- a/lib_ass_kt/src/main/cpp/AssKt.c +++ b/lib_ass_kt/src/main/cpp/AssKt.c @@ -9,6 +9,8 @@ #include #include "ass/ass.h" #include "fontconfig/fontconfig.h" +#include "GLES2/gl2.h" +#include "GLES2/gl2ext.h" #define LOG_TAG "SubtitleRenderer" @@ -265,6 +267,24 @@ jobject createAlphaBitmap(JNIEnv* env, const ASS_Image* image) { return bitmap; } +jint createTexture(JNIEnv* env, const ASS_Image* image) { + GLuint texture; + glGenTextures(1, &texture); + if (texture <= 0) return 0; + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, image->stride); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, image->w, image->h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, image->bitmap); + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); + glBindTexture(GL_TEXTURE_2D, 0); + return texture; +} + static int count_ass_images(ASS_Image *images) { int count = 0; for (ASS_Image *img = images; img != NULL; img = img->next) { @@ -273,7 +293,7 @@ static int count_ass_images(ASS_Image *images) { return count; } -jobject nativeAssRenderFrame(JNIEnv* env, jclass clazz, jlong render, jlong track, jlong time, jboolean onlyAlpha) { +jobject nativeAssRenderFrame(JNIEnv* env, jclass clazz, jlong render, jlong track, jlong time, jint type) { int changed; ASS_Image *image = ass_render_frame((ASS_Renderer *) render, (ASS_Track *) track, time, &changed); if (image == NULL) { @@ -289,7 +309,7 @@ jobject nativeAssRenderFrame(JNIEnv* env, jclass clazz, jlong render, jlong trac int size = count_ass_images(image); jclass assTexClass = (*env)->FindClass(env, "io/github/peerless2012/ass/AssTex"); - jmethodID assTexConstructor = (*env)->GetMethodID(env, assTexClass, "", "(IIILandroid/graphics/Bitmap;)V"); + jmethodID assTexConstructor = (*env)->GetMethodID(env, assTexClass, "", "(IIIIILandroid/graphics/Bitmap;I)V"); jobjectArray assTexArr = (*env)->NewObjectArray(env, size, assTexClass, NULL); if (assTexArr == NULL) { @@ -299,13 +319,18 @@ jobject nativeAssRenderFrame(JNIEnv* env, jclass clazz, jlong render, jlong trac int index = 0; for (ASS_Image *img = image; img != NULL; img = img->next) { jobject bitmap = NULL; + jint tex = 0; if (img->w > 0 && img->h > 0) { - bitmap = onlyAlpha ? createAlphaBitmap(env, img) : createBitmap(env, img); + if (type == 0) { + bitmap = createBitmap(env, img); + } else if (type == 1) { + bitmap = createAlphaBitmap(env, img); + } else if (type == 2) { + tex = createTexture(env, img); + } } int32_t color = (int32_t) img->color; - - jobject assTexObject = (*env)->NewObject(env, assTexClass, assTexConstructor, img->dst_x, img->dst_y, color, bitmap); - + jobject assTexObject = (*env)->NewObject(env, assTexClass, assTexConstructor, img->dst_x, img->dst_y, img->w, img->h, color, bitmap, tex); (*env)->SetObjectArrayElement(env, assTexArr, index, assTexObject); (*env)->DeleteLocalRef(env, assTexObject); if (bitmap != NULL) { @@ -329,7 +354,7 @@ static JNINativeMethod renderMethodTable[] = { {"nativeAssRenderSetCacheLimit", "(JII)V", (void*)nativeAssRenderSetCacheLimit}, {"nativeAssRenderSetStorageSize", "(JII)V", (void*) nativeAssRenderSetStorageSize}, {"nativeAssRenderSetFrameSize", "(JII)V", (void*)nativeAssRenderSetFrameSize}, - {"nativeAssRenderFrame", "(JJJZ)Lio/github/peerless2012/ass/AssFrame;", (void*) nativeAssRenderFrame}, + {"nativeAssRenderFrame", "(JJJI)Lio/github/peerless2012/ass/AssFrame;", (void*) nativeAssRenderFrame}, {"nativeAssRenderDeinit", "(J)V", (void*)nativeAssRenderDeinit}, }; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { diff --git a/lib_ass_kt/src/main/cpp/CMakeLists.txt b/lib_ass_kt/src/main/cpp/CMakeLists.txt index a902ab9..da87fac 100644 --- a/lib_ass_kt/src/main/cpp/CMakeLists.txt +++ b/lib_ass_kt/src/main/cpp/CMakeLists.txt @@ -32,5 +32,6 @@ target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE lib_ass::ass android jnigraphics + GLESv2 z log) \ No newline at end of file diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt index c2fe7c6..4edc60c 100644 --- a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssRender.kt @@ -27,7 +27,7 @@ class AssRender(nativeAss: Long) { external fun nativeAssRenderSetFrameSize(render: Long, width: Int, height: Int) @JvmStatic - external fun nativeAssRenderFrame(render: Long, track: Long, time: Long, onlyAlpha: Boolean): AssFrame? + external fun nativeAssRenderFrame(render: Long, track: Long, time: Long, type: Int): AssFrame? @JvmStatic external fun nativeAssRenderDeinit(render: Long) @@ -57,8 +57,8 @@ class AssRender(nativeAss: Long) { nativeAssRenderSetFrameSize(nativeRender, width, height) } - public fun renderFrame(time: Long, onlyAlpha: Boolean): AssFrame? { - return track?.let { nativeAssRenderFrame(nativeRender, it.nativeAssTrack, time, onlyAlpha) } + public fun renderFrame(time: Long, type: AssTexType): AssFrame? { + return track?.let { nativeAssRenderFrame(nativeRender, it.nativeAssTrack, time, type.ordinal) } } protected fun finalize() { diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTex.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTex.kt index cc5f71a..09f39e1 100644 --- a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTex.kt +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTex.kt @@ -9,4 +9,4 @@ import android.graphics.Bitmap * @Version V1.0 * @Description */ -data class AssTex(val x: Int, val y: Int, val color: Int, val bitmap: Bitmap? = null) +data class AssTex(val x: Int, val y: Int, val w: Int, val h: Int, val color: Int, val bitmap: Bitmap? = null, val tex: Int = 0) \ No newline at end of file diff --git a/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTexType.kt b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTexType.kt new file mode 100644 index 0000000..f0a6c9d --- /dev/null +++ b/lib_ass_kt/src/main/java/io/github/peerless2012/ass/AssTexType.kt @@ -0,0 +1,11 @@ +package io.github.peerless2012.ass + +enum class AssTexType { + + BITMAP_RGBA, + + BITMAP_ALPHA, + + TEXTURE + +} \ No newline at end of file diff --git a/lib_ass_media/README.md b/lib_ass_media/README.md index 8aac6a1..bfad0e1 100644 --- a/lib_ass_media/README.md +++ b/lib_ass_media/README.md @@ -7,33 +7,50 @@ App use media3 can use this module to add ass for your player. There are three ways to render ass subtitle. Which is defined in `AssRenderType`. -| Type | Feature | Anim | HDR/DV | -| :----: | :----: | :----: | :----: | -| LEGACY | SubtitleView & Cue | ❌ | ✅ | -| CANVAS | Effect | ✅ | ❓ | -| OPEN_GL | Effect | ✅ | ❓ | +| Type | Feature | Anim | HDR/DV | Block render/UI | +| :----: | :----: | :----: | :----: | :----: | +| CUES | SubtitleView & Cue | ❌ | ✅ | ❌ | +| EFFECTS_CANVAS | Effect | ✅ | ❓ | ✅ | +| EFFECTS_OPEN_GL | Effect | ✅ | ❓ | ✅ | +| OVERLAY_CANVAS | Overlay | ✅ | ✅ | ✅ | +| OVERLAY_OPEN_GL | Overlay | ✅ | ✅ | ❌ | * [OverlayShaderProgram does not support HDR colors yet](https://github.com/androidx/media/issues/723) * [Why does TextOverLay support hdr, but Bitmap not support?](https://github.com/androidx/media/issues/2383) -### 1. LEGACY +### 1. CUES The ass/ssa subtitle will be parsed and transcode to bytes, and decode to bitmap when render. This type not support dynamic feature, because all subtitle and it time is static. But since the subtitle is transcode, it will not cost too much time when render. All work is done in parse thread. -### 2. CANVAS +### 2. EFFECTS_CANVAS The ass/ssa subtitle will be cal and render at runtime use media3 effect feature, and this will support all dynamic features. And this need to create a screen size offscreen bitmap to render the libass bitmap pieces. But when the dynamic feature is too complex, and libass will cost too much time to cal, the render will be blocked. -### 3. OPEN_GL -Just like `CANVAS`, but use OpenGL to render. and the offscreen tex is create to render the bitmap pieces. +### 3. EFFECTS_OPEN_GL +Just like `EFFECTS_CANVAS`, but use OpenGL to render. and the offscreen tex is create to render the bitmap pieces. + +Due to test, the `EFFECTS_OPEN_GL` will save 1/3 time when render. + +### 4. OVERLAY_CANVAS +The ass/ssa subtitle will be cal at runtime, and add a `Overlay` widget in `SubtitleView` to render subtitle. + +The `libass` render result will copy to bitmap, and draw in `Canvas`. + +It will block UI thread when rendering. + +### 4. OVERLAY_OPEN_GL +Just like `OVERLAY_CANVAS`, but the `libass` render result will pass to `OpenGL` texture, and avoid create tmp bitmap. + +It will save half memory than `OVERLAY_CANVAS`. + +And the `libass` render and `OpenGL` draw on another separate thread, it will not block the UI thread like `OVERLAY_CANVAS`. -Due to test, the `OPEN_GL` will save 1/3 time when render. ## How to use 1. Add MavenCenter to your project diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt index aca7582..328694a 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/AssHandler.kt @@ -19,8 +19,6 @@ import io.github.peerless2012.ass.Ass import io.github.peerless2012.ass.media.parser.AssHeaderParser import io.github.peerless2012.ass.media.render.AssOverlayManager import io.github.peerless2012.ass.media.type.AssRenderType -import kotlin.math.floor -import kotlin.math.roundToInt /** * Handles ASS subtitle rendering and integration with ExoPlayer. @@ -98,8 +96,8 @@ class AssHandler(val renderType: AssRenderType) : Listener { fun init(player: ExoPlayer) { player.addListener(this) handler = Handler(player.applicationLooper) - if (renderType == AssRenderType.CANVAS || renderType == AssRenderType.OPEN_GL) { - overlayManager = AssOverlayManager(this, player, renderType == AssRenderType.OPEN_GL) + if (renderType == AssRenderType.EFFECTS_CANVAS || renderType == AssRenderType.EFFECTS_OPEN_GL) { + overlayManager = AssOverlayManager(this, player, renderType == AssRenderType.EFFECTS_OPEN_GL) } } @@ -162,7 +160,7 @@ class AssHandler(val renderType: AssRenderType) : Listener { this.track = track val render = requireNotNull(render) render.setStorageSize(videoSize.width, videoSize.height) - if (renderType == AssRenderType.OVERLAY) { + if (renderType == AssRenderType.OVERLAY_CANVAS || renderType == AssRenderType.OVERLAY_OPEN_GL) { render.setFrameSize(surfaceSize.width, surfaceSize.height) } else { render.setFrameSize(videoSize.width, videoSize.height) @@ -186,7 +184,7 @@ class AssHandler(val renderType: AssRenderType) : Listener { Log.i("AssHandler", "onSurfaceSizeChanged: width = $width, height = $height") if (surfaceSize.width == width && surfaceSize.height == height) return surfaceSize = Size(width, height) - if (renderType == AssRenderType.OVERLAY && surfaceSize.isValid) { + if ((renderType == AssRenderType.OVERLAY_CANVAS || renderType == AssRenderType.OVERLAY_OPEN_GL) && surfaceSize.isValid) { render?.setFrameSize(surfaceSize.width, surfaceSize.height) } } @@ -229,7 +227,7 @@ class AssHandler(val renderType: AssRenderType) : Listener { val track = ass.createTrack() if (format.initializationData.size > 0) { - val header = AssHeaderParser.parse(format, renderType != AssRenderType.LEGACY) + val header = AssHeaderParser.parse(format, renderType != AssRenderType.CUES) track.readBuffer(header) } availableTracks[format.id!!] = track @@ -252,7 +250,7 @@ class AssHandler(val renderType: AssRenderType) : Listener { if (videoSize.isValid) { render.setFrameSize(videoSize.width, videoSize.height) } - if (renderType == AssRenderType.OVERLAY) { + if (renderType == AssRenderType.OVERLAY_CANVAS || renderType == AssRenderType.OVERLAY_OPEN_GL) { if (surfaceSize.isValid) { render.setFrameSize(surfaceSize.width, surfaceSize.height) } diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssExecutor.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssExecutor.kt index 94a5b29..297517b 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssExecutor.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssExecutor.kt @@ -2,6 +2,7 @@ package io.github.peerless2012.ass.media.executor import io.github.peerless2012.ass.AssFrame import io.github.peerless2012.ass.AssRender +import io.github.peerless2012.ass.AssTexType import java.util.concurrent.ExecutorCompletionService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -23,7 +24,7 @@ class AssExecutor(private val render: AssRender) { private val task = AssTask(render) - public fun renderFrame(presentationTimeUs: Long): AssFrame? { + public fun renderFrame(presentationTimeUs: Long, type: AssTexType): AssFrame? { var assFrame: AssFrame? = null if (executorBusy) { // render thread is busy, keep last content @@ -32,7 +33,7 @@ class AssExecutor(private val render: AssRender) { // submit render task val future = executorService.submit{ executorBusy = true - lastFrame = render.renderFrame(presentationTimeUs / 1000, true) + lastFrame = render.renderFrame(presentationTimeUs / 1000, type) executorBusy = false lastFrame } @@ -55,13 +56,14 @@ class AssExecutor(private val render: AssRender) { return assFrame } - public fun asyncRenderFrame(presentationTimeUs: Long, callback: (AssFrame?) -> Unit) { + public fun asyncRenderFrame(presentationTimeUs: Long, type: AssTexType, callback: (AssFrame?) -> Unit) { if (task.executorBusy) { // render thread is busy, keep last content callback.invoke(assFrameNotChange) } else { task.presentationTimeUs = presentationTimeUs task.callback = callback + task.type = type // execute render task executor.execute(task) } diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssTask.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssTask.kt index bf893bf..d563216 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssTask.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/executor/AssTask.kt @@ -2,6 +2,7 @@ package io.github.peerless2012.ass.media.executor import io.github.peerless2012.ass.AssFrame import io.github.peerless2012.ass.AssRender +import io.github.peerless2012.ass.AssTexType /** * @Author peerless2012 @@ -16,6 +17,8 @@ class AssTask(private val render: AssRender) : Runnable { var presentationTimeUs: Long = 0 + var type: AssTexType = AssTexType.BITMAP_ALPHA + var callback: ((AssFrame?) -> Unit)? = null private var lastFrame: AssFrame? = null @@ -24,7 +27,7 @@ class AssTask(private val render: AssRender) : Runnable { executorBusy = true var result: AssFrame? = null try { - result = render.renderFrame(presentationTimeUs / 1000, true) + result = render.renderFrame(presentationTimeUs / 1000, type) lastFrame = result } catch (e: Exception) { result = null diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/extractor/AssMatroskaExtractor.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/extractor/AssMatroskaExtractor.kt index 0531f79..ccf14bc 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/extractor/AssMatroskaExtractor.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/extractor/AssMatroskaExtractor.kt @@ -41,7 +41,7 @@ class AssMatroskaExtractor( override fun startMasterElement(id: Int, contentPosition: Long, contentSize: Long) { when (id) { ID_EBML -> { - if (assHandler.renderType != AssRenderType.LEGACY) { + if (assHandler.renderType != AssRenderType.CUES) { val currentExtractor = extractorOutput.get(this) as ExtractorOutput if (currentExtractor !is AssSubtitleExtractorOutput) { extractorOutput.set( diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/kt/AssPlayer.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/kt/AssPlayer.kt index 5428953..d096250 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/kt/AssPlayer.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/kt/AssPlayer.kt @@ -23,7 +23,7 @@ import io.github.peerless2012.ass.media.widget.AssSubtitleView @OptIn(UnstableApi::class) fun ExoPlayer.Builder.buildWithAssSupport( context: Context, - renderType: AssRenderType = AssRenderType.LEGACY, + renderType: AssRenderType = AssRenderType.CUES, subtitleView: SubtitleView? = null, dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(context), extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory(), @@ -45,7 +45,7 @@ fun ExoPlayer.Builder.buildWithAssSupport( .setRenderersFactory(renderersFactory.withAssSupport(assHandler)) .build() - if (renderType === AssRenderType.OVERLAY) { + if (renderType === AssRenderType.OVERLAY_CANVAS || renderType === AssRenderType.OVERLAY_OPEN_GL) { subtitleView?.withAssSupport(assHandler) } diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParser.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParser.kt index d937cb0..16cf9ad 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParser.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParser.kt @@ -6,6 +6,7 @@ import androidx.media3.common.util.Consumer import androidx.media3.common.util.UnstableApi import androidx.media3.extractor.text.CuesWithTiming import androidx.media3.extractor.text.SubtitleParser +import io.github.peerless2012.ass.AssTexType import io.github.peerless2012.ass.media.AssHandler import io.github.peerless2012.ass.AssTrack import io.github.peerless2012.ass.media.type.AssRenderType @@ -27,7 +28,7 @@ abstract class AssSubtitleParser( output: Consumer ) { onParse(data, offset, length) - if (assHandler.renderType != AssRenderType.LEGACY) { + if (assHandler.renderType != AssRenderType.CUES) { return } val events = track.getEvents() @@ -46,7 +47,7 @@ abstract class AssSubtitleParser( } val cues = mutableListOf() - val frames = assHandler.render?.renderFrame(event.start + fadeIn, false) + val frames = assHandler.render?.renderFrame(event.start + fadeIn, AssTexType.BITMAP_RGBA) frames?.images?.let { texts -> texts.forEach { tex -> tex.bitmap?.let { bitmap -> diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParserFactory.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParserFactory.kt index ba0da5b..b3bc686 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParserFactory.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/parser/AssSubtitleParserFactory.kt @@ -34,7 +34,7 @@ class AssSubtitleParserFactory(private val assHandler: AssHandler): SubtitlePars .contentEquals(format.containerMimeType) val track = assHandler.createTrack(format) if (embeddedSubtitles) { - if (assHandler.renderType != AssRenderType.LEGACY) { + if (assHandler.renderType != AssRenderType.CUES) { AssNoOpSubtitleParser() } else { AssSegmentSubtitleParser(assHandler, track) diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssCanvasOverlay.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssCanvasOverlay.kt index 8103c81..af84fd6 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssCanvasOverlay.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssCanvasOverlay.kt @@ -11,6 +11,7 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.effect.CanvasOverlay import io.github.peerless2012.ass.AssFrame import io.github.peerless2012.ass.AssRender +import io.github.peerless2012.ass.AssTexType import io.github.peerless2012.ass.media.AssHandler import io.github.peerless2012.ass.media.executor.AssExecutor @@ -37,7 +38,7 @@ class AssCanvasOverlay(private val handler: AssHandler, private val render: AssR } else { presentationTimeUs } - val assFrame: AssFrame? = executor.renderFrame(timeUs) + val assFrame: AssFrame? = executor.renderFrame(timeUs, AssTexType.BITMAP_ALPHA) if (assFrame != null && assFrame.changed == 0) { return diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssTexOverlay.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssTexOverlay.kt index eedd818..9d8f517 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssTexOverlay.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/render/AssTexOverlay.kt @@ -8,6 +8,7 @@ import androidx.media3.common.util.Size import androidx.media3.common.util.UnstableApi import androidx.media3.effect.TextureOverlay import io.github.peerless2012.ass.AssRender +import io.github.peerless2012.ass.AssTexType import io.github.peerless2012.ass.media.AssHandler import io.github.peerless2012.ass.media.executor.AssExecutor import java.nio.ByteBuffer @@ -83,7 +84,7 @@ class AssTexOverlay(private val handler: AssHandler, private val render: AssRend } else { presentationTimeUs } - val assFrame = executor.renderFrame(timeUs) + val assFrame = executor.renderFrame(timeUs, AssTexType.BITMAP_ALPHA) // if content not change, just return the tex if (assFrame != null && assFrame.changed == 0) { diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/type/AssRenderType.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/type/AssRenderType.kt index 20f5640..ef050ca 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/type/AssRenderType.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/type/AssRenderType.kt @@ -8,22 +8,27 @@ enum class AssRenderType { /** * Use SubtitleView render. */ - LEGACY, + CUES, /** * Use Effect(Powered by canvas) */ @Deprecated("Use OVERLAY instead.") - CANVAS, + EFFECTS_CANVAS, /** * Use Effect(Powered by OpenGL) */ @Deprecated("Use OVERLAY instead.") - OPEN_GL, + EFFECTS_OPEN_GL, /** - * Use Widget overlay. + * Use Widget overlay(Powered by Canvas). */ - OVERLAY + OVERLAY_CANVAS, + + /** + * Use Widget overlay(Powered by OPEN GL). + */ + OVERLAY_OPEN_GL, } \ No newline at end of file diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleCanvasView.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleCanvasView.kt new file mode 100644 index 0000000..8b402c3 --- /dev/null +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleCanvasView.kt @@ -0,0 +1,108 @@ +package io.github.peerless2012.ass.media.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.util.AttributeSet +import android.view.View +import io.github.peerless2012.ass.AssFrame +import io.github.peerless2012.ass.AssTexType +import io.github.peerless2012.ass.media.AssHandler +import io.github.peerless2012.ass.media.executor.AssExecutor + +/** + * @Author peerless2012 + * @Email peerless2012@126.com + * @DateTime 12/02/25 9:58 PM + * @Version V1.0 + * @Description + */ +class AssSubtitleCanvasView : View, AssSubtitleRender { + + private val assHandler: AssHandler + + private val paint = Paint().apply { + xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER) + } + + private var assExecutor: AssExecutor? = null + + private var assFrame: AssFrame? = null + + // Use a local param, avoid create each time. + private val invalidateCallback = Runnable { invalidate() } + + // Use a local param, avoid create each time. + private val assRenderCallback: (AssFrame?) -> Unit = assRenderCallback@{ assFrame -> + // Not change + if (assFrame != null && assFrame.changed == 0) { + return@assRenderCallback + } + // prepare to draw + assFrame?.images?.forEach { + it.bitmap?.prepareToDraw() + } + this.assFrame = assFrame + handler.post(invalidateCallback) + } + + constructor(context: Context, assHandler: AssHandler) : this(context, null, assHandler) + + constructor(context: Context, attrs: AttributeSet?, assHandler: AssHandler) : this( + context, + attrs, + 0, + assHandler + ) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, assHandler: AssHandler) : + super(context, attrs, defStyleAttr) { + setWillNotDraw(false) + this.assHandler = assHandler + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + assHandler.render?.let { + assExecutor = AssExecutor(it) + } + assHandler.renderCallback = { assRender -> + assExecutor?.shutdown() + assExecutor = null + if (assRender != null) { + assExecutor = AssExecutor(assRender) + } + } + + } + + override fun requestRender(timestampNanos: Long) { + assExecutor?.asyncRenderFrame(timestampNanos, AssTexType.BITMAP_ALPHA, assRenderCallback) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + assFrame?.images?.let { frames -> + frames.forEach { frame -> + frame.bitmap?.let { bitmap -> + val r = frame.color shr 24 and 0xFF + val g = frame.color shr 16 and 0xFF + val b = frame.color shr 8 and 0xFF + val a = 0xFF - frame.color and 0xFF + val color = (a shl 24) or (r shl 16) or (g shl 8) or b + paint.color = color + canvas.drawBitmap(bitmap, frame.x.toFloat(), frame.y.toFloat(), paint) + } + } + } + } + + override fun onDetachedFromWindow() { + assHandler.renderCallback = null + assExecutor?.shutdown() + assExecutor = null + super.onDetachedFromWindow() + } +} \ No newline at end of file diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleRender.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleRender.kt new file mode 100644 index 0000000..287792a --- /dev/null +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleRender.kt @@ -0,0 +1,7 @@ +package io.github.peerless2012.ass.media.widget + +interface AssSubtitleRender { + + fun requestRender(timestampNanos: Long) + +} \ No newline at end of file diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleTextureView.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleTextureView.kt new file mode 100644 index 0000000..fd785a4 --- /dev/null +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleTextureView.kt @@ -0,0 +1,349 @@ +package io.github.peerless2012.ass.media.widget + +import android.content.Context +import android.graphics.SurfaceTexture +import android.opengl.EGL14 +import android.opengl.EGLContext +import android.opengl.EGLDisplay +import android.opengl.EGLSurface +import android.opengl.GLES20 +import android.os.Handler +import android.os.HandlerThread +import android.os.Message +import android.util.AttributeSet +import android.util.Log +import android.view.TextureView +import androidx.annotation.WorkerThread +import androidx.media3.common.C +import androidx.media3.common.util.GlProgram +import androidx.media3.common.util.GlUtil +import androidx.media3.common.util.Size +import androidx.media3.common.util.UnstableApi +import io.github.peerless2012.ass.AssTexType +import io.github.peerless2012.ass.media.AssHandler +import java.nio.ByteBuffer +import java.nio.ByteOrder + +/** + * @Author peerless2012 + * @Email peerless2012@126.com + * @DateTime 12/02/25 9:59 PM + * @Version V1.0 + * @Description + */ +@UnstableApi +class AssSubtitleTextureView : TextureView, AssSubtitleRender, TextureView.SurfaceTextureListener { + + private val assHandler: AssHandler + + constructor(context: Context, assHandler: AssHandler) : this(context, null, assHandler) + + constructor(context: Context, attrs: AttributeSet?, assHandler: AssHandler) : this( + context, + attrs, + 0, + assHandler + ) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, assHandler: AssHandler) : + super(context, attrs, defStyleAttr) { + this.assHandler = assHandler + isOpaque = false + surfaceTextureListener = this + } + + interface Renderer { + @WorkerThread + fun onSurfaceCreated() + @WorkerThread + fun onSurfaceChanged(width: Int, height: Int) + @WorkerThread + fun onDrawFrame(timestampNanos: Long): Boolean + @WorkerThread + fun onSurfaceDestroyed() + } + + private var renderThread: AssRenderThread? = null + + override fun requestRender(timestampNanos: Long) { + renderThread?.requestRender(timestampNanos) + } + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + AssRenderThread(surface, width, height, AssRender(assHandler)).also { + renderThread = it + it.start() + } + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) { + renderThread?.onSurfaceSizeChanged(width, height) + } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { + renderThread?.release() + renderThread = null + return true + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { + } + + companion object { + + } + + private class AssRenderThread( + private val surfaceTexture: SurfaceTexture, + private var width: Int, + private var height: Int, + private val render: Renderer + ) : HandlerThread("AssTexRenderThread"), Handler.Callback { + + private val TAG = "AssTexRenderThread" + + private val MSG_INIT = 1 + private val MSG_DRAW = 2 + private val MSG_SURFACE_SIZE_CHANGED = 3 + private val MSG_RELEASE = 4 + + private lateinit var handler: Handler + + private var eglDisplay: EGLDisplay = EGL14.EGL_NO_DISPLAY + private var eglContext: EGLContext = EGL14.EGL_NO_CONTEXT + private var eglSurface: EGLSurface = EGL14.EGL_NO_SURFACE + + override fun start() { + super.start() + handler = Handler(looper, this) + handler.sendEmptyMessage(MSG_INIT) + } + + fun requestRender(timestampNanos: Long) { + handler.removeMessages(MSG_DRAW) + handler.obtainMessage(MSG_DRAW, timestampNanos).sendToTarget() + } + + fun onSurfaceSizeChanged(width: Int, height: Int) { + this.width = width + handler.sendEmptyMessage(MSG_SURFACE_SIZE_CHANGED) + } + + fun release() { + handler.sendEmptyMessage(MSG_RELEASE) + } + + override fun handleMessage(msg: Message): Boolean { + try { + when (msg.what) { + MSG_INIT -> initInternal() + MSG_DRAW -> drawInternal(msg.obj as Long) + MSG_SURFACE_SIZE_CHANGED -> sizeChangedInternal(width, height) + MSG_RELEASE -> releaseInternal() + } + } catch (e: Exception) { + Log.e(TAG, "GL thread error", e) + releaseInternal() + } + return true + } + + private fun initInternal() { + try { + eglDisplay = GlUtil.getDefaultEglDisplay() + eglContext = GlUtil.createEglContext(eglDisplay) + eglSurface = GlUtil.createEglSurface(eglDisplay, surfaceTexture, C.COLOR_TRANSFER_SDR, false) + EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) + render.onSurfaceCreated() + this.sizeChangedInternal(width, height) + } catch (e: GlUtil.GlException) { + Log.e(TAG, "Failed to initialize EGL", e) + throw IllegalStateException("EGL initialization failed", e) + } + } + + private fun sizeChangedInternal(width: Int, height: Int) { + render.onSurfaceChanged(width, height) + } + + private fun drawInternal(timestampNanos: Long) { + if (eglDisplay == EGL14.EGL_NO_DISPLAY) return + if (render.onDrawFrame(timestampNanos)) { + EGL14.eglSwapBuffers(eglDisplay, eglSurface) + } + } + + private fun releaseInternal() { + if (eglDisplay != EGL14.EGL_NO_DISPLAY) { + try { + render.onSurfaceDestroyed() + GlUtil.destroyEglSurface(eglDisplay, eglSurface) + GlUtil.destroyEglContext(eglDisplay, eglContext) + } catch (e: GlUtil.GlException) { + Log.e(TAG, "Failed to release EGL resources", e) + } finally { + eglDisplay = EGL14.EGL_NO_DISPLAY + eglContext = EGL14.EGL_NO_CONTEXT + eglSurface = EGL14.EGL_NO_SURFACE + } + } + quit() + } + } + + private class AssRender(private val assHandler: AssHandler) : Renderer { + + private val vertexShaderCode = """ + attribute vec4 a_Position; + attribute vec2 a_TexCoord; + varying vec2 v_TexCoord; + void main() { + gl_Position = a_Position; + v_TexCoord = a_TexCoord.xy; + } + """.trimIndent() + + // alpha + private val fragmentShaderCode = """ + precision mediump float; + varying vec2 v_TexCoord; + uniform sampler2D u_Texture; + uniform vec4 u_Color; + void main() { + float alpha = texture2D(u_Texture, v_TexCoord).a; + gl_FragColor = u_Color * alpha; + + } + """.trimIndent() + + private val rectangleCoords = floatArrayOf( + -1f, 1f, // Top left + 1f, 1f, // Top right + -1f, -1f, // Bottom left + 1f, -1f // Bottom right + ) + + private val textureCoords = floatArrayOf( + 0f, 0f, // Top left + 1f, 0f, // Top right + 0f, 1f, // Bottom left + 1f, 1f // Bottom right + ) + + private var surfaceDirty = true + + private var surfaceSize = Size.ZERO + + private lateinit var glProgram: GlProgram + + private var vertexBufferId = 0 + + private var texCoordBufferId = 0 + + override fun onSurfaceCreated() { + glProgram = GlProgram(vertexShaderCode, fragmentShaderCode) + GlUtil.checkGlError() + + val vertexBuffer = ByteBuffer.allocateDirect(rectangleCoords.size * 4) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer() + .put(rectangleCoords) + vertexBuffer.position(0) + + val texCordBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer() + .put(textureCoords) + texCordBuffer.position(0) + + val buffers = IntArray(2) + GLES20.glGenBuffers(2, buffers, 0) + vertexBufferId = buffers[0] + texCoordBufferId = buffers[1] + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId) + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, rectangleCoords.size * 4, vertexBuffer, GLES20.GL_STATIC_DRAW) + GlUtil.checkGlError() + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, texCoordBufferId) + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, textureCoords.size * 4, texCordBuffer, GLES20.GL_STATIC_DRAW) + GlUtil.checkGlError() + // ALPHA_8 need set pixel store to 1 + // Or the render result may error or crash + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1) + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) + + // enable blend + GLES20.glEnable(GLES20.GL_BLEND); + // set blend mode + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); + } + + override fun onSurfaceChanged(width: Int, height: Int) { + surfaceSize = Size(width, height) + assHandler.render?.setFrameSize(width, height) + GLES20.glViewport(0, 0, width, height) + } + + override fun onDrawFrame(timestampNanos: Long): Boolean { + val assFrame = assHandler.render?.renderFrame(timestampNanos / 1000, AssTexType.TEXTURE) + + // if content not change, just return the tex + if (assFrame != null && assFrame.changed == 0) { + return false + } + + // no content && tex is clean, just return the tex + if (assFrame == null && !surfaceDirty) { + return false + } + + // clear tex content + GlUtil.clearFocusedBuffers() + surfaceDirty = false + + // render each frame + assFrame?.images?.let { frames -> + surfaceDirty = true + glProgram.use() + val aPosition = glProgram.getAttributeArrayLocationAndEnable("a_Position") + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId) + GLES20.glVertexAttribPointer(aPosition, 2, GLES20.GL_FLOAT, false, 0, 0) + GlUtil.checkGlError() + val aTexCoord = glProgram.getAttributeArrayLocationAndEnable("a_TexCoord") + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, texCoordBufferId) + GLES20.glVertexAttribPointer(aTexCoord, 2, GLES20.GL_FLOAT, false, 0, 0) + GlUtil.checkGlError() + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) + + frames.forEach { frame -> + if (frame.tex > 0 ) { + val r = frame.color shr 24 and 0xFF + val g = frame.color shr 16 and 0xFF + val b = frame.color shr 8 and 0xFF + val a = 0xFF - frame.color and 0xFF + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frame.tex) + GlUtil.checkGlError() + + GLES20.glViewport(frame.x, surfaceSize.height - frame.y - frame.h, frame.w, frame.h) + GLES20.glUniform4f(glProgram.getUniformLocation("u_Color"), r / 255f, g / 255f, b / 255f, a / 255f) + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + GlUtil.checkGlError() + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0) + GlUtil.deleteTexture(frame.tex) + } + } + } + GLES20.glViewport(0, 0, surfaceSize.width, surfaceSize.height) + return true + } + + override fun onSurfaceDestroyed() { + GlUtil.deleteBuffer(vertexBufferId) + GlUtil.deleteBuffer(texCoordBufferId) + glProgram.delete() + } + + } +} \ No newline at end of file diff --git a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleView.kt b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleView.kt index a27b31a..318ed9f 100644 --- a/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleView.kt +++ b/lib_ass_media/src/main/java/io/github/peerless2012/ass/media/widget/AssSubtitleView.kt @@ -1,15 +1,10 @@ package io.github.peerless2012.ass.media.widget - import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode import android.util.AttributeSet -import android.view.View -import io.github.peerless2012.ass.AssFrame +import android.widget.FrameLayout +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi import io.github.peerless2012.ass.media.AssHandler -import io.github.peerless2012.ass.media.executor.AssExecutor import io.github.peerless2012.ass.media.type.AssRenderType /** @@ -19,34 +14,11 @@ import io.github.peerless2012.ass.media.type.AssRenderType * @Version V1.0 * @Description */ -class AssSubtitleView: View { - - private val paint = Paint().apply { - xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER) - } +class AssSubtitleView: FrameLayout { private val assHandler: AssHandler - private var assExecutor: AssExecutor? = null - - private var assFrame: AssFrame? = null - - // Use a local param, avoid create each time. - private val invalidateCallback = Runnable { invalidate() } - - // Use a local param, avoid create each time. - private val assRenderCallback: (AssFrame?) -> Unit = assRenderCallback@{ assFrame -> - // Not change - if (assFrame != null && assFrame.changed == 0) { - return@assRenderCallback - } - // prepare to draw - assFrame?.images?.forEach { - it.bitmap?.prepareToDraw() - } - this.assFrame = assFrame - handler.post(invalidateCallback) - } + private var assSubtitleRender: AssSubtitleRender? = null constructor(context: Context, assHandler: AssHandler) : this(context, null, assHandler) @@ -57,55 +29,39 @@ class AssSubtitleView: View { assHandler ) + @OptIn(UnstableApi::class) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, assHandler: AssHandler) : super(context, attrs, defStyleAttr) { - setWillNotDraw(false) this.assHandler = assHandler - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (assHandler.renderType != AssRenderType.OVERLAY) { - return - } - assHandler.render?.let { - assExecutor = AssExecutor(it) - } - assHandler.renderCallback = { assRender -> - assExecutor?.shutdown() - assExecutor = null - if (assRender != null) { - assExecutor = AssExecutor(assRender) + val view = when (assHandler.renderType) { + AssRenderType.OVERLAY_CANVAS -> { + AssSubtitleCanvasView(context, attrs, defStyleAttr, assHandler) + } + AssRenderType.OVERLAY_OPEN_GL -> { + AssSubtitleTextureView(context, attrs, defStyleAttr, assHandler) + } + else -> { + null } } - assHandler.videoTimeCallback = { presentationTimeUs -> - assExecutor?.asyncRenderFrame(presentationTimeUs, assRenderCallback) + view?.let { + assSubtitleRender = it + val params = LayoutParams(MarginLayoutParams.MATCH_PARENT, + MarginLayoutParams.MATCH_PARENT) + addView(it, params) } } - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - assFrame?.images?.let { frames -> - frames.forEach { frame -> - frame.bitmap?.let { bitmap -> - val r = frame.color shr 24 and 0xFF - val g = frame.color shr 16 and 0xFF - val b = frame.color shr 8 and 0xFF - val a = 0xFF - frame.color and 0xFF - val color = (a shl 24) or (r shl 16) or (g shl 8) or b - - paint.color = color - canvas.drawBitmap(bitmap, frame.x.toFloat(), frame.y.toFloat(), paint) - } - } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + assHandler.videoTimeCallback = { presentationTimeUs -> + assSubtitleRender?.requestRender(presentationTimeUs) } } override fun onDetachedFromWindow() { - assHandler.renderCallback = null - assHandler.videoTimeCallback = null - assExecutor?.shutdown() - assExecutor = null super.onDetachedFromWindow() + assHandler.videoTimeCallback = null } + } \ No newline at end of file