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