Merge branch 'develop' into add-local-feeds
This commit is contained in:
commit
679e482999
@ -375,7 +375,7 @@ public class DBReaderTest {
|
||||
for (Feed feed : feeds) {
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
assertFalse(item.hasChapters());
|
||||
DBReader.loadChaptersOfFeedItem(item);
|
||||
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
||||
assertFalse(item.hasChapters());
|
||||
assertNull(item.getChapters());
|
||||
}
|
||||
@ -390,7 +390,7 @@ public class DBReaderTest {
|
||||
for (Feed feed : feeds) {
|
||||
for (FeedItem item : feed.getItems()) {
|
||||
assertTrue(item.hasChapters());
|
||||
DBReader.loadChaptersOfFeedItem(item);
|
||||
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
||||
assertTrue(item.hasChapters());
|
||||
assertNotNull(item.getChapters());
|
||||
assertEquals(NUM_CHAPTERS, item.getChapters().size());
|
||||
@ -404,7 +404,7 @@ public class DBReaderTest {
|
||||
List<Feed> feeds = saveFeedlist(1, 1, false, true, NUM_CHAPTERS);
|
||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||
FeedItem item2 = DBReader.getFeedItem(item1.getId());
|
||||
DBReader.loadChaptersOfFeedItem(item2);
|
||||
item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
|
||||
assertTrue(item2.hasChapters());
|
||||
assertEquals(item1.getChapters(), item2.getChapters());
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ public class DownloadStatisticsListAdapter extends StatisticsListAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
int getHeaderCaptionResourceId() {
|
||||
return R.string.total_size_downloaded_podcasts;
|
||||
String getHeaderCaption() {
|
||||
return context.getString(R.string.total_size_downloaded_podcasts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,10 +4,13 @@ import android.content.Context;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.StatisticsItem;
|
||||
import de.danoeh.antennapod.core.util.Converter;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.view.PieChartView;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -26,8 +29,14 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
int getHeaderCaptionResourceId() {
|
||||
return R.string.total_time_listened_to_podcasts;
|
||||
String getHeaderCaption() {
|
||||
long usageCounting = UserPreferences.getUsageCountingDateMillis();
|
||||
if (usageCounting > 0) {
|
||||
String date = DateUtils.formatAbbrev(context, new Date(usageCounting));
|
||||
return context.getString(R.string.statistics_counting_since, date);
|
||||
} else {
|
||||
return context.getString(R.string.total_time_listened_to_podcasts);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -50,7 +50,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle
|
||||
if (viewType == TYPE_HEADER) {
|
||||
View view = inflater.inflate(R.layout.statistics_listitem_total, parent, false);
|
||||
TextView totalText = view.findViewById(R.id.total_description);
|
||||
totalText.setText(getHeaderCaptionResourceId());
|
||||
totalText.setText(getHeaderCaption());
|
||||
return new HeaderHolder(view);
|
||||
}
|
||||
return new StatisticsHolder(inflater.inflate(R.layout.statistics_listitem, parent, false));
|
||||
@ -113,7 +113,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle
|
||||
}
|
||||
}
|
||||
|
||||
abstract int getHeaderCaptionResourceId();
|
||||
abstract String getHeaderCaption();
|
||||
|
||||
abstract String getHeaderValue();
|
||||
|
||||
|
@ -20,7 +20,7 @@ public class StreamingConfirmationDialog {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.stream_label)
|
||||
.setMessage(R.string.confirm_mobile_streaming_notification_message)
|
||||
.setPositiveButton(R.string.stream_label, (dialog, which) -> stream())
|
||||
.setPositiveButton(R.string.confirm_mobile_streaming_button_once, (dialog, which) -> stream())
|
||||
.setNegativeButton(R.string.confirm_mobile_streaming_button_always, (dialog, which) -> {
|
||||
UserPreferences.setAllowMobileStreaming(true);
|
||||
stream();
|
||||
|
@ -26,6 +26,7 @@ import de.danoeh.antennapod.core.export.ExportWriter;
|
||||
import de.danoeh.antennapod.core.export.favorites.FavoritesWriter;
|
||||
import de.danoeh.antennapod.core.export.html.HtmlWriter;
|
||||
import de.danoeh.antennapod.core.export.opml.OpmlWriter;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DatabaseExporter;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
@ -268,6 +269,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat {
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
showDatabaseImportSuccessDialog();
|
||||
UserPreferences.unsetUsageCountingDate();
|
||||
progressDialog.dismiss();
|
||||
}, this::showExportErrorDialog);
|
||||
} else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) {
|
||||
|
@ -25,6 +25,7 @@ import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.PreferenceActivity;
|
||||
import de.danoeh.antennapod.adapter.PlaybackStatisticsListAdapter;
|
||||
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.StatisticsItem;
|
||||
@ -159,7 +160,10 @@ public class PlaybackStatisticsFragment extends Fragment {
|
||||
disposable = Completable.fromFuture(DBWriter.resetStatistics())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::refreshStatistics, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
.subscribe(() -> {
|
||||
refreshStatistics();
|
||||
UserPreferences.resetUsageCountingDate();
|
||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
||||
}
|
||||
|
||||
private void refreshStatistics() {
|
||||
|
@ -34,6 +34,10 @@ public class PreferenceUpgrader {
|
||||
|
||||
private static void upgrade(int oldVersion) {
|
||||
if (oldVersion == -1) {
|
||||
//New installation
|
||||
if (UserPreferences.getUsageCountingDateMillis() < 0) {
|
||||
UserPreferences.resetUsageCountingDate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (oldVersion < 1070196) {
|
||||
|
@ -0,0 +1,55 @@
|
||||
package de.danoeh.antennapod.core.feed;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChapterMerger {
|
||||
private static final String TAG = "ChapterMerger";
|
||||
|
||||
private ChapterMerger() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method might modify the input data.
|
||||
*/
|
||||
@Nullable
|
||||
public static List<Chapter> merge(@Nullable List<Chapter> chapters1, @Nullable List<Chapter> chapters2) {
|
||||
Log.d(TAG, "Merging chapters");
|
||||
if (chapters1 == null) {
|
||||
return chapters2;
|
||||
} else if (chapters2 == null) {
|
||||
return chapters1;
|
||||
} else if (chapters2.size() > chapters1.size()) {
|
||||
return chapters2;
|
||||
} else if (chapters2.size() < chapters1.size()) {
|
||||
return chapters1;
|
||||
} else {
|
||||
// Merge chapter lists of same length. Store in chapters2 array.
|
||||
// In case the lists can not be merged, return chapters1 array.
|
||||
for (int i = 0; i < chapters2.size(); i++) {
|
||||
Chapter chapterTarget = chapters2.get(i);
|
||||
Chapter chapterOther = chapters1.get(i);
|
||||
|
||||
if (Math.abs(chapterTarget.start - chapterOther.start) > 1000) {
|
||||
Log.e(TAG, "Chapter lists are too different. Cancelling merge.");
|
||||
return chapters1;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(chapterTarget.imageUrl)) {
|
||||
chapterTarget.imageUrl = chapterOther.imageUrl;
|
||||
}
|
||||
if (TextUtils.isEmpty(chapterTarget.link)) {
|
||||
chapterTarget.link = chapterOther.link;
|
||||
}
|
||||
if (TextUtils.isEmpty(chapterTarget.title)) {
|
||||
chapterTarget.title = chapterOther.title;
|
||||
}
|
||||
}
|
||||
return chapters2;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
@ -383,21 +384,32 @@ public class FeedMedia extends FeedFile implements Playable {
|
||||
if (item == null || item.getChapters() != null) {
|
||||
return;
|
||||
}
|
||||
// check if chapters are stored in db and not loaded yet.
|
||||
if (item.hasChapters()) {
|
||||
DBReader.loadChaptersOfFeedItem(item);
|
||||
|
||||
List<Chapter> chapters = loadChapters(context);
|
||||
if (chapters == null) {
|
||||
// Do not try loading again. There are no chapters.
|
||||
item.setChapters(Collections.emptyList());
|
||||
} else {
|
||||
if (localFileAvailable()) {
|
||||
ChapterUtils.loadChaptersFromFileUrl(this);
|
||||
} else {
|
||||
ChapterUtils.loadChaptersFromStreamUrl(this, context);
|
||||
}
|
||||
if (item.getChapters() != null) {
|
||||
DBWriter.setFeedItem(item);
|
||||
}
|
||||
item.setChapters(chapters);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Chapter> loadChapters(Context context) {
|
||||
List<Chapter> chaptersFromDatabase = null;
|
||||
if (item.hasChapters()) {
|
||||
chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(item);
|
||||
}
|
||||
|
||||
List<Chapter> chaptersFromMediaFile;
|
||||
if (localFileAvailable()) {
|
||||
chaptersFromMediaFile = ChapterUtils.loadChaptersFromFileUrl(this);
|
||||
} else {
|
||||
chaptersFromMediaFile = ChapterUtils.loadChaptersFromStreamUrl(this, context);
|
||||
}
|
||||
|
||||
return ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEpisodeTitle() {
|
||||
if (item == null) {
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@ -12,6 +11,7 @@ import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -23,7 +23,7 @@ import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -115,6 +115,7 @@ public class UserPreferences {
|
||||
private static final String PREF_DATA_FOLDER = "prefDataFolder";
|
||||
public static final String PREF_IMAGE_CACHE_SIZE = "prefImageCacheSize";
|
||||
public static final String PREF_DELETE_REMOVES_FROM_QUEUE = "prefDeleteRemovesFromQueue";
|
||||
public static final String PREF_USAGE_COUNTING_DATE = "prefUsageCounting";
|
||||
|
||||
// Mediaplayer
|
||||
public static final String PREF_MEDIA_PLAYER = "prefMediaPlayer";
|
||||
@ -1056,4 +1057,19 @@ public class UserPreferences {
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static long getUsageCountingDateMillis() {
|
||||
return prefs.getLong(PREF_USAGE_COUNTING_DATE, -1);
|
||||
}
|
||||
|
||||
private static void setUsageCountingDateMillis(long value) {
|
||||
prefs.edit().putLong(PREF_USAGE_COUNTING_DATE, value).apply();
|
||||
}
|
||||
|
||||
public static void resetUsageCountingDate() {
|
||||
setUsageCountingDateMillis(Calendar.getInstance().getTimeInMillis());
|
||||
}
|
||||
|
||||
public static void unsetUsageCountingDate() {
|
||||
setUsageCountingDateMillis(-1);
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class MediaDownloadedHandler implements Runnable {
|
||||
|
||||
// check if file has chapters
|
||||
if (media.getItem() != null && !media.getItem().hasChapters()) {
|
||||
ChapterUtils.loadChaptersFromFileUrl(media);
|
||||
media.setChapters(ChapterUtils.loadChaptersFromFileUrl(media));
|
||||
}
|
||||
|
||||
// Get duration
|
||||
|
@ -2,9 +2,7 @@ package de.danoeh.antennapod.core.service.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.telephony.TelephonyManager;
|
||||
@ -12,11 +10,13 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import androidx.media.AudioAttributesCompat;
|
||||
import androidx.media.AudioFocusRequestCompat;
|
||||
import androidx.media.AudioManagerCompat;
|
||||
import org.antennapod.audio.MediaPlayer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
@ -57,6 +57,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||
private final AtomicBoolean startWhenPrepared;
|
||||
private volatile boolean pausedBecauseOfTransientAudiofocusLoss;
|
||||
private volatile Pair<Integer, Integer> videoSize;
|
||||
private final AudioFocusRequestCompat audioFocusRequest;
|
||||
|
||||
/**
|
||||
* Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads
|
||||
@ -154,6 +155,16 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
mediaType = MediaType.UNKNOWN;
|
||||
videoSize = null;
|
||||
|
||||
AudioAttributesCompat audioAttributes = new AudioAttributesCompat.Builder()
|
||||
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
|
||||
.build();
|
||||
audioFocusRequest = new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(audioAttributes)
|
||||
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
||||
.setWillPauseWhenDucked(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,25 +298,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||
|
||||
private void resumeSync() {
|
||||
if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
|
||||
int focusGained;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||
.build();
|
||||
AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setAudioAttributes(audioAttributes)
|
||||
.setOnAudioFocusChangeListener(audioFocusChangeListener)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setWillPauseWhenDucked(true)
|
||||
.build();
|
||||
focusGained = audioManager.requestAudioFocus(audioFocusRequest);
|
||||
} else {
|
||||
focusGained = audioManager.requestAudioFocus(
|
||||
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
}
|
||||
int focusGained = AudioManagerCompat.requestAudioFocus(audioManager, audioFocusRequest);
|
||||
|
||||
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Log.d(TAG, "Audiofocus successfully requested");
|
||||
@ -373,13 +366,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||
}
|
||||
|
||||
private void abandonAudioFocus() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AudioFocusRequest.Builder builder = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
||||
.setOnAudioFocusChangeListener(audioFocusChangeListener);
|
||||
audioManager.abandonAudioFocusRequest(builder.build());
|
||||
} else {
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
}
|
||||
AudioManagerCompat.abandonAudioFocusRequest(audioManager, audioFocusRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -504,16 +504,27 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||
stateManager.stopService();
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
if (playable instanceof FeedMedia) {
|
||||
playable = DBReader.getFeedMedia(((FeedMedia) playable).getId());
|
||||
}
|
||||
if (playable == null) {
|
||||
Log.d(TAG, "Playable was not found. Stopping service.");
|
||||
stateManager.stopService();
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately);
|
||||
addPlayableToQueue(playable);
|
||||
|
||||
Observable.fromCallable(
|
||||
() -> {
|
||||
if (playable instanceof FeedMedia) {
|
||||
return DBReader.getFeedMedia(((FeedMedia) playable).getId());
|
||||
} else {
|
||||
return playable;
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
playableLoaded -> {
|
||||
mediaPlayer.playMediaObject(playable, stream, startWhenPrepared,
|
||||
prepareImmediately);
|
||||
addPlayableToQueue(playable);
|
||||
}, error -> {
|
||||
Log.d(TAG, "Playable was not found. Stopping service.");
|
||||
stateManager.stopService();
|
||||
});
|
||||
return Service.START_NOT_STICKY;
|
||||
} else {
|
||||
Log.d(TAG, "Did not handle intent to PlaybackService: " + intent);
|
||||
Log.d(TAG, "Extras: " + intent.getExtras());
|
||||
@ -582,7 +593,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntentAllowThisTime)
|
||||
.addAction(R.drawable.ic_stream_white,
|
||||
getString(R.string.stream_label),
|
||||
getString(R.string.confirm_mobile_streaming_button_once),
|
||||
pendingIntentAllowThisTime)
|
||||
.addAction(R.drawable.ic_stream_white,
|
||||
getString(R.string.confirm_mobile_streaming_button_always),
|
||||
@ -682,27 +693,33 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||
}
|
||||
|
||||
private void startPlayingFromPreferences() {
|
||||
Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
|
||||
if (playable != null) {
|
||||
boolean localFeed = URLUtil.isContentUrl(playable.getStreamUrl());
|
||||
if (PlaybackPreferences.getCurrentEpisodeIsStream() && !NetworkUtils.isStreamingAllowed() && !localFeed) {
|
||||
displayStreamingNotAllowedNotification(
|
||||
new PlaybackServiceStarter(this, playable)
|
||||
.prepareImmediately(true)
|
||||
.startWhenPrepared(true)
|
||||
.shouldStream(true)
|
||||
.getIntent());
|
||||
PlaybackPreferences.writeNoMediaPlaying();
|
||||
stateManager.stopService();
|
||||
return;
|
||||
}
|
||||
mediaPlayer.playMediaObject(playable, PlaybackPreferences.getCurrentEpisodeIsStream(), true, true);
|
||||
stateManager.validStartCommandWasReceived();
|
||||
PlaybackService.this.updateMediaSessionMetadata(playable);
|
||||
addPlayableToQueue(playable);
|
||||
} else {
|
||||
stateManager.stopService();
|
||||
}
|
||||
Observable.fromCallable(() -> Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext()))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
playable -> {
|
||||
boolean localFeed = URLUtil.isContentUrl(playable.getStreamUrl());
|
||||
if (PlaybackPreferences.getCurrentEpisodeIsStream()
|
||||
&& !NetworkUtils.isStreamingAllowed() && !localFeed) {
|
||||
displayStreamingNotAllowedNotification(
|
||||
new PlaybackServiceStarter(this, playable)
|
||||
.prepareImmediately(true)
|
||||
.startWhenPrepared(true)
|
||||
.shouldStream(true)
|
||||
.getIntent());
|
||||
PlaybackPreferences.writeNoMediaPlaying();
|
||||
stateManager.stopService();
|
||||
return;
|
||||
}
|
||||
mediaPlayer.playMediaObject(playable, PlaybackPreferences.getCurrentEpisodeIsStream(),
|
||||
true, true);
|
||||
stateManager.validStartCommandWasReceived();
|
||||
PlaybackService.this.updateMediaSessionMetadata(playable);
|
||||
addPlayableToQueue(playable);
|
||||
}, error -> {
|
||||
Log.d(TAG, "Playable was not loaded from preferences. Stopping service.");
|
||||
stateManager.stopService();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -651,29 +651,30 @@ public final class DBReader {
|
||||
*
|
||||
* @param item The FeedItem
|
||||
*/
|
||||
public static void loadChaptersOfFeedItem(final FeedItem item) {
|
||||
public static List<Chapter> loadChaptersOfFeedItem(final FeedItem item) {
|
||||
Log.d(TAG, "loadChaptersOfFeedItem() called with: " + "item = [" + item + "]");
|
||||
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
try {
|
||||
loadChaptersOfFeedItem(adapter, item);
|
||||
return loadChaptersOfFeedItem(adapter, item);
|
||||
} finally {
|
||||
adapter.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
|
||||
private static List<Chapter> loadChaptersOfFeedItem(PodDBAdapter adapter, FeedItem item) {
|
||||
try (Cursor cursor = adapter.getSimpleChaptersOfFeedItemCursor(item)) {
|
||||
int chaptersCount = cursor.getCount();
|
||||
if (chaptersCount == 0) {
|
||||
item.setChapters(null);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
item.setChapters(new ArrayList<>(chaptersCount));
|
||||
ArrayList<Chapter> chapters = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
item.getChapters().add(Chapter.fromCursor(cursor));
|
||||
chapters.add(Chapter.fromCursor(cursor));
|
||||
}
|
||||
return chapters;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,32 +52,34 @@ public class ChapterUtils {
|
||||
return chapters.size() - 1;
|
||||
}
|
||||
|
||||
public static void loadChaptersFromStreamUrl(Playable media, Context context) {
|
||||
ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media, context);
|
||||
if (media.getChapters() == null) {
|
||||
ChapterUtils.readOggChaptersFromPlayableStreamUrl(media, context);
|
||||
public static List<Chapter> loadChaptersFromStreamUrl(Playable media, Context context) {
|
||||
List<Chapter> chapters = ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media, context);
|
||||
if (chapters == null) {
|
||||
chapters = ChapterUtils.readOggChaptersFromPlayableStreamUrl(media, context);
|
||||
}
|
||||
return chapters;
|
||||
}
|
||||
|
||||
public static void loadChaptersFromFileUrl(Playable media) {
|
||||
public static List<Chapter> loadChaptersFromFileUrl(Playable media) {
|
||||
if (!media.localFileAvailable()) {
|
||||
Log.e(TAG, "Could not load chapters from file url: local file not available");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
ChapterUtils.readID3ChaptersFromPlayableFileUrl(media);
|
||||
if (media.getChapters() == null) {
|
||||
ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
|
||||
List<Chapter> chapters = ChapterUtils.readID3ChaptersFromPlayableFileUrl(media);
|
||||
if (chapters == null) {
|
||||
chapters = ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
|
||||
}
|
||||
return chapters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the download URL of a media object of a feeditem to read its ID3
|
||||
* chapters.
|
||||
*/
|
||||
private static void readID3ChaptersFromPlayableStreamUrl(Playable p, Context context) {
|
||||
private static List<Chapter> readID3ChaptersFromPlayableStreamUrl(Playable p, Context context) {
|
||||
if (p == null || p.getStreamUrl() == null) {
|
||||
Log.e(TAG, "Unable to read ID3 chapters: media or download URL was null");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
|
||||
CountingInputStream in = null;
|
||||
@ -93,7 +95,7 @@ public class ChapterUtils {
|
||||
}
|
||||
List<Chapter> chapters = readChaptersFrom(in);
|
||||
if (!chapters.isEmpty()) {
|
||||
p.setChapters(chapters);
|
||||
return chapters;
|
||||
}
|
||||
Log.i(TAG, "Chapters loaded");
|
||||
} catch (IOException | ID3ReaderException | IllegalArgumentException e) {
|
||||
@ -101,21 +103,22 @@ public class ChapterUtils {
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the file URL of a media object of a feeditem to read its ID3
|
||||
* chapters.
|
||||
*/
|
||||
private static void readID3ChaptersFromPlayableFileUrl(Playable p) {
|
||||
private static List<Chapter> readID3ChaptersFromPlayableFileUrl(Playable p) {
|
||||
if (p == null || !p.localFileAvailable() || p.getLocalMediaUrl() == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
|
||||
File source = new File(p.getLocalMediaUrl());
|
||||
if (!source.exists()) {
|
||||
Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
CountingInputStream in = null;
|
||||
@ -123,7 +126,7 @@ public class ChapterUtils {
|
||||
in = new CountingInputStream(new BufferedInputStream(new FileInputStream(source)));
|
||||
List<Chapter> chapters = readChaptersFrom(in);
|
||||
if (!chapters.isEmpty()) {
|
||||
p.setChapters(chapters);
|
||||
return chapters;
|
||||
}
|
||||
Log.i(TAG, "Chapters loaded");
|
||||
} catch (IOException | ID3ReaderException e) {
|
||||
@ -131,6 +134,7 @@ public class ChapterUtils {
|
||||
} finally {
|
||||
IOUtils.closeQuietly(in);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -152,9 +156,9 @@ public class ChapterUtils {
|
||||
return chapters;
|
||||
}
|
||||
|
||||
private static void readOggChaptersFromPlayableStreamUrl(Playable media, Context context) {
|
||||
private static List<Chapter> readOggChaptersFromPlayableStreamUrl(Playable media, Context context) {
|
||||
if (media == null || !media.streamAvailable()) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
InputStream input = null;
|
||||
try {
|
||||
@ -168,34 +172,36 @@ public class ChapterUtils {
|
||||
input = urlConnection.getInputStream();
|
||||
}
|
||||
if (input != null) {
|
||||
readOggChaptersFromInputStream(media, input);
|
||||
return readOggChaptersFromInputStream(media, input);
|
||||
}
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(input);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void readOggChaptersFromPlayableFileUrl(Playable media) {
|
||||
private static List<Chapter> readOggChaptersFromPlayableFileUrl(Playable media) {
|
||||
if (media == null || media.getLocalMediaUrl() == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
File source = new File(media.getLocalMediaUrl());
|
||||
if (source.exists()) {
|
||||
InputStream input = null;
|
||||
try {
|
||||
input = new BufferedInputStream(new FileInputStream(source));
|
||||
readOggChaptersFromInputStream(media, input);
|
||||
return readOggChaptersFromInputStream(media, input);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
} finally {
|
||||
IOUtils.closeQuietly(input);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void readOggChaptersFromInputStream(Playable p, InputStream input) {
|
||||
private static List<Chapter> readOggChaptersFromInputStream(Playable p, InputStream input) {
|
||||
Log.d(TAG, "Trying to read chapters from item with title " + p.getEpisodeTitle());
|
||||
try {
|
||||
VorbisCommentChapterReader reader = new VorbisCommentChapterReader();
|
||||
@ -203,19 +209,20 @@ public class ChapterUtils {
|
||||
List<Chapter> chapters = reader.getChapters();
|
||||
if (chapters == null) {
|
||||
Log.i(TAG, "ChapterReader could not find any Ogg vorbis chapters");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
Collections.sort(chapters, new ChapterStartTimeComparator());
|
||||
enumerateEmptyChapterTitles(chapters);
|
||||
if (chaptersValid(chapters)) {
|
||||
p.setChapters(chapters);
|
||||
Log.i(TAG, "Chapters loaded");
|
||||
return chapters;
|
||||
} else {
|
||||
Log.e(TAG, "Chapter data was invalid");
|
||||
}
|
||||
} catch (VorbisCommentReaderException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +99,7 @@ public class ExternalMedia implements Playable {
|
||||
e.printStackTrace();
|
||||
throw new PlayableException("NumberFormatException when reading duration of media file");
|
||||
}
|
||||
ChapterUtils.loadChaptersFromFileUrl(this);
|
||||
setChapters(ChapterUtils.loadChaptersFromFileUrl(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +130,7 @@ public class RemoteMedia implements Playable {
|
||||
|
||||
@Override
|
||||
public void loadChapterMarks(Context context) {
|
||||
ChapterUtils.loadChaptersFromStreamUrl(this, context);
|
||||
setChapters(ChapterUtils.loadChaptersFromStreamUrl(this, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,6 +41,7 @@
|
||||
<string name="statistics_speed_not_counted">Notice: Playback speed is never taken into account.</string>
|
||||
<string name="statistics_reset_data">Reset statistics data</string>
|
||||
<string name="statistics_reset_data_msg">This will erase the history of duration played for all episodes. Are you sure you want to proceed?</string>
|
||||
<string name="statistics_counting_since">Since %s,\nyou played</string>
|
||||
|
||||
<!-- Download Statistics fragment -->
|
||||
<string name="total_size_downloaded_podcasts">Total size of episodes on the device:</string>
|
||||
@ -269,7 +270,8 @@
|
||||
<string name="confirm_mobile_download_dialog_message">Downloading over mobile data connection is disabled in the settings.\n\nDo you want to allow downloading temporarily?\n\n<small>Your choice will be remembered for 10 minutes.</small></string>
|
||||
<string name="confirm_mobile_streaming_notification_title">Confirm Mobile streaming</string>
|
||||
<string name="confirm_mobile_streaming_notification_message">Streaming over mobile data connection is disabled in the settings. Tap to stream anyway.</string>
|
||||
<string name="confirm_mobile_streaming_button_always">Always allow</string>
|
||||
<string name="confirm_mobile_streaming_button_always">Always</string>
|
||||
<string name="confirm_mobile_streaming_button_once">Once</string>
|
||||
<string name="confirm_mobile_download_dialog_only_add_to_queue">Enqueue</string>
|
||||
<string name="confirm_mobile_download_dialog_enable_temporarily">Allow temporarily</string>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user