In my current app there is requirement to create custom video player and the special requirement is, to display or mark video progress-bar with different color at some given time-index just like Youtube marks its video with yellow color to indicate ads on its video progress bar.
For more clarity please check below screen-shot:
At this moment I’m done with Video Player functionality using VideoView and for now, I’m using Horizontal ProgressBar to show video progress. I did lot of research, but unfortunately no result so far. There is no compulsion to use only Horizontal ProgressBar, we can use SeekBar or inbuilt MediaController too, but the thing is we should be able to mark it with different color at some given position. Below is my code I have written so far:
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"
tools:context=".MainActivity">
<VideoView
android:id="@+id/myVideo"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerInParent="true" />
<RelativeLayout
android:id="@+id/rlVidProgress"
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvCurrentTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="20dp"
android:text="00:00"
android:textColor="@android:color/white"
android:textSize="13dp"
android:textStyle="bold" />
<ProgressBar
android:id="@+id/videoProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/tvTotalTime"
android:layout_width="fill_parent"
android:layout_toRightOf="@+id/tvCurrentTime"
android:layout_height="10dp"
android:layout_marginBottom="10dp" />
<TextView
android:id="@+id/tvTotalTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="20dp"
android:layout_marginRight="5dp"
android:text="00:00"
android:textColor="@android:color/white"
android:textSize="13dp"
android:textStyle="bold" />
</RelativeLayout>
<TextView
android:id="@+id/tvAutoSave"
android:layout_width="wrap_content"
android:textColor="@android:color/white"
android:layout_alignParentRight="true"
android:layout_marginBottom="10dp"
android:text="@string/click_to_auto_save_offer"
android:background="@drawable/white_border_bg"
android:textSize="13dp"
android:layout_above="@+id/rlVidProgress"
android:visibility="gone"
android:paddingBottom="3dp"
android:paddingTop="3dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textStyle="bold"
android:layout_height="wrap_content" />
</RelativeLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
ProgressBar videoProgress;
TextView tvCurrentTime;
Handler handler;
VideoView vidView;
String strTotalDuration;
TextView tvAutoSave;
Animation animFadeIn, animFadeOut;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
animFadeIn = new AlphaAnimation(0, 1);
animFadeIn.setInterpolator(new DecelerateInterpolator()); //add this
animFadeIn.setDuration(1000);
animFadeOut = new AlphaAnimation(1, 0);
animFadeOut.setInterpolator(new AccelerateInterpolator()); //and this
//animFadeOut.setStartOffset(500);
animFadeOut.setDuration(500);
tvCurrentTime = (TextView) findViewById(R.id.tvCurrentTime);
final TextView tvTotalTime = (TextView) findViewById(R.id.tvTotalTime);
tvAutoSave = (TextView) findViewById(R.id.tvAutoSave);
tvAutoSave.setOnClickListener(this);
vidView = (VideoView)findViewById(R.id.myVideo);
videoProgress = (ProgressBar) findViewById(R.id.videoProgress);
videoProgress.setProgress(0);
videoProgress.setMax(100);
final String videoSource = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
vidView.setKeepScreenOn(true);
vidView.setVideoURI(Uri.parse(videoSource));
vidView.setMediaController(null);
handler = new Handler();
// Define the code block to be executed
final Runnable runnableCode = new Runnable() {
@Override
public void run()
{
updateCurrentTime();
// Repeat this the same runnable code block again another 1 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 1000);
}
};
vidView.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer mp)
{
if(tvAutoSave.getVisibility() == View.VISIBLE)
{
tvAutoSave.setVisibility(View.GONE);
tvAutoSave.startAnimation(animFadeOut);
}
}
});
vidView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
@Override
public void onPrepared(MediaPlayer mp)
{
vidView.start();
strTotalDuration = msToTimeConverter(vidView.getDuration());
tvTotalTime.setText(""+strTotalDuration);
// Start the initial runnable task by posting through the handler
handler.post(runnableCode);
//startHandler();
}
}
);
}
private void updateCurrentTime()
{
if (videoProgress.getProgress() >= 100)
{
handler.removeMessages(0);
}
String currentPosition = msToTimeConverter(vidView.getCurrentPosition());
String[] strArr = currentPosition.split(":");
if(strArr.length==2 && (strArr[1].equals("06") || strArr[1].equals("6")))
{
Toast.makeText(MainActivity.this, "Trigger success at 6 sec position.......", Toast.LENGTH_SHORT).show();
tvAutoSave.setVisibility(View.VISIBLE);
tvAutoSave.startAnimation(animFadeIn);
}
int progress = vidView.getCurrentPosition() * 100 / vidView.getDuration();
videoProgress.setProgress(progress);
tvCurrentTime.setText(""+currentPosition);
}
String msToTimeConverter(int millis)
{
return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tvAutoSave:
tvAutoSave.setVisibility(View.GONE);
tvAutoSave.startAnimation(animFadeOut);
Toast.makeText(MainActivity.this, "Offer(s) saved in wallet successfully", Toast.LENGTH_SHORT).show();
break;
}
}
}
One possibility is to create a custom view. By doing so you can draw exactly what you need on a canvas, and for a custom progress-bar view this is rather easy. However, it is not as quick as using built-in Views, but the advantage is you can customize it exactly as you want it. Do note this code is just a draft showing it is possible.
I created attributes so it is easy to customize the color of the progress-bar’s components, and you can modify the height. The gif below shows the progress bar created at the bottom:
class IndicatorProgressBar(context: Context, attrs: AttributeSet) : View(context, attrs) {
private val TAG = "IndicatorProgressBar"
private var barColor = Color.GRAY
private var barHeight = 25F
private var indicatorColor = Color.CYAN
private var progressColor = Color.GREEN
private val paint = Paint()
lateinit var indicatorPositions: List<Float>
var progress = 0F // From float from 0 to 1
set(state) {
field = state
invalidate()
}
init {
paint.isAntiAlias = true
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet?) {
context.theme.obtainStyledAttributes(
attrs, R.styleable.IndicatorProgressBar,
0, 0
).apply {
barColor = getColor(R.styleable.IndicatorProgressBar_barColor, barColor)
barHeight = getFloat(R.styleable.IndicatorProgressBar_barHeight, barHeight)
progress = getFloat(R.styleable.IndicatorProgressBar_progress, progress)
progressColor = getColor(R.styleable.IndicatorProgressBar_progressColor, progressColor)
indicatorColor =
getColor(R.styleable.IndicatorProgressBar_indicatorColor, indicatorColor)
recycle()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.style = Paint.Style.FILL // We will only use FILL for the progress bar's components.
drawProgressBar(canvas)
drawProgress(canvas)
drawIndicators(canvas)
}
/**
* Used to get the measuredWidth from the view as a float to be used in the draw methods.
*/
private fun width(): Float {
return measuredWidth.toFloat()
}
private fun drawProgressBar(canvas: Canvas) {
paint.color = barColor
drawCenteredBar(canvas, 0F, width())
}
private fun drawProgress(canvas: Canvas) {
paint.color = progressColor
val barWidth = (progress) * width()
drawCenteredBar(canvas, 0F, barWidth)
}
private fun drawIndicators(canvas: Canvas) {
paint.color = indicatorColor
indicatorPositions.forEach {
val barPositionCenter = it * width()
val barPositionLeft = barPositionCenter - 3F
val barPositionRight = barPositionCenter + 3F
drawCenteredBar(canvas, barPositionLeft, barPositionRight)
}
}
private fun drawCenteredBar(canvas: Canvas, left: Float, right: Float) {
val barTop = (measuredHeight - barHeight) / 2
val barBottom = (measuredHeight + barHeight) / 2
val barRect = RectF(left, barTop, right, barBottom)
canvas.drawRoundRect(barRect, 50F, 50F, paint)
}
override fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()
bundle.putFloat("progress", progress)
bundle.putParcelable("superState", super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
var viewState = state
if (viewState is Bundle) {
progress = viewState.getFloat("progress", progress)
viewState = viewState.getParcelable("superState")!!
}
super.onRestoreInstanceState(viewState)
}
override fun performClick(): Boolean {
super.performClick()
return true
}
override fun onTouchEvent(event: MotionEvent): Boolean {
super.onTouchEvent(event)
Log.d(TAG, "x=${event.x} / ${width()} (${event.x / measuredWidth}%), y=${event.y}")
when (event.action) {
MotionEvent.ACTION_DOWN -> {
return updateProgress(event)
}
MotionEvent.ACTION_MOVE -> {
return updateProgress(event)
}
MotionEvent.ACTION_UP -> {
performClick()
return true
}
}
return false
}
private fun updateProgress(event: MotionEvent): Boolean {
// percent may be outside the range (0..1)
val percent = event.x / width()
val boundedPercent = min(max(percent, 0F), 1F) // not above 1
progress = boundedPercent
invalidate() // Make the view redraw itself
return true
}
}
The attributes for custom views are defined in res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="IndicatorProgressBar">
<!-- The color of the progressbar (not the progress)-->
<attr name="barColor" format="color" />
<!-- The color of the indicators on the progress bar (such as ads on YouTube)-->
<attr name="indicatorColor" format="color" />
<!-- The color of the progressed time/work of the progressbar-->
<attr name="progressColor" format="color" />
<!-- The initial progress value, a value from 0 to 1 -->
<attr name="progress" format="float"/>
<!-- The height of the progress bar, note that layout_height should be set to a larger
number so the onTouchEvent listener is more easy to trigger-->
<attr name="barHeight" format="float"/>
</declare-styleable>
</resources>
You use the custom view in layouts like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:gravity="bottom"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"
tools:context=".MainActivity">
<com.yourpackagename.progressbarindicator.IndicatorProgressBar
android:id="@+id/indicatorProgressBar"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_centerVertical="true"
android:foregroundGravity="center"
app:barColor="@color/colorAccent"
app:barHeight="12"
app:indicatorColor="#ffffff"
app:progress="0"
app:progressColor="#11c011" />
</RelativeLayout>
Main activity:
class MainActivity : AppCompatActivity() {
private lateinit var indicatorProgressBar: IndicatorProgressBar
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined)
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
indicatorProgressBar = findViewById(R.id.indicatorProgressBar)
indicatorProgressBar.indicatorPositions = listOf(0.13F, 0.34F, 0.57F, 0.85F, 0.92F)
updateCurrentTime()
indicatorProgressBar.setOnClickListener {
if(indicatorProgressBar.progress >= 1F){
updateCurrentTime()
}
}
}
private fun updateCurrentTime() {
scope.launch {
while (indicatorProgressBar.progress <= 1F){
Log.d(TAG, "In while loop")
delay(33)
runOnUiThread{
indicatorProgressBar.progress += 0.003F
Log.d(TAG, "Progress is now: ${indicatorProgressBar.progress}")
}
}
}
}
Add Kotlin Coroutines to your dependencies in build.gradle (app) if you want to run the updateCurrentTime method in the MainActivity:
dependencies {
...
def coroutines_version = "1.3.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}
Answer:
Finally I got the solution. Below are the steps to implement the same–
Step-1] Create one “attrs.xml” file in “res/values/” folder and paste below code in that file–
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DottedSeekBar">
<attr name="dots_positions" format="reference"/>
<attr name="dots_drawable" format="reference"/>
</declare-styleable>
</resources>
Step-2] Prepare one image icon which you want to use to mark on progress bar and name it “video_mark.png”.
Step-3] Create one custom SeekBar as below–
public class DottedSeekBar extends AppCompatSeekBar {
/** Int values which corresponds to dots */
private int[] mDotsPositions = null;
/** Drawable for dot */
private Bitmap mDotBitmap = null;
public DottedSeekBar(final Context context) {
super(context);
init(null);
}
public DottedSeekBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public DottedSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
/**
* Initializes Seek bar extended attributes from xml
*
* @param attributeSet {@link AttributeSet}
*/
private void init(final AttributeSet attributeSet) {
final TypedArray attrsArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.DottedSeekBar, 0, 0);
final int dotsArrayResource = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_positions, 0);
if (0 != dotsArrayResource) {
mDotsPositions = getResources().getIntArray(dotsArrayResource);
}
final int dotDrawableId = attrsArray.getResourceId(R.styleable.DottedSeekBar_dots_drawable, 0);
if (0 != dotDrawableId) {
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotDrawableId);
}
}
/**
* @param dots to be displayed on this SeekBar
*/
public void setDots(final int[] dots) {
mDotsPositions = dots;
invalidate();
}
/**
* @param dotsResource resource id to be used for dots drawing
*/
public void setDotsDrawable(final int dotsResource)
{
mDotBitmap = BitmapFactory.decodeResource(getResources(), dotsResource);
invalidate();
}
@Override
protected synchronized void onDraw(final Canvas canvas) {
super.onDraw(canvas);
final float width=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
final float step=width/(float)(getMax());
if (null != mDotsPositions && 0 != mDotsPositions.length && null != mDotBitmap) {
// draw dots if we have ones
for (int position : mDotsPositions) {
canvas.drawBitmap(mDotBitmap, position * step, 0, null);
}
}
}
}
Step-4] Use this custom SeekBar in your activity.xml file as below–
<com.your_package.DottedSeekBar
android:id="@+id/videoProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Step-5] Add below code in “onCreate()” method of your “Activity.java” class–
DottedSeekBar videoProgress = (DottedSeekBar) findViewById(R.id.videoProgress);
// Disable SeekBar Thumb Drag. (Optional)
videoProgress.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
return true;
}
});
// Set custom thumb icon color here (Optional)
videoProgress.getThumb().setColorFilter(getResources().getColor(R.color.cerulean_blue), PorterDuff.Mode.SRC_IN);
// Add below line to avoid unnecessary SeekBar padding. (Optional)
videoProgress.setPadding(0, 0, 0, 0);
// Handler to update video progress time--
handler = new Handler();
// Define the code block to be executed
final Runnable runnableCode = new Runnable() {
@Override
public void run()
{
updateCurrentTime();
// Repeat this the same runnable code block again another 1 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 1000);
}
};
Use "videoView.setOnPreparedListener()" method to calculate total video time in seconds
yourVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
@Override
public void onPrepared(MediaPlayer mp)
{
String strTotalDuration = msToTimeConverter(vidView.getDuration());
String[] strTimeArr = strTotalDuration.split(":");
int min = Integer.parseInt(strTimeArr[0]);
int videoLengthInSec = Integer.parseInt(strTimeArr[1]);
videoLengthInSec = videoLengthInSec + (min*60);
videoProgress.setProgress(0);
videoProgress.setMax(videoLengthInSec);
// Start the initial runnable task by posting through the handler
handler.post(runnableCode);
initVideoMarkers();
}
}
);
Step-6] Copy below required methods in your “Activity.java” class–
// Method to update time progress
private void updateCurrentTime()
{
if (videoProgress.getProgress() >= 100)
{
handler.removeMessages(0);
}
String currentPosition = msToTimeConverter(vidView.getCurrentPosition());
String[] strArr = currentPosition.split(":");
int progress = vidView.getCurrentPosition() * videoLengthInSec / vidView.getDuration();
videoProgress.setProgress(progress);
}
// Milliseconds to Time converter Method
String msToTimeConverter(int millis)
{
return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)),
TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
}
// Method to set Marker values
private void initVideoMarkers()
{
// Here I'm adding markers on 10, 15 and 20 Second index
videoProgress.setDots(new int[] {10, 15, 20});
videoProgress.setDotsDrawable(R.drawable.video_mark);
}
Tags: androidandroid, video