简体   繁体   中英

Keep Aspect Ratio Exoplayer without using com.google.android.exoplayer2.ui.PlayerView

I am currently working on an OpenGL demo project of exoplayer version "2.12.1".

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;
}

For my application I cannot use com.google.android.exoplayer2.ui.PlayerView . But if i did not called Assertions.checkNotNull(playerView).setPlayer(player); , then the video will not keep it's aspect ratio.So How can I programmatically correct the aspect ratio of the video by manipulating Shader code instead of PlayerView?

Just get the layoutparams from your playerView, check my Kotlin code:

  private fun setupPlayerRatio() {
   val lp = binding.playerView.layoutParams
   val width = getScreenWidth()
   val height = getScreenHeight()
   val smallerValue = if(width > height) height else width
   lp.width = smallerValue
   lp.height = (smallerValue.toFloat() * aspectRatio).roundToInt()
   binding.videoView.layoutParams = lp
   binding.videoView.requestLayout()
 }

The variable aspectRatio is 2 / 3 for portrait and for fullscreen I calculate it like this:

  private fun calcAspectRatio() {
   val metrics = resources.displayMetrics
   aspectRatio = metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat()
 }

If you are using xml, you can add your playerview to contraint layout

and then add attribute

app:layout_constraintDimensionRatio="16:9"

inside the xml declaration of playerview, for example I am using styledplayerview with

<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"
    />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM