title: ExoPlayer2 Zoomable View?
body: <p>There are what appear to be several clones of the same answer for the first version of Exoplayer and the original Android media player, but they do not compile on Exoplayer2, which reorganized quite a bit of the internal code.</p>
  
  <p>A reasonably-diligent search has not found anything in the way of a library or example code to do this (e.g. pinch zoom and scroll, etc.)  There's plenty of code around to do it for still images (e.g. retrieved through Picasso, etc)</p>
  
  <p>Does anyone have a sample that will build and work with ExoPlayer2?</p>
  
  <p>Thanks in advance!</p>
  
  <p>Update: The problem appears to be that I cannot either subclass or attach a VideoListener to a SimpleExoPlayer instance; attempting to do so leaves you with nothing, as the instance has already attached its own listener which pays exactly zero attention to aspect ratio when a TextureView is involved.  This makes the video completely unusable; a listener could correct that quite easily, but there appears to be no way to attach it (the methods to do so are marked deprecated, and if you try to use them anyway you get no video output.)</p>
  
  <p>This code will paint and run the ua.pohohalo.zoomabletextureview (or just a plain TextureView) but I cannot attach a videolistener to it <em>and</em> the default, when it initializes, fits the video to the view size <em>vertically</em> in portrait mode which destroys the aspect ratio.  It also has serious glitches if you resize the video below the display window size but I can test for and fix that in polohalo's code.  What I've not figured out how to do is to get the original display to honor the original aspect ratio or to attach a VideoListener to set it on init -- it works fine if I use a PlayerView, but that's not able to be extended to support translations.  The "VideoListener" prototype in this codeblock should fix the aspect ratio problem -- that's what I've been unable to attach or find a way to set a flag on the original view (which would also do the job) that tells ExoPlayer to honor the original aspect ratio and fit within the screen size.</p>
  
  <p>The call to simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT), which works on a PlayerView, is not valid on a TextureView -- it appears that the mode defaults to RESIZE_MODE_FILL and I cannot find a method to set it to FIT.</p>
  
  <pre><code>package net.cudasystems.android.videotest;
  
  import android.graphics.Matrix;
  import android.net.Uri;
  import android.os.Bundle;
  import android.support.v7.app.AppCompatActivity;
  import android.util.Log;
  import android.view.TextureView;
  import com.google.android.exoplayer2.DefaultLoadControl;
  import com.google.android.exoplayer2.DefaultRenderersFactory;
  import com.google.android.exoplayer2.ExoPlayerFactory;
  import com.google.android.exoplayer2.SimpleExoPlayer;
  import com.google.android.exoplayer2.source.ExtractorMediaSource;
  import com.google.android.exoplayer2.source.MediaSource;
  import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
  import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
  
  import com.google.android.exoplayer2.util.Util;
  import com.google.android.exoplayer2.video.VideoListener;
  
  
  public class MainActivity extends AppCompatActivity {
  
      private String mURL = "http://point-at-an-mp4-file";
  
      TextureView mPlayerView;
  
      SimpleExoPlayer player = null;
  
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
  
          mPlayerView = findViewById(R.id.video_view2);
      }
  
      private void initializePlayer() {
  
          DefaultRenderersFactory renderersFactory =
                  new DefaultRenderersFactory(this, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
  
          VideoListener mVideoListener = new VideoListener() {
              @Override
              public void onRenderedFirstFrame() {
  
              }
              @Override
              public void onVideoSizeChanged(int width, int height, int rotation, float pixelWidthHeightRatio ) {
                  String TAG = "VideoSizeChange";
                  int viewWidth = mPlayerView.getWidth();
                  int viewHeight = mPlayerView.getHeight();
                  double aspectRatio = (double) height / width;
  
                  int newWidth, newHeight;
                  if (viewHeight &gt; (int) (viewWidth * aspectRatio)) {
                      // limited by narrow width; restrict height
                      newWidth = viewWidth;
                      newHeight = (int) (viewWidth * aspectRatio);
                  } else {
                      // limited by short height; restrict width
                      newWidth = (int) (viewHeight / aspectRatio);
                      newHeight = viewHeight;
                  }
                  int xoff = (viewWidth - newWidth) / 2;
                  int yoff = (viewHeight - newHeight) / 2;
                  Log.v(TAG, "video=" + width + "x" + height +
                          " view=" + viewWidth + "x" + viewHeight +
                          " newView=" + newWidth + "x" + newHeight +
                          " off=" + xoff + "," + yoff);
  
                  Matrix txform = new Matrix();
                  mPlayerView.getTransform(txform);
                  txform.setScale((float) newWidth / viewWidth, (float) newHeight / viewHeight);
                  txform.postTranslate(xoff, yoff);
                  mPlayerView.setTransform(txform);
              }
  
          };
  
          player = ExoPlayerFactory.newSimpleInstance(
                  renderersFactory,
                  new DefaultTrackSelector(), new DefaultLoadControl());
  
  
          player.setVideoTextureView(mPlayerView);
  
          // mPlayerView.setPlayer(player);
  
          player.setPlayWhenReady(true);
  
          Uri uri = Uri.parse(mURL);
          MediaSource mediaSource = buildMediaSource(uri);
          player.prepare(mediaSource, true, true);
      }
  
  
      private MediaSource buildMediaSource(Uri uri) {
  
          return new ExtractorMediaSource.Factory(
                  new DefaultHttpDataSourceFactory("exoplayer-codelab")).
                  createMediaSource(uri);
      }
  
  
      @Override
      public void onStart() {
          super.onStart();
          if (Util.SDK_INT &gt; 23) {
              if (player == null) {
                  initializePlayer();
              }
          }
      }
  
      @Override
      public void onResume() {
          super.onResume();
          if ((Util.SDK_INT &lt;= 23 || player == null)) {
              initializePlayer();
          }
      }
      @Override
      public void onPause() {
          super.onPause();
          if (Util.SDK_INT &lt;= 23) {
              releasePlayer();
          }
      }
      @Override
      public void onStop() {
          super.onStop();
          if (Util.SDK_INT &gt; 23) {
              releasePlayer();
          }
      }
      private void releasePlayer() {
          if (player != null) {
              player.release();
              player = null;
          }
      }
  
  }
  </code></pre>
  
  <p>and the XML file to go with it....  The zoomable declaration is "on" right now but the code can easily use either the non-zoomable one or the PlayerView (by changing the type and not attaching the texture); that one works perfectly well, including properly handling rotation.</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:id="@+id/root"
      android:focusable="true"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:keepScreenOn="true"&gt;
  
      &lt;TextureView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:visibility="gone"
          android:id="@+id/video_view3" /&gt;
  
      &lt;ua.polohalo.zoomabletextureview.ZoomableTextureView
          android:id="@+id/video_view2"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="top"
          app:maxScale="4"/&gt;
  
      &lt;com.google.android.exoplayer2.ui.PlayerView
          android:id="@+id/video_view"
          android:visibility="gone"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          app:use_controller="false"/&gt;
  
  &lt;/FrameLayout&gt;
  </code></pre>
  
  <p>Update: After much head-banging the following code works EXCEPT if you try to use it in a fragment, in which case the TextView extension has problems due to how minimum and maximum scale values get picked up.  The obvious "hack" answer is to check for minScale = 0 and force it to 1.0 if you find it un-initialized.</p>
  
  <p>Hope this helps someone else out.</p>
  
  <pre><code>package net.cudasystems.android.videotest;
  
  import android.net.Uri;
  import android.os.Bundle;
  import android.support.constraint.ConstraintLayout;
  import android.support.v7.app.AppCompatActivity;
  import android.util.DisplayMetrics;
  import android.view.TextureView;
  
  import com.google.android.exoplayer2.DefaultLoadControl;
  import com.google.android.exoplayer2.DefaultRenderersFactory;
  import com.google.android.exoplayer2.ExoPlayerFactory;
  import com.google.android.exoplayer2.SimpleExoPlayer;
  import com.google.android.exoplayer2.source.ExtractorMediaSource;
  import com.google.android.exoplayer2.source.MediaSource;
  import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
  import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
  
  import com.google.android.exoplayer2.util.Util;
  
  
  public class MainActivity extends AppCompatActivity {
  
      private String mURL = "http://set-to-an-mp4-URL"
  
      TextureView mPlayerView;
  
      SimpleExoPlayer player = null;
  
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
  
          mPlayerView = findViewById(R.id.video_view);
      }
  
      private void initializePlayer() {
  
          DefaultRenderersFactory renderersFactory =
                  new DefaultRenderersFactory(this, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
  
  
          player = ExoPlayerFactory.newSimpleInstance(
                  renderersFactory,
                  new DefaultTrackSelector(), new DefaultLoadControl());
  
  
          DisplayMetrics metrics = new DisplayMetrics();
          getWindowManager().getDefaultDisplay().getMetrics(metrics);
  
  
          // Make sure the initial aspect ratio is 16:9 (otherwise a TextureView init's to the LARGER of
          // the two dimensions of the video irrespective of the orientation setting and screws the aspect ratio!)
          int width = metrics.widthPixels;
          int newHeight = (width * 9) / 16;
          mPlayerView.setLayoutParams(new ConstraintLayout.LayoutParams(width, newHeight));
          mPlayerView.invalidate();
  
          player.setVideoTextureView(mPlayerView);
  
          player.setPlayWhenReady(true);
  
          Uri uri = Uri.parse(mURL);
          MediaSource mediaSource = buildMediaSource(uri);
          player.prepare(mediaSource, true, true);
      }
  
  
      private MediaSource buildMediaSource(Uri uri) {
  
          return new ExtractorMediaSource.Factory(
                  new DefaultHttpDataSourceFactory("exoplayer-codelab")).
                  createMediaSource(uri);
      }
  
  
      @Override
      public void onStart() {
          super.onStart();
          if (Util.SDK_INT &gt; 23) {
              if (player == null) {
                  initializePlayer();
              }
          }
      }
  
      @Override
      public void onResume() {
          super.onResume();
          if ((Util.SDK_INT &lt;= 23 || player == null)) {
              initializePlayer();
          }
      }
      @Override
      public void onPause() {
          super.onPause();
          if (Util.SDK_INT &lt;= 23) {
              releasePlayer();
          }
      }
      @Override
      public void onStop() {
          super.onStop();
          if (Util.SDK_INT &gt; 23) {
              releasePlayer();
          }
      }
      private void releasePlayer() {
          if (player != null) {
              player.release();
              player = null;
          }
      }
  
  }
  </code></pre>
  
  <p>And the working XML to go with it:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;android.support.constraint.ConstraintLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:id="@+id/root"
      android:focusable="true"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      android:keepScreenOn="true"&gt;
  
      &lt;ua.polohalo.zoomabletextureview.ZoomableTextureView
          android:id="@+id/video_view"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_gravity="top"
          app:maxScale="4"
          app:minScale="1"/&gt;
  
      &lt;TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Does This work?"
          android:textColor="@android:color/holo_red_dark"
          app:layout_constraintBottom_toBottomOf="parent" /&gt;
  
  &lt;/android.support.constraint.ConstraintLayout&gt;
  </code></pre>