Merge branch 'playbackservice_rewrite' into develop
|
@ -153,7 +153,7 @@
|
|||
android:name=".service.download.DownloadService"
|
||||
android:enabled="true"/>
|
||||
<service
|
||||
android:name="de.danoeh.antennapod.service.PlaybackService"
|
||||
android:name=".service.playback.PlaybackService"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
</service>
|
||||
|
@ -194,7 +194,7 @@
|
|||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".service.PlayerWidgetService"
|
||||
android:name=".service.playback.PlayerWidgetService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="radial"
|
||||
android:gradientRadius="60"
|
||||
android:startColor="#000000"
|
||||
android:endColor="@android:color/transparent"/>
|
||||
</shape>
|
|
@ -1,13 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<VideoView
|
||||
<de.danoeh.antennapod.view.AspectRatioVideoView
|
||||
android:id="@+id/videoview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressIndicator"
|
||||
|
@ -15,58 +16,31 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"
|
||||
android:indeterminateOnly="true" />
|
||||
<!-- Mediaplayer controls -->
|
||||
android:indeterminateOnly="true"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitXY"
|
||||
android:background="@drawable/overlay_button_circle_background"
|
||||
android:src="@drawable/ic_action_pause_over_video"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:background="?attr/overlay_background"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/playercontrol"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="clip_horizontal"
|
||||
android:layout_margin="4dp" >
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="?attr/borderless_button"
|
||||
android:src="?attr/av_pause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butFF"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_toRightOf="@+id/butPlay"
|
||||
android:background="?attr/borderless_button"
|
||||
android:src="?attr/av_fast_forward" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butRev"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_toLeftOf="@+id/butPlay"
|
||||
android:background="?attr/borderless_button"
|
||||
android:src="?attr/av_rewind" />
|
||||
</RelativeLayout>
|
||||
android:background="#80000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/timecontrol"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginBottom="4dp" >
|
||||
android:layout_height="50dp"
|
||||
android:paddingTop="8dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPosition"
|
||||
|
@ -77,7 +51,10 @@
|
|||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/position_default_label" />
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/position_default_label"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvLength"
|
||||
|
@ -88,7 +65,10 @@
|
|||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/position_default_label" />
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/position_default_label"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/sbPosition"
|
||||
|
@ -96,7 +76,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@+id/txtvLength"
|
||||
android:layout_toRightOf="@+id/txtvPosition"
|
||||
android:max="500" />
|
||||
android:max="500"/>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -11,15 +11,10 @@ import android.support.v4.app.ListFragment;
|
|||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.Window;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.view.Window;
|
||||
import android.widget.*;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.ChapterListAdapter;
|
||||
|
@ -31,501 +26,511 @@ import de.danoeh.antennapod.feed.SimpleChapter;
|
|||
import de.danoeh.antennapod.fragment.CoverFragment;
|
||||
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
|
||||
/** Activity for playing audio files. */
|
||||
/**
|
||||
* Activity for playing audio files.
|
||||
*/
|
||||
public class AudioplayerActivity extends MediaplayerActivity {
|
||||
private static final int POS_COVER = 0;
|
||||
private static final int POS_DESCR = 1;
|
||||
private static final int POS_CHAPTERS = 2;
|
||||
private static final int NUM_CONTENT_FRAGMENTS = 3;
|
||||
private static final int POS_COVER = 0;
|
||||
private static final int POS_DESCR = 1;
|
||||
private static final int POS_CHAPTERS = 2;
|
||||
private static final int NUM_CONTENT_FRAGMENTS = 3;
|
||||
|
||||
final String TAG = "AudioplayerActivity";
|
||||
private static final String PREFS = "AudioPlayerActivityPreferences";
|
||||
private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
|
||||
private static final String PREF_PLAYABLE_ID = "playableId";
|
||||
final String TAG = "AudioplayerActivity";
|
||||
private static final String PREFS = "AudioPlayerActivityPreferences";
|
||||
private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
|
||||
private static final String PREF_PLAYABLE_ID = "playableId";
|
||||
|
||||
private Fragment[] detachedFragments;
|
||||
private Fragment[] detachedFragments;
|
||||
|
||||
private CoverFragment coverFragment;
|
||||
private ItemDescriptionFragment descriptionFragment;
|
||||
private ListFragment chapterFragment;
|
||||
private CoverFragment coverFragment;
|
||||
private ItemDescriptionFragment descriptionFragment;
|
||||
private ListFragment chapterFragment;
|
||||
|
||||
private Fragment currentlyShownFragment;
|
||||
private int currentlyShownPosition = -1;
|
||||
/** Used if onResume was called without loadMediaInfo. */
|
||||
private int savedPosition = -1;
|
||||
private Fragment currentlyShownFragment;
|
||||
private int currentlyShownPosition = -1;
|
||||
/**
|
||||
* Used if onResume was called without loadMediaInfo.
|
||||
*/
|
||||
private int savedPosition = -1;
|
||||
|
||||
private TextView txtvTitle;
|
||||
private TextView txtvFeed;
|
||||
private Button butPlaybackSpeed;
|
||||
private ImageButton butNavLeft;
|
||||
private ImageButton butNavRight;
|
||||
private TextView txtvTitle;
|
||||
private TextView txtvFeed;
|
||||
private Button butPlaybackSpeed;
|
||||
private ImageButton butNavLeft;
|
||||
private ImageButton butNavRight;
|
||||
|
||||
private void resetFragmentView() {
|
||||
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
|
||||
private void resetFragmentView() {
|
||||
FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
if (coverFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing cover fragment");
|
||||
fT.remove(coverFragment);
|
||||
}
|
||||
if (descriptionFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing description fragment");
|
||||
fT.remove(descriptionFragment);
|
||||
}
|
||||
if (chapterFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing chapter fragment");
|
||||
fT.remove(chapterFragment);
|
||||
}
|
||||
if (currentlyShownFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing currently shown fragment");
|
||||
fT.remove(currentlyShownFragment);
|
||||
}
|
||||
for (int i = 0; i < detachedFragments.length; i++) {
|
||||
Fragment f = detachedFragments[i];
|
||||
if (f != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing detached fragment");
|
||||
fT.remove(f);
|
||||
}
|
||||
}
|
||||
fT.commit();
|
||||
currentlyShownFragment = null;
|
||||
coverFragment = null;
|
||||
descriptionFragment = null;
|
||||
chapterFragment = null;
|
||||
currentlyShownPosition = -1;
|
||||
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
|
||||
}
|
||||
if (coverFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing cover fragment");
|
||||
fT.remove(coverFragment);
|
||||
}
|
||||
if (descriptionFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing description fragment");
|
||||
fT.remove(descriptionFragment);
|
||||
}
|
||||
if (chapterFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing chapter fragment");
|
||||
fT.remove(chapterFragment);
|
||||
}
|
||||
if (currentlyShownFragment != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing currently shown fragment");
|
||||
fT.remove(currentlyShownFragment);
|
||||
}
|
||||
for (int i = 0; i < detachedFragments.length; i++) {
|
||||
Fragment f = detachedFragments[i];
|
||||
if (f != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing detached fragment");
|
||||
fT.remove(f);
|
||||
}
|
||||
}
|
||||
fT.commit();
|
||||
currentlyShownFragment = null;
|
||||
coverFragment = null;
|
||||
descriptionFragment = null;
|
||||
chapterFragment = null;
|
||||
currentlyShownPosition = -1;
|
||||
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "onStop");
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "onStop");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
|
||||
}
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
|
||||
}
|
||||
|
||||
private void savePreferences() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Saving preferences");
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
if (currentlyShownPosition >= 0 && controller != null
|
||||
&& controller.getMedia() != null) {
|
||||
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
|
||||
currentlyShownPosition);
|
||||
editor.putString(PREF_PLAYABLE_ID, controller.getMedia()
|
||||
.getIdentifier().toString());
|
||||
} else {
|
||||
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
|
||||
editor.putString(PREF_PLAYABLE_ID, "");
|
||||
}
|
||||
editor.commit();
|
||||
private void savePreferences() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Saving preferences");
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
if (currentlyShownPosition >= 0 && controller != null
|
||||
&& controller.getMedia() != null) {
|
||||
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
|
||||
currentlyShownPosition);
|
||||
editor.putString(PREF_PLAYABLE_ID, controller.getMedia()
|
||||
.getIdentifier().toString());
|
||||
} else {
|
||||
editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
|
||||
editor.putString(PREF_PLAYABLE_ID, "");
|
||||
}
|
||||
editor.commit();
|
||||
|
||||
savedPosition = currentlyShownPosition;
|
||||
}
|
||||
savedPosition = currentlyShownPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
// super.onSaveInstanceState(outState); would cause crash
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "onSaveInstanceState");
|
||||
}
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
// super.onSaveInstanceState(outState); would cause crash
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "onSaveInstanceState");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
savePreferences();
|
||||
resetFragmentView();
|
||||
super.onPause();
|
||||
}
|
||||
@Override
|
||||
protected void onPause() {
|
||||
savePreferences();
|
||||
resetFragmentView();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
restoreFromPreferences();
|
||||
}
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
restoreFromPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to restore the selected fragment position from the Activity's
|
||||
* preferences.
|
||||
*
|
||||
* @return true if restoreFromPrefernces changed the activity's state
|
||||
* */
|
||||
private boolean restoreFromPreferences() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Restoring instance state");
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
|
||||
-1);
|
||||
String playableId = prefs.getString(PREF_PLAYABLE_ID, "");
|
||||
/**
|
||||
* Tries to restore the selected fragment position from the Activity's
|
||||
* preferences.
|
||||
*
|
||||
* @return true if restoreFromPrefernces changed the activity's state
|
||||
*/
|
||||
private boolean restoreFromPreferences() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Restoring instance state");
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
|
||||
-1);
|
||||
String playableId = prefs.getString(PREF_PLAYABLE_ID, "");
|
||||
|
||||
if (savedPosition != -1
|
||||
&& controller != null
|
||||
&& controller.getMedia() != null
|
||||
&& controller.getMedia().getIdentifier().toString()
|
||||
.equals(playableId)) {
|
||||
switchToFragment(savedPosition);
|
||||
return true;
|
||||
} else if (controller == null || controller.getMedia() == null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Couldn't restore from preferences: controller or media was null");
|
||||
} else {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
|
||||
+ savedPosition + ", id: " + playableId);
|
||||
if (savedPosition != -1
|
||||
&& controller != null
|
||||
&& controller.getMedia() != null
|
||||
&& controller.getMedia().getIdentifier().toString()
|
||||
.equals(playableId)) {
|
||||
switchToFragment(savedPosition);
|
||||
return true;
|
||||
} else if (controller == null || controller.getMedia() == null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Couldn't restore from preferences: controller or media was null");
|
||||
} else {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
|
||||
+ savedPosition + ", id: " + playableId);
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (getIntent().getAction() != null
|
||||
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
|
||||
Intent intent = getIntent();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Received VIEW intent: "
|
||||
+ intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
|
||||
MediaType.AUDIO);
|
||||
Intent launchIntent = new Intent(this, PlaybackService.class);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
|
||||
true);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
|
||||
true);
|
||||
startService(launchIntent);
|
||||
}
|
||||
if (savedPosition != -1) {
|
||||
switchToFragment(savedPosition);
|
||||
}
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (getIntent().getAction() != null
|
||||
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
|
||||
Intent intent = getIntent();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Received VIEW intent: "
|
||||
+ intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
|
||||
MediaType.AUDIO);
|
||||
Intent launchIntent = new Intent(this, PlaybackService.class);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
|
||||
true);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
|
||||
true);
|
||||
startService(launchIntent);
|
||||
}
|
||||
if (savedPosition != -1) {
|
||||
switchToFragment(savedPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
}
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAwaitingVideoSurface() {
|
||||
startActivity(new Intent(this, VideoplayerActivity.class));
|
||||
}
|
||||
@Override
|
||||
protected void onAwaitingVideoSurface() {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
|
||||
startActivity(new Intent(this, VideoplayerActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postStatusMsg(int resId) {
|
||||
setSupportProgressBarIndeterminateVisibility(resId == R.string.player_preparing_msg
|
||||
|| resId == R.string.player_seeking_msg
|
||||
|| resId == R.string.player_buffering_msg);
|
||||
}
|
||||
@Override
|
||||
protected void postStatusMsg(int resId) {
|
||||
setSupportProgressBarIndeterminateVisibility(resId == R.string.player_preparing_msg
|
||||
|| resId == R.string.player_seeking_msg
|
||||
|| resId == R.string.player_buffering_msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearStatusMsg() {
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
@Override
|
||||
protected void clearStatusMsg() {
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the currently displayed fragment.
|
||||
*
|
||||
* @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
|
||||
* */
|
||||
private void switchToFragment(int pos) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Switching contentView to position " + pos);
|
||||
if (currentlyShownPosition != pos && controller != null) {
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
if (currentlyShownFragment != null) {
|
||||
detachedFragments[currentlyShownPosition] = currentlyShownFragment;
|
||||
ft.detach(currentlyShownFragment);
|
||||
}
|
||||
switch (pos) {
|
||||
case POS_COVER:
|
||||
if (coverFragment == null) {
|
||||
Log.i(TAG, "Using new coverfragment");
|
||||
coverFragment = CoverFragment.newInstance(media);
|
||||
}
|
||||
currentlyShownFragment = coverFragment;
|
||||
break;
|
||||
case POS_DESCR:
|
||||
if (descriptionFragment == null) {
|
||||
descriptionFragment = ItemDescriptionFragment
|
||||
.newInstance(media, true);
|
||||
}
|
||||
currentlyShownFragment = descriptionFragment;
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
if (chapterFragment == null) {
|
||||
chapterFragment = new ListFragment() {
|
||||
/**
|
||||
* Changes the currently displayed fragment.
|
||||
*
|
||||
* @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
|
||||
*/
|
||||
private void switchToFragment(int pos) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Switching contentView to position " + pos);
|
||||
if (currentlyShownPosition != pos && controller != null) {
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
FragmentTransaction ft = getSupportFragmentManager()
|
||||
.beginTransaction();
|
||||
if (currentlyShownFragment != null) {
|
||||
detachedFragments[currentlyShownPosition] = currentlyShownFragment;
|
||||
ft.detach(currentlyShownFragment);
|
||||
}
|
||||
switch (pos) {
|
||||
case POS_COVER:
|
||||
if (coverFragment == null) {
|
||||
Log.i(TAG, "Using new coverfragment");
|
||||
coverFragment = CoverFragment.newInstance(media);
|
||||
}
|
||||
currentlyShownFragment = coverFragment;
|
||||
break;
|
||||
case POS_DESCR:
|
||||
if (descriptionFragment == null) {
|
||||
descriptionFragment = ItemDescriptionFragment
|
||||
.newInstance(media, true);
|
||||
}
|
||||
currentlyShownFragment = descriptionFragment;
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
if (chapterFragment == null) {
|
||||
chapterFragment = new ListFragment() {
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v,
|
||||
int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
Chapter chapter = (Chapter) this
|
||||
.getListAdapter().getItem(position);
|
||||
controller.seekToChapter(chapter);
|
||||
}
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v,
|
||||
int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
Chapter chapter = (Chapter) this
|
||||
.getListAdapter().getItem(position);
|
||||
controller.seekToChapter(chapter);
|
||||
}
|
||||
|
||||
};
|
||||
chapterFragment.setListAdapter(new ChapterListAdapter(
|
||||
AudioplayerActivity.this, 0, media
|
||||
.getChapters(), media));
|
||||
}
|
||||
currentlyShownFragment = chapterFragment;
|
||||
break;
|
||||
}
|
||||
if (currentlyShownFragment != null) {
|
||||
currentlyShownPosition = pos;
|
||||
if (detachedFragments[pos] != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Reattaching fragment at position "
|
||||
+ pos);
|
||||
ft.attach(detachedFragments[pos]);
|
||||
} else {
|
||||
ft.add(R.id.contentView, currentlyShownFragment);
|
||||
}
|
||||
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||
ft.disallowAddToBackStack();
|
||||
ft.commit();
|
||||
updateNavButtonDrawable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
chapterFragment.setListAdapter(new ChapterListAdapter(
|
||||
AudioplayerActivity.this, 0, media
|
||||
.getChapters(), media));
|
||||
}
|
||||
currentlyShownFragment = chapterFragment;
|
||||
break;
|
||||
}
|
||||
if (currentlyShownFragment != null) {
|
||||
currentlyShownPosition = pos;
|
||||
if (detachedFragments[pos] != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Reattaching fragment at position "
|
||||
+ pos);
|
||||
ft.attach(detachedFragments[pos]);
|
||||
} else {
|
||||
ft.add(R.id.contentView, currentlyShownFragment);
|
||||
}
|
||||
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||
ft.disallowAddToBackStack();
|
||||
ft.commit();
|
||||
updateNavButtonDrawable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNavButtonDrawable() {
|
||||
TypedArray drawables = obtainStyledAttributes(new int[] {
|
||||
R.attr.navigation_shownotes, R.attr.navigation_chapters });
|
||||
final Playable media = controller.getMedia();
|
||||
if (butNavLeft != null && butNavRight != null && media != null) {
|
||||
switch (currentlyShownPosition) {
|
||||
case POS_COVER:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER);
|
||||
butNavLeft.setImageDrawable(drawables.getDrawable(0));
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(1));
|
||||
break;
|
||||
case POS_DESCR:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
|
||||
butNavLeft.post(new Runnable() {
|
||||
private void updateNavButtonDrawable() {
|
||||
TypedArray drawables = obtainStyledAttributes(new int[]{
|
||||
R.attr.navigation_shownotes, R.attr.navigation_chapters});
|
||||
final Playable media = controller.getMedia();
|
||||
if (butNavLeft != null && butNavRight != null && media != null) {
|
||||
switch (currentlyShownPosition) {
|
||||
case POS_COVER:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER);
|
||||
butNavLeft.setImageDrawable(drawables.getDrawable(0));
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(1));
|
||||
break;
|
||||
case POS_DESCR:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
|
||||
butNavLeft.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ImageLoader.getInstance().loadThumbnailBitmap(media,
|
||||
butNavLeft);
|
||||
}
|
||||
});
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(1));
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
|
||||
butNavLeft.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ImageLoader.getInstance().loadThumbnailBitmap(media,
|
||||
butNavLeft);
|
||||
}
|
||||
});
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(1));
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
butNavLeft.setScaleType(ScaleType.CENTER_CROP);
|
||||
butNavLeft.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ImageLoader.getInstance().loadThumbnailBitmap(media,
|
||||
butNavLeft);
|
||||
}
|
||||
});
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
ImageLoader.getInstance().loadThumbnailBitmap(media,
|
||||
butNavLeft);
|
||||
}
|
||||
});
|
||||
butNavRight.setImageDrawable(drawables.getDrawable(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupGUI() {
|
||||
super.setupGUI();
|
||||
resetFragmentView();
|
||||
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
|
||||
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
|
||||
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
|
||||
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
|
||||
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
|
||||
@Override
|
||||
protected void setupGUI() {
|
||||
super.setupGUI();
|
||||
resetFragmentView();
|
||||
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
|
||||
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
|
||||
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
|
||||
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
|
||||
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
|
||||
|
||||
butNavLeft.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentlyShownFragment == null
|
||||
|| currentlyShownPosition == POS_DESCR) {
|
||||
switchToFragment(POS_COVER);
|
||||
} else if (currentlyShownPosition == POS_COVER) {
|
||||
switchToFragment(POS_DESCR);
|
||||
} else if (currentlyShownPosition == POS_CHAPTERS) {
|
||||
switchToFragment(POS_COVER);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
butNavRight.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentlyShownPosition == POS_CHAPTERS) {
|
||||
switchToFragment(POS_DESCR);
|
||||
} else {
|
||||
switchToFragment(POS_CHAPTERS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
|
||||
butNavLeft.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (controller != null && controller.canSetPlaybackSpeed()) {
|
||||
String[] availableSpeeds = UserPreferences
|
||||
.getPlaybackSpeedArray();
|
||||
String currentSpeed = UserPreferences.getPlaybackSpeed();
|
||||
|
||||
// Provide initial value in case the speed list has changed
|
||||
// out from under us
|
||||
// and our current speed isn't in the new list
|
||||
String newSpeed;
|
||||
if (availableSpeeds.length > 0) {
|
||||
newSpeed = availableSpeeds[0];
|
||||
} else {
|
||||
newSpeed = "1.0";
|
||||
}
|
||||
|
||||
for (int i = 0; i < availableSpeeds.length; i++) {
|
||||
if (availableSpeeds[i].equals(currentSpeed)) {
|
||||
if (i == availableSpeeds.length - 1) {
|
||||
newSpeed = availableSpeeds[0];
|
||||
} else {
|
||||
newSpeed = availableSpeeds[i + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
UserPreferences.setPlaybackSpeed(newSpeed);
|
||||
controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
|
||||
if (currentlyShownFragment == null
|
||||
|| currentlyShownPosition == POS_DESCR) {
|
||||
switchToFragment(POS_COVER);
|
||||
} else if (currentlyShownPosition == POS_COVER) {
|
||||
switchToFragment(POS_DESCR);
|
||||
} else if (currentlyShownPosition == POS_CHAPTERS) {
|
||||
switchToFragment(POS_COVER);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
|
||||
butNavRight.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentlyShownPosition == POS_CHAPTERS) {
|
||||
switchToFragment(POS_DESCR);
|
||||
} else {
|
||||
switchToFragment(POS_CHAPTERS);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (controller != null && controller.canSetPlaybackSpeed()) {
|
||||
String[] availableSpeeds = UserPreferences
|
||||
.getPlaybackSpeedArray();
|
||||
String currentSpeed = UserPreferences.getPlaybackSpeed();
|
||||
|
||||
// Provide initial value in case the speed list has changed
|
||||
// out from under us
|
||||
// and our current speed isn't in the new list
|
||||
String newSpeed;
|
||||
if (availableSpeeds.length > 0) {
|
||||
newSpeed = availableSpeeds[0];
|
||||
} else {
|
||||
newSpeed = "1.0";
|
||||
}
|
||||
|
||||
for (int i = 0; i < availableSpeeds.length; i++) {
|
||||
if (availableSpeeds[i].equals(currentSpeed)) {
|
||||
if (i == availableSpeeds.length - 1) {
|
||||
newSpeed = availableSpeeds[0];
|
||||
} else {
|
||||
newSpeed = availableSpeeds[i + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
UserPreferences.setPlaybackSpeed(newSpeed);
|
||||
controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
VariableSpeedDialog.showDialog(AudioplayerActivity.this);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPlaybackSpeedChange() {
|
||||
super.onPlaybackSpeedChange();
|
||||
updateButPlaybackSpeed();
|
||||
}
|
||||
@Override
|
||||
protected void onPlaybackSpeedChange() {
|
||||
super.onPlaybackSpeedChange();
|
||||
updateButPlaybackSpeed();
|
||||
}
|
||||
|
||||
private void updateButPlaybackSpeed() {
|
||||
if (controller == null
|
||||
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
} else {
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
|
||||
}
|
||||
}
|
||||
private void updateButPlaybackSpeed() {
|
||||
if (controller == null
|
||||
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
|
||||
butPlaybackSpeed.setVisibility(View.GONE);
|
||||
} else {
|
||||
butPlaybackSpeed.setVisibility(View.VISIBLE);
|
||||
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPositionObserverUpdate() {
|
||||
super.onPositionObserverUpdate();
|
||||
notifyMediaPositionChanged();
|
||||
}
|
||||
@Override
|
||||
protected void onPositionObserverUpdate() {
|
||||
super.onPositionObserverUpdate();
|
||||
notifyMediaPositionChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadMediaInfo() {
|
||||
super.loadMediaInfo();
|
||||
final Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
txtvTitle.setText(media.getEpisodeTitle());
|
||||
txtvFeed.setText(media.getFeedTitle());
|
||||
if (media.getChapters() != null) {
|
||||
butNavRight.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
butNavRight.setVisibility(View.GONE);
|
||||
}
|
||||
@Override
|
||||
protected boolean loadMediaInfo() {
|
||||
if (!super.loadMediaInfo()) {
|
||||
return false;
|
||||
}
|
||||
final Playable media = controller.getMedia();
|
||||
if (media == null) {
|
||||
return false;
|
||||
}
|
||||
txtvTitle.setText(media.getEpisodeTitle());
|
||||
txtvFeed.setText(media.getFeedTitle());
|
||||
if (media.getChapters() != null) {
|
||||
butNavRight.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
butNavRight.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
if (currentlyShownPosition == -1) {
|
||||
if (!restoreFromPreferences()) {
|
||||
switchToFragment(POS_COVER);
|
||||
}
|
||||
}
|
||||
if (currentlyShownFragment instanceof AudioplayerContentFragment) {
|
||||
((AudioplayerContentFragment) currentlyShownFragment)
|
||||
.onDataSetChanged(media);
|
||||
}
|
||||
updateButPlaybackSpeed();
|
||||
}
|
||||
|
||||
public void notifyMediaPositionChanged() {
|
||||
if (chapterFragment != null) {
|
||||
ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment
|
||||
.getListAdapter();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
if (currentlyShownPosition == -1) {
|
||||
if (!restoreFromPreferences()) {
|
||||
switchToFragment(POS_COVER);
|
||||
}
|
||||
}
|
||||
if (currentlyShownFragment instanceof AudioplayerContentFragment) {
|
||||
((AudioplayerContentFragment) currentlyShownFragment)
|
||||
.onDataSetChanged(media);
|
||||
}
|
||||
updateButPlaybackSpeed();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"ReloadNotification received, switching to Videoplayer now");
|
||||
startActivity(new Intent(this, VideoplayerActivity.class));
|
||||
public void notifyMediaPositionChanged() {
|
||||
if (chapterFragment != null) {
|
||||
ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment
|
||||
.getListAdapter();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"ReloadNotification received, switching to Videoplayer now");
|
||||
finish();
|
||||
startActivity(new Intent(this, VideoplayerActivity.class));
|
||||
|
||||
@Override
|
||||
protected void onBufferStart() {
|
||||
postStatusMsg(R.string.player_buffering_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBufferEnd() {
|
||||
clearStatusMsg();
|
||||
}
|
||||
@Override
|
||||
protected void onBufferStart() {
|
||||
postStatusMsg(R.string.player_buffering_msg);
|
||||
}
|
||||
|
||||
public interface AudioplayerContentFragment {
|
||||
public void onDataSetChanged(Playable media);
|
||||
}
|
||||
@Override
|
||||
protected void onBufferEnd() {
|
||||
clearStatusMsg();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return R.layout.audioplayer_activity;
|
||||
}
|
||||
public interface AudioplayerContentFragment {
|
||||
public void onDataSetChanged(Playable media);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return R.layout.audioplayer_activity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import de.danoeh.antennapod.fragment.EpisodesFragment;
|
|||
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
|
||||
import de.danoeh.antennapod.fragment.FeedlistFragment;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.service.download.DownloadService;
|
||||
import de.danoeh.antennapod.storage.DBReader;
|
||||
import de.danoeh.antennapod.storage.DBTasks;
|
||||
|
|
|
@ -22,7 +22,7 @@ import de.danoeh.antennapod.R;
|
|||
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
|
||||
import de.danoeh.antennapod.dialog.TimeDialog;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.util.Converter;
|
||||
import de.danoeh.antennapod.util.ShareUtils;
|
||||
import de.danoeh.antennapod.util.StorageUtils;
|
||||
|
@ -106,8 +106,8 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
MediaplayerActivity.this.loadMediaInfo();
|
||||
public boolean loadMediaInfo() {
|
||||
return MediaplayerActivity.this.loadMediaInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -146,9 +146,13 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
protected void chooseTheme() {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
chooseTheme();
|
||||
super.onCreate(savedInstanceState);
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Creating Activity");
|
||||
|
@ -339,8 +343,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
}
|
||||
|
||||
/**
|
||||
* Called by 'handleStatus()' when the PlaybackService is in the
|
||||
* AWAITING_VIDEO_SURFACE state.
|
||||
* Called by 'handleStatus()' when the PlaybackService is waiting for
|
||||
* a video surface.
|
||||
*
|
||||
*/
|
||||
protected abstract void onAwaitingVideoSurface();
|
||||
|
||||
|
@ -380,7 +385,7 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
* to the PlaybackService to ensure that the activity has the right
|
||||
* FeedMedia object.
|
||||
*/
|
||||
protected void loadMediaInfo() {
|
||||
protected boolean loadMediaInfo() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Loading media info");
|
||||
Playable media = controller.getMedia();
|
||||
|
@ -395,7 +400,10 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
/ media.getDuration();
|
||||
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupGUI() {
|
||||
|
@ -415,9 +423,12 @@ public abstract class MediaplayerActivity extends ActionBarActivity
|
|||
|
||||
butPlay.setOnClickListener(controller.newOnPlayButtonClickListener());
|
||||
|
||||
butFF.setOnClickListener(controller.newOnFFButtonClickListener());
|
||||
|
||||
butRev.setOnClickListener(controller.newOnRevButtonClickListener());
|
||||
if (butFF != null) {
|
||||
butFF.setOnClickListener(controller.newOnFFButtonClickListener());
|
||||
}
|
||||
if (butRev != null) {
|
||||
butRev.setOnClickListener(controller.newOnRevButtonClickListener());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,289 +2,338 @@ package de.danoeh.antennapod.activity;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.*;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.feed.MediaType;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.PlayerStatus;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
import de.danoeh.antennapod.view.AspectRatioVideoView;
|
||||
|
||||
/** Activity for playing audio files. */
|
||||
public class VideoplayerActivity extends MediaplayerActivity implements
|
||||
SurfaceHolder.Callback {
|
||||
private static final String TAG = "VideoplayerActivity";
|
||||
/**
|
||||
* Activity for playing video files.
|
||||
*/
|
||||
public class VideoplayerActivity extends MediaplayerActivity {
|
||||
private static final String TAG = "VideoplayerActivity";
|
||||
|
||||
/** True if video controls are currently visible. */
|
||||
private boolean videoControlsShowing = true;
|
||||
private boolean videoSurfaceCreated = false;
|
||||
private VideoControlsHider videoControlsToggler;
|
||||
/**
|
||||
* True if video controls are currently visible.
|
||||
*/
|
||||
private boolean videoControlsShowing = true;
|
||||
private boolean videoSurfaceCreated = false;
|
||||
private VideoControlsHider videoControlsToggler;
|
||||
|
||||
private LinearLayout videoOverlay;
|
||||
private VideoView videoview;
|
||||
private ProgressBar progressIndicator;
|
||||
private LinearLayout videoOverlay;
|
||||
private AspectRatioVideoView videoview;
|
||||
private ProgressBar progressIndicator;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
||||
setTheme(UserPreferences.getTheme());
|
||||
@Override
|
||||
protected void chooseTheme() {
|
||||
setTheme(R.style.Theme_AntennaPod_Dark);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (getIntent().getAction() != null
|
||||
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
|
||||
Intent intent = getIntent();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Received VIEW intent: "
|
||||
+ intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
|
||||
MediaType.VIDEO);
|
||||
Intent launchIntent = new Intent(this, PlaybackService.class);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
|
||||
true);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
|
||||
true);
|
||||
startService(launchIntent);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (getIntent().getAction() != null
|
||||
&& getIntent().getAction().equals(Intent.ACTION_VIEW)) {
|
||||
Intent intent = getIntent();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Received VIEW intent: "
|
||||
+ intent.getData().getPath());
|
||||
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
|
||||
MediaType.VIDEO);
|
||||
Intent launchIntent = new Intent(this, PlaybackService.class);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
|
||||
true);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
|
||||
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
|
||||
true);
|
||||
startService(launchIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadMediaInfo() {
|
||||
super.loadMediaInfo();
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
|
||||
getSupportActionBar().setTitle(media.getFeedTitle());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected boolean loadMediaInfo() {
|
||||
if (!super.loadMediaInfo()) {
|
||||
return false;
|
||||
}
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
getSupportActionBar().setSubtitle(media.getEpisodeTitle());
|
||||
getSupportActionBar().setTitle(media.getFeedTitle());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupGUI() {
|
||||
super.setupGUI();
|
||||
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
|
||||
videoview = (VideoView) findViewById(R.id.videoview);
|
||||
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
|
||||
videoview.getHolder().addCallback(this);
|
||||
videoview.setOnTouchListener(onVideoviewTouched);
|
||||
return false;
|
||||
}
|
||||
|
||||
setupVideoControlsToggler();
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
@Override
|
||||
protected void setupGUI() {
|
||||
super.setupGUI();
|
||||
videoOverlay = (LinearLayout) findViewById(R.id.overlay);
|
||||
videoview = (AspectRatioVideoView) findViewById(R.id.videoview);
|
||||
progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
|
||||
videoview.getHolder().addCallback(surfaceHolderCallback);
|
||||
videoview.setOnTouchListener(onVideoviewTouched);
|
||||
|
||||
@Override
|
||||
protected void onAwaitingVideoSurface() {
|
||||
if (videoSurfaceCreated) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Videosurface already created, setting videosurface now");
|
||||
controller.setVideoSurface(videoview.getHolder());
|
||||
}
|
||||
}
|
||||
setupVideoControlsToggler();
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postStatusMsg(int resId) {
|
||||
if (resId == R.string.player_preparing_msg) {
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
@Override
|
||||
protected void onAwaitingVideoSurface() {
|
||||
if (videoSurfaceCreated) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"Videosurface already created, setting videosurface now");
|
||||
|
||||
}
|
||||
Pair<Integer, Integer> videoSize = controller.getVideoSize();
|
||||
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second);
|
||||
videoview.setVideoSize(videoSize.first, videoSize.second);
|
||||
} else {
|
||||
Log.e(TAG, "Could not determine video size");
|
||||
}
|
||||
controller.setVideoSurface(videoview.getHolder());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearStatusMsg() {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
@Override
|
||||
protected void postStatusMsg(int resId) {
|
||||
if (resId == R.string.player_preparing_msg) {
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
toggleVideoControlsVisibility();
|
||||
if (videoControlsShowing) {
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
@Override
|
||||
protected void clearStatusMsg() {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() {
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
void setupVideoControlsToggler() {
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
videoControlsToggler = new VideoControlsHider();
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
videoControlsToggler
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
videoControlsToggler.execute();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
toggleVideoControlsVisibility();
|
||||
if (videoControlsShowing) {
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
private void toggleVideoControlsVisibility() {
|
||||
if (videoControlsShowing) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls();
|
||||
} else {
|
||||
getSupportActionBar().show();
|
||||
showVideoControls();
|
||||
}
|
||||
videoControlsShowing = !videoControlsShowing;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Hides the videocontrols after a certain period of time. */
|
||||
public class VideoControlsHider extends AsyncTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
videoControlsToggler = null;
|
||||
}
|
||||
@SuppressLint("NewApi")
|
||||
void setupVideoControlsToggler() {
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
videoControlsToggler = new VideoControlsHider();
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
videoControlsToggler
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
videoControlsToggler.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
videoControlsToggler = null;
|
||||
}
|
||||
private void toggleVideoControlsVisibility() {
|
||||
if (videoControlsShowing) {
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls();
|
||||
} else {
|
||||
getSupportActionBar().show();
|
||||
showVideoControls();
|
||||
}
|
||||
videoControlsShowing = !videoControlsShowing;
|
||||
}
|
||||
|
||||
private static final int WAITING_INTERVALL = 5000;
|
||||
private static final String TAG = "VideoControlsToggler";
|
||||
/**
|
||||
* Hides the videocontrols after a certain period of time.
|
||||
*/
|
||||
public class VideoControlsHider extends AsyncTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
videoControlsToggler = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
if (videoControlsShowing) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Hiding video controls");
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls();
|
||||
videoControlsShowing = false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
videoControlsToggler = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
Thread.sleep(WAITING_INTERVALL);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
publishProgress();
|
||||
return null;
|
||||
}
|
||||
private static final int WAITING_INTERVALL = 5000;
|
||||
private static final String TAG = "VideoControlsToggler";
|
||||
|
||||
}
|
||||
@Override
|
||||
protected void onProgressUpdate(Void... values) {
|
||||
if (videoControlsShowing) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Hiding video controls");
|
||||
getSupportActionBar().hide();
|
||||
hideVideoControls();
|
||||
videoControlsShowing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
||||
int height) {
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
Thread.sleep(WAITING_INTERVALL);
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
publishProgress();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Videoview holder created");
|
||||
videoSurfaceCreated = true;
|
||||
if (controller.getStatus() == PlayerStatus.AWAITING_VIDEO_SURFACE) {
|
||||
if (controller.serviceAvailable()) {
|
||||
controller.setVideoSurface(holder);
|
||||
} else {
|
||||
Log.e(TAG,
|
||||
"Could'nt attach surface to mediaplayer - reference to service was null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
||||
int height) {
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Videosurface was destroyed");
|
||||
videoSurfaceCreated = false;
|
||||
controller.notifyVideoSurfaceAbandoned();
|
||||
}
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Videoview holder created");
|
||||
videoSurfaceCreated = true;
|
||||
if (controller.getStatus() == PlayerStatus.PLAYING) {
|
||||
if (controller.serviceAvailable()) {
|
||||
controller.setVideoSurface(holder);
|
||||
} else {
|
||||
Log.e(TAG,
|
||||
"Could'nt attach surface to mediaplayer - reference to service was null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"ReloadNotification received, switching to Audioplayer now");
|
||||
startActivity(new Intent(this, AudioplayerActivity.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
super.onStartTrackingTouch(seekBar);
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Videosurface was destroyed");
|
||||
videoSurfaceCreated = false;
|
||||
controller.notifyVideoSurfaceAbandoned();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBufferStart() {
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@Override
|
||||
protected void onReloadNotification(int notificationCode) {
|
||||
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG,
|
||||
"ReloadNotification received, switching to Audioplayer now");
|
||||
finish();
|
||||
startActivity(new Intent(this, AudioplayerActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBufferEnd() {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
super.onStartTrackingTouch(seekBar);
|
||||
if (videoControlsToggler != null) {
|
||||
videoControlsToggler.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void showVideoControls() {
|
||||
videoOverlay.setVisibility(View.VISIBLE);
|
||||
videoOverlay.startAnimation(AnimationUtils.loadAnimation(this,
|
||||
R.anim.fade_in));
|
||||
}
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
super.onStopTrackingTouch(seekBar);
|
||||
setupVideoControlsToggler();
|
||||
}
|
||||
|
||||
private void hideVideoControls() {
|
||||
videoOverlay.startAnimation(AnimationUtils.loadAnimation(this,
|
||||
R.anim.fade_out));
|
||||
videoOverlay.setVisibility(View.GONE);
|
||||
}
|
||||
@Override
|
||||
protected void onBufferStart() {
|
||||
progressIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return R.layout.videoplayer_activity;
|
||||
}
|
||||
@Override
|
||||
protected void onBufferEnd() {
|
||||
progressIndicator.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
private void showVideoControls() {
|
||||
videoOverlay.setVisibility(View.VISIBLE);
|
||||
butPlay.setVisibility(View.VISIBLE);
|
||||
final Animation animation = AnimationUtils.loadAnimation(this,
|
||||
R.anim.fade_in);
|
||||
if (animation != null) {
|
||||
videoOverlay.startAnimation(animation);
|
||||
butPlay.startAnimation(animation);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideVideoControls() {
|
||||
final Animation animation = AnimationUtils.loadAnimation(this,
|
||||
R.anim.fade_out);
|
||||
if (animation != null) {
|
||||
videoOverlay.startAnimation(animation);
|
||||
butPlay.startAnimation(animation);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
||||
}
|
||||
videoOverlay.setVisibility(View.GONE);
|
||||
butPlay.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentViewResourceId() {
|
||||
return R.layout.videoplayer_activity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import android.widget.TextView;
|
|||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.asynctask.ImageLoader;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.util.Converter;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
import de.danoeh.antennapod.util.playback.PlaybackController;
|
||||
|
@ -137,10 +137,12 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void loadMediaInfo() {
|
||||
public boolean loadMediaInfo() {
|
||||
ExternalPlayerFragment fragment = ExternalPlayerFragment.this;
|
||||
if (fragment != null) {
|
||||
fragment.loadMediaInfo();
|
||||
return fragment.loadMediaInfo();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,7 +211,7 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void loadMediaInfo() {
|
||||
private boolean loadMediaInfo() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Loading media info");
|
||||
if (controller.serviceAvailable()) {
|
||||
|
@ -230,13 +232,16 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
} else {
|
||||
butPlay.setVisibility(View.VISIBLE);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Log.w(TAG,
|
||||
"loadMediaInfo was called while the media object of playbackService was null!");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG,
|
||||
"loadMediaInfo was called while playbackService was null!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -319,7 +319,9 @@ public class UserPreferences implements
|
|||
PREF_PLAYBACK_SPEED_ARRAY, null));
|
||||
} else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
|
||||
pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
|
||||
}
|
||||
} else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) {
|
||||
pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setPlaybackSpeed(String speed) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.content.Intent;
|
|||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
|
||||
/** Receives media button events. */
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.service.PlayerWidgetService;
|
||||
import de.danoeh.antennapod.service.playback.PlayerWidgetService;
|
||||
|
||||
public class PlayerWidget extends AppWidgetProvider {
|
||||
private static final String TAG = "PlayerWidget";
|
||||
|
|
|
@ -0,0 +1,917 @@
|
|||
package de.danoeh.antennapod.service.playback;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.RemoteControlClient;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.SurfaceHolder;
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.feed.Chapter;
|
||||
import de.danoeh.antennapod.feed.MediaType;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.receiver.MediaButtonReceiver;
|
||||
import de.danoeh.antennapod.util.playback.AudioPlayer;
|
||||
import de.danoeh.antennapod.util.playback.IPlayer;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
import de.danoeh.antennapod.util.playback.VideoPlayer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Manages the MediaPlayer object of the PlaybackService.
|
||||
*/
|
||||
public class PlaybackServiceMediaPlayer {
|
||||
public static final String TAG = "PlaybackServiceMediaPlayer";
|
||||
|
||||
/**
|
||||
* Return value of some PSMP methods if the method call failed.
|
||||
*/
|
||||
public static final int INVALID_TIME = -1;
|
||||
|
||||
private final AudioManager audioManager;
|
||||
|
||||
private volatile PlayerStatus playerStatus;
|
||||
private volatile PlayerStatus statusBeforeSeeking;
|
||||
private volatile IPlayer mediaPlayer;
|
||||
private volatile Playable media;
|
||||
|
||||
private volatile boolean stream;
|
||||
private volatile MediaType mediaType;
|
||||
private volatile AtomicBoolean startWhenPrepared;
|
||||
private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
|
||||
private volatile Pair<Integer, Integer> videoSize;
|
||||
|
||||
/**
|
||||
* Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
|
||||
* have to wait until these operations have finished.
|
||||
*/
|
||||
private final ReentrantLock playerLock;
|
||||
|
||||
private final PSMPCallback callback;
|
||||
private final Context context;
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
public PlaybackServiceMediaPlayer(Context context, PSMPCallback callback) {
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("context = null");
|
||||
if (callback == null)
|
||||
throw new IllegalArgumentException("callback = null");
|
||||
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
this.playerLock = new ReentrantLock();
|
||||
this.startWhenPrepared = new AtomicBoolean(false);
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
mediaPlayer = null;
|
||||
statusBeforeSeeking = null;
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
mediaType = MediaType.UNKNOWN;
|
||||
playerStatus = PlayerStatus.STOPPED;
|
||||
videoSize = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
|
||||
* episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will
|
||||
* not do anything.
|
||||
* Whether playback starts immediately depends on the given parameters. See below for more details.
|
||||
* <p/>
|
||||
* States:
|
||||
* During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters.
|
||||
* <p/>
|
||||
* If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If
|
||||
* 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state.
|
||||
* <p/>
|
||||
* If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object
|
||||
* will enter the ERROR state.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*
|
||||
* @param playable The Playable object that is supposed to be played. This parameter must not be null.
|
||||
* @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via
|
||||
* getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by
|
||||
* the Android MediaPlayer via getStreamUrl.
|
||||
* @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the
|
||||
* episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared
|
||||
* for playback immediately (see 'prepareImmediately' parameter for more details)
|
||||
* @param prepareImmediately Set to true if the method should also prepare the episode for playback.
|
||||
*/
|
||||
public void playMediaObject(final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
|
||||
if (playable == null)
|
||||
throw new IllegalArgumentException("playable = null");
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Play media object.");
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
try {
|
||||
playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately);
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} finally {
|
||||
playerLock.unlock();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if
|
||||
* the given playable parameter is the same object as the currently playing media.
|
||||
* <p/>
|
||||
* This method requires the playerLock and is executed on the caller's thread.
|
||||
*
|
||||
* @see #playMediaObject(de.danoeh.antennapod.util.playback.Playable, boolean, boolean, boolean)
|
||||
*/
|
||||
private void playMediaObject(final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
|
||||
if (playable == null)
|
||||
throw new IllegalArgumentException("playable = null");
|
||||
if (!playerLock.isHeldByCurrentThread())
|
||||
throw new IllegalStateException("method requires playerLock");
|
||||
|
||||
|
||||
if (media != null) {
|
||||
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
|
||||
// episode is already playing -> ignore method call
|
||||
return;
|
||||
} else {
|
||||
// stop playback of this episode
|
||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PREPARED) {
|
||||
mediaPlayer.stop();
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||
}
|
||||
}
|
||||
|
||||
this.media = playable;
|
||||
this.stream = stream;
|
||||
this.mediaType = media.getMediaType();
|
||||
this.videoSize = null;
|
||||
createMediaPlayer();
|
||||
PlaybackServiceMediaPlayer.this.startWhenPrepared.set(startWhenPrepared);
|
||||
setPlayerStatus(PlayerStatus.INITIALIZING, media);
|
||||
try {
|
||||
media.loadMetadata();
|
||||
if (stream) {
|
||||
mediaPlayer.setDataSource(media.getStreamUrl());
|
||||
} else {
|
||||
mediaPlayer.setDataSource(media.getLocalMediaUrl());
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.INITIALIZED, media);
|
||||
|
||||
if (mediaType == MediaType.VIDEO) {
|
||||
VideoPlayer vp = (VideoPlayer) mediaPlayer;
|
||||
// vp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||
}
|
||||
|
||||
if (prepareImmediately) {
|
||||
setPlayerStatus(PlayerStatus.PREPARING, media);
|
||||
mediaPlayer.prepare();
|
||||
onPrepared(startWhenPrepared);
|
||||
}
|
||||
|
||||
} catch (Playable.PlayableException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state.
|
||||
* nothing will happen.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
public void resume() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
resumeSync();
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void resumeSync() {
|
||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
|
||||
int focusGained = audioManager.requestAudioFocus(
|
||||
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
|
||||
setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
|
||||
mediaPlayer.start();
|
||||
if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) {
|
||||
mediaPlayer.seekTo(media.getPosition());
|
||||
}
|
||||
|
||||
setPlayerStatus(PlayerStatus.PLAYING, media);
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 14) {
|
||||
RemoteControlClient remoteControlClient = callback.getRemoteControlClient();
|
||||
if (remoteControlClient != null) {
|
||||
audioManager
|
||||
.registerRemoteControlClient(remoteControlClient);
|
||||
}
|
||||
}
|
||||
audioManager
|
||||
.registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(),
|
||||
MediaButtonReceiver.class.getName()));
|
||||
media.onPlaybackStart();
|
||||
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.e(TAG, "Failed to request audio focus");
|
||||
}
|
||||
} else {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Call to resume() was ignored because current state of PSMP object is " + playerStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the current position and pauses playback. Note that, if audiofocus
|
||||
* is abandoned, the lockscreen controls will also disapear.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*
|
||||
* @param abandonFocus is true if the service should release audio focus
|
||||
* @param reinit is true if service should reinit after pausing if the media
|
||||
* file is being streamed
|
||||
*/
|
||||
public void pause(final boolean abandonFocus, final boolean reinit) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Pausing playback.");
|
||||
mediaPlayer.pause();
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
|
||||
if (abandonFocus) {
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
}
|
||||
if (stream && reinit) {
|
||||
reinit();
|
||||
}
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepared media player for playback if the service is in the INITALIZED
|
||||
* state.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
public void prepare() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
|
||||
if (playerStatus == PlayerStatus.INITIALIZED) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Preparing media player");
|
||||
setPlayerStatus(PlayerStatus.PREPARING, media);
|
||||
try {
|
||||
mediaPlayer.prepare();
|
||||
onPrepared(startWhenPrepared.get());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
setPlayerStatus(PlayerStatus.ERROR, null);
|
||||
}
|
||||
}
|
||||
playerLock.unlock();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after media player has been prepared. This method is executed on the caller's thread.
|
||||
*/
|
||||
void onPrepared(final boolean startWhenPrepared) {
|
||||
playerLock.lock();
|
||||
|
||||
if (playerStatus != PlayerStatus.PREPARING) {
|
||||
playerLock.unlock();
|
||||
throw new IllegalStateException("Player is not in PREPARING state");
|
||||
}
|
||||
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Resource prepared");
|
||||
|
||||
if (mediaType == MediaType.VIDEO) {
|
||||
VideoPlayer vp = (VideoPlayer) mediaPlayer;
|
||||
videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
|
||||
}
|
||||
|
||||
if (media.getPosition() > 0) {
|
||||
mediaPlayer.seekTo(media.getPosition());
|
||||
}
|
||||
|
||||
if (media.getDuration() == 0) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Setting duration of media");
|
||||
media.setDuration(mediaPlayer.getDuration());
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.PREPARED, media);
|
||||
|
||||
if (startWhenPrepared) {
|
||||
resumeSync();
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the media player and moves it into INITIALIZED state.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
public void reinit() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
|
||||
if (media != null) {
|
||||
playMediaObject(media, true, stream, startWhenPrepared.get(), false);
|
||||
} else if (mediaPlayer != null) {
|
||||
mediaPlayer.reset();
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Call to reinit was ignored: media and mediaPlayer were null");
|
||||
}
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
|
||||
* Invalid time values (< 0) will be ignored.
|
||||
* <p/>
|
||||
* This method is executed on the caller's thread.
|
||||
*/
|
||||
private void seekToSync(int t) {
|
||||
if (t < 0) {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Received invalid value for t");
|
||||
return;
|
||||
}
|
||||
playerLock.lock();
|
||||
|
||||
if (playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.PREPARED) {
|
||||
if (stream) {
|
||||
// statusBeforeSeeking = playerStatus;
|
||||
// setPlayerStatus(PlayerStatus.SEEKING, media);
|
||||
}
|
||||
mediaPlayer.seekTo(t);
|
||||
|
||||
} else if (playerStatus == PlayerStatus.INITIALIZED) {
|
||||
media.setPosition(t);
|
||||
startWhenPrepared.set(true);
|
||||
prepare();
|
||||
}
|
||||
playerLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
|
||||
* Invalid time values (< 0) will be ignored.
|
||||
* <p/>
|
||||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
public void seekTo(final int t) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
seekToSync(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek a specific position from the current position
|
||||
*
|
||||
* @param d offset from current position (positive or negative)
|
||||
*/
|
||||
public void seekDelta(final int d) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
int currentPosition = getPosition();
|
||||
if (currentPosition != INVALID_TIME) {
|
||||
seekToSync(currentPosition + d);
|
||||
} else {
|
||||
Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta");
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the start of the specified chapter.
|
||||
*/
|
||||
public void seekToChapter(Chapter c) {
|
||||
if (c == null)
|
||||
throw new IllegalArgumentException("c = null");
|
||||
seekTo((int) c.getStart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
|
||||
*/
|
||||
public int getDuration() {
|
||||
if (!playerLock.tryLock()) {
|
||||
return INVALID_TIME;
|
||||
}
|
||||
|
||||
int retVal = INVALID_TIME;
|
||||
if (playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.PREPARED) {
|
||||
retVal = mediaPlayer.getDuration();
|
||||
} else if (media != null && media.getDuration() > 0) {
|
||||
retVal = media.getDuration();
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the current media object or INVALID_TIME if the position could not be retrieved.
|
||||
*/
|
||||
public int getPosition() {
|
||||
if (!playerLock.tryLock()) {
|
||||
return INVALID_TIME;
|
||||
}
|
||||
|
||||
int retVal = INVALID_TIME;
|
||||
if (playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.PREPARED) {
|
||||
retVal = mediaPlayer.getCurrentPosition();
|
||||
} else if (media != null && media.getPosition() > 0) {
|
||||
retVal = media.getPosition();
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public boolean isStartWhenPrepared() {
|
||||
return startWhenPrepared.get();
|
||||
}
|
||||
|
||||
public void setStartWhenPrepared(boolean startWhenPrepared) {
|
||||
this.startWhenPrepared.set(startWhenPrepared);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the playback speed can be adjusted. This method can also return false if the PSMP object's
|
||||
* internal MediaPlayer cannot be accessed at the moment.
|
||||
*/
|
||||
public boolean canSetSpeed() {
|
||||
if (!playerLock.tryLock()) {
|
||||
return false;
|
||||
}
|
||||
boolean retVal = false;
|
||||
if (mediaPlayer != null && media != null && media.getMediaType() == MediaType.AUDIO) {
|
||||
retVal = (mediaPlayer).canSetSpeed();
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback speed.
|
||||
* This method is executed on the caller's thread.
|
||||
*/
|
||||
private void setSpeedSync(float speed) {
|
||||
playerLock.lock();
|
||||
if (media != null && media.getMediaType() == MediaType.AUDIO) {
|
||||
if (mediaPlayer.canSetSpeed()) {
|
||||
mediaPlayer.setPlaybackSpeed((float) speed);
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Playback speed was set to " + speed);
|
||||
callback.playbackSpeedChanged(speed);
|
||||
}
|
||||
}
|
||||
playerLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback speed.
|
||||
* This method is executed on an internal executor service.
|
||||
*/
|
||||
public void setSpeed(final float speed) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSpeedSync(speed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned.
|
||||
*/
|
||||
public float getPlaybackSpeed() {
|
||||
if (!playerLock.tryLock()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int retVal = 1;
|
||||
if (playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.PREPARED) {
|
||||
retVal = mediaPlayer.getCurrentPosition();
|
||||
} else if (media != null && media.getPosition() > 0) {
|
||||
retVal = media.getPosition();
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public MediaType getCurrentMediaType() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Releases internally used resources. This method should only be called when the object is not used anymore.
|
||||
*/
|
||||
public void shutdown() {
|
||||
executor.shutdown();
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void setVideoSurface(final SurfaceHolder surface) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.setDisplay(surface);
|
||||
}
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void resetVideoSurface() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Resetting video surface");
|
||||
mediaPlayer.setDisplay(null);
|
||||
reinit();
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return width and height of the currently playing video as a pair.
|
||||
*
|
||||
* @return Width and height as a Pair or null if the video size could not be determined. The method might still
|
||||
* return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return
|
||||
* invalid values.
|
||||
*/
|
||||
public Pair<Integer, Integer> getVideoSize() {
|
||||
if (!playerLock.tryLock()) {
|
||||
// use cached value if lock can't be aquired
|
||||
return videoSize;
|
||||
}
|
||||
Pair<Integer, Integer> res;
|
||||
if (mediaPlayer == null || playerStatus == PlayerStatus.ERROR || mediaType != MediaType.VIDEO) {
|
||||
res = null;
|
||||
} else {
|
||||
VideoPlayer vp = (VideoPlayer) mediaPlayer;
|
||||
videoSize = new Pair<Integer, Integer>(vp.getVideoWidth(), vp.getVideoHeight());
|
||||
res = videoSize;
|
||||
}
|
||||
playerLock.unlock();
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PSMInfo object that contains information about the current state of the PSMP object.
|
||||
*
|
||||
* @return The PSMPInfo object.
|
||||
*/
|
||||
public synchronized PSMPInfo getPSMPInfo() {
|
||||
return new PSMPInfo(playerStatus, media);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time
|
||||
* so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null).
|
||||
* <p/>
|
||||
* This method will notify the callback about the change of the player status (even if the new status is the same
|
||||
* as the old one).
|
||||
*
|
||||
* @param newStatus The new PlayerStatus. This must not be null.
|
||||
* @param newMedia The new playable object of the PSMP object. This can be null.
|
||||
*/
|
||||
private synchronized void setPlayerStatus(PlayerStatus newStatus, Playable newMedia) {
|
||||
if (newStatus == null)
|
||||
throw new IllegalArgumentException("newStatus = null");
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Setting player status to " + newStatus);
|
||||
|
||||
this.playerStatus = newStatus;
|
||||
this.media = newMedia;
|
||||
callback.statusChanged(new PSMPInfo(playerStatus, media));
|
||||
}
|
||||
|
||||
private IPlayer createMediaPlayer() {
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.release();
|
||||
}
|
||||
if (media == null || media.getMediaType() == MediaType.VIDEO) {
|
||||
mediaPlayer = new VideoPlayer();
|
||||
} else {
|
||||
mediaPlayer = new AudioPlayer(context);
|
||||
}
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
return setMediaPlayerListeners(mediaPlayer);
|
||||
}
|
||||
|
||||
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onAudioFocusChange(final int focusChange) {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Lost audio focus");
|
||||
pause(true, false);
|
||||
callback.shouldStop();
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_GAIN:
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Gained audio focus");
|
||||
if (pausedBecauseOfTransientAudiofocusLoss) // we paused => play now
|
||||
resume();
|
||||
else // we ducked => raise audio level back
|
||||
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_RAISE, 0);
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
if (!UserPreferences.shouldPauseForFocusLoss()) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Lost audio focus temporarily. Ducking...");
|
||||
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
|
||||
AudioManager.ADJUST_LOWER, 0);
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
} else {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Lost audio focus temporarily. Could duck, but won't, pausing...");
|
||||
pause(false, false);
|
||||
pausedBecauseOfTransientAudiofocusLoss = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Lost audio focus temporarily. Pausing...");
|
||||
pause(false, false);
|
||||
pausedBecauseOfTransientAudiofocusLoss = true;
|
||||
}
|
||||
}
|
||||
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public void endPlayback() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
|
||||
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
||||
}
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.reset();
|
||||
|
||||
}
|
||||
callback.endPlayback(true);
|
||||
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds information about a PSMP object.
|
||||
*/
|
||||
public class PSMPInfo {
|
||||
public PlayerStatus playerStatus;
|
||||
public Playable playable;
|
||||
|
||||
public PSMPInfo(PlayerStatus playerStatus, Playable playable) {
|
||||
this.playerStatus = playerStatus;
|
||||
this.playable = playable;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface PSMPCallback {
|
||||
public void statusChanged(PSMPInfo newInfo);
|
||||
|
||||
public void shouldStop();
|
||||
|
||||
public void playbackSpeedChanged(float s);
|
||||
|
||||
public void onBufferingUpdate(int percent);
|
||||
|
||||
public boolean onMediaPlayerInfo(int code);
|
||||
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra);
|
||||
|
||||
public boolean endPlayback(boolean playNextEpisode);
|
||||
|
||||
public RemoteControlClient getRemoteControlClient();
|
||||
}
|
||||
|
||||
private IPlayer setMediaPlayerListeners(IPlayer mp) {
|
||||
if (mp != null && media != null) {
|
||||
if (media.getMediaType() == MediaType.AUDIO) {
|
||||
((AudioPlayer) mp)
|
||||
.setOnCompletionListener(audioCompletionListener);
|
||||
((AudioPlayer) mp)
|
||||
.setOnSeekCompleteListener(audioSeekCompleteListener);
|
||||
((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
|
||||
((AudioPlayer) mp)
|
||||
.setOnBufferingUpdateListener(audioBufferingUpdateListener);
|
||||
((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
|
||||
} else {
|
||||
((VideoPlayer) mp)
|
||||
.setOnCompletionListener(videoCompletionListener);
|
||||
((VideoPlayer) mp)
|
||||
.setOnSeekCompleteListener(videoSeekCompleteListener);
|
||||
((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
|
||||
((VideoPlayer) mp)
|
||||
.setOnBufferingUpdateListener(videoBufferingUpdateListener);
|
||||
((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
|
||||
}
|
||||
}
|
||||
return mp;
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(com.aocate.media.MediaPlayer mp) {
|
||||
genericOnCompletion();
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(android.media.MediaPlayer mp) {
|
||||
genericOnCompletion();
|
||||
}
|
||||
};
|
||||
|
||||
private void genericOnCompletion() {
|
||||
endPlayback();
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
@Override
|
||||
public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
|
||||
int percent) {
|
||||
genericOnBufferingUpdate(percent);
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
@Override
|
||||
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
|
||||
genericOnBufferingUpdate(percent);
|
||||
}
|
||||
};
|
||||
|
||||
private void genericOnBufferingUpdate(int percent) {
|
||||
callback.onBufferingUpdate(percent);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericInfoListener(what);
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
|
||||
return genericInfoListener(what);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean genericInfoListener(int what) {
|
||||
return callback.onMediaPlayerInfo(what);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(com.aocate.media.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericOnError(mp, what, extra);
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
|
||||
return genericOnError(mp, what, extra);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean genericOnError(Object inObj, int what, int extra) {
|
||||
return callback.onMediaPlayerError(inObj, what, extra);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
|
||||
@Override
|
||||
public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
|
||||
genericSeekCompleteListener();
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
|
||||
@Override
|
||||
public void onSeekComplete(android.media.MediaPlayer mp) {
|
||||
genericSeekCompleteListener();
|
||||
}
|
||||
};
|
||||
|
||||
private final void genericSeekCompleteListener() {
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerLock.lock();
|
||||
if (playerStatus == PlayerStatus.SEEKING) {
|
||||
setPlayerStatus(statusBeforeSeeking, media);
|
||||
}
|
||||
playerLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
package de.danoeh.antennapod.service.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.feed.FeedItem;
|
||||
import de.danoeh.antennapod.storage.DBReader;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Manages the background tasks of PlaybackSerivce, i.e.
|
||||
* the sleep timer, the position saver, the widget updater and
|
||||
* the queue loader.
|
||||
* <p/>
|
||||
* The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
|
||||
* to notify the PlaybackService about updates from the running tasks.
|
||||
*/
|
||||
public class PlaybackServiceTaskManager {
|
||||
private static final String TAG = "PlaybackServiceTaskManager";
|
||||
|
||||
/**
|
||||
* Update interval of position saver in milliseconds.
|
||||
*/
|
||||
public static final int POSITION_SAVER_WAITING_INTERVAL = 5000;
|
||||
/**
|
||||
* Notification interval of widget updater in milliseconds.
|
||||
*/
|
||||
public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
|
||||
|
||||
private static final int SCHED_EX_POOL_SIZE = 2;
|
||||
private final ScheduledThreadPoolExecutor schedExecutor;
|
||||
|
||||
private ScheduledFuture positionSaverFuture;
|
||||
private ScheduledFuture widgetUpdaterFuture;
|
||||
private ScheduledFuture sleepTimerFuture;
|
||||
private volatile Future<List<FeedItem>> queueFuture;
|
||||
private volatile Future chapterLoaderFuture;
|
||||
|
||||
private SleepTimer sleepTimer;
|
||||
|
||||
private final Context context;
|
||||
private final PSTMCallback callback;
|
||||
|
||||
/**
|
||||
* Sets up a new PSTM. This method will also start the queue loader task.
|
||||
*
|
||||
* @param context
|
||||
* @param callback A PSTMCallback object for notifying the user about updates. Must not be null.
|
||||
*/
|
||||
public PlaybackServiceTaskManager(Context context, PSTMCallback callback) {
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("context must not be null");
|
||||
if (callback == null)
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
loadQueue();
|
||||
EventDistributor.getInstance().register(eventDistributorListener);
|
||||
}
|
||||
|
||||
private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
|
||||
cancelQueueLoader();
|
||||
loadQueue();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized boolean isQueueLoaderActive() {
|
||||
return queueFuture != null && !queueFuture.isDone();
|
||||
}
|
||||
|
||||
private synchronized void cancelQueueLoader() {
|
||||
if (isQueueLoaderActive()) {
|
||||
queueFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadQueue() {
|
||||
if (!isQueueLoaderActive()) {
|
||||
queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
|
||||
@Override
|
||||
public List<FeedItem> call() throws Exception {
|
||||
return DBReader.getQueue(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue if it is already loaded or null if it hasn't been loaded yet.
|
||||
* In order to wait until the queue has been loaded, use getQueue()
|
||||
*/
|
||||
public synchronized List<FeedItem> getQueueIfLoaded() {
|
||||
if (queueFuture.isDone()) {
|
||||
try {
|
||||
return queueFuture.get();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue or waits until the PSTM has loaded the queue from the database.
|
||||
*/
|
||||
public synchronized List<FeedItem> getQueue() throws InterruptedException {
|
||||
try {
|
||||
return queueFuture.get();
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the position saver task. If the position saver is already active, nothing will happen.
|
||||
*/
|
||||
public synchronized void startPositionSaver() {
|
||||
if (!isPositionSaverActive()) {
|
||||
Runnable positionSaver = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.positionSaverTick();
|
||||
}
|
||||
};
|
||||
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
|
||||
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the position saver is currently running.
|
||||
*/
|
||||
public synchronized boolean isPositionSaverActive() {
|
||||
return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the position saver. If the position saver is not running, nothing will happen.
|
||||
*/
|
||||
public synchronized void cancelPositionSaver() {
|
||||
if (isPositionSaverActive()) {
|
||||
positionSaverFuture.cancel(false);
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the widget updater task. If the widget updater is already active, nothing will happen.
|
||||
*/
|
||||
public synchronized void startWidgetUpdater() {
|
||||
if (!isWidgetUpdaterActive()) {
|
||||
Runnable widgetUpdater = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onWidgetUpdaterTick();
|
||||
}
|
||||
};
|
||||
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
|
||||
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
|
||||
* cancelled first.
|
||||
* After waitingTime has elapsed, onSleepTimerExpired() will be called.
|
||||
*
|
||||
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
|
||||
*/
|
||||
public synchronized void setSleepTimer(long waitingTime) {
|
||||
if (waitingTime <= 0)
|
||||
throw new IllegalArgumentException("waitingTime <= 0");
|
||||
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
|
||||
+ " milliseconds");
|
||||
if (isSleepTimerActive()) {
|
||||
sleepTimerFuture.cancel(true);
|
||||
}
|
||||
sleepTimer = new SleepTimer(waitingTime);
|
||||
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the sleep timer is currently active.
|
||||
*/
|
||||
public synchronized boolean isSleepTimerActive() {
|
||||
return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the sleep timer. If the sleep timer is not active, nothing will happen.
|
||||
*/
|
||||
public synchronized void disableSleepTimer() {
|
||||
if (isSleepTimerActive()) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Disabling sleep timer");
|
||||
sleepTimerFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current sleep timer time or 0 if the sleep timer is not active.
|
||||
*/
|
||||
public synchronized long getSleepTimerTimeLeft() {
|
||||
if (isSleepTimerActive()) {
|
||||
return sleepTimer.getWaitingTime();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the widget updater is currently running.
|
||||
*/
|
||||
public synchronized boolean isWidgetUpdaterActive() {
|
||||
return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the widget updater. If the widget updater is not running, nothing will happen.
|
||||
*/
|
||||
public synchronized void cancelWidgetUpdater() {
|
||||
if (isWidgetUpdaterActive()) {
|
||||
widgetUpdaterFuture.cancel(false);
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void cancelChapterLoader() {
|
||||
if (isChapterLoaderActive()) {
|
||||
chapterLoaderFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean isChapterLoaderActive() {
|
||||
return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
|
||||
* it will be cancelled first.
|
||||
* On completion, the callback's onChapterLoaded method will be called.
|
||||
*/
|
||||
public synchronized void startChapterLoader(final Playable media) {
|
||||
if (media == null)
|
||||
throw new IllegalArgumentException("media = null");
|
||||
|
||||
if (isChapterLoaderActive()) {
|
||||
cancelChapterLoader();
|
||||
}
|
||||
|
||||
Runnable chapterLoader = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Chapter loader started");
|
||||
if (media.getChapters() == null) {
|
||||
media.loadChapterMarks();
|
||||
if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
|
||||
callback.onChapterLoaded(media);
|
||||
}
|
||||
}
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Chapter loader stopped");
|
||||
}
|
||||
};
|
||||
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancels all tasks. The PSTM will be in the initial state after execution of this method.
|
||||
*/
|
||||
public synchronized void cancelAllTasks() {
|
||||
cancelPositionSaver();
|
||||
cancelWidgetUpdater();
|
||||
disableSleepTimer();
|
||||
cancelQueueLoader();
|
||||
cancelChapterLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after
|
||||
* execution of this method.
|
||||
*/
|
||||
public synchronized void shutdown() {
|
||||
EventDistributor.getInstance().unregister(eventDistributorListener);
|
||||
cancelAllTasks();
|
||||
schedExecutor.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for a given time and then pauses playback.
|
||||
*/
|
||||
private class SleepTimer implements Runnable {
|
||||
private static final String TAG = "SleepTimer";
|
||||
private static final long UPDATE_INTERVALL = 1000L;
|
||||
private volatile long waitingTime;
|
||||
private volatile boolean isWaiting;
|
||||
|
||||
public SleepTimer(long waitingTime) {
|
||||
super();
|
||||
this.waitingTime = waitingTime;
|
||||
isWaiting = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Starting");
|
||||
while (waitingTime > 0) {
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVALL);
|
||||
waitingTime -= UPDATE_INTERVALL;
|
||||
|
||||
if (waitingTime <= 0) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Waiting completed");
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
callback.onSleepTimerExpired();
|
||||
}
|
||||
postExecute();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Thread was interrupted while waiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
postExecute();
|
||||
}
|
||||
|
||||
protected void postExecute() {
|
||||
isWaiting = false;
|
||||
}
|
||||
|
||||
public long getWaitingTime() {
|
||||
return waitingTime;
|
||||
}
|
||||
|
||||
public boolean isWaiting() {
|
||||
return isWaiting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static interface PSTMCallback {
|
||||
void positionSaverTick();
|
||||
|
||||
void onSleepTimerExpired();
|
||||
|
||||
void onWidgetUpdaterTick();
|
||||
|
||||
void onChapterLoaded(Playable media);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package de.danoeh.antennapod.service;
|
||||
package de.danoeh.antennapod.service.playback;
|
||||
|
||||
public enum PlayerStatus {
|
||||
INDETERMINATE, // player is currently changing its state, listeners should wait until the player has left this state.
|
||||
ERROR,
|
||||
PREPARING,
|
||||
PAUSED,
|
||||
PLAYING,
|
||||
STOPPED,
|
||||
PREPARED,
|
||||
SEEKING,
|
||||
AWAITING_VIDEO_SURFACE, // player has been initialized and the media type to be played is a video.
|
||||
SEEKING,
|
||||
INITIALIZING, // playback service is loading the Playable's metadata
|
||||
INITIALIZED // playback service was started, data source of media player was set.
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package de.danoeh.antennapod.service;
|
||||
package de.danoeh.antennapod.service.playback;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
|
@ -72,9 +72,11 @@ public class PlayerWidgetService extends Service {
|
|||
}
|
||||
|
||||
private void updateViews() {
|
||||
if (playbackService == null) {
|
||||
return;
|
||||
}
|
||||
isUpdating = true;
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Updating widget views");
|
||||
|
||||
ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
|
||||
AppWidgetManager manager = AppWidgetManager.getInstance(this);
|
||||
RemoteViews views = new RemoteViews(getPackageName(),
|
||||
|
@ -83,8 +85,8 @@ public class PlayerWidgetService extends Service {
|
|||
PlaybackService.getPlayerActivityIntent(this), 0);
|
||||
|
||||
views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
|
||||
if (playbackService != null && playbackService.getMedia() != null) {
|
||||
Playable media = playbackService.getMedia();
|
||||
final Playable media = playbackService.getPlayable();
|
||||
if (playbackService != null && media != null) {
|
||||
PlayerStatus status = playbackService.getStatus();
|
||||
|
||||
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
|
||||
|
@ -101,8 +103,6 @@ public class PlayerWidgetService extends Service {
|
|||
views.setOnClickPendingIntent(R.id.butPlay,
|
||||
createMediaButtonIntent());
|
||||
} else {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "No media playing. Displaying defaultt views");
|
||||
views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
|
||||
views.setTextViewText(R.id.txtvTitle,
|
||||
this.getString(R.string.no_media_playing_label));
|
||||
|
@ -126,8 +126,8 @@ public class PlayerWidgetService extends Service {
|
|||
}
|
||||
|
||||
private String getProgressString(PlaybackService ps) {
|
||||
int position = ps.getCurrentPositionSafe();
|
||||
int duration = ps.getDurationSafe();
|
||||
int position = ps.getCurrentPosition();
|
||||
int duration = ps.getDuration();
|
||||
if (position != PlaybackService.INVALID_TIME
|
||||
&& duration != PlaybackService.INVALID_TIME) {
|
||||
return Converter.getDurationStringLong(position) + " / "
|
|
@ -8,7 +8,7 @@ import de.danoeh.antennapod.AppConfig;
|
|||
import de.danoeh.antennapod.feed.*;
|
||||
import de.danoeh.antennapod.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.service.GpodnetSyncService;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.util.DownloadError;
|
||||
import de.danoeh.antennapod.util.NetworkUtils;
|
||||
|
|
|
@ -20,8 +20,7 @@ import de.danoeh.antennapod.AppConfig;
|
|||
import de.danoeh.antennapod.feed.*;
|
||||
import de.danoeh.antennapod.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.service.GpodnetSyncService;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.service.download.DownloadStatus;
|
||||
import de.danoeh.antennapod.util.QueueAccess;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import de.danoeh.antennapod.AppConfig;
|
|||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
|
||||
import de.danoeh.antennapod.feed.FeedItem;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.storage.DBTasks;
|
||||
import de.danoeh.antennapod.storage.DBWriter;
|
||||
import de.danoeh.antennapod.storage.DownloadRequestException;
|
||||
|
@ -15,8 +15,6 @@ import de.danoeh.antennapod.storage.DownloadRequester;
|
|||
import de.danoeh.antennapod.util.QueueAccess;
|
||||
import de.danoeh.antennapod.util.ShareUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Handles interactions with the FeedItemMenu. */
|
||||
public class FeedItemMenuHandler {
|
||||
private FeedItemMenuHandler() {
|
||||
|
|
|
@ -27,4 +27,9 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
|
|||
throw new UnsupportedOperationException("Setting display not supported in Audio Player");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoScalingMode(int mode) {
|
||||
throw new UnsupportedOperationException("Setting scaling mode is not supported in Audio Player");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,4 +61,6 @@ public interface IPlayer {
|
|||
void start();
|
||||
|
||||
void stop();
|
||||
|
||||
public void setVideoScalingMode(int mode);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,14 @@
|
|||
package de.danoeh.antennapod.util.playback;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.*;
|
||||
import android.content.res.TypedArray;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
@ -30,13 +19,17 @@ import de.danoeh.antennapod.AppConfig;
|
|||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.feed.Chapter;
|
||||
import de.danoeh.antennapod.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.feed.MediaType;
|
||||
import de.danoeh.antennapod.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.service.PlaybackService;
|
||||
import de.danoeh.antennapod.service.PlayerStatus;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackServiceMediaPlayer;
|
||||
import de.danoeh.antennapod.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.storage.DBTasks;
|
||||
import de.danoeh.antennapod.util.Converter;
|
||||
import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Communicates with the playback service. GUI classes should use this class to
|
||||
* control playback instead of communicating with the PlaybackService directly.
|
||||
|
@ -44,10 +37,10 @@ import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
|
|||
public abstract class PlaybackController {
|
||||
private static final String TAG = "PlaybackController";
|
||||
|
||||
public static final int DEFAULT_SEEK_DELTA = 30000;
|
||||
public static final int INVALID_TIME = -1;
|
||||
public static final int DEFAULT_SEEK_DELTA = 30000;
|
||||
public static final int INVALID_TIME = -1;
|
||||
|
||||
private Activity activity;
|
||||
private final Activity activity;
|
||||
|
||||
private PlaybackService playbackService;
|
||||
private Playable media;
|
||||
|
@ -69,6 +62,8 @@ public abstract class PlaybackController {
|
|||
private boolean reinitOnPause;
|
||||
|
||||
public PlaybackController(Activity activity, boolean reinitOnPause) {
|
||||
if (activity == null)
|
||||
throw new IllegalArgumentException("activity = null");
|
||||
this.activity = activity;
|
||||
this.reinitOnPause = reinitOnPause;
|
||||
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE,
|
||||
|
@ -157,9 +152,6 @@ public abstract class PlaybackController {
|
|||
*/
|
||||
public void pause() {
|
||||
mediaInfoLoaded = false;
|
||||
if (playbackService != null && playbackService.isPlayingVideo()) {
|
||||
playbackService.pause(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,6 +173,7 @@ public abstract class PlaybackController {
|
|||
boolean bound = false;
|
||||
if (!PlaybackService.isRunning) {
|
||||
if (serviceIntent != null) {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Calling start service");
|
||||
activity.startService(serviceIntent);
|
||||
bound = activity.bindService(serviceIntent, mConnection, 0);
|
||||
} else {
|
||||
|
@ -297,7 +290,9 @@ public abstract class PlaybackController {
|
|||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Received statusUpdate Intent.");
|
||||
if (isConnectedToPlaybackService()) {
|
||||
status = playbackService.getStatus();
|
||||
PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo();
|
||||
status = info.playerStatus;
|
||||
media = info.playable;
|
||||
handleStatus();
|
||||
} else {
|
||||
Log.w(TAG,
|
||||
|
@ -328,10 +323,9 @@ public abstract class PlaybackController {
|
|||
case PlaybackService.NOTIFICATION_TYPE_RELOAD:
|
||||
cancelPositionObserver();
|
||||
mediaInfoLoaded = false;
|
||||
queryService();
|
||||
onReloadNotification(intent.getIntExtra(
|
||||
PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
|
||||
queryService();
|
||||
|
||||
break;
|
||||
case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
|
||||
onSleepTimerUpdate();
|
||||
|
@ -401,16 +395,24 @@ public abstract class PlaybackController {
|
|||
* should be used to update the GUI or start/cancel background threads.
|
||||
*/
|
||||
private void handleStatus() {
|
||||
TypedArray res = activity.obtainStyledAttributes(new int[]{
|
||||
R.attr.av_play, R.attr.av_pause});
|
||||
final int playResource = res.getResourceId(0, R.drawable.av_play);
|
||||
final int pauseResource = res.getResourceId(1, R.drawable.av_pause);
|
||||
res.recycle();
|
||||
final int playResource;
|
||||
final int pauseResource;
|
||||
if (PlaybackService.getCurrentMediaType() == MediaType.AUDIO) {
|
||||
TypedArray res = activity.obtainStyledAttributes(new int[]{
|
||||
R.attr.av_play, R.attr.av_pause});
|
||||
playResource = res.getResourceId(0, R.drawable.av_play);
|
||||
pauseResource = res.getResourceId(1, R.drawable.av_pause);
|
||||
res.recycle();
|
||||
} else {
|
||||
playResource = R.drawable.ic_action_play_over_video;
|
||||
pauseResource = R.drawable.ic_action_pause_over_video;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
|
||||
case ERROR:
|
||||
postStatusMsg(R.string.player_error_msg);
|
||||
handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN);
|
||||
break;
|
||||
case PAUSED:
|
||||
clearStatusMsg();
|
||||
|
@ -421,6 +423,9 @@ public abstract class PlaybackController {
|
|||
case PLAYING:
|
||||
clearStatusMsg();
|
||||
checkMediaInfoLoaded();
|
||||
if (PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
|
||||
onAwaitingVideoSurface();
|
||||
}
|
||||
setupPositionObserver();
|
||||
updatePlayButtonAppearance(pauseResource);
|
||||
break;
|
||||
|
@ -446,9 +451,6 @@ public abstract class PlaybackController {
|
|||
case SEEKING:
|
||||
postStatusMsg(R.string.player_seeking_msg);
|
||||
break;
|
||||
case AWAITING_VIDEO_SURFACE:
|
||||
onAwaitingVideoSurface();
|
||||
break;
|
||||
case INITIALIZED:
|
||||
checkMediaInfoLoaded();
|
||||
clearStatusMsg();
|
||||
|
@ -458,10 +460,7 @@ public abstract class PlaybackController {
|
|||
}
|
||||
|
||||
private void checkMediaInfoLoaded() {
|
||||
if (!mediaInfoLoaded) {
|
||||
loadMediaInfo();
|
||||
}
|
||||
mediaInfoLoaded = true;
|
||||
mediaInfoLoaded = (mediaInfoLoaded || loadMediaInfo());
|
||||
}
|
||||
|
||||
private void updatePlayButtonAppearance(int resource) {
|
||||
|
@ -475,7 +474,7 @@ public abstract class PlaybackController {
|
|||
|
||||
public abstract void clearStatusMsg();
|
||||
|
||||
public abstract void loadMediaInfo();
|
||||
public abstract boolean loadMediaInfo();
|
||||
|
||||
public abstract void onAwaitingVideoSurface();
|
||||
|
||||
|
@ -488,7 +487,8 @@ public abstract class PlaybackController {
|
|||
Log.d(TAG, "Querying service info");
|
||||
if (playbackService != null) {
|
||||
status = playbackService.getStatus();
|
||||
media = playbackService.getMedia();
|
||||
media = playbackService.getPlayable();
|
||||
/*
|
||||
if (media == null) {
|
||||
Log.w(TAG,
|
||||
"PlaybackService has no media object. Trying to restore last played media.");
|
||||
|
@ -497,6 +497,7 @@ public abstract class PlaybackController {
|
|||
activity.startService(serviceIntent);
|
||||
}
|
||||
}
|
||||
*/
|
||||
onServiceQueried();
|
||||
|
||||
setupGUI();
|
||||
|
@ -517,7 +518,7 @@ public abstract class PlaybackController {
|
|||
*/
|
||||
public float onSeekBarProgressChanged(SeekBar seekBar, int progress,
|
||||
boolean fromUser, TextView txtvPosition) {
|
||||
if (fromUser && playbackService != null) {
|
||||
if (fromUser && playbackService != null && media != null) {
|
||||
float prog = progress / ((float) seekBar.getMax());
|
||||
int duration = media.getDuration();
|
||||
txtvPosition.setText(Converter
|
||||
|
@ -541,7 +542,7 @@ public abstract class PlaybackController {
|
|||
*/
|
||||
public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
|
||||
if (playbackService != null) {
|
||||
playbackService.seek((int) (prog * media.getDuration()));
|
||||
playbackService.seekTo((int) (prog * media.getDuration()));
|
||||
setupPositionObserver();
|
||||
}
|
||||
}
|
||||
|
@ -557,7 +558,7 @@ public abstract class PlaybackController {
|
|||
break;
|
||||
case PAUSED:
|
||||
case PREPARED:
|
||||
playbackService.play();
|
||||
playbackService.resume();
|
||||
break;
|
||||
case PREPARING:
|
||||
playbackService.setStartWhenPrepared(!playbackService
|
||||
|
@ -609,7 +610,7 @@ public abstract class PlaybackController {
|
|||
|
||||
public int getPosition() {
|
||||
if (playbackService != null) {
|
||||
return playbackService.getCurrentPositionSafe();
|
||||
return playbackService.getCurrentPosition();
|
||||
} else {
|
||||
return PlaybackService.INVALID_TIME;
|
||||
}
|
||||
|
@ -617,7 +618,7 @@ public abstract class PlaybackController {
|
|||
|
||||
public int getDuration() {
|
||||
if (playbackService != null) {
|
||||
return playbackService.getDurationSafe();
|
||||
return playbackService.getDuration();
|
||||
} else {
|
||||
return PlaybackService.INVALID_TIME;
|
||||
}
|
||||
|
@ -675,27 +676,35 @@ public abstract class PlaybackController {
|
|||
return playbackService != null && playbackService.canSetSpeed();
|
||||
}
|
||||
|
||||
public void setPlaybackSpeed(float speed) {
|
||||
if (playbackService != null) {
|
||||
playbackService.setSpeed(speed);
|
||||
}
|
||||
}
|
||||
|
||||
public float getCurrentPlaybackSpeedMultiplier() {
|
||||
if (canSetPlaybackSpeed()) {
|
||||
return playbackService.getCurrentPlaybackSpeed();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
public void setPlaybackSpeed(float speed) {
|
||||
if (playbackService != null) {
|
||||
playbackService.setSpeed(speed);
|
||||
}
|
||||
}
|
||||
|
||||
public float getCurrentPlaybackSpeedMultiplier() {
|
||||
if (canSetPlaybackSpeed()) {
|
||||
return playbackService.getCurrentPlaybackSpeed();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlayingVideo() {
|
||||
if (playbackService != null) {
|
||||
return PlaybackService.isPlayingVideo();
|
||||
return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Pair<Integer, Integer> getVideoSize() {
|
||||
if (playbackService != null) {
|
||||
return playbackService.getVideoSize();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if PlaybackController can communicate with the playback
|
||||
|
@ -716,7 +725,7 @@ public abstract class PlaybackController {
|
|||
*/
|
||||
public void reinitServiceIfPaused() {
|
||||
if (playbackService != null
|
||||
&& playbackService.isShouldStream()
|
||||
&& playbackService.isStreaming()
|
||||
&& (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
|
||||
.getStatus() == PlayerStatus.PREPARING && playbackService
|
||||
.isStartWhenPrepared() == false))) {
|
||||
|
@ -733,8 +742,7 @@ public abstract class PlaybackController {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
if (playbackService != null && playbackService.getPlayer() != null
|
||||
&& playbackService.getPlayer().isPlaying()) {
|
||||
if (playbackService != null && playbackService.getStatus() == PlayerStatus.PLAYING) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -59,4 +59,9 @@ public class VideoPlayer extends MediaPlayer implements IPlayer {
|
|||
Log.e(TAG, "Setting playback speed unsupported in video player");
|
||||
throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoScalingMode(int mode) {
|
||||
super.setVideoScalingMode(mode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package de.danoeh.antennapod.view;
|
||||
|
||||
/*
|
||||
* Copyright (C) Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.VideoView;
|
||||
|
||||
public class AspectRatioVideoView extends VideoView {
|
||||
|
||||
|
||||
private int mVideoWidth;
|
||||
private int mVideoHeight;
|
||||
|
||||
public AspectRatioVideoView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AspectRatioVideoView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AspectRatioVideoView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
mVideoWidth = 0;
|
||||
mVideoHeight = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (mVideoWidth <= 0 || mVideoHeight <= 0) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
return;
|
||||
}
|
||||
|
||||
float heightRatio = (float) mVideoHeight / (float) getHeight();
|
||||
float widthRatio = (float) mVideoWidth / (float) getWidth();
|
||||
|
||||
int scaledHeight;
|
||||
int scaledWidth;
|
||||
|
||||
if (heightRatio > widthRatio) {
|
||||
scaledHeight = (int) Math.ceil((float) mVideoHeight
|
||||
/ heightRatio);
|
||||
scaledWidth = (int) Math.ceil((float) mVideoWidth
|
||||
/ heightRatio);
|
||||
} else {
|
||||
scaledHeight = (int) Math.ceil((float) mVideoHeight
|
||||
/ widthRatio);
|
||||
scaledWidth = (int) Math.ceil((float) mVideoWidth
|
||||
/ widthRatio);
|
||||
}
|
||||
|
||||
setMeasuredDimension(scaledWidth, scaledHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source code originally from:
|
||||
* http://clseto.mysinablog.com/index.php?op=ViewArticle&articleId=2992625
|
||||
*
|
||||
* @param videoWidth
|
||||
* @param videoHeight
|
||||
*/
|
||||
public void setVideoSize(int videoWidth, int videoHeight) {
|
||||
// Set the new video size
|
||||
mVideoWidth = videoWidth;
|
||||
mVideoHeight = videoHeight;
|
||||
|
||||
/**
|
||||
* If this isn't set the video is stretched across the
|
||||
* SurfaceHolders display surface (i.e. the SurfaceHolder
|
||||
* as the same size and the video is drawn to fit this
|
||||
* display area). We want the size to be the video size
|
||||
* and allow the aspectratio to handle how the surface is shown
|
||||
*/
|
||||
getHolder().setFixedSize(videoWidth, videoHeight);
|
||||
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
package instrumentationTest.de.test.antennapod.service.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import de.danoeh.antennapod.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.feed.Feed;
|
||||
import de.danoeh.antennapod.feed.FeedItem;
|
||||
import de.danoeh.antennapod.service.playback.PlaybackServiceTaskManager;
|
||||
import de.danoeh.antennapod.storage.PodDBAdapter;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Test class for PlaybackServiceTaskManager
|
||||
*/
|
||||
public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
assertTrue(PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
final Context context = getInstrumentation().getTargetContext();
|
||||
context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
|
||||
// make sure database is created
|
||||
PodDBAdapter adapter = new PodDBAdapter(context);
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
}
|
||||
|
||||
public void testInit() {
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(getInstrumentation().getTargetContext(), defaultPSTM);
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
private List<FeedItem> writeTestQueue(String pref) {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int NUM_ITEMS = 10;
|
||||
Feed f = new Feed(0, new Date(), "title", "link", "d", null, null, null, null, "id", null, "null", "url", false);
|
||||
f.setItems(new ArrayList<FeedItem>());
|
||||
for (int i = 0; i < NUM_ITEMS; i++) {
|
||||
f.getItems().add(new FeedItem(0, pref + i, pref + i, "link", new Date(), true, f));
|
||||
}
|
||||
PodDBAdapter adapter = new PodDBAdapter(c);
|
||||
adapter.open();
|
||||
adapter.setCompleteFeed(f);
|
||||
adapter.setQueue(f.getItems());
|
||||
adapter.close();
|
||||
|
||||
for (FeedItem item : f.getItems()) {
|
||||
assertTrue(item.getId() != 0);
|
||||
}
|
||||
return f.getItems();
|
||||
}
|
||||
|
||||
public void testGetQueueWriteBeforeCreation() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
List<FeedItem> queue = writeTestQueue("a");
|
||||
assertNotNull(queue);
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
List<FeedItem> testQueue = pstm.getQueue();
|
||||
assertNotNull(testQueue);
|
||||
assertTrue(queue.size() == testQueue.size());
|
||||
for (int i = 0; i < queue.size(); i++) {
|
||||
assertTrue(queue.get(i).getId() == testQueue.get(i).getId());
|
||||
}
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testGetQueueWriteAfterCreation() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
List<FeedItem> testQueue = pstm.getQueue();
|
||||
assertNotNull(testQueue);
|
||||
assertTrue(testQueue.isEmpty());
|
||||
|
||||
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
EventDistributor.EventListener queueListener = new EventDistributor.EventListener() {
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
};
|
||||
EventDistributor.getInstance().register(queueListener);
|
||||
List<FeedItem> queue = writeTestQueue("a");
|
||||
EventDistributor.getInstance().sendQueueUpdateBroadcast();
|
||||
countDownLatch.await(5000, TimeUnit.MILLISECONDS);
|
||||
|
||||
assertNotNull(queue);
|
||||
testQueue = pstm.getQueue();
|
||||
assertNotNull(testQueue);
|
||||
assertTrue(queue.size() == testQueue.size());
|
||||
for (int i = 0; i < queue.size(); i++) {
|
||||
assertTrue(queue.get(i).getId() == testQueue.get(i).getId());
|
||||
}
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testStartPositionSaver() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int NUM_COUNTDOWNS = 2;
|
||||
final int TIMEOUT = 3 * PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(NUM_COUNTDOWNS);
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerExpired() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChapterLoaded(Playable media) {
|
||||
|
||||
}
|
||||
});
|
||||
pstm.startPositionSaver();
|
||||
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testIsPositionSaverActive() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.startPositionSaver();
|
||||
assertTrue(pstm.isPositionSaverActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testCancelPositionSaver() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.startPositionSaver();
|
||||
pstm.cancelPositionSaver();
|
||||
assertFalse(pstm.isPositionSaverActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testStartWidgetUpdater() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int NUM_COUNTDOWNS = 2;
|
||||
final int TIMEOUT = 3 * PlaybackServiceTaskManager.WIDGET_UPDATER_NOTIFICATION_INTERVAL;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(NUM_COUNTDOWNS);
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerExpired() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChapterLoaded(Playable media) {
|
||||
|
||||
}
|
||||
});
|
||||
pstm.startWidgetUpdater();
|
||||
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testIsWidgetUpdaterActive() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.startWidgetUpdater();
|
||||
assertTrue(pstm.isWidgetUpdaterActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testCancelWidgetUpdater() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.startWidgetUpdater();
|
||||
pstm.cancelWidgetUpdater();
|
||||
assertFalse(pstm.isWidgetUpdaterActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testCancelAllTasksNoTasksStarted() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.cancelAllTasks();
|
||||
assertFalse(pstm.isPositionSaverActive());
|
||||
assertFalse(pstm.isWidgetUpdaterActive());
|
||||
assertFalse(pstm.isSleepTimerActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testCancelAllTasksAllTasksStarted() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.startWidgetUpdater();
|
||||
pstm.startPositionSaver();
|
||||
pstm.setSleepTimer(100000);
|
||||
pstm.cancelAllTasks();
|
||||
assertFalse(pstm.isPositionSaverActive());
|
||||
assertFalse(pstm.isWidgetUpdaterActive());
|
||||
assertFalse(pstm.isSleepTimerActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testSetSleepTimer() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final long TIME = 2000;
|
||||
final long TIMEOUT = 2 * TIME;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerExpired() {
|
||||
if (countDownLatch.getCount() == 0) {
|
||||
fail();
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChapterLoaded(Playable media) {
|
||||
|
||||
}
|
||||
});
|
||||
pstm.setSleepTimer(TIME);
|
||||
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testDisableSleepTimer() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final long TIME = 1000;
|
||||
final long TIMEOUT = 2 * TIME;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerExpired() {
|
||||
fail("Sleeptimer expired");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChapterLoaded(Playable media) {
|
||||
|
||||
}
|
||||
});
|
||||
pstm.setSleepTimer(TIME);
|
||||
pstm.disableSleepTimer();
|
||||
assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testIsSleepTimerActivePositive() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.setSleepTimer(10000);
|
||||
assertTrue(pstm.isSleepTimerActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
public void testIsSleepTimerActiveNegative() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
|
||||
pstm.setSleepTimer(10000);
|
||||
pstm.disableSleepTimer();
|
||||
assertFalse(pstm.isSleepTimerActive());
|
||||
pstm.shutdown();
|
||||
}
|
||||
|
||||
private final PlaybackServiceTaskManager.PSTMCallback defaultPSTM = new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSleepTimerExpired() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWidgetUpdaterTick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChapterLoaded(Playable media) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|