Skip to main content
Skip table of contents

Unified API track selection

To test this feature and view the example code, please see the Unified API Example Code Quick Start guide.

Import classes

To use the track selection feature using UPI, the application first needs to import the following.

JAVA
import nagra.otv.upi.*
import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO;
import static com.google.android.exoplayer2.C.TRACK_TYPE_TEXT;
import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO;

Example code

The Example code uses a media controller to demonstrate the track selection feature. The media controller helper class extends the frame layout. It uses the application context to inherit the parent view and creates a control bar widget on top of it. The control bar holds the options to play, pause, seek and select audio, video and text tracks if available in the stream.

The application is responsible for creating and attaching the media controller instance to it after setting the view in the onCreate() Method.

JAVA
@Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    mFrame = findViewById(R.id.frame);

    OTVUPISource source = new OTVUPISource(STREAM_URI, "", "", "", null, null);
    MyUPIEventListener listener = new MyUPIEventListener();
    mIOTVUPIPlayer = OTVUPIPlayerFactory.createPlayer(this, source, listener);
    if (mIOTVUPIPlayer != null) {
      mIOTVUPIPlayer.setView(mFrame);
      initMediaController();
      mMediaController.setMediaPlayer(mIOTVUPIPlayer);
    }
  }

  /*
   * Creates and attaches a MyMediaController to this class
   */
  private void initMediaController() {
    if (mMediaController == null) {
      mMediaController = new MyMediaController(this);

      mMediaController.setEnabled(true);
      mMediaController.addVisibilityListener(this);
    }
    FrameLayout controllerAnchor = mFrame.findViewById(R.id.controller_anchor);
    controllerAnchor.bringToFront();
    mMediaController.setAnchorView(controllerAnchor);
    setVideoClickListener(mFrame);
  }
  
  /*
   * Adds an onClickListener to FrameLayout to display the mediaController when clicked
   *
   * @param mFrame the frame layout
   */
  private void setVideoClickListener(View mFrame) {
    mFrame.setOnClickListener(xView -> {
      if (mMediaController != null) {
        if (!mMediaController.isShowing()) {
          mMediaController.show(5000);
        } else {
          mMediaController.hide();
        }
      }
    });
  }

The application extends the Activity class and implements VisibilityListener interface from the MyMediaController class after enabling the controller to monitor the visibility of the frame widget.

The application will have to override the required event listener APIs, which have already been implemented in the Preparing the listener section, as needed to set or update track information.

JAVA
private class MyUPIEventListener extends OTVUPIEventListener {
    private static final String TAG = "MyUPIEventListener";

    @Override
    public void onLoad(int xDurationMs, int xTrickModeFlags, @NonNull NaturalSize xNaturalSize) {
      OTVLog.d(TAG,"Duration "+ xDurationMs );
      if (mMediaController!= null) { mMediaController.setDuration(xDurationMs); }
    }

    @Override
    public void onTracksChanged(@NonNull List<TrackInfo> xTextTrackList, @NonNull List<TrackInfo> xAudioTrackList, @NonNull List<TrackInfo> xVideoTrackList) {
      OTVLog.d(TAG, "onTracksChanged " + xTextTrackList.toString());
      if (mMediaController!= null) { mMediaController.updateTracks(xTextTrackList, xAudioTrackList, xVideoTrackList); }
    }

    @Override
    public void onProgress(int xCurrentTimeMs, int xCurrentPositionMs, int xPlayableDurationMs, int xSeekableDurationMs) {
      OTVLog.d(TAG, "onProgress: current " + xCurrentPositionMs + ", playable " + xPlayableDurationMs + ", seekable " + xSeekableDurationMs);
      if (mMediaController!= null) { mMediaController.handleVodProgress(xCurrentPositionMs); }
    }

    @Override
    public void onVideoTrackSelected(int index) {
      OTVLog.d(TAG, "onVideoTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setVideoTrackIndex(index); }
    }

    @Override
    public void onAudioTrackSelected(int index) {
      OTVLog.d(TAG, "onAudioTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setAudioTrackIndex(index); }
    }

    @Override
    public void onTextTrackSelected(int index) {
      OTVLog.d(TAG, "onTextTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setTextTrackIndex(index); }
    }
  }

The MyMediaController class initializes the audio, video and text tracks default index. For Audio and Text tracks, the index is initialized to -1, whereas the video track index is always initialized to 0 as the default track always will be on the 0th index. In the case of text tracks, no default tracks are selected by default. This also means that the user can select and deselect the text tracks, but not the audio or video tracks. The track information w.r.t audio, video and text tracks are maintained in a list defined in the MyMediaController class.

JAVA
private ImageButton  mMultiAudio;
private ImageButton  mSubtitles;
private ImageButton  mMultiVideo;

private int          mCurrentVideoTrack = 0;
private int          mCurrentAudioTrack = -1;
private int          mCurrentTextTrack = -1;

private List<IOTVUPIEventListener.TrackInfo> mSubtitleTracks;
private List<IOTVUPIEventListener.TrackInfo> mAudioTracks;
private List<IOTVUPIEventListener.TrackInfo> mVideoTracks;

When the controller is initialized, the listeners are set for onClick and visibility for the audio, video, text track Image Buttons on the control bar.

JAVA
mMultiVideo = root.findViewById(R.id.videos);
if (mMultiVideo != null) {
    mMultiVideo.setOnClickListener(mVideoTrackListener);
    mMultiVideo.setVisibility(setVideosVisibility());
}

mMultiAudio = root.findViewById(R.id.multiaudio);
if (mMultiAudio != null) {
    mMultiAudio.setOnClickListener(mMultiAudioListener);
    mMultiAudio.setVisibility(setMultiAudioVisiblity());
}

mSubtitles = root.findViewById(R.id.subtitles);
if (mSubtitles != null) {
   mSubtitles.setOnClickListener(mSubtitleListener);
   mSubtitles.setVisibility(setSubtitlesVisibility());
}

The UPI player supports track selection if the stream supports multiple tracks in audio, video or text. The UPI has the following methods that can be used for track selection.

JAVA
void setSelectedVideoTrack(int index);
void setSelectedAudioTrack(int index);
void addTextTrack(String url, String mimeType, String language);
void setSelectedTextTrack(int index);

For text tracks, the application needs to add the text tracks first after starting the UPI Player explicitly.

JAVA
if(mIOTVUPIPlayer != null) {
    mIOTVUPIPlayer.start();
}
if (!stream.getSRTSubtitles().isEmpty()) {
    for (int i = 0; i < stream.getSRTSubtitles().size(); i++) {
      SRTSubtitle subs = stream.getSRTSubtitles().get(i);
      mIOTVUPIPlayer.addTextTrack(subs.getUrl(), subs.getMIME(), subs.getLanguage());
    }
}

The application needs to set the respective tracks by passing the index to select the track.

JAVA
if(trackType == OTVTrackInfo.MEDIA_TRACK_TYPE_VIDEO){
   mPlayer.setSelectedVideoTrack(index);
} else if(trackType == OTVTrackInfo.MEDIA_TRACK_TYPE_AUDIO){
   mPlayer.setSelectedAudioTrack(index);
} else if(trackType == OTVTrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
  mPlayer.setSelectedTextTrack(index);
}
Click here to view the full example code.
JAVA
public class MainActivity extends Activity implements MyMediaController.VisibilityListener{
  private final String STREAM_URI = "https://d3bqrzf9w11pn3.cloudfront.net/sintel/sintel_without_adaptationSetSwitch.mpd";
  private FrameLayout mFrame = null;
  private IOTVUPIPlayer mIOTVUPIPlayer;
  private MyMediaController mMediaController;

  private class MyUPIEventListener extends OTVUPIEventListener {
    private static final String TAG = "MyUPIEventListener";

    @Override
    public void onLoad(int xDurationMs, int xTrickModeFlags, @NonNull NaturalSize xNaturalSize) {
      OTVLog.d(TAG,"Duration "+ xDurationMs );
      mMediaController.setDuration(xDurationMs);
    }

    @Override
    public void onTracksChanged(@NonNull List<TrackInfo> xTextTrackList, @NonNull List<TrackInfo> xAudioTrackList, @NonNull List<TrackInfo> xVideoTrackList) {
      OTVLog.d(TAG, "onTracksChanged " + xTextTrackList.toString());
      if (mMediaController!= null) { mMediaController.updateTracks(xTextTrackList, xAudioTrackList, xVideoTrackList); }
    }

    @Override
    public void onProgress(int xCurrentTimeMs, int xCurrentPositionMs, int xPlayableDurationMs, int xSeekableDurationMs) {
      OTVLog.d(TAG, "onProgress: current " + xCurrentPositionMs + ", playable " + xPlayableDurationMs + ", seekable " + xSeekableDurationMs);
      if (mMediaController!= null) { mMediaController.handleVodProgress(xCurrentPositionMs); }
    }

    @Override
    public void onVideoTrackSelected(int index) {
      OTVLog.d(TAG, "onVideoTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setVideoTrackIndex(index); }
    }

    @Override
    public void onAudioTrackSelected(int index) {
      OTVLog.d(TAG, "onAudioTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setAudioTrackIndex(index); }
    }

    @Override
    public void onTextTrackSelected(int index) {
      OTVLog.d(TAG, "onTextTrackSelected " + index);
      if (mMediaController!= null) { mMediaController.setTextTrackIndex(index); }
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    mFrame = findViewById(R.id.frame);

    OTVUPISource source = new OTVUPISource(STREAM_URI, "", "", "", null, null);
    MyUPIEventListener listener = new MyUPIEventListener();
    mIOTVUPIPlayer = OTVUPIPlayerFactory.createPlayer(this, source, listener);
    if (mIOTVUPIPlayer != null) {
      mIOTVUPIPlayer.setView(mFrame);
      initMediaController();
      mMediaController.setMediaPlayer(mIOTVUPIPlayer);
    }
  }

  /*
   * Creates and attaches a MyMediaController to this class
   */
  private void initMediaController() {
    if (mMediaController == null) {
      mMediaController = new MyMediaController(this);

      mMediaController.setEnabled(true);
      mMediaController.addVisibilityListener(this);
    }
    FrameLayout controllerAnchor = mFrame.findViewById(R.id.controller_anchor);
    controllerAnchor.bringToFront();
    mMediaController.setAnchorView(controllerAnchor);
    setVideoClickListener(mFrame);
  }


  /*
   * Adds an onClickListener to FrameLayout to display the mediaController when clicked
   *
   * @param mFrame the frame layout
   */
  private void setVideoClickListener(View mFrame) {
    mFrame.setOnClickListener(xView -> {
      if (mMediaController != null) {
        if (!mMediaController.isShowing()) {
          mMediaController.show(5000);
        } else {
          mMediaController.hide();
        }
      }
    });
  }

  /*
   * This code is only necessary if you want to switch between different layouts or change config
   * values such as video display area between rotations. As long as androidManifest contains
   * android:configChanges="orientation|screenSize" then your view/player should not be destroyed on rotation.
   * see https://developer.android.com/guide/topics/resources/runtime-changes for more information
   */
  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mIOTVUPIPlayer.onConfigurationChanged(newConfig);
    mIOTVUPIPlayer.detachPlayerView();
    mIOTVUPIPlayer.setView(mFrame);
    initMediaController();
    mMediaController.setMediaPlayer(mIOTVUPIPlayer);
  }

  @Override
  public void onPause() {
    super.onPause();
    if (mIOTVUPIPlayer != null) {
      mIOTVUPIPlayer.pause();
    }
  }

  @Override
  public void onResume() {
    super.onResume();
    if (mIOTVUPIPlayer != null) {
      mIOTVUPIPlayer.play();
    }
  }

  @Override
  public void visibilityChanged(int visibility) {
  }
}
JAVA
import static com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO;
import static com.google.android.exoplayer2.C.TRACK_TYPE_TEXT;
import static com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO;

public class MyMediaController extends FrameLayout {
    private static final String  TAG                  = "MyMediaController";

    private List<VisibilityListener> visibilityListeners = new ArrayList<>();

    private ImageButton  mPauseButton;
    private ImageButton  mMultiAudio;
    private ImageButton  mSubtitles;
    private ImageButton  mMultiVideo;
    private LinearLayout mSeekBarWrapper;
    private TextView     mEndTime;
    private TextView     mCurrentTime;
    private SeekBar      mProgress;
    private boolean      mShowing;
    private boolean      mSeekBarClicked  = false;
    private boolean      mSeekBarChanging = false;
    private Context      mContext;
    private ViewGroup    mAnchor;
    private View         mRoot;
    private long         mDuration;
    private long         mCurrentPosition;
    private int          mCurrentVideoTrack = 0;
    private int          mCurrentAudioTrack = -1;
    private int          mCurrentTextTrack = -1;
    private boolean      isPlaying = true;


    private List<IOTVUPIEventListener.TrackInfo> mSubtitleTracks;
    private List<IOTVUPIEventListener.TrackInfo> mAudioTracks;
    private List<IOTVUPIEventListener.TrackInfo> mVideoTracks;

    private static final int  DEFAULT_TIMEOUT  = 4500;
    private static final int  FADE_OUT         = 1;
    private static final int  SHOW_PROGRESS    = 2;

    private IOTVUPIPlayer mPlayer;

    public MyMediaController(@NonNull Context context) {
        super(context);
        //can be used to hide buttons that should only display in primary view like fullscreen
        mContext = context;
        mRoot = this;
    }

    @Override
    public void onFinishInflate() {
        if (mRoot != null) {
            initController(mRoot);
        }
        super.onFinishInflate();
    }

    private void initController(View root) {
        OTVLog.w(TAG, "controller init entered");

        mProgress = root.findViewById(R.id.mediacontroller_progress);
        if (mProgress != null) {
            mProgress.setOnSeekBarChangeListener(mSeekListener);
            mProgress.setMax(1000);
        }

        mSeekBarWrapper = root.findViewById(R.id.seek_bar_wrapper);
        if (mSeekBarWrapper != null) {
            mSeekBarWrapper.setOnClickListener(mSeekClickListener);
        }

        mPauseButton = root.findViewById(R.id.play_btn);
        if (mPauseButton != null) {
            mPauseButton.setOnClickListener(mPauseListener);
        }

        if(isPlaying && mPauseButton != null){
            mPauseButton.setImageResource(R.drawable.pause_light);
        }


        mMultiVideo = root.findViewById(R.id.videos);
        if (mMultiVideo != null) {
            mMultiVideo.setOnClickListener(mVideoTrackListener);
            mMultiVideo.setVisibility(setVideosVisibility());
        }

        mMultiAudio = root.findViewById(R.id.multiaudio);
        if (mMultiAudio != null) {
            mMultiAudio.setOnClickListener(mMultiAudioListener);
            mMultiAudio.setVisibility(setMultiAudioVisiblity());
        }

        mSubtitles = root.findViewById(R.id.subtitles);
        if (mSubtitles != null) {
            mSubtitles.setOnClickListener(mSubtitleListener);
            mSubtitles.setVisibility(setSubtitlesVisibility());
        }

        mEndTime = root.findViewById(R.id.time_video_length);
        mCurrentTime = root.findViewById(R.id.time_current);


    }

    public void setMediaPlayer(IOTVUPIPlayer player) {
        mPlayer = player;
        updateButtons();
    }

    public void setAnchorView(ViewGroup view) {
        mAnchor = view;

        LayoutParams frameParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);

        removeAllViews();
        View v = makeControllerView();
        addView(v, frameParams);
    }

    public void setVideoTrackIndex(int index) {
        mCurrentVideoTrack = index;
    }

    public void setAudioTrackIndex(int index){
        mCurrentAudioTrack = index;
    }

    public void setTextTrackIndex(int index){
        mCurrentTextTrack = index;
    }

    /*
     * Create the view that holds the widgets that control playback. Derived classes can override this
     * to create their own.
     *
     * @return The controller view.
     *
     * @hide This doesn't work as advertised
     */
    protected View makeControllerView() {
        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mRoot = inflate.inflate(R.layout.my_media_controller, null);

        initController(mRoot);
        return mRoot;
    }

    /*
     * Show the controller on screen. It will go away automatically after 'timeout' milliseconds of
     * inactivity.
     *
     * @param timeout
     *     The timeout in milliseconds. Use 0 to show the controller until hide() is called.
     */
    public void show(int timeout) {
        if (!mShowing) {

            if (mPauseButton != null) {
                mPauseButton.requestFocus();
            }

            LinearLayout.LayoutParams tlp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    Gravity.BOTTOM);
            ViewGroup parentAnchor = (ViewGroup) getParent();
            if (parentAnchor != null) {
                parentAnchor.removeView(this);
            }
            mAnchor.addView(this, tlp);
            mShowing = true;

            for (VisibilityListener listener : visibilityListeners) {
                listener.visibilityChanged(VISIBLE);
            }
        }

        updateButtons();

        Message msg = mHandler.obtainMessage(FADE_OUT);
        if (timeout != 0) {
            mHandler.removeMessages(FADE_OUT);
            mHandler.sendMessageDelayed(msg, timeout);
        }
    }

    public boolean isShowing() {
        return mShowing;
    }

    /*
     * Remove the controller from the screen.
     */
    public void hide() {
        if (mAnchor == null) {
            return;
        }
        mSeekBarChanging = false;
        mSeekBarClicked = false;
        mSeekBarWrapper.setSelected(false);
        if (mShowing) {
            try {
                mAnchor.removeView(this);
            } catch (IllegalArgumentException ex) {
                Log.w("MediaController", "already removed");
            }
            mShowing = false;
        }
        for (VisibilityListener listener : visibilityListeners) {
            listener.visibilityChanged(GONE);
        }
    }

    private int setVideosVisibility() {
        if (mVideoTracks != null && mVideoTracks.size() > 1) {
            return VISIBLE;
        } else {
            return GONE;
        }
    }

    private int setMultiAudioVisiblity() {
        if (mAudioTracks != null && mAudioTracks.size() > 1) {
            return VISIBLE;
        } else {
            return GONE;
        }
    }

    private int setSubtitlesVisibility() {
        if (mSubtitleTracks != null && !mSubtitleTracks.isEmpty()) {
            return VISIBLE;
        } else {
            return GONE;
        }
    }

    private AlertDialog createTrackDialog(String xTitle,
                                          int xTracksType,
                                          List<IOTVUPIEventListener.TrackInfo> xTracks,
                                          int xCurrentTrack,
                                          boolean xCanSelectNoTrack) {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);

        // If there's a "no tracks" option, apply this offset so the expected track gets selected
        final int disableButtonOffset = xCanSelectNoTrack ? 1 : 0;

        ArrayList<String> trackItems = new ArrayList<>();
        if (xCanSelectNoTrack) {
            trackItems.add("Off");
        }

        for (int index = 0; index < xTracks.size(); index++) {
            IOTVUPIEventListener.TrackInfo track = xTracks.get(index);
            String displayTrackName = track.mTitle;
            trackItems.add(displayTrackName);
        }

        final int selectedItem = xCurrentTrack + disableButtonOffset;
        builder.setTitle(xTitle)
                .setSingleChoiceItems(trackItems.toArray(new String[0]), selectedItem, (dialog, which) -> {
                    if (xCanSelectNoTrack && which == 0 && selectedItem != 0) {
                        // Indices including 'off'
                        if (xTracksType == TRACK_TYPE_VIDEO) {
                            mPlayer.setSelectedVideoTrack(selectedItem);
                        } else if (xTracksType == TRACK_TYPE_AUDIO) {
                            mPlayer.setSelectedAudioTrack(selectedItem - 1);
                        } else if (xTracksType == TRACK_TYPE_TEXT) {
                            mPlayer.setSelectedTextTrack(-1);
                        }
                    } else if (which - disableButtonOffset >= 0) {
                        // Indices not including 'off'
                        if (xTracksType == TRACK_TYPE_VIDEO) {
                            mPlayer.setSelectedVideoTrack(which - disableButtonOffset);
                        } else if (xTracksType == TRACK_TYPE_AUDIO) {
                            mPlayer.setSelectedAudioTrack(which - disableButtonOffset);
                        } else if (xTracksType == TRACK_TYPE_TEXT) {
                            mPlayer.setSelectedTextTrack(which - disableButtonOffset);
                        }
                    }
                })
                .setPositiveButton(R.string.done, (dialog, which) -> dialog.dismiss());
        return builder.create();
    }

    @SuppressLint("HandlerLeak")
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case FADE_OUT:
                    hide();
                    break;
                case SHOW_PROGRESS:
                    if (mCurrentTime != null) {
                        mCurrentTime.setText(stringForTime((int)mCurrentPosition));
                    }

                    break;
                default:
                    break;
            }
        }
    };

    /*
     * Used to apply the changes during SeekBar drag events
     */
    private void saveProgressChange(SeekBar bar) {
        mSeekBarChanging = false;
        long newPosition = 0;

        if (mDuration > 0) {
          newPosition = (mDuration * bar.getProgress() / bar.getMax()) / 1000;
        }
        mPlayer.seek((int) newPosition);
        show(DEFAULT_TIMEOUT);
    }


    /*
     * Turns time values into properly formatted strings based on length
     */
    private String stringForTime(int timeMs) {
        // Live video windows are specified in negative values
        String prefix = "";
        if ((timeMs < 0)) {
            timeMs = -timeMs;
            prefix = "-";
        }
        return String.format(Locale.ENGLISH,
                "%s%02d:%02d:%02d",
                prefix,
                TimeUnit.MILLISECONDS.toHours(timeMs),
                TimeUnit.MILLISECONDS.toMinutes(timeMs) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(
                        timeMs)),
                TimeUnit.MILLISECONDS.toSeconds(timeMs) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
                        timeMs)));
    }

  /*
   * Sets the given duration of a stream.
   * @param duration The long representation of the stream duration in milliseconds
   */
    public void setDuration(long duration){
        mDuration = duration;
        if (mEndTime != null) {
            mEndTime.setText(stringForTime((int)duration));
            mEndTime.setOnClickListener(null);
            mEndTime.setClickable(false);
            mEndTime.setAlpha(1);
            mEndTime.setFocusable(false);
        }
    }

  /*
   * Updates the stream progress in the control bar.
   * @param position The current xPositionMs within the stream
   */
    public long handleVodProgress(long position) {
        long pos = 1000L * position / mDuration;
        mProgress.setProgress((int) pos);

        mCurrentPosition = position;
        Message msg = mHandler.obtainMessage(SHOW_PROGRESS);
        mHandler.sendMessage(msg);

        return position;
    }


    private final OnClickListener mPauseListener = view -> {
        OTVLog.w(TAG, "play/pause pressed");

        if(isPlaying) {
          mPlayer.setPaused(true);
          mPauseButton.setImageResource(R.drawable.play_light);
          isPlaying = false;
        }
        else
        {
          mPlayer.setPaused(false);
          mPauseButton.setImageResource(R.drawable.pause_light);
          isPlaying = true;
        }
      updateButtons();
    };

    public void updateButtons() {
        if (mRoot == null || mPauseButton == null || mPlayer == null) {
            return;
        }

        mMultiVideo.setVisibility(setVideosVisibility());
        mSubtitles.setVisibility(setSubtitlesVisibility());
        mMultiAudio.setVisibility(setMultiAudioVisiblity());
    }

    public void updateTracks(List<IOTVUPIEventListener.TrackInfo> xTextTracks, List<IOTVUPIEventListener.TrackInfo> xAudioTracks, List<IOTVUPIEventListener.TrackInfo> xVideoTracks) {
        mSubtitleTracks = xTextTracks;
        mSubtitles.setVisibility(setSubtitlesVisibility());
        if (mSubtitles.getVisibility() == VISIBLE) {
            mSubtitles.setOnClickListener(mSubtitleListener);
        }
        mAudioTracks = xAudioTracks;
        mMultiAudio.setVisibility(setMultiAudioVisiblity());
        if (mMultiAudio.getVisibility() == VISIBLE) {
            mMultiAudio.setOnClickListener(mMultiAudioListener);
        }
        mVideoTracks = xVideoTracks;
        mMultiVideo.setVisibility(setVideosVisibility());
        if (mMultiVideo.getVisibility() == VISIBLE) {
            mMultiVideo.setOnClickListener(mVideoTrackListener);
        }
    }

    // There are two scenarios that can trigger the seekbar listener to trigger:
    //
    // The first is the user using the touchpad to adjust the position of the
    // seekbar's thumb. In this case onStartTrackingTouch is called followed by
    // a number of onProgressChanged notifications, concluded by
    // onStopTrackingTouch.
    // We're setting the field " mSeekBarChanging" to true for the duration of the
    // dragging
    // session to avoid jumps in the position in case of ongoing playback.
    //
    // The second scenario involves the user operating the scroll ball, in this
    // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
    // we will simply apply the updated position without suspending regular
    // updates.
    private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
        public void onStartTrackingTouch(SeekBar bar) {
            show(3600000);

            mSeekBarChanging = true;

            // By removing these pending progress messages we make sure
            // that a) we won't update the progress while the user adjusts
            // the seekbar and b) once the user is done dragging the thumb
            // we will post one of these messages to the queue again and
            // this ensures that there will be exactly one message queued up.
            mHandler.removeMessages(SHOW_PROGRESS);
        }

        public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
            if (mPlayer == null) {
                return;
            }

            long newPosition;

            if (mDuration > 0) {
                newPosition = (mDuration * progress) / 1000L;
                //Warning, not seekTo here to avoid seek repeatedly
                if (mCurrentTime != null) {
                    mCurrentTime.setText(stringForTime((int) newPosition));
                }

            }
        }

        public void onStopTrackingTouch(SeekBar bar) {
            saveProgressChange(bar);
        }
    };


    private final OnClickListener mSeekClickListener = view -> {
        mSeekBarClicked = !mSeekBarClicked;
        mSeekBarChanging = mSeekBarClicked;
        view.setSelected(!view.isSelected());
        view.requestLayout();

        if (!mSeekBarChanging) {
            saveProgressChange(mProgress);
        }
    };

    private final OnClickListener mVideoTrackListener = view -> {
        AlertDialog dialog = createTrackDialog("Set Video Track", TRACK_TYPE_VIDEO, mVideoTracks, mCurrentVideoTrack, false);
        dialog.show();
        show(DEFAULT_TIMEOUT);
    };

    private final OnClickListener mMultiAudioListener = view -> {
        AlertDialog dialog = createTrackDialog("Set Audio Track", TRACK_TYPE_AUDIO, mAudioTracks, mCurrentAudioTrack, false);
        dialog.show();
        show(DEFAULT_TIMEOUT);
    };

    private final OnClickListener mSubtitleListener = view -> {
        AlertDialog dialog = createTrackDialog("Set Subtitle Track", TRACK_TYPE_TEXT, mSubtitleTracks, mCurrentTextTrack, true);
        dialog.show();
        show(DEFAULT_TIMEOUT);
    };
    public interface VisibilityListener {
        void visibilityChanged(int visibility);
    }
    public void addVisibilityListener(VisibilityListener listener) {
        visibilityListeners.add(listener);
    }
}


JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.