Replacing the ExoPlayer SurfaceView with a custom SurfaceView

This isn't a code copy/pasta post.

The ExoPlayer library is a great place to start building your own video player. It's extensible in the right places and gives you great customization options, while giving you a bucket-load of features out-of-the-box.

However, one tiny aspect it doesn't easily lend to customization is the SurfaceView. Another one is the show/hide behaviour of the controller. Both of these issues are really nitpicks considering that if you don't like the default UX, you are free to turn off defaults and implement your own UX. But that can seem tedious if all you want to do is tiny tweaks.

In my case, I was tasked with using ZoomSurfaceView as the SurfaceView for video rendered so that users can zoom as well as pan. ExoPlayer does provide an option to zoom via the included AspectRatioFrameLayout but without an option to pan. The ZoomLayout engine provides really great features to zoom and pan to certain portions of the video. With a custom produced high resolution, say 4K or 8K, video, you can set the video player to consume it in vertical orientation by zooming and panning to certain portions at certain times, say driven by a JSON. Or coupled this with the device's sensors, and you can move your phone to pan a 2-d around. I'm sure there are more use-cases.

Issue is that the SurfaceView isn't part of the ExoPlayer PlayerView layout, and is instead created internally based on configuration usually supplied based on device capabilities or configured in the layout with app:surface_type.

So how do we go about supplying our own SurfaceView?

  1. Extend the PlayerView to your own class, say ZoomPlayerView
  2. Set app:surface_type to none in the layout file. This prevents the PlayerView class from instantiating and using a SurfaceView

3. Create an exo_player_view.xml layout file and copy over contents from the default layout for PlayerView. Add your custom SurfaceView to this layout file below the comment. Alternatively, you can create/inflate your custom SurfaceView in the custom PlayerView view and add it programmatically with contentFrame.addView(surfaceView, 0) like the actual PlayerView does in its constructor.

4. In your custom view constructor, initialize your custom SurfaceView. For e.g., for ZoomSurfaceView it could be something like this:

5. Go through the code in the PlayerView.java class. In your custom extended class, override all parent methods that make use of the surfaceView instance variable, and apply the same operations to your own surface view (surfaceView instance variable in the parent will be null).

This should give you some basic idea on how to manage replacing the default SurfaceView. It's not a whole lot different from other cases where you want to override functionality in a parent class, but have no access to the private variable.