1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
|
package net.sourceforge.subsonic.androidapp.service; |
20 |
|
|
21 |
|
import android.content.Context; |
22 |
|
import android.os.Handler; |
23 |
|
import android.util.Log; |
24 |
|
import android.view.Gravity; |
25 |
|
import android.view.LayoutInflater; |
26 |
|
import android.view.View; |
27 |
|
import android.widget.ProgressBar; |
28 |
|
import android.widget.Toast; |
29 |
|
import net.sourceforge.subsonic.androidapp.R; |
30 |
|
import net.sourceforge.subsonic.androidapp.domain.JukeboxStatus; |
31 |
|
import net.sourceforge.subsonic.androidapp.domain.PlayerState; |
32 |
|
import net.sourceforge.subsonic.androidapp.service.parser.SubsonicRESTException; |
33 |
|
import net.sourceforge.subsonic.androidapp.util.Util; |
34 |
|
|
35 |
|
import java.util.ArrayList; |
36 |
|
import java.util.Iterator; |
37 |
|
import java.util.List; |
38 |
|
import java.util.concurrent.Executors; |
39 |
|
import java.util.concurrent.LinkedBlockingQueue; |
40 |
|
import java.util.concurrent.ScheduledExecutorService; |
41 |
|
import java.util.concurrent.ScheduledFuture; |
42 |
|
import java.util.concurrent.TimeUnit; |
43 |
|
import java.util.concurrent.atomic.AtomicLong; |
44 |
|
|
45 |
|
|
46 |
|
|
47 |
|
|
48 |
|
@author |
49 |
|
@version |
50 |
|
|
|
|
| 47,5% |
Uncovered Elements: 62 (118) |
Complexity: 38 |
Complexity Density: 0,49 |
|
51 |
|
public class JukeboxService { |
52 |
|
|
53 |
|
private static final String TAG = JukeboxService.class.getSimpleName(); |
54 |
|
private static final long STATUS_UPDATE_INTERVAL_SECONDS = 5L; |
55 |
|
|
56 |
|
private final Handler handler = new Handler(); |
57 |
|
private final TaskQueue tasks = new TaskQueue(); |
58 |
|
private final DownloadServiceImpl downloadService; |
59 |
|
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); |
60 |
|
private ScheduledFuture<?> statusUpdateFuture; |
61 |
|
private final AtomicLong timeOfLastUpdate = new AtomicLong(); |
62 |
|
private JukeboxStatus jukeboxStatus; |
63 |
|
private float gain = 0.5f; |
64 |
|
private VolumeToast volumeToast; |
65 |
|
|
66 |
|
|
67 |
|
|
68 |
|
|
69 |
|
|
70 |
|
|
71 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0,5 |
|
72 |
1
|
public JukeboxService(DownloadServiceImpl downloadService) {... |
73 |
1
|
this.downloadService = downloadService; |
74 |
1
|
new Thread() { |
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
75 |
1
|
@Override... |
76 |
|
public void run() { |
77 |
1
|
processTasks(); |
78 |
|
} |
79 |
|
}.start(); |
80 |
|
} |
81 |
|
|
|
|
| 0% |
Uncovered Elements: 3 (3) |
Complexity: 1 |
Complexity Density: 0,33 |
|
82 |
0
|
private synchronized void startStatusUpdate() {... |
83 |
0
|
stopStatusUpdate(); |
84 |
0
|
Runnable updateTask = new Runnable() { |
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 0,5 |
|
85 |
0
|
@Override... |
86 |
|
public void run() { |
87 |
0
|
tasks.remove(GetStatus.class); |
88 |
0
|
tasks.add(new GetStatus()); |
89 |
|
} |
90 |
|
}; |
91 |
0
|
statusUpdateFuture = executorService.scheduleWithFixedDelay(updateTask, STATUS_UPDATE_INTERVAL_SECONDS, |
92 |
|
STATUS_UPDATE_INTERVAL_SECONDS, TimeUnit.SECONDS); |
93 |
|
} |
94 |
|
|
|
|
| 40% |
Uncovered Elements: 3 (5) |
Complexity: 2 |
Complexity Density: 0,67 |
|
95 |
6
|
private synchronized void stopStatusUpdate() {... |
96 |
6
|
if (statusUpdateFuture != null) { |
97 |
0
|
statusUpdateFuture.cancel(false); |
98 |
0
|
statusUpdateFuture = null; |
99 |
|
} |
100 |
|
} |
101 |
|
|
|
|
| 85,7% |
Uncovered Elements: 1 (7) |
Complexity: 2 |
Complexity Density: 0,29 |
|
102 |
1
|
private void processTasks() {... |
103 |
1
|
while (true) { |
104 |
7
|
JukeboxTask task = null; |
105 |
7
|
try { |
106 |
7
|
task = tasks.take(); |
107 |
6
|
JukeboxStatus status = task.execute(); |
108 |
0
|
onStatusUpdate(status); |
109 |
|
} catch (Throwable x) { |
110 |
6
|
onError(task, x); |
111 |
|
} |
112 |
|
} |
113 |
|
} |
114 |
|
|
|
|
| 0% |
Uncovered Elements: 7 (7) |
Complexity: 4 |
Complexity Density: 0,8 |
|
115 |
0
|
private void onStatusUpdate(JukeboxStatus jukeboxStatus) {... |
116 |
0
|
timeOfLastUpdate.set(System.currentTimeMillis()); |
117 |
0
|
this.jukeboxStatus = jukeboxStatus; |
118 |
|
|
119 |
|
|
120 |
0
|
Integer index = jukeboxStatus.getCurrentPlayingIndex(); |
121 |
0
|
if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) { |
122 |
0
|
downloadService.setCurrentPlaying(index, true); |
123 |
|
} |
124 |
|
} |
125 |
|
|
|
|
| 84,6% |
Uncovered Elements: 2 (13) |
Complexity: 8 |
Complexity Density: 1,14 |
|
126 |
6
|
private void onError(JukeboxTask task, Throwable x) {... |
127 |
6
|
if (x instanceof ServerTooOldException && !(task instanceof Stop)) { |
128 |
0
|
disableJukeboxOnError(x, R.string.download_jukebox_server_too_old); |
129 |
6
|
} else if (x instanceof OfflineException && !(task instanceof Stop)) { |
130 |
1
|
disableJukeboxOnError(x, R.string.download_jukebox_offline); |
131 |
5
|
} else if (x instanceof SubsonicRESTException && ((SubsonicRESTException) x).getCode() == 50 && !(task instanceof Stop)) { |
132 |
2
|
disableJukeboxOnError(x, R.string.download_jukebox_not_authorized); |
133 |
|
} else { |
134 |
3
|
Log.e(TAG, "Failed to process jukebox task: " + x, x); |
135 |
|
} |
136 |
|
} |
137 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (3) |
Complexity: 1 |
Complexity Density: 0,33 |
|
138 |
3
|
private void disableJukeboxOnError(Throwable x, final int resourceId) {... |
139 |
3
|
Log.w(TAG, x.toString()); |
140 |
3
|
handler.post(new Runnable() { |
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
141 |
3
|
@Override... |
142 |
|
public void run() { |
143 |
3
|
Util.toast(downloadService, resourceId, false); |
144 |
|
} |
145 |
|
}); |
146 |
3
|
downloadService.setJukeboxEnabled(false); |
147 |
|
} |
148 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (7) |
Complexity: 1 |
Complexity Density: 0,14 |
|
149 |
3
|
public void updatePlaylist() {... |
150 |
3
|
tasks.remove(Skip.class); |
151 |
3
|
tasks.remove(Stop.class); |
152 |
3
|
tasks.remove(Start.class); |
153 |
|
|
154 |
3
|
List<String> ids = new ArrayList<String>(); |
155 |
3
|
for (DownloadFile file : downloadService.getDownloads()) { |
156 |
60
|
ids.add(file.getSong().getId()); |
157 |
|
} |
158 |
3
|
tasks.add(new SetPlaylist(ids)); |
159 |
|
} |
160 |
|
|
|
|
| 0% |
Uncovered Elements: 10 (10) |
Complexity: 2 |
Complexity Density: 0,25 |
|
161 |
0
|
public void skip(final int index, final int offsetSeconds) {... |
162 |
0
|
tasks.remove(Skip.class); |
163 |
0
|
tasks.remove(Stop.class); |
164 |
0
|
tasks.remove(Start.class); |
165 |
|
|
166 |
0
|
startStatusUpdate(); |
167 |
0
|
if (jukeboxStatus != null) { |
168 |
0
|
jukeboxStatus.setPositionSeconds(offsetSeconds); |
169 |
|
} |
170 |
0
|
tasks.add(new Skip(index, offsetSeconds)); |
171 |
0
|
downloadService.setPlayerState(PlayerState.STARTED); |
172 |
|
} |
173 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 1 |
Complexity Density: 0,25 |
|
174 |
6
|
public void stop() {... |
175 |
6
|
tasks.remove(Stop.class); |
176 |
6
|
tasks.remove(Start.class); |
177 |
|
|
178 |
6
|
stopStatusUpdate(); |
179 |
6
|
tasks.add(new Stop()); |
180 |
|
} |
181 |
|
|
|
|
| 0% |
Uncovered Elements: 4 (4) |
Complexity: 1 |
Complexity Density: 0,25 |
|
182 |
0
|
public void start() {... |
183 |
0
|
tasks.remove(Stop.class); |
184 |
0
|
tasks.remove(Start.class); |
185 |
|
|
186 |
0
|
startStatusUpdate(); |
187 |
0
|
tasks.add(new Start()); |
188 |
|
} |
189 |
|
|
|
|
| 0% |
Uncovered Elements: 13 (13) |
Complexity: 3 |
Complexity Density: 0,33 |
|
190 |
0
|
public synchronized void adjustVolume(boolean up) {... |
191 |
0
|
float delta = up ? 0.1f : -0.1f; |
192 |
0
|
gain += delta; |
193 |
0
|
gain = Math.max(gain, 0.0f); |
194 |
0
|
gain = Math.min(gain, 1.0f); |
195 |
|
|
196 |
0
|
tasks.remove(SetGain.class); |
197 |
0
|
tasks.add(new SetGain(gain)); |
198 |
|
|
199 |
0
|
if (volumeToast == null) { |
200 |
0
|
volumeToast = new VolumeToast(downloadService); |
201 |
|
} |
202 |
0
|
volumeToast.setVolume(gain); |
203 |
|
} |
204 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
205 |
6
|
private MusicService getMusicService() {... |
206 |
6
|
return MusicServiceFactory.getMusicService(downloadService); |
207 |
|
} |
208 |
|
|
|
|
| 0% |
Uncovered Elements: 10 (10) |
Complexity: 5 |
Complexity Density: 0,83 |
|
209 |
0
|
public int getPositionSeconds() {... |
210 |
0
|
if (jukeboxStatus == null || jukeboxStatus.getPositionSeconds() == null || timeOfLastUpdate.get() == 0) { |
211 |
0
|
return 0; |
212 |
|
} |
213 |
|
|
214 |
0
|
if (jukeboxStatus.isPlaying()) { |
215 |
0
|
int secondsSinceLastUpdate = (int) ((System.currentTimeMillis() - timeOfLastUpdate.get()) / 1000L); |
216 |
0
|
return jukeboxStatus.getPositionSeconds() + secondsSinceLastUpdate; |
217 |
|
} |
218 |
|
|
219 |
0
|
return jukeboxStatus.getPositionSeconds(); |
220 |
|
} |
221 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (7) |
Complexity: 2 |
Complexity Density: 0,4 |
|
222 |
6
|
public void setEnabled(boolean enabled) {... |
223 |
6
|
tasks.clear(); |
224 |
6
|
if (enabled) { |
225 |
3
|
updatePlaylist(); |
226 |
|
} |
227 |
6
|
stop(); |
228 |
6
|
downloadService.setPlayerState(PlayerState.IDLE); |
229 |
|
} |
230 |
|
|
|
|
| 83,3% |
Uncovered Elements: 3 (18) |
Complexity: 7 |
Complexity Density: 0,7 |
|
231 |
|
private static class TaskQueue { |
232 |
|
|
233 |
|
private final LinkedBlockingQueue<JukeboxTask> queue = new LinkedBlockingQueue<JukeboxTask>(); |
234 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
235 |
9
|
void add(JukeboxTask jukeboxTask) {... |
236 |
9
|
queue.add(jukeboxTask); |
237 |
|
} |
238 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
239 |
7
|
JukeboxTask take() throws InterruptedException {... |
240 |
7
|
return queue.take(); |
241 |
|
} |
242 |
|
|
|
|
| 72,7% |
Uncovered Elements: 3 (11) |
Complexity: 4 |
Complexity Density: 0,57 |
|
243 |
21
|
void remove(Class<? extends JukeboxTask> clazz) {... |
244 |
21
|
try { |
245 |
21
|
Iterator<JukeboxTask> iterator = queue.iterator(); |
246 |
22
|
while (iterator.hasNext()) { |
247 |
1
|
JukeboxTask task = iterator.next(); |
248 |
1
|
if (clazz.equals(task.getClass())) { |
249 |
0
|
iterator.remove(); |
250 |
|
} |
251 |
|
} |
252 |
|
} catch (Throwable x) { |
253 |
0
|
Log.w(TAG, "Failed to clean-up task queue.", x); |
254 |
|
} |
255 |
|
} |
256 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
257 |
6
|
void clear() {... |
258 |
6
|
queue.clear(); |
259 |
|
} |
260 |
|
} |
261 |
|
|
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 1 |
|
262 |
|
private abstract class JukeboxTask { |
263 |
|
|
264 |
|
abstract JukeboxStatus execute() throws Exception; |
265 |
|
|
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
266 |
0
|
@Override... |
267 |
|
public String toString() { |
268 |
0
|
return getClass().getSimpleName(); |
269 |
|
} |
270 |
|
} |
271 |
|
|
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 1 |
|
272 |
|
private class GetStatus extends JukeboxTask { |
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
273 |
0
|
@Override... |
274 |
|
JukeboxStatus execute() throws Exception { |
275 |
0
|
return getMusicService().getJukeboxStatus(downloadService, null); |
276 |
|
} |
277 |
|
} |
278 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 2 |
Complexity Density: 1 |
|
279 |
|
private class SetPlaylist extends JukeboxTask { |
280 |
|
|
281 |
|
private final List<String> ids; |
282 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
283 |
3
|
SetPlaylist(List<String> ids) {... |
284 |
3
|
this.ids = ids; |
285 |
|
} |
286 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
287 |
3
|
@Override... |
288 |
|
JukeboxStatus execute() throws Exception { |
289 |
3
|
return getMusicService().updateJukeboxPlaylist(ids, downloadService, null); |
290 |
|
} |
291 |
|
} |
292 |
|
|
|
|
| 0% |
Uncovered Elements: 5 (5) |
Complexity: 2 |
Complexity Density: 0,67 |
|
293 |
|
private class Skip extends JukeboxTask { |
294 |
|
private final int index; |
295 |
|
private final int offsetSeconds; |
296 |
|
|
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 0,5 |
|
297 |
0
|
Skip(int index, int offsetSeconds) {... |
298 |
0
|
this.index = index; |
299 |
0
|
this.offsetSeconds = offsetSeconds; |
300 |
|
} |
301 |
|
|
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
302 |
0
|
@Override... |
303 |
|
JukeboxStatus execute() throws Exception { |
304 |
0
|
return getMusicService().skipJukebox(index, offsetSeconds, downloadService, null); |
305 |
|
} |
306 |
|
} |
307 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 1 |
|
308 |
|
private class Stop extends JukeboxTask { |
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
309 |
3
|
@Override... |
310 |
|
JukeboxStatus execute() throws Exception { |
311 |
3
|
return getMusicService().stopJukebox(downloadService, null); |
312 |
|
} |
313 |
|
} |
314 |
|
|
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 1 |
|
315 |
|
private class Start extends JukeboxTask { |
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
316 |
0
|
@Override... |
317 |
|
JukeboxStatus execute() throws Exception { |
318 |
0
|
return getMusicService().startJukebox(downloadService, null); |
319 |
|
} |
320 |
|
} |
321 |
|
|
|
|
| 0% |
Uncovered Elements: 4 (4) |
Complexity: 2 |
Complexity Density: 1 |
|
322 |
|
private class SetGain extends JukeboxTask { |
323 |
|
|
324 |
|
private final float gain; |
325 |
|
|
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
326 |
0
|
private SetGain(float gain) {... |
327 |
0
|
this.gain = gain; |
328 |
|
} |
329 |
|
|
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
330 |
0
|
@Override... |
331 |
|
JukeboxStatus execute() throws Exception { |
332 |
0
|
return getMusicService().setJukeboxGain(gain, downloadService, null); |
333 |
|
} |
334 |
|
} |
335 |
|
|
|
|
| 0% |
Uncovered Elements: 11 (11) |
Complexity: 2 |
Complexity Density: 0,22 |
|
336 |
|
private static class VolumeToast extends Toast { |
337 |
|
|
338 |
|
private final ProgressBar progressBar; |
339 |
|
|
|
|
| 0% |
Uncovered Elements: 7 (7) |
Complexity: 1 |
Complexity Density: 0,14 |
|
340 |
0
|
public VolumeToast(Context context) {... |
341 |
0
|
super(context); |
342 |
0
|
setDuration(Toast.LENGTH_SHORT); |
343 |
0
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
344 |
0
|
View view = inflater.inflate(R.layout.jukebox_volume, null); |
345 |
0
|
progressBar = (ProgressBar) view.findViewById(R.id.jukebox_volume_progress_bar); |
346 |
|
|
347 |
0
|
setView(view); |
348 |
0
|
setGravity(Gravity.TOP, 0, 0); |
349 |
|
} |
350 |
|
|
|
|
| 0% |
Uncovered Elements: 2 (2) |
Complexity: 1 |
Complexity Density: 0,5 |
|
351 |
0
|
public void setVolume(float volume) {... |
352 |
0
|
progressBar.setProgress(Math.round(100 * volume)); |
353 |
0
|
show(); |
354 |
|
} |
355 |
|
} |
356 |
|
} |