Create your own Mini Player

In this tutorial, we will explain how to create your own mini player on your Android project.

Notes and Requirements before creating your player

Views are going to use many android resources as drawable dimens and colors from the StayTuned Android SDK. Feel free to change with your own resources to match your application style.

The tutorial requires the usage of LiveData

Setup the UI

Goal

Our goal is to create the view class and layout to have a MiniPlayer as below :

We can describe the player as follows :

  • On the top, a progress bar to display the current STTrack progression

  • On the left, the image of the current STTrack

  • The first line is the title of the current STTrack

  • The second line is the title of the current STContent

  • On the right, the main button to control the STPlayer

To begin, we need to create the 2 files we are going to use for the MiniPlayer.

Lets create a mini_player.xml in your layout resource folder as follows :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/mini_player_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
</LinearLayout>

And a class MiniPlayer.kt as follows :

class MiniPlayer(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.mini_player, this, true)
}
}

For now, we only have an empty view.

Complete and Style the layout file

We are not going to explain the layout file view by view, instead we are going to see how to change the style of the layout. You can find the full layout file at : Link to Layout File

Background Color

You can change the background color of the MiniPlayer by updating the background property :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/mini_player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:background="@android:color/white" > <!-- Update with your color here -->

Vizualizer Color

The vizualizer is the view animated on the image of the track. Update the property app:barColor

<com.staytuned.core.ui.STLineBarVisualizer
android:id="@+id/mini_player_visualizer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="@dimen/st_size_24"
app:barColor="@android:color/white" /> <!-- Update with your color here -->

Text Color and Font

You can update the color and the font of every TextView on the miniplayer, as follows :

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
..
android:fontFamily="?attr/fontFamilySemiBold" <!-- Update with your font here -->
android:textColor="@color/stTextDefault" /> <!-- Update with your color here -->

Progress Color

You can update the progress color as follows :

<ProgressBar
android:id="@+id/mini_player_progress"
style="@android:style/Widget.ProgressBar.Horizontal"
..
android:layout_width="match_parent"
android:progressDrawable="@drawable/st_seek_style_transparent"
android:progressTint="@color/colorPrimary" /> <!-- Update with your progress color here -->

Button Background, Loader and Icons Color

The main button of the MiniPlayer has a background color (the gray circle by default), multiple Icons (Play, Pause) and a ProgressBar loader. You can update their color as follows :

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/mini_player_play_toggle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/st_circle_gray"
app:srcCompat="@drawable/st_ic_play"
..
android:backgroundTint="@color/stGrayLight" <!-- Update this to change background color -->
android:tint="@android:color/black" /> <!-- Update this to change icons color -->
<ProgressBar
android:id="@+id/mini_player_play_loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"
android:indeterminateTint="@android:color/black" /> <!-- Update this to change loader color -->

Connect to the SDK

Now that we have a complete layout view file, we have to connect the views with the SDK values. You can find the full code at : Link to Class File

Hide the MiniPlayer when the Player has no track

First of all, we want the MiniPlayer to hide itself when the Player is not playing any track. We need to add an observer to the currentTrack of the Player. If it's null, we hide the MiniPlayer, else we show it.

override fun onAttachedToWindow() {
super.onAttachedToWindow()
STPlayer.getInstance()?.currentTrack?.observe(context as LifecycleOwner, Observer {
visibility = if (it == null) {
View.GONE
} else {
View.VISIBLE
}
})
}

Bind with the Current Track and Current Content

Now we need to connect the 2 TextView(mini_player_track_title and mini_player_content_title) and the ImageView(mini_player_image) with the current track and content. We add code to the currentTrackobserver we've added before and add a new observer to currentContent

override fun onAttachedToWindow() {
super.onAttachedToWindow()
STPlayer.getInstance()?.currentTrack?.observe(context as LifecycleOwner, Observer {
visibility = if (it == null) {
View.GONE
} else {
View.VISIBLE
}
mini_player_track_title.text = it?.title
Glide.with(this).load(it?.imgSrc).centerInside().into(mini_player_image)
})
STPlayer.getInstance()?.currentContent?.observe(context as LifecycleOwner, Observer {
mini_player_content_title.text = it?.title
})
}

Bind the progress

We want the progress to be binded with the Player current track progress. For this, we add an observer to the player currentTime

override fun onAttachedToWindow() {
super.onAttachedToWindow()
..
STPlayer.getInstance()?.currentTime?.observe(context as LifecycleOwner, Observer {
mini_player_progress.progress = STPlayer.getInstance()?.getAudioProgress()?.toInt() ?: 0
})
}

Bind the state

The player has multiple states. Playing, Paused and Loading. When it's playing, we want the Visualizer to animate and the button to display a pause icon. When it's paused, we want the Visualizer to stop and the button to display a play icon, and when it's loading, we want the button to be replaced by a loader.

For this, we add an observer to the Player currentState

override fun onAttachedToWindow() {
super.onAttachedToWindow()
..
STPlayer.getInstance()?.currentState?.observe(context as LifecycleOwner, Observer {
setVisualizerState()
setButtonState()
})
}
private fun setVisualizerState() {
// pause or play the visualizer animation by the player state
mini_player_visualizer.isPlaying = STPlayer.getInstance()?.isPlaying() == true
}
private fun setButtonState() {
val player = STPlayer.getInstance()
when {
// When it's playing, we show a pause button and hide the loader
player?.isPlaying() == true && !player.isLoading() -> {
mini_player_play_toggle.visibility = View.VISIBLE
mini_player_play_loader.visibility = View.GONE
mini_player_play_toggle.setImageResource(com.staytuned.R.drawable.st_ic_pause)
}
// When it's loading, we hide the button and show the loader
player?.isLoading() == true -> {
mini_player_play_toggle.visibility = View.GONE
mini_player_play_loader.visibility = View.VISIBLE
}
// Else we consider the state as PAUSED, we show a play icon and hide the loader
else -> {
mini_player_play_toggle.visibility = View.VISIBLE
mini_player_play_loader.visibility = View.GONE
mini_player_play_toggle.setImageResource(com.staytuned.R.drawable.st_ic_play)
}
}
}

Handle Click on the button

Now the button displays a nice icon or a loader for each state, we want the button to dispach the right action to the player. For this, we need to add a click listener on the button.

init {
..
mini_player_play_toggle.setOnClickListener {
togglePlayPause()
}
}
..
private fun togglePlayPause() {
if (STPlayer.getInstance()?.isPlaying() == true) {
STPlayer.getInstance()?.stop()
} else {
STPlayer.getInstance()?.resume()
}
}

Open the Player on Click

To finish this tutorial, we want the fully expanded Player to open when the user click anywhere on the MiniPlayer. For this, we need to add a click listener on the root view.

init {
..
mini_player.setOnClickListener {
STPlayer.getInstance()?.launchUI(it.context)
}
}

Add it to your main layout

Now, you have a fully working MiniPlayer with your own theme. The last thing you have to do is to add your custom MiniPlayer to the screen layout you want it to be displayed.

..
<com.staytuned.demo.views.MiniPlayer
android:layout_width="match_parent"
android:layout_height="wrap_content" />
..

Resources

Demo Application source code : Link to Github Repository MiniPlayer xml file : Link to Layout File MiniPlayer class file : Link to Class File