X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/e4cd3fe54c8da5964936de926f61f83f027f42ef..21cacceea00a4181566b4c41ee50cdf2ed1cd55f:/src/com/owncloud/android/media/MediaService.java diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java index 987d94e3..7bd9c0c6 100644 --- a/src/com/owncloud/android/media/MediaService.java +++ b/src/com/owncloud/android/media/MediaService.java @@ -1,10 +1,12 @@ -/* ownCloud Android client application - * Copyright 2013 ownCloud Inc. +/** + * ownCloud Android client application + * + * @author David A. Velasco + * Copyright (C) 2015 ownCloud Inc. * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -19,39 +21,42 @@ package com.owncloud.android.media; import android.accounts.Account; +import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.IBinder; import android.os.PowerManager; -import android.util.Log; +import android.support.v7.app.AlertDialog; import android.widget.Toast; import java.io.IOException; -import com.owncloud.android.AccountUtils; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.ui.activity.FileDetailActivity; -import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.lib.common.accounts.AccountUtils; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; + /** * Service that handles media playback, both audio and video. * * Waits for Intents which signal the service to perform specific operations: Play, Pause, * Rewind, etc. - * - * @author David A. Velasco */ public class MediaService extends Service implements OnCompletionListener, OnPreparedListener, OnErrorListener, AudioManager.OnAudioFocusChangeListener { @@ -61,18 +66,30 @@ public class MediaService extends Service implements OnCompletionListener, OnPre private static final String MY_PACKAGE = MediaService.class.getPackage() != null ? MediaService.class.getPackage().getName() : "com.owncloud.android.media"; /// Intent actions that we are prepared to handle - public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".android.media.action.PLAY_FILE"; + public static final String ACTION_PLAY_FILE = MY_PACKAGE + ".action.PLAY_FILE"; + public static final String ACTION_STOP_ALL = MY_PACKAGE + ".action.STOP_ALL"; /// Keys to add extras to the action public static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; public static final String EXTRA_ACCOUNT = MY_PACKAGE + ".extra.ACCOUNT"; + public static String EXTRA_START_POSITION = MY_PACKAGE + ".extra.START_POSITION"; + public static final String EXTRA_PLAY_ON_LOAD = MY_PACKAGE + ".extra.PLAY_ON_LOAD"; + + + /** Error code for specific messages - see regular error codes at {@link MediaPlayer} */ + public static final int OC_MEDIA_ERROR = 0; + + /** Time To keep the control panel visible when the user does not use it */ + public static final int MEDIA_CONTROL_SHORT_LIFE = 4000; + + /** Time To keep the control panel visible when the user does not use it */ + public static final int MEDIA_CONTROL_PERMANENT = 0; /** Volume to set when audio focus is lost and ducking is allowed */ private static final float DUCK_VOLUME = 0.1f; /** Media player instance */ private MediaPlayer mPlayer = null; - /** Reference to the system AudioManager */ private AudioManager mAudioManager = null; @@ -119,11 +136,106 @@ public class MediaService extends Service implements OnCompletionListener, OnPre /** Account holding the file being played */ private Account mAccount; + /** Flag signaling if the audio should be played immediately when the file is prepared */ + protected boolean mPlayOnPrepared; + + /** Position, in miliseconds, where the audio should be started */ + private int mStartPosition; + /** Interface to access the service through binding */ private IBinder mBinder; + + /** Control panel shown to the user to control the playback, to register through binding */ + private MediaControlView mMediaController; + + /** + * Helper method to get an error message suitable to show to users for errors occurred in media playback, + * + * @param context A context to access string resources. + * @param what See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) + * @param extra See {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int) + * @return Message suitable to users. + */ + public static String getMessageForMediaError(Context context, int what, int extra) { + int messageId; + + if (what == OC_MEDIA_ERROR) { + messageId = extra; + + } else if (extra == MediaPlayer.MEDIA_ERROR_UNSUPPORTED) { + /* Added in API level 17 + Bitstream is conforming to the related coding standard or file spec, but the media framework does not support the feature. + Constant Value: -1010 (0xfffffc0e) + */ + messageId = R.string.media_err_unsupported; + + } else if (extra == MediaPlayer.MEDIA_ERROR_IO) { + /* Added in API level 17 + File or network related operation errors. + Constant Value: -1004 (0xfffffc14) + */ + messageId = R.string.media_err_io; + + } else if (extra == MediaPlayer.MEDIA_ERROR_MALFORMED) { + /* Added in API level 17 + Bitstream is not conforming to the related coding standard or file spec. + Constant Value: -1007 (0xfffffc11) + */ + messageId = R.string.media_err_malformed; + + } else if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { + /* Added in API level 17 + Some operation takes too long to complete, usually more than 3-5 seconds. + Constant Value: -110 (0xffffff92) + */ + messageId = R.string.media_err_timeout; + + } else if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + /* Added in API level 3 + 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. + Constant Value: 200 (0x000000c8) + */ + messageId = R.string.media_err_invalid_progressive_playback; + + } else { + /* MediaPlayer.MEDIA_ERROR_UNKNOWN + Added in API level 1 + Unspecified media player error. + Constant Value: 1 (0x00000001) + */ + /* MediaPlayer.MEDIA_ERROR_SERVER_DIED) + Added in API level 1 + Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one. + Constant Value: 100 (0x00000064) + */ + messageId = R.string.media_err_unknown; + } + return context.getString(messageId); + } + + public static AlertDialog.Builder streamWithExternalApp(final String uri, final Activity activity){ + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMessage(activity.getString(R.string.stream_expose_password)) + .setPositiveButton(activity.getString(R.string.common_yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(uri)); + activity.startActivity(i); + } + }) + .setNegativeButton(activity.getString(R.string.common_no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); + return builder; + } + + /** * Initialize a service instance * @@ -131,7 +243,8 @@ public class MediaService extends Service implements OnCompletionListener, OnPre */ @Override public void onCreate() { - Log.d(TAG, "Creating ownCloud media service"); + super.onCreate(); + Log_OC.d(TAG, "Creating ownCloud media service"); mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)). createWifiLock(WifiManager.WIFI_MODE_FULL, MEDIA_WIFI_LOCK_TAG); @@ -152,6 +265,9 @@ public class MediaService extends Service implements OnCompletionListener, OnPre String action = intent.getAction(); if (action.equals(ACTION_PLAY_FILE)) { processPlayFileRequest(intent); + + } else if (action.equals(ACTION_STOP_ALL)) { + processStopRequest(true); } return START_NOT_STICKY; // don't want it to restart in case it's killed. @@ -166,9 +282,11 @@ public class MediaService extends Service implements OnCompletionListener, OnPre * @param intent Intent received in the request with the data to identify the file to play. */ private void processPlayFileRequest(Intent intent) { - if (mState == State.PLAYING || mState == State.PAUSED || mState == State.STOPPED) { + if (mState != State.PREPARING) { mFile = intent.getExtras().getParcelable(EXTRA_FILE); mAccount = intent.getExtras().getParcelable(EXTRA_ACCOUNT); + mPlayOnPrepared = intent.getExtras().getBoolean(EXTRA_PLAY_ON_LOAD, false); + mStartPosition = intent.getExtras().getInt(EXTRA_START_POSITION, 0); tryToGetAudioFocus(); playMedia(); } @@ -199,7 +317,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre /** * Makes sure the media player exists and has been reset. This will create the media player - * if needed, or reset the existing media player if one already exists. + * if needed. reset the existing media player if one already exists. */ protected void createMediaPlayerIfNeeded() { if (mPlayer == null) { @@ -237,12 +355,10 @@ public class MediaService extends Service implements OnCompletionListener, OnPre * @param force When 'true', the playback is stopped no matter the value of mState */ protected void processStopRequest(boolean force) { - if (mState == State.PLAYING || mState == State.PAUSED || mState == State.STOPPED || force) { + if (mState != State.PREPARING || force) { mState = State.STOPPED; - mFile = null; mAccount = null; - releaseResources(true); giveUpAudioFocus(); stopSelf(); // service is no longer necessary @@ -338,12 +454,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre releaseResources(false); // release everything except MediaPlayer try { - if (mFile == null) { - Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show(); - processStopRequest(true); - return; - - } else if (mAccount == null) { + if (mAccount == null) { Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show(); processStopRequest(true); return; @@ -352,10 +463,13 @@ public class MediaService extends Service implements OnCompletionListener, OnPre createMediaPlayerIfNeeded(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); String url = mFile.getStoragePath(); + if (url == null || url.length() <= 0) { url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath(); } mIsStreaming = url.startsWith("http:") || url.startsWith("https:"); + + mIsStreaming = false; mPlayer.setDataSource(url); @@ -373,21 +487,26 @@ public class MediaService extends Service implements OnCompletionListener, OnPre } } catch (SecurityException e) { - Log.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e); - // TODO message to the user + Log_OC.e(TAG, "SecurityException playing " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_security_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); } catch (IOException e) { - Log.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e); - // TODO message to the user + Log_OC.e(TAG, "IOException playing " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_io_ex), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); } catch (IllegalStateException e) { - Log.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e); - // TODO message to the user + Log_OC.e(TAG, "IllegalStateException " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); } catch (IllegalArgumentException e) { - Log.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e); + Log_OC.e(TAG, "IllegalArgumentException " + mAccount.name + mFile.getRemotePath(), e); + Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); + processStopRequest(true); + } catch (AccountUtils.AccountNotFoundException e) { e.printStackTrace(); - // TODO message to the user } } @@ -395,7 +514,15 @@ public class MediaService extends Service implements OnCompletionListener, OnPre /** Called when media player is done playing current song. */ public void onCompletion(MediaPlayer player) { Toast.makeText(this, String.format(getString(R.string.media_event_done, mFile.getFileName())), Toast.LENGTH_LONG).show(); - processStopRequest(true); + if (mMediaController != null) { + // somebody is still bound to the service + player.seekTo(0); + processPauseRequest(); + mMediaController.updatePausePlay(); + } else { + // nobody is bound + processStopRequest(true); + } return; } @@ -408,7 +535,18 @@ public class MediaService extends Service implements OnCompletionListener, OnPre public void onPrepared(MediaPlayer player) { mState = State.PLAYING; updateNotification(String.format(getString(R.string.media_state_playing), mFile.getFileName())); + if (mMediaController != null) { + mMediaController.setEnabled(true); + } + player.seekTo(mStartPosition); configAndStartMediaPlayer(); + if (!mPlayOnPrepared) { + processPauseRequest(); + } + + if (mMediaController != null) { + mMediaController.updatePausePlay(); + } } @@ -418,9 +556,9 @@ public class MediaService extends Service implements OnCompletionListener, OnPre @SuppressWarnings("deprecation") private void updateNotification(String content) { // TODO check if updating the Intent is really necessary - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), @@ -455,9 +593,9 @@ public class MediaService extends Service implements OnCompletionListener, OnPre /// includes a pending intent in the notification showing the details view of the file - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), @@ -478,19 +616,15 @@ public class MediaService extends Service implements OnCompletionListener, OnPre * Warns the user about the error and resets the media player. */ public boolean onError(MediaPlayer mp, int what, int extra) { - // TODO FOLLOW HERE!!!!!! + Log_OC.e(TAG, "Error in audio playback, what = " + what + ", extra = " + extra); - Toast.makeText(getApplicationContext(), "Media player error! Resetting.", - Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra)); - - mState = State.STOPPED; - releaseResources(true); - giveUpAudioFocus(); + String message = getMessageForMediaError(this, what, extra); + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); + + processStopRequest(true); return true; } - /** * Called by the system when another app tries to play some sound. * @@ -500,9 +634,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre public void onAudioFocusChange(int focusChange) { if (focusChange > 0) { // focus gain; check AudioManager.AUDIOFOCUS_* values - Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show(); mAudioFocus = AudioFocus.FOCUS; - // restart media player with new focus settings if (mState == State.PLAYING) configAndStartMediaPlayer(); @@ -510,10 +642,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre } else if (focusChange < 0) { // focus loss; check AudioManager.AUDIOFOCUS_* values boolean canDuck = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK == focusChange; - Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" : - "no duck"), Toast.LENGTH_SHORT).show(); mAudioFocus = canDuck ? AudioFocus.NO_FOCUS_CAN_DUCK : AudioFocus.NO_FOCUS; - // start/restart/pause media player with new focus settings if (mPlayer != null && mPlayer.isPlaying()) configAndStartMediaPlayer(); @@ -531,6 +660,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre mState = State.STOPPED; releaseResources(true); giveUpAudioFocus(); + super.onDestroy(); } @@ -588,4 +718,13 @@ public class MediaService extends Service implements OnCompletionListener, OnPre return mState; } + + protected void setMediaContoller(MediaControlView mediaController) { + mMediaController = mediaController; + } + + protected MediaControlView getMediaController() { + return mMediaController; + } + }