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;
|
package de.test.antennapod.service.playback;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
|
|
||||||
import junit.framework.AssertionFailedError;
|
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.FeedItem;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.FeedPreferences;
|
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.PlaybackServiceMediaPlayer;
|
||||||
import de.danoeh.antennapod.core.service.playback.LocalPSMP;
|
import de.danoeh.antennapod.core.service.playback.LocalPSMP;
|
||||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||||
|
@ -112,7 +115,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
|
|
||||||
public void testInit() {
|
public void testInit() {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, defaultCallback);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, new DefaultPSMPCallback());
|
||||||
psmp.shutdown();
|
psmp.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +141,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException {
|
public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -159,47 +162,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||||
|
@ -217,7 +179,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException {
|
public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -238,46 +200,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||||
|
@ -296,7 +218,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException {
|
public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -320,46 +242,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||||
|
@ -376,7 +258,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException {
|
public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -403,47 +285,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
|
||||||
|
@ -459,7 +300,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException {
|
public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -480,47 +321,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -537,7 +337,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException {
|
public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
final CountDownLatch countDownLatch = new CountDownLatch(2);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -558,46 +358,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -614,7 +374,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException {
|
public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
final CountDownLatch countDownLatch = new CountDownLatch(4);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -638,46 +398,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = e;
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -693,7 +413,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException {
|
public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
final CountDownLatch countDownLatch = new CountDownLatch(5);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
try {
|
try {
|
||||||
|
@ -721,46 +441,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
countDownLatch.countDown();
|
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);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -773,58 +453,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
psmp.shutdown();
|
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 {
|
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 Context c = getInstrumentation().getTargetContext();
|
||||||
final int latchCount = (stream && reinit) ? 2 : 1;
|
final int latchCount = (stream && reinit) ? 2 : 1;
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||||
|
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
checkPSMPInfo(newInfo);
|
checkPSMPInfo(newInfo);
|
||||||
|
@ -863,42 +497,12 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = new AssertionFailedError("Unexpected call to shouldStop");
|
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
|
@Override
|
||||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||||
if (assertionError == null)
|
if (assertionError == null)
|
||||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -955,7 +559,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
(initialState == PlayerStatus.PREPARED) ? 1 : 0;
|
(initialState == PlayerStatus.PREPARED) ? 1 : 0;
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||||
|
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
checkPSMPInfo(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
|
@Override
|
||||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||||
if (assertionError == null) {
|
if (assertionError == null) {
|
||||||
|
@ -1010,11 +584,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) {
|
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 Context c = getInstrumentation().getTargetContext();
|
||||||
final int latchCount = 1;
|
final int latchCount = 1;
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
checkPSMPInfo(newInfo);
|
checkPSMPInfo(newInfo);
|
||||||
|
@ -1062,37 +631,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
countDownLatch.countDown();
|
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
|
@Override
|
||||||
|
@ -1101,11 +639,6 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -1153,7 +686,7 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
final Context c = getInstrumentation().getTargetContext();
|
final Context c = getInstrumentation().getTargetContext();
|
||||||
final int latchCount = 2;
|
final int latchCount = 2;
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
|
||||||
PlaybackServiceMediaPlayer.PSMPCallback callback = new PlaybackServiceMediaPlayer.PSMPCallback() {
|
PlaybackServiceMediaPlayer.PSMPCallback callback = new DefaultPSMPCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
|
||||||
checkPSMPInfo(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
|
@Override
|
||||||
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
public boolean onMediaPlayerError(Object inObj, int what, int extra) {
|
||||||
if (assertionError == null)
|
if (assertionError == null)
|
||||||
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
|
||||||
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, PLAYABLE_LOCAL_URL);
|
||||||
|
@ -1248,4 +746,71 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
|
||||||
super("Unexpected state change: " + status);
|
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;
|
package de.danoeh.antennapod.core.feed;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
@ -14,12 +15,16 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
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.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.storage.DBReader;
|
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.DBWriter;
|
||||||
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
import de.danoeh.antennapod.core.storage.PodDBAdapter;
|
||||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||||
|
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
|
||||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||||
|
|
||||||
public class FeedMedia extends FeedFile implements Playable {
|
public class FeedMedia extends FeedFile implements Playable {
|
||||||
|
@ -48,6 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
|
||||||
private String mime_type;
|
private String mime_type;
|
||||||
@Nullable private volatile FeedItem item;
|
@Nullable private volatile FeedItem item;
|
||||||
private Date playbackCompletionDate;
|
private Date playbackCompletionDate;
|
||||||
|
private int startPosition = -1;
|
||||||
|
private int playedDurationWhenStarted;
|
||||||
|
|
||||||
// if null: unknown, will be checked
|
// if null: unknown, will be checked
|
||||||
private Boolean hasEmbeddedPicture;
|
private Boolean hasEmbeddedPicture;
|
||||||
|
@ -73,6 +80,7 @@ public class FeedMedia extends FeedFile implements Playable {
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.played_duration = played_duration;
|
this.played_duration = played_duration;
|
||||||
|
this.playedDurationWhenStarted = played_duration;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.mime_type = mime_type;
|
this.mime_type = mime_type;
|
||||||
this.playbackCompletionDate = playbackCompletionDate == null
|
this.playbackCompletionDate = playbackCompletionDate == null
|
||||||
|
@ -472,15 +480,59 @@ public class FeedMedia extends FeedFile implements Playable {
|
||||||
}
|
}
|
||||||
setPosition(newPosition);
|
setPosition(newPosition);
|
||||||
setLastPlayedTime(timeStamp);
|
setLastPlayedTime(timeStamp);
|
||||||
|
if(startPosition>=0 && position > startPosition) {
|
||||||
|
setPlayedDuration(playedDurationWhenStarted + position - startPosition);
|
||||||
|
}
|
||||||
DBWriter.setFeedMediaPlaybackInformation(this);
|
DBWriter.setFeedMediaPlaybackInformation(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackStart() {
|
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
|
@Override
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.antennapod.audio.MediaPlayer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
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
|
// set temporarily to pause in order to update list with current position
|
||||||
if (playerStatus == PlayerStatus.PLAYING) {
|
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);
|
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||||
}
|
}
|
||||||
|
@ -199,6 +203,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
|
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
|
||||||
AudioManager.AUDIOFOCUS_GAIN);
|
AudioManager.AUDIOFOCUS_GAIN);
|
||||||
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||||
|
Log.d(TAG, "Audiofocus successfully requested");
|
||||||
|
Log.d(TAG, "Resuming/Starting playback");
|
||||||
acquireWifiLockIfNecessary();
|
acquireWifiLockIfNecessary();
|
||||||
float speed = 1.0f;
|
float speed = 1.0f;
|
||||||
try {
|
try {
|
||||||
|
@ -220,8 +226,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
|
|
||||||
setPlayerStatus(PlayerStatus.PLAYING, media);
|
setPlayerStatus(PlayerStatus.PLAYING, media);
|
||||||
pausedBecauseOfTransientAudiofocusLoss = false;
|
pausedBecauseOfTransientAudiofocusLoss = false;
|
||||||
media.onPlaybackStart();
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "Failed to request audio focus");
|
Log.e(TAG, "Failed to request audio focus");
|
||||||
}
|
}
|
||||||
|
@ -249,7 +253,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
if (playerStatus == PlayerStatus.PLAYING) {
|
if (playerStatus == PlayerStatus.PLAYING) {
|
||||||
Log.d(TAG, "Pausing playback.");
|
Log.d(TAG, "Pausing playback.");
|
||||||
mediaPlayer.pause();
|
mediaPlayer.pause();
|
||||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
setPlayerStatus(PlayerStatus.PAUSED, media, getPosition());
|
||||||
|
|
||||||
if (abandonFocus) {
|
if (abandonFocus) {
|
||||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||||
|
@ -311,11 +315,12 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
|
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this call has no effect!
|
||||||
if (media.getPosition() > 0) {
|
if (media.getPosition() > 0) {
|
||||||
seekToSync(media.getPosition());
|
seekToSync(media.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media.getDuration() == 0) {
|
if (media.getDuration() <= 0) {
|
||||||
Log.d(TAG, "Setting duration of media");
|
Log.d(TAG, "Setting duration of media");
|
||||||
media.setDuration(mediaPlayer.getDuration());
|
media.setDuration(mediaPlayer.getDuration());
|
||||||
}
|
}
|
||||||
|
@ -367,10 +372,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
if (playerStatus == PlayerStatus.PLAYING
|
if (playerStatus == PlayerStatus.PLAYING
|
||||||
|| playerStatus == PlayerStatus.PAUSED
|
|| playerStatus == PlayerStatus.PAUSED
|
||||||
|| playerStatus == PlayerStatus.PREPARED) {
|
|| playerStatus == PlayerStatus.PREPARED) {
|
||||||
if (!stream) {
|
|
||||||
statusBeforeSeeking = playerStatus;
|
|
||||||
setPlayerStatus(PlayerStatus.SEEKING, media);
|
|
||||||
}
|
|
||||||
if(seekLatch != null && seekLatch.getCount() > 0) {
|
if(seekLatch != null && seekLatch.getCount() > 0) {
|
||||||
try {
|
try {
|
||||||
seekLatch.await(3, TimeUnit.SECONDS);
|
seekLatch.await(3, TimeUnit.SECONDS);
|
||||||
|
@ -379,6 +380,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
seekLatch = new CountDownLatch(1);
|
seekLatch = new CountDownLatch(1);
|
||||||
|
statusBeforeSeeking = playerStatus;
|
||||||
|
setPlayerStatus(PlayerStatus.SEEKING, media, getPosition());
|
||||||
mediaPlayer.seekTo(t);
|
mediaPlayer.seekTo(t);
|
||||||
try {
|
try {
|
||||||
seekLatch.await(3, TimeUnit.SECONDS);
|
seekLatch.await(3, TimeUnit.SECONDS);
|
||||||
|
@ -752,8 +755,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endPlayback(final boolean wasSkipped, boolean switchingPlayers) {
|
protected Future<?> endPlayback(final boolean wasSkipped, final boolean shouldContinue, final boolean toStoppedState) {
|
||||||
executor.submit(() -> {
|
return executor.submit(() -> {
|
||||||
playerLock.lock();
|
playerLock.lock();
|
||||||
releaseWifiLockIfNecessary();
|
releaseWifiLockIfNecessary();
|
||||||
|
|
||||||
|
@ -762,13 +765,58 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
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) {
|
if (mediaPlayer != null) {
|
||||||
mediaPlayer.reset();
|
mediaPlayer.reset();
|
||||||
|
|
||||||
}
|
}
|
||||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
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();
|
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
|
* 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.
|
* abandoning audio focus have to be done with other methods.
|
||||||
*/
|
*/
|
||||||
@Override
|
private void stop() {
|
||||||
public void stop() {
|
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
playerLock.lock();
|
playerLock.lock();
|
||||||
releaseWifiLockIfNecessary();
|
releaseWifiLockIfNecessary();
|
||||||
|
@ -833,7 +880,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
mp -> genericOnCompletion();
|
mp -> genericOnCompletion();
|
||||||
|
|
||||||
private void genericOnCompletion() {
|
private void genericOnCompletion() {
|
||||||
endPlayback(false, false);
|
endPlayback(false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
|
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
|
||||||
|
@ -889,8 +936,11 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
|
||||||
seekLatch.countDown();
|
seekLatch.countDown();
|
||||||
}
|
}
|
||||||
playerLock.lock();
|
playerLock.lock();
|
||||||
|
if (playerStatus == PlayerStatus.PLAYING) {
|
||||||
|
callback.onPlaybackStart(media, getPosition());
|
||||||
|
}
|
||||||
if (playerStatus == PlayerStatus.SEEKING) {
|
if (playerStatus == PlayerStatus.SEEKING) {
|
||||||
setPlayerStatus(statusBeforeSeeking, media);
|
setPlayerStatus(statusBeforeSeeking, media, getPosition());
|
||||||
}
|
}
|
||||||
playerLock.unlock();
|
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.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.MediaType;
|
import de.danoeh.antennapod.core.feed.MediaType;
|
||||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
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.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
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.storage.DBWriter;
|
||||||
import de.danoeh.antennapod.core.util.IntList;
|
import de.danoeh.antennapod.core.util.IntList;
|
||||||
import de.danoeh.antennapod.core.util.QueueAccess;
|
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.ExternalMedia;
|
||||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||||
|
|
||||||
|
@ -206,8 +202,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
*/
|
*/
|
||||||
private MediaSessionCompat mediaSession;
|
private MediaSessionCompat mediaSession;
|
||||||
|
|
||||||
private int startPosition;
|
|
||||||
|
|
||||||
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
|
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
|
||||||
|
|
||||||
private final IBinder mBinder = new LocalBinder();
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
@ -473,7 +467,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
UserPreferences.shouldHardwareButtonSkip()) {
|
UserPreferences.shouldHardwareButtonSkip()) {
|
||||||
// assume the skip command comes from a notification or the lockscreen
|
// assume the skip command comes from a notification or the lockscreen
|
||||||
// a >| skip button should actually skip
|
// a >| skip button should actually skip
|
||||||
mediaPlayer.endPlayback(true, false);
|
mediaPlayer.skip();
|
||||||
} else {
|
} else {
|
||||||
// assume skip command comes from a (bluetooth) media button
|
// assume skip command comes from a (bluetooth) media button
|
||||||
// user actually wants to fast-forward
|
// user actually wants to fast-forward
|
||||||
|
@ -530,7 +524,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
|
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void positionSaverTick() {
|
public void positionSaverTick() {
|
||||||
saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL);
|
saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -582,9 +576,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PAUSED:
|
case PAUSED:
|
||||||
taskManager.cancelPositionSaver();
|
|
||||||
saveCurrentPosition(false, 0);
|
|
||||||
taskManager.cancelWidgetUpdater();
|
|
||||||
if ((UserPreferences.isPersistNotify() || isCasting) &&
|
if ((UserPreferences.isPersistNotify() || isCasting) &&
|
||||||
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
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
|
// 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);
|
stopForeground(true);
|
||||||
}
|
}
|
||||||
writePlayerStatusPlaybackPreferences();
|
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;
|
break;
|
||||||
|
|
||||||
case STOPPED:
|
case STOPPED:
|
||||||
|
@ -619,15 +594,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PLAYING:
|
case PLAYING:
|
||||||
Log.d(TAG, "Audiofocus successfully requested");
|
|
||||||
Log.d(TAG, "Resuming/Starting playback");
|
|
||||||
|
|
||||||
taskManager.startPositionSaver();
|
|
||||||
taskManager.startWidgetUpdater();
|
|
||||||
writePlayerStatusPlaybackPreferences();
|
writePlayerStatusPlaybackPreferences();
|
||||||
setupNotification(newInfo);
|
setupNotification(newInfo);
|
||||||
started = true;
|
started = true;
|
||||||
startPosition = mediaPlayer.getPosition();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ERROR:
|
case ERROR:
|
||||||
|
@ -700,49 +669,158 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
|
public void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext) {
|
||||||
PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers);
|
PlaybackService.this.onPostPlayback(media, ended, playingNext);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
@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) {
|
private Playable getNextInQueue(final Playable currentMedia) {
|
||||||
Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": ""));
|
if (!(currentMedia instanceof FeedMedia)) {
|
||||||
|
Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding");
|
||||||
if (playable == null) {
|
return null;
|
||||||
Log.e(TAG, "Cannot end playback: media was null");
|
}
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
taskManager.cancelPositionSaver();
|
|
||||||
|
|
||||||
boolean isInQueue = false;
|
|
||||||
FeedItem nextItem = null;
|
|
||||||
|
|
||||||
if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) {
|
|
||||||
FeedMedia media = (FeedMedia) playable;
|
|
||||||
FeedItem item = media.getItem();
|
FeedItem item = media.getItem();
|
||||||
|
if (item == null) {
|
||||||
if (!switchingPlayers) {
|
Log.w(TAG, "getNextInQueue() with FeedMedia object whose FeedItem is null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
FeedItem nextItem;
|
||||||
try {
|
try {
|
||||||
final List<FeedItem> queue = taskManager.getQueue();
|
final List<FeedItem> queue = taskManager.getQueue();
|
||||||
isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
|
|
||||||
nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
|
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 do post-playback processing: media was null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "onPostPlayback(): media=" + playable.getEpisodeTitle());
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ended || smartMarkAsPlayed) {
|
||||||
|
media.onPlaybackCompleted(getApplicationContext());
|
||||||
|
} else {
|
||||||
|
media.onPlaybackPause(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
// isInQueue remains false
|
// 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
|
// Delete episode if enabled
|
||||||
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
|
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
|
||||||
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
|
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
|
||||||
|
@ -751,70 +829,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ended || playingNext) {
|
||||||
DBWriter.addItemToPlaybackHistory(media);
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1218,28 +1234,23 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
/**
|
/**
|
||||||
* Persists the current position and last played time of the media file.
|
* 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 fromMediaPlayer if true, the information is gathered from the current Media Player
|
||||||
* @param deltaPlayedDuration value by which played_duration should be increased.
|
* 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) {
|
private synchronized void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
|
||||||
int position = getCurrentPosition();
|
int duration;
|
||||||
int duration = getDuration();
|
if (fromMediaPlayer) {
|
||||||
float playbackSpeed = getCurrentPlaybackSpeed();
|
position = getCurrentPosition();
|
||||||
final Playable playable = mediaPlayer.getPlayable();
|
duration = getDuration();
|
||||||
|
playable = mediaPlayer.getPlayable();
|
||||||
|
} else {
|
||||||
|
duration = playable.getDuration();
|
||||||
|
}
|
||||||
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
|
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
|
||||||
Log.d(TAG, "Saving current position to " + position);
|
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(
|
playable.saveCurrentPosition(
|
||||||
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()),
|
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()),
|
||||||
position,
|
position,
|
||||||
|
@ -1407,7 +1418,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
|
if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
|
||||||
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
|
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) {
|
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);
|
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) {
|
public void seekToChapter(Chapter c) {
|
||||||
mediaPlayer.seekToChapter(c);
|
seekTo((int) c.getStart());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1558,15 +1550,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
return mediaPlayer.getVideoSize();
|
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 final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
|
||||||
|
|
||||||
private static final String TAG = "MediaSessionCompat";
|
private static final String TAG = "MediaSessionCompat";
|
||||||
|
@ -1602,19 +1585,14 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
Log.d(TAG, "onPause()");
|
Log.d(TAG, "onPause()");
|
||||||
if (getStatus() == PlayerStatus.PLAYING) {
|
if (getStatus() == PlayerStatus.PLAYING) {
|
||||||
pause(false, true);
|
pause(!UserPreferences.isPersistNotify(), true);
|
||||||
}
|
|
||||||
if (UserPreferences.isPersistNotify()) {
|
|
||||||
pause(false, true);
|
|
||||||
} else {
|
|
||||||
pause(true, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
Log.d(TAG, "onStop()");
|
Log.d(TAG, "onStop()");
|
||||||
mediaPlayer.stop();
|
mediaPlayer.stopPlayback(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1639,7 +1617,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
public void onSkipToNext() {
|
public void onSkipToNext() {
|
||||||
Log.d(TAG, "onSkipToNext()");
|
Log.d(TAG, "onSkipToNext()");
|
||||||
if(UserPreferences.shouldHardwareButtonSkip()) {
|
if(UserPreferences.shouldHardwareButtonSkip()) {
|
||||||
mediaPlayer.endPlayback(true, false);
|
mediaPlayer.skip();
|
||||||
} else {
|
} else {
|
||||||
seekDelta(UserPreferences.getFastFowardSecs() * 1000);
|
seekDelta(UserPreferences.getFastFowardSecs() * 1000);
|
||||||
}
|
}
|
||||||
|
@ -1682,7 +1660,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
PlaybackServiceMediaPlayer getMediaPlayer();
|
PlaybackServiceMediaPlayer getMediaPlayer();
|
||||||
void setIsCasting(boolean isCasting);
|
void setIsCasting(boolean isCasting);
|
||||||
void sendNotificationBroadcast(int type, int code);
|
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);
|
void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
|
||||||
MediaSessionCompat getMediaSession();
|
MediaSessionCompat getMediaSession();
|
||||||
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
|
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
|
||||||
|
@ -1716,8 +1694,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
|
public void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
|
||||||
PlaybackService.this.saveCurrentPosition(updatePlayedDuration, deltaPlayedDuration);
|
PlaybackService.this.saveCurrentPosition(fromMediaPlayer, playable, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -8,11 +8,9 @@ import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.feed.Chapter;
|
import java.util.concurrent.Future;
|
||||||
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.feed.MediaType;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
|
||||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,13 +127,6 @@ public abstract class PlaybackServiceMediaPlayer {
|
||||||
*/
|
*/
|
||||||
public abstract void seekDelta(int d);
|
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.
|
* 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);
|
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
|
* Ends playback of current media (if any) and moves into INDETERMINATE state, unless
|
||||||
* INDETERMINATE state, for example after a call to endPlayback.
|
* {@param toStoppedState} is set to true, in which case it moves into STOPPED state.
|
||||||
* 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.
|
* @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.
|
* @return {@code true} if the WifiLock feature should be used, {@code false} otherwise.
|
||||||
|
@ -274,41 +294,39 @@ public abstract class PlaybackServiceMediaPlayer {
|
||||||
* <p/>
|
* <p/>
|
||||||
* This method will notify the callback about the change of the player status (even if the new status is the same
|
* 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).
|
* 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 newStatus The new PlayerStatus. This must not be null.
|
||||||
* @param newMedia The new playable object of the PSMP object. This can 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);
|
Log.d(TAG, this.getClass().getSimpleName() + ": Setting player status to " + newStatus);
|
||||||
|
|
||||||
|
PlayerStatus oldStatus = playerStatus;
|
||||||
|
|
||||||
this.playerStatus = newStatus;
|
this.playerStatus = newStatus;
|
||||||
setPlayable(newMedia);
|
setPlayable(newMedia);
|
||||||
|
|
||||||
if (playerStatus != null) {
|
if (newMedia != null && newStatus != PlayerStatus.INDETERMINATE) {
|
||||||
Log.d(TAG, "playerStatus: " + playerStatus.toString());
|
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()));
|
callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void smartMarkAsPlayed(Playable media) {
|
/**
|
||||||
if(media != null && media instanceof FeedMedia) {
|
* @see #setPlayerStatus(PlayerStatus, Playable, int)
|
||||||
FeedMedia oldMedia = (FeedMedia) media;
|
*/
|
||||||
if(oldMedia.hasAlmostEnded()) {
|
protected final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
|
||||||
Log.d(TAG, "smart mark as read");
|
setPlayerStatus(newStatus, newMedia, INVALID_TIME);
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface PSMPCallback {
|
public interface PSMPCallback {
|
||||||
|
@ -328,7 +346,15 @@ public abstract class PlaybackServiceMediaPlayer {
|
||||||
|
|
||||||
boolean onMediaPlayerError(Object inObj, int what, int extra);
|
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;
|
package de.danoeh.antennapod.core.util.playback;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.media.MediaMetadataRetriever;
|
import android.media.MediaMetadataRetriever;
|
||||||
|
@ -205,7 +206,12 @@ public class ExternalMedia implements Playable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
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();
|
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
|
* 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()
|
if (ConnectionResult.SUCCESS != GoogleApiAvailability.getInstance()
|
||||||
.isGooglePlayServicesAvailable(context)) {
|
.isGooglePlayServicesAvailable(context)) {
|
||||||
Log.e(TAG, "Couldn't find the appropriate version of Google Play Services");
|
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);
|
INSTANCE = new CastManager(context, castConfiguration);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.danoeh.antennapod.core.cast;
|
package de.danoeh.antennapod.core.cast;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
@ -255,7 +256,12 @@ public class RemoteMedia implements Playable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackCompleted() {
|
public void onPlaybackPause(Context context) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackCompleted(Context context) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ import android.widget.Toast;
|
||||||
import com.google.android.gms.cast.ApplicationMetadata;
|
import com.google.android.gms.cast.ApplicationMetadata;
|
||||||
import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
|
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.CastConsumer;
|
||||||
import de.danoeh.antennapod.core.cast.CastManager;
|
import de.danoeh.antennapod.core.cast.CastManager;
|
||||||
import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
|
import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
|
||||||
|
@ -108,7 +110,7 @@ public class PlaybackServiceFlavorHelper {
|
||||||
// to the latest position.
|
// to the latest position.
|
||||||
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
||||||
if (mediaPlayer != null) {
|
if (mediaPlayer != null) {
|
||||||
callback.saveCurrentPosition(false, 0);
|
callback.saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||||
infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo();
|
infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo();
|
||||||
if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT &&
|
if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT &&
|
||||||
infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) {
|
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,
|
// 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
|
// since pause runs asynchronously and we could be directing the new player to play even before
|
||||||
// the old player gives us back the position.
|
// the old player gives us back the position.
|
||||||
callback.saveCurrentPosition(false, 0);
|
callback.saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
|
@ -182,7 +184,11 @@ public class PlaybackServiceFlavorHelper {
|
||||||
boolean wasLaunched) {
|
boolean wasLaunched) {
|
||||||
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
|
||||||
if (mediaPlayer != null) {
|
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.shutdownQuietly();
|
||||||
}
|
}
|
||||||
mediaPlayer = newPlayer;
|
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.NoConnectionException;
|
||||||
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
|
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 java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.R;
|
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.cast.RemoteMedia;
|
||||||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||||
import de.danoeh.antennapod.core.feed.MediaType;
|
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.RewindAfterPauseUtils;
|
||||||
import de.danoeh.antennapod.core.util.playback.Playable;
|
import de.danoeh.antennapod.core.util.playback.Playable;
|
||||||
|
|
||||||
|
@ -42,8 +45,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
private final CastManager castMgr;
|
private final CastManager castMgr;
|
||||||
|
|
||||||
private volatile Playable media;
|
private volatile Playable media;
|
||||||
private volatile MediaInfo remoteMedia;
|
|
||||||
private volatile MediaType mediaType;
|
private volatile MediaType mediaType;
|
||||||
|
private volatile MediaInfo remoteMedia;
|
||||||
|
private volatile int remoteState;
|
||||||
|
|
||||||
private final AtomicBoolean isBuffering;
|
private final AtomicBoolean isBuffering;
|
||||||
|
|
||||||
|
@ -57,31 +61,28 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
mediaType = null;
|
mediaType = null;
|
||||||
startWhenPrepared = new AtomicBoolean(false);
|
startWhenPrepared = new AtomicBoolean(false);
|
||||||
isBuffering = new AtomicBoolean(false);
|
isBuffering = new AtomicBoolean(false);
|
||||||
|
remoteState = MediaStatus.PLAYER_STATE_UNKNOWN;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) {
|
if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) {
|
||||||
// updates the state, but does not start playing new media if it was going to
|
onRemoteMediaPlayerStatusUpdated();
|
||||||
onRemoteMediaPlayerStatusUpdated(
|
|
||||||
((p, playNextEpisode, wasSkipped, switchingPlayers) ->
|
|
||||||
this.callback.endPlayback(p, false, wasSkipped, switchingPlayers)));
|
|
||||||
}
|
}
|
||||||
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||||
Log.e(TAG, "Unable to do initial check for loaded media", e);
|
Log.e(TAG, "Unable to do initial check for loaded media", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
castMgr.addCastConsumer(castConsumer);
|
castMgr.addCastConsumer(castConsumer);
|
||||||
//TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CastConsumer castConsumer = new DefaultCastConsumer() {
|
private CastConsumer castConsumer = new DefaultCastConsumer() {
|
||||||
@Override
|
@Override
|
||||||
public void onRemoteMediaPlayerMetadataUpdated() {
|
public void onRemoteMediaPlayerMetadataUpdated() {
|
||||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
|
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRemoteMediaPlayerStatusUpdated() {
|
public void onRemoteMediaPlayerStatusUpdated() {
|
||||||
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
|
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,8 +122,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()");
|
Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()");
|
||||||
}
|
}
|
||||||
if (playbackEnded) {
|
if (playbackEnded) {
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
// This is an unconventional thing to occur...
|
||||||
callback.endPlayback(media, true, false, false);
|
endPlayback(true, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,85 +167,123 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) {
|
private void onRemoteMediaPlayerStatusUpdated() {
|
||||||
MediaStatus status = castMgr.getMediaStatus();
|
MediaStatus status = castMgr.getMediaStatus();
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
Log.d(TAG, "Received null MediaStatus");
|
Log.d(TAG, "Received null MediaStatus");
|
||||||
//setBuffering(false);
|
|
||||||
//setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState());
|
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 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);
|
setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case MediaStatus.PLAYER_STATE_PLAYING:
|
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;
|
break;
|
||||||
case MediaStatus.PLAYER_STATE_PAUSED:
|
case MediaStatus.PLAYER_STATE_PAUSED:
|
||||||
setPlayerStatus(PlayerStatus.PAUSED, currentMedia);
|
setPlayerStatus(PlayerStatus.PAUSED, currentMedia, position);
|
||||||
break;
|
break;
|
||||||
case MediaStatus.PLAYER_STATE_BUFFERING:
|
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;
|
break;
|
||||||
case MediaStatus.PLAYER_STATE_IDLE:
|
case MediaStatus.PLAYER_STATE_IDLE:
|
||||||
int reason = status.getIdleReason();
|
int reason = status.getIdleReason();
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case MediaStatus.IDLE_REASON_CANCELED:
|
case MediaStatus.IDLE_REASON_CANCELED:
|
||||||
// check if we're already loading something else
|
// Essentially means stopped at the request of a user
|
||||||
if (!updateUI || media == null) {
|
callback.onPlaybackEnded(null, true);
|
||||||
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
|
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
|
||||||
} else {
|
if (oldMedia != null) {
|
||||||
updateUI = false;
|
if (position >= 0) {
|
||||||
|
oldMedia.setPosition(position);
|
||||||
}
|
}
|
||||||
break;
|
callback.onPostPlayback(oldMedia, false, false);
|
||||||
|
}
|
||||||
|
// onPlaybackEnded pretty much takes care of updating the UI
|
||||||
|
return;
|
||||||
case MediaStatus.IDLE_REASON_INTERRUPTED:
|
case MediaStatus.IDLE_REASON_INTERRUPTED:
|
||||||
// check if we're already loading something else
|
// Means that a request to load a different media was sent
|
||||||
if (!updateUI || media == null) {
|
// Not sure if currentMedia already reflects the to be loaded one
|
||||||
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
|
if (mediaChanged && oldState == MediaStatus.PLAYER_STATE_PLAYING) {
|
||||||
} else {
|
callback.onPlaybackPause(null, INVALID_TIME);
|
||||||
updateUI = false;
|
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||||
}
|
}
|
||||||
|
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
|
||||||
break;
|
break;
|
||||||
case MediaStatus.IDLE_REASON_NONE:
|
case MediaStatus.IDLE_REASON_NONE:
|
||||||
|
// This probably only happens when we connected but no command has been sent yet.
|
||||||
setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
|
setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
|
||||||
break;
|
break;
|
||||||
case MediaStatus.IDLE_REASON_FINISHED:
|
case MediaStatus.IDLE_REASON_FINISHED:
|
||||||
boolean playing = playerStatus == PlayerStatus.PLAYING;
|
// This is our onCompletionListener...
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
if (mediaChanged && currentMedia != null) {
|
||||||
endPlaybackCall.endPlayback(currentMedia,playing, false, false);
|
media = currentMedia;
|
||||||
// endPlayback already updates the UI, so no need to trigger it ourselves
|
}
|
||||||
updateUI = false;
|
endPlayback(false, true, true);
|
||||||
break;
|
return;
|
||||||
case MediaStatus.IDLE_REASON_ERROR:
|
case MediaStatus.IDLE_REASON_ERROR:
|
||||||
Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
|
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,
|
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH,
|
||||||
R.string.cast_failed_media_error_skipping);
|
R.string.cast_failed_media_error_skipping);
|
||||||
endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false);
|
endPlayback(true, true, true);
|
||||||
// endPlayback already updates the UI, so no need to trigger it ourselves
|
return;
|
||||||
updateUI = false;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
||||||
//is this right?
|
if (playerStatus != PlayerStatus.INDETERMINATE || media != currentMedia) {
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.e(TAG, "Remote media state undetermined!");
|
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
|
||||||
}
|
}
|
||||||
if (updateUI) {
|
break;
|
||||||
|
default:
|
||||||
|
Log.wtf(TAG, "Remote media state undetermined!");
|
||||||
|
}
|
||||||
|
if (mediaChanged) {
|
||||||
callback.onMediaChanged(true);
|
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)) {
|
if (!CastUtils.isCastable(playable)) {
|
||||||
Log.d(TAG, "media provided is not compatible with cast device");
|
Log.d(TAG, "media provided is not compatible with cast device");
|
||||||
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable);
|
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable);
|
||||||
try {
|
Playable nextPlayable = playable;
|
||||||
playable.loadMetadata();
|
do {
|
||||||
} catch (Playable.PlayableException e) {
|
nextPlayable = callback.getNextInQueue(nextPlayable);
|
||||||
Log.e(TAG, "Unable to load metadata of playable", e);
|
} while (nextPlayable != null && !CastUtils.isCastable(nextPlayable));
|
||||||
|
if (nextPlayable != null) {
|
||||||
|
playMediaObject(nextPlayable, forceReset, stream, startWhenPrepared, prepareImmediately);
|
||||||
}
|
}
|
||||||
callback.endPlayback(playable, startWhenPrepared, true, false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,19 +321,21 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// set temporarily to pause in order to update list with current position
|
// set temporarily to pause in order to update list with current position
|
||||||
|
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
|
||||||
|
int position = media.getPosition();
|
||||||
try {
|
try {
|
||||||
if (castMgr.isRemoteMediaPlaying()) {
|
isPlaying = castMgr.isRemoteMediaPlaying();
|
||||||
setPlayerStatus(PlayerStatus.PAUSED, media);
|
position = (int) castMgr.getCurrentMediaPosition();
|
||||||
}
|
|
||||||
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||||
Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", 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);
|
|
||||||
}
|
}
|
||||||
|
if (isPlaying) {
|
||||||
|
callback.onPlaybackPause(media, position);
|
||||||
|
}
|
||||||
|
if (!media.getIdentifier().equals(playable.getIdentifier())) {
|
||||||
|
final Playable oldMedia = media;
|
||||||
|
callback.onPostPlayback(oldMedia, false, true);
|
||||||
}
|
}
|
||||||
smartMarkAsPlayed(media);
|
|
||||||
|
|
||||||
|
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
|
||||||
}
|
}
|
||||||
|
@ -301,7 +343,6 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
|
|
||||||
this.media = playable;
|
this.media = playable;
|
||||||
remoteMedia = remoteVersion(playable);
|
remoteMedia = remoteVersion(playable);
|
||||||
//this.stream = stream;
|
|
||||||
this.mediaType = media.getMediaType();
|
this.mediaType = media.getMediaType();
|
||||||
this.startWhenPrepared.set(startWhenPrepared);
|
this.startWhenPrepared.set(startWhenPrepared);
|
||||||
setPlayerStatus(PlayerStatus.INITIALIZING, media);
|
setPlayerStatus(PlayerStatus.INITIALIZING, media);
|
||||||
|
@ -328,8 +369,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
media.getPosition(),
|
media.getPosition(),
|
||||||
media.getLastPlayedTime());
|
media.getLastPlayedTime());
|
||||||
castMgr.play(newPosition);
|
castMgr.play(newPosition);
|
||||||
}
|
} else {
|
||||||
castMgr.play();
|
castMgr.play();
|
||||||
|
}
|
||||||
} catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
|
} catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
|
||||||
Log.e(TAG, "Unable to resume remote playback", e);
|
Log.e(TAG, "Unable to resume remote playback", e);
|
||||||
}
|
}
|
||||||
|
@ -464,8 +506,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
this.startWhenPrepared.set(startWhenPrepared);
|
this.startWhenPrepared.set(startWhenPrepared);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO I believe some parts of the code make the same decision skipping this check, so that
|
// As things are right now, changing the return value of this function is not enough to ensure
|
||||||
//should be changed as well
|
// all other components recognize it.
|
||||||
@Override
|
@Override
|
||||||
public boolean canSetSpeed() {
|
public boolean canSetSpeed() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -557,23 +599,67 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endPlayback(boolean wasSkipped, boolean switchingPlayers) {
|
protected Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState) {
|
||||||
Log.d(TAG, "endPlayback() called");
|
Log.d(TAG, "endPlayback() called");
|
||||||
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
|
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) {
|
if (playerStatus != PlayerStatus.INDETERMINATE) {
|
||||||
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
|
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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (nextMedia != null) {
|
||||||
public void stop() {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
if (playerStatus == PlayerStatus.INDETERMINATE) {
|
if (playerStatus == PlayerStatus.INDETERMINATE) {
|
||||||
setPlayerStatus(PlayerStatus.STOPPED, null);
|
setPlayerStatus(PlayerStatus.STOPPED, null);
|
||||||
} else {
|
} else {
|
||||||
|
@ -585,8 +671,4 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
|
||||||
protected boolean shouldLockWifi() {
|
protected boolean shouldLockWifi() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface EndPlaybackCall {
|
|
||||||
boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue