Merge branch 'playback-speed' into develop

Conflicts:
	proguard.cfg
	src/de/danoeh/antennapod/storage/DBTasks.java
This commit is contained in:
daniel oeh 2013-08-25 14:06:41 +02:00
commit b1911e5ff7
23 changed files with 1002 additions and 134 deletions

View File

@ -12,6 +12,17 @@ repositories {
mavenCentral()
}
dependencies {
def libsdir = new File('libs');
if (!libsdir.exists()) {
println "Creating libs directory"
libsdir.mkdir()
}
def prestoLib = new File('libs/presto_client-0.8.5.jar')
if (!prestoLib.exists()) {
println "Downloading presto library into libs folder"
new URL('http://www.aocate.com/presto/client/presto_client-0.8.5.jar').withInputStream{ i -> prestoLib.withOutputStream{ it << i }}
}
compile 'com.android.support:appcompat-v7:18.0.+'
compile 'org.apache.commons:commons-lang3:3.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.4') {
@ -22,6 +33,7 @@ dependencies {
compile 'commons-io:commons-io:2.4'
compile 'com.nineoldandroids:library:2.4.0'
compile project(':submodules:dslv:library')
compile files('libs/presto_client-0.8.5.jar')
}
android {

10
pom.xml
View File

@ -84,6 +84,14 @@
<artifactId>library</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.aocate</groupId>
<artifactId>presto_client</artifactId>
<version>0.8.5</version>
<type>jar</type>
<scope>system</scope>
<systemPath>${project.basedir}/libs/presto_client-0.8.5.jar</systemPath>
</dependency>
</dependencies>
<build>
@ -212,7 +220,7 @@
</manifest>
<proguard>
<skip>false</skip>
<config>proguard.cfg</config>
<config>proguard-mvn.cfg</config>
</proguard>
</configuration>
<executions>

66
proguard-mvn.cfg Normal file
View File

@ -0,0 +1,66 @@
-printmapping out.map
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
-injars libs/presto_client-0.8.5.jar
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.content.Context {
public void *(android.view.View);
public void *(android.view.MenuItem);
}
-keepclassmembers class * implements android.os.Parcelable {
static android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.** { *; }
-keep class android.support.v7.** { *; }
-keep interface android.support.v7.** { *; }
-dontwarn android.support.v4.**
-dontwarn android.support.v7.**
-keepattributes *Annotation*
-keep class org.shredzone.flattr4j.** { *; }
-dontwarn org.shredzone.flattr4j.**
-keep class org.apache.commons.** { *; }
-dontskipnonpubliclibraryclassmembers

View File

@ -8,6 +8,8 @@
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
#-injars libs/presto_client-0.8.5.jar
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service

View File

@ -92,14 +92,12 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
<ImageButton
android:id="@+id/butRev"
android:layout_width="80dp"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/borderless_button"
@ -107,11 +105,22 @@
<ImageButton
android:id="@+id/butFF"
android:layout_width="80dp"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout

View File

@ -79,8 +79,6 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
@ -99,6 +97,17 @@
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="update_intervall_options">
<item>Manual</item>
<item>1 hour</item>
<item>2 hours</item>
<item>4 hours</item>
<item>8 hours</item>
<item>12 hours</item>
<item>24 hours</item>
</string-array>
<string-array name="update_intervall_values">
<item>0</item>
<item>1</item>
@ -28,6 +38,50 @@
<item>80</item>
<item>100</item>
</string-array>
<string-array name="playback_speed_values">
<item>1.0</item>
<item>1.05</item>
<item>1.10</item>
<item>1.15</item>
<item>1.20</item>
<item>1.25</item>
<item>1.30</item>
<item>1.35</item>
<item>1.40</item>
<item>1.45</item>
<item>1.50</item>
<item>1.55</item>
<item>1.60</item>
<item>1.65</item>
<item>1.70</item>
<item>1.75</item>
<item>1.80</item>
<item>1.85</item>
<item>1.90</item>
<item>1.95</item>
<item>2.00</item>
<item>2.10</item>
<item>2.20</item>
<item>2.30</item>
<item>2.40</item>
<item>2.50</item>
<item>2.60</item>
<item>2.70</item>
<item>2.80</item>
<item>2.90</item>
<item>3.00</item>
<item>3.10</item>
<item>3.20</item>
<item>3.30</item>
<item>3.40</item>
<item>3.50</item>
<item>3.60</item>
<item>3.70</item>
<item>3.80</item>
<item>3.90</item>
<item>4.00</item>
</string-array>
<string-array name="autodl_select_networks_default_entries">
<item>N/A</item>
</string-array>
@ -43,4 +97,5 @@
<item>1</item>
</string-array>
</resources>

View File

@ -47,6 +47,8 @@
<string name="processing_label">Processing</string>
<string name="loading_label">Loading...</string>
<string name="image_of_prefix">Image of:\u0020</string>
<string name="close_label">Close</string>
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
@ -144,6 +146,12 @@
<string name="flattr_click_success">Successfully flattred this thing!</string>
<string name="flattring_label">Flattring</string>
<!-- Variable Speed -->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Not Installed</string>
<string name="no_playback_plugin_msg">For variable speed playback to work, a third party library must be installed.\n\nTap \'Download Plugin\' to download a free plugin from the Play Store\n\nAny problems found using this plugin are not the responsibility of AntennaPod and should be reported to the plugin owner.</string>
<string name="set_playback_speed_label">Playback Speeds</string>
<!-- Empty list labels -->
<string name="no_items_label">There are no items in this list.</string>
<string name="no_feeds_label">You haven\'t subscribed to any feeds yet.</string>
@ -188,6 +196,8 @@
<string name="pref_update_interval_hours_plural">hours</string>
<string name="pref_update_interval_hours_singular">hour</string>
<string name="pref_update_interval_hours_manual">Manual</string>
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
<!-- Search -->

View File

@ -17,6 +17,10 @@
android:key="prefFollowQueue"
android:summary="@string/pref_followQueue_sum"
android:title="@string/pref_followQueue_title" />
<Preference
android:key="prefPlaybackSpeedLauncher"
android:summary="@string/pref_playback_speed_sum"
android:title="@string/pref_playback_speed_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/network_pref" >

View File

@ -12,7 +12,9 @@ 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.widget.ImageView.ScaleType;
import android.widget.ListView;
@ -22,11 +24,14 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
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.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
@ -56,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private TextView txtvTitle;
private TextView txtvFeed;
private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
@ -363,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
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() {
@ -390,6 +397,65 @@ public class AudioplayerActivity extends MediaplayerActivity {
}
}
});
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();
}
private void updateButPlaybackSpeed() {
if (controller == null
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
butPlaybackSpeed.setVisibility(View.GONE);
} else {
butPlaybackSpeed.setVisibility(View.VISIBLE);
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
}
}
@Override
@ -421,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
updateButPlaybackSpeed();
}
public void notifyMediaPositionChanged() {

View File

@ -129,10 +129,19 @@ public abstract class MediaplayerActivity extends ActionBarActivity
public void onPlaybackEnd() {
finish();
}
@Override
public void onPlaybackSpeedChange() {
MediaplayerActivity.this.onPlaybackSpeedChange();
}
};
}
protected void onPlaybackSpeedChange() {
}
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}

View File

@ -25,6 +25,7 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
@ -41,6 +42,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
private static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher";
private CheckBoxPreference[] selectedNetworks;
@ -156,6 +158,14 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
VariableSpeedDialog.showDialog(PreferenceActivity.this);
return true;
}
});
buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences

View File

@ -0,0 +1,94 @@
package de.danoeh.antennapod.dialog;
import java.util.Arrays;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
public class VariableSpeedDialog {
private VariableSpeedDialog() {
}
public static void showDialog(final Context context) {
if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) {
showSpeedSelectorDialog(context);
} else {
showGetPluginDialog(context);
}
}
private static void showGetPluginDialog(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.no_playback_plugin_title);
builder.setMessage(R.string.no_playback_plugin_msg);
builder.setNegativeButton(R.string.close_label, null);
builder.setPositiveButton(R.string.download_plugin_label,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent playStoreIntent = new Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.falconware.prestissimo"));
context.startActivity(playStoreIntent);
}
});
builder.create().show();
}
private static void showSpeedSelectorDialog(final Context context) {
final String[] speedValues = context.getResources().getStringArray(
R.array.playback_speed_values);
// According to Java spec these get initialized to false on creation
final boolean[] speedChecked = new boolean[speedValues.length];
// Build the "isChecked" array so that multiChoice dialog is
// populated correctly
List<String> selectedSpeedList = Arrays.asList(UserPreferences
.getPlaybackSpeedArray());
for (int i = 0; i < speedValues.length; i++) {
speedChecked[i] = selectedSpeedList.contains(speedValues[i]);
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.set_playback_speed_label);
builder.setMultiChoiceItems(R.array.playback_speed_values,
speedChecked, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
speedChecked[which] = isChecked;
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int choiceCount = 0;
for (int i = 0; i < speedChecked.length; i++) {
if (speedChecked[i]) {
choiceCount++;
}
}
String[] newSpeedValues = new String[choiceCount];
int newSpeedIndex = 0;
for (int i = 0; i < speedChecked.length; i++) {
if (speedChecked[i]) {
newSpeedValues[newSpeedIndex++] = speedValues[i];
}
}
UserPreferences.setPlaybackSpeedArray(newSpeedValues);
}
});
builder.create().show();
}
}

View File

@ -173,6 +173,12 @@ public class ExternalPlayerFragment extends Fragment {
.newOnPlayButtonClickListener());
}
}
@Override
public void onPlaybackSpeedChange() {
// TODO Auto-generated method stub
}
};
}

View File

@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlarmManager;
import android.app.PendingIntent;
@ -41,11 +45,13 @@ public class UserPreferences implements
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance;
private Context context;
private final Context context;
// Preferences
private boolean pauseOnHeadsetDisconnect;
@ -60,6 +66,8 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
private int episodeCacheSize;
private String playbackSpeed;
private String[] playbackSpeedArray;
private UserPreferences(Context context) {
this.context = context;
@ -83,6 +91,7 @@ public class UserPreferences implements
createNoMediaFile();
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(instance);
}
private void loadPreferences() {
@ -108,6 +117,9 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSize(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
PREF_PLAYBACK_SPEED_ARRAY, null));
}
private int readThemeValue(String valueFromPrefs) {
@ -135,6 +147,36 @@ public class UserPreferences implements
}
}
private String[] readPlaybackSpeedArray(String valueFromPrefs) {
String[] selectedSpeeds = null;
// If this preference hasn't been set yet, return the default options
if (valueFromPrefs == null) {
String[] allSpeeds = context.getResources().getStringArray(
R.array.playback_speed_values);
List<String> speedList = new LinkedList<String>();
for (String speedStr : allSpeeds) {
float speed = Float.parseFloat(speedStr);
if (speed < 2.0001 && speed * 10 % 1 == 0) {
speedList.add(speedStr);
}
}
selectedSpeeds = speedList.toArray(new String[speedList.size()]);
} else {
try {
JSONArray jsonArray = new JSONArray(valueFromPrefs);
selectedSpeeds = new String[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
selectedSpeeds[i] = jsonArray.getString(i);
}
} catch (JSONException e) {
Log.e(TAG,
"Got JSON error when trying to get speeds from JSONArray");
e.printStackTrace();
}
}
return selectedSpeeds;
}
private static void instanceAvailable() {
if (instance == null) {
throw new IllegalStateException(
@ -196,6 +238,16 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
public static String getPlaybackSpeed() {
instanceAvailable();
return instance.playbackSpeed;
}
public static String[] getPlaybackSpeedArray() {
instanceAvailable();
return instance.playbackSpeedArray;
}
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@ -250,9 +302,29 @@ public class UserPreferences implements
PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
} else if (key.equals(PREF_PLAYBACK_SPEED)) {
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
} else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
PREF_PLAYBACK_SPEED_ARRAY, null));
}
}
public static void setPlaybackSpeed(String speed) {
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED, speed).apply();
}
public static void setPlaybackSpeedArray(String[] speeds) {
JSONArray jsonArray = new JSONArray();
for (String speed : speeds) {
jsonArray.put(speed);
}
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
.apply();
}
public static void setAutodownloadSelectedNetworks(Context context,
String[] value) {
SharedPreferences.Editor editor = PreferenceManager

View File

@ -45,9 +45,13 @@ import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.BitmapDecoder;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.DuckType;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
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.Playable.PlayableException;
import de.danoeh.antennapod.util.playback.VideoPlayer;
import de.danoeh.antennapod.util.playback.PlaybackController;
/**
@ -119,6 +123,11 @@ public class PlaybackService extends Service {
*/
public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7;
/**
* Playback speed has changed
* */
public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8;
/**
* Returned by getPositionSafe() or getDurationSafe() if the playbackService
* is in an invalid state.
@ -132,13 +141,12 @@ public class PlaybackService extends Service {
private static final int NOTIFICATION_ID = 1;
private volatile IPlayer player;
private RemoteControlClient remoteControlClient;
private AudioManager audioManager;
private ComponentName mediaButtonReceiver;
private MediaPlayer player;
private RemoteControlClient remoteControlClient;
private Playable media;
private volatile Playable media;
/**
* True if media should be streamed (Extracted from Intent Extra) .
@ -252,7 +260,6 @@ public class PlaybackService extends Service {
}
);
dbLoaderExecutor = Executors.newSingleThreadExecutor();
player = createMediaPlayer();
mediaButtonReceiver = new ComponentName(getPackageName(),
MediaButtonReceiver.class.getName());
@ -273,18 +280,39 @@ public class PlaybackService extends Service {
loadQueue();
}
private MediaPlayer createMediaPlayer() {
return createMediaPlayer(new MediaPlayer());
private IPlayer createMediaPlayer() {
IPlayer player;
if (media == null || media.getMediaType() == MediaType.VIDEO) {
player = new VideoPlayer();
} else {
player = new AudioPlayer(this);
}
return createMediaPlayer(player);
}
private MediaPlayer createMediaPlayer(MediaPlayer mp) {
if (mp != null) {
mp.setOnPreparedListener(preparedListener);
mp.setOnCompletionListener(completionListener);
mp.setOnSeekCompleteListener(onSeekCompleteListener);
mp.setOnErrorListener(onErrorListener);
mp.setOnBufferingUpdateListener(onBufferingUpdateListener);
mp.setOnInfoListener(onInfoListener);
private IPlayer createMediaPlayer(IPlayer mp) {
if (mp != null && media != null) {
if (media.getMediaType() == MediaType.AUDIO) {
((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
((AudioPlayer) mp)
.setOnCompletionListener(audioCompletionListener);
((AudioPlayer) mp)
.setOnSeekCompleteListener(audioSeekCompleteListener);
((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
((AudioPlayer) mp)
.setOnBufferingUpdateListener(audioBufferingUpdateListener);
((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
} else {
((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener);
((VideoPlayer) mp)
.setOnCompletionListener(videoCompletionListener);
((VideoPlayer) mp)
.setOnSeekCompleteListener(videoSeekCompleteListener);
((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
((VideoPlayer) mp)
.setOnBufferingUpdateListener(videoBufferingUpdateListener);
((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
}
}
return mp;
}
@ -568,6 +596,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Setting up media player");
try {
MediaType mediaType = media.getMediaType();
player = createMediaPlayer();
if (mediaType == MediaType.AUDIO) {
if (AppConfig.DEBUG)
Log.d(TAG, "Mime type is audio");
@ -662,9 +691,22 @@ public class PlaybackService extends Service {
}
}
private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
public void onPrepared(com.aocate.media.MediaPlayer mp) {
genericOnPrepared(mp);
}
};
private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(android.media.MediaPlayer mp) {
genericOnPrepared(mp);
}
};
private final void genericOnPrepared(Object inObj) {
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
if (AppConfig.DEBUG)
Log.d(TAG, "Resource prepared");
mp.seekTo(media.getPosition());
@ -699,23 +741,43 @@ public class PlaybackService extends Service {
play();
}
}
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 MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() {
private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mp) {
public void onSeekComplete(android.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
private final void genericSeekCompleteListener() {
if (status == PlayerStatus.SEEKING) {
setStatus(statusBeforeSeek);
}
}
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 MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() {
private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
return genericInfoListener(what);
}
};
private boolean genericInfoListener(int what) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
@ -727,14 +789,26 @@ public class PlaybackService extends Service {
return false;
}
}
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 MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() {
private static final String TAG = "PlaybackService.onErrorListener";
private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.w(TAG, "An error has occured: " + what);
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) {
final String TAG = "PlaybackService.onErrorListener";
Log.w(TAG, "An error has occured: " + what + " " + extra);
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
if (mp.isPlaying()) {
pause(true, true);
}
@ -743,25 +817,44 @@ public class PlaybackService extends Service {
stopSelf();
return true;
}
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 MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
public void onCompletion(android.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private void genericOnCompletion() {
endPlayback(true);
}
};
private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
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) {
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
}
private void endPlayback(boolean playNextEpisode) {
if (AppConfig.DEBUG)
Log.d(TAG, "Playback ended");
@ -938,6 +1031,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Resuming/Starting playback");
writePlaybackPreferences();
setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
player.start();
if (status != PlayerStatus.PAUSED) {
player.seekTo((int) media.getPosition());
@ -1473,7 +1567,7 @@ public class PlaybackService extends Service {
return media;
}
public MediaPlayer getPlayer() {
public IPlayer getPlayer() {
return player;
}
@ -1486,6 +1580,53 @@ public class PlaybackService extends Service {
postStatusUpdateIntent();
}
public boolean canSetSpeed() {
if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
return ((AudioPlayer) player).canSetSpeed();
}
return false;
}
public boolean canSetPitch() {
if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
return ((AudioPlayer) player).canSetPitch();
}
return false;
}
public void setSpeed(float speed) {
if (media != null && media.getMediaType() == MediaType.AUDIO) {
AudioPlayer audioPlayer = (AudioPlayer) player;
if (audioPlayer.canSetSpeed()) {
audioPlayer.setPlaybackSpeed((float) speed);
if (AppConfig.DEBUG)
Log.d(TAG, "Playback speed was set to " + speed);
sendNotificationBroadcast(
NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
}
}
}
public void setPitch(float pitch) {
if (media != null && media.getMediaType() == MediaType.AUDIO) {
AudioPlayer audioPlayer = (AudioPlayer) player;
if (audioPlayer.canSetPitch()) {
audioPlayer.setPlaybackPitch((float) pitch);
}
}
}
public float getCurrentPlaybackSpeed() {
if (media.getMediaType() == MediaType.AUDIO
&& player instanceof AudioPlayer) {
AudioPlayer audioPlayer = (AudioPlayer) player;
if (audioPlayer.canSetSpeed()) {
return audioPlayer.getCurrentSpeedMultiplier();
}
}
return -1;
}
/**
* call getDuration() on mediaplayer or return INVALID_TIME if player is in
* an invalid state. This method should be used instead of calling

View File

@ -800,8 +800,9 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
// Get duration
MediaPlayer mediaplayer = new MediaPlayer();
MediaPlayer mediaplayer = null;
try {
mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(media.getFile_url());
mediaplayer.prepare();
media.setDuration(mediaplayer.getDuration());
@ -810,9 +811,14 @@ public class DownloadService extends Service {
mediaplayer.reset();
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
// Thrown by MediaPlayer initialization on some devices
e.printStackTrace();
} finally {
if (mediaplayer != null) {
mediaplayer.release();
}
}
if (media.getItem().getChapters() == null) {
ChapterUtils.loadChaptersFromFileUrl(media);

View File

@ -441,13 +441,7 @@ public final class DBTasks {
}
for (FeedItem item : delete) {
try {
DBWriter.deleteFeedMediaOfItem(context, item.getId()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
DBWriter.deleteFeedMediaOfItem(context, item.getId());
}
int counter = delete.size();

View File

@ -0,0 +1,115 @@
/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */
package de.danoeh.antennapod.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Allows "duck typing" or dynamic invocation based on method signature rather
* than type hierarchy. In other words, rather than checking whether something
* IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck.
*
* To use first use the coerce static method to indicate the object you want to
* do Duck Typing for, then specify an interface to the to method which you want
* to coerce the type to, e.g:
*
* public interface Foo { void aMethod(); } class Bar { ... public void
* aMethod() { ... } ... } Bar bar = ...; Foo foo =
* DuckType.coerce(bar).to(Foo.class); foo.aMethod();
*
*
*/
public class DuckType {
private final Object objectToCoerce;
private DuckType(Object objectToCoerce) {
this.objectToCoerce = objectToCoerce;
}
private class CoercedProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method delegateMethod = findMethodBySignature(method);
assert delegateMethod != null;
return delegateMethod.invoke(DuckType.this.objectToCoerce, args);
}
}
/**
* Specify the duck typed object to coerce.
*
* @param object
* the object to coerce
* @return
*/
public static DuckType coerce(Object object) {
return new DuckType(object);
}
/**
* Coerce the Duck Typed object to the given interface providing it
* implements all the necessary methods.
*
* @param
* @param iface
* @return an instance of the given interface that wraps the duck typed
* class
* @throws ClassCastException
* if the object being coerced does not implement all the
* methods in the given interface.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <T> T to(Class iface) {
assert iface.isInterface() : "cannot coerce object to a class, must be an interface";
if (isA(iface)) {
return (T) iface.cast(objectToCoerce);
}
if (quacksLikeA(iface)) {
return generateProxy(iface);
}
throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface);
}
@SuppressWarnings("rawtypes")
private boolean isA(Class iface) {
return objectToCoerce.getClass().isInstance(iface);
}
/**
* Determine whether the duck typed object can be used with the given
* interface.
*
* @param Type
* of the interface to check.
* @param iface
* Interface class to check
* @return true if the object will support all the methods in the interface,
* false otherwise.
*/
@SuppressWarnings("rawtypes")
public boolean quacksLikeA(Class iface) {
for (Method method : iface.getMethods()) {
if (findMethodBySignature(method) == null) {
return false;
}
}
return true;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> T generateProxy(Class iface) {
return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy());
}
private Method findMethodBySignature(Method method) {
try {
return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
return null;
}
}
}

View File

@ -0,0 +1,30 @@
package de.danoeh.antennapod.util.playback;
import android.content.Context;
import android.util.Log;
import android.view.SurfaceHolder;
import com.aocate.media.MediaPlayer;
public class AudioPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "AudioPlayer";
public AudioPlayer(Context context) {
super(context);
}
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
}
@Override
public void setDisplay(SurfaceHolder sh) {
if (sh != null) {
Log.e(TAG, "Setting display not supported in Audio Player");
throw new UnsupportedOperationException("Setting display not supported in Audio Player");
}
}
}

View File

@ -0,0 +1,64 @@
package de.danoeh.antennapod.util.playback;
import java.io.IOException;
import android.view.SurfaceHolder;
public interface IPlayer {
boolean canSetPitch();
boolean canSetSpeed();
float getCurrentPitchStepsAdjustment();
int getCurrentPosition();
float getCurrentSpeedMultiplier();
int getDuration();
float getMaxSpeedMultiplier();
float getMinSpeedMultiplier();
boolean isLooping();
boolean isPlaying();
void pause();
void prepare() throws IllegalStateException, IOException;
void prepareAsync();
void release();
void reset();
void seekTo(int msec);
void setAudioStreamType(int streamtype);
void setScreenOnWhilePlaying(boolean screenOn);
void setDataSource(String path) throws IllegalStateException, IOException,
IllegalArgumentException, SecurityException;
void setDisplay(SurfaceHolder sh);
void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
void setLooping(boolean looping);
void setPitchStepsAdjustment(float pitchSteps);
void setPlaybackPitch(float f);
void setPlaybackSpeed(float f);
void setVolume(float left, float right);
void start();
void stop();
}

View File

@ -342,6 +342,9 @@ public abstract class PlaybackController {
case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
onPlaybackEnd();
break;
case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
onPlaybackSpeedChange();
break;
}
} else {
@ -369,6 +372,8 @@ public abstract class PlaybackController {
}
};
public abstract void onPlaybackSpeedChange();
public abstract void onShutdownNotification();
/**
@ -663,6 +668,24 @@ public abstract class PlaybackController {
return status;
}
public boolean canSetPlaybackSpeed() {
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 boolean isPlayingVideo() {
if (playbackService != null) {
return PlaybackService.isPlayingVideo();
@ -670,6 +693,7 @@ public abstract class PlaybackController {
return false;
}
/**
* Returns true if PlaybackController can communicate with the playback
* service.

View File

@ -0,0 +1,62 @@
package de.danoeh.antennapod.util.playback;
import android.media.MediaPlayer;
import android.util.Log;
public class VideoPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "VideoPlayer";
@Override
public boolean canSetPitch() {
return false;
}
@Override
public boolean canSetSpeed() {
return false;
}
@Override
public float getCurrentPitchStepsAdjustment() {
return 1;
}
@Override
public float getCurrentSpeedMultiplier() {
return 1;
}
@Override
public float getMaxSpeedMultiplier() {
return 1;
}
@Override
public float getMinSpeedMultiplier() {
return 1;
}
@Override
public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException {
Log.e(TAG, "Setting enable speed adjustment unsupported in video player");
throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player");
}
@Override
public void setPitchStepsAdjustment(float pitchSteps) {
Log.e(TAG, "Setting pitch steps adjustment unsupported in video player");
throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player");
}
@Override
public void setPlaybackPitch(float f) {
Log.e(TAG, "Setting playback pitch unsupported in video player");
throw new UnsupportedOperationException("Setting playback pitch unsupported in video player");
}
@Override
public void setPlaybackSpeed(float f) {
Log.e(TAG, "Setting playback speed unsupported in video player");
throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
}
}