Music Player – .NET REST Service And Android

By | February 26, 2011

I often use my computer as a music player. But I don’t have a remote controller for it. If I want to change the song, I need to do it on my PC.
That’s not cool. Yesterday I decided to build a remote control app for my Samsung Galaxy S (Android 2.0). This should solve my problem forever and ever.

Here is a picture of my new home “infrastructure”. I’ve removed my confidential information.
😉

Home Infrastructure

Description:

The WPF application parses my MP3 playlist (.m3u file) and shows the songs in a datagrid. I created some basic functions like “play”, “pause” etc. Playing media is done with the “MediaElement”.

The WPF application contains a self-hosted WCF service. This service exposes the media player functions (“play”, “pause” etc.).
I decided to create a REST service, because this is more suitable for the Android development than SOAP webservice.

The Android app is easy. It sends via http get the commands to the WCF REST service. Following requests are accepted:

http://…/ Gets the playlist.
http://…/controller/pause Pause song.
http://…/controller/play Play song.
http://…/songs/{songId} Change song by passing the songId.
http://…/controller/next Play the next song.
http://…/controller/previous Play the previous song.

Here are some key functions. If you need the whole code, please let me know.

Computer music player application (WPF/WCF):

WCF contract for REST service.

 
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        [WebGet(UriTemplate = "/")]
        Song[] GetPlaylist();

        [OperationContract]
        [WebGet(UriTemplate = "/songs/{songId}")]
        void ChangeSong(string songId);

        [OperationContract]
        [WebGet(UriTemplate = "/controller/pause")]
        void Pause();

        [OperationContract]
        [WebGet(UriTemplate = "/controller/play")]
        void Play();

        [OperationContract]
        [WebGet(UriTemplate = "/controller/next")]
        void Next();

        [OperationContract]
        [WebGet(UriTemplate = "/controller/previous")]
        void Previous();
    }

m3u Parser.

    public class Playlist
    {
        public Songs GetPlaylist(string path)
        {
            if (!path.ToLower().EndsWith(".m3u"))
                throw new ArgumentException("Wrong file. Only *.m3u files accepted.", "path");

            if (!File.Exists(path))
                throw new Exception("File doesn't exists.");

            var songList = new Songs();
            string fileRow;
            int id = 0;
            string title;
            using (var reader = new StreamReader(path, Encoding.Default))
            { 
                reader.ReadLine();
                while (!reader.EndOfStream)
                {
                    fileRow = reader.ReadLine();
                    if(fileRow.StartsWith("#"))
                    {
                        if(reader.EndOfStream)
                            throw new Exception(string.Format("m3u file format is wrong. Can't find file for song '{0}'", fileRow));

                        title = fileRow.Substring(fileRow.IndexOf(',')+1);
                        songList.Add(new Song(id++, title, reader.ReadLine()));
                    }
                    else
                        throw new Exception("m3u file format is wrong. Check file.");
                }
            }
            return songList;
        }

        public Songs GetPlaylist()
        {
            return this.GetPlaylist(@"E:\MySongs.m3u");
        }
    }

Android mobile app (Java):

I created a class which handles the REST calls.

public class RESTHandler {
	
	private DefaultHttpClient request;
	private String uri;
	private HttpGet method;
	
	public RESTHandler(String baseUri) 
			throws URISyntaxException, ClientProtocolException, IOException {	
    	this.request = new DefaultHttpClient();
    	this.uri = baseUri;
	}
	
	public void sendRequest(String action) 
			throws URISyntaxException, ClientProtocolException, IOException {
		if(this.method == null)
			this.method = new HttpGet(this.uri);
		
		this.method.setURI(new URI(this.uri + action));
		this.request.execute(this.method);
	}
	
	public String getUri() {
		return this.uri;
	}
}

The following actions are performed when opening the app:
– Call REST service and get playlist.
– Parse playlist with SAX.
– Register click event.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    try {
    	// Parse playlist with SAX parser
        URL url = new URL(controller.PlayerUrl + "/");
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser parser = factory.newSAXParser();
		PlaylistHandler handler = new PlaylistHandler();
		URLConnection connect = url.openConnection();
		connect.setConnectTimeout(8000);
		parser.parse(connect.getInputStream(), handler);
		// Get the songs
		final List songs = handler.getSongs();
		
		// Define the adapter
        ListAdapter adapter = new SongListAdapter(this, songs, 
		android.R.layout.simple_list_item_2,
		new String[] { Song.KEY_TITLE}, 
		new int[] { android.R.id.text2 });
        this.setListAdapter(adapter);
        
        // Click-handler.
        // Click = change song on pc. -> Send REST call http://..../songs/134
        final ListView.OnItemClickListener onItemClickListener = new  ListView.OnItemClickListener() {
            public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) {
				try {
					request.sendRequest("/songs/" + songs.get(arg2).Id);
				} catch (Throwable e) {
					e.printStackTrace();
				} 
            };
        };
        
        this.getListView().setOnItemClickListener(onItemClickListener);
    } catch(Throwable e) {
    	e.printStackTrace();
    }
}

The listview is defined in the layout/main.xml file.




Music player functions (play, pause, previous and next) are displayed in the menu.
The menu is defined in menu/main.xml:



	
	
	
	

Initialization of the menu happens in the onCreateOptionsMenu.

   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
	   super.onCreateOptionsMenu(menu);
	   new MenuInflater(this.getApplication()).inflate(R.menu.main, menu);
	   return true;
   }

The menu click handler calls the REST service.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	try {
		String action = "";
		switch(item.getItemId()) {
			case R.id.menuPause: action = "/controller/pause"; break;
			case R.id.menuPlay:  action = "/controller/play"; break;
			case R.id.menuPrevious: action = "/controller/previous"; break;
			case R.id.menuNext: action = "/controller/next"; break;
		}
		if(action.length() > 0)
			this.request.sendRequest(action);
	} catch(Throwable e) {
		e.printStackTrace();
	}
	return true;
}

That’s all. 😉
Here is a short video that I made!