【问题标题】:Keep Aspect Ratio Exoplayer without using com.google.android.exoplayer2.ui.PlayerView在不使用 com.google.android.exoplayer2.ui.PlayerView 的情况下保持 Aspect Ratio Exoplayer
【发布时间】:2022-08-13 21:55:20
【问题描述】:

我目前正在开发 exoplayer 版本“2.12.1”的 OpenGL 演示项目。

https://github.com/google/ExoPlayer/tree/r2.12.1/demos/gl

MainActivity.java

/*
 * 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 com.google.android.exoplayer2.gldemo;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.Util;
import java.util.UUID;

/**
 * Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
 * postprocessing of the video content using GL.
 */
public final class MainActivity extends Activity {

  private static final String TAG = \"MainActivity\";

  private static final String DEFAULT_MEDIA_URI =
      \"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv\";

  private static final String ACTION_VIEW = \"com.google.android.exoplayer.gldemo.action.VIEW\";
  private static final String EXTENSION_EXTRA = \"extension\";
  private static final String DRM_SCHEME_EXTRA = \"drm_scheme\";
  private static final String DRM_LICENSE_URL_EXTRA = \"drm_license_url\";

  @Nullable private PlayerView playerView;
  @Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;

  @Nullable private SimpleExoPlayer player;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
    playerView = findViewById(R.id.player_view);

    Context context = getApplicationContext();
    boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
    if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
      Toast.makeText(
              context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
          .show();
    }

    VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
        new VideoProcessingGLSurfaceView(
            context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
    FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
    contentFrame.addView(videoProcessingGLSurfaceView);
    this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
  }

  @Override
  public void onStart() {
    super.onStart();
    if (Util.SDK_INT > 23) {
      initializePlayer();
      if (playerView != null) {
        playerView.onResume();
      }
    }
  }

  @Override
  public void onResume() {
    super.onResume();
    if (Util.SDK_INT <= 23 || player == null) {
      initializePlayer();
      if (playerView != null) {
        playerView.onResume();
      }
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    if (Util.SDK_INT <= 23) {
      if (playerView != null) {
        playerView.onPause();
      }
      releasePlayer();
    }
  }

  @Override
  public void onStop() {
    super.onStop();
    if (Util.SDK_INT > 23) {
      if (playerView != null) {
        playerView.onPause();
      }
      releasePlayer();
    }
  }

  private void initializePlayer() {
    Intent intent = getIntent();
    String action = intent.getAction();
    Uri uri =
        ACTION_VIEW.equals(action)
            ? Assertions.checkNotNull(intent.getData())
            : Uri.parse(DEFAULT_MEDIA_URI);
    DrmSessionManager drmSessionManager;
    if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
      String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
      String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
      UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
      HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSourceFactory();
      HttpMediaDrmCallback drmCallback =
          new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
      drmSessionManager =
          new DefaultDrmSessionManager.Builder()
              .setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
              .build(drmCallback);
    } else {
      drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
    }

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);
    MediaSource mediaSource;
    @C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
    if (type == C.TYPE_DASH) {
      mediaSource =
          new DashMediaSource.Factory(dataSourceFactory)
              .setDrmSessionManager(drmSessionManager)
              .createMediaSource(MediaItem.fromUri(uri));
    } else if (type == C.TYPE_OTHER) {
      mediaSource =
          new ProgressiveMediaSource.Factory(dataSourceFactory)
              .setDrmSessionManager(drmSessionManager)
              .createMediaSource(MediaItem.fromUri(uri));
    } else {
      throw new IllegalStateException();
    }

    SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
    player.setRepeatMode(Player.REPEAT_MODE_ALL);
    player.setMediaSource(mediaSource);
    player.prepare();
    player.play();
    VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
        Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
    videoProcessingGLSurfaceView.setVideoComponent(
        Assertions.checkNotNull(player.getVideoComponent()));
    Assertions.checkNotNull(playerView).setPlayer(player);
    player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
    this.player = player;
  }

  private void releasePlayer() {
    Assertions.checkNotNull(playerView).setPlayer(null);
    if (player != null) {
      player.release();
      Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
      player = null;
    }
  }
}

BitmapOverlayVideoProcessor.java

package com.google.android.exoplayer2.gldemo;

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 androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
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 VideoProcessingGLSurfaceView.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 int program;
  @Nullable private GlUtil.Attribute[] attributes;
  @Nullable private GlUtil.Uniform[] uniforms;

  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, 0xFF, 0xFF);
    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() {
    String vertexShaderCode =
        loadAssetAsString(context, \"bitmap_overlay_video_processor_vertex.glsl\");
    String fragmentShaderCode =
        loadAssetAsString(context, \"bitmap_overlay_video_processor_fragment.glsl\");
    program = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode);
    GlUtil.Attribute[] attributes = GlUtil.getAttributes(program);
    GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program);
    for (GlUtil.Attribute attribute : attributes) {
      if (attribute.name.equals(\"a_position\")) {
        attribute.setBuffer(new float[] {-1, -1, 1, -1, -1, 1, 1, 1}, 2);
      } else if (attribute.name.equals(\"a_texcoord\")) {
        attribute.setBuffer(new float[] {0, 1, 1, 1, 0, 0, 1, 0}, 2);
      }
    }
    this.attributes = attributes;
    this.uniforms = uniforms;
    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, long frameTimestampUs) {
    // Draw to the canvas and store it in a texture.
    String text = String.format(Locale.US, \"%.02f\", frameTimestampUs / (float) C.MICROS_PER_SECOND);
    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.
    GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms);
    GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes);
    GLES20.glUseProgram(program);
    for (GlUtil.Uniform uniform : uniforms) {
      switch (uniform.name) {
        case \"tex_sampler_0\":
          uniform.setSamplerTexId(frameTexture, /* unit= */ 0);
          break;
        case \"tex_sampler_1\":
          uniform.setSamplerTexId(textures[0], /* unit= */ 1);
          break;
        case \"scaleX\":
          uniform.setFloat(bitmapScaleX);
          break;
        case \"scaleY\":
          uniform.setFloat(bitmapScaleY);
          break;
      }
    }
    for (GlUtil.Attribute copyExternalAttribute : attributes) {
      copyExternalAttribute.bind();
    }
    for (GlUtil.Uniform copyExternalUniform : uniforms) {
      copyExternalUniform.bind();
    }
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
    GlUtil.checkGlError();
  }

  private static String loadAssetAsString(Context context, String assetFileName) {
    @Nullable InputStream inputStream = null;
    try {
      inputStream = context.getAssets().open(assetFileName);
      return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
    } catch (IOException e) {
      throw new IllegalStateException(e);
    } finally {
      Util.closeQuietly(inputStream);
    }
  }
}

VideoProcessingGLSurfaceView.java

package com.google.android.exoplayer2.gldemo;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaFormat;
import android.opengl.EGL14;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.GlUtil;
import com.google.android.exoplayer2.util.TimedValueQueue;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
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;

/**
 * {@link GLSurfaceView} that creates a GL context (optionally for protected content) and passes
 * video frames to a {@link VideoProcessor} for drawing to the view.
 *
 * <p>This view must be created programmatically, as it is necessary to specify whether a context
 * supporting protected content should be created at construction time.
 */
public final class VideoProcessingGLSurfaceView extends GLSurfaceView {

  /** 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 frameTimestampUs The presentation timestamp of the frame, in microseconds.
     */
    void draw(int frameTexture, long frameTimestampUs);
  }

  private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;

  private final VideoRenderer renderer;
  private final Handler mainHandler;

  @Nullable private SurfaceTexture surfaceTexture;
  @Nullable private Surface surface;
  @Nullable private Player.VideoComponent videoComponent;

  /**
   * 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}.
   * @param requireSecureContext Whether a GL context supporting protected content should be
   *     created, if supported by the device.
   * @param videoProcessor Processor that draws to the view.
   */
  @SuppressWarnings(\"InlinedApi\")
  public VideoProcessingGLSurfaceView(
      Context context, boolean requireSecureContext, VideoProcessor videoProcessor) {
    super(context);
    renderer = new VideoRenderer(videoProcessor);
    mainHandler = new Handler();
    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(renderer);
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
  }

  /**
   * Attaches or detaches (if {@code newVideoComponent} is {@code null}) this view from the video
   * component of the player.
   *
   * @param newVideoComponent The new video component, or {@code null} to detach this view.
   */
  public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
    if (newVideoComponent == videoComponent) {
      return;
    }
    if (videoComponent != null) {
      if (surface != null) {
        videoComponent.clearVideoSurface(surface);
      }
      videoComponent.clearVideoFrameMetadataListener(renderer);
    }
    videoComponent = newVideoComponent;
    if (videoComponent != null) {
      videoComponent.setVideoFrameMetadataListener(renderer);
      videoComponent.setVideoSurface(surface);
    }
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    // Post to make sure we occur in order with any onSurfaceTextureAvailable calls.
    mainHandler.post(
        () -> {
          if (surface != null) {
            if (videoComponent != null) {
              videoComponent.setVideoSurface(null);
            }
            releaseSurface(surfaceTexture, surface);
            surfaceTexture = null;
            surface = null;
          }
        });
  }

  private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
    mainHandler.post(
        () -> {
          SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
          Surface oldSurface = VideoProcessingGLSurfaceView.this.surface;
          this.surfaceTexture = surfaceTexture;
          this.surface = new Surface(surfaceTexture);
          releaseSurface(oldSurfaceTexture, oldSurface);
          if (videoComponent != null) {
            videoComponent.setVideoSurface(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 GLSurfaceView.Renderer, VideoFrameMetadataListener {

    private final VideoProcessor videoProcessor;
    private final AtomicBoolean frameAvailable;
    private final TimedValueQueue<Long> sampleTimestampQueue;

    private int texture;
    @Nullable private SurfaceTexture surfaceTexture;

    private boolean initialized;
    private int width;
    private int height;
    private long frameTimestampUs;

    public VideoRenderer(VideoProcessor videoProcessor) {
      this.videoProcessor = videoProcessor;
      frameAvailable = new AtomicBoolean();
      sampleTimestampQueue = new TimedValueQueue<>();
      width = -1;
      height = -1;
    }

    @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);
      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 surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
        surfaceTexture.updateTexImage();
        long lastFrameTimestampNs = surfaceTexture.getTimestamp();
        Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
        if (frameTimestampUs != null) {
          this.frameTimestampUs = frameTimestampUs;
        }
      }

      videoProcessor.draw(texture, frameTimestampUs);
    }

    @Override
    public void onVideoFrameAboutToBeRendered(
        long presentationTimeUs,
        long releaseTimeNs,
        @NonNull Format format,
        @Nullable MediaFormat mediaFormat) {
      sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs);
    }
  }
}

bitmap_overlay_video_processor_vertex.glsl

attribute vec2 a_position;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;
void main() {
 gl_Position = vec4(a_position.x, a_position.y, 0, 1);
 v_texcoord = a_texcoord;
}

bitmap_overlay_video_processor_fragment.glsl

#extension GL_OES_EGL_image_external : require
precision mediump float;
// External texture containing video decoder output.
uniform samplerExternalOES tex_sampler_0;
// Texture containing the overlap bitmap.
uniform sampler2D tex_sampler_1;
// Horizontal scaling factor for the overlap bitmap.
uniform float scaleX;
// Vertical scaling factory for the overlap bitmap.
uniform float scaleY;
varying vec2 v_texcoord;
void main() {
  vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
  vec4 overlayColor = texture2D(tex_sampler_1,
                                vec2(v_texcoord.x * scaleX,
                                     v_texcoord.y * scaleY));
  // Blend the video decoder output and the overlay bitmap.
  gl_FragColor = videoColor * (1.0 - overlayColor.a)
      + overlayColor * overlayColor.a;
}

对于我的应用程序,我不能使用com.google.android.exoplayer2.ui.PlayerView。 但是如果我没有调用 Assertions.checkNotNull(playerView).setPlayer(player); ,那么视频将不会保持它的纵横比。那么如何通过操纵 Shader 代码而不是 PlayerView 以编程方式更正视频的纵横比?

  • @Rabbid76 问题更新:-)

标签: android opengl-es exoplayer exoplayer2.x


【解决方案1】:

没注意到最后的评论,我看到你的设置是错误的,而不是这个

SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setMediaSource(mediaSource);
player.prepare();
player.play();
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
    Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setVideoComponent(
    Assertions.checkNotNull(player.getVideoComponent()));
Assertions.checkNotNull(playerView).setPlayer(player);
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
this.player = player;

尝试这个:

this.player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setMediaSource(mediaSource);
Assertions.checkNotNull(playerView).setPlayer(player); // this needs to happen once during your setup 
player.playWhenReady = true // this is recommended by official docs instead of player.play() always call it before prepare
player.prepare();
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
    Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
videoProcessingGLSurfaceView.setVideoComponent(
    Assertions.checkNotNull(player.getVideoComponent()));
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));

【讨论】:

  • 阅读我的问题的最后一行“对于我的应用程序,我不能使用 com.google.android.exoplayer2.ui.PlayerView。但是如果我没有调用 Assertions.checkNotNull(playerView).setPlayer(player); ,那么视频将不会保留它是纵横比。那么如何通过操纵着色器代码而不是 PlayerView 以编程方式更正视频的纵横比?”
【解决方案2】:

如果您使用的是 xml,则可以将播放器视图添加到约束布局

然后添加属性

app:layout_constraintDimensionRatio="16:9"

在 playerview 的 xml 声明中,例如我使用 styledplayerview

<com.google.android.exoplayer2.ui.StyledPlayerView
    android:id="@+id/home_exoplayer_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    />

编辑:此属性不依赖于 Exoplayer 的版本。

出于您的目的,无论屏幕尺寸或视频尺寸如何,您都有两个选项可以保持纵横比不变。

1)要么将框架布局更改为约束布局,然后在播放器视图中使用“app:layout_constraintDimensionRatio”属性。

用于替换框架布局的示例代码

<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:keepScreenOn="true">

  <com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/player_view"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:surface_type="none"/>

</androidx.constraintlayout.widget.ConstraintLayout>

另一种解决方法是在框架布局内嵌套约束布局。

像这样,

<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:keepScreenOn="true">

    <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">
    
      <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:surface_type="none"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>

【讨论】:

  • 问题是关于 exoplayer 版本“2.12.1”。
猜你喜欢
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-29
  • 2022-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多