How to make an Music Player App using Android Studio

Kostas Kourlos
8 min readFeb 28, 2021

Making a music player in android is fairly simple . You basically need to do three things.

First you have to locate your songs/tracks saved on your device.

Second thing you need to do is to display these tracks .

Last you have to play the selected track when clicked on.

Dependencies

Add this to build.gradle(module: app)

implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'

Go to Refractor → Migrate to AndroiX and re-sync the project.

Resources

You will need to create icons for the play/pause button, etc . You can essentially use any image/icon you like , that’s why I choose to include these steps.

Go to res/drawable and create 3 vector files named ic_music_note.xml , ic_play.xml, ic_pause.xml and ic_more.xml

ic_music_note.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
</vector>

ic_play.xml

<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"/>
</vector>

ic_pause.xml

<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,16L9,16L9,8h2v8zM15,16h-2L13,8h2v8z"/>
</vector>

ic_more.xml

<vector android:height="24dp" android:tint="@android:color/darker_gray"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>
res/drawable

Displaying Tracks

Now that we got this out of the way ,we need to worry about how are we going to display our tracks.

The way way to do so is by creating a ListView or RecyclerView to take care of displaying our list , and an Adaptor that takes care of putting an Object inside our list.

Go to activity_main.xml and create a Recycler View . The RecyclerView will be important to display our tracks in a nice list.

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Tracks"
android:textSize="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTracks"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />

Now create a new xml file under res/layout named track_row.xml and paste this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="65dp"
>


<ImageView
android:id="@+id/cover"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="12dp"
android:cropToPadding="true"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />



<TextView
android:id="@+id/trackName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="Funky Monks"
android:textColor="@android:color/black"
android:textSize="20sp"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/artistName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Red Hot Chilli Peppers"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="@android:color/darker_gray"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@+id/cover"
app:layout_constraintTop_toBottomOf="@+id/trackName" />



<ImageButton
android:id="@+id/trackMore"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_more" />

</androidx.constraintlayout.widget.ConstraintLayout>
This is how it should look like

The recyclerview will create a list with track_row.xml items .

Next thing is to create an adapter for the recyclerview.

Getting Tracks

Create a new java file named Track. The Track will be used to pass Track objects to the adapter.

package com.example.musify;

import java.io.Serializable;

public class Track implements Serializable {


private String trackName; /* Track name*/
private String artistName; /* Artists name*/
private String path; /* File path */
private long id; /* ID used to trace the file */
private String size; /* File size */

/* private int duration etc..*/
/* ......................... */


// Constructors
public Track(){
this.trackName = "Unknown";
this.artistName = "Unknown";
}

public Track(String trackName,String artistName,String path,long id){
this.trackName = trackName;
this.artistName = artistName;
this.path = path;
this.id = id;
}
Track Class

Getting your local saved songs.

Create a new java file named FileManager. This class will be used to get us tracks locally stored in the device.

Define a method to return a list of all the tracks in your device such as :

public List<Track> getTracks(){

Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC + "!=0";

ArrayList<Track> allTracks = new ArrayList<Track>();

Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));

allTracks.add(new Track(name,artist,path));

} while (cursor.moveToNext());

}
cursor.close();
}


return allTracks;
}
FileManager Class

Adapter

For the adapter create a new java file named TrackAdapter.java .

package com.example.musify;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

//RecyclerView Adapter got track objects
public class TrackAdapter extends RecyclerView.Adapter<TrackAdapter.ViewHolder> {

private List<Track> mData;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;

// data is passed into the constructor
TrackAdapter(Context context, List<Track> data) {
this.mInflater = LayoutInflater.from(context);
this.mData = data;
}

// inflates the row layout from xml when needed
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.track_row, parent, false);
return new ViewHolder(view);
}

// binds the data to the TextView in each row
@Override
public void onBindViewHolder(ViewHolder holder, int position) {

Track track = mData.get(position);

holder.trackNameV.setText(track.getTrackName());
holder.artistNameV.setText(track.getArtistName());

}

// total number of rows
@Override
public int getItemCount() {
return mData.size();
}


// stores and recycles views as they are scrolled off screen
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView trackNameV;
TextView artistNameV;

ViewHolder(View itemView) {
super(itemView);
trackNameV = itemView.findViewById(R.id.trackName);
artistNameV = itemView.findViewById(R.id.artistName);
itemView.setOnClickListener(this);
}

@Override
public void onClick(View view) {
if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
}
}

// convenience method for getting data at click position
Track getItem(int id) {
return mData.get(id);
}

// allows clicks events to be caught
void setClickListener(ItemClickListener itemClickListener) {
this.mClickListener = itemClickListener;
}

// parent activity will implement this method to respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
It should look like this

Now go to MainActivity.java and use the FileManager/Adapter to show the tracks as such:

public class MainActivity extends AppCompatActivity {

//Declare recyclerview
RecyclerView tracksRV;
TrackAdapter trackAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

isStoragePermissionGranted(); //Ask for permission runtime

tracksRV = findViewById(R.id.rvTracks);
tracksRV.setLayoutManager(new LinearLayoutManager(this));

//Get tracks using FileManager
FileManager fileManager = new FileManager(this);
// data to populate the RecyclerView with
ArrayList<Track> tracks = (ArrayList<Track>) fileManager.getTracks();

trackAdapter = new TrackAdapter(this, tracks);
trackAdapter.setClickListener(new TrackAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Track track = trackAdapter.getItem(position); // Track selected

/* On click go to Player Activity and play the track*/
Intent i = new Intent(MainActivity.this, PlayerActivity.class);
i.putExtra("TRACK", (Serializable) track); /* Track object sent to the PlayerActivity*/
startActivity(i);
}
});

tracksRV.setAdapter(trackAdapter);

}



public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v("PERMINIT","Permission is granted");
return true;
} else {

Log.v("PERMINIT","Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else {
Log.v("PERMINIT","Permission is granted");
return true;
}
}


}

The MainActivity is responsible for reading and displaying a list of available songs in your device . When an item is clicked , a new intent is created and a Track object is sent to the PlayerActivity to play.

PlayerActivity

Create a new Activity named PlayerActivity , remember to define your new activities in the manifest!

The PlayerActivity is responsible for handling the media player and playing the track.

activity_player.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_alignParentEnd="false"
android:layout_alignParentBottom="false"
android:backgroundTintMode="screen"
android:fadingEdge="horizontal|vertical"
tools:context=".PlayerActivity">

<TextView
android:id="@+id/title"
android:layout_width="338dp"
android:layout_height="57dp"
android:autoSizeMaxTextSize="100sp"
android:autoSizeMinTextSize="30sp"
android:autoSizeStepGranularity="2sp"
android:autoSizeTextType="uniform"
android:bufferType="spannable"
android:contextClickable="true"
android:fadingEdge="horizontal|vertical"
android:text="Undefined"
android:textAlignment="center"
android:textColor="@android:color/black"
android:textSize="32dp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cover"
app:layout_constraintVertical_bias="0.13" />

<ImageView
android:id="@+id/cover"
android:layout_width="322dp"
android:layout_height="321dp"
android:layout_marginTop="128dp"
android:cropToPadding="true"
android:scaleType="fitXY"
android:background="@drawable/ic_music_note"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<SeekBar
android:id="@+id/seekBar"
android:layout_width="330dp"
android:layout_height="37dp"
android:backgroundTint="@android:color/black"
android:fadingEdge="horizontal|vertical"
android:progressTint="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintVertical_bias="0.36" />

<TextView
android:id="@+id/currenttime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/seekBar"
android:layout_marginTop="12dp"
android:text="0:00"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/black"
app:layout_constraintEnd_toStartOf="@+id/play"
app:layout_constraintHorizontal_bias="0.204"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/seekBar" />

<TextView
android:id="@+id/artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/seekBar"
android:text="Undefined"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/darker_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />

<TextView
android:id="@+id/totaltime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/seekBar"
android:layout_marginTop="12dp"
android:text="0:00"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.805"
app:layout_constraintStart_toEndOf="@+id/play"
app:layout_constraintTop_toBottomOf="@+id/seekBar" />

<ImageButton
android:id="@+id/play"
android:layout_width="52dp"
android:layout_height="52dp"
android:background="@null"
android:scaleType="centerInside"
android:scaleX="2"
android:scaleY="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/seekBar"
app:layout_constraintVertical_bias="0.337"
app:srcCompat="@drawable/ic_pause" />





<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_end="194dp" />


</androidx.constraintlayout.widget.ConstraintLayout>
Activity Player.xml

PlayerActivity.java

package com.example.musify;

import android.content.ContentUris;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class PlayerActivity extends AppCompatActivity {

ImageButton playBtnV;
TextView trackNameV;
TextView artistNameV;
TextView current_timeV;
TextView total_timeV;
SeekBar seekBarV;

boolean playing;
private Handler mediaHandler = new Handler();

MediaPlayer mediaPlayer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);

trackNameV = findViewById(R.id.title);
artistNameV = findViewById(R.id.artist);
current_timeV = findViewById(R.id.currenttime);
total_timeV = findViewById(R.id.totaltime);
seekBarV = findViewById(R.id.seekBar);
playBtnV = findViewById(R.id.play);

// Get track EXTRA
Track track = (Track) getIntent().getSerializableExtra("TRACK");
trackNameV.setText(track.getTrackName());
artistNameV.setText(track.getArtistName());


Toast.makeText(getApplicationContext(),"Playing...",Toast.LENGTH_SHORT).show();



// Get track Uri
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, track.getID());

// Set MediaPlayer attributes
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
);

//Set MediaPlayer data source
// Add an onPreparedListener to start the track as soon as it gets prepared
try {
mediaPlayer.setDataSource(getApplicationContext(), contentUri);
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
int finalTime = mediaPlayer.getDuration();
int startTime = mediaPlayer.getCurrentPosition();
seekBarV.setMax((int) finalTime);
total_timeV.setText(String.format("%d:%d ",
TimeUnit.MILLISECONDS.toMinutes((long) finalTime),
TimeUnit.MILLISECONDS.toSeconds((long) finalTime) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long)
finalTime)))
);

current_timeV.setText(String.format("%d:%d",
TimeUnit.MILLISECONDS.toMinutes((long) startTime),
TimeUnit.MILLISECONDS.toSeconds((long) startTime) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long)
startTime)))
);

// Set progress on the seek bar
seekBarV.setProgress((int) startTime);

/* Handlers for updating song time-slider */
mediaHandler.postDelayed(UpdateSongTime, 100);
mediaHandler.post(UpdateSlider);


//Play/Pause button Listener
playBtnV.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

if (mediaPlayer.isPlaying()) {
Toast.makeText(getApplicationContext(), "Paused...", Toast.LENGTH_SHORT).show();
mediaPlayer.pause();
playBtnV.setImageResource(R.drawable.ic_play);
playing = false;
} else {
Toast.makeText(getApplicationContext(), "Playing...", Toast.LENGTH_SHORT).show();
mediaPlayer.start();
playBtnV.setImageResource(R.drawable.ic_pause);
playing = true;
}


}
});
mp.start();
}
});
} catch (IOException e) {

e.printStackTrace();
}

/* Prepare the MediaPlayer
* Automatically starts the track once prepared */
mediaPlayer.prepareAsync();
}

/*
* Updates song time on slider and starttime TextView
*/
private Runnable UpdateSongTime = new Runnable() {
public void run() {
int currenttime = 111;
try {
currenttime = mediaPlayer.getCurrentPosition();
}catch(Exception e){
// good practice
Thread.currentThread().interrupt();
return;
//TODO RELEASE SLIDER RNNABLE
}
current_timeV.setText(String.format("%d:%d",
TimeUnit.MILLISECONDS.toMinutes((long) currenttime),
TimeUnit.MILLISECONDS.toSeconds((long) currenttime) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.
toMinutes((long) currenttime))));

seekBarV.setProgress((int)currenttime);
mediaHandler.postDelayed(this, 100);
}
};

/*
* Updates song time base on User slide on track
*/
private Runnable UpdateSlider = new Runnable() {
public void run() {
seekBarV.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(mediaPlayer != null && fromUser){mediaPlayer.seekTo(progress);}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
return;
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
return;
}
});
}
};




@Override
public void onDestroy() {
super.onDestroy();

try {
mediaPlayer.release();
mediaPlayer = null;
}

catch(Exception e){}
}


}

Result

The end result is a simple Music Player for your local saves audio files.

View the full project on GitHub :

kourloskostas/android-music-player: A simple android music player app (github.com)

This tutorial is still being constructed , so pardon any parts that aren’t explained properly.

--

--