Merge remote-tracking branch 'remotes/upstream/master' into beta
[pub/Android/ownCloud.git] / src / com / owncloud / android / media / MediaService.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 package com.owncloud.android.media;
22
23 import android.accounts.Account;
24 import android.app.Activity;
25 import android.app.Notification;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.media.AudioManager;
33 import android.media.MediaPlayer;
34 import android.media.MediaPlayer.OnCompletionListener;
35 import android.media.MediaPlayer.OnErrorListener;
36 import android.media.MediaPlayer.OnPreparedListener;
37 import android.net.Uri;
38 import android.net.wifi.WifiManager;
39 import android.net.wifi.WifiManager.WifiLock;
40 import android.os.IBinder;
41 import android.os.PowerManager;
42 import android.support.v7.app.AlertDialog;
43 import android.support.v7.app.NotificationCompat;
44 import android.widget.Toast;
45
46 import java.io.IOException;
47
48 import com.owncloud.android.R;
49 import com.owncloud.android.datamodel.OCFile;
50 import com.owncloud.android.lib.common.accounts.AccountUtils;
51 import com.owncloud.android.lib.common.utils.Log_OC;
52 import com.owncloud.android.ui.activity.FileActivity;
53 import com.owncloud.android.ui.activity.FileDisplayActivity;
54
55
56 /**
57 * Service that handles media playback, both audio and video.
58 *
59 * Waits for Intents which signal the service to perform specific operations: Play, Pause,
60 * Rewind, etc.
61 */
62 public class MediaService extends Service implements OnCompletionListener, OnPreparedListener,
63 OnErrorListener, AudioManager.OnAudioFocusChangeListener {
64
65 private static final String TAG = MediaService.class.getSimpleName();
66
67 private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media";
68
69 /// Intent actions that we are prepared to handle
70 public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE";
71 public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL";
72
73 /// Keys to add extras to the action
74 public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
75 public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT";
76 public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION";
77 public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD";
78
79
80 /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */
81 public static final int OC_MEDIA_ERROR = 0;
82
83 /** Time To keep the control panel visible when the user does not use it */
84 public static final int MEDIA_CONTROL_SHORT_LIFE = 4000;
85
86 /** Time To keep the control panel visible when the user does not use it */
87 public static final int MEDIA_CONTROL_PERMANENT = 0;
88
89 /** Volume to set when audio focus is lost and ducking is allowed */
90 private static final float DUCK_VOLUME = 0.1f;
91
92 /** Media player instance */
93 private MediaPlayer mPlayer = null;
94
95 /** Reference to the system AudioManager */
96 private AudioManager mAudioManager = null;
97
98
99 /** Values to indicate the state of the service */
100 enum State {
101 STOPPED,
102 PREPARING,
103 PLAYING,
104 PAUSED
105 };
106
107
108 /** Current state */
109 private State mState = State.STOPPED;
110
111 /** Possible focus values */
112 enum AudioFocus {
113 NO_FOCUS,
114 NO_FOCUS_CAN_DUCK,
115 FOCUS
116 }
117
118 /** Current focus state */
119 private AudioFocus mAudioFocus = AudioFocus.NO_FOCUS;
120
121
122 /** 'True' when the current song is streaming from the network */
123 private boolean mIsStreaming = false;
124
125 /** Wifi lock kept to prevents the device from shutting off the radio when streaming a file. */
126 private WifiLock mWifiLock;
127
128 private static final String MEDIA_WIFI_LOCK_TAG = MY_PACKAGE + ".WIFI_LOCK";
129
130 /** Notification to keep in the notification bar while a song is playing */
131 private NotificationManager mNotificationManager;
132
133 /** File being played */
134 private OCFile mFile;
135
136 /** Account holding the file being played */
137 private Account mAccount;
138
139 /** Flag signaling if the audio should be played immediately when the file is prepared */
140 protected boolean mPlayOnPrepared;
141
142 /** Position, in miliseconds, where the audio should be started */
143 private int mStartPosition;
144
145 /** Interface to access the service through binding */
146 private IBinder mBinder;
147
148 /** Control panel shown to the user to control the playback, to register through binding */
149 private MediaControlView mMediaController;
150
151 /** Notification builder to create notifications, new reuse way since Android 6 */
152 private NotificationCompat.Builder mNotificationBuilder;
153
154 /**
155 * Helper method to get an error message suitable to show to users for errors occurred in media playback,
156 *
157 * @param context A context to access string resources.
158 * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
159 * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
160 * @return Message suitable to users.
161 */
162 public static String getMessageForMediaError(Context context, int what, int extra) {
163 int messageId;
164
165 if (what == OC_MEDIA_ERROR) {
166 messageId = extra;
167
168 } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) {
169 /* Added in API level 17
170 Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature.
171 Constant Value: -1010 (0xfffffc0e)
172 */
173 messageId = R.string.media_err_unsupported;
174
175 } else if (extra == MediaPlayer.MEDIA_ERROR_IO) {
176 /* Added in API level 17
177 File or network related operation errors.
178 Constant Value: -1004 (0xfffffc14)
179 */
180 messageId = R.string.media_err_io;
181
182 } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) {
183 /* Added in API level 17
184 Bitstream is not conforming to the related coding standard or file spec.
185 Constant Value: -1007 (0xfffffc11)
186 */
187 messageId = R.string.media_err_malformed;
188
189 } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
190 /* Added in API level 17
191 Some operation takes too long to complete, usually more than 3-5 seconds.
192 Constant Value: -110 (0xffffff92)
193 */
194 messageId = R.string.media_err_timeout;
195
196 } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
197 /* Added in API level 3
198 The video is streamed and its container is not valid for progressive playback i.e the video's index (e.g moov atom) is not at the start of the file.
199 Constant Value: 200 (0x000000c8)
200 */
201 messageId = R.string.media_err_invalid_progressive_playback;
202
203 } else {
204 /* MediaPlayer.MEDIA_ERROR_UNKNOWN
205 Added in API level 1
206 Unspecified media player error.
207 Constant Value: 1 (0x00000001)
208 */
209 /* MediaPlayer.MEDIA_ERROR_SERVER_DIED)
210 Added in API level 1
211 Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.
212 Constant Value: 100 (0x00000064)
213 */
214 messageId = R.string.media_err_unknown;
215 }
216 return context.getString(messageId);
217 }
218
219 public static AlertDialog.Builder streamWithExternalApp(final String uri, final Activity activity){
220 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
221 builder.setMessage(activity.getString(R.string.stream_expose_password))
222 .setPositiveButton(activity.getString(R.string.common_yes),
223 new DialogInterface.OnClickListener() {
224 public void onClick(DialogInterface dialog, int id) {
225 Intent i = new Intent(Intent.ACTION_VIEW);
226 i.setData(Uri.parse(uri));
227 activity.startActivity(i);
228 }
229 })
230 .setNegativeButton(activity.getString(R.string.common_no), new DialogInterface.OnClickListener() {
231 public void onClick(DialogInterface dialog, int id) {
232 // User cancelled the dialog
233 }
234 });
235 return builder;
236 }
237
238
239
240 /**
241 * Initialize a service instance
242 *
243 * {@inheritDoc}
244 */
245 @Override
246 public void onCreate() {
247 super.onCreate();
248 Log_OC.d(TAG, "Creating ownCloud media service");
249
250 mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)).
251 createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG);
252
253 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
254 mNotificationBuilder = new NotificationCompat.Builder(this);
255 mNotificationBuilder.setColor(this.getResources().getColor(R.color.primary));
256 mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
257 mBinder = new MediaServiceBinder(this);
258 }
259
260
261 /**
262 * Entry point for Intents requesting actions, sent here via startService.
263 *
264 * {@inheritDoc}
265 */
266 @Override
267 public int onStartCommand(Intent intent, int flags, int startId) {
268 String action = intent.getAction();
269 if (action.equals(ACTION_PLAY_FILE)) {
270 processPlayFileRequest(intent);
271
272 } else if (action.equals(ACTION_STOP_ALL)) {
273 processStopRequest(true);
274 }
275
276 return START_NOT_STICKY; // don't want it to restart in case it's killed.
277 }
278
279
280 /**
281 * Processes a request to play a media file received as a parameter
282 *
283 * TODO If a new request is received when a file is being prepared, it is ignored. Is this what we want?
284 *
285 * @param intent Intent received in the request with the data to identify the file to play.
286 */
287 private void processPlayFileRequest(Intent intent) {
288 if (mState != State.PREPARING) {
289 mFile = intent.getExtras().getParcelable(EXTRA_FILE);
290 mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT);
291 mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false);
292 mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0);
293 tryToGetAudioFocus();
294 playMedia();
295 }
296 }
297
298
299 /**
300 * Processes a request to play a media file.
301 */
302 protected void processPlayRequest() {
303 // request audio focus
304 tryToGetAudioFocus();
305
306 // actually play the song
307 if (mState == State.STOPPED) {
308 // (re)start playback
309 playMedia();
310
311 } else if (mState == State.PAUSED) {
312 // continue playback
313 mState = State.PLAYING;
314 setUpAsForeground(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
315 configAndStartMediaPlayer();
316 }
317 }
318
319
320 /**
321 * Makes sure the media player exists and has been reset. This will create the media player
322 * if needed. reset the existing media player if one already exists.
323 */
324 protected void createMediaPlayerIfNeeded() {
325 if (mPlayer == null) {
326 mPlayer = new MediaPlayer();
327
328 // make sure the CPU won't go to sleep while media is playing
329 mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
330
331 // the media player will notify the service when it's ready preparing, and when it's done playing
332 mPlayer.setOnPreparedListener(this);
333 mPlayer.setOnCompletionListener(this);
334 mPlayer.setOnErrorListener(this);
335
336 } else {
337 mPlayer.reset();
338 }
339 }
340
341 /**
342 * Processes a request to pause the current playback
343 */
344 protected void processPauseRequest() {
345 if (mState == State.PLAYING) {
346 mState = State.PAUSED;
347 mPlayer.pause();
348 releaseResources(false); // retain media player in pause
349 // TODO polite audio focus, instead of keep it owned; or not?
350 }
351 }
352
353
354 /**
355 * Processes a request to stop the playback.
356 *
357 * @param force When 'true', the playback is stopped no matter the value of mState
358 */
359 protected void processStopRequest(boolean force) {
360 if (mState != State.PREPARING || force) {
361 mState = State.STOPPED;
362 mFile = null;
363 mAccount = null;
364 releaseResources(true);
365 giveUpAudioFocus();
366 stopSelf(); // service is no longer necessary
367 }
368 }
369
370
371 /**
372 * Releases resources used by the service for playback. This includes the "foreground service"
373 * status and notification, the wake locks and possibly the MediaPlayer.
374 *
375 * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
376 */
377 protected void releaseResources(boolean releaseMediaPlayer) {
378 // stop being a foreground service
379 stopForeground(true);
380
381 // stop and release the Media Player, if it's available
382 if (releaseMediaPlayer && mPlayer != null) {
383 mPlayer.reset();
384 mPlayer.release();
385 mPlayer = null;
386 }
387
388 // release the Wifi lock, if holding it
389 if (mWifiLock.isHeld()) {
390 mWifiLock.release();
391 }
392 }
393
394
395 /**
396 * Fully releases the audio focus.
397 */
398 private void giveUpAudioFocus() {
399 if (mAudioFocus == AudioFocus.FOCUS
400 && mAudioManager != null
401 && AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this)) {
402
403 mAudioFocus = AudioFocus.NO_FOCUS;
404 }
405 }
406
407
408 /**
409 * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it.
410 */
411 protected void configAndStartMediaPlayer() {
412 if (mPlayer == null) {
413 throw new IllegalStateException("mPlayer is NULL");
414 }
415
416 if (mAudioFocus == AudioFocus.NO_FOCUS) {
417 if (mPlayer.isPlaying()) {
418 mPlayer.pause(); // have to be polite; but mState is not changed, to resume when focus is received again
419 }
420
421 } else {
422 if (mAudioFocus == AudioFocus.NO_FOCUS_CAN_DUCK) {
423 mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);
424
425 } else {
426 mPlayer.setVolume(1.0f, 1.0f); // full volume
427 }
428
429 if (!mPlayer.isPlaying()) {
430 mPlayer.start();
431 }
432 }
433 }
434
435
436 /**
437 * Requests the audio focus to the Audio Manager
438 */
439 private void tryToGetAudioFocus() {
440 if (mAudioFocus != AudioFocus.FOCUS
441 && mAudioManager != null
442 && (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus( this,
443 AudioManager.STREAM_MUSIC,
444 AudioManager.AUDIOFOCUS_GAIN))
445 ) {
446 mAudioFocus = AudioFocus.FOCUS;
447 }
448 }
449
450
451 /**
452 * Starts playing the current media file.
453 */
454 protected void playMedia() {
455 mState = State.STOPPED;
456 releaseResources(false); // release everything except MediaPlayer
457
458 try {
459 if (mAccount == null) {
460 Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show();
461 processStopRequest(true);
462 return;
463 }
464
465 createMediaPlayerIfNeeded();
466 mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
467 String url = mFile.getStoragePath();
468
469 if (url == null || url.length() <= 0) {
470 url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath();
471 }
472 mIsStreaming = url.startsWith("http:") || url.startsWith("https:");
473
474 mIsStreaming = false;
475
476 mPlayer.setDataSource(url);
477
478 mState = State.PREPARING;
479 setUpAsForeground(String.format(getString(R.string.media_state_loading), mFile.getFileName()));
480
481 // starts preparing the media player in background
482 mPlayer.prepareAsync();
483
484 // prevent the Wifi from going to sleep when streaming
485 if (mIsStreaming) {
486 mWifiLock.acquire();
487 } else if (mWifiLock.isHeld()) {
488 mWifiLock.release();
489 }
490
491 } catch (SecurityException e) {
492 Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e);
493 Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
494 processStopRequest(true);
495
496 } catch (IOException e) {
497 Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e);
498 Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show();
499 processStopRequest(true);
500
501 } catch (IllegalStateException e) {
502 Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e);
503 Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
504 processStopRequest(true);
505
506 } catch (IllegalArgumentException e) {
507 Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e);
508 Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show();
509 processStopRequest(true);
510 } catch (AccountUtils.AccountNotFoundException e) {
511 e.printStackTrace();
512 }
513 }
514
515
516 /** Called when media player is done playing current song. */
517 public void onCompletion(MediaPlayer player) {
518 Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show();
519 if (mMediaController != null) {
520 // somebody is still bound to the service
521 player.seekTo(0);
522 processPauseRequest();
523 mMediaController.updatePausePlay();
524 } else {
525 // nobody is bound
526 processStopRequest(true);
527 }
528 return;
529 }
530
531
532 /**
533 * Called when media player is done preparing.
534 *
535 * Time to start.
536 */
537 public void onPrepared(MediaPlayer player) {
538 mState = State.PLAYING;
539 updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName()));
540 if (mMediaController != null) {
541 mMediaController.setEnabled(true);
542 }
543 player.seekTo(mStartPosition);
544 configAndStartMediaPlayer();
545 if (!mPlayOnPrepared) {
546 processPauseRequest();
547 }
548
549 if (mMediaController != null) {
550 mMediaController.updatePausePlay();
551 }
552 }
553
554
555 /**
556 * Updates the status notification
557 */
558 private void updateNotification(String content) {
559 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
560
561 // TODO check if updating the Intent is really necessary
562 Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
563 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
564 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
565 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
566
567 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(getApplicationContext(),
568 (int) System.currentTimeMillis(),
569 showDetailsIntent,
570 PendingIntent.FLAG_UPDATE_CURRENT));
571 mNotificationBuilder.setWhen(System.currentTimeMillis());
572 mNotificationBuilder.setTicker(ticker);
573 mNotificationBuilder.setContentTitle(ticker);
574 mNotificationBuilder.setContentText(content);
575
576 mNotificationManager.notify(R.string.media_notif_ticker, mNotificationBuilder.build());
577 }
578
579
580 /**
581 * Configures the service as a foreground service.
582 *
583 * The system will avoid finishing the service as much as possible when resources as low.
584 *
585 * A notification must be created to keep the user aware of the existance of the service.
586 */
587 private void setUpAsForeground(String content) {
588 String ticker = String.format(getString(R.string.media_notif_ticker), getString(R.string.app_name));
589
590 /// creates status notification
591 // TODO put a progress bar to follow the playback progress
592 mNotificationBuilder.setSmallIcon(R.drawable.ic_play_arrow);
593 //mNotification.tickerText = text;
594 mNotificationBuilder.setWhen(System.currentTimeMillis());
595 mNotificationBuilder.setOngoing(true);
596
597 /// includes a pending intent in the notification showing the details view of the file
598 Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class);
599 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile);
600 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
601 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
602 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(getApplicationContext(),
603 (int) System.currentTimeMillis(),
604 showDetailsIntent,
605 PendingIntent.FLAG_UPDATE_CURRENT));
606 mNotificationBuilder.setContentTitle(ticker);
607 mNotificationBuilder.setContentText(content);
608
609 startForeground(R.string.media_notif_ticker, mNotificationBuilder.build());
610 }
611
612 /**
613 * Called when there's an error playing media.
614 *
615 * Warns the user about the error and resets the media player.
616 */
617 public boolean onError(MediaPlayer mp, int what, int extra) {
618 Log_OC.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra);
619
620 String message = getMessageForMediaError(this, what, extra);
621 Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
622
623 processStopRequest(true);
624 return true;
625 }
626
627 /**
628 * Called by the system when another app tries to play some sound.
629 *
630 * {@inheritDoc}
631 */
632 @Override
633 public void onAudioFocusChange(int focusChange) {
634 if (focusChange > 0) {
635 // focus gain; check AudioManager.AUDIOFOCUS_* values
636 mAudioFocus = AudioFocus.FOCUS;
637 // restart media player with new focus settings
638 if (mState == State.PLAYING)
639 configAndStartMediaPlayer();
640
641 } else if (focusChange < 0) {
642 // focus loss; check AudioManager.AUDIOFOCUS_* values
643 boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange;
644 mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS;
645 // start/restart/pause media player with new focus settings
646 if (mPlayer != null && mPlayer.isPlaying())
647 configAndStartMediaPlayer();
648 }
649
650 }
651
652 /**
653 * Called when the service is finished for final clean-up.
654 *
655 * {@inheritDoc}
656 */
657 @Override
658 public void onDestroy() {
659 mState = State.STOPPED;
660 releaseResources(true);
661 giveUpAudioFocus();
662 stopForeground(true);
663 super.onDestroy();
664 }
665
666
667 /**
668 * Provides a binder object that clients can use to perform operations on the MediaPlayer managed by the MediaService.
669 */
670 @Override
671 public IBinder onBind(Intent arg) {
672 return mBinder;
673 }
674
675
676 /**
677 * Called when ALL the bound clients were onbound.
678 *
679 * The service is destroyed if playback stopped or paused
680 */
681 @Override
682 public boolean onUnbind(Intent intent) {
683 if (mState == State.PAUSED || mState == State.STOPPED) {
684 processStopRequest(false);
685 }
686 return false; // not accepting rebinding (default behaviour)
687 }
688
689
690 /**
691 * Accesses the current MediaPlayer instance in the service.
692 *
693 * To be handled carefully. Visibility is protected to be accessed only
694 *
695 * @return Current MediaPlayer instance handled by MediaService.
696 */
697 protected MediaPlayer getPlayer() {
698 return mPlayer;
699 }
700
701
702 /**
703 * Accesses the current OCFile loaded in the service.
704 *
705 * @return The current OCFile loaded in the service.
706 */
707 protected OCFile getCurrentFile() {
708 return mFile;
709 }
710
711
712 /**
713 * Accesses the current {@link State} of the MediaService.
714 *
715 * @return The current {@link State} of the MediaService.
716 */
717 protected State getState() {
718 return mState;
719 }
720
721
722 protected void setMediaContoller(MediaControlView mediaController) {
723 mMediaController = mediaController;
724 }
725
726 protected MediaControlView getMediaController() {
727 return mMediaController;
728 }
729
730 }