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