Merge pull request #2010 from domingos86/chromecast-issue-340-new-mp-protocol
Changes to media player protocol
This commit is contained in:
commit
606c924f3c
|
@ -1,6 +1,8 @@
|
|||
package de.test.antennapod.service.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
@ -20,6 +22,7 @@ import de.danoeh.antennapod.core.feed.Feed;
|
|||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
|
||||
import de.danoeh.antennapod.core.service.playback.LocalPSMP;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
|
@ -112,7 +115,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
|
||||
public void testInit() {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, defaultCallback);
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, new DefaultPSMPCallback());
|
||||
psmp.shutdown();
|
||||
}
|
||||
|
||||
|
@ -138,7 +141,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -159,47 +162,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||
|
@ -217,7 +179,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -238,46 +200,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||
|
@ -296,7 +218,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -320,46 +242,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||
|
@ -376,7 +258,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -403,47 +285,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||
|
@ -459,7 +300,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -480,47 +321,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -537,7 +337,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -558,46 +358,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -614,7 +374,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -638,46 +398,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -693,7 +413,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
try {
|
||||
|
@ -721,46 +441,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -773,58 +453,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
psmp.shutdown();
|
||||
}
|
||||
|
||||
|
||||
private final PlaybackServiceMediaPlayer.PSMPCallback defaultCallback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) { return false; }
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private void pauseTestSkeleton(final PlayerStatus initialState, final boolean stream, final boolean abandonAudioFocus, final boolean reinit, long timeoutSeconds) throws InterruptedException {
|
||||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int latchCount = (stream && reinit) ? 2 : 1;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
|
@ -863,42 +497,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = new AssertionFailedError("Unexpected call to shouldStop");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -955,7 +559,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
(initialState == PlayerStatus.PREPARED) ? 1 : 0;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
|
@ -973,36 +577,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null) {
|
||||
|
@ -1010,11 +584,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) {
|
||||
|
@ -1048,7 +617,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int latchCount = 1;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
|
@ -1062,37 +631,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1101,11 +639,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -1153,7 +686,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
final Context c = getInstrumentation().getTargetContext();
|
||||
final int latchCount = 2;
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||
@Override
|
||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||
checkPSMPInfo(newInfo);
|
||||
|
@ -1169,47 +702,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
if (assertionError == null)
|
||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||
|
@ -1248,4 +746,71 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
|||
super("Unexpected state change: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultPSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback {
|
||||
@Override
|
||||
public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shouldStop() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAbilityChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBufferingUpdate(int percent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaChanged(boolean reloadUI) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStart(@NonNull Playable playable, int position) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackPause(Playable playable, int position) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Playable getNextInQueue(Playable currentMedia) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.danoeh.antennapod.core.feed;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.database.Cursor;
|
||||
|
@ -14,12 +15,16 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
|
||||
public class FeedMedia extends FeedFile implements Playable {
|
||||
|
@ -48,6 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
|
|||
private String mime_type;
|
||||
@Nullable private volatile FeedItem item;
|
||||
private Date playbackCompletionDate;
|
||||
private int startPosition = -1;
|
||||
private int playedDurationWhenStarted;
|
||||
|
||||
// if null: unknown, will be checked
|
||||
private Boolean hasEmbeddedPicture;
|
||||
|
@ -73,6 +80,7 @@ public class FeedMedia extends FeedFile implements Playable {
|
|||
this.duration = duration;
|
||||
this.position = position;
|
||||
this.played_duration = played_duration;
|
||||
this.playedDurationWhenStarted = played_duration;
|
||||
this.size = size;
|
||||
this.mime_type = mime_type;
|
||||
this.playbackCompletionDate = playbackCompletionDate == null
|
||||
|
@ -472,15 +480,59 @@ public class FeedMedia extends FeedFile implements Playable {
|
|||
}
|
||||
setPosition(newPosition);
|
||||
setLastPlayedTime(timeStamp);
|
||||
if(startPosition>=0 && position > startPosition) {
|
||||
setPlayedDuration(playedDurationWhenStarted + position - startPosition);
|
||||
}
|
||||
DBWriter.setFeedMediaPlaybackInformation(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStart() {
|
||||
startPosition = (position > 0) ? position : 0;
|
||||
playedDurationWhenStarted = played_duration;
|
||||
}
|
||||
@Override
|
||||
public void onPlaybackCompleted() {
|
||||
|
||||
@Override
|
||||
public void onPlaybackPause(Context context) {
|
||||
if (position > startPosition) {
|
||||
played_duration = playedDurationWhenStarted + position - startPosition;
|
||||
playedDurationWhenStarted = played_duration;
|
||||
}
|
||||
postPlaybackTasks(context, false);
|
||||
startPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackCompleted(Context context) {
|
||||
postPlaybackTasks(context, true);
|
||||
startPosition = -1;
|
||||
}
|
||||
|
||||
private void postPlaybackTasks(Context context, boolean completed) {
|
||||
if (item != null) {
|
||||
// gpodder play action
|
||||
if (startPosition >= 0 && (completed || startPosition < position) &&
|
||||
GpodnetPreferences.loggedIn()) {
|
||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.PLAY)
|
||||
.currentDeviceId()
|
||||
.currentTimestamp()
|
||||
.started(startPosition / 1000)
|
||||
.position((completed ? duration : position) / 1000)
|
||||
.total(duration / 1000)
|
||||
.build();
|
||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
||||
}
|
||||
// Auto flattr
|
||||
float autoFlattrThreshold = UserPreferences.getAutoFlattrPlayedDurationThreshold();
|
||||
if (FlattrUtils.hasToken() &&
|
||||
UserPreferences.isAutoFlattr() &&
|
||||
item.getPaymentLink() != null &&
|
||||
item.getFlattrStatus().getUnflattred() &&
|
||||
(completed && autoFlattrThreshold <= 1.0f ||
|
||||
played_duration >= autoFlattrThreshold * duration)) {
|
||||
DBTasks.flattrItemIfLoggedIn(context, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.antennapod.audio.MediaPlayer;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -140,10 +141,13 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
}
|
||||
// set temporarily to pause in order to update list with current position
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
callback.onPlaybackPause(media, getPosition());
|
||||
}
|
||||
|
||||
smartMarkAsPlayed(media);
|
||||
if (!media.getIdentifier().equals(playable.getIdentifier())) {
|
||||
final Playable oldMedia = media;
|
||||
executor.submit(() -> callback.onPostPlayback(oldMedia, false, true));
|
||||
}
|
||||
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||
}
|
||||
|
@ -199,6 +203,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Log.d(TAG, "Audiofocus successfully requested");
|
||||
Log.d(TAG, "Resuming/Starting playback");
|
||||
acquireWifiLockIfNecessary();
|
||||
float speed = 1.0f;
|
||||
try {
|
||||
|
@ -220,8 +226,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
|
||||
setPlayerStatus(PlayerStatus.PLAYING, media);
|
||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||
media.onPlaybackStart();
|
||||
|
||||
} else {
|
||||
Log.e(TAG, "Failed to request audio focus");
|
||||
}
|
||||
|
@ -249,7 +253,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
Log.d(TAG, "Pausing playback.");
|
||||
mediaPlayer.pause();
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media, getPosition());
|
||||
|
||||
if (abandonFocus) {
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
|
@ -311,11 +315,12 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
|
||||
}
|
||||
|
||||
// TODO this call has no effect!
|
||||
if (media.getPosition() > 0) {
|
||||
seekToSync(media.getPosition());
|
||||
}
|
||||
|
||||
if (media.getDuration() == 0) {
|
||||
if (media.getDuration() <= 0) {
|
||||
Log.d(TAG, "Setting duration of media");
|
||||
media.setDuration(mediaPlayer.getDuration());
|
||||
}
|
||||
|
@ -367,10 +372,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
if (playerStatus == PlayerStatus.PLAYING
|
||||
|| playerStatus == PlayerStatus.PAUSED
|
||||
|| playerStatus == PlayerStatus.PREPARED) {
|
||||
if (!stream) {
|
||||
statusBeforeSeeking = playerStatus;
|
||||
setPlayerStatus(PlayerStatus.SEEKING, media);
|
||||
}
|
||||
if(seekLatch != null && seekLatch.getCount() > 0) {
|
||||
try {
|
||||
seekLatch.await(3, TimeUnit.SECONDS);
|
||||
|
@ -379,6 +380,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
}
|
||||
}
|
||||
seekLatch = new CountDownLatch(1);
|
||||
statusBeforeSeeking = playerStatus;
|
||||
setPlayerStatus(PlayerStatus.SEEKING, media, getPosition());
|
||||
mediaPlayer.seekTo(t);
|
||||
try {
|
||||
seekLatch.await(3, TimeUnit.SECONDS);
|
||||
|
@ -752,8 +755,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
|
||||
|
||||
@Override
|
||||
public void endPlayback(final boolean wasSkipped, boolean switchingPlayers) {
|
||||
executor.submit(() -> {
|
||||
protected Future<?> endPlayback(final boolean wasSkipped, final boolean shouldContinue, final boolean toStoppedState) {
|
||||
return executor.submit(() -> {
|
||||
playerLock.lock();
|
||||
releaseWifiLockIfNecessary();
|
||||
|
||||
|
@ -762,13 +765,58 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
||||
}
|
||||
// we're relying on the position stored in the Playable object for post-playback processing
|
||||
if (media != null) {
|
||||
int position = getPosition();
|
||||
if (position >= 0) {
|
||||
media.setPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.reset();
|
||||
|
||||
}
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
|
||||
|
||||
final Playable currentMedia = media;
|
||||
Playable nextMedia = null;
|
||||
|
||||
if (shouldContinue) {
|
||||
// Load next episode if previous episode was in the queue and if there
|
||||
// is an episode in the queue left.
|
||||
// Start playback immediately if continuous playback is enabled
|
||||
nextMedia = callback.getNextInQueue(currentMedia);
|
||||
|
||||
boolean playNextEpisode = isPlaying &&
|
||||
nextMedia != null &&
|
||||
UserPreferences.isFollowQueue();
|
||||
|
||||
if (playNextEpisode) {
|
||||
Log.d(TAG, "Playback of next episode will start immediately.");
|
||||
} else if (nextMedia == null){
|
||||
Log.d(TAG, "No more episodes available to play");
|
||||
} else {
|
||||
Log.d(TAG, "Loading next episode, but not playing automatically.");
|
||||
}
|
||||
|
||||
if (nextMedia != null) {
|
||||
callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode);
|
||||
// setting media to null signals to playMediaObject() that we're taking care of post-playback processing
|
||||
media = null;
|
||||
playMediaObject(nextMedia, false, !nextMedia.localFileAvailable(), playNextEpisode, playNextEpisode);
|
||||
}
|
||||
}
|
||||
if (shouldContinue || toStoppedState) {
|
||||
if (nextMedia == null) {
|
||||
callback.onPlaybackEnded(null, true);
|
||||
stop();
|
||||
}
|
||||
final boolean hasNext = nextMedia != null;
|
||||
|
||||
executor.submit(() -> callback.onPostPlayback(currentMedia, !wasSkipped, hasNext));
|
||||
} else if (isPlaying) {
|
||||
callback.onPlaybackPause(currentMedia, currentMedia.getPosition());
|
||||
}
|
||||
playerLock.unlock();
|
||||
});
|
||||
}
|
||||
|
@ -779,8 +827,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
|
||||
* abandoning audio focus have to be done with other methods.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
private void stop() {
|
||||
executor.submit(() -> {
|
||||
playerLock.lock();
|
||||
releaseWifiLockIfNecessary();
|
||||
|
@ -833,7 +880,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
mp -> genericOnCompletion();
|
||||
|
||||
private void genericOnCompletion() {
|
||||
endPlayback(false, false);
|
||||
endPlayback(false, true, true);
|
||||
}
|
||||
|
||||
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
|
||||
|
@ -889,8 +936,11 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
|||
seekLatch.countDown();
|
||||
}
|
||||
playerLock.lock();
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
callback.onPlaybackStart(media, getPosition());
|
||||
}
|
||||
if (playerStatus == PlayerStatus.SEEKING) {
|
||||
setPlayerStatus(statusBeforeSeeking, media);
|
||||
setPlayerStatus(statusBeforeSeeking, media, getPosition());
|
||||
}
|
||||
playerLock.unlock();
|
||||
});
|
||||
|
|
|
@ -52,9 +52,6 @@ import de.danoeh.antennapod.core.feed.FeedItem;
|
|||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
|
||||
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
|
||||
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
||||
|
@ -63,7 +60,6 @@ import de.danoeh.antennapod.core.storage.DBTasks;
|
|||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.IntList;
|
||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
||||
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
|
||||
|
@ -206,8 +202,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
*/
|
||||
private MediaSessionCompat mediaSession;
|
||||
|
||||
private int startPosition;
|
||||
|
||||
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
@ -473,7 +467,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
UserPreferences.shouldHardwareButtonSkip()) {
|
||||
// assume the skip command comes from a notification or the lockscreen
|
||||
// a >| skip button should actually skip
|
||||
mediaPlayer.endPlayback(true, false);
|
||||
mediaPlayer.skip();
|
||||
} else {
|
||||
// assume skip command comes from a (bluetooth) media button
|
||||
// user actually wants to fast-forward
|
||||
|
@ -530,7 +524,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
|
||||
@Override
|
||||
public void positionSaverTick() {
|
||||
saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL);
|
||||
saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -582,9 +576,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
break;
|
||||
|
||||
case PAUSED:
|
||||
taskManager.cancelPositionSaver();
|
||||
saveCurrentPosition(false, 0);
|
||||
taskManager.cancelWidgetUpdater();
|
||||
if ((UserPreferences.isPersistNotify() || isCasting) &&
|
||||
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// do not remove notification on pause based on user pref and whether android version supports expanded notifications
|
||||
|
@ -595,22 +586,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
stopForeground(true);
|
||||
}
|
||||
writePlayerStatusPlaybackPreferences();
|
||||
|
||||
final Playable playable = newInfo.playable;
|
||||
|
||||
// Gpodder: send play action
|
||||
if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) {
|
||||
FeedMedia media = (FeedMedia) playable;
|
||||
FeedItem item = media.getItem();
|
||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
|
||||
.currentDeviceId()
|
||||
.currentTimestamp()
|
||||
.started(startPosition / 1000)
|
||||
.position(getCurrentPosition() / 1000)
|
||||
.total(getDuration() / 1000)
|
||||
.build();
|
||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
||||
}
|
||||
break;
|
||||
|
||||
case STOPPED:
|
||||
|
@ -619,15 +594,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
break;
|
||||
|
||||
case PLAYING:
|
||||
Log.d(TAG, "Audiofocus successfully requested");
|
||||
Log.d(TAG, "Resuming/Starting playback");
|
||||
|
||||
taskManager.startPositionSaver();
|
||||
taskManager.startWidgetUpdater();
|
||||
writePlayerStatusPlaybackPreferences();
|
||||
setupNotification(newInfo);
|
||||
started = true;
|
||||
startPosition = mediaPlayer.getPosition();
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
|
@ -700,121 +669,168 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers);
|
||||
return true;
|
||||
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext) {
|
||||
PlaybackService.this.onPostPlayback(media, ended, playingNext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStart(@NonNull Playable playable, int position) {
|
||||
taskManager.startWidgetUpdater();
|
||||
if (position != PlaybackServiceMediaPlayer.INVALID_TIME) {
|
||||
playable.setPosition(position);
|
||||
}
|
||||
playable.onPlaybackStart();
|
||||
taskManager.startPositionSaver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackPause(Playable playable, int position) {
|
||||
taskManager.cancelPositionSaver();
|
||||
saveCurrentPosition(position == PlaybackServiceMediaPlayer.INVALID_TIME || playable == null,
|
||||
playable, position);
|
||||
taskManager.cancelWidgetUpdater();
|
||||
if (playable != null) {
|
||||
playable.onPlaybackPause(getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Playable getNextInQueue(Playable currentMedia) {
|
||||
return PlaybackService.this.getNextInQueue(currentMedia);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
|
||||
PlaybackService.this.onPlaybackEnded(mediaType, stopPlaying);
|
||||
}
|
||||
};
|
||||
|
||||
private void endPlayback(final Playable playable, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
||||
Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": ""));
|
||||
private Playable getNextInQueue(final Playable currentMedia) {
|
||||
if (!(currentMedia instanceof FeedMedia)) {
|
||||
Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding");
|
||||
return null;
|
||||
}
|
||||
if (!ClientConfig.playbackServiceCallbacks.useQueue()) {
|
||||
Log.d(TAG, "getNextInQueue(), but queue not in use by this app");
|
||||
return null;
|
||||
}
|
||||
Log.d(TAG, "getNextInQueue()");
|
||||
FeedMedia media = (FeedMedia) currentMedia;
|
||||
try {
|
||||
media.loadMetadata();
|
||||
} catch (Playable.PlayableException e) {
|
||||
Log.e(TAG, "Unable to load metadata to get next in queue", e);
|
||||
return null;
|
||||
}
|
||||
FeedItem item = media.getItem();
|
||||
if (item == null) {
|
||||
Log.w(TAG, "getNextInQueue() with FeedMedia object whose FeedItem is null");
|
||||
return null;
|
||||
}
|
||||
FeedItem nextItem;
|
||||
try {
|
||||
final List<FeedItem> queue = taskManager.getQueue();
|
||||
nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error handling the queue in order to retrieve the next item", e);
|
||||
return null;
|
||||
}
|
||||
return (nextItem != null)? nextItem.getMedia() : null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of instructions to be performed when playback ends.
|
||||
*/
|
||||
private void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
|
||||
Log.d(TAG, "Playback ended");
|
||||
if (stopPlaying) {
|
||||
taskManager.cancelPositionSaver();
|
||||
writePlaybackPreferencesNoMediaPlaying();
|
||||
if (!isCasting) {
|
||||
stopForeground(true);
|
||||
}
|
||||
stopWidgetUpdater();
|
||||
}
|
||||
if (mediaType == null) {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
|
||||
} else {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
|
||||
isCasting ? EXTRA_CODE_CAST :
|
||||
(mediaType == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method processes the media object after its playback ended, either because it completed
|
||||
* or because a different media object was selected for playback.
|
||||
*
|
||||
* Even though these tasks aren't supposed to be resource intensive, a good practice is to
|
||||
* usually call this method on a background thread.
|
||||
*
|
||||
* @param playable the media object that was playing. It is assumed that its position property
|
||||
* was updated before this method was called.
|
||||
* @param ended if true, it signals that {@param playable} was played until its end.
|
||||
* In such case, the position property of the media becomes irrelevant for most of
|
||||
* the tasks (although it's still a good practice to keep it accurate).
|
||||
* @param playingNext if true, it means another media object is being loaded in place of this one.
|
||||
* Instances when we'd set it to false would be when we're not following the
|
||||
* queue or when the queue has ended.
|
||||
*/
|
||||
private void onPostPlayback(final Playable playable, boolean ended, boolean playingNext) {
|
||||
if (playable == null) {
|
||||
Log.e(TAG, "Cannot end playback: media was null");
|
||||
Log.e(TAG, "Cannot do post-playback processing: media was null");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "onPostPlayback(): media=" + playable.getEpisodeTitle());
|
||||
|
||||
taskManager.cancelPositionSaver();
|
||||
if (!(playable instanceof FeedMedia)) {
|
||||
Log.d(TAG, "Not doing post-playback processing: media not of type FeedMedia");
|
||||
if (ended) {
|
||||
playable.onPlaybackCompleted(getApplicationContext());
|
||||
} else {
|
||||
playable.onPlaybackPause(getApplicationContext());
|
||||
}
|
||||
return;
|
||||
}
|
||||
FeedMedia media = (FeedMedia) playable;
|
||||
FeedItem item = media.getItem();
|
||||
boolean smartMarkAsPlayed = playingNext && media.hasAlmostEnded();
|
||||
if (!ended && smartMarkAsPlayed) {
|
||||
Log.d(TAG, "smart mark as played");
|
||||
}
|
||||
|
||||
boolean isInQueue = false;
|
||||
FeedItem nextItem = null;
|
||||
if (ended || smartMarkAsPlayed) {
|
||||
media.onPlaybackCompleted(getApplicationContext());
|
||||
} else {
|
||||
media.onPlaybackPause(getApplicationContext());
|
||||
}
|
||||
|
||||
if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) {
|
||||
FeedMedia media = (FeedMedia) playable;
|
||||
FeedItem item = media.getItem();
|
||||
|
||||
if (!switchingPlayers) {
|
||||
if (item != null) {
|
||||
if (ended || smartMarkAsPlayed ||
|
||||
!UserPreferences.shouldSkipKeepEpisode()) {
|
||||
// only mark the item as played if we're not keeping it anyways
|
||||
DBWriter.markItemPlayed(item, FeedItem.PLAYED, ended);
|
||||
try {
|
||||
final List<FeedItem> queue = taskManager.getQueue();
|
||||
isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
|
||||
nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
|
||||
if (QueueAccess.ItemListAccess(queue).contains(item.getId())) {
|
||||
// don't know if it actually matters to not autodownload when smart mark as played is triggered
|
||||
DBWriter.removeQueueItem(PlaybackService.this, item, ended);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
// isInQueue remains false
|
||||
}
|
||||
|
||||
boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode();
|
||||
|
||||
if (!shouldKeep) {
|
||||
// only mark the item as played if we're not keeping it anyways
|
||||
DBWriter.markItemPlayed(item, FeedItem.PLAYED, true);
|
||||
|
||||
if (isInQueue) {
|
||||
DBWriter.removeQueueItem(PlaybackService.this, item, true);
|
||||
}
|
||||
|
||||
// Delete episode if enabled
|
||||
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
|
||||
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
|
||||
Log.d(TAG, "Episode Deleted");
|
||||
}
|
||||
// Delete episode if enabled
|
||||
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
|
||||
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
|
||||
Log.d(TAG, "Episode Deleted");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DBWriter.addItemToPlaybackHistory(media);
|
||||
|
||||
// auto-flattr if enabled
|
||||
if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
|
||||
DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
|
||||
}
|
||||
|
||||
// gpodder play action
|
||||
if(GpodnetPreferences.loggedIn()) {
|
||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
|
||||
.currentDeviceId()
|
||||
.currentTimestamp()
|
||||
.started(startPosition / 1000)
|
||||
.position(getDuration() / 1000)
|
||||
.total(getDuration() / 1000)
|
||||
.build();
|
||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
if (!switchingPlayers) {
|
||||
// Load next episode if previous episode was in the queue and if there
|
||||
// is an episode in the queue left.
|
||||
// Start playback immediately if continuous playback is enabled
|
||||
Playable nextMedia = null;
|
||||
boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() &&
|
||||
isInQueue &&
|
||||
nextItem != null;
|
||||
|
||||
playNextEpisode = playNextEpisode &&
|
||||
loadNextItem &&
|
||||
UserPreferences.isFollowQueue();
|
||||
|
||||
if (loadNextItem) {
|
||||
Log.d(TAG, "Loading next item in queue");
|
||||
nextMedia = nextItem.getMedia();
|
||||
}
|
||||
final boolean prepareImmediately;
|
||||
final boolean startWhenPrepared;
|
||||
final boolean stream;
|
||||
|
||||
if (playNextEpisode) {
|
||||
Log.d(TAG, "Playback of next episode will start immediately.");
|
||||
prepareImmediately = startWhenPrepared = true;
|
||||
} else {
|
||||
Log.d(TAG, "No more episodes available to play");
|
||||
prepareImmediately = startWhenPrepared = false;
|
||||
stopForeground(true);
|
||||
stopWidgetUpdater();
|
||||
}
|
||||
|
||||
writePlaybackPreferencesNoMediaPlaying();
|
||||
if (nextMedia != null) {
|
||||
stream = !nextMedia.localFileAvailable();
|
||||
mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
|
||||
isCasting ? EXTRA_CODE_CAST :
|
||||
(nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
|
||||
} else {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
|
||||
mediaPlayer.stop();
|
||||
//stopSelf();
|
||||
}
|
||||
if (ended || playingNext) {
|
||||
DBWriter.addItemToPlaybackHistory(media);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1218,28 +1234,23 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
/**
|
||||
* Persists the current position and last played time of the media file.
|
||||
*
|
||||
* @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects
|
||||
* @param deltaPlayedDuration value by which played_duration should be increased.
|
||||
* @param fromMediaPlayer if true, the information is gathered from the current Media Player
|
||||
* and {@param playable} and {@param position} become irrelevant.
|
||||
* @param playable the playable for which the current position should be saved, unless
|
||||
* {@param fromMediaPlayer} is true.
|
||||
* @param position the position that should be saved, unless {@param fromMediaPlayer} is true.
|
||||
*/
|
||||
private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
|
||||
int position = getCurrentPosition();
|
||||
int duration = getDuration();
|
||||
float playbackSpeed = getCurrentPlaybackSpeed();
|
||||
final Playable playable = mediaPlayer.getPlayable();
|
||||
private synchronized void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
|
||||
int duration;
|
||||
if (fromMediaPlayer) {
|
||||
position = getCurrentPosition();
|
||||
duration = getDuration();
|
||||
playable = mediaPlayer.getPlayable();
|
||||
} else {
|
||||
duration = playable.getDuration();
|
||||
}
|
||||
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
|
||||
Log.d(TAG, "Saving current position to " + position);
|
||||
if (updatePlayedDuration && playable instanceof FeedMedia) {
|
||||
FeedMedia media = (FeedMedia) playable;
|
||||
FeedItem item = media.getItem();
|
||||
media.setPlayedDuration(media.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed)));
|
||||
// Auto flattr
|
||||
if (isAutoFlattrable(media) &&
|
||||
(media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
|
||||
Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration())
|
||||
+ " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration));
|
||||
DBTasks.flattrItemIfLoggedIn(this, item);
|
||||
}
|
||||
}
|
||||
playable.saveCurrentPosition(
|
||||
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()),
|
||||
position,
|
||||
|
@ -1407,7 +1418,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
public void onReceive(Context context, Intent intent) {
|
||||
if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
|
||||
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
|
||||
mediaPlayer.endPlayback(true, false);
|
||||
mediaPlayer.skip();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1500,26 +1511,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
|
||||
|
||||
public void seekTo(final int t) {
|
||||
if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING
|
||||
&& GpodnetPreferences.loggedIn()) {
|
||||
final Playable playable = mediaPlayer.getPlayable();
|
||||
if (playable instanceof FeedMedia) {
|
||||
FeedMedia media = (FeedMedia) playable;
|
||||
FeedItem item = media.getItem();
|
||||
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
|
||||
.currentDeviceId()
|
||||
.currentTimestamp()
|
||||
.started(startPosition / 1000)
|
||||
.position(getCurrentPosition() / 1000)
|
||||
.total(getDuration() / 1000)
|
||||
.build();
|
||||
GpodnetPreferences.enqueueEpisodeAction(action);
|
||||
}
|
||||
}
|
||||
mediaPlayer.seekTo(t);
|
||||
if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING ) {
|
||||
startPosition = t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1528,10 +1520,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
/**
|
||||
* @see LocalPSMP#seekToChapter(de.danoeh.antennapod.core.feed.Chapter)
|
||||
* Seek to the start of the specified chapter.
|
||||
*/
|
||||
public void seekToChapter(Chapter c) {
|
||||
mediaPlayer.seekToChapter(c);
|
||||
seekTo((int) c.getStart());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1558,15 +1550,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
return mediaPlayer.getVideoSize();
|
||||
}
|
||||
|
||||
private boolean isAutoFlattrable(FeedMedia media) {
|
||||
if (media != null) {
|
||||
FeedItem item = media.getItem();
|
||||
return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
|
||||
|
||||
private static final String TAG = "MediaSessionCompat";
|
||||
|
@ -1602,19 +1585,14 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
public void onPause() {
|
||||
Log.d(TAG, "onPause()");
|
||||
if (getStatus() == PlayerStatus.PLAYING) {
|
||||
pause(false, true);
|
||||
}
|
||||
if (UserPreferences.isPersistNotify()) {
|
||||
pause(false, true);
|
||||
} else {
|
||||
pause(true, true);
|
||||
pause(!UserPreferences.isPersistNotify(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
Log.d(TAG, "onStop()");
|
||||
mediaPlayer.stop();
|
||||
mediaPlayer.stopPlayback(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1639,7 +1617,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
public void onSkipToNext() {
|
||||
Log.d(TAG, "onSkipToNext()");
|
||||
if(UserPreferences.shouldHardwareButtonSkip()) {
|
||||
mediaPlayer.endPlayback(true, false);
|
||||
mediaPlayer.skip();
|
||||
} else {
|
||||
seekDelta(UserPreferences.getFastFowardSecs() * 1000);
|
||||
}
|
||||
|
@ -1682,7 +1660,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
PlaybackServiceMediaPlayer getMediaPlayer();
|
||||
void setIsCasting(boolean isCasting);
|
||||
void sendNotificationBroadcast(int type, int code);
|
||||
void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration);
|
||||
void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position);
|
||||
void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
|
||||
MediaSessionCompat getMediaSession();
|
||||
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
|
||||
|
@ -1716,8 +1694,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
|
||||
PlaybackService.this.saveCurrentPosition(updatePlayedDuration, deltaPlayedDuration);
|
||||
public void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
|
||||
PlaybackService.this.saveCurrentPosition(fromMediaPlayer, playable, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,11 +8,9 @@ import android.util.Log;
|
|||
import android.util.Pair;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.Chapter;
|
||||
import de.danoeh.antennapod.core.feed.FeedItem;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
|
||||
|
||||
|
@ -129,13 +127,6 @@ public abstract class PlaybackServiceMediaPlayer {
|
|||
*/
|
||||
public abstract void seekDelta(int d);
|
||||
|
||||
/**
|
||||
* Seek to the start of the specified chapter.
|
||||
*/
|
||||
public void seekToChapter(@NonNull Chapter c) {
|
||||
seekTo((int) c.getStart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved.
|
||||
*/
|
||||
|
@ -236,15 +227,44 @@ public abstract class PlaybackServiceMediaPlayer {
|
|||
|
||||
protected abstract void setPlayable(Playable playable);
|
||||
|
||||
public abstract void endPlayback(boolean wasSkipped, boolean switchingPlayers);
|
||||
public void skip() {
|
||||
endPlayback(true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the PSMP into STOPPED state. This call is only valid if the player is currently in
|
||||
* INDETERMINATE state, for example after a call to endPlayback.
|
||||
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
|
||||
* abandoning audio focus have to be done with other methods.
|
||||
* Ends playback of current media (if any) and moves into INDETERMINATE state, unless
|
||||
* {@param toStoppedState} is set to true, in which case it moves into STOPPED state.
|
||||
*
|
||||
* @see #endPlayback(boolean, boolean, boolean)
|
||||
*/
|
||||
public abstract void stop();
|
||||
public Future<?> stopPlayback(boolean toStoppedState) {
|
||||
return endPlayback(true, false, toStoppedState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that handles end of playback.
|
||||
*
|
||||
* Currently, it has 4 use cases:
|
||||
* <ul>
|
||||
* <li>Media playback has completed: call with (false, true, true)</li>
|
||||
* <li>User asks to skip to next episode: call with (true, true, true)</li>
|
||||
* <li>Stopping the media player: call with (true, false, true)</li>
|
||||
* <li>We want to change the media player implementation: call with (true, false, false)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param wasSkipped If true, we assume the current media's playback has ended, for
|
||||
* purposes of post playback processing.
|
||||
* @param shouldContinue If true, the media player should try to load, and possibly play,
|
||||
* the next item, based on the user preferences and whether such item
|
||||
* exists.
|
||||
* @param toStoppedState If true, the playback state gets set to STOPPED if the media player
|
||||
* is not loading/playing after this call, and the UI will reflect that.
|
||||
* Only relevant if {@param shouldContinue} is set to false, otherwise
|
||||
* this method's behavior defaults as if this parameter was true.
|
||||
*
|
||||
* @return a Future, just for the purpose of tracking its execution.
|
||||
*/
|
||||
protected abstract Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState);
|
||||
|
||||
/**
|
||||
* @return {@code true} if the WifiLock feature should be used, {@code false} otherwise.
|
||||
|
@ -274,41 +294,39 @@ public abstract class PlaybackServiceMediaPlayer {
|
|||
* <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).
|
||||
* <p/>
|
||||
* It will also call {@link PSMPCallback#onPlaybackPause(Playable, int)} or {@link PSMPCallback#onPlaybackStart(Playable, int)}
|
||||
* depending on the status change.
|
||||
*
|
||||
* @param newStatus The new PlayerStatus. This must not be null.
|
||||
* @param newMedia The new playable object of the PSMP object. This can be null.
|
||||
* @param position The position to be set to the current Playable object in case playback started or paused.
|
||||
* Will be ignored if given the value of {@link #INVALID_TIME}.
|
||||
*/
|
||||
protected final synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
|
||||
protected final synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia, int position) {
|
||||
Log.d(TAG, this.getClass().getSimpleName() + ": Setting player status to " + newStatus);
|
||||
|
||||
PlayerStatus oldStatus = playerStatus;
|
||||
|
||||
this.playerStatus = newStatus;
|
||||
setPlayable(newMedia);
|
||||
|
||||
if (playerStatus != null) {
|
||||
Log.d(TAG, "playerStatus: " + playerStatus.toString());
|
||||
if (newMedia != null && newStatus != PlayerStatus.INDETERMINATE) {
|
||||
if (oldStatus == PlayerStatus.PLAYING && newStatus != PlayerStatus.PLAYING) {
|
||||
callback.onPlaybackPause(newMedia, position);
|
||||
} else if (oldStatus != PlayerStatus.PLAYING && newStatus == PlayerStatus.PLAYING) {
|
||||
callback.onPlaybackStart(newMedia, position);
|
||||
}
|
||||
}
|
||||
|
||||
callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
|
||||
}
|
||||
|
||||
protected void smartMarkAsPlayed(Playable media) {
|
||||
if(media != null && media instanceof FeedMedia) {
|
||||
FeedMedia oldMedia = (FeedMedia) media;
|
||||
if(oldMedia.hasAlmostEnded()) {
|
||||
Log.d(TAG, "smart mark as read");
|
||||
FeedItem item = oldMedia.getItem();
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
|
||||
DBWriter.removeQueueItem(context, item, false);
|
||||
DBWriter.addItemToPlaybackHistory(oldMedia);
|
||||
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
|
||||
Log.d(TAG, "Delete " + oldMedia.toString());
|
||||
DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @see #setPlayerStatus(PlayerStatus, Playable, int)
|
||||
*/
|
||||
protected final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
|
||||
setPlayerStatus(newStatus, newMedia, INVALID_TIME);
|
||||
}
|
||||
|
||||
public interface PSMPCallback {
|
||||
|
@ -328,7 +346,15 @@ public abstract class PlaybackServiceMediaPlayer {
|
|||
|
||||
boolean onMediaPlayerError(Object inObj, int what, int extra);
|
||||
|
||||
boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
|
||||
void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext);
|
||||
|
||||
void onPlaybackStart(@NonNull Playable playable, int position);
|
||||
|
||||
void onPlaybackPause(Playable playable, int position);
|
||||
|
||||
Playable getNextInQueue(Playable currentMedia);
|
||||
|
||||
void onPlaybackEnded(MediaType mediaType, boolean stopPlaying);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.danoeh.antennapod.core.util.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
|
@ -205,7 +206,12 @@ public class ExternalMedia implements Playable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackCompleted() {
|
||||
public void onPlaybackPause(Context context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackCompleted(Context context) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -138,14 +138,27 @@ public interface Playable extends Parcelable,
|
|||
void setLastPlayedTime(long lastPlayedTimestamp);
|
||||
|
||||
/**
|
||||
* Is called by the PlaybackService when playback starts.
|
||||
* This method should be called every time playback starts on this object.
|
||||
* <p/>
|
||||
* Position held by this Playable should be set accurately before a call to this method is made.
|
||||
*/
|
||||
void onPlaybackStart();
|
||||
|
||||
/**
|
||||
* Is called by the PlaybackService when playback is completed.
|
||||
* This method should be called every time playback pauses or stops on this object,
|
||||
* including just before a seeking operation is performed, after which a call to
|
||||
* {@link #onPlaybackStart()} should be made. If playback completes, calling this method is not
|
||||
* necessary, as long as a call to {@link #onPlaybackCompleted(Context)} is made.
|
||||
* <p/>
|
||||
* Position held by this Playable should be set accurately before a call to this method is made.
|
||||
*/
|
||||
void onPlaybackCompleted();
|
||||
void onPlaybackPause(Context context);
|
||||
|
||||
/**
|
||||
* This method should be called when playback completes for this object.
|
||||
* @param context
|
||||
*/
|
||||
void onPlaybackCompleted(Context context);
|
||||
|
||||
/**
|
||||
* Returns an integer that must be unique among all Playable classes. The
|
||||
|
|
|
@ -142,7 +142,6 @@ public class CastManager extends BaseCastManager implements OnFailedListener {
|
|||
if (ConnectionResult.SUCCESS != GoogleApiAvailability.getInstance()
|
||||
.isGooglePlayServicesAvailable(context)) {
|
||||
Log.e(TAG, "Couldn't find the appropriate version of Google Play Services");
|
||||
//TODO check whether creating an instance without google play services installed actually gives an exception
|
||||
}
|
||||
INSTANCE = new CastManager(context, castConfiguration);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.danoeh.antennapod.core.cast;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
|
@ -255,7 +256,12 @@ public class RemoteMedia implements Playable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackCompleted() {
|
||||
public void onPlaybackPause(Context context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackCompleted(Context context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import android.widget.Toast;
|
|||
import com.google.android.gms.cast.ApplicationMetadata;
|
||||
import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import de.danoeh.antennapod.core.cast.CastConsumer;
|
||||
import de.danoeh.antennapod.core.cast.CastManager;
|
||||
import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
|
||||
|
@ -108,7 +110,7 @@ public class PlaybackServiceFlavorHelper {
|
|||
// to the latest position.
|
||||
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
||||
if (mediaPlayer != null) {
|
||||
callback.saveCurrentPosition(false, 0);
|
||||
callback.saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||
infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo();
|
||||
if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT &&
|
||||
infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) {
|
||||
|
@ -160,7 +162,7 @@ public class PlaybackServiceFlavorHelper {
|
|||
// could be pause, but this way we make sure the new player will get the correct position,
|
||||
// since pause runs asynchronously and we could be directing the new player to play even before
|
||||
// the old player gives us back the position.
|
||||
callback.saveCurrentPosition(false, 0);
|
||||
callback.saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||
}
|
||||
}
|
||||
if (info == null) {
|
||||
|
@ -182,7 +184,11 @@ public class PlaybackServiceFlavorHelper {
|
|||
boolean wasLaunched) {
|
||||
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
||||
if (mediaPlayer != null) {
|
||||
mediaPlayer.endPlayback(true, true);
|
||||
try {
|
||||
mediaPlayer.stopPlayback(false).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.e(TAG, "There was a problem stopping playback while switching media players", e);
|
||||
}
|
||||
mediaPlayer.shutdownQuietly();
|
||||
}
|
||||
mediaPlayer = newPlayer;
|
||||
|
|
|
@ -15,6 +15,8 @@ import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastEx
|
|||
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
|
||||
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.danoeh.antennapod.core.R;
|
||||
|
@ -25,6 +27,7 @@ import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
|
|||
import de.danoeh.antennapod.core.cast.RemoteMedia;
|
||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.MediaType;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||
|
||||
|
@ -42,8 +45,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
private final CastManager castMgr;
|
||||
|
||||
private volatile Playable media;
|
||||
private volatile MediaInfo remoteMedia;
|
||||
private volatile MediaType mediaType;
|
||||
private volatile MediaInfo remoteMedia;
|
||||
private volatile int remoteState;
|
||||
|
||||
private final AtomicBoolean isBuffering;
|
||||
|
||||
|
@ -57,31 +61,28 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
mediaType = null;
|
||||
startWhenPrepared = new AtomicBoolean(false);
|
||||
isBuffering = new AtomicBoolean(false);
|
||||
remoteState = MediaStatus.PLAYER_STATE_UNKNOWN;
|
||||
|
||||
try {
|
||||
if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) {
|
||||
// updates the state, but does not start playing new media if it was going to
|
||||
onRemoteMediaPlayerStatusUpdated(
|
||||
((p, playNextEpisode, wasSkipped, switchingPlayers) ->
|
||||
this.callback.endPlayback(p, false, wasSkipped, switchingPlayers)));
|
||||
onRemoteMediaPlayerStatusUpdated();
|
||||
}
|
||||
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||
Log.e(TAG, "Unable to do initial check for loaded media", e);
|
||||
}
|
||||
|
||||
castMgr.addCastConsumer(castConsumer);
|
||||
//TODO
|
||||
}
|
||||
|
||||
private CastConsumer castConsumer = new DefaultCastConsumer() {
|
||||
@Override
|
||||
public void onRemoteMediaPlayerMetadataUpdated() {
|
||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
|
||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteMediaPlayerStatusUpdated() {
|
||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
|
||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,8 +122,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()");
|
||||
}
|
||||
if (playbackEnded) {
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
||||
callback.endPlayback(media, true, false, false);
|
||||
// This is an unconventional thing to occur...
|
||||
endPlayback(true, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,85 +167,123 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) {
|
||||
private void onRemoteMediaPlayerStatusUpdated() {
|
||||
MediaStatus status = castMgr.getMediaStatus();
|
||||
if (status == null) {
|
||||
Log.d(TAG, "Received null MediaStatus");
|
||||
//setBuffering(false);
|
||||
//setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||
return;
|
||||
} else {
|
||||
Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState());
|
||||
}
|
||||
Playable currentMedia = localVersion(status.getMediaInfo());
|
||||
boolean updateUI = currentMedia != media;
|
||||
if (currentMedia != null) {
|
||||
long position = status.getStreamPosition();
|
||||
if (position > 0 && currentMedia.getPosition() == 0) {
|
||||
currentMedia.setPosition((int) position);
|
||||
}
|
||||
}
|
||||
int state = status.getPlayerState();
|
||||
int oldState = remoteState;
|
||||
remoteMedia = status.getMediaInfo();
|
||||
boolean mediaChanged = !CastUtils.matches(remoteMedia, media);
|
||||
boolean stateChanged = state != oldState;
|
||||
if (!mediaChanged && !stateChanged) {
|
||||
Log.d(TAG, "Both media and state haven't changed, so nothing to do");
|
||||
return;
|
||||
}
|
||||
Playable currentMedia = mediaChanged ? localVersion(remoteMedia) : media;
|
||||
Playable oldMedia = media;
|
||||
int position = (int) status.getStreamPosition();
|
||||
// check for incompatible states
|
||||
if ((state == MediaStatus.PLAYER_STATE_PLAYING || state == MediaStatus.PLAYER_STATE_PAUSED)
|
||||
&& currentMedia == null) {
|
||||
Log.w(TAG, "RemoteMediaPlayer returned playing or pausing state, but with no media");
|
||||
state = MediaStatus.PLAYER_STATE_UNKNOWN;
|
||||
stateChanged = oldState != MediaStatus.PLAYER_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
if (stateChanged) {
|
||||
remoteState = state;
|
||||
}
|
||||
|
||||
if (mediaChanged && stateChanged && oldState == MediaStatus.PLAYER_STATE_PLAYING &&
|
||||
state != MediaStatus.PLAYER_STATE_IDLE) {
|
||||
callback.onPlaybackPause(null, INVALID_TIME);
|
||||
// We don't want setPlayerStatus to handle the onPlaybackPause callback
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
}
|
||||
|
||||
setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING);
|
||||
|
||||
switch (state) {
|
||||
case MediaStatus.PLAYER_STATE_PLAYING:
|
||||
setPlayerStatus(PlayerStatus.PLAYING, currentMedia);
|
||||
if (!stateChanged) {
|
||||
//These steps are necessary because they won't be performed by setPlayerStatus()
|
||||
if (position >= 0) {
|
||||
currentMedia.setPosition(position);
|
||||
}
|
||||
currentMedia.onPlaybackStart();
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.PLAYING, currentMedia, position);
|
||||
break;
|
||||
case MediaStatus.PLAYER_STATE_PAUSED:
|
||||
setPlayerStatus(PlayerStatus.PAUSED, currentMedia);
|
||||
setPlayerStatus(PlayerStatus.PAUSED, currentMedia, position);
|
||||
break;
|
||||
case MediaStatus.PLAYER_STATE_BUFFERING:
|
||||
setPlayerStatus(playerStatus, currentMedia);
|
||||
setPlayerStatus((mediaChanged || playerStatus == PlayerStatus.PREPARING) ?
|
||||
PlayerStatus.PREPARING : PlayerStatus.SEEKING,
|
||||
currentMedia,
|
||||
currentMedia != null ? currentMedia.getPosition() : INVALID_TIME);
|
||||
break;
|
||||
case MediaStatus.PLAYER_STATE_IDLE:
|
||||
int reason = status.getIdleReason();
|
||||
switch (reason) {
|
||||
case MediaStatus.IDLE_REASON_CANCELED:
|
||||
// check if we're already loading something else
|
||||
if (!updateUI || media == null) {
|
||||
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
|
||||
} else {
|
||||
updateUI = false;
|
||||
// Essentially means stopped at the request of a user
|
||||
callback.onPlaybackEnded(null, true);
|
||||
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
|
||||
if (oldMedia != null) {
|
||||
if (position >= 0) {
|
||||
oldMedia.setPosition(position);
|
||||
}
|
||||
callback.onPostPlayback(oldMedia, false, false);
|
||||
}
|
||||
break;
|
||||
// onPlaybackEnded pretty much takes care of updating the UI
|
||||
return;
|
||||
case MediaStatus.IDLE_REASON_INTERRUPTED:
|
||||
// check if we're already loading something else
|
||||
if (!updateUI || media == null) {
|
||||
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
|
||||
} else {
|
||||
updateUI = false;
|
||||
// Means that a request to load a different media was sent
|
||||
// Not sure if currentMedia already reflects the to be loaded one
|
||||
if (mediaChanged && oldState == MediaStatus.PLAYER_STATE_PLAYING) {
|
||||
callback.onPlaybackPause(null, INVALID_TIME);
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
}
|
||||
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
|
||||
break;
|
||||
case MediaStatus.IDLE_REASON_NONE:
|
||||
// This probably only happens when we connected but no command has been sent yet.
|
||||
setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
|
||||
break;
|
||||
case MediaStatus.IDLE_REASON_FINISHED:
|
||||
boolean playing = playerStatus == PlayerStatus.PLAYING;
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
endPlaybackCall.endPlayback(currentMedia,playing, false, false);
|
||||
// endPlayback already updates the UI, so no need to trigger it ourselves
|
||||
updateUI = false;
|
||||
break;
|
||||
// This is our onCompletionListener...
|
||||
if (mediaChanged && currentMedia != null) {
|
||||
media = currentMedia;
|
||||
}
|
||||
endPlayback(false, true, true);
|
||||
return;
|
||||
case MediaStatus.IDLE_REASON_ERROR:
|
||||
Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH,
|
||||
R.string.cast_failed_media_error_skipping);
|
||||
endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false);
|
||||
// endPlayback already updates the UI, so no need to trigger it ourselves
|
||||
updateUI = false;
|
||||
endPlayback(true, true, true);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
||||
//is this right?
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
if (playerStatus != PlayerStatus.INDETERMINATE || media != currentMedia) {
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Remote media state undetermined!");
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||
Log.wtf(TAG, "Remote media state undetermined!");
|
||||
}
|
||||
if (updateUI) {
|
||||
if (mediaChanged) {
|
||||
callback.onMediaChanged(true);
|
||||
if (oldMedia != null) {
|
||||
callback.onPostPlayback(oldMedia, false, currentMedia != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,12 +303,13 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
if (!CastUtils.isCastable(playable)) {
|
||||
Log.d(TAG, "media provided is not compatible with cast device");
|
||||
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable);
|
||||
try {
|
||||
playable.loadMetadata();
|
||||
} catch (Playable.PlayableException e) {
|
||||
Log.e(TAG, "Unable to load metadata of playable", e);
|
||||
Playable nextPlayable = playable;
|
||||
do {
|
||||
nextPlayable = callback.getNextInQueue(nextPlayable);
|
||||
} while (nextPlayable != null && !CastUtils.isCastable(nextPlayable));
|
||||
if (nextPlayable != null) {
|
||||
playMediaObject(nextPlayable, forceReset, stream, startWhenPrepared, prepareImmediately);
|
||||
}
|
||||
callback.endPlayback(playable, startWhenPrepared, true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -281,19 +321,21 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
return;
|
||||
} else {
|
||||
// set temporarily to pause in order to update list with current position
|
||||
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
|
||||
int position = media.getPosition();
|
||||
try {
|
||||
if (castMgr.isRemoteMediaPlaying()) {
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
}
|
||||
isPlaying = castMgr.isRemoteMediaPlaying();
|
||||
position = (int) castMgr.getCurrentMediaPosition();
|
||||
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||
Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", e);
|
||||
// this might end up just being pointless if we need to query the remote device for the position
|
||||
if (playerStatus == PlayerStatus.PLAYING) {
|
||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
||||
}
|
||||
}
|
||||
smartMarkAsPlayed(media);
|
||||
|
||||
if (isPlaying) {
|
||||
callback.onPlaybackPause(media, position);
|
||||
}
|
||||
if (!media.getIdentifier().equals(playable.getIdentifier())) {
|
||||
final Playable oldMedia = media;
|
||||
callback.onPostPlayback(oldMedia, false, true);
|
||||
}
|
||||
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||
}
|
||||
|
@ -301,7 +343,6 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
|
||||
this.media = playable;
|
||||
remoteMedia = remoteVersion(playable);
|
||||
//this.stream = stream;
|
||||
this.mediaType = media.getMediaType();
|
||||
this.startWhenPrepared.set(startWhenPrepared);
|
||||
setPlayerStatus(PlayerStatus.INITIALIZING, media);
|
||||
|
@ -328,8 +369,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
media.getPosition(),
|
||||
media.getLastPlayedTime());
|
||||
castMgr.play(newPosition);
|
||||
} else {
|
||||
castMgr.play();
|
||||
}
|
||||
castMgr.play();
|
||||
} catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||
Log.e(TAG, "Unable to resume remote playback", e);
|
||||
}
|
||||
|
@ -464,8 +506,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
this.startWhenPrepared.set(startWhenPrepared);
|
||||
}
|
||||
|
||||
//TODO I believe some parts of the code make the same decision skipping this check, so that
|
||||
//should be changed as well
|
||||
// As things are right now, changing the return value of this function is not enough to ensure
|
||||
// all other components recognize it.
|
||||
@Override
|
||||
public boolean canSetSpeed() {
|
||||
return false;
|
||||
|
@ -557,23 +599,67 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void endPlayback(boolean wasSkipped, boolean switchingPlayers) {
|
||||
protected Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState) {
|
||||
Log.d(TAG, "endPlayback() called");
|
||||
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
|
||||
try {
|
||||
isPlaying = castMgr.isRemoteMediaPlaying();
|
||||
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||
Log.e(TAG, "Could not determine if media is playing", e);
|
||||
}
|
||||
// TODO make sure we stop playback whenever there's no next episode.
|
||||
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
||||
}
|
||||
callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
|
||||
if (media != null && wasSkipped) {
|
||||
// current position only really matters when we skip
|
||||
int position = getPosition();
|
||||
if (position >= 0) {
|
||||
media.setPosition(position);
|
||||
}
|
||||
}
|
||||
final Playable currentMedia = media;
|
||||
Playable nextMedia = null;
|
||||
if (shouldContinue) {
|
||||
nextMedia = callback.getNextInQueue(currentMedia);
|
||||
|
||||
boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue();
|
||||
if (playNextEpisode) {
|
||||
Log.d(TAG, "Playback of next episode will start immediately.");
|
||||
} else if (nextMedia == null){
|
||||
Log.d(TAG, "No more episodes available to play");
|
||||
} else {
|
||||
Log.d(TAG, "Loading next episode, but not playing automatically.");
|
||||
}
|
||||
|
||||
if (nextMedia != null) {
|
||||
callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode);
|
||||
// setting media to null signals to playMediaObject() that we're taking care of post-playback processing
|
||||
media = null;
|
||||
playMediaObject(nextMedia, false, true /*TODO for now we always stream*/, playNextEpisode, playNextEpisode);
|
||||
}
|
||||
}
|
||||
if (shouldContinue || toStoppedState) {
|
||||
boolean shouldPostProcess = true;
|
||||
if (nextMedia == null) {
|
||||
try {
|
||||
castMgr.stop();
|
||||
shouldPostProcess = false;
|
||||
} catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||
Log.e(TAG, "Unable to stop playback", e);
|
||||
callback.onPlaybackEnded(null, true);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
if (shouldPostProcess) {
|
||||
// Otherwise we rely on the chromecast callback to tell us the playback has stopped.
|
||||
callback.onPostPlayback(currentMedia, !wasSkipped, nextMedia != null);
|
||||
}
|
||||
} else if (isPlaying) {
|
||||
callback.onPlaybackPause(currentMedia,
|
||||
currentMedia != null ? currentMedia.getPosition() : INVALID_TIME);
|
||||
}
|
||||
|
||||
FutureTask<?> future = new FutureTask<>(() -> {}, null);
|
||||
future.run();
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
private void stop() {
|
||||
if (playerStatus == PlayerStatus.INDETERMINATE) {
|
||||
setPlayerStatus(PlayerStatus.STOPPED, null);
|
||||
} else {
|
||||
|
@ -585,8 +671,4 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
|||
protected boolean shouldLockWifi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private interface EndPlaybackCall {
|
||||
boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue