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
Search within Codexpedia
Search the entire web