android media player
DESCRIPTION
android appsTRANSCRIPT
Android Media Player
People ask me all the time how to develop an Android program that can load up and play music.
Sadly, there are not a lot of good answers on the internet. The good news is, it's really not all that
bad. Today we are going to:
1. Learn how to implement features of the android.media.MediaPlayer class.
2. Load Drawables on the fly from the SD card using the createFromPath() function
3. Learn what assets are and how to incorporate these into our program
4. Learn how to load media from an SD card
5. Write a fully functional Media Player program
6. Become a better Java Monkey!
By the end of this tutorial, you will not only have a fully functional simple media player, but you will
also have a clear understanding of how to incorporate media into your apps. Before going any
further, make sure your development environment is set up correctly for Android development. If you
are unsure, check my guide here.
*** I recommend you download the source code for this project here, as it contains sample tracks
and all of the drawables you will need, as well as a cheesy icon I made myself ***
File New Android Project
New Android Project (Name, Path, Target)
New Android Project (Properties)
File Import
Import Existing Projects into Workspace
Browse for the project you want to import and click OK, check the checkbox, and then click Finish
Here we go!
When making a media player, a few things come to mind:
1. There will be multiple tracks to load
2. Moving from track to track
3. Play, Pause, and Stop functionality
4. Shuffle and Loop functionality
5. The SD Card must be mounted by the phone in order to load music/pictures from it
Doesn't sound too hard right? So let's begin:
1. Create a new project File > New > Android Project
2. Name it MyMediaPlayer
3. Set the MinSdk to 3 and the TargetSdk to 8
4. Name your package
5. Eclipse will create the onCreate Activity for you (e.g. 'MyMediaPlayerActivity.java' ) If you want
to name it something else, go ahead; it won't hurt anything.
If you have downloaded the source for this project and want to simply import it:
1. Make sure that theMyMediaPlayer project is in the "Eclipse Projects" folder
2. File > Import > Existing Projects into Workspace
3. Browse > MyMediaPlayer > OK
4. Make sure the checkbox in the "Projects:" box is checked > Finish
Now let's take a look at the manifest file.
Manifest.xml
?
12345678910111213141516171819
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.technegames.mymediaplayer" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MyMediaPlayerActivity" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
</application> <uses-permission android:name="android.permission.WAKE_LOCK" /></manifest>
Manifest
As you can see, the manifest acts like a parental guardian for your App. It is responsible for
supplying the versionCode, which is important when uploading applications to the Android Market. It
also determines how and when activities can be launched via intent filters. The manifest is also
where you specify certain permissions such as internet access. For example, in MyMediaPlayer, we
use the WAKE_LOCK permission so that we can keep the phone on while the app is running.
Otherwise, the song would pause itself after 20 seconds or so. Without the permission specified in
the manifest, the application would crash upon loading because it attempted to utilize a wake lock
without permission.
The Android Manifest is an important XML document, so familiarize yourself with it. Eclipse will
generate a manifest file for you when creating a new project, so if you created this project from
scratch, the only line you would have to add would be the "uses-permission" for the WAKE_LOCK
Alright, while on the topic of XML documents, let's take a look at our main layout. After this we will
get to the Java code I promise!
main.xml
?
12345678910111213141516171819202122232425262728
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/screen" android:layout_width="fill_parent" android:layout_height="fill_parent" <LinearLayout android:orientation="vertical" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_width="fill_parent" android:layout_height="fill_parent" android:weightSum="7" > <LinearLayout android:layout_weight="6" android:orientation="vertical" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_width="fill_parent" android:layout_height="0dip" <ImageView android:id="@+id/bg" android:layout_width="fill_parent" android:layout_height="fill_parent" </ImageView> </LinearLayout> <LinearLayout android:layout_weight="1" android:id="@+id/buttons" android:orientation="horizontal" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_width="fill_parent" android:layout_height="0dip" android:background="#FFFFFF" <Button android:id="@+id/btnPrevious" android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1" android:background="@drawable/previous" android:layout_margin="5dp" android:onClick="click" /> <View android:layout_weight="1" android:layout_width="0dip" android:layout_height="0dip" <Button android:id="@+id/btnPlay" android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1" android:background="@drawable/play" android:layout_margin="5dp" android:onClick="click" /> <View android:layout_weight="1" android:layout_width="0dip" android:layout_height="0dip" <Button android:id="@+id/btnNext" android:layout_width="0dip" android:layout_height="fill_parent" android:layout_weight="1" android:background="@drawable/next" android:layout_margin="5dp" android:onClick="click" /> </LinearLayout> </LinearLayout></RelativeLayout>
main
If you are unfamilar with XML, at first glance this code might look very confusing. I assure you it is
very simple, and is one of the greatest features of Android. If you are coming into Android
Development from a Java Swing background, you will be happy to know that you can completely
separate your layout/gui elements from your main code by setting it up in an XMLdocument.
In the MyMediaPlayer program, we use a Relative Layout with Linear Layouts nested inside. The
Relative Layout allows us to specify where the Linear Layouts are placed in relation to each other.
The topmost Linear Layout is used for the album art. We use
theandroid:layout_alignParentTop="true" and android:layout_alignParentLeft="true"to specify that
the album art layout is aligned to the topleft corner of the screen. We use layout_weight to dictate
that the layout containing the image takes up 6/7 of the screen, with the remaining 1/7 for the
buttons. Speaking of buttons, in each of the buttons there is android:onClick="click". This means that
when you click on any of these buttons, they call a function called click in
MyMediaPlayerActivity.java, which we will get to in a bit.
The best way to learn Android layouts is to simply play around with one that works. Adjust settings
and values and notice the changes. Parent/Child relationships can get very complicated, so it is best
to just take it one step at a time. There is a nice Tool online that generates an XML layout based on
what you drag-and-drop onto the screen. If you think this tool may help you with Android Layouts,
click here to download it!
Alright, Let's dive into some java code!
Music.java
?
12345678910111213141516171819
package com.technegames.mymediaplayer; import java.io.FileDescriptor;import java.io.IOException; import android.content.res.AssetFileDescriptor;import android.media.MediaPlayer;import android.media.MediaPlayer.OnCompletionListener; public class Music implements OnCompletionListener{ MediaPlayer mediaPlayer; boolean isPrepared = false; public Music(AssetFileDescriptor assetDescriptor){ mediaPlayer = new MediaPlayer(); try{ mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(), assetDescriptor.getStartOffset(), assetDescriptor.getLength()); mediaPlayer.prepare(); isPrepared = true; mediaPlayer.setOnCompletionListener(this); } catch(Exception ex){ throw new RuntimeException("Couldn't load music, uh oh!");
20212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
} }
public Music(FileDescriptor fileDescriptor){ mediaPlayer = new MediaPlayer(); try{ mediaPlayer.setDataSource(fileDescriptor); mediaPlayer.prepare(); isPrepared = true; mediaPlayer.setOnCompletionListener(this); } catch(Exception ex){ throw new RuntimeException("Couldn't load music, uh oh!"); } }
public void onCompletion(MediaPlayer mediaPlayer) { synchronized(this){ isPrepared = false; } }
public void play() { if(mediaPlayer.isPlaying()){ return; } try{ synchronized(this){ if(!isPrepared){ mediaPlayer.prepare(); } mediaPlayer.start(); } } catch(IllegalStateException ex){ ex.printStackTrace(); } catch(IOException ex){ ex.printStackTrace(); } }
public void stop() { mediaPlayer.stop(); synchronized(this){ isPrepared = false; } }
public void switchTracks(){ mediaPlayer.seekTo(0); mediaPlayer.pause(); }
public void pause() { mediaPlayer.pause(); }
66676869707172737475767778798081828384858687888990919293949596979899100
public boolean isPlaying() { return mediaPlayer.isPlaying(); }
public boolean isLooping() { return mediaPlayer.isLooping(); }
public void setLooping(boolean isLooping) { mediaPlayer.setLooping(isLooping); }
public void setVolume(float volumeLeft, float volumeRight) { mediaPlayer.setVolume(volumeLeft, volumeRight); }
public void dispose() { if(mediaPlayer.isPlaying()){ stop(); } mediaPlayer.release(); }}
Music
Alright, if you gave the above code a good scan, you probably noticed that Music.java is responsible
for instantiating our media player and controlling it. It implements the OnCompletionListener so that
we can monitor when a song is finished playing. The reason we do all of this in a separate class is
because we will instantiate it every time we seek to a different track, rather than trying to load all of
the tracks into memory.
Whenever you feel comfortable with the Music class, let's move on to the main attraction,
MyMediaPlayerActivity.java
MyMediaPlayerActivity.java Variables
?
123456789101112131415161718192021222324252627282930313233343536373839404142434445
package com.technegames.mymediaplayer; import java.io.File;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.IOException;import java.util.ArrayList;import java.util.List;import java.util.Random; import android.app.Activity;import android.content.Context;import android.content.res.AssetFileDescriptor;import android.content.res.AssetManager;import android.graphics.drawable.Drawable;import android.media.AudioManager;import android.os.Bundle;import android.os.Environment;import android.os.PowerManager;import android.os.PowerManager.WakeLock;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.Window;import android.view.WindowManager;import android.widget.Button;import android.widget.ImageView;import android.widget.Toast; public class MyMediaPlayerActivity extends Activity { WakeLock wakeLock; private static final String[] EXTENSIONS = { ".mp3", ".mid", ".wav", ".ogg", ".mp4" List<String> trackNames; //Playable Track Titles List<String> trackArtworks; //Track artwork names AssetManager assets; //Assets (Compiled with APK) File path; //directory where music is loaded from on SD Card File path2; //directory where album artwork is loaded from on SD Card Music track; //currently loaded track ImageView bg; //Track artwork Button btnPlay; //The play button will need to change from 'play' to 'pause', so we need an instance of it Random random; //used for shuffle boolean shuffle; //is shuffle mode on? boolean isTuning; //is user currently jammin out, if so automatically start playing the next track int currentTrack; //index of current track selected int type; //0 for loading from assets, 1 for loading from SD card
MyMediaPlayerActivity Variables
Before we discuss the onCreate activity, let's talk about our Class Level variables.
First, we have our wakeLock, which if you recall from earlier is used to keep the phone on. We also
have our EXTENSIONS array, which is used to validate that the files we load into our program are
actual sound files. The trackNames ArrayList is used to store all of the track names in memory for
reference by the media player. The trackArtworks ArrayList matches the trackNames List except it
does not contain any extensions. It is used when trying to find a picture with the same name as the
track currently playing and if so load that image. The assets are used to retrieve a list of all the files
in the 'assets' folder of your program. Assets are the resources that get compiled along with your
program. I've included 4 tracks in the source of this program below. The program by default loads up
the assets folder. By pressing the MENU button on your phone and then clicking the SOURCE
option you can specify the program to load tracks from the 'music' folder on the root of your SD card.
The rest of the variables are pretty self explanatory and if not I've included inline comments.
MyMediaPlayerActivity.java onCreate, onResume, onPause
?
123456789101112131415161718192021222324252627
@Overridepublic void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setVolumeControlStream(AudioManager.STREAM_MUSIC); PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "MyMediaPlayer"); setContentView(R.layout.main);
initialize(0);}
@Overridepublic void onResume(){ super.onResume(); wakeLock.acquire();}
@Overridepublic void onPause(){ super.onPause(); wakeLock.release(); if(track != null){ if(track.isPlaying()){ track.pause(); isTuning = false; btnPlay.setBackgroundResource(R.drawable.play); } if(isFinishing()){ track.dispose(); finish();
282930313233343536373839
} } else{ if(isFinishing()){ finish(); } }}
onCreate, onResume, onPause
These functions override their parent counterparts from Activity class. They are important because
they handle the Activity Lifecycle. Lines 4-8 set up full-screen mode and a wakeLock to insure that
the phone screen does not turn off (which would result in an onPause event). On Line 9 we set the
content view to the layout we created above, meaning we will have an ImageView and 3 Buttons on
the screen. The last thing we do in onCreate is call initialize(0). The 0 in the parentheses means that
the media player will load files from the program's assets upon load. Change it to a 1 to load from
the SD Card upon load. We will look at the initialize method in the next code block.
The onResume method is where we will acquire the wakeLock. We do this in onResume because
we release it in the onPause method. If we were to acquire the wakeLock in the onCreate method, it
would only be done so once.
The onPause method is fired when the program is interrupted. For example, the phone is receiving a
phone call or the user presses the HOME button. To prepare for something like this, we override the
onPause method from Activity. If track is not null and it is playing it gets paused and the pause
button gets turned into a play button. We could have also chosen to override onStop, but the
isFinishing method serves the same purpose. If the program is finishing, we dispose the mediaplayer
object and call the Activity's finish() method.
MyMediaPlayerActivity.java initialize, addTracks, loadTrack
?
123456789
private void initialize(int type){ bg = (ImageView) findViewById(R.id.bg); btnPlay = (Button) findViewById(R.id.btnPlay); btnPlay.setBackgroundResource(R.drawable.play); trackNames = new ArrayList<String>(); trackArtworks = new ArrayList<String>(); assets = getAssets(); currentTrack = 0; shuffle = false; isTuning = false;
10111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
random = new Random(); this.type = type;
addTracks(getTracks()); loadTrack();}
//Generate a String Array that represents all of the files foundprivate String[] getTracks(){ if(type == 0){ try { String[] temp = getAssets().list(""); return temp; } catch (IOException e) { e.printStackTrace(); Toast.makeText(getBaseContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } } else if(type == 1){ if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY)){ path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); path2 = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); String[] temp = path.list(); return temp; } else{ Toast.makeText(getBaseContext(), "SD Card is either mounted elsewhere or is unusable", Toast.LENGTH_LONG).show(); } } return null;}
//Adds the playable files to the trackNames Listprivate void addTracks(String[] temp){ if(temp != null){ for(int i = 0; i < temp.length; i++){ //Only accept files that have one of the extensions in the EXTENSIONS array if(trackChecker(temp[i])){ trackNames.add(temp[i]); trackArtworks.add(temp[i].substring(0, temp[i].length()-4)); } } Toast.makeText(getBaseContext(), "Loaded " + Integer.toString(trackNames.size()) + " Tracks", Toast.LENGTH_SHORT).show(); }}
//Checks to make sure that the track to be loaded has a correct extensonprivate boolean trackChecker(String trackToTest){ for(int j = 0; j < EXTENSIONS.length; j++){ if(trackToTest.contains(EXTENSIONS[j])){ return true; } } return false;}
5657585960616263646566676869707172737475
//Loads the track by calling loadMusicprivate void loadTrack(){ if(track != null){ track.dispose(); } if(trackNames.size() > 0){ track = loadMusic(type); setImage("drawable/" + trackArtworks.get(currentTrack)); }}
initialize, addTracks, loadTrack
The initialize method initializes all of our variables so that we do not end up getting null pointer
exceptions. The bottom of the method adds all of the tracks from either the compiled assets or from
"scard/music", making sure to only add tracks with a valid extension ( e.g. ".mp3", ".mid" ) After that,
the loadTrack() method is called.
The loadTrack() method will be called everytime a new track has been selected. In the loadTrack()
method, we call loadMusic() and setImage(), which we will take a look at below.
MyMediaPlayerActivity.java loadMusic, setImage
?
12345678910111213
//loads a Music instance using either a built in asset or an external resourceprivate Music loadMusic(int type){ switch(type){ case 0: try{ AssetFileDescriptor assetDescriptor = assets.openFd(trackNames.get(currentTrack)); return new Music(assetDescriptor); } catch(IOException e){ e.printStackTrace(); Toast.makeText(getBaseContext(), "Error Loading " + trackNames.get(currentTrack), Toast.LENGTH_LONG).show(); } return null; case 1: try{ FileInputStream fis = new FileInputStream(new File(path, trackNames.get(currentTrack))); FileDescriptor fileDescriptor = fis.getFD();
14151617181920212223242526272829303132333435363738394041424344454647484950515253
return new Music(fileDescriptor); } catch(IOException e){ e.printStackTrace(); Toast.makeText(getBaseContext(), "Error Loading " + trackNames.get(currentTrack), Toast.LENGTH_LONG).show(); } return null; default: return null; }}
//Sets the background image to match the track currently playing or a default imageprivate void setImage(String name) { if(type == 0){ int imageResource = getResources().getIdentifier(name, null, getPackageName()); if(imageResource != 0){ Drawable image = getResources().getDrawable(imageResource); bg.setImageDrawable(image); } else{ int defaultImageResource = getResources().getIdentifier("drawable/defaultbg", null, getPackageName()); if(defaultImageResource != 0){ Drawable image = getResources().getDrawable(defaultImageResource); bg.setImageDrawable(image); } } } else if(type == 1){ if(new File(path2.getAbsolutePath(), trackArtworks.get(currentTrack) + ".jpg").exists()){ bg.setImageDrawable(Drawable.createFromPath(path2.getAbsolutePath() + "/" } else{ int defaultImageResource = getResources().getIdentifier("drawable/defaultbg", null, getPackageName()); if(defaultImageResource != 0){ Drawable image = getResources().getDrawable(defaultImageResource); bg.setImageDrawable(image); } } }}
loadMusic, setImage
The loadMusic is probably the most important method in the entire program. You will want to learn
the Music.java class and this method because they can translate to any program that needs music,
not just a media player.
In our loadMusic() method, we either load up a track from our compiled assets (the ones I include
with the download above) or one of your songs located in the "music" folder on your SD Card.
The setImage() method is called immediately the loadMusic() method to change the album art
accordingly. In our simple example, it simply scans the "pictures" folder on your SD Card for a
picture with same name as the track currently playing excluding the extension and sets it
accordingly. If no image can be found, a default image is loaded up from the drawable folder of your
project.
Alright, I know we have covered a lot of ground here, but we are almost done. The next two sections
cover the user input.
MyMediaPlayerActivity.java Menu, Options, setShuffle
?
12345678910111213141516171819202122232425262728293031
@Overridepublic boolean onCreateOptionsMenu(Menu menu){ super.onCreateOptionsMenu(menu); createMenu(menu); return true;}
private void createMenu(Menu menu){ MenuItem miLooping = menu.add(0, 0, 0, "Looping");{ miLooping.setIcon(R.drawable.looping); } MenuItem miShuffle = menu.add(0, 1, 1, "Shuffle");{ miShuffle.setIcon(R.drawable.shuffle); } MenuItem miStop = menu.add(0, 2, 2, "Stop");{ miStop.setIcon(R.drawable.stop); } MenuItem miSource = menu.add(0, 3, 3, "Source");{ miSource.setIcon(R.drawable.source); }}
@Overridepublic boolean onOptionsItemSelected(MenuItem item){ switch(item.getItemId()){ case 0: //Set Looping synchronized(this){ if(track.isLooping()){ track.setLooping(false); Toast.makeText(getBaseContext(), "Playing Tracks Sequentially", Toast.LENGTH_SHORT).show(); } else{ track.setLooping(true); Toast.makeText(getBaseContext(), "Looping " + trackNames.get(currentTrack), Toast.LENGTH_SHORT).show(); } } return true;
32333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
case 1: //Set Shuffle synchronized(this){ if(shuffle){ setShuffle(false); } else{ setShuffle(true); } } return true; case 2: //Stop Music synchronized(this){ track.switchTracks(); btnPlay.setBackgroundResource(R.drawable.play); } return true; case 3: //Change Source from Assets to SD Card and vice versa synchronized(this){ type++; if(type > 1){ type = 0; } } if(type == 0){ Toast.makeText(getBaseContext(), "Loading Tracks from Assets ", Toast.LENGTH_SHORT).show(); } else if(type == 1){ Toast.makeText(getBaseContext(), "Loading Tracks from SD Card", Toast.LENGTH_SHORT).show(); } initialize(type); return true; default: return false; }}
//Simply sets shuffle to isShuffle and then displays a message for confirmationprivate void setShuffle(boolean isShuffle) { shuffle = isShuffle; if(shuffle){ Toast.makeText(getBaseContext(), "Shuffle On", Toast.LENGTH_SHORT).show(); } else{ Toast.makeText(getBaseContext(), "Shuffle Off", Toast.LENGTH_SHORT).show(); }}
787980818283Menu, Options, setShuffle
First off, we must override a functon called onCreateOptionsMenu(). It is called whenever the Menu
Button is pressed on your phone. It is particularly useful if there are extra controls you want to
provide to your users without having to clutter the screen with additional buttons. In our
onCreateOptionsMenu() method, we call a createMenu() method.
The createMenu() method is responsible for setting populating the menu options. We have 4 menu
options in total: "Looping", "Shuffle", "Stop", and "Source"
The onOptionsItemSelected() method provides functionality for these menu options. We use a
switch on the item.getItemId() function, which returns an int that distinguishes the options from each
other. The "Looping" and "Stop" Options seem pretty self explanatory, so I won't go over those.
When the "Shuffle" Option is clicked, the setShuffle() method is called, which sets shuffle to to either
true of false and displays a message for confirmation.
When the "Source" Option is clicked, type is switched from 0 to 1 and vice versa and the initialize()
method is called again, except this time with the new type. If the type is set to 1, the SD Card will be
loaded up instead of the "assets" folder of your project.
Alright, let's move on to the on-screen controls!
MyMediaPlayerActivity.java click, setTrack, playTrack
?
123456789101112131415
public void click(View view){ int id = view.getId(); switch(id){ case R.id.btnPlay: synchronized(this){ if(isTuning){ isTuning = false; btnPlay.setBackgroundResource(R.drawable.play); track.pause(); } else{ isTuning = true; btnPlay.setBackgroundResource(R.drawable.pause); playTrack(); } } return; case R.id.btnPrevious: setTrack(0);
16171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
loadTrack(); playTrack(); return; case R.id.btnNext: setTrack(1); loadTrack(); playTrack(); return; default: return; }}
private void setTrack(int direction){ if(direction == 0){ currentTrack--; if(currentTrack < 0){ currentTrack = trackNames.size()-1; } } else if(direction == 1){ currentTrack++; if(currentTrack > trackNames.size()-1){ currentTrack = 0; } } if(shuffle){ int temp = random.nextInt(trackNames.size()); while(true){ if(temp != currentTrack){ currentTrack = temp; break; } temp++; if(temp > trackNames.size()-1){ temp = 0; } } }}
//Plays the Trackprivate void playTrack(){ if(isTuning && track != null){ track.play(); Toast.makeText(getBaseContext(), "Playing " + trackNames.get(currentTrack).substring(0, trackNames.get(currentTrack).length()-4), Toast.LENGTH_SHORT).show(); }}
62636465click, setTrack, playTrack, setShuffle
If you recall from earlier when we created our main.xml layout, we talked about the
android:onClick="click" in each of the buttons. The click function must be public, have a return type
of void, and accept a View instance as a parameter; otherwise you will receive errors. Since all of
the buttons call the same function, how do we know which button called it? We set up a switch on
the view.getId() function, which returns the id of the button. The Next and Previous Buttons simply
call the setTrack() method, the loadTrack() method we discussed earlier, and then the playTrack()
method, which plays the newly loaded song if the user was playing the last song.
The setTrack() method moves the currentTrack index either forward or backward. If shuffle is on,
then currentTrack index becomes a random number not equal to what it was before the method call.
The playTrack() method simply calls the play() method from Music.java, provided that track is not
null.
Run Run As Android Application
Compiling and Running!
Did I forget anything? I think that's about it, if you have been following along and creating your
project from scratch, make sure you do not have any pesky red underlines in your code. At this
point, we can go ahead and run our program. You can either click Run > Run As > Android
Application or right-click your project and Run As > Android Application
Well? Do the Buttons work? How about the Menu Options? Try alternating between Loop and
Shuffle Mode. Click the Source button to load up your SD Card, but make sure you disconnect your
phone from the PC first!
Playing the built in tracks from the "assets" folder of your project
Playing Songs from the SD Card's "music" folder
Congratulations
You now have a working custom media player for your Android Device! The program works
decently, but there is more to be desired isn't there? If you wanted to publish this it most likely would
not perform well on the Market, as there are much better media players available. To make this
media player better, you could add a thread that constantly checks if the onCompletionListener
event is satisfied and if so go to the next song. Or you could stream album art from a locally stored
database instead of relying on the "pictures" folder of the SD card. Animating visualizations would be
nice too wouldn't they? The possibilities are endless! As an Android Developer, these choices are
entirely yours, so experiment and try new things!
*** If you had any problems with this tutorial, get the source code hereand take a look at it. Play
around with the code and understand how it all works together. Good Luck! ***