Skip to content

Instantly share code, notes, and snippets.

@jd-alexander
Created July 31, 2015 15:17
Show Gist options
  • Save jd-alexander/3560c5c4a902be33dc11 to your computer and use it in GitHub Desktop.
Save jd-alexander/3560c5c4a902be33dc11 to your computer and use it in GitHub Desktop.
// Copyright 2009 Google Inc.
// Copyright 2011 NPR
//
// 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.npr.android.news;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.List;
import android.os.*;
import org.npr.android.util.AudioManagerProxy;
import org.npr.android.util.M3uParser;
import org.npr.android.util.PlaylistParser;
import org.npr.android.util.PlaylistRepository;
import org.npr.android.util.PlsParser;
import org.npr.android.util.Tracker;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnSeekCompleteListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
public class PlaybackService extends Service implements
OnPreparedListener, OnSeekCompleteListener,
OnBufferingUpdateListener, OnCompletionListener, OnErrorListener,
OnInfoListener {
private static final String LOG_TAG = PlaybackService.class.getName();
private static final String SERVICE_PREFIX = "org.npr.android.news.";
public static final String SERVICE_CHANGE_NAME = SERVICE_PREFIX + "CHANGE";
public static final String SERVICE_CLOSE_NAME = SERVICE_PREFIX + "CLOSE";
public static final String SERVICE_UPDATE_NAME = SERVICE_PREFIX + "UPDATE";
public static final String SERVICE_ERROR_NAME = SERVICE_PREFIX + "ERROR";
public static final String SERVICE_PLAY_SINGLE = SERVICE_PREFIX +
"PLAY_SINGLE";
public static final String SERVICE_PLAY_ENTRY = SERVICE_PREFIX + "PLAY_ENTRY";
public static final String SERVICE_TOGGLE_PLAY = SERVICE_PREFIX +
"TOGGLE_PLAY";
public static final String SERVICE_RESUME_PLAYING = SERVICE_PREFIX +
"RESUME_PLAYING";
public static final String SERVICE_PAUSE = SERVICE_PREFIX + "PAUSE";
public static final String SERVICE_BACK_30 = SERVICE_PREFIX + "BACK_30";
public static final String SERVICE_FORWARD_30 = SERVICE_PREFIX + "FORWARD_30";
public static final String SERVICE_SEEK_TO = SERVICE_PREFIX + "SEEK_TO";
public static final String SERVICE_PLAY_NEXT = SERVICE_PREFIX + "PLAYNEXT";
public static final String SERVICE_PLAY_PREVIOUS = SERVICE_PREFIX +
"PLAYPREVIOUS";
public static final String SERVICE_STOP_PLAYBACK = SERVICE_PREFIX + "STOP_PLAYBACK";
public static final String SERVICE_STATUS = SERVICE_PREFIX + "STATUS";
public static final String SERVICE_CLEAR_PLAYER = SERVICE_PREFIX +
"CLEAR_PLAYER";
public static final String EXTRA_DOWNLOADED = SERVICE_PREFIX + "DOWNLOADED";
public static final String EXTRA_DURATION = SERVICE_PREFIX + "DURATION";
public static final String EXTRA_POSITION = SERVICE_PREFIX + "POSITION";
public static final String EXTRA_SEEK_TO = SERVICE_PREFIX + "SEEK_TO";
public static final String EXTRA_IS_PLAYING = SERVICE_PREFIX + "IS_PLAYING";
public static final String EXTRA_IS_PREPARED = SERVICE_PREFIX + "IS_PREPARED";
public static final String EXTRA_KEEP_AUDIO_FOCUS = SERVICE_PREFIX + "KEEP_AUDIO_FOCUS";
public static final String EXTRA_ERROR = SERVICE_PREFIX + "ERROR";
public static enum PLAYBACK_SERVICE_ERROR {Connection, Playback, InvalidPlayable}
private MediaPlayer mediaPlayer;
private boolean isPrepared = false;
private boolean markedRead;
// Track whether we ever called start() on the media player so we don't try
// to reset or release it. This causes a hang (ANR) on Droid X
// http://code.google.com/p/android/issues/detail?id=959
private boolean mediaPlayerHasStarted = false;
private StreamProxy proxy;
private static final int NOTIFICATION_ID = 1;
private PlaylistRepository playlist;
private int startId;
private String currentAction;
private Playable currentPlayable = null;
private List<String> playlistUrls;
// Error handling
private int errorCount;
private int connectionErrorWaitTime;
private int seekToPosition;
private TelephonyManager telephonyManager;
private PhoneStateListener listener;
private boolean isPausedInCall = false;
private Intent lastChangeBroadcast;
private Intent lastUpdateBroadcast;
private int lastBufferPercent = 0;
private Thread updateProgressThread;
private AudioManagerProxy audioManagerProxy;
// Amount of time to rewind playback when resuming after call
private final static int RESUME_REWIND_TIME = 3000;
private final static int ERROR_RETRY_COUNT = 3;
private final static int RETRY_SLEEP_TIME = 30000;
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
startId = msg.arg1;
onHandleIntent((Intent) msg.obj);
}
}
@Override
public void onCreate() {
super.onCreate();
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnBufferingUpdateListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setOnInfoListener(this);
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnSeekCompleteListener(this);
playlist = new PlaylistRepository(getApplicationContext(),
getContentResolver());
audioManagerProxy = new AudioManagerProxy(getApplicationContext());
Log.d(LOG_TAG, "Playback service created");
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
// Create a PhoneStateListener to watch for off-hook and idle events
listener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_OFFHOOK:
case TelephonyManager.CALL_STATE_RINGING:
// Phone going off-hook or ringing, pause the player.
if (isPlaying()) {
pause(false);
isPausedInCall = true;
}
break;
case TelephonyManager.CALL_STATE_IDLE:
// Phone idle. Rewind a couple of seconds and start playing.
if (isPausedInCall) {
isPausedInCall = false;
seekTo(Math.max(0, getPosition() - RESUME_REWIND_TIME));
play();
}
break;
}
}
};
// Register the listener with the telephony manager.
telephonyManager.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
HandlerThread thread = new HandlerThread("PlaybackService:WorkerThread");
thread.start();
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
Message message = serviceHandler.obtainMessage();
message.arg1 = startId;
message.obj = intent;
serviceHandler.sendMessage(message);
return START_STICKY;
}
protected void onHandleIntent(Intent intent) {
if (intent == null || intent.getAction() == null) {
Log.d(LOG_TAG, "Null intent received");
return;
}
String action = intent.getAction();
Log.d(LOG_TAG, "Playback service action received: " + action);
if (action.equals(SERVICE_PLAY_SINGLE) || action.equals(SERVICE_PLAY_ENTRY)) {
currentAction = action;
currentPlayable = intent.getParcelableExtra(Playable.PLAYABLE_TYPE);
seekToPosition = intent.getIntExtra(EXTRA_SEEK_TO, 0);
playCurrent(0, 1);
} else if (action.equals(SERVICE_TOGGLE_PLAY)) {
if (isPlaying()) {
pause(false);
// Get rid of the toggle intent, since we don't want it redelivered
// on restart
Intent emptyIntent = new Intent(intent);
emptyIntent.setAction("");
startService(emptyIntent);
} else {
if (currentPlayable == null) {
currentAction = action;
currentPlayable = intent.getParcelableExtra(Playable.PLAYABLE_TYPE);
}
if (currentPlayable != null) {
resumePlaying();
} else {
currentAction = SERVICE_PLAY_ENTRY;
errorCount = 0;
playFirstUnreadEntry();
}
}
} else if (action.equals(SERVICE_RESUME_PLAYING)) {
resumePlaying();
} else if (action.equals(SERVICE_PAUSE)) {
if (isPlaying()) {
pause(intent.getBooleanExtra(EXTRA_KEEP_AUDIO_FOCUS, false));
}
} else if (action.equals(SERVICE_BACK_30)) {
seekRelative(-30000);
} else if (action.equals(SERVICE_FORWARD_30)) {
seekRelative(30000);
} else if (action.equals(SERVICE_SEEK_TO)) {
seekTo(intent.getIntExtra(EXTRA_SEEK_TO, 0));
} else if (action.equals(SERVICE_PLAY_NEXT)) {
seekToPosition = 0;
playNextEntry();
} else if (action.equals(SERVICE_PLAY_PREVIOUS)) {
seekToPosition = 0;
playPreviousEntry();
} else if (action.equals(SERVICE_STOP_PLAYBACK)) {
stopSelfResult(startId);
} else if (action.equals(SERVICE_STATUS)) {
updateProgress();
} else if (action.equals(SERVICE_CLEAR_PLAYER)) {
if (!isPlaying()) {
stopSelfResult(startId);
}
}
}
@Override
public IBinder onBind(Intent intent) {
Log.w(LOG_TAG, "onBind called, but binding no longer supported.");
return null;
}
private void resumePlaying() {
if (currentPlayable != null) {
if (isPrepared) {
play();
} else {
playCurrent(0, 1);
}
}
}
private boolean playCurrent(int startingErrorCount, int startingWaitTime) {
errorCount = startingErrorCount;
connectionErrorWaitTime = startingWaitTime;
while (errorCount < ERROR_RETRY_COUNT) {
try {
if (currentPlayable == null || currentPlayable.getUrl() == null) {
Intent intent = new Intent(SERVICE_ERROR_NAME);
intent.putExtra(EXTRA_ERROR, PLAYBACK_SERVICE_ERROR.InvalidPlayable.ordinal());
getApplicationContext().sendBroadcast(intent);
return false;
}
prepareThenPlay(currentPlayable.getUrl(), currentPlayable.isStream());
return true;
} catch (UnknownHostException e) {
Log.w(LOG_TAG, "Unknown host in playCurrent");
handleConnectionError();
} catch (ConnectException e) {
Log.w(LOG_TAG, "Connect exception in playCurrent");
handleConnectionError();
} catch (IOException e) {
Log.e(LOG_TAG, "IOException on playlist entry " + currentPlayable.getId(), e);
incrementErrorCount();
} catch (IllegalStateException e) {
Log.e(LOG_TAG, "Illegal state exception trying to play entry " + currentPlayable.getId(), e);
incrementErrorCount();
}
}
return false;
}
private void playNextEntry() {
do {
if (currentPlayable != null && currentPlayable.getId() != -1) {
currentPlayable = playlist.getNextEntry(currentPlayable.getId());
} else {
currentPlayable = playlist.getFirstUnreadEntry();
}
} while (currentPlayable != null && !playCurrent(0, 1));
}
private void playPreviousEntry() {
do {
if (currentPlayable != null && currentPlayable.getId() != -1) {
currentPlayable = playlist.getPreviousEntry(currentPlayable.getId());
} else {
currentPlayable = playlist.getFirstUnreadEntry();
}
} while (currentPlayable != null && !playCurrent(0, 1));
}
private void playFirstUnreadEntry() {
do {
currentPlayable = playlist.getFirstUnreadEntry();
} while (currentPlayable != null && !playCurrent(0, 1));
if (currentPlayable == null) {
stopSelfResult(startId);
}
}
private void finishEntryAndPlayNext() {
if (currentPlayable != null && currentPlayable.getId() >= 0 && !markedRead) {
playlist.markAsRead(currentPlayable.getId());
}
do {
if (currentPlayable == null) {
currentPlayable = playlist.getFirstUnreadEntry();
} else {
currentPlayable = playlist.getNextEntry(currentPlayable.getId());
}
} while (currentPlayable != null && !playCurrent(0, 1));
if (currentPlayable == null) {
stopSelfResult(startId);
}
}
synchronized private int getPosition() {
if (isPrepared) {
return mediaPlayer.getCurrentPosition();
}
return 0;
}
synchronized private boolean isPlaying() {
return isPrepared && mediaPlayer.isPlaying();
}
synchronized private void seekRelative(int pos) {
if (isPrepared) {
seekToPosition = 0;
mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + pos);
}
}
synchronized private void seekTo(int pos) {
if (isPrepared) {
seekToPosition = 0;
mediaPlayer.seekTo(pos);
}
}
private void prepareThenPlay(String url, boolean stream)
throws IllegalArgumentException, IllegalStateException, IOException {
Log.d(LOG_TAG, "playNew");
// First, clean up any existing audio.
stop();
if (isPlaylist(url)) {
new downloadPlaylist().execute(url);
return;
}
Log.d(LOG_TAG, "listening to " + url + " stream=" + stream);
String playUrl = url;
// From 2.2 on (SDK ver 8), the local mediaplayer can handle Shoutcast
// streams natively. Let's detect that, and not proxy.
if (stream && Build.VERSION.SDK_INT < 8) {
if (proxy == null) {
proxy = new StreamProxy();
proxy.init();
proxy.start();
}
playUrl = String.format("http://127.0.0.1:%d/%s",
proxy.getPort(), url);
}
// We only have to mark an item read on playlist items,
// so set markedRead to false only when a playlist entry
markedRead = !currentAction.equals(SERVICE_PLAY_ENTRY);
synchronized (this) {
Log.d(LOG_TAG, "reset: " + playUrl);
mediaPlayer.reset();
mediaPlayer.setDataSource(playUrl);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
Log.d(LOG_TAG, "Preparing: " + playUrl);
mediaPlayer.prepareAsync();
Log.d(LOG_TAG, "Waiting for prepare");
}
}
synchronized private void play() {
if (!isPrepared || currentPlayable == null) {
Log.e(LOG_TAG, "play - not prepared");
return;
}
Log.d(LOG_TAG, "play " + currentPlayable.getId());
if (!audioManagerProxy.getAudioFocus()) {
Log.d(LOG_TAG, "Unable to get audio focus, so stop");
return;
}
mediaPlayer.start();
mediaPlayerHasStarted = true;
presentPlayingNotification();
// Change broadcasts are sticky, so when a new receiver connects, it will
// have the data without polling.
if (lastChangeBroadcast != null) {
getApplicationContext().removeStickyBroadcast(lastChangeBroadcast);
}
lastChangeBroadcast = new Intent(SERVICE_CHANGE_NAME);
lastChangeBroadcast.putExtra(Playable.PLAYABLE_TYPE, currentPlayable);
getApplicationContext().sendStickyBroadcast(lastChangeBroadcast);
if (currentPlayable != null && currentPlayable.getUrl() != null) {
Tracker.PlayEvent e = new Tracker.PlayEvent(currentPlayable.getUrl());
Tracker.instance(getApplication()).trackLink(e);
}
}
private void presentPlayingNotification()
{
CharSequence contentText = currentPlayable.getTitle();
Notification notification =
new Notification(R.drawable.stat_notify_musicplayer,
contentText,
System.currentTimeMillis());
notification.flags = Notification.FLAG_NO_CLEAR
| Notification.FLAG_ONGOING_EVENT;
Context context = getApplicationContext();
CharSequence title = getString(R.string.app_name);
Class<?> notificationActivity;
if (currentPlayable.getActivityName() != null) {
try {
notificationActivity = Class.forName(currentPlayable.getActivityName());
} catch (ClassNotFoundException e) {
notificationActivity = NewsListActivity.class;
}
} else {
notificationActivity = NewsListActivity.class;
}
Intent notificationIntent = new Intent(this, notificationActivity);
if (currentPlayable.getActivityData() != null) {
notificationIntent.putExtra(Constants.EXTRA_ACTIVITY_DATA,
currentPlayable.getActivityData());
notificationIntent.putExtra(Constants.EXTRA_DESCRIPTION,
R.string.msg_main_subactivity_nowplaying);
}
notificationIntent.setAction(Intent.ACTION_VIEW);
notificationIntent.addCategory(Intent.CATEGORY_DEFAULT);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
notification.setLatestEventInfo(context, title, contentText, contentIntent);
startForeground(NOTIFICATION_ID, notification);
}
synchronized private void pause(boolean maintainFocus) {
Log.d(LOG_TAG, "pause");
if (isPrepared) {
if (currentPlayable != null && currentPlayable.isStream()) {
isPrepared = false;
if (proxy != null) {
proxy.stop();
proxy = null;
}
mediaPlayer.stop();
} else {
mediaPlayer.pause();
}
}
if (!maintainFocus) {
audioManagerProxy.releaseAudioFocus();
}
stopForeground( true );
if (currentPlayable != null) {
Tracker.PauseEvent e = new Tracker.PauseEvent(currentPlayable.getUrl());
Tracker.instance(getApplication()).trackLink(e);
}
}
synchronized private void stop() {
Log.d(LOG_TAG, "stop");
audioManagerProxy.releaseAudioFocus();
if (isPrepared) {
isPrepared = false;
if (proxy != null) {
proxy.stop();
proxy = null;
}
mediaPlayer.stop();
}
}
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(LOG_TAG, "Prepared");
synchronized (this) {
if (mediaPlayer != null) {
isPrepared = true;
}
}
if (seekToPosition > 0) {
Log.d(LOG_TAG, "Seeking to starting position: " + seekToPosition);
mp.seekTo(seekToPosition);
} else {
startPlaying();
}
}
@Override
public void onSeekComplete(MediaPlayer mp) {
Log.d(LOG_TAG, "Seek complete");
if (seekToPosition > 0) {
seekToPosition = 0;
startPlaying();
}
}
private void startPlaying() {
play();
updateProgressThread = new Thread(new Runnable() {
public void run() {
while (true) {
updateProgress();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
break;
}
}
}
});
updateProgressThread.start();
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(LOG_TAG, "Service exiting");
stop();
if (updateProgressThread != null) {
updateProgressThread.interrupt();
try {
updateProgressThread.join(1000);
} catch (InterruptedException e) {
Log.e(LOG_TAG, "", e);
}
}
synchronized (this) {
if (mediaPlayer != null) {
if (mediaPlayerHasStarted) {
mediaPlayer.release();
} else {
mediaPlayer.setOnBufferingUpdateListener(null);
mediaPlayer.setOnCompletionListener(null);
mediaPlayer.setOnErrorListener(null);
mediaPlayer.setOnInfoListener(null);
mediaPlayer.setOnPreparedListener(null);
mediaPlayer.setOnSeekCompleteListener(null);
}
mediaPlayer = null;
}
}
serviceLooper.quit();
stopForeground( true );
if (lastChangeBroadcast != null) {
getApplicationContext().removeStickyBroadcast(lastChangeBroadcast);
}
getApplicationContext().sendBroadcast(new Intent(SERVICE_CLOSE_NAME));
telephonyManager.listen(listener, PhoneStateListener.LISTEN_NONE);
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int progress) {
if (isPrepared) {
lastBufferPercent = progress;
updateProgress();
}
}
/**
* Sends an UPDATE broadcast with the latest info.
*/
private void updateProgress() {
// Stop updating after mediaplayer is released
if (mediaPlayer == null)
return;
if (isPrepared) {
if (lastUpdateBroadcast != null) {
getApplicationContext().removeStickyBroadcast(lastUpdateBroadcast);
lastUpdateBroadcast = null;
}
int duration = mediaPlayer.getDuration();
seekToPosition = mediaPlayer.getCurrentPosition();
if (!markedRead) {
if (seekToPosition > duration / 10) {
markedRead = true;
if (playlist != null && currentPlayable != null) {
playlist.markAsRead(currentPlayable.getId());
}
}
}
Intent tempUpdateBroadcast = new Intent(SERVICE_UPDATE_NAME);
tempUpdateBroadcast.putExtra(EXTRA_DURATION, duration);
tempUpdateBroadcast.putExtra(EXTRA_DOWNLOADED,
(int) ((lastBufferPercent / 100.0) * duration));
tempUpdateBroadcast.putExtra(EXTRA_POSITION, seekToPosition);
tempUpdateBroadcast.putExtra(EXTRA_IS_PLAYING, mediaPlayer.isPlaying());
tempUpdateBroadcast.putExtra(EXTRA_IS_PREPARED, isPrepared);
// Update broadcasts while playing are not sticky, due to concurrency
// issues. These fire very often, so this shouldn't be a problem.
getApplicationContext().sendBroadcast(tempUpdateBroadcast);
} else {
if (lastUpdateBroadcast == null) {
lastUpdateBroadcast = new Intent(SERVICE_UPDATE_NAME);
lastUpdateBroadcast.putExtra(EXTRA_IS_PLAYING, false);
getApplicationContext().sendStickyBroadcast(lastUpdateBroadcast);
}
}
}
@Override
public void onCompletion(MediaPlayer mp) {
Log.w(LOG_TAG, "onComplete()");
synchronized (this) {
if (!isPrepared) {
// This file was not good and MediaPlayer quit
Log.w(LOG_TAG,
"MediaPlayer refused to play current item. Bailing on prepare.");
}
}
seekToPosition = 0;
if (currentPlayable != null) {
Tracker.StopEvent e = new Tracker.StopEvent(currentPlayable.getUrl());
Tracker.instance(getApplication()).trackLink(e);
}
// Unfinished playlist
if (playlistUrls != null && playlistUrls.size() > 0) {
boolean successfulPlay = false;
while (!successfulPlay && playlistUrls.size() > 0) {
String url = playlistUrls.remove(0);
errorCount = 0;
while (errorCount < ERROR_RETRY_COUNT) {
try {
prepareThenPlay(url, currentPlayable.isStream());
successfulPlay = true;
break;
} catch (UnknownHostException e) {
Log.w(LOG_TAG, "Unknown host in onCompletion");
handleConnectionError();
} catch (ConnectException e) {
Log.w(LOG_TAG, "Connect exception in onCompletion");
handleConnectionError();
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "", e);
incrementErrorCount();
} catch (IllegalStateException e) {
Log.e(LOG_TAG, "", e);
incrementErrorCount();
} catch (IOException e) {
Log.e(LOG_TAG, "", e);
incrementErrorCount();
}
}
}
}
if (currentAction.equals(SERVICE_PLAY_ENTRY)) {
finishEntryAndPlayNext();
} else {
stopSelfResult(startId);
}
}
private void incrementErrorCount() {
errorCount++;
Log.e(LOG_TAG, "Media player increment error count:" + errorCount);
if (errorCount >= ERROR_RETRY_COUNT) {
Intent intent = new Intent(SERVICE_ERROR_NAME);
intent.putExtra(EXTRA_ERROR, PLAYBACK_SERVICE_ERROR.Playback.ordinal());
getApplicationContext().sendBroadcast(intent);
}
}
private void handleConnectionError() {
connectionErrorWaitTime *= 5;
if (connectionErrorWaitTime > RETRY_SLEEP_TIME) {
Log.e(LOG_TAG, "Connection failed. Resetting mediaPlayer" +
" and trying again in 30 seconds.");
Intent intent = new Intent(SERVICE_ERROR_NAME);
intent.putExtra(EXTRA_ERROR, PLAYBACK_SERVICE_ERROR.Connection.ordinal());
getApplicationContext().sendBroadcast(intent);
// If a stream, increment since it could be bad
if (currentPlayable.isStream()) {
errorCount++;
}
connectionErrorWaitTime = RETRY_SLEEP_TIME;
// Send error notification and keep waiting
isPrepared = false;
mediaPlayer.reset();
} else {
Log.w(LOG_TAG, "Connection error. Waiting for " +
connectionErrorWaitTime + " milliseconds.");
}
SystemClock.sleep(connectionErrorWaitTime);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.w(LOG_TAG, "onError(" + what + ", " + extra + ")");
synchronized (this) {
if (!isPrepared) {
// This file was not good and MediaPlayer quit
Log.w(LOG_TAG,
"MediaPlayer refused to play current item. Bailing on prepare.");
}
}
isPrepared = false;
mediaPlayer.reset();
incrementErrorCount();
if (errorCount < ERROR_RETRY_COUNT) {
playCurrent(errorCount, 1);
// Returning true means we handled the error, false causes the
// onCompletion handler to be called
return true;
} else {
return false;
}
}
@Override
public boolean onInfo(MediaPlayer arg0, int arg1, int arg2) {
Log.w(LOG_TAG, "onInfo(" + arg1 + ", " + arg2 + ")");
return false;
}
private boolean isPlaylist(String url) {
return url.contains("m3u") || url.contains("pls");
}
private class downloadPlaylist extends AsyncTask<String, Void, Boolean> {
protected Boolean doInBackground(String... urls) {
Log.d(LOG_TAG, "downloading " + urls[0]);
try {
URLConnection cn = new URL(urls[0]).openConnection();
cn.connect();
InputStream stream = cn.getInputStream();
if (stream == null) {
Log.e(LOG_TAG, "Unable to create InputStream for url: + url");
return false;
}
File downloadingMediaFile = new File(getCacheDir(), "playlist_data");
FileOutputStream out = new FileOutputStream(downloadingMediaFile);
byte buf[] = new byte[16384];
int bytesRead;
while ((bytesRead = stream.read(buf)) > 0) {
out.write(buf, 0, bytesRead);
}
stream.close();
out.close();
PlaylistParser parser;
if (urls[0].contains("m3u")) {
parser = new M3uParser(downloadingMediaFile);
} else if (urls[0].contains("pls")) {
parser = new PlsParser(downloadingMediaFile);
} else {
return false;
}
playlistUrls = parser.getUrls();
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to download playlist from url" + urls[0]);
return false;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (result && playlistUrls != null && playlistUrls.size() > 0) {
try {
prepareThenPlay(playlistUrls.remove(0), currentPlayable.isStream());
} catch (IOException e) {
Log.e(LOG_TAG, "IOException on playlist entry " + currentPlayable.getId(), e);
incrementErrorCount();
playCurrent(errorCount, connectionErrorWaitTime);
}
} else {
incrementErrorCount();
playCurrent(errorCount, connectionErrorWaitTime);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment