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 Preparing the listener, 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 for audio, video and text tracks is 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);
}
}