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.

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:
|
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | [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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | @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<song> 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(); } } </song> |
The listview is defined in the layout/main.xml file.
1 2 3 | < linearlayout xmlns:android = "http://schemas.android.com/apk/res/android" android:orientation = "vertical" android:layout_width = "fill_parent" android:layout_height = "fill_parent" > < listview android:id = "@+id/gridPlaylist" android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:visibility = "visible" > </ listview ></ linearlayout > |
Music player functions (play, pause, previous and next) are displayed in the menu.
The menu is defined in menu/main.xml:
1 2 3 4 5 6 7 | <!--?xml version="1.0" encoding="UTF-8"?--> < item android:id = "@+id/menuPause" android:title = "Pause" > < item android:id = "@+id/menuPlay" android:title = "Play" > < item android:id = "@+id/menuPrevious" android:title = "Previous" > < item android:id = "@+id/menuNext" android:title = "Next" > </ item ></ item ></ item ></ item ></ menu > |
Initialization of the menu happens in the onCreateOptionsMenu.
1 2 3 4 5 6 | @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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @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!