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"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With