Clover Coverage Report - Subsonic-Android Coverage Report
Coverage timestamp: ven dic 19 2014 17:57:13 EST
../../../../../img/srcFileCovDistChart8.png 45% of files have more coverage
381   930   185   5,6
170   756   0,49   34
68     2,72  
2    
This report was generated with an evaluation server license. Purchase Clover or configure your license.
 
  DownloadServiceImpl       Line # 53 364 179 76,1% 0.76094276
  DownloadServiceImpl.BufferTask       Line # 882 17 6 100% 1.0
 
No Tests
 
1    /*
2    This file is part of Subsonic.
3   
4    Subsonic is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8   
9    Subsonic is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12    GNU General Public License for more details.
13   
14    You should have received a copy of the GNU General Public License
15    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
16   
17    Copyright 2009 (C) Sindre Mehus
18    */
19    package net.sourceforge.subsonic.androidapp.service;
20   
21    import android.app.Service;
22    import android.content.Context;
23    import android.content.Intent;
24    import android.media.AudioManager;
25    import android.media.MediaPlayer;
26    import android.os.Handler;
27    import android.os.IBinder;
28    import android.os.PowerManager;
29    import android.util.Log;
30    import net.sourceforge.subsonic.androidapp.audiofx.EqualizerController;
31    import net.sourceforge.subsonic.androidapp.audiofx.VisualizerController;
32    import net.sourceforge.subsonic.androidapp.domain.MusicDirectory;
33    import net.sourceforge.subsonic.androidapp.domain.PlayerState;
34    import net.sourceforge.subsonic.androidapp.domain.RepeatMode;
35    import net.sourceforge.subsonic.androidapp.util.CancellableTask;
36    import net.sourceforge.subsonic.androidapp.util.LRUCache;
37    import net.sourceforge.subsonic.androidapp.util.ShufflePlayBuffer;
38    import net.sourceforge.subsonic.androidapp.util.SimpleServiceBinder;
39    import net.sourceforge.subsonic.androidapp.util.Util;
40   
41    import java.io.File;
42    import java.util.ArrayList;
43    import java.util.Collections;
44    import java.util.Iterator;
45    import java.util.List;
46   
47    import static net.sourceforge.subsonic.androidapp.domain.PlayerState.*;
48   
49    /**
50    * @author Sindre Mehus
51    * @version $Id$
52    */
 
53    public class DownloadServiceImpl extends Service implements DownloadService {
54   
55    private static final String TAG = DownloadServiceImpl.class.getSimpleName();
56   
57    public static final String CMD_PLAY = "net.sourceforge.subsonic.androidapp.CMD_PLAY";
58    public static final String CMD_TOGGLEPAUSE = "net.sourceforge.subsonic.androidapp.CMD_TOGGLEPAUSE";
59    public static final String CMD_PAUSE = "net.sourceforge.subsonic.androidapp.CMD_PAUSE";
60    public static final String CMD_STOP = "net.sourceforge.subsonic.androidapp.CMD_STOP";
61    public static final String CMD_PREVIOUS = "net.sourceforge.subsonic.androidapp.CMD_PREVIOUS";
62    public static final String CMD_NEXT = "net.sourceforge.subsonic.androidapp.CMD_NEXT";
63   
64    private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
65    private MediaPlayer mediaPlayer;
66    private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>();
67    private final Handler handler = new Handler();
68    private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this);
69    private final ShufflePlayBuffer shufflePlayBuffer = new ShufflePlayBuffer(this);
70   
71    private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<MusicDirectory.Entry, DownloadFile>(100);
72    private final List<DownloadFile> cleanupCandidates = new ArrayList<DownloadFile>();
73    private final Scrobbler scrobbler = new Scrobbler();
74    private final JukeboxService jukeboxService = new JukeboxService(this);
75    private DownloadFile currentPlaying;
76    private DownloadFile currentDownloading;
77    private CancellableTask bufferTask;
78    private PlayerState playerState = IDLE;
79    private boolean shufflePlay;
80    private long revision;
81    private static DownloadService instance;
82    private String suggestedPlaylistName;
83    private PowerManager.WakeLock wakeLock;
84    private boolean keepScreenOn = false;
85   
86    private static boolean equalizerAvailable;
87    private static boolean visualizerAvailable;
88    private EqualizerController equalizerController;
89    private VisualizerController visualizerController;
90    private boolean showVisualization;
91    private boolean jukeboxEnabled;
92   
 
93  1 toggle static {
94  1 try {
95  1 EqualizerController.checkAvailable();
96  1 equalizerAvailable = true;
97    } catch (Throwable t) {
98  0 equalizerAvailable = false;
99    }
100    }
 
101  1 toggle static {
102  1 try {
103  1 VisualizerController.checkAvailable();
104  1 visualizerAvailable = true;
105    } catch (Throwable t) {
106  0 visualizerAvailable = false;
107    }
108    }
109   
 
110  1 toggle @Override
111    public void onCreate() {
112  1 super.onCreate();
113   
114  1 mediaPlayer = new MediaPlayer();
115  1 mediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
116   
117  1 mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
 
118  1 toggle @Override
119    public boolean onError(MediaPlayer mediaPlayer, int what, int more) {
120  1 handleError(new Exception("MediaPlayer error: " + what + " (" + more + ")"));
121  1 return false;
122    }
123    });
124   
125  1 if (equalizerAvailable) {
126  1 equalizerController = new EqualizerController(this, mediaPlayer);
127  1 if (!equalizerController.isAvailable()) {
128  0 equalizerController = null;
129    } else {
130  1 equalizerController.loadSettings();
131    }
132    }
133  1 if (visualizerAvailable) {
134  1 visualizerController = new VisualizerController(this, mediaPlayer);
135  1 if (!visualizerController.isAvailable()) {
136  0 visualizerController = null;
137    }
138    }
139   
140  1 PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
141  1 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
142  1 wakeLock.setReferenceCounted(false);
143   
144  1 instance = this;
145  1 lifecycleSupport.onCreate();
146    }
147   
 
148  38 toggle @Override
149    public void onStart(Intent intent, int startId) {
150  38 super.onStart(intent, startId);
151  38 lifecycleSupport.onStart(intent);
152    }
153   
 
154  0 toggle @Override
155    public void onDestroy() {
156  0 super.onDestroy();
157  0 lifecycleSupport.onDestroy();
158  0 mediaPlayer.release();
159  0 shufflePlayBuffer.shutdown();
160  0 if (equalizerController != null) {
161  0 equalizerController.release();
162    }
163  0 if (visualizerController != null) {
164  0 visualizerController.release();
165    }
166   
167  0 instance = null;
168    }
169   
 
170  3419 toggle public static DownloadService getInstance() {
171  3419 return instance;
172    }
173   
 
174  0 toggle @Override
175    public IBinder onBind(Intent intent) {
176  0 return binder;
177    }
178   
 
179  13 toggle @Override
180    public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext) {
181  13 shufflePlay = false;
182  13 int offset = 1;
183   
184  13 if (songs.isEmpty()) {
185  0 return;
186    }
187  13 if (playNext) {
188  3 if (autoplay && getCurrentPlayingIndex() >= 0) {
189  1 offset = 0;
190    }
191  3 for (MusicDirectory.Entry song : songs) {
192  3 DownloadFile downloadFile = new DownloadFile(this, song, save);
193  3 downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
194  3 offset++;
195    }
196  3 revision++;
197    } else {
198  10 for (MusicDirectory.Entry song : songs) {
199  45 DownloadFile downloadFile = new DownloadFile(this, song, save);
200  45 downloadList.add(downloadFile);
201    }
202  10 revision++;
203    }
204  13 updateJukeboxPlaylist();
205   
206  13 if (autoplay) {
207  5 play(0);
208    } else {
209  8 if (currentPlaying == null) {
210  1 currentPlaying = downloadList.get(0);
211    }
212  8 checkDownloads();
213    }
214  13 lifecycleSupport.serializeDownloadQueue();
215    }
216   
 
217  31 toggle private void updateJukeboxPlaylist() {
218  31 if (jukeboxEnabled) {
219  0 jukeboxService.updatePlaylist();
220    }
221    }
222   
 
223  0 toggle public void restore(List<MusicDirectory.Entry> songs, int currentPlayingIndex, int currentPlayingPosition) {
224  0 download(songs, false, false, false);
225  0 if (currentPlayingIndex != -1) {
226  0 play(currentPlayingIndex, false);
227  0 if (currentPlaying.isCompleteFileAvailable()) {
228  0 doPlay(currentPlaying, currentPlayingPosition, false);
229    }
230    }
231    }
232   
 
233  5 toggle @Override
234    public synchronized void setShufflePlayEnabled(boolean enabled) {
235  5 if (shufflePlay == enabled) {
236  3 return;
237    }
238   
239  2 shufflePlay = enabled;
240  2 if (shufflePlay) {
241  2 clear();
242  2 checkDownloads();
243    }
244    }
245   
 
246  99 toggle @Override
247    public synchronized boolean isShufflePlayEnabled() {
248  99 return shufflePlay;
249    }
250   
 
251  1 toggle @Override
252    public synchronized void shuffle() {
253  1 Collections.shuffle(downloadList);
254  1 if (currentPlaying != null) {
255  1 downloadList.remove(getCurrentPlayingIndex());
256  1 downloadList.add(0, currentPlaying);
257    }
258  1 revision++;
259  1 lifecycleSupport.serializeDownloadQueue();
260  1 updateJukeboxPlaylist();
261    }
262   
 
263  27 toggle @Override
264    public RepeatMode getRepeatMode() {
265  27 return Util.getRepeatMode(this);
266    }
267   
 
268  5 toggle @Override
269    public void setRepeatMode(RepeatMode repeatMode) {
270  5 Util.setRepeatMode(this, repeatMode);
271    }
272   
 
273  20 toggle @Override
274    public boolean getKeepScreenOn() {
275  20 return keepScreenOn;
276    }
277   
 
278  1 toggle @Override
279    public void setKeepScreenOn(boolean keepScreenOn) {
280  1 this.keepScreenOn = keepScreenOn;
281    }
282   
 
283  12 toggle @Override
284    public boolean getShowVisualization() {
285  12 return showVisualization;
286    }
287   
 
288  4 toggle @Override
289    public void setShowVisualization(boolean showVisualization) {
290  4 this.showVisualization = showVisualization;
291    }
292   
 
293  1171 toggle @Override
294    public synchronized DownloadFile forSong(MusicDirectory.Entry song) {
295  1171 for (DownloadFile downloadFile : downloadList) {
296  8065 if (downloadFile.getSong().equals(song)) {
297  753 return downloadFile;
298    }
299    }
300   
301  418 DownloadFile downloadFile = downloadFileCache.get(song);
302  418 if (downloadFile == null) {
303  50 downloadFile = new DownloadFile(this, song, false);
304  50 downloadFileCache.put(song, downloadFile);
305    }
306  418 return downloadFile;
307    }
308   
 
309  8 toggle @Override
310    public synchronized void clear() {
311  8 clear(true);
312    }
313   
 
314  4 toggle @Override
315    public synchronized void clearIncomplete() {
316  4 reset();
317  4 Iterator<DownloadFile> iterator = downloadList.iterator();
318  41 while (iterator.hasNext()) {
319  37 DownloadFile downloadFile = iterator.next();
320  37 if (!downloadFile.isCompleteFileAvailable()) {
321  12 iterator.remove();
322    }
323    }
324  4 lifecycleSupport.serializeDownloadQueue();
325  4 updateJukeboxPlaylist();
326    }
327   
 
328  155 toggle @Override
329    public synchronized int size() {
330  155 return downloadList.size();
331    }
332   
 
333  8 toggle public synchronized void clear(boolean serialize) {
334  8 reset();
335  8 downloadList.clear();
336  8 revision++;
337  8 if (currentDownloading != null) {
338  6 currentDownloading.cancelDownload();
339  6 currentDownloading = null;
340    }
341  8 setCurrentPlaying(null, false);
342   
343  8 if (serialize) {
344  8 lifecycleSupport.serializeDownloadQueue();
345    }
346  8 updateJukeboxPlaylist();
347    }
348   
 
349  0 toggle @Override
350    public synchronized void remove(DownloadFile downloadFile) {
351  0 if (downloadFile == currentDownloading) {
352  0 currentDownloading.cancelDownload();
353  0 currentDownloading = null;
354    }
355  0 if (downloadFile == currentPlaying) {
356  0 reset();
357  0 setCurrentPlaying(null, false);
358    }
359  0 downloadList.remove(downloadFile);
360  0 revision++;
361  0 lifecycleSupport.serializeDownloadQueue();
362  0 updateJukeboxPlaylist();
363    }
364   
 
365  1 toggle @Override
366    public synchronized void delete(List<MusicDirectory.Entry> songs) {
367  1 for (MusicDirectory.Entry song : songs) {
368  4 forSong(song).delete();
369    }
370    }
371   
 
372  0 toggle @Override
373    public synchronized void unpin(List<MusicDirectory.Entry> songs) {
374  0 for (MusicDirectory.Entry song : songs) {
375  0 forSong(song).unpin();
376    }
377    }
378   
 
379  26 toggle synchronized void setCurrentPlaying(int currentPlayingIndex, boolean showNotification) {
380  26 try {
381  26 setCurrentPlaying(downloadList.get(currentPlayingIndex), showNotification);
382    } catch (IndexOutOfBoundsException x) {
383    // Ignored
384    }
385    }
386   
 
387  38 toggle synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) {
388  38 this.currentPlaying = currentPlaying;
389   
390  38 if (currentPlaying != null) {
391  26 Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
392    } else {
393  12 Util.broadcastNewTrackInfo(this, null);
394    }
395   
396  38 if (currentPlaying != null && showNotification) {
397  26 Util.showPlayingNotification(this, this, handler, currentPlaying.getSong());
398    } else {
399  12 Util.hidePlayingNotification(this, this, handler);
400    }
401    }
402   
 
403  204 toggle @Override
404    public synchronized int getCurrentPlayingIndex() {
405  204 return downloadList.indexOf(currentPlaying);
406    }
407   
 
408  1253 toggle @Override
409    public DownloadFile getCurrentPlaying() {
410  1253 return currentPlaying;
411    }
412   
 
413  1 toggle @Override
414    public DownloadFile getCurrentDownloading() {
415  1 return currentDownloading;
416    }
417   
 
418  103 toggle @Override
419    public synchronized List<DownloadFile> getDownloads() {
420  103 return new ArrayList<DownloadFile>(downloadList);
421    }
422   
423    /** Plays either the current song (resume) or the first/next one in queue. */
 
424  0 toggle public synchronized void play()
425    {
426  0 int current = getCurrentPlayingIndex();
427  0 if (current == -1) {
428  0 play(0);
429    } else {
430  0 play(current);
431    }
432    }
433   
 
434  30 toggle @Override
435    public synchronized void play(int index) {
436  30 play(index, true);
437    }
438   
 
439  30 toggle private synchronized void play(int index, boolean start) {
440  30 if (index < 0 || index >= size()) {
441  4 reset();
442  4 setCurrentPlaying(null, false);
443    } else {
444  26 setCurrentPlaying(index, start);
445  26 checkDownloads();
446  26 if (start) {
447  26 if (jukeboxEnabled) {
448  0 jukeboxService.skip(getCurrentPlayingIndex(), 0);
449  0 setPlayerState(STARTED);
450    } else {
451  26 bufferAndPlay();
452    }
453    }
454    }
455    }
456   
457    /** Plays or resumes the playback, depending on the current player state. */
 
458  0 toggle public synchronized void togglePlayPause()
459    {
460  0 if (playerState == PAUSED || playerState == COMPLETED) {
461  0 start();
462  0 } else if (playerState == STOPPED || playerState == IDLE) {
463  0 play();
464  0 } else if (playerState == STARTED) {
465  0 pause();
466    }
467    }
468   
 
469  9 toggle @Override
470    public synchronized void seekTo(int position) {
471  9 try {
472  9 if (jukeboxEnabled) {
473  0 jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
474    } else {
475  9 mediaPlayer.seekTo(position);
476    }
477    } catch (Exception x) {
478  0 handleError(x);
479    }
480    }
481   
 
482  5 toggle @Override
483    public synchronized void previous() {
484  5 int index = getCurrentPlayingIndex();
485  5 if (index == -1) {
486  0 return;
487    }
488   
489    // Restart song if played more than five seconds.
490  5 if (getPlayerPosition() > 5000 || index == 0) {
491  1 play(index);
492    } else {
493  4 play(index - 1);
494    }
495    }
496   
 
497  10 toggle @Override
498    public synchronized void next() {
499  10 int index = getCurrentPlayingIndex();
500  10 if (index != -1) {
501  10 play(index + 1);
502    }
503    }
504   
 
505  0 toggle private void onSongCompleted() {
506  0 int index = getCurrentPlayingIndex();
507  0 if (index != -1) {
508  0 switch (getRepeatMode()) {
509  0 case OFF:
510  0 play(index + 1);
511  0 break;
512  0 case ALL:
513  0 play((index + 1) % size());
514  0 break;
515  0 case SINGLE:
516  0 play(index);
517  0 break;
518  0 default:
519  0 break;
520    }
521    }
522    }
523   
 
524  6 toggle @Override
525    public synchronized void pause() {
526  6 try {
527  6 if (playerState == STARTED) {
528  6 if (jukeboxEnabled) {
529  0 jukeboxService.stop();
530    } else {
531  6 mediaPlayer.pause();
532    }
533  6 setPlayerState(PAUSED);
534    }
535    } catch (Exception x) {
536  0 handleError(x);
537    }
538    }
539   
 
540  3 toggle @Override
541    public synchronized void start() {
542  3 try {
543  3 if (jukeboxEnabled) {
544  0 jukeboxService.start();
545    } else {
546  3 mediaPlayer.start();
547    }
548  3 setPlayerState(STARTED);
549    } catch (Exception x) {
550  0 handleError(x);
551    }
552    }
553   
 
554  51 toggle @Override
555    public synchronized void reset() {
556  51 if (bufferTask != null) {
557  50 bufferTask.cancel();
558    }
559  51 try {
560  51 mediaPlayer.reset();
561  51 setPlayerState(IDLE);
562    } catch (Exception x) {
563  0 handleError(x);
564    }
565    }
566   
 
567  268 toggle @Override
568    public synchronized int getPlayerPosition() {
569  268 try {
570  268 if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) {
571  96 return 0;
572    }
573  172 if (jukeboxEnabled) {
574  0 return jukeboxService.getPositionSeconds() * 1000;
575    } else {
576  172 return mediaPlayer.getCurrentPosition();
577    }
578    } catch (Exception x) {
579  0 handleError(x);
580  0 return 0;
581    }
582    }
583   
 
584  202 toggle @Override
585    public synchronized int getPlayerDuration() {
586  202 if (currentPlaying != null) {
587  202 Integer duration = currentPlaying.getSong().getDuration();
588  202 if (duration != null) {
589  146 return duration * 1000;
590    }
591    }
592  56 if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING) {
593  39 try {
594  39 return mediaPlayer.getDuration();
595    } catch (Exception x) {
596  0 handleError(x);
597    }
598    }
599  17 return 0;
600    }
601   
 
602  251 toggle @Override
603    public PlayerState getPlayerState() {
604  251 return playerState;
605    }
606   
 
607  197 toggle synchronized void setPlayerState(PlayerState playerState) {
608  197 Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")");
609   
610  197 if (playerState == PAUSED) {
611  6 lifecycleSupport.serializeDownloadQueue();
612    }
613   
614  197 boolean show = this.playerState == PAUSED && playerState == PlayerState.STARTED;
615  197 boolean hide = this.playerState == STARTED && playerState == PlayerState.PAUSED;
616  197 Util.broadcastPlaybackStatusChange(this, playerState);
617   
618  197 this.playerState = playerState;
619  197 if (show) {
620  3 Util.showPlayingNotification(this, this, handler, currentPlaying.getSong());
621  194 } else if (hide) {
622  6 Util.hidePlayingNotification(this, this, handler);
623    }
624   
625  197 if (playerState == STARTED) {
626  27 scrobbler.scrobble(this, currentPlaying, false);
627  170 } else if (playerState == COMPLETED) {
628  4 scrobbler.scrobble(this, currentPlaying, true);
629    }
630    }
631   
 
632  3 toggle @Override
633    public void setSuggestedPlaylistName(String name) {
634  3 this.suggestedPlaylistName = name;
635    }
636   
 
637  2 toggle @Override
638    public String getSuggestedPlaylistName() {
639  2 return suggestedPlaylistName;
640    }
641   
 
642  46 toggle @Override
643    public EqualizerController getEqualizerController() {
644  46 return equalizerController;
645    }
646   
 
647  34 toggle @Override
648    public VisualizerController getVisualizerController() {
649  34 return visualizerController;
650    }
651   
 
652  339 toggle @Override
653    public boolean isJukeboxEnabled() {
654  339 return jukeboxEnabled;
655    }
656   
 
657  6 toggle @Override
658    public void setJukeboxEnabled(boolean jukeboxEnabled) {
659  6 this.jukeboxEnabled = jukeboxEnabled;
660  6 jukeboxService.setEnabled(jukeboxEnabled);
661  6 if (jukeboxEnabled) {
662  3 reset();
663   
664    // Cancel current download, if necessary.
665  3 if (currentDownloading != null) {
666  2 currentDownloading.cancelDownload();
667    }
668    }
669    }
670   
 
671  0 toggle @Override
672    public void adjustJukeboxVolume(boolean up) {
673  0 jukeboxService.adjustVolume(up);
674    }
675   
 
676  26 toggle private synchronized void bufferAndPlay() {
677  26 reset();
678   
679  26 bufferTask = new BufferTask(currentPlaying, 0);
680  26 bufferTask.start();
681    }
682   
 
683  24 toggle private synchronized void doPlay(final DownloadFile downloadFile, int position, boolean start) {
684  24 try {
685  24 final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
686  24 downloadFile.updateModificationDate();
687  24 mediaPlayer.setOnCompletionListener(null);
688  24 mediaPlayer.reset();
689  24 setPlayerState(IDLE);
690  24 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
691  24 mediaPlayer.setDataSource(file.getPath());
692  24 setPlayerState(PREPARING);
693  24 mediaPlayer.prepare();
694  24 setPlayerState(PREPARED);
695   
696  24 mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
 
697  4 toggle @Override
698    public void onCompletion(MediaPlayer mediaPlayer) {
699   
700    // Acquire a temporary wakelock, since when we return from
701    // this callback the MediaPlayer will release its wakelock
702    // and allow the device to go to sleep.
703  4 wakeLock.acquire(60000);
704   
705  4 setPlayerState(COMPLETED);
706   
707    // If COMPLETED and not playing partial file, we are *really" finished
708    // with the song and can move on to the next.
709  4 if (!file.equals(downloadFile.getPartialFile())) {
710  0 onSongCompleted();
711  0 return;
712    }
713   
714    // If file is not completely downloaded, restart the playback from the current position.
715  4 int pos = mediaPlayer.getCurrentPosition();
716  4 synchronized (DownloadServiceImpl.this) {
717   
718    // Work-around for apparent bug on certain phones: If close (less than ten seconds) to the end
719    // of the song, skip to the next rather than restarting it.
720  4 Integer duration = downloadFile.getSong().getDuration() == null ? null : downloadFile.getSong().getDuration() * 1000;
721  4 if (duration != null) {
722  4 if (Math.abs(duration - pos) < 10000) {
723  0 Log.i(TAG, "Skipping restart from " + pos + " of " + duration);
724  0 onSongCompleted();
725  0 return;
726    }
727    }
728   
729  4 Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
730  4 reset();
731  4 bufferTask = new BufferTask(downloadFile, pos);
732  4 bufferTask.start();
733    }
734    }
735    });
736   
737  24 if (position != 0) {
738  3 Log.i(TAG, "Restarting player from position " + position);
739  3 mediaPlayer.seekTo(position);
740    }
741   
742  24 if (start) {
743  24 mediaPlayer.start();
744  24 setPlayerState(STARTED);
745    } else {
746  0 setPlayerState(PAUSED);
747    }
748  24 lifecycleSupport.serializeDownloadQueue();
749   
750    } catch (Exception x) {
751  0 handleError(x);
752    }
753    }
754   
 
755  1 toggle private void handleError(Exception x) {
756  1 Log.w(TAG, "Media player error: " + x, x);
757  1 mediaPlayer.reset();
758  1 setPlayerState(IDLE);
759    }
760   
 
761  128 toggle protected synchronized void checkDownloads() {
762   
763  128 if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) {
764  0 return;
765    }
766   
767  128 if (shufflePlay) {
768  61 checkShufflePlay();
769    }
770   
771  128 if (jukeboxEnabled || !Util.isNetworkConnected(this)) {
772  0 return;
773    }
774   
775  128 if (downloadList.isEmpty()) {
776  10 return;
777    }
778   
779    // Need to download current playing?
780  118 if (currentPlaying != null &&
781    currentPlaying != currentDownloading &&
782    !currentPlaying.isCompleteFileAvailable()) {
783   
784    // Cancel current download, if necessary.
785  14 if (currentDownloading != null) {
786  10 currentDownloading.cancelDownload();
787    }
788   
789  14 currentDownloading = currentPlaying;
790  14 currentDownloading.download();
791  14 cleanupCandidates.add(currentDownloading);
792    }
793   
794    // Find a suitable target for download.
795  104 else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed()) {
796   
797  54 int n = size();
798  54 if (n == 0) {
799  0 return;
800    }
801   
802  54 int preloaded = 0;
803   
804  54 int start = currentPlaying == null ? 0 : getCurrentPlayingIndex();
805  54 int i = start;
806  54 do {
807  850 DownloadFile downloadFile = downloadList.get(i);
808  850 if (!downloadFile.isWorkDone()) {
809  57 if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this)) {
810  7 currentDownloading = downloadFile;
811  7 currentDownloading.download();
812  7 cleanupCandidates.add(currentDownloading);
813  7 break;
814    }
815  793 } else if (currentPlaying != downloadFile) {
816  740 preloaded++;
817    }
818   
819  843 i = (i + 1) % n;
820  843 } while (i != start);
821    }
822   
823    // Delete obsolete .partial and .complete files.
824  118 cleanup();
825    }
826   
 
827  61 toggle private synchronized void checkShufflePlay() {
828   
829  61 final int listSize = 20;
830  61 boolean wasEmpty = downloadList.isEmpty();
831   
832  61 long revisionBefore = revision;
833   
834    // First, ensure that list is at least 20 songs long.
835  61 int size = size();
836  61 if (size < listSize) {
837  5 for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) {
838  41 DownloadFile downloadFile = new DownloadFile(this, song, false);
839  41 downloadList.add(downloadFile);
840  41 revision++;
841    }
842    }
843   
844  61 int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex();
845   
846    // Only shift playlist if playing song #5 or later.
847  61 if (currIndex > 4) {
848  2 int songsToShift = currIndex - 2;
849  2 for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift)) {
850  25 downloadList.add(new DownloadFile(this, song, false));
851  25 downloadList.get(0).cancelDownload();
852  25 downloadList.remove(0);
853  25 revision++;
854    }
855    }
856   
857  61 if (revisionBefore != revision) {
858  5 updateJukeboxPlaylist();
859    }
860   
861  61 if (wasEmpty && !downloadList.isEmpty()) {
862  2 play(0);
863    }
864    }
865   
 
866  183 toggle public long getDownloadListUpdateRevision() {
867  183 return revision;
868    }
869   
 
870  118 toggle private synchronized void cleanup() {
871  118 Iterator<DownloadFile> iterator = cleanupCandidates.iterator();
872  237 while (iterator.hasNext()) {
873  119 DownloadFile downloadFile = iterator.next();
874  119 if (downloadFile != currentPlaying && downloadFile != currentDownloading) {
875  19 if (downloadFile.cleanup()) {
876  19 iterator.remove();
877    }
878    }
879    }
880    }
881   
 
882    private class BufferTask extends CancellableTask {
883   
884    private static final int BUFFER_LENGTH_SECONDS = 5;
885   
886    private final DownloadFile downloadFile;
887    private final int position;
888    private final long expectedFileSize;
889    private final File partialFile;
890   
 
891  30 toggle public BufferTask(DownloadFile downloadFile, int position) {
892  30 this.downloadFile = downloadFile;
893  30 this.position = position;
894  30 partialFile = downloadFile.getPartialFile();
895   
896    // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to.
897  30 int bitRate = downloadFile.getBitRate();
898  30 long byteCount = Math.max(100000, bitRate * 1024 / 8 * BUFFER_LENGTH_SECONDS);
899   
900    // Find out how large the file should grow before resuming playback.
901  30 expectedFileSize = partialFile.length() + byteCount;
902    }
903   
 
904  30 toggle @Override
905    public void execute() {
906  30 setPlayerState(DOWNLOADING);
907   
908  56 while (!bufferComplete()) {
909  32 Util.sleepQuietly(1000L);
910  32 if (isCancelled()) {
911  6 return;
912    }
913    }
914  24 doPlay(downloadFile, position, true);
915    }
916   
 
917  56 toggle private boolean bufferComplete() {
918  56 boolean completeFileAvailable = downloadFile.isCompleteFileAvailable();
919  56 long size = partialFile.length();
920   
921  56 Log.i(TAG, "Buffering " + partialFile + " (" + size + "/" + expectedFileSize + ", " + completeFileAvailable + ")");
922  56 return completeFileAvailable || size >= expectedFileSize;
923    }
924   
 
925  110 toggle @Override
926    public String toString() {
927  110 return "BufferTask (" + downloadFile + ")";
928    }
929    }
930    }