ultrasonic-app-subsonic-and.../ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java

2298 lines
54 KiB
Java

/*
This file is part of Subsonic.
Subsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Subsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2009 (C) Sindre Mehus
*/
package org.moire.ultrasonic.service;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.SeekBar;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.DownloadActivity;
import org.moire.ultrasonic.activity.SubsonicTabActivity;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.audiofx.VisualizerController;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.domain.RepeatMode;
import org.moire.ultrasonic.domain.UserInfo;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x1;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x2;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x3;
import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x4;
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
import org.moire.ultrasonic.util.CancellableTask;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.FileUtil;
import org.moire.ultrasonic.util.LRUCache;
import org.moire.ultrasonic.util.ShufflePlayBuffer;
import org.moire.ultrasonic.util.SimpleServiceBinder;
import org.moire.ultrasonic.util.StreamProxy;
import org.moire.ultrasonic.util.Util;
import java.io.File;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.moire.ultrasonic.domain.PlayerState.COMPLETED;
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
import static org.moire.ultrasonic.domain.PlayerState.IDLE;
import static org.moire.ultrasonic.domain.PlayerState.PAUSED;
import static org.moire.ultrasonic.domain.PlayerState.PREPARED;
import static org.moire.ultrasonic.domain.PlayerState.PREPARING;
import static org.moire.ultrasonic.domain.PlayerState.STARTED;
import static org.moire.ultrasonic.domain.PlayerState.STOPPED;
/**
* @author Sindre Mehus, Joshua Bahnsen
* @version $Id$
*/
public class DownloadServiceImpl extends Service implements DownloadService
{
private static final String TAG = DownloadServiceImpl.class.getSimpleName();
public static final String CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY";
public static final String CMD_TOGGLEPAUSE = "org.moire.ultrasonic.CMD_TOGGLEPAUSE";
public static final String CMD_PAUSE = "org.moire.ultrasonic.CMD_PAUSE";
public static final String CMD_STOP = "org.moire.ultrasonic.CMD_STOP";
public static final String CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS";
public static final String CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT";
private static final int NOTIFICATION_ID = 3033;
private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
private Looper mediaPlayerLooper;
private MediaPlayer mediaPlayer;
private MediaPlayer nextMediaPlayer;
private boolean nextSetup;
private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>();
private final List<DownloadFile> backgroundDownloadList = new ArrayList<DownloadFile>();
private final Handler handler = new Handler();
private Handler mediaPlayerHandler;
private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this);
private final ShufflePlayBuffer shufflePlayBuffer = new ShufflePlayBuffer(this);
private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<MusicDirectory.Entry, DownloadFile>(100);
private final List<DownloadFile> cleanupCandidates = new ArrayList<DownloadFile>();
private final Scrobbler scrobbler = new Scrobbler();
private final JukeboxService jukeboxService = new JukeboxService(this);
private DownloadFile currentPlaying;
private DownloadFile nextPlaying;
private DownloadFile currentDownloading;
private CancellableTask bufferTask;
private CancellableTask nextPlayingTask;
private PlayerState playerState = IDLE;
private PlayerState nextPlayerState = IDLE;
private boolean shufflePlay;
private long revision;
private static DownloadService instance;
private String suggestedPlaylistName;
private PowerManager.WakeLock wakeLock;
private boolean keepScreenOn;
private int cachedPosition;
private static boolean equalizerAvailable;
private static boolean visualizerAvailable;
private EqualizerController equalizerController;
private VisualizerController visualizerController;
private boolean showVisualization;
private boolean jukeboxEnabled;
private PositionCache positionCache;
private StreamProxy proxy;
public RemoteControlClient remoteControlClient;
private AudioManager audioManager;
private int secondaryProgress = -1;
private boolean autoPlayStart;
private final static int lockScreenBitmapSize = 500;
static
{
try
{
EqualizerController.checkAvailable();
equalizerAvailable = true;
}
catch (Throwable t)
{
equalizerAvailable = false;
}
}
static
{
try
{
VisualizerController.checkAvailable();
visualizerAvailable = true;
}
catch (Throwable t)
{
visualizerAvailable = false;
}
}
@SuppressLint("NewApi")
@Override
public void onCreate()
{
super.onCreate();
new Thread(new Runnable()
{
@Override
public void run()
{
Thread.currentThread().setName("DownloadServiceImpl");
Looper.prepare();
if (mediaPlayer != null)
{
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setWakeMode(DownloadServiceImpl.this, PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
{
@Override
public boolean onError(MediaPlayer mediaPlayer, int what, int more)
{
handleError(new Exception(String.format("MediaPlayer error: %d (%d)", what, more)));
return false;
}
});
try
{
Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.getAudioSessionId());
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(i);
}
catch (Throwable e)
{
// Froyo or lower
}
mediaPlayerLooper = Looper.myLooper();
mediaPlayerHandler = new Handler(mediaPlayerLooper);
Looper.loop();
}
}).start();
audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
setUpRemoteControlClient();
if (equalizerAvailable)
{
equalizerController = new EqualizerController(this, mediaPlayer);
if (!equalizerController.isAvailable())
{
equalizerController = null;
}
else
{
equalizerController.loadSettings();
}
}
if (visualizerAvailable)
{
visualizerController = new VisualizerController(mediaPlayer);
if (!visualizerController.isAvailable())
{
visualizerController = null;
}
}
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
wakeLock.setReferenceCounted(false);
instance = this;
lifecycleSupport.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
super.onStartCommand(intent, flags, startId);
lifecycleSupport.onStart(intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy()
{
super.onDestroy();
try
{
instance = null;
lifecycleSupport.onDestroy();
mediaPlayer.release();
if (nextMediaPlayer != null)
{
nextMediaPlayer.release();
}
mediaPlayerLooper.quit();
shufflePlayBuffer.shutdown();
if (equalizerController != null)
{
equalizerController.release();
}
if (visualizerController != null)
{
visualizerController.release();
}
if (bufferTask != null)
{
bufferTask.cancel();
}
if (nextPlayingTask != null)
{
nextPlayingTask.cancel();
}
Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.getAudioSessionId());
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(i);
audioManager.unregisterRemoteControlClient(remoteControlClient);
clearRemoteControl();
wakeLock.release();
}
catch (Throwable ignored)
{
}
}
public static DownloadService getInstance()
{
return instance;
}
@Override
public IBinder onBind(Intent intent)
{
return binder;
}
@Override
public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist)
{
shufflePlay = false;
int offset = 1;
if (songs.isEmpty())
{
return;
}
if (newPlaylist)
{
downloadList.clear();
}
if (playNext)
{
if (autoplay && getCurrentPlayingIndex() >= 0)
{
offset = 0;
}
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(this, song, save);
downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
offset++;
}
revision++;
}
else
{
int size = size();
int index = getCurrentPlayingIndex();
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(this, song, save);
downloadList.add(downloadFile);
}
if (!autoplay && (size - 1) == index)
{
setNextPlaying();
}
revision++;
}
updateJukeboxPlaylist();
if (shuffle) shuffle();
if (autoplay)
{
play(0);
}
else
{
if (currentPlaying == null)
{
currentPlaying = downloadList.get(0);
currentPlaying.setPlaying(true);
}
checkDownloads();
}
lifecycleSupport.serializeDownloadQueue();
}
@Override
public synchronized void downloadBackground(List<MusicDirectory.Entry> songs, boolean save)
{
for (MusicDirectory.Entry song : songs)
{
DownloadFile downloadFile = new DownloadFile(this, song, save);
backgroundDownloadList.add(downloadFile);
}
revision++;
checkDownloads();
lifecycleSupport.serializeDownloadQueue();
}
private void updateJukeboxPlaylist()
{
if (jukeboxEnabled)
{
jukeboxService.updatePlaylist();
}
}
@Override
public void restore(List<MusicDirectory.Entry> songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist)
{
download(songs, false, false, false, false, newPlaylist);
if (currentPlayingIndex != -1)
{
while (mediaPlayer == null)
{
Util.sleepQuietly(50L);
}
play(currentPlayingIndex, autoPlayStart);
if (currentPlaying != null)
{
if (autoPlay && jukeboxEnabled)
{
jukeboxService.skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000);
}
else
{
if (currentPlaying.isCompleteFileAvailable())
{
doPlay(currentPlaying, currentPlayingPosition, autoPlay);
}
}
}
autoPlayStart = false;
}
}
@Override
public void stopJukeboxService()
{
jukeboxService.stopJukeboxService();
}
@Override
public void startJukeboxService()
{
jukeboxService.startJukeboxService();
}
@Override
public synchronized void setShufflePlayEnabled(boolean enabled)
{
shufflePlay = enabled;
if (shufflePlay)
{
clear();
checkDownloads();
}
}
@Override
public boolean isShufflePlayEnabled()
{
return shufflePlay;
}
@Override
public synchronized void shuffle()
{
Collections.shuffle(downloadList);
if (currentPlaying != null)
{
downloadList.remove(getCurrentPlayingIndex());
downloadList.add(0, currentPlaying);
}
revision++;
lifecycleSupport.serializeDownloadQueue();
updateJukeboxPlaylist();
setNextPlaying();
}
@Override
public RepeatMode getRepeatMode()
{
return Util.getRepeatMode(this);
}
@Override
public void setRepeatMode(RepeatMode repeatMode)
{
Util.setRepeatMode(this, repeatMode);
setNextPlaying();
}
@Override
public boolean getKeepScreenOn()
{
return keepScreenOn;
}
@Override
public void setKeepScreenOn(boolean keepScreenOn)
{
this.keepScreenOn = keepScreenOn;
}
@Override
public boolean getShowVisualization()
{
return showVisualization;
}
@Override
public void setShowVisualization(boolean showVisualization)
{
this.showVisualization = showVisualization;
}
@Override
public synchronized DownloadFile forSong(MusicDirectory.Entry song)
{
for (DownloadFile downloadFile : downloadList)
{
if (downloadFile.getSong().equals(song) && ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone()))
{
return downloadFile;
}
}
for (DownloadFile downloadFile : backgroundDownloadList)
{
if (downloadFile.getSong().equals(song))
{
return downloadFile;
}
}
DownloadFile downloadFile = downloadFileCache.get(song);
if (downloadFile == null)
{
downloadFile = new DownloadFile(this, song, false);
downloadFileCache.put(song, downloadFile);
}
return downloadFile;
}
@Override
public synchronized void clear()
{
clear(true);
}
@Override
public synchronized void clearBackground()
{
if (currentDownloading != null && backgroundDownloadList.contains(currentDownloading))
{
currentDownloading.cancelDownload();
currentDownloading = null;
}
backgroundDownloadList.clear();
}
@Override
public synchronized void clearIncomplete()
{
reset();
Iterator<DownloadFile> iterator = downloadList.iterator();
while (iterator.hasNext())
{
DownloadFile downloadFile = iterator.next();
if (!downloadFile.isCompleteFileAvailable())
{
iterator.remove();
}
}
lifecycleSupport.serializeDownloadQueue();
updateJukeboxPlaylist();
}
@Override
public synchronized int size()
{
return downloadList.size();
}
public synchronized void clear(boolean serialize)
{
reset();
downloadList.clear();
revision++;
if (currentDownloading != null)
{
currentDownloading.cancelDownload();
currentDownloading = null;
}
setCurrentPlaying(null);
if (serialize)
{
lifecycleSupport.serializeDownloadQueue();
}
updateJukeboxPlaylist();
setNextPlaying();
}
@Override
public synchronized void remove(int which)
{
downloadList.remove(which);
}
@Override
public synchronized void remove(DownloadFile downloadFile)
{
if (downloadFile == currentDownloading)
{
currentDownloading.cancelDownload();
currentDownloading = null;
}
if (downloadFile == currentPlaying)
{
reset();
setCurrentPlaying(null);
}
downloadList.remove(downloadFile);
backgroundDownloadList.remove(downloadFile);
revision++;
lifecycleSupport.serializeDownloadQueue();
updateJukeboxPlaylist();
if (downloadFile == nextPlaying)
{
setNextPlaying();
}
}
@Override
public synchronized void delete(List<MusicDirectory.Entry> songs)
{
for (MusicDirectory.Entry song : songs)
{
forSong(song).delete();
}
}
@Override
public synchronized void unpin(List<MusicDirectory.Entry> songs)
{
for (MusicDirectory.Entry song : songs)
{
forSong(song).unpin();
}
}
synchronized void setCurrentPlaying(int currentPlayingIndex)
{
try
{
setCurrentPlaying(downloadList.get(currentPlayingIndex));
}
catch (IndexOutOfBoundsException x)
{
// Ignored
}
}
synchronized void setCurrentPlaying(DownloadFile currentPlaying)
{
this.currentPlaying = currentPlaying;
if (currentPlaying != null)
{
Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
Util.broadcastA2dpMetaDataChange(this, instance);
}
else
{
Util.broadcastNewTrackInfo(this, null);
Util.broadcastA2dpMetaDataChange(this, null);
}
updateRemoteControl();
// Update widget
UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, true);
UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
if (currentPlaying != null)
{
if (tabInstance != null) {
if (Util.isNotificationEnabled(this)) {
startForeground(NOTIFICATION_ID, buildForegroundNotification());
}
tabInstance.showNowPlaying();
}
}
else
{
if (tabInstance != null)
{
stopForeground(true);
tabInstance.hideNowPlaying();
}
}
}
synchronized void setNextPlaying()
{
boolean gaplessPlayback = Util.getGaplessPlaybackPreference(DownloadServiceImpl.this);
if (!gaplessPlayback)
{
nextPlaying = null;
nextPlayerState = IDLE;
return;
}
int index = getCurrentPlayingIndex();
if (index != -1)
{
switch (getRepeatMode())
{
case OFF:
index += 1;
break;
case ALL:
index = (index + 1) % size();
break;
case SINGLE:
break;
default:
break;
}
}
nextSetup = false;
if (nextPlayingTask != null)
{
nextPlayingTask.cancel();
nextPlayingTask = null;
}
if (index < size() && index != -1)
{
nextPlaying = downloadList.get(index);
nextPlayingTask = new CheckCompletionTask(nextPlaying);
nextPlayingTask.start();
}
else
{
nextPlaying = null;
setNextPlayerState(IDLE);
}
}
@Override
public synchronized int getCurrentPlayingIndex()
{
return downloadList.indexOf(currentPlaying);
}
@Override
public DownloadFile getCurrentPlaying()
{
return currentPlaying;
}
@Override
public DownloadFile getCurrentDownloading()
{
return currentDownloading;
}
@Override
public List<DownloadFile> getSongs()
{
return downloadList;
}
@Override
public long getDownloadListDuration()
{
long totalDuration = 0;
for (DownloadFile downloadFile : downloadList)
{
Entry entry = downloadFile.getSong();
if (!entry.isDirectory())
{
if (entry.getArtist() != null)
{
Integer duration = entry.getDuration();
if (duration != null)
{
totalDuration += duration;
}
}
}
}
return totalDuration;
}
@Override
public synchronized List<DownloadFile> getDownloads()
{
List<DownloadFile> temp = new ArrayList<DownloadFile>();
temp.addAll(downloadList);
temp.addAll(backgroundDownloadList);
return temp;
}
@Override
public List<DownloadFile> getBackgroundDownloads()
{
return backgroundDownloadList;
}
/**
* Plays either the current song (resume) or the first/next one in queue.
*/
public synchronized void play()
{
int current = getCurrentPlayingIndex();
if (current == -1)
{
play(0);
}
else
{
play(current);
}
}
@Override
public synchronized void play(int index)
{
play(index, true);
}
private synchronized void play(int index, boolean start)
{
updateRemoteControl();
if (index < 0 || index >= size())
{
resetPlayback();
}
else
{
if (nextPlayingTask != null)
{
nextPlayingTask.cancel();
nextPlayingTask = null;
}
setCurrentPlaying(index);
if (start)
{
if (jukeboxEnabled)
{
jukeboxService.skip(getCurrentPlayingIndex(), 0);
setPlayerState(STARTED);
}
else
{
bufferAndPlay();
}
}
checkDownloads();
setNextPlaying();
}
}
private synchronized void resetPlayback()
{
reset();
setCurrentPlaying(null);
lifecycleSupport.serializeDownloadQueue();
}
private synchronized void playNext()
{
MediaPlayer tmp = mediaPlayer;
mediaPlayer = nextMediaPlayer;
nextMediaPlayer = tmp;
setCurrentPlaying(nextPlaying);
setPlayerState(PlayerState.STARTED);
setupHandlers(currentPlaying, false);
setNextPlaying();
// Proxy should not be being used here since the next player was already setup to play
if (proxy != null)
{
proxy.stop();
proxy = null;
}
}
/**
* Plays or resumes the playback, depending on the current player state.
*/
@Override
public synchronized void togglePlayPause()
{
if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED)
{
start();
}
else if (playerState == IDLE)
{
autoPlayStart = true;
play();
}
else if (playerState == STARTED)
{
pause();
}
}
@Override
public synchronized void seekTo(int position)
{
try
{
if (jukeboxEnabled)
{
jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
}
else
{
mediaPlayer.seekTo(position);
cachedPosition = position;
updateRemoteControl();
}
}
catch (Exception x)
{
handleError(x);
}
}
@Override
public synchronized void previous()
{
int index = getCurrentPlayingIndex();
if (index == -1)
{
return;
}
// Restart song if played more than five seconds.
if (getPlayerPosition() > 5000 || index == 0)
{
play(index);
}
else
{
play(index - 1);
}
}
@Override
public synchronized void next()
{
int index = getCurrentPlayingIndex();
if (index != -1)
{
play(index + 1);
}
}
private void onSongCompleted()
{
int index = getCurrentPlayingIndex();
if (currentPlaying != null)
{
final Entry song = currentPlaying.getSong();
if (song != null && song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(this))
{
MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
try
{
musicService.deleteBookmark(song.getId(), DownloadServiceImpl.this, null);
}
catch (Exception ignored)
{
}
}
}
if (index != -1)
{
switch (getRepeatMode())
{
case OFF:
if (index + 1 < 0 || index + 1 >= size())
{
if (Util.getShouldClearPlaylist(this))
{
clear();
}
resetPlayback();
break;
}
play(index + 1);
break;
case ALL:
play((index + 1) % size());
break;
case SINGLE:
play(index);
break;
default:
break;
}
}
}
@Override
public synchronized void pause()
{
try
{
if (playerState == STARTED)
{
if (jukeboxEnabled)
{
jukeboxService.stop();
}
else
{
mediaPlayer.pause();
}
setPlayerState(PAUSED);
}
}
catch (Exception x)
{
handleError(x);
}
}
@Override
public synchronized void stop()
{
try
{
if (playerState == STARTED)
{
if (jukeboxEnabled)
{
jukeboxService.stop();
}
else
{
mediaPlayer.pause();
}
setPlayerState(STOPPED);
}
else
{
setPlayerState(STOPPED);
}
}
catch (Exception x)
{
handleError(x);
}
}
@Override
public synchronized void start()
{
try
{
if (jukeboxEnabled)
{
jukeboxService.start();
}
else
{
mediaPlayer.start();
}
setPlayerState(STARTED);
}
catch (Exception x)
{
handleError(x);
}
}
@Override
public synchronized void reset()
{
if (bufferTask != null)
{
bufferTask.cancel();
}
try
{
setPlayerState(IDLE);
mediaPlayer.setOnErrorListener(null);
mediaPlayer.setOnCompletionListener(null);
mediaPlayer.reset();
}
catch (Exception x)
{
handleError(x);
}
}
@Override
public synchronized int getPlayerPosition()
{
try
{
if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING)
{
return 0;
}
return jukeboxEnabled ? jukeboxService.getPositionSeconds() * 1000 : cachedPosition;
}
catch (Exception x)
{
handleError(x);
return 0;
}
}
@Override
public synchronized int getPlayerDuration()
{
if (currentPlaying != null)
{
Integer duration = currentPlaying.getSong().getDuration();
if (duration != null)
{
return duration * 1000;
}
}
if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING)
{
try
{
return mediaPlayer.getDuration();
}
catch (Exception x)
{
handleError(x);
}
}
return 0;
}
@Override
public PlayerState getPlayerState()
{
return playerState;
}
synchronized void setPlayerState(PlayerState playerState)
{
Log.i(TAG, String.format("%s -> %s (%s)", this.playerState.name(), playerState.name(), currentPlaying));
this.playerState = playerState;
if (this.playerState == PAUSED)
{
lifecycleSupport.serializeDownloadQueue();
}
if (this.playerState == PlayerState.STARTED)
{
Util.requestAudioFocus(this);
}
boolean showWhenPaused = (this.playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(this));
boolean show = this.playerState == PlayerState.STARTED || showWhenPaused;
Util.broadcastPlaybackStatusChange(this, this.playerState);
Util.broadcastA2dpPlayStatusChange(this, this.playerState, instance);
if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED)
{
// Set remote control
updateRemoteControl();
}
// Update widget
UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, true);
UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
if (show)
{
if (tabInstance != null)
{
// Only update notification is player state is one that will change the icon
if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED)
{
if (Util.isNotificationEnabled(this)) {
final NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
}
tabInstance.showNowPlaying();
}
}
}
else
{
if (tabInstance != null)
{
stopForeground(true);
tabInstance.hideNowPlaying();
}
}
if (this.playerState == STARTED)
{
scrobbler.scrobble(this, currentPlaying, false);
}
else if (this.playerState == COMPLETED)
{
scrobbler.scrobble(this, currentPlaying, true);
}
if (playerState == STARTED && positionCache == null)
{
positionCache = new PositionCache();
Thread thread = new Thread(positionCache);
thread.start();
}
else if (playerState != STARTED && positionCache != null)
{
positionCache.stop();
positionCache = null;
}
}
private void setPlayerStateCompleted()
{
Log.i(TAG, String.format("%s -> %s (%s)", this.playerState.name(), PlayerState.COMPLETED, currentPlaying));
this.playerState = PlayerState.COMPLETED;
if (positionCache != null)
{
positionCache.stop();
positionCache = null;
}
scrobbler.scrobble(this, currentPlaying, true);
}
private synchronized void setNextPlayerState(PlayerState playerState)
{
Log.i(TAG, String.format("Next: %s -> %s (%s)", this.nextPlayerState.name(), playerState.name(), nextPlaying));
this.nextPlayerState = playerState;
}
@Override
public void setSuggestedPlaylistName(String name)
{
this.suggestedPlaylistName = name;
}
@Override
public String getSuggestedPlaylistName()
{
return suggestedPlaylistName;
}
@Override
public boolean getEqualizerAvailable()
{
return equalizerAvailable;
}
@Override
public boolean getVisualizerAvailable()
{
return visualizerAvailable;
}
@Override
public EqualizerController getEqualizerController()
{
if (equalizerAvailable && equalizerController == null)
{
equalizerController = new EqualizerController(this, mediaPlayer);
if (!equalizerController.isAvailable())
{
equalizerController = null;
}
else
{
equalizerController.loadSettings();
}
}
return equalizerController;
}
@Override
public VisualizerController getVisualizerController()
{
if (visualizerAvailable && visualizerController == null)
{
visualizerController = new VisualizerController(mediaPlayer);
if (!visualizerController.isAvailable())
{
visualizerController = null;
}
}
return visualizerController;
}
@Override
public boolean isJukeboxEnabled()
{
return jukeboxEnabled;
}
@Override
public boolean isJukeboxAvailable()
{
MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
try
{
String username = Util.getUserName(DownloadServiceImpl.this, Util.getActiveServer(DownloadServiceImpl.this));
UserInfo user = musicService.getUser(username, DownloadServiceImpl.this, null);
return user.getJukeboxRole();
}
catch (Exception e)
{
Log.w(TAG, "Error getting user information", e);
}
return false;
}
@Override
public boolean isSharingAvailable()
{
MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
try
{
String username = Util.getUserName(DownloadServiceImpl.this, Util.getActiveServer(DownloadServiceImpl.this));
UserInfo user = musicService.getUser(username, DownloadServiceImpl.this, null);
return user.getShareRole();
}
catch (Exception e)
{
Log.w(TAG, "Error getting user information", e);
}
return false;
}
@Override
public void setJukeboxEnabled(boolean jukeboxEnabled)
{
this.jukeboxEnabled = jukeboxEnabled;
jukeboxService.setEnabled(jukeboxEnabled);
if (jukeboxEnabled)
{
jukeboxService.startJukeboxService();
reset();
// Cancel current download, if necessary.
if (currentDownloading != null)
{
currentDownloading.cancelDownload();
}
}
else
{
jukeboxService.stopJukeboxService();
}
}
@Override
public void adjustJukeboxVolume(boolean up)
{
jukeboxService.adjustVolume(up);
}
@SuppressLint("NewApi")
public void setUpRemoteControlClient()
{
if (!Util.isLockScreenEnabled(this)) return;
ComponentName componentName = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
if (remoteControlClient == null)
{
final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(componentName);
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteControlClient = new RemoteControlClient(broadcast);
audioManager.registerRemoteControlClient(remoteControlClient);
// Flags for the media transport control that this client supports.
int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_STOP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
{
flags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
remoteControlClient.setOnGetPlaybackPositionListener(new RemoteControlClient.OnGetPlaybackPositionListener()
{
@Override
public long onGetPlaybackPosition()
{
return mediaPlayer.getCurrentPosition();
}
});
remoteControlClient.setPlaybackPositionUpdateListener(new RemoteControlClient.OnPlaybackPositionUpdateListener()
{
@Override
public void onPlaybackPositionUpdate(long newPositionMs)
{
seekTo((int) newPositionMs);
}
});
}
remoteControlClient.setTransportControlFlags(flags);
}
}
private void clearRemoteControl()
{
if (remoteControlClient != null)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
audioManager.unregisterRemoteControlClient(remoteControlClient);
remoteControlClient = null;
}
}
private void updateRemoteControl()
{
if (!Util.isLockScreenEnabled(this))
{
clearRemoteControl();
return;
}
if (remoteControlClient != null)
{
audioManager.unregisterRemoteControlClient(remoteControlClient);
audioManager.registerRemoteControlClient(remoteControlClient);
}
else
{
setUpRemoteControlClient();
}
Log.i(TAG, String.format("In updateRemoteControl, playerState: %s [%d]", playerState, getPlayerPosition()));
switch (playerState)
{
case STARTED:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
}
else
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, getPlayerPosition(), 1.0f);
}
break;
default:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
else
{
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED, getPlayerPosition(), 1.0f);
}
break;
}
if (currentPlaying != null)
{
MusicDirectory.Entry currentSong = currentPlaying.getSong();
Bitmap lockScreenBitmap = FileUtil.getAlbumArtBitmap(this, currentSong, Util.getMinDisplayMetric(this), true);
String artist = currentSong.getArtist();
String album = currentSong.getAlbum();
String title = currentSong.getTitle();
Integer currentSongDuration = currentSong.getDuration();
Long duration = 0L;
if (currentSongDuration != null) duration = (long) currentSongDuration * 1000;
remoteControlClient.editMetadata(true).putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album).putString(MediaMetadataRetriever.METADATA_KEY_TITLE, title).putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration)
.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, lockScreenBitmap).apply();
}
}
private synchronized void bufferAndPlay()
{
if (playerState != PREPARED)
{
reset();
bufferTask = new BufferTask(currentPlaying, 0);
bufferTask.start();
}
else
{
doPlay(currentPlaying, 0, true);
}
}
private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start)
{
try
{
downloadFile.setPlaying(false);
//downloadFile.setPlaying(true);
final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
boolean partial = file.equals(downloadFile.getPartialFile());
downloadFile.updateModificationDate();
mediaPlayer.setOnCompletionListener(null);
secondaryProgress = -1; // Ensure seeking in non StreamProxy playback works
mediaPlayer.reset();
setPlayerState(IDLE);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
String dataSource = file.getPath();
if (partial)
{
if (proxy == null)
{
proxy = new StreamProxy(this);
proxy.start();
}
dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), URLEncoder.encode(dataSource, Constants.UTF_8));
Log.i(TAG, String.format("Data Source: %s", dataSource));
}
else if (proxy != null)
{
proxy.stop();
proxy = null;
}
Log.i(TAG, "Preparing media player");
mediaPlayer.setDataSource(dataSource);
setPlayerState(PREPARING);
mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener()
{
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent)
{
SeekBar progressBar = DownloadActivity.getProgressBar();
MusicDirectory.Entry song = downloadFile.getSong();
if (percent == 100)
{
if (progressBar != null)
{
progressBar.setSecondaryProgress(100 * progressBar.getMax());
}
mp.setOnBufferingUpdateListener(null);
}
else if (progressBar != null && song.getTranscodedContentType() == null && Util.getMaxBitRate(DownloadServiceImpl.this) == 0)
{
secondaryProgress = (int) (((double) percent / (double) 100) * progressBar.getMax());
progressBar.setSecondaryProgress(secondaryProgress);
}
}
});
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
@Override
public void onPrepared(MediaPlayer mp)
{
Log.i(TAG, "Media player prepared");
setPlayerState(PREPARED);
SeekBar progressBar = DownloadActivity.getProgressBar();
if (progressBar != null && downloadFile.isWorkDone())
{
// Populate seek bar secondary progress if we have a complete file for consistency
DownloadActivity.getProgressBar().setSecondaryProgress(100 * progressBar.getMax());
}
synchronized (DownloadServiceImpl.this)
{
if (position != 0)
{
Log.i(TAG, String.format("Restarting player from position %d", position));
seekTo(position);
}
cachedPosition = position;
if (start)
{
mediaPlayer.start();
setPlayerState(STARTED);
}
else
{
setPlayerState(PAUSED);
}
}
lifecycleSupport.serializeDownloadQueue();
}
});
setupHandlers(downloadFile, partial);
mediaPlayer.prepareAsync();
}
catch (Exception x)
{
handleError(x);
}
}
private synchronized void setupNext(final DownloadFile downloadFile)
{
try
{
final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
if (nextMediaPlayer != null)
{
nextMediaPlayer.setOnCompletionListener(null);
nextMediaPlayer.release();
nextMediaPlayer = null;
}
nextMediaPlayer = new MediaPlayer();
nextMediaPlayer.setWakeMode(DownloadServiceImpl.this, PowerManager.PARTIAL_WAKE_LOCK);
try
{
nextMediaPlayer.setAudioSessionId(mediaPlayer.getAudioSessionId());
}
catch (Throwable e)
{
nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
nextMediaPlayer.setDataSource(file.getPath());
setNextPlayerState(PREPARING);
nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
{
@Override
@SuppressLint("NewApi")
public void onPrepared(MediaPlayer mp)
{
try
{
setNextPlayerState(PREPARED);
if (Util.getGaplessPlaybackPreference(DownloadServiceImpl.this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED))
{
mediaPlayer.setNextMediaPlayer(nextMediaPlayer);
nextSetup = true;
}
}
catch (Exception x)
{
handleErrorNext(x);
}
}
});
nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
{
@Override
public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
{
Log.w(TAG, String.format("Error on playing next (%d, %d): %s", what, extra, downloadFile));
return true;
}
});
nextMediaPlayer.prepareAsync();
}
catch (Exception x)
{
handleErrorNext(x);
}
}
private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial)
{
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
{
@Override
public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
{
Log.w(TAG, String.format("Error on playing file (%d, %d): %s", what, extra, downloadFile));
int pos = cachedPosition;
reset();
downloadFile.setPlaying(false);
doPlay(downloadFile, pos, true);
downloadFile.setPlaying(true);
return true;
}
});
final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer mediaPlayer)
{
// Acquire a temporary wakelock, since when we return from
// this callback the MediaPlayer will release its wakelock
// and allow the device to go to sleep.
wakeLock.acquire(60000);
int pos = cachedPosition;
Log.i(TAG, String.format("Ending position %d of %d", pos, duration));
if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 1000)))
{
setPlayerStateCompleted();
if (Util.getGaplessPlaybackPreference(DownloadServiceImpl.this) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED)
{
if (!nextSetup)
{
playNext();
}
else
{
nextSetup = false;
playNext();
}
}
else
{
onSongCompleted();
}
return;
}
synchronized (DownloadServiceImpl.this)
{
if (downloadFile.isWorkDone())
{
// Complete was called early even though file is fully buffered
Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
reset();
downloadFile.setPlaying(false);
doPlay(downloadFile, pos, true);
downloadFile.setPlaying(true);
}
else
{
Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
reset();
bufferTask = new BufferTask(downloadFile, pos);
bufferTask.start();
}
}
}
});
}
@Override
public void setVolume(float volume)
{
if (mediaPlayer != null)
{
mediaPlayer.setVolume(volume, volume);
}
}
@Override
public synchronized void swap(boolean mainList, int from, int to)
{
List<DownloadFile> list = mainList ? downloadList : backgroundDownloadList;
int max = list.size();
if (to >= max)
{
to = max - 1;
}
else if (to < 0)
{
to = 0;
}
int currentPlayingIndex = getCurrentPlayingIndex();
DownloadFile movedSong = list.remove(from);
list.add(to, movedSong);
if (jukeboxEnabled && mainList)
{
updateJukeboxPlaylist();
}
else if (mainList && (movedSong == nextPlaying || (currentPlayingIndex + 1) == to))
{
// Moving next playing or moving a song to be next playing
setNextPlaying();
}
}
private void handleError(Exception x)
{
Log.w(TAG, String.format("Media player error: %s", x), x);
try
{
mediaPlayer.reset();
}
catch (Exception ex)
{
Log.w(TAG, String.format("Exception encountered when resetting media player: %s", ex), ex);
}
setPlayerState(IDLE);
}
private void handleErrorNext(Exception x)
{
Log.w(TAG, String.format("Next Media player error: %s", x), x);
nextMediaPlayer.reset();
setNextPlayerState(IDLE);
}
protected synchronized void checkDownloads()
{
if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable())
{
return;
}
if (shufflePlay)
{
checkShufflePlay();
}
if (jukeboxEnabled || !Util.isNetworkConnected(this))
{
return;
}
if (downloadList.isEmpty() && backgroundDownloadList.isEmpty())
{
return;
}
// Need to download current playing?
if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone())
{
// Cancel current download, if necessary.
if (currentDownloading != null)
{
currentDownloading.cancelDownload();
}
currentDownloading = currentPlaying;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
}
// Find a suitable target for download.
else
{
if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty()))
{
currentDownloading = null;
int n = size();
int preloaded = 0;
if (n != 0)
{
int start = currentPlaying == null ? 0 : getCurrentPlayingIndex();
if (start == -1)
{
start = 0;
}
int i = start;
do
{
DownloadFile downloadFile = downloadList.get(i);
if (!downloadFile.isWorkDone())
{
if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this))
{
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
if (i == (start + 1))
{
setNextPlayerState(DOWNLOADING);
}
break;
}
}
else if (currentPlaying != downloadFile)
{
preloaded++;
}
i = (i + 1) % n;
} while (i != start);
}
if ((preloaded + 1 == n || preloaded >= Util.getPreloadCount(this) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty())
{
for (int i = 0; i < backgroundDownloadList.size(); i++)
{
DownloadFile downloadFile = backgroundDownloadList.get(i);
if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()))
{
if (Util.getShouldScanMedia(this))
{
Util.scanMedia(this, downloadFile.getCompleteFile());
}
// Don't need to keep list like active song list
backgroundDownloadList.remove(i);
revision++;
i--;
}
else
{
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
break;
}
}
}
}
}
// Delete obsolete .partial and .complete files.
cleanup();
}
private synchronized void checkShufflePlay()
{
// Get users desired random playlist size
int listSize = Util.getMaxSongs(this);
boolean wasEmpty = downloadList.isEmpty();
long revisionBefore = revision;
// First, ensure that list is at least 20 songs long.
int size = size();
if (size < listSize)
{
for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size))
{
DownloadFile downloadFile = new DownloadFile(this, song, false);
downloadList.add(downloadFile);
revision++;
}
}
int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex();
// Only shift playlist if playing song #5 or later.
if (currIndex > 4)
{
int songsToShift = currIndex - 2;
for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift))
{
downloadList.add(new DownloadFile(this, song, false));
downloadList.get(0).cancelDownload();
downloadList.remove(0);
revision++;
}
}
if (revisionBefore != revision)
{
updateJukeboxPlaylist();
}
if (wasEmpty && !downloadList.isEmpty())
{
play(0);
}
}
@Override
public long getDownloadListUpdateRevision()
{
return revision;
}
private synchronized void cleanup()
{
Iterator<DownloadFile> iterator = cleanupCandidates.iterator();
while (iterator.hasNext())
{
DownloadFile downloadFile = iterator.next();
if (downloadFile != currentPlaying && downloadFile != currentDownloading)
{
if (downloadFile.cleanup())
{
iterator.remove();
}
}
}
}
@SuppressWarnings("IconColors")
private Notification buildForegroundNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
builder.setAutoCancel(false);
builder.setOngoing(true);
builder.setWhen(System.currentTimeMillis());
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
RemoteViews contentView = new RemoteViews(this.getPackageName(), R.layout.notification);
Util.linkButtons(this, contentView, false);
RemoteViews bigView = new RemoteViews(this.getPackageName(), R.layout.notification_large);
Util.linkButtons(this, bigView, false);
builder.setContent(contentView);
Intent notificationIntent = new Intent(this, DownloadActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0));
if (playerState == PlayerState.PAUSED || playerState == PlayerState.IDLE) {
contentView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
bigView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
} else if (playerState == PlayerState.STARTED) {
contentView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
bigView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
}
final Entry song = currentPlaying.getSong();
final String title = song.getTitle();
final String text = song.getArtist();
final String album = song.getAlbum();
final int imageSize = Util.getNotificationImageSize(this);
try {
final Bitmap nowPlayingImage = FileUtil.getAlbumArtBitmap(this, currentPlaying.getSong(), imageSize, true);
if (nowPlayingImage == null) {
contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
} else {
contentView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
bigView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
}
} catch (Exception x) {
Log.w(TAG, "Failed to get notification cover art", x);
contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
}
contentView.setTextViewText(R.id.trackname, title);
bigView.setTextViewText(R.id.trackname, title);
contentView.setTextViewText(R.id.artist, text);
bigView.setTextViewText(R.id.artist, text);
contentView.setTextViewText(R.id.album, album);
bigView.setTextViewText(R.id.album, album);
Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification.bigContentView = bigView;
}
return notification;
}
private class BufferTask extends CancellableTask
{
private final DownloadFile downloadFile;
private final int position;
private final long expectedFileSize;
private final File partialFile;
public BufferTask(DownloadFile downloadFile, int position)
{
this.downloadFile = downloadFile;
this.position = position;
partialFile = downloadFile.getPartialFile();
long bufferLength = Util.getBufferLength(DownloadServiceImpl.this);
if (bufferLength == 0)
{
// Set to seconds in a day, basically infinity
bufferLength = 86400L;
}
// Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to.
int bitRate = downloadFile.getBitRate();
long byteCount = Math.max(100000, bitRate * 1024L / 8L * bufferLength);
// Find out how large the file should grow before resuming playback.
Log.i(TAG, String.format("Buffering from position %d and bitrate %d", position, bitRate));
expectedFileSize = (position * bitRate / 8) + byteCount;
}
@Override
public void execute()
{
setPlayerState(DOWNLOADING);
while (!bufferComplete() && !Util.isOffline(DownloadServiceImpl.this))
{
Util.sleepQuietly(1000L);
if (isCancelled())
{
return;
}
}
doPlay(downloadFile, position, true);
}
private boolean bufferComplete()
{
boolean completeFileAvailable = downloadFile.isWorkDone();
long size = partialFile.length();
Log.i(TAG, String.format("Buffering %s (%d/%d, %s)", partialFile, size, expectedFileSize, completeFileAvailable));
return completeFileAvailable || size >= expectedFileSize;
}
@Override
public String toString()
{
return String.format("BufferTask (%s)", downloadFile);
}
}
private class PositionCache implements Runnable
{
boolean isRunning = true;
public void stop()
{
isRunning = false;
}
@Override
public void run()
{
Thread.currentThread().setName("PositionCache");
// Stop checking position before the song reaches completion
while (isRunning)
{
try
{
if (mediaPlayer != null && playerState == STARTED)
{
cachedPosition = mediaPlayer.getCurrentPosition();
}
Util.sleepQuietly(25L);
}
catch (Exception e)
{
Log.w(TAG, "Crashed getting current position", e);
isRunning = false;
positionCache = null;
}
}
}
}
private class CheckCompletionTask extends CancellableTask
{
private final DownloadFile downloadFile;
private final File partialFile;
public CheckCompletionTask(DownloadFile downloadFile)
{
super();
setNextPlayerState(PlayerState.IDLE);
this.downloadFile = downloadFile;
partialFile = downloadFile != null ? downloadFile.getPartialFile() : null;
}
@Override
public void execute()
{
Thread.currentThread().setName("CheckCompletionTask");
if (downloadFile == null)
{
return;
}
// Do an initial sleep so this prepare can't compete with main prepare
Util.sleepQuietly(5000L);
while (!bufferComplete())
{
Util.sleepQuietly(5000L);
if (isCancelled())
{
return;
}
}
// Start the setup of the next media player
mediaPlayerHandler.post(new Runnable()
{
@Override
public void run()
{
setupNext(downloadFile);
}
});
}
private boolean bufferComplete()
{
boolean completeFileAvailable = downloadFile.isWorkDone();
Log.i(TAG, String.format("Buffering next %s (%d)", partialFile, partialFile.length()));
return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED);
}
@Override
public String toString()
{
return String.format("CheckCompletionTask (%s)", downloadFile);
}
}
}