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.
[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 Listsongs = 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!