Merge branch 'playbackservice_rewrite' into develop

This commit is contained in:
daniel oeh 2014-02-11 18:58:08 +01:00
commit 5707d014d0
35 changed files with 4811 additions and 2562 deletions

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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();
}
});
}
}

View File

@ -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);
}
}

View File

@ -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.
}

View File

@ -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) + " / "

View File

@ -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;

View File

@ -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;

View File

@ -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() {

View File

@ -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");
}
}

View File

@ -61,4 +61,6 @@ public interface IPlayer {
void start();
void stop();
public void setVideoScalingMode(int mode);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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) {
}
};
}