MediaService.java 26 KB

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