2

I am trying to add my custom controls to bright cove exo media player for HLS streaming. I am able to play the hls videos perfectly but the default controls are not what I want to use.

Any suggestions.

drulabs
  • 3,071
  • 28
  • 35
  • I have tried BrighcoveMediaController and looking for samples and all. I am currently trying the android media player controller – drulabs Oct 15 '15 at 08:33

1 Answers1

1

First of all you have to hide the default media controller of BrightCove and which can be achieved by the below method

brightcoveVideoView.setMediaController((MediaController) null);

Then you need to design a custom controller as per your needs. A sample is attaching below

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

private MediaPlayerControl  mPlayer;
private Context             mContext;
private ViewGroup           mAnchor;
private View                mRoot;
private ProgressBar         mProgress;
private TextView            mEndTime, mCurrentTime,mTextViewLive;
private boolean             mShowing;
private boolean             mDragging;
private static final int    sDefaultTimeout = 3000;
private static final int    FADE_OUT = 1;
private static final int    SHOW_PROGRESS = 2;
private boolean             mUseFastForward;
private boolean             mFromXml;
private boolean             mListenersSet;
private OnSingleClickListener mNextListener, mPrevListener;
StringBuilder               mFormatBuilder;
Formatter                   mFormatter;
private Button              mPauseButton;
private ImageButton         mFfwdButton;
private ImageButton         mRewButton;
private Button              mNextButton;
private Button              mPrevButton;
private Handler             mHandler = new MessageHandler(this);

public VideoControllerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mRoot = null;
    mContext = context;
    mUseFastForward = true;
    mFromXml = true;

    Log.i(TAG, TAG);
}

public VideoControllerView(Context context, boolean useFastForward) {
    super(context);
    mContext = context;
    mUseFastForward = useFastForward;

    Log.i(TAG, TAG);
}

public VideoControllerView(Context context) {
    this(context, true);

    Log.i(TAG, TAG);
}

@Override
public void onFinishInflate() {
    if (mRoot != null)
        initControllerView(mRoot);
}

public void setMediaPlayer(MediaPlayerControl player) {
    mPlayer = player;
    updatePausePlay();
}

/**
 * Set the view that acts as the anchor for the control view.
 * This can for example be a VideoView, or your Activity's main view.
 * @param view The view to which to anchor the controller when it is visible.
 */
public void setAnchorView(ViewGroup view) {
    mAnchor = view;

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

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

/**
 * 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.media_controller, null);

    initControllerView(mRoot);

    return mRoot;
}

private void initControllerView(View v) {
    mPauseButton = (Button) v.findViewById(R.id.pause);
    if (mPauseButton != null) {
        mPauseButton.requestFocus();
        mPauseButton.setOnClickListener(mPauseListener);
    }

   /* mFullscreenButton = (ImageButton) v.findViewById(R.id.fullscreen);
    if (mFullscreenButton != null) {
        mFullscreenButton.requestFocus();
        mFullscreenButton.setOnClickListener(mFullscreenListener);
    }

    mFfwdButton = (ImageButton) v.findViewById(R.id.ffwd);
    if (mFfwdButton != null) {
        mFfwdButton.setOnClickListener(mFfwdListener);
        if (!mFromXml) {
            mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
        }
    }

    mRewButton = (ImageButton) v.findViewById(R.id.rew);
    if (mRewButton != null) {
        mRewButton.setOnClickListener(mRewListener);
        if (!mFromXml) {
            mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
        }
    }*/

  setPrevNextListeners() is called
    mNextButton = (Button) v.findViewById(R.id.next);
    if (mNextButton != null && !mFromXml && !mListenersSet) {
        mNextButton.setVisibility(View.GONE);
    }
    mPrevButton = (Button) v.findViewById(R.id.previous);
    if (mPrevButton != null && !mFromXml && !mListenersSet) {
        mPrevButton.setVisibility(View.GONE);
    }

    mProgress = (SeekBar) v.findViewById(R.id.mediacontroller_progress);
    if (mProgress != null) {
        if (mProgress instanceof SeekBar) {
            SeekBar seeker = (SeekBar) mProgress;
            seeker.setOnSeekBarChangeListener(mSeekListener);
        }
        mProgress.setMax(1000);
    }

    mEndTime = (TextView) v.findViewById(R.id.time);
    mCurrentTime = (TextView) v.findViewById(R.id.time_current);
    mTextViewLive = (TextView) v.findViewById(R.id.textViewLive);
    mFormatBuilder = new StringBuilder();
    mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());

    installPrevNextListeners();
}

/**
 * Show the controller on screen. It will go away
 * automatically after 3 seconds of inactivity.
 */
public void show() {
    show(sDefaultTimeout);
}

/**
 * Disable pause or seek buttons if the stream cannot be paused or seeked.
 * This requires the control interface to be a MediaPlayerControlExt
 */
private void disableUnsupportedButtons() {
    if (mPlayer == null) {
        return;
    }

    try {
        if (mPauseButton != null && !mPlayer.canPause()) {
            mPauseButton.setEnabled(false);
        }
      /*  if (mRewButton != null && !mPlayer.canSeekBackward()) {
            mRewButton.setEnabled(false);
        }
        if (mFfwdButton != null && !mPlayer.canSeekForward()) {
            mFfwdButton.setEnabled(false);
        }*/
    } catch (IncompatibleClassChangeError ex) {
        // We were given an old version of the interface, that doesn't have
        // the canPause/canSeekXYZ methods. This is OK, it just means we
        // assume the media can be paused and seeked, and so we don't disable
        // the buttons.
    }
}

/**
 * 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 && mAnchor != null) {
        setProgress();
        if (mPauseButton != null) {
            mPauseButton.requestFocus();
        }
        disableUnsupportedButtons();

        FrameLayout.LayoutParams tlp = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                Gravity.BOTTOM
        );

        mAnchor.addView(this, tlp);
        mShowing = true;
    }
    updatePausePlay();
    updateFullScreen();

    // cause the progress bar to be updated even if mShowing
    // was already true.  This happens, for example, if we're
    // paused with the progress bar showing the user hits play.
    mHandler.sendEmptyMessage(SHOW_PROGRESS);

    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;
    }

    try {
        mAnchor.removeView(this);
        mHandler.removeMessages(SHOW_PROGRESS);
    } catch (IllegalArgumentException ex) {
        Log.w("MediaController", "already removed");
    }
    mShowing = false;
}

private String stringForTime(int timeMs) {
    int totalSeconds = timeMs / 1000;

    int seconds = totalSeconds % 60;
    int minutes = (totalSeconds / 60) % 60;
    int hours   = totalSeconds / 3600;

    mFormatBuilder.setLength(0);
 //   if (hours > 0) {
        return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
//    } else {
//        return mFormatter.format("%02d:%02d", minutes, seconds).toString();
//    }
}

private int setProgress() {
    int position =0;
    if (mPlayer == null || mDragging) {
        return 0;
    }
    try
    {
        position = mPlayer.getCurrentPosition();
        int duration = mPlayer.getDuration();
        if (mProgress != null) {
            if (duration > 0) {
                // use long to avoid overflow
                long pos = 1000L * position / duration;
                mProgress.setProgress( (int) pos);
            }
            int percent = mPlayer.getBufferPercentage();
            mProgress.setSecondaryProgress(percent * 10);
        }

        if (mEndTime != null)
            mEndTime.setText(stringForTime(duration));
        if (mCurrentTime != null)
            mCurrentTime.setText(stringForTime(position));
    }
    catch (IllegalStateException e)
    {
    }
    return position;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    show(sDefaultTimeout);
    return true;
}

@Override
public boolean onTrackballEvent(MotionEvent ev) {
    show(sDefaultTimeout);
    return false;
}

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (mPlayer == null) {
        return true;
    }

    int keyCode = event.getKeyCode();
    final boolean uniqueDown = event.getRepeatCount() == 0
            && event.getAction() == KeyEvent.ACTION_DOWN;
    if (keyCode ==  KeyEvent.KEYCODE_HEADSETHOOK
            || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
            || keyCode == KeyEvent.KEYCODE_SPACE) {
        if (uniqueDown) {
            doPauseResume();
            show(sDefaultTimeout);
            if (mPauseButton != null) {
                mPauseButton.requestFocus();
            }
        }
        return true;
    } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
        if (uniqueDown && !mPlayer.isPlaying()) {
            mPlayer.start();
            updatePausePlay();
            show(sDefaultTimeout);
        }
        return true;
    } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
            || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
        if (uniqueDown && mPlayer.isPlaying()) {
            mPlayer.pause();
            updatePausePlay();
            show(sDefaultTimeout);
        }
        return true;
    } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
            || keyCode == KeyEvent.KEYCODE_VOLUME_UP
            || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
        // don't show the controls for volume adjustment
        return super.dispatchKeyEvent(event);
    } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
        if (uniqueDown) {
            hide();
        }
        return true;
    }

    show(sDefaultTimeout);
    return super.dispatchKeyEvent(event);
}

private View.OnClickListener mPauseListener = new View.OnClickListener() {
    public void onClick(View v) {
        doPauseResume();
        show(sDefaultTimeout);
    }
};

private View.OnClickListener mFullscreenListener = new View.OnClickListener() {
    public void onClick(View v) {
        doToggleFullscreen();
        show(sDefaultTimeout);
    }
};

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

    if (mPlayer.isPlaying()) {
        mPauseButton.setBackgroundResource(R.drawable.ic_pause);
    } else {
        mPauseButton.setBackgroundResource(R.drawable.ic_play_btn);
    }
}
public void updatePausePlay(boolean isPlay) {
    if (mRoot == null || mPauseButton == null || mPlayer == null) {
        return;
    }

    if (isPlay) {
        mPauseButton.setBackgroundResource(R.drawable.ic_pause);
    } else {
        mPauseButton.setBackgroundResource(R.drawable.ic_play_btn);
    }
}
public void updateFullScreen() {
    if (mRoot == null || mPlayer == null) { // mFullscreenButton == null  {
        return;
    }
}

private void doPauseResume() {
    if (mPlayer == null) {
        return;
    }
    /*if (mPlayer.isPlaying()) {
        mPlayer.pause();
    } else {
        mPlayer.start();
    }*/
    mPlayer.togglePausePlay();
  //  updatePausePlay();
}
private void doToggleFullscreen() {
    if (mPlayer == null) {
        return;
    }
    mPlayer.toggleFullScreen();
}

// There are two scenarios that can trigger the seekbar listener to trigger:
//
// The first is the user using the touchpad to adjust the posititon 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 "mDragging" 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 OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
    public void onStartTrackingTouch(SeekBar bar) {
        show(3600000);

        mDragging = 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;
        }

        if (!fromuser) {
            // We're not interested in programmatically generated changes to
            // the progress bar's position.
            return;
        }

        long duration = mPlayer.getDuration();
        long newposition = (duration * progress) / 1000L;
        mPlayer.seekTo( (int) newposition);
        if (mCurrentTime != null)
            mCurrentTime.setText(stringForTime( (int) newposition));
    }

    public void onStopTrackingTouch(SeekBar bar) {
        mDragging = false;
        setProgress();
        updatePausePlay();
        show(sDefaultTimeout);

        // Ensure that progress is properly updated in the future,
        // the call to show() does not guarantee this because it is a
        // no-op if we are already showing.
        mHandler.sendEmptyMessage(SHOW_PROGRESS);
    }
};

@Override
public void setEnabled(boolean enabled) {
    if (mPauseButton != null) {
        mPauseButton.setEnabled(enabled);
    }
    /*if (mFfwdButton != null) {
        mFfwdButton.setEnabled(enabled);
    }
    if (mRewButton != null) {
        mRewButton.setEnabled(enabled);
    } */
    if (mNextButton != null) {
        mNextButton.setEnabled(enabled && mNextListener != null);
    }
    if (mPrevButton != null) {
        mPrevButton.setEnabled(enabled && mPrevListener != null);
    }
    if (mProgress != null) {
        mProgress.setEnabled(enabled);
    }
    disableUnsupportedButtons();
    super.setEnabled(enabled);
}

public void enableNextButton(boolean enabled) {
    if (mNextButton != null) {
        mNextButton.setEnabled(enabled);
        if(enabled) {
            mNextButton.setVisibility(VISIBLE);
        } else {
            mNextButton.setVisibility(GONE);
        }
    }
}

public void enablePreviousButton(boolean enabled) {
    if (mPrevButton != null) {
        mPrevButton.setEnabled(enabled);
        if(enabled) {
            mPrevButton.setVisibility(VISIBLE);
        } else {
            mPrevButton.setVisibility(GONE);
        }
    }
}

public void enableProgressBar(boolean enabled) {
    if (mProgress != null) {
        mProgress.setEnabled(enabled);
        if(enabled) {
            mProgress.setVisibility(VISIBLE);
        } else {
            mProgress.setVisibility(GONE);
        }
    }
}
public void enableTimerText(boolean enabled) {
    if (mCurrentTime != null && mEndTime!=null) {
        mCurrentTime.setEnabled(enabled);
        mEndTime.setEnabled(enabled);
        if(enabled) {
            mCurrentTime.setVisibility(VISIBLE);
            mEndTime.setVisibility(VISIBLE);
        } else {
            mCurrentTime.setVisibility(GONE);
            if(mPlayer!=null)
            {
                mTextViewLive.setVisibility(VISIBLE);
                mPlayer.setLiveText(mTextViewLive);
            }
            mEndTime.setVisibility(GONE);
        }
    }
}

@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    super.onInitializeAccessibilityEvent(event);
    event.setClassName(VideoControllerView.class.getName());
}

@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    info.setClassName(VideoControllerView.class.getName());
}

private View.OnClickListener mRewListener = new View.OnClickListener() {
    public void onClick(View v) {
        if (mPlayer == null) {
            return;
        }

        int pos = mPlayer.getCurrentPosition();
        pos -= 5000; // milliseconds
        mPlayer.seekTo(pos);
        setProgress();

        show(sDefaultTimeout);
    }
};

private View.OnClickListener mFfwdListener = new View.OnClickListener() {
    public void onClick(View v) {
        if (mPlayer == null) {
            return;
        }

        int pos = mPlayer.getCurrentPosition();
        pos += 15000; // milliseconds
        mPlayer.seekTo(pos);
        setProgress();

        show(sDefaultTimeout);
    }
};

private void installPrevNextListeners() {
    if (mNextButton != null) {
        mNextButton.setOnClickListener(mNextListener);
        mNextButton.setEnabled(mNextListener != null);
    }

    if (mPrevButton != null) {
        mPrevButton.setOnClickListener(mPrevListener);
        mPrevButton.setEnabled(mPrevListener != null);
    }
}

public void setPrevNextListeners(OnSingleClickListener next, OnSingleClickListener prev) {
    mNextListener = next;
    mPrevListener = prev;
    mListenersSet = true;

    if (mRoot != null) {
        installPrevNextListeners();

        if (mNextButton != null && !mFromXml) {
            mNextButton.setVisibility(View.VISIBLE);
        }
        if (mPrevButton != null && !mFromXml) {
            mPrevButton.setVisibility(View.VISIBLE);
        }
    }
}

public interface MediaPlayerControl {
    void    start();
    void    pause();
    int     getDuration();
    int     getCurrentPosition();
    void    seekTo(int pos);
    boolean isPlaying();
    int     getBufferPercentage();
    boolean canPause();
    boolean canSeekBackward();
    boolean canSeekForward();
    boolean isFullScreen();
    void    toggleFullScreen();
    void    togglePausePlay();
    void    setLiveText(TextView view);
}

private static class MessageHandler extends Handler {
    private final WeakReference<VideoControllerView> mView;

    MessageHandler(VideoControllerView view) {
        mView = new WeakReference<VideoControllerView>(view);
    }
    @Override
    public void handleMessage(Message msg) {
        VideoControllerView view = mView.get();
        if (view == null || view.mPlayer == null) {
            return;
        }

        int pos;
        switch (msg.what) {
            case FADE_OUT:
               view.hide();
                break;
            case SHOW_PROGRESS:
                pos = view.setProgress();
                if (!view.mDragging && view.mShowing && view.mPlayer.isPlaying()) {
                    msg = obtainMessage(SHOW_PROGRESS);
                    sendMessageDelayed(msg, 1000 - (pos % 1000));
                }
                break;
        }
    }
}

Next you need to set the below code to display the controller on top of the player

 controller = new VideoControllerView(this);
 controller.setMediaPlayer(this);
 controller.setAnchorView(videoContainer);

Note: There are various methods in the VideoControllerView class which is exclusively for my project dependencies. Please discard/add as per your needs

Jiju Induchoodan
  • 4,236
  • 1
  • 22
  • 24