Fix possible memory leaks in new playback service (#8578)

### Description

Fix possible memory leaks in new playback service

### Checklist
<!-- 
To help us keep the issue tracker clean and work as efficient as
possible,
  please make sure that you have done all of the following.
You can tick the boxes below by placing an x inside the brackets like
this: [x]
-->
- [x] I have read the contribution guidelines:
https://github.com/AntennaPod/AntennaPod/blob/develop/CONTRIBUTING.md#submit-a-pull-request
- [x] I have performed a self-review of my code, going through my
changes line by line and carefully considering why this line change is
necessary
- [x] I have run the automated code checks using `./gradlew checkstyle
lint`
- [x] My code follows the style guidelines of the AntennaPod project:
https://antennapod.org/contribute/develop/app/code-style
- [x] I have mentioned the corresponding issue and the relevant keyword
(e.g., "Closes: #xy") in the description (see
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
- [x] If it is a core feature, I have added automated tests
This commit is contained in:
Hans-Peter Lehmann
2026-07-08 07:53:13 +02:00
committed by GitHub
parent bdfd540c32
commit 78af5e353b
2 changed files with 24 additions and 22 deletions

View File

@ -521,12 +521,16 @@ public abstract class PlaybackController {
ListenableFuture<MediaController> controllerFuture =
new MediaController.Builder(context, sessionToken).buildAsync();
controllerFuture.addListener(() -> {
MediaController controller = null;
try {
MediaController controller = controllerFuture.get();
controller = controllerFuture.get();
consumer.accept(controller);
controller.release();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
} finally {
if (controller != null) {
controller.release();
}
}
}, MoreExecutors.directExecutor());

View File

@ -41,7 +41,6 @@ import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent;
import org.greenrobot.eventbus.EventBus;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.Collections;
@ -102,7 +101,6 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
}
private final Context context;
private final CompositeDisposable disposables = new CompositeDisposable();
public MediaLibrarySessionCallback(Context context) {
this.context = context;
@ -264,7 +262,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
mediaItems, index, startPositionMs));
}
SettableFuture<MediaSession.MediaItemsWithStartPosition> future = SettableFuture.create();
disposables.add(Single.fromCallable(
Single.fromCallable(
() -> {
List<MediaItem> updatedItems = onAddMediaItems(mediaSession, controller, mediaItems).get();
long mediaId = Long.parseLong(updatedItems.get(index).mediaId);
@ -280,7 +278,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
}, error -> {
Log.e(TAG, "Failed to load media", error);
future.set(new MediaSession.MediaItemsWithStartPosition(mediaItems, index, startPositionMs));
}));
});
return future;
}
@ -294,7 +292,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
}
SettableFuture<List<MediaItem>> future = SettableFuture.create();
disposables.add(Single.fromCallable(() -> enrichMediaItems(mediaItems))
Single.fromCallable(() -> enrichMediaItems(mediaItems))
.subscribeOn(Schedulers.io())
.subscribe(
items -> future.set(items.isEmpty() ? Collections.emptyList() : items),
@ -302,7 +300,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
Log.e(TAG, "Failed to load media items", error);
future.set(Collections.emptyList());
}
));
);
return future;
}
@ -312,7 +310,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
public ListenableFuture<MediaSession.MediaItemsWithStartPosition> onPlaybackResumption(
@NonNull MediaSession mediaSession, @NonNull MediaSession.ControllerInfo controller) {
SettableFuture<MediaSession.MediaItemsWithStartPosition> future = SettableFuture.create();
disposables.add(Single.fromCallable(() -> {
Single.fromCallable(() -> {
FeedMedia media = DBReader.getFeedMedia(PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
// If there is no media to resume, media3 crashes. So instead of crashing, just play something random.
if (media == null) {
@ -343,7 +341,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
future.set(result);
},
future::setException
));
);
return future;
}
@ -373,10 +371,10 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
@NonNull MediaSession.ControllerInfo browser, @NonNull String mediaId) {
if (BROWSABLE_MEDIA_IDS.contains(mediaId) || mediaId.startsWith(MediaItemAdapter.MEDIA_ID_FEED_PREFIX)) {
SettableFuture<LibraryResult<MediaItem>> future = SettableFuture.create();
disposables.add(Single.fromCallable(() -> createBrowsableMediaItem(mediaId))
Single.fromCallable(() -> createBrowsableMediaItem(mediaId))
.subscribeOn(Schedulers.io())
.subscribe(item -> future.set(LibraryResult.ofItem(item, null)),
future::setException));
future::setException);
return future;
}
return MediaLibraryService.MediaLibrarySession.Callback.super.onGetItem(session, browser, mediaId);
@ -393,7 +391,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
switch (parentId) {
case MEDIA_ID_ROOT:
disposables.add(Single.fromCallable(() -> ImmutableList.of(
Single.fromCallable(() -> ImmutableList.of(
createBrowsableMediaItem(MEDIA_ID_CONTINUE_LISTENING),
createBrowsableMediaItem(MEDIA_ID_QUEUE),
createBrowsableMediaItem(MEDIA_ID_DOWNLOADS),
@ -401,10 +399,10 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
createBrowsableMediaItem(MEDIA_ID_SUBSCRIPTIONS)))
.subscribeOn(Schedulers.io())
.subscribe(items -> future.set(LibraryResult.ofItemList(items, params)),
future::setException));
future::setException);
return future;
case MEDIA_ID_SUBSCRIPTIONS:
disposables.add(Single.fromCallable(DBReader::getFeedList)
Single.fromCallable(DBReader::getFeedList)
.subscribeOn(Schedulers.io())
.subscribe(
items -> {
@ -416,10 +414,10 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
}
future.set(LibraryResult.ofItemList(builder.build(), params));
},
future::setException));
future::setException);
return future;
case MEDIA_ID_CONTINUE_LISTENING:
disposables.add(Single.fromCallable(
Single.fromCallable(
() -> DBReader.getPausedQueue(CONTINUE_LISTENING_NUM_EPISODES))
.subscribeOn(Schedulers.io())
.subscribe(
@ -429,10 +427,10 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
Log.e(TAG, "Failed to load continue listening", error);
future.set(LibraryResult.ofItemList(ImmutableList.of(), params));
}
));
);
return future;
default: // Episodes lists
disposables.add(Single.fromCallable(() -> {
Single.fromCallable(() -> {
if (parentId.startsWith(MediaItemAdapter.MEDIA_ID_FEED_PREFIX)) {
long feedId = Long.parseLong(parentId.split(":")[1]);
return DBReader.getFeed(feedId, true, page * pageSize, pageSize).getItems();
@ -451,7 +449,7 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
.subscribeOn(Schedulers.io())
.subscribe(items -> future.set(LibraryResult.ofItemList(
MediaItemAdapter.fromItemList(context, items), params)),
future::setException));
future::setException);
return future;
}
}
@ -462,12 +460,12 @@ public class MediaLibrarySessionCallback implements MediaLibraryService.MediaLib
@NonNull MediaLibraryService.MediaLibrarySession session, @NonNull MediaSession.ControllerInfo browser,
@NonNull String query, int page, int pageSize, @Nullable MediaLibraryService.LibraryParams params) {
SettableFuture<LibraryResult<ImmutableList<MediaItem>>> future = SettableFuture.create();
disposables.add(Single.fromCallable(() ->
Single.fromCallable(() ->
DBReader.searchFeedItems(0, query, FeedItemFilter.unfiltered()))
.subscribeOn(Schedulers.io())
.subscribe(items -> future.set(LibraryResult.ofItemList(
MediaItemAdapter.fromItemList(context, items), params)),
future::setException));
future::setException);
return future;
}