feat: Implemented continuous playing

This commit is contained in:
CappielloAntonio 2024-06-02 19:18:16 +02:00
parent 2c3aebc83b
commit 176db09662
9 changed files with 101 additions and 44 deletions

View file

@ -1,13 +1,9 @@
package com.cappielloantonio.tempo.repository; package com.cappielloantonio.tempo.repository;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse; import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
@ -54,12 +50,12 @@ public class SongRepository {
return starredSongs; return starredSongs;
} }
public MutableLiveData<List<Child>> getInstantMix(Child song, int count) { public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(); MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getBrowsingClient() .getBrowsingClient()
.getSimilarSongs2(song.getId(), count) .getSimilarSongs2(id, count)
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {

View file

@ -1,8 +1,16 @@
package com.cappielloantonio.tempo.service; package com.cappielloantonio.tempo.service;
import androidx.media3.common.MediaItem; import android.content.ComponentName;
import androidx.media3.session.MediaBrowser;
import androidx.annotation.OptIn;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback; import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
import com.cappielloantonio.tempo.model.Chronology; import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.repository.ChronologyRepository; import com.cappielloantonio.tempo.repository.ChronologyRepository;
@ -299,6 +307,30 @@ public class MediaManager {
} }
} }
@OptIn(markerClass = UnstableApi.class)
public static void continuousPlay(MediaItem mediaItem) {
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
Preferences.setLastInstantMix();
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, 10);
instantMix.observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> media) {
if (media != null) {
ListenableFuture<MediaBrowser> mediaBrowserListenableFuture = new MediaBrowser.Builder(
App.getContext(),
new SessionToken(App.getContext(), new ComponentName(App.getContext(), MediaService.class))
).buildAsync();
enqueue(mediaBrowserListenableFuture, media, true);
}
instantMix.removeObserver(this);
}
});
}
}
public static void saveChronology(MediaItem mediaItem) { public static void saveChronology(MediaItem mediaItem) {
if (mediaItem != null) { if (mediaItem != null) {
getChronologyRepository().insert(new Chronology(mediaItem)); getChronologyRepository().insert(new Chronology(mediaItem));

View file

@ -62,6 +62,8 @@ object Preferences {
private const val HOME_SECTOR_LIST = "home_sector_list" private const val HOME_SECTOR_LIST = "home_sector_list"
private const val RATING_PER_ITEM = "rating_per_item" private const val RATING_PER_ITEM = "rating_per_item"
private const val NEXT_UPDATE_CHECK = "next_update_check" private const val NEXT_UPDATE_CHECK = "next_update_check"
private const val CONTINUOUS_PLAY = "continuous_play"
private const val LAST_INSTANT_MIX = "last_instant_mix"
@JvmStatic @JvmStatic
@ -476,4 +478,21 @@ object Preferences {
fun setTempoUpdateReminder() { fun setTempoUpdateReminder() {
App.getInstance().preferences.edit().putLong(NEXT_UPDATE_CHECK, System.currentTimeMillis()).apply() App.getInstance().preferences.edit().putLong(NEXT_UPDATE_CHECK, System.currentTimeMillis()).apply()
} }
@JvmStatic
fun isContinuousPlayEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(CONTINUOUS_PLAY, true)
}
@JvmStatic
fun setLastInstantMix() {
App.getInstance().preferences.edit().putLong(LAST_INSTANT_MIX, System.currentTimeMillis()).apply()
}
@JvmStatic
fun isInstantMixUsable(): Boolean {
return App.getInstance().preferences.getLong(
LAST_INSTANT_MIX, 0
) + 5000 < System.currentTimeMillis()
}
} }

View file

@ -203,7 +203,7 @@ public class HomeViewModel extends AndroidViewModel {
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) { public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
mediaInstantMix.setValue(Collections.emptyList()); mediaInstantMix.setValue(Collections.emptyList());
songRepository.getInstantMix(media, 20).observe(owner, mediaInstantMix::postValue); songRepository.getInstantMix(media.getId(), 20).observe(owner, mediaInstantMix::postValue);
return mediaInstantMix; return mediaInstantMix;
} }

View file

@ -190,7 +190,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) { public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
instantMix.setValue(Collections.emptyList()); instantMix.setValue(Collections.emptyList());
songRepository.getInstantMix(media, 20).observe(owner, instantMix::postValue); songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
return instantMix; return instantMix;
} }

View file

@ -128,7 +128,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) { public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
instantMix.setValue(Collections.emptyList()); instantMix.setValue(Collections.emptyList());
songRepository.getInstantMix(media, 20).observe(owner, instantMix::postValue); songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
return instantMix; return instantMix;
} }

View file

@ -262,6 +262,8 @@
<string name="settings_audio_transcode_priority_toast">Priority on transcoding of track given to server</string> <string name="settings_audio_transcode_priority_toast">Priority on transcoding of track given to server</string>
<string name="settings_buffering_strategy">Buffering strategy</string> <string name="settings_buffering_strategy">Buffering strategy</string>
<string name="settings_buffering_strategy_summary">For the change to take effect you must manually restart the app.</string> <string name="settings_buffering_strategy_summary">For the change to take effect you must manually restart the app.</string>
<string name="settings_continuous_play_summary">Allows music to keep playing after a playlist has ended, playing similar songs</string>
<string name="settings_continuous_play_title">Continuous play</string>
<string name="settings_covers_cache">Size of artwork cache</string> <string name="settings_covers_cache">Size of artwork cache</string>
<string name="settings_data_saving_mode_summary">In order to reduce data consumption, avoid downloading covers.</string> <string name="settings_data_saving_mode_summary">In order to reduce data consumption, avoid downloading covers.</string>
<string name="settings_data_saving_mode_title">Limit mobile data usage</string> <string name="settings_data_saving_mode_title">Limit mobile data usage</string>

View file

@ -109,6 +109,12 @@
app:title="@string/settings_image_size" app:title="@string/settings_image_size"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
<SwitchPreference
android:title="@string/settings_continuous_play_title"
android:defaultValue="true"
android:summary="@string/settings_continuous_play_summary"
android:key="continuous_play" />
<SwitchPreference <SwitchPreference
android:title="@string/settings_wifi_only_title" android:title="@string/settings_wifi_only_title"
android:defaultValue="false" android:defaultValue="false"

View file

@ -29,7 +29,6 @@ import com.google.android.gms.common.GoogleApiAvailability
@UnstableApi @UnstableApi
class MediaService : MediaLibraryService(), SessionAvailabilityListener { class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var automotiveRepository: AutomotiveRepository private lateinit var automotiveRepository: AutomotiveRepository
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer private lateinit var castPlayer: CastPlayer
@ -121,6 +120,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
ReplayGainUtil.setReplayGain(player, tracks) ReplayGainUtil.setReplayGain(player, tracks)
MediaManager.scrobble(player.currentMediaItem, false) MediaManager.scrobble(player.currentMediaItem, false)
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
MediaManager.continuousPlay(player.currentMediaItem)
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {