diff --git a/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_fragment.glsl b/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_fragment.glsl new file mode 100644 index 00000000..1c39979a --- /dev/null +++ b/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_fragment.glsl @@ -0,0 +1,34 @@ +// Copyright 2020 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. + +#extension GL_OES_EGL_image_external : require +precision mediump float; +// External texture containing video decoder output. +uniform samplerExternalOES uTexSampler0; +// Texture containing the overlap bitmap. +uniform sampler2D uTexSampler1; +// Horizontal scaling factor for the overlap bitmap. +uniform float uScaleX; +// Vertical scaling factory for the overlap bitmap. +uniform float uScaleY; +varying vec2 vTexCoords; +void main() { + vec4 videoColor = texture2D(uTexSampler0, vTexCoords); + vec4 overlayColor = texture2D(uTexSampler1, + vec2(vTexCoords.x * uScaleX, + vTexCoords.y * uScaleY)); + // Blend the video decoder output and the overlay bitmap. + gl_FragColor = videoColor * (1.0 - overlayColor.a) + + overlayColor * overlayColor.a; +} diff --git a/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_vertex.glsl b/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_vertex.glsl new file mode 100644 index 00000000..b10aa688 --- /dev/null +++ b/dkplayer-sample/src/main/assets/bitmap_overlay_video_processor_vertex.glsl @@ -0,0 +1,21 @@ +// Copyright 2020 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. +attribute vec4 aFramePosition; +attribute vec4 aTexCoords; +uniform mat4 uTexTransform; +varying vec2 vTexCoords; +void main() { + gl_Position = aFramePosition; + vTexCoords = (uTexTransform * aTexCoords).xy; +} diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/activity/api/PlayerActivity.kt b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/activity/api/PlayerActivity.kt index 5513e5e9..a6d8e83e 100644 --- a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/activity/api/PlayerActivity.kt +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/activity/api/PlayerActivity.kt @@ -13,6 +13,7 @@ import xyz.doikki.dkplayer.util.IntentKeys import xyz.doikki.dkplayer.util.Utils import xyz.doikki.dkplayer.widget.component.DebugInfoView import xyz.doikki.dkplayer.widget.component.PlayerMonitor +import xyz.doikki.dkplayer.widget.render.gl.GLSurfaceRenderViewFactory import xyz.doikki.videocontroller.StandardVideoController import xyz.doikki.videocontroller.component.* import xyz.doikki.videoplayer.player.BaseVideoView @@ -104,6 +105,10 @@ class PlayerActivity : BaseActivity() { //播放状态监听 mVideoView.addOnStateChangeListener(mOnStateChangeListener) + // 临时切换RenderView, 如需全局请通过VideoConfig配置,详见MyApplication + if (intent.getBooleanExtra(IntentKeys.CUSTOM_RENDER, false)) { + mVideoView.setRenderViewFactory(GLSurfaceRenderViewFactory.create()) + } //临时切换播放核心,如需全局请通过VideoConfig配置,详见MyApplication //使用IjkPlayer解码 // mVideoView.setPlayerFactory(IjkPlayerFactory.create()) @@ -208,11 +213,12 @@ class PlayerActivity : BaseActivity() { "https://cms-bucket.nosdn.127.net/eb411c2810f04ffa8aaafc42052b233820180418095416.jpeg" @JvmStatic - fun start(context: Context, url: String, title: String, isLive: Boolean) { + fun start(context: Context, url: String, title: String, isLive: Boolean, customRender: Boolean = false) { val intent = Intent(context, PlayerActivity::class.java) intent.putExtra(IntentKeys.URL, url) intent.putExtra(IntentKeys.IS_LIVE, isLive) intent.putExtra(IntentKeys.TITLE, title) + intent.putExtra(IntentKeys.CUSTOM_RENDER, customRender) context.startActivity(intent) } } diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/app/MyApplication.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/app/MyApplication.java index 6d6d57ce..753c3ba6 100644 --- a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/app/MyApplication.java +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/app/MyApplication.java @@ -32,6 +32,8 @@ public void onCreate() { .setPlayerFactory(ExoMediaPlayerFactory.create()) // 设置自己的渲染view,内部默认TextureView实现 // .setRenderViewFactory(SurfaceRenderViewFactory.create()) + // GLSurfaceView 可对视频加滤镜 +// .setRenderViewFactory(GLSurfaceRenderViewFactory.create()) // 根据手机重力感应自动切换横竖屏,默认false // .setEnableOrientation(true) // 监听系统中其他播放器是否获取音频焦点,实现不与其他播放器同时播放的效果,默认true diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ApiFragment.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ApiFragment.java index 16a5ffab..78776d92 100644 --- a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ApiFragment.java +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ApiFragment.java @@ -59,19 +59,19 @@ protected void initView() { public void onClick(View v) { switch (v.getId()) { case R.id.btn_vod: - PlayerActivity.start(getActivity(), VOD_URL, getString(R.string.str_api_vod), false); + PlayerActivity.start(getActivity(), VOD_URL, getString(R.string.str_api_vod), false, false); break; case R.id.btn_live: - PlayerActivity.start(getActivity(), LIVE_URL, getString(R.string.str_api_live), true); + PlayerActivity.start(getActivity(), LIVE_URL, getString(R.string.str_api_live), true, false); break; case R.id.btn_music: - PlayerActivity.start(getActivity(), MUSIC_URL, getString(R.string.str_api_music), false); + PlayerActivity.start(getActivity(), MUSIC_URL, getString(R.string.str_api_music), false, false); break; case R.id.btn_file: // 此处演示的是播放私有目录的文件,如果是共有目录需要存储权限 String url = "file://" + requireContext().getExternalCacheDir().getAbsolutePath() + "/test.mp4"; L.d("play local file: " + url); - PlayerActivity.start(getActivity(), url, getString(R.string.str_file), false); + PlayerActivity.start(getActivity(), url, getString(R.string.str_file), false, false); break; case R.id.btn_raw_assets: startActivity(new Intent(getActivity(), PlayRawAssetsActivity.class)); diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ExtensionFragment.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ExtensionFragment.java index 39be7e34..b3949e2d 100644 --- a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ExtensionFragment.java +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/fragment/main/ExtensionFragment.java @@ -4,16 +4,18 @@ import android.view.View; import xyz.doikki.dkplayer.R; -import xyz.doikki.dkplayer.activity.extend.DefinitionPlayerActivity; +import xyz.doikki.dkplayer.activity.api.PlayerActivity; import xyz.doikki.dkplayer.activity.extend.ADActivity; import xyz.doikki.dkplayer.activity.extend.CacheActivity; import xyz.doikki.dkplayer.activity.extend.CustomExoPlayerActivity; import xyz.doikki.dkplayer.activity.extend.CustomIjkPlayerActivity; import xyz.doikki.dkplayer.activity.extend.DanmakuActivity; +import xyz.doikki.dkplayer.activity.extend.DefinitionPlayerActivity; import xyz.doikki.dkplayer.activity.extend.FullScreenActivity; import xyz.doikki.dkplayer.activity.extend.PadActivity; import xyz.doikki.dkplayer.activity.extend.PlayListActivity; import xyz.doikki.dkplayer.fragment.BaseFragment; +import xyz.doikki.dkplayer.util.DataUtil; public class ExtensionFragment extends BaseFragment implements View.OnClickListener { @Override @@ -33,6 +35,7 @@ protected void initView() { findViewById(R.id.btn_custom_exo_player).setOnClickListener(this); findViewById(R.id.btn_custom_ijk_player).setOnClickListener(this); findViewById(R.id.btn_definition).setOnClickListener(this); + findViewById(R.id.btn_custom_render_view).setOnClickListener(this); } @Override @@ -65,6 +68,9 @@ public void onClick(View v) { case R.id.btn_definition: startActivity(new Intent(getActivity(), DefinitionPlayerActivity.class)); break; + case R.id.btn_custom_render_view: + PlayerActivity.start(getActivity(), DataUtil.SAMPLE_URL, getString(R.string.str_custom_render_view), false, true); + break; } } } diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/util/IntentKeys.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/util/IntentKeys.java index 41ec8536..bdc4cf26 100644 --- a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/util/IntentKeys.java +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/util/IntentKeys.java @@ -6,4 +6,5 @@ public class IntentKeys { public static final String SEAMLESS_PLAY = "seamless_play"; public static final String TITLE = "title"; public static final String IS_LIVE = "isLive"; + public static final String CUSTOM_RENDER = "custom_render"; } diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/BitmapOverlayVideoProcessor.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/BitmapOverlayVideoProcessor.java new file mode 100644 index 00000000..d7a44236 --- /dev/null +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/BitmapOverlayVideoProcessor.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 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 xyz.doikki.dkplayer.widget.render.gl; + +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.opengl.GLES20; +import android.opengl.GLUtils; + +import com.google.android.exoplayer2.util.GlProgram; +import com.google.android.exoplayer2.util.GlUtil; + +import java.io.IOException; + +import javax.microedition.khronos.opengles.GL10; + +/** + * Video processor that demonstrates how to overlay a bitmap on video output using a GL shader. The + * bitmap is drawn using an Android {@link Canvas}. + */ +/* package */ final class BitmapOverlayVideoProcessor implements GLSurfaceRenderView.VideoProcessor { + + private static final int OVERLAY_WIDTH = 512; + private static final int OVERLAY_HEIGHT = 256; + + private final Context context; + private final Paint paint; + private final int[] textures; + private final Bitmap overlayBitmap; + private final Bitmap logoBitmap; + private final Canvas overlayCanvas; + + private GlProgram program; + + private float bitmapScaleX; + private float bitmapScaleY; + + public BitmapOverlayVideoProcessor(Context context) { + this.context = context.getApplicationContext(); + paint = new Paint(); + paint.setTextSize(64); + paint.setAntiAlias(true); + paint.setARGB(0xFF, 0xFF, 0x00, 0x00); + textures = new int[1]; + overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888); + overlayCanvas = new Canvas(overlayBitmap); + try { + logoBitmap = + ((BitmapDrawable) + context.getPackageManager().getApplicationIcon(context.getPackageName())) + .getBitmap(); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException(e); + } + } + + @Override + public void initialize() { + try { + program = + new GlProgram( + context, + /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl", + /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); + } catch (IOException e) { + throw new IllegalStateException(e); + } + program.setBufferAttribute( + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + program.setBufferAttribute( + "aTexCoords", + GlUtil.getTextureCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + GLES20.glGenTextures(1, textures, 0); + GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); + GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); + GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); + GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); + } + + @Override + public void setSurfaceSize(int width, int height) { + bitmapScaleX = (float) width / OVERLAY_WIDTH; + bitmapScaleY = (float) height / OVERLAY_HEIGHT; + } + + @Override + public void draw(int frameTexture, float[] transformMatrix) { + // Draw to the canvas and store it in a texture. + String text = "视频水印"; + overlayBitmap.eraseColor(Color.TRANSPARENT); + overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint); + overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint); + GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); + GLUtils.texSubImage2D( + GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap); + GlUtil.checkGlError(); + + // Run the shader program. + GlProgram program = checkNotNull(this.program); + program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0); + program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1); + program.setFloatUniform("uScaleX", bitmapScaleX); + program.setFloatUniform("uScaleY", bitmapScaleY); + program.setFloatsUniform("uTexTransform", transformMatrix); + program.bindAttributesAndUniforms(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); + GlUtil.checkGlError(); + } + + @Override + public void release() { + if (program != null) { + program.delete(); + } + } +} diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderView.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderView.java new file mode 100644 index 00000000..d07951c7 --- /dev/null +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderView.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2020 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 xyz.doikki.dkplayer.widget.render.gl; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.os.Handler; +import android.view.Surface; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.util.GlUtil; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL10; + +import xyz.doikki.videoplayer.player.AbstractPlayer; +import xyz.doikki.videoplayer.render.IRenderView; +import xyz.doikki.videoplayer.render.MeasureHelper; + +/** + * {@link GLSurfaceView} that creates a GL context (optionally for protected content) and passes + * video frames to a {@link VideoProcessor} for drawing to the view. + * + *

This view must be created programmatically, as it is necessary to specify whether a context + * supporting protected content should be created at construction time. + * 参考:https://github.com/google/ExoPlayer/tree/release-v2/demos/gl + * 请尽量理解此demo的含义,关键代码在 {@link VideoRenderer} 的 onDrawFrame 方法,有问题问Google,不要来找我 + * OpenGL相关资料: + * https://developer.android.com/guide/topics/graphics/opengl?hl=zh-cn + * https://github.com/google/grafika + */ +public final class GLSurfaceRenderView extends GLSurfaceView implements IRenderView { + + private final MeasureHelper mMeasureHelper = new MeasureHelper(); + + private AbstractPlayer player; + + @Override + public void attachToPlayer(@NonNull AbstractPlayer player) { + this.player = player; + setVideoRenderer(new BitmapOverlayVideoProcessor(getContext()), false); + } + + @Override + public void setVideoSize(int videoWidth, int videoHeight) { + if (videoWidth > 0 && videoHeight > 0) { + mMeasureHelper.setVideoSize(videoWidth, videoHeight); + requestLayout(); + } + } + + @Override + public void setVideoRotation(int degree) { + mMeasureHelper.setVideoRotation(degree); + setRotation(degree); + } + + @Override + public void setScaleType(int scaleType) { + mMeasureHelper.setScreenScale(scaleType); + requestLayout(); + } + + @Override + public View getView() { + return this; + } + + @Override + public Bitmap doScreenShot() { + return null; + } + + @Override + public void release() { + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int[] measuredSize = mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(measuredSize[0], measuredSize[1]); + } + + /** + * Processes video frames, provided via a GL texture. + */ + public interface VideoProcessor { + /** + * Performs any required GL initialization. + */ + void initialize(); + + /** + * Sets the size of the output surface in pixels. + */ + void setSurfaceSize(int width, int height); + + /** + * Draws using GL operations. + * + * @param frameTexture The ID of a GL texture containing a video frame. + * @param transformMatrix The 4 * 4 transform matrix to be applied to the texture. + */ + void draw(int frameTexture, float[] transformMatrix); + + /** + * Releases any resources associated with this {@link VideoProcessor}. + */ + void release(); + } + + private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + + private final Handler mainHandler; + + @Nullable + private SurfaceTexture surfaceTexture; + @Nullable + private Surface surface; + + /** + * Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link + * GLSurfaceView GLSurfaceView's} associated GL context should handle secure content (if the + * device supports it). + * + * @param context The {@link Context}. + */ + @SuppressWarnings("InlinedApi") + public GLSurfaceRenderView(Context context) { + super(context); + mainHandler = new Handler(); + } + + /** + * @param processor Processor that draws to the view. + * @param requireSecureContext Whether a GL context supporting protected content should be + * created, if supported by the device. + */ + public void setVideoRenderer(VideoProcessor processor, boolean requireSecureContext) { + setEGLContextClientVersion(2); + setEGLConfigChooser( + /* redSize= */ 8, + /* greenSize= */ 8, + /* blueSize= */ 8, + /* alphaSize= */ 8, + /* depthSize= */ 0, + /* stencilSize= */ 0); + setEGLContextFactory( + new EGLContextFactory() { + @Override + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) { + int[] glAttributes; + if (requireSecureContext) { + glAttributes = + new int[]{ + EGL14.EGL_CONTEXT_CLIENT_VERSION, + 2, + EGL_PROTECTED_CONTENT_EXT, + EGL14.EGL_TRUE, + EGL14.EGL_NONE + }; + } else { + glAttributes = new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + } + return egl.eglCreateContext( + display, eglConfig, /* share_context= */ EGL10.EGL_NO_CONTEXT, glAttributes); + } + + @Override + public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) { + egl.eglDestroyContext(display, context); + } + }); + setEGLWindowSurfaceFactory( + new EGLWindowSurfaceFactory() { + @Override + public EGLSurface createWindowSurface( + EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) { + int[] attribsList = + requireSecureContext + ? new int[]{EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE} + : new int[]{EGL10.EGL_NONE}; + return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList); + } + + @Override + public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } + }); + setRenderer(new VideoRenderer(processor)); + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + // Post to make sure we occur in order with any onSurfaceTextureAvailable calls. + mainHandler.post( + () -> { + if (surface != null) { + if (player != null) { + player.setSurface(null); + } + releaseSurface(surfaceTexture, surface); + surfaceTexture = null; + surface = null; + } + }); + } + + private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) { + mainHandler.post( + () -> { + SurfaceTexture oldSurfaceTexture = this.surfaceTexture; + Surface oldSurface = GLSurfaceRenderView.this.surface; + this.surfaceTexture = surfaceTexture; + this.surface = new Surface(surfaceTexture); + releaseSurface(oldSurfaceTexture, oldSurface); + if (player != null) { + player.setSurface(surface); + } + }); + } + + private static void releaseSurface( + @Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) { + if (oldSurfaceTexture != null) { + oldSurfaceTexture.release(); + } + if (oldSurface != null) { + oldSurface.release(); + } + } + + private final class VideoRenderer implements Renderer { + + private final VideoProcessor videoProcessor; + private final AtomicBoolean frameAvailable; + private final float[] transformMatrix; + + private int texture; + @Nullable + private SurfaceTexture surfaceTexture; + + private boolean initialized; + private int width; + private int height; + + public VideoRenderer(VideoProcessor videoProcessor) { + this.videoProcessor = videoProcessor; + frameAvailable = new AtomicBoolean(); + width = -1; + height = -1; + transformMatrix = new float[16]; + } + + @Override + public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) { + texture = GlUtil.createExternalTexture(); + surfaceTexture = new SurfaceTexture(texture); + surfaceTexture.setOnFrameAvailableListener( + surfaceTexture -> { + frameAvailable.set(true); + requestRender(); + }); + onSurfaceTextureAvailable(surfaceTexture); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + GLES20.glViewport(0, 0, width, height); + initialized = false; + this.width = width; + this.height = height; + } + + @Override + public void onDrawFrame(GL10 gl) { + if (videoProcessor == null) { + return; + } + + if (!initialized) { + videoProcessor.initialize(); + initialized = true; + } + + if (width != -1 && height != -1) { + videoProcessor.setSurfaceSize(width, height); + width = -1; + height = -1; + } + + if (frameAvailable.compareAndSet(true, false) && surfaceTexture != null) { + surfaceTexture.updateTexImage(); + surfaceTexture.getTransformMatrix(transformMatrix); + } + + videoProcessor.draw(texture, transformMatrix); + } + } +} diff --git a/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderViewFactory.java b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderViewFactory.java new file mode 100644 index 00000000..734a68f4 --- /dev/null +++ b/dkplayer-sample/src/main/java/xyz/doikki/dkplayer/widget/render/gl/GLSurfaceRenderViewFactory.java @@ -0,0 +1,18 @@ +package xyz.doikki.dkplayer.widget.render.gl; + +import android.content.Context; + +import xyz.doikki.videoplayer.render.IRenderView; +import xyz.doikki.videoplayer.render.RenderViewFactory; + +public class GLSurfaceRenderViewFactory extends RenderViewFactory { + + public static GLSurfaceRenderViewFactory create() { + return new GLSurfaceRenderViewFactory(); + } + + @Override + public IRenderView createRenderView(Context context) { + return new GLSurfaceRenderView(context); + } +} diff --git a/dkplayer-sample/src/main/res/layout/fragment_extension.xml b/dkplayer-sample/src/main/res/layout/fragment_extension.xml index 34f10f69..b812b162 100644 --- a/dkplayer-sample/src/main/res/layout/fragment_extension.xml +++ b/dkplayer-sample/src/main/res/layout/fragment_extension.xml @@ -44,13 +44,20 @@ android:id="@+id/btn_custom_exo_player" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="自定义ExoMediaPlayer"/> + android:text="@string/str_custom_exo"/>