Java tutorial
// Copyright 2011 The Chicago Independent Radio Project // Copyright 2010 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.chirpradio.mobile; import java.io.IOException; import android.app.Service; import android.content.Intent; import android.content.Context; import android.content.IntentFilter; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.content.BroadcastReceiver; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import java.util.LinkedList; import java.util.List; import android.os.AsyncTask; import java.io.BufferedReader; import org.json.JSONObject; import org.json.JSONArray; import android.os.Handler; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Notification; import org.npr.android.news.StreamProxy; public class PlaybackService extends Service implements OnPreparedListener, OnErrorListener, OnCompletionListener { // This URL redirects to the real stream URL. private static final String STREAM_URL = "http://chirpradio.org/stream"; private MediaPlayer mediaPlayer; private StreamProxy streamProxy; private Boolean isPrepared = false; private Boolean isPreparing = false; private Boolean isPlaying = false; private Boolean isInCall = false; private Boolean isStopping = false; private Boolean stopAfterPrepared = false; private Handler handler; private Boolean updatePlaylist; private Boolean headphonesInUse; private PhoneStateListener phoneStateListener; private OnPlaybackStartedListener onPlaybackStartedListener; private OnPlaybackStoppedListener onPlaybackStoppedListener; // Service setup // public class PlaybackBinder extends Binder { PlaybackService getService() { return PlaybackService.this; } } @Override public IBinder onBind(Intent intent) { Debug.log(this, "Binding to service"); return new PlaybackBinder(); } @Override public boolean onUnbind(Intent intent) { Debug.log(this, "onUnbind"); return false; } @Override public void onCreate() { Debug.log(this, "created"); mediaPlayer = new MediaPlayer(); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnErrorListener(this); mediaPlayer.setOnCompletionListener(this); String playbackUrl; playbackUrl = STREAM_URL; try { mediaPlayer.setDataSource(playbackUrl); } catch (IOException e) { e.printStackTrace(); } mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); setupTelephonyHooks(); // the headphone action gets called every time we get a start // message, i guess so i know the headphone state all the time. // so, i can assume there are no headphones plugged in, and the // broadcast receiver will set it to true if there are when // it gets notified the first time headphonesInUse = false; } public boolean isPlaying() { return isPlaying; } // Start on pre-2.0 systems @Override public void onStart(Intent intent, int startId) { Debug.log(this, "error - we shouldn't be starting on pre 2.0 devices"); Debug.log(this, "Received start id " + startId + ": " + intent); } // Start on 2.0+ systems @Override public int onStartCommand(Intent intent, int flags, int startId) { Debug.log(this, "Received start id " + startId + ": " + intent); registerReceiver(headphoneReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); Debug.log(this, "Starting get playlist task"); updatePlaylist = true; handler = new Handler(); handler.post(mUpdateTask); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; } @Override public void onDestroy() { Debug.log(this, "DESTROYED"); mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } private void notifyUi(String playlist) { Debug.log(this, "Notifying UI of playlist update"); try { Track t = new Track(new JSONObject(playlist).getJSONObject("now_playing")); if (isPlaying) setNotification("CHIRP Radio", t.getArtist() + " - " + t.getTrack()); } catch (Exception e) { Debug.log(this, playlist); Debug.log(this, "Error parsing now_playing: " + e.toString()); } Intent intent = new Intent(); intent.setAction("CHIRP"); intent.putExtra("playlist", playlist); sendBroadcast(intent); } public synchronized void start() { Debug.log(this, "start called"); if (isStopping) { Debug.log(this, "still stopping"); return; } if (isPrepared) { Debug.log(this, "start"); mediaPlayer.start(); } else { // make sure we get to the newest part of the stream mediaPlayer.reset(); try { mediaPlayer.setDataSource(STREAM_URL); } catch (IOException e) { e.printStackTrace(); } Debug.log(this, "start, but not prepared"); if (streamProxy != null) { streamProxy.start(); } Debug.log(this, "Preparing media player"); isPreparing = true; mediaPlayer.prepareAsync(); return; } } public synchronized void stop() { if (isPreparing) { stopAfterPrepared = true; return; } if (isStopping || !isPlaying) { Debug.log(this, "Stop playback called, but we're either stopped or not playing"); return; } mediaPlayer.stop(); isPlaying = false; isPrepared = false; isStopping = true; cancelNotification(); // Stopping the StreamProxy can be slow, so do it in a different thread // so we don't block the UI new Thread(new Runnable() { public void run() { if (streamProxy != null) { streamProxy.stop(); } isStopping = false; if (onPlaybackStoppedListener != null) { onPlaybackStoppedListener.onPlaybackStopped(); } } }).start(); } // MediaPlayer callbacks // @Override public void onPrepared(MediaPlayer mp) { Debug.log(this, "prepared, starting media player"); isPreparing = false; isPrepared = true; isPlaying = true; if (stopAfterPrepared) { // i should be able to leave the media player in the prepared state // to make subsequent start calls go faster Debug.log(this, "Prepare completed, but the user told us to stop already"); } else { mediaPlayer.start(); if (onPlaybackStartedListener != null) { onPlaybackStartedListener.onPlaybackStarted(); } } } @Override public boolean onError(MediaPlayer mp, int what, int extra) { Debug.log(this, "ERROR! what: " + what + " extra: " + extra); return false; // Also call onCompletion } @Override public void onCompletion(MediaPlayer mp) { if (onPlaybackStoppedListener != null) { onPlaybackStoppedListener.onPlaybackStopped(); } Debug.log(this, "onCompletion"); isPrepared = false; isPlaying = false; } public void setOnPlaybackStoppedListener(OnPlaybackStoppedListener listener) { onPlaybackStoppedListener = listener; } public void setOnPlaybackStartedListener(OnPlaybackStartedListener listener) { onPlaybackStartedListener = listener; } private static final int CHIRP_ID = 1019; private void setNotification(String title, String message) { String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); CharSequence tickerText = message; long when = System.currentTimeMillis(); int icon = R.drawable.icon; Notification notification = new Notification(icon, tickerText, when); notification.flags = Notification.FLAG_ONGOING_EVENT; Context context = getApplicationContext(); CharSequence contentTitle = title; CharSequence contentText = message; Intent notificationIntent = new Intent(this, Playing.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); mNotificationManager.notify(CHIRP_ID, notification); } private void cancelNotification() { String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); mNotificationManager.cancel(CHIRP_ID); } private void setupTelephonyHooks() { TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); phoneStateListener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_RINGING: if (isPlaying) { Debug.log(this, "call began, stopping..."); stop(); isInCall = true; } break; case TelephonyManager.CALL_STATE_IDLE: if (isInCall) { isInCall = false; Debug.log(this, "call ended; resuming..."); start(); } break; } } }; telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } private BroadcastReceiver headphoneReceiver = new BroadcastReceiver() { @Override public void onReceive(Context arg0, Intent intent) { Debug.log(this, "headphone broadcast message received"); if (intent.getAction().equalsIgnoreCase(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", -1); if (state == 1) { headphonesInUse = true; } if (state == 0 && headphonesInUse) { Debug.log(this, "headphones unplugged - stopping playback"); Debug.log(this, "name: " + intent.getStringExtra("name")); Debug.log(this, "microphone: " + intent.getIntExtra("microphone", -1)); headphonesInUse = false; stop(); } } } }; // handles updating the get playlist task private Runnable mUpdateTask = new Runnable() { public void run() { if (updatePlaylist) { Debug.log(this, "updating playlist from timer"); new GetPlaylistTask().execute(); } } }; private class GetPlaylistTask extends AsyncTask<Void, Integer, Boolean> { private boolean result = false; //private List<Track> playlist = new LinkedList<Track>(); private String playlist; protected Boolean doInBackground(Void... no) { BufferedReader in = null; Boolean result = false; try { playlist = Request.sendRequest(); result = true; } catch (Exception e) { Debug.log(this, "Exception getting playlist: " + e.toString()); result = false; } finally { } return result; } protected void onPostExecute(Boolean result) { Debug.log(this, "Finished getting playlist"); if (!result) { Debug.log(this, "Retrieving playlist failed"); } else { //updateCurrentlyPlaying(playlist); notifyUi(playlist); } Debug.log(this, "Next playlist update in 20 seconds"); //Track t = playlist.get(0); //setNotification("CHIRP Radio", t.getArtist() + " - " + t.getTrack()); handler.postDelayed(mUpdateTask, 20000); } } }