Android ExoPlayer 2 track selection example

A simple activity for choosing a video url to be played by the ExoPlayer 2.

class MainActivity : AppCompatActivity() {

    // Sample videos, more can be found in the media.exolist.json in the assets folder
    private val hls = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
    private val dash = "http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0"
    private val mp4 = "https://html5demos.com/assets/dizzy.mp4"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn1.setOnClickListener { et_url.setText(hls) }
        btn2.setOnClickListener { et_url.setText(dash) }
        btn3.setOnClickListener { et_url.setText(mp4) }

        btn_play.setOnClickListener {
            val preferExtensionDecoders = false
            val abrAlgorithm = PlayerActivity.ABR_ALGORITHM_DEFAULT
            val intent = Intent(this, PlayerActivity::class.java)

            val videoUrl = et_url.text.toString()
            intent.putExtra(PlayerActivity.PREFER_EXTENSION_DECODERS_EXTRA, preferExtensionDecoders)
            intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm)
            intent.data = Uri.parse(videoUrl)
            intent.action = PlayerActivity.ACTION_VIEW

            // this video url doesn't end with a file extension, therefore it needs a extension override
            // for buildMediaSource(Uri uri, @Nullable String overrideExtension) in the PlayerActivity
            if (videoUrl == dash) {
                intent.putExtra(EXTENSION_EXTRA, ".mpd")
            }

            startActivity(intent)
        }
    }

}

In the PlayerActivity, after the DefaultTrackSelector is initialized by the ExoPlayer, it will be populated with track info of the provided video source. Here is a code snippet to print out the track info to logcat.

    logTracksButton.setOnClickListener( view -> {
      Log.d(TAG, "log tracks clicked");
      MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
      DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();

      for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
        if (TrackSelectionDialog.showTabForRenderer(mappedTrackInfo, rendererIndex)) {
          int trackType = mappedTrackInfo.getRendererType(rendererIndex);
          TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
          Boolean isRendererDisabled = parameters.getRendererDisabled(rendererIndex);
          DefaultTrackSelector.SelectionOverride selectionOverride = parameters.getSelectionOverride(rendererIndex, trackGroupArray);

          Log.d(TAG, "------------------------------------------------------Track item " + rendererIndex + "------------------------------------------------------");
          Log.d(TAG, "track type: " + trackTypeToName(trackType));
          Log.d(TAG, "track group array: " + new Gson().toJson(trackGroupArray));
          for (int groupIndex = 0; groupIndex < trackGroupArray.length; groupIndex++) {
            for (int trackIndex = 0; trackIndex <  trackGroupArray.get(groupIndex).length; trackIndex++) {
              String trackName = new DefaultTrackNameProvider(getResources()).getTrackName(trackGroupArray.get(groupIndex).getFormat(trackIndex));
              Boolean isTrackSupported = mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex) == RendererCapabilities.FORMAT_HANDLED;
              Log.d(TAG, "track item " + groupIndex +": trackName: " + trackName + ", isTrackSupported: " + isTrackSupported);
            }
          }
          Log.d(TAG, "isRendererDisabled: " + isRendererDisabled);
          Log.d(TAG, "selectionOverride: " + new Gson().toJson(selectionOverride));
        }
      }

    });

This is what it will look like from the above code for printing the track info.

PlayerActivity: log tracks clicked
PlayerActivity: ------------------------------------------------------Track item 0------------------------------------------------------
PlayerActivity: track type: TRACK_TYPE_VIDEO
PlayerActivity: track group array: {"hashCode":0,"length":1,"trackGroups":[{"formats":[{"accessibilityChannel":-1,"bitrate":263851,"channelCount":-1,"codecs":"avc1.4d400d","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":234,"id":"0","initializationData":[[0,0,1,39,77,64,13,-87,24,52,63,-14,96,13,65,-128,65,-83,-73,-96,-48,38,94,-9,-64,64,0],[0,0,1,40,-34,9,-120]],"maxInputSize":-1,"metadata":{"entries":[{"variantInfos":[{"audioGroupId":"bipbop_audio","bitrate":263851,"subtitleGroupId":"subs"}]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"video/avc","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":416},{"accessibilityChannel":-1,"bitrate":577610,"channelCount":-1,"codecs":"avc1.4d401e","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":360,"id":"1","initializationData":[[0,0,1,39,77,64,13,-87,24,52,63,-14,96,13,65,-128,65,-83,-73,-96,-48,38,94,-9,-64,64,0],[0,0,1,40,-34,9,-120]],"maxInputSize":-1,"metadata":{"entries":[{"variantInfos":[{"audioGroupId":"bipbop_audio","bitrate":577610,"subtitleGroupId":"subs"}]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"video/avc","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":640},{"accessibilityChannel":-1,"bitrate":915905,"channelCount":-1,"codecs":"avc1.4d401f","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":540,"id":"2","initializationData":[[0,0,1,39,77,64,13,-87,24,52,63,-14,96,13,65,-128,65,-83,-73,-96,-48,38,94,-9,-64,64,0],[0,0,1,40,-34,9,-120]],"maxInputSize":-1,"metadata":{"entries":[{"variantInfos":[{"audioGroupId":"bipbop_audio","bitrate":915905,"subtitleGroupId":"subs"}]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"video/avc","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":960},{"accessibilityChannel":-1,"bitrate":1030138,"channelCount":-1,"codecs":"avc1.4d401f","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":720,"id":"3","initializationData":[[0,0,1,39,77,64,13,-87,24,52,63,-14,96,13,65,-128,65,-83,-73,-96,-48,38,94,-9,-64,64,0],[0,0,1,40,-34,9,-120]],"maxInputSize":-1,"metadata":{"entries":[{"variantInfos":[{"audioGroupId":"bipbop_audio","bitrate":1030138,"subtitleGroupId":"subs"}]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"video/avc","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":1280},{"accessibilityChannel":-1,"bitrate":1924009,"channelCount":-1,"codecs":"avc1.4d401f","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":1080,"id":"4","initializationData":[[0,0,1,39,77,64,13,-87,24,52,63,-14,96,13,65,-128,65,-83,-73,-96,-48,38,94,-9,-64,64,0],[0,0,1,40,-34,9,-120]],"maxInputSize":-1,"metadata":{"entries":[{"variantInfos":[{"audioGroupId":"bipbop_audio","bitrate":1924009,"subtitleGroupId":"subs"}]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"video/avc","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":1920}],"hashCode":0,"length":5}]}
PlayerActivity: track item 0: trackName: 416 × 234, 0.26 Mbps, isTrackSupported: true
PlayerActivity: track item 0: trackName: 640 × 360, 0.58 Mbps, isTrackSupported: true
PlayerActivity: track item 0: trackName: 960 × 540, 0.92 Mbps, isTrackSupported: true
PlayerActivity: track item 0: trackName: 1280 × 720, 1.03 Mbps, isTrackSupported: true
PlayerActivity: track item 0: trackName: 1920 × 1080, 1.92 Mbps, isTrackSupported: true
PlayerActivity: isRendererDisabled: false
PlayerActivity: selectionOverride: null
PlayerActivity: ------------------------------------------------------Track item 1------------------------------------------------------
PlayerActivity: track type: TRACK_TYPE_AUDIO
PlayerActivity: track group array: {"hashCode":0,"length":2,"trackGroups":[{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":2,"codecs":"mp4a.40.2","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":-1,"id":"bipbop_audio:BipBop Audio 1","initializationData":[[19,-112]],"label":"BipBop Audio 1","language":"en","maxInputSize":-1,"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"audio/mp4a-latm","sampleRate":22050,"selectionFlags":5,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":0,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":2,"codecs":"mp4a.40.2","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":0,"height":-1,"id":"bipbop_audio:BipBop Audio 2","initializationData":[[19,-112]],"label":"BipBop Audio 2","language":"en","maxInputSize":-1,"metadata":{"entries":[{"groupId":"bipbop_audio","name":"BipBop Audio 2","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"audio/mp4a-latm","sampleRate":22050,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":0,"length":1}]}
PlayerActivity: track item 0: trackName: English, Stereo, isTrackSupported: true
PlayerActivity: track item 1: trackName: English, Stereo, isTrackSupported: true
PlayerActivity: isRendererDisabled: false
PlayerActivity: selectionOverride: null
PlayerActivity: ------------------------------------------------------Track item 2------------------------------------------------------
PlayerActivity: track type: TRACK_TYPE_TEXT
PlayerActivity: track group array: {"hashCode":-1775022508,"length":9,"trackGroups":[{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":-2035873696,"height":-1,"id":"1/8219","initializationData":[],"maxInputSize":-1,"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"application/cea-608","sampleRate":-1,"selectionFlags":0,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":-2035873138,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":1531687113,"height":-1,"id":"subs:English","initializationData":[],"label":"English","language":"en","maxInputSize":-1,"metadata":{"entries":[{"groupId":"subs","name":"English","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":4096,"rotationDegrees":0,"sampleMimeType":"text/vtt","sampleRate":-1,"selectionFlags":5,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":1531687671,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":-1996913626,"height":-1,"id":"subs:English (Forced)","initializationData":[],"label":"English (Forced)","language":"en","maxInputSize":-1,"metadata":{"entries":[{"groupId":"subs","name":"English (Forced)","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"text/vtt","sampleRate":-1,"selectionFlags":2,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":-1996913068,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":-119866970,"height":-1,"id":"subs:Français","initializationData":[],"label":"Français","language":"fr","maxInputSize":-1,"metadata":{"entries":[{"groupId":"subs","name":"Français","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":4096,"rotationDegrees":0,"sampleMimeType":"text/vtt","sampleRate":-1,"selectionFlags":4,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":-119866412,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":19283972,"height":-1,"id":"subs:Français (Forced)","initializationData":[],"label":"Français (Forced)","language":"fr","maxInputSize":-1,"metadata":{"entries":[{"groupId":"subs","name":"Français (Forced)","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":0,"rotationDegrees":0,"sampleMimeType":"text/vtt","sampleRate":-1,"selectionFlags":2,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":19284530,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":-1638324860,"height":-1,"id":"subs:Español","initializationData":[],"label":"Español","language":"es","maxInputSize":-1,"metadata":{"entries":[{"groupId":"subs","name":"Español","variantInfos":[]}]},"pcmEncoding":-1,"pixelWidthHeightRatio":1.0,"roleFlags":4096,"rotationDegrees":0,"sampleMimeType":"text/vtt","sampleRate":-1,"selectionFlags":4,"stereoMode":-1,"subsampleOffsetUs":9223372036854775807,"width":-1}],"hashCode":-1638324302,"length":1},{"formats":[{"accessibilityChannel":-1,"bitrate":-1,"channelCount":-1,"containerMimeType":"application/x-mpegURL","encoderDelay":0,"encoderPadding":0,"frameRate":-1.0,"hashCode":1239776482,"height":-1,"id":"subs:Español (Forced)","initializationData":[],"label":"Español (Forced)","language":"es","maxInputSize":-1,"metadata":
PlayerActivity: track item 0: trackName: Unknown, isTrackSupported: true
PlayerActivity: track item 1: trackName: English, isTrackSupported: true
PlayerActivity: track item 2: trackName: English, isTrackSupported: true
PlayerActivity: track item 3: trackName: French, isTrackSupported: true
PlayerActivity: track item 4: trackName: French, isTrackSupported: true
PlayerActivity: track item 5: trackName: Spanish, isTrackSupported: true
PlayerActivity: track item 6: trackName: Spanish, isTrackSupported: true
PlayerActivity: track item 7: trackName: Japanese, isTrackSupported: true
PlayerActivity: track item 8: trackName: Japanese, isTrackSupported: true
PlayerActivity: isRendererDisabled: false
PlayerActivity: selectionOverride: {"data":0,"groupIndex":0,"length":1,"reason":2,"tracks":[0]}

To select a track, it requires to override the parameters in the TrackSelector. Here is an example to pick the first track item in the text type, if the video source has text tracks, the following will make the video to display the text(closed caption).

    showTextCaptionsButton.setOnClickListener(view -> {
      Log.d(TAG, "showTextCaptionsButton clicked");

      MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
      DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
      DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
      for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
        int trackType = mappedTrackInfo.getRendererType(rendererIndex);
        if (trackType == C.TRACK_TYPE_TEXT) {
          builder.clearSelectionOverrides(rendererIndex).setRendererDisabled(rendererIndex, false);
          //{"data":0,"groupIndex":1,"length":1,"reason":2,"tracks":[0]}
          int groupIndex = 0;
          int [] tracks = {0};
          int reason = 2;
          int data = 0;
          DefaultTrackSelector.SelectionOverride override = new DefaultTrackSelector.SelectionOverride(groupIndex, tracks, reason, data);

          builder.setSelectionOverride(rendererIndex, mappedTrackInfo.getTrackGroups(rendererIndex), override);
        }
      }

      trackSelector.setParameters(builder);

    });

To remove closed captions, it requires to clear the parameters in the track selector. Here is an example for removing the closed captions.

    removeTextCaptionsButton.setOnClickListener(view -> {
      Log.d(TAG, "removeTextCaptionsButton clicked");
      MappedTrackInfo mappedTrackInfo = Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
      DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
      DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon();
      for (int rendererIndex = 0; rendererIndex < mappedTrackInfo.getRendererCount(); rendererIndex++) {
        int trackType = mappedTrackInfo.getRendererType(rendererIndex);
        if (trackType == C.TRACK_TYPE_TEXT) {
          builder.clearSelectionOverrides(rendererIndex).setRendererDisabled(rendererIndex, true);
        }
      }
      trackSelector.setParameters(builder);
    });

More sample videos:
https://github.com/codexpedia/android_exoplayer_track_selection/blob/master/app/src/main/assets/media.exolist.json

Complete example in Github

Search within Codexpedia

Custom Search

Search the entire web

Custom Search