From 30d6fbffaa7dfdf6ed8a228859a8492229d2b7cd Mon Sep 17 00:00:00 2001 From: guoshuyu <359369982@qq.com> Date: Mon, 17 Jun 2024 10:59:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=B3=BB=E7=BB=9F=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=86=85=E6=A0=B8=E7=9A=84=20PlaceholderSurface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gsyvideoplayer/DetailListPlayer.java | 6 +- .../placeholder/EGLSurfaceTexture.java | 275 ++++++++++++++++++ .../placeholder/PlaceholderSurface.java | 173 +++++++++++ .../player/SystemPlayerManager.java | 17 +- 4 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/EGLSurfaceTexture.java create mode 100644 gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/PlaceholderSurface.java diff --git a/app/src/main/java/com/example/gsyvideoplayer/DetailListPlayer.java b/app/src/main/java/com/example/gsyvideoplayer/DetailListPlayer.java index e7bf26380..11cc23f94 100644 --- a/app/src/main/java/com/example/gsyvideoplayer/DetailListPlayer.java +++ b/app/src/main/java/com/example/gsyvideoplayer/DetailListPlayer.java @@ -45,9 +45,9 @@ protected void onCreate(Bundle savedInstanceState) { //String url = "http://baobab.wd jcdn.com/14564977406580.mp4"; List urls = new ArrayList<>(); urls.add(new GSYVideoModel("http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear3/prog_index.m3u8", "标题1")); - urls.add(new GSYVideoModel("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", "标题2")); - urls.add(new GSYVideoModel("https://res.exexm.com/cw_145225549855002", "标题3")); - urls.add(new GSYVideoModel("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", "标题4")); +// urls.add(new GSYVideoModel("https://res.exexm.com/cw_145225549855002", "标题2")); +// urls.add(new GSYVideoModel("http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear3/prog_index.m3u8", "标题3")); +// urls.add(new GSYVideoModel("https://res.exexm.com/cw_145225549855002", "标题4")); binding.detailPlayer.setUp(urls, true, 0); //增加封面 diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/EGLSurfaceTexture.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/EGLSurfaceTexture.java new file mode 100644 index 000000000..08c96905d --- /dev/null +++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/EGLSurfaceTexture.java @@ -0,0 +1,275 @@ +package com.shuyu.gsyvideoplayer.placeholder; + +import static java.lang.annotation.ElementType.TYPE_USE; + +import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES20; +import android.os.Build; +import android.os.Handler; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Generates a {@link SurfaceTexture} using EGL/GLES functions. */ +@RequiresApi(17) +public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableListener, Runnable { + + /** Listener to be called when the texture image on {@link SurfaceTexture} has been updated. */ + public interface TextureImageListener { + /** Called when the {@link SurfaceTexture} receives a new frame from its image producer. */ + void onFrameAvailable(); + } + + /** + * Secure mode to be used by the EGL surface and context. One of {@link #SECURE_MODE_NONE}, {@link + * #SECURE_MODE_SURFACELESS_CONTEXT} or {@link #SECURE_MODE_PROTECTED_PBUFFER}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) + public @interface SecureMode {} + + /** No secure EGL surface and context required. */ + public static final int SECURE_MODE_NONE = 0; + + /** Creating a surfaceless, secured EGL context. */ + public static final int SECURE_MODE_SURFACELESS_CONTEXT = 1; + + /** Creating a secure surface backed by a pixel buffer. */ + public static final int SECURE_MODE_PROTECTED_PBUFFER = 2; + + private static final int EGL_SURFACE_WIDTH = 1; + private static final int EGL_SURFACE_HEIGHT = 1; + + private static final int[] EGL_CONFIG_ATTRIBUTES = + new int[] { + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 0, + EGL14.EGL_CONFIG_CAVEAT, EGL14.EGL_NONE, + EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT, + EGL14.EGL_NONE + }; + + private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0; + + private final Handler handler; + private final int[] textureIdHolder; + @Nullable private final TextureImageListener callback; + + @Nullable private EGLDisplay display; + @Nullable private EGLContext context; + @Nullable private EGLSurface surface; + @Nullable private SurfaceTexture texture; + + /** + * @param handler The {@link Handler} that will be used to call {@link + * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that + * {@link #init(int)} has to be called on the same looper thread as the {@link Handler}'s + * looper. + */ + public EGLSurfaceTexture(Handler handler) { + this(handler, /* callback= */ null); + } + + /** + * @param handler The {@link Handler} that will be used to call {@link + * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that + * {@link #init(int)} has to be called on the same looper thread as the looper of the {@link + * Handler}. + * @param callback The {@link TextureImageListener} to be called when the texture image on {@link + * SurfaceTexture} has been updated. This callback will be called on the same handler thread + * as the {@code handler}. + */ + public EGLSurfaceTexture(Handler handler, @Nullable TextureImageListener callback) { + this.handler = handler; + this.callback = callback; + textureIdHolder = new int[1]; + } + + /** + * Initializes required EGL parameters and creates the {@link SurfaceTexture}. + * + * @param secureMode The {@link SecureMode} to be used for EGL surface. + */ + public void init(@SecureMode int secureMode) { + display = getDefaultDisplay(); + EGLConfig config = chooseEGLConfig(display); + context = createEGLContext(display, config, secureMode); + surface = createEGLSurface(display, config, context, secureMode); + generateTextureIds(textureIdHolder); + texture = new SurfaceTexture(textureIdHolder[0]); + texture.setOnFrameAvailableListener(this); + } + + /** Releases all allocated resources. */ + @SuppressWarnings("nullness:argument") + public void release() { + handler.removeCallbacks(this); + try { + if (texture != null) { + texture.release(); + GLES20.glDeleteTextures(1, textureIdHolder, 0); + } + } finally { + if (display != null && !display.equals(EGL14.EGL_NO_DISPLAY)) { + EGL14.eglMakeCurrent( + display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + } + if (surface != null && !surface.equals(EGL14.EGL_NO_SURFACE)) { + EGL14.eglDestroySurface(display, surface); + } + if (context != null) { + EGL14.eglDestroyContext(display, context); + } + // EGL14.eglReleaseThread could crash before Android K (see [internal: b/11327779]). + if (Build.VERSION.SDK_INT >= 19) { + EGL14.eglReleaseThread(); + } + if (display != null && !display.equals(EGL14.EGL_NO_DISPLAY)) { + // Android is unusual in that it uses a reference-counted EGLDisplay. So for + // every eglInitialize() we need an eglTerminate(). + EGL14.eglTerminate(display); + } + display = null; + context = null; + surface = null; + texture = null; + } + } + + /** + * Returns the wrapped {@link SurfaceTexture}. This can only be called after {@link #init(int)}. + */ + public SurfaceTexture getSurfaceTexture() { + return texture; + } + + // SurfaceTexture.OnFrameAvailableListener + + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + handler.post(this); + } + + // Runnable + + @Override + public void run() { + // Run on the provided handler thread when a new image frame is available. + dispatchOnFrameAvailable(); + if (texture != null) { + try { + texture.updateTexImage(); + } catch (RuntimeException e) { + // Ignore + } + } + } + + private void dispatchOnFrameAvailable() { + if (callback != null) { + callback.onFrameAvailable(); + } + } + + private static EGLDisplay getDefaultDisplay() { + EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + boolean eglInitialized = + EGL14.eglInitialize(display, version, /* majorOffset= */ 0, version, /* minorOffset= */ 1); + return display; + } + + private static EGLConfig chooseEGLConfig(EGLDisplay display) { + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + boolean success = + EGL14.eglChooseConfig( + display, + EGL_CONFIG_ATTRIBUTES, + /* attrib_listOffset= */ 0, + configs, + /* configsOffset= */ 0, + /* config_size= */ 1, + numConfigs, + /* num_configOffset= */ 0); + + return configs[0]; + } + + private static EGLContext createEGLContext( + EGLDisplay display, EGLConfig config, @SecureMode int secureMode) { + int[] glAttributes; + if (secureMode == SECURE_MODE_NONE) { + glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + } else { + glAttributes = + new int[] { + EGL14.EGL_CONTEXT_CLIENT_VERSION, + 2, + EGL_PROTECTED_CONTENT_EXT, + EGL14.EGL_TRUE, + EGL14.EGL_NONE + }; + } + EGLContext context = + EGL14.eglCreateContext( + display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); + return context; + } + + private static EGLSurface createEGLSurface( + EGLDisplay display, EGLConfig config, EGLContext context, @SecureMode int secureMode) { + EGLSurface surface; + if (secureMode == SECURE_MODE_SURFACELESS_CONTEXT) { + surface = EGL14.EGL_NO_SURFACE; + } else { + int[] pbufferAttributes; + if (secureMode == SECURE_MODE_PROTECTED_PBUFFER) { + pbufferAttributes = + new int[] { + EGL14.EGL_WIDTH, + EGL_SURFACE_WIDTH, + EGL14.EGL_HEIGHT, + EGL_SURFACE_HEIGHT, + EGL_PROTECTED_CONTENT_EXT, + EGL14.EGL_TRUE, + EGL14.EGL_NONE + }; + } else { + pbufferAttributes = + new int[] { + EGL14.EGL_WIDTH, + EGL_SURFACE_WIDTH, + EGL14.EGL_HEIGHT, + EGL_SURFACE_HEIGHT, + EGL14.EGL_NONE + }; + } + surface = EGL14.eglCreatePbufferSurface(display, config, pbufferAttributes, /* offset= */ 0); + } + + boolean eglMadeCurrent = + EGL14.eglMakeCurrent(display, /* draw= */ surface, /* read= */ surface, context); + return surface; + } + + private static void generateTextureIds(int[] textureIdHolder) { + GLES20.glGenTextures(/* n= */ 1, textureIdHolder, /* offset= */ 0); + } +} diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/PlaceholderSurface.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/PlaceholderSurface.java new file mode 100644 index 000000000..11afcb538 --- /dev/null +++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/placeholder/PlaceholderSurface.java @@ -0,0 +1,173 @@ + +package com.shuyu.gsyvideoplayer.placeholder; + +import static com.shuyu.gsyvideoplayer.placeholder.EGLSurfaceTexture.SECURE_MODE_NONE; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import android.view.Surface; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +/** + * A placeholder {@link Surface}. + */ +@RequiresApi(17) +public final class PlaceholderSurface extends Surface { + + private static final String TAG = "PlaceholderSurface"; + + /** + * Whether the surface is secure. + */ + public final boolean secure; + + private static boolean secureModeInitialized; + + private final PlaceholderSurfaceThread thread; + private boolean threadReleased; + + /** + * Returns whether the device supports secure placeholder surfaces. + * + * @param context Any {@link Context}. + * @return Whether the device supports secure placeholder surfaces. + */ + public static synchronized boolean isSecureSupported(Context context) { + return false; + } + + /** + * Returns a newly created placeholder surface. The surface must be released by calling {@link + * #release} when it's no longer required. + * + * @param context Any {@link Context}. + * @param secure Whether a secure surface is required. Must only be requested if {@link + * #isSecureSupported(Context)} returns {@code true}. + * @throws IllegalStateException If a secure surface is requested on a device for which {@link + * #isSecureSupported(Context)} returns {@code false}. + */ + public static PlaceholderSurface newInstanceV17(Context context, boolean secure) { + PlaceholderSurfaceThread thread = new PlaceholderSurfaceThread(); + return thread.init(SECURE_MODE_NONE); + } + + private PlaceholderSurface(PlaceholderSurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure) { + super(surfaceTexture); + this.thread = thread; + this.secure = secure; + } + + @Override + public void release() { + super.release(); + // The Surface may be released multiple times (explicitly and by Surface.finalize()). The + // implementation of super.release() has its own deduplication logic. Below we need to + // deduplicate ourselves. Synchronization is required as we don't control the thread on which + // Surface.finalize() is called. + synchronized (thread) { + if (!threadReleased) { + thread.release(); + threadReleased = true; + } + } + } + + + private static class PlaceholderSurfaceThread extends HandlerThread implements Handler.Callback { + + private static final int MSG_INIT = 1; + private static final int MSG_RELEASE = 2; + + private EGLSurfaceTexture eglSurfaceTexture; + private Handler handler; + @Nullable + private Error initError; + @Nullable + private RuntimeException initException; + @Nullable + private PlaceholderSurface surface; + + public PlaceholderSurfaceThread() { + super("ExoPlayer:PlaceholderSurface"); + } + + public PlaceholderSurface init(int secureMode) { + start(); + handler = new Handler(getLooper(), /* callback= */ this); + eglSurfaceTexture = new EGLSurfaceTexture(handler); + boolean wasInterrupted = false; + synchronized (this) { + handler.obtainMessage(MSG_INIT, secureMode, 0).sendToTarget(); + while (surface == null && initException == null && initError == null) { + try { + wait(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } + } + if (wasInterrupted) { + // Restore the interrupted status. + Thread.currentThread().interrupt(); + } + if (initException != null) { + throw initException; + } else if (initError != null) { + throw initError; + } else { + return surface; + } + } + + public void release() { + handler.sendEmptyMessage(MSG_RELEASE); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + try { + initInternal(/* secureMode= */ msg.arg1); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to initialize placeholder surface", e); + initException = e; + } catch (Error e) { + Log.e(TAG, "Failed to initialize placeholder surface", e); + initError = e; + } finally { + synchronized (this) { + notify(); + } + } + return true; + case MSG_RELEASE: + try { + releaseInternal(); + } catch (Throwable e) { + Log.e(TAG, "Failed to release placeholder surface", e); + } finally { + quit(); + } + return true; + default: + return true; + } + } + + private void initInternal(int secureMode) { + eglSurfaceTexture.init(secureMode); + this.surface = new PlaceholderSurface(this, eglSurfaceTexture.getSurfaceTexture(), false); + } + + private void releaseInternal() { + eglSurfaceTexture.release(); + } + } +} diff --git a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/player/SystemPlayerManager.java b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/player/SystemPlayerManager.java index a648f4d1a..3935e933a 100644 --- a/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/player/SystemPlayerManager.java +++ b/gsyVideoPlayer-java/src/main/java/com/shuyu/gsyvideoplayer/player/SystemPlayerManager.java @@ -13,6 +13,7 @@ import com.shuyu.gsyvideoplayer.model.GSYModel; import com.shuyu.gsyvideoplayer.model.VideoOptionModel; import com.shuyu.gsyvideoplayer.utils.Debuger; +import com.shuyu.gsyvideoplayer.placeholder.PlaceholderSurface; import java.util.List; @@ -40,6 +41,9 @@ public class SystemPlayerManager extends BasePlayerManager { private boolean isPlaying = false; + private PlaceholderSurface dummySurface; + + @Override public IMediaPlayer getMediaPlayer() { return mediaPlayer; @@ -49,6 +53,9 @@ public IMediaPlayer getMediaPlayer() { public void initVideoPlayer(Context context, Message msg, List optionModelList, ICacheManager cacheManager) { this.context = context.getApplicationContext(); mediaPlayer = new AndroidMediaPlayer(); + if (dummySurface == null) { + dummySurface = PlaceholderSurface.newInstanceV17(context, false); + } mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); release = false; GSYModel gsyModel = (GSYModel) msg.obj; @@ -71,7 +78,11 @@ public void initVideoPlayer(Context context, Message msg, List @Override public void showDisplay(Message msg) { if (msg.obj == null && mediaPlayer != null && !release) { - mediaPlayer.setSurface(null); + if (dummySurface != null) { + mediaPlayer.setSurface(dummySurface); + } else { + mediaPlayer.setSurface(null); + } } else if (msg.obj != null) { Surface holder = (Surface) msg.obj; surface = holder; @@ -126,6 +137,10 @@ public void release() { mediaPlayer.release(); mediaPlayer = null; } + if (dummySurface != null) { + dummySurface.release(); + dummySurface = null; + } lastTotalRxBytes = 0; lastTimeStamp = 0; }