Finally fixed the music queue and swap logic

This commit is contained in:
CappielloAntonio 2021-12-30 18:13:50 +01:00
parent fc271e8b44
commit 60b741bc11
10 changed files with 134 additions and 140 deletions

View file

@ -8,7 +8,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.media3.session.MediaBrowser;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
@ -16,14 +15,10 @@ import com.cappielloantonio.play.R;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment;
import com.cappielloantonio.play.util.MappingUtil;
import com.cappielloantonio.play.util.MusicUtil;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
private static final String TAG = "SongResultSearchAdapter";

View file

@ -20,7 +20,7 @@ import com.cappielloantonio.play.model.Server;
@SuppressLint("RestrictedApi")
@Database(
version = 28,
version = 29,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Playlist.class}
// autoMigrations = { @AutoMigration(from = 23, to = 24) }
)

View file

@ -26,7 +26,7 @@ public interface QueueDao {
void insertAll(List<Queue> songQueueObjects);
@Query("DELETE FROM queue WHERE queue.track_order=:position")
void deleteByPosition(int position);
void delete(int position);
@Query("DELETE FROM queue")
void deleteAll();

View file

@ -6,7 +6,7 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "queue")
public class Queue {
@PrimaryKey(autoGenerate = true)
@PrimaryKey
@ColumnInfo(name = "track_order")
private int trackOrder;
@ -37,7 +37,8 @@ public class Queue {
@ColumnInfo(name = "last_play")
private long lastPlay;
public Queue(String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration, long lastPlay) {
public Queue(int trackOrder, String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration, long lastPlay) {
this.trackOrder = trackOrder;
this.songID = songID;
this.title = title;
this.albumId = albumId;

View file

@ -45,46 +45,19 @@ public class QueueRepository {
return songs;
}
public void insert(Song song, boolean reset) {
public void insert(Song song, boolean reset, int afterIndex) {
try {
if(reset) {
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
delete.start();
delete.join();
List<Song> songs = new ArrayList<>();
if (!reset) {
GetSongsThreadSafe getSongsThreadSafe = new GetSongsThreadSafe(queueDao);
Thread getSongsThread = new Thread(getSongsThreadSafe);
getSongsThread.start();
getSongsThread.join();
songs = getSongsThreadSafe.getSongs();
}
Thread insert = new Thread(new InsertThreadSafe(queueDao, song));
insert.start();
insert.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void insertAll(List<Song> songs, boolean reset) {
try {
if(reset) {
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
delete.start();
delete.join();
}
Thread insertAll = new Thread(new InsertAllThreadSafe(queueDao, songs));
insertAll.start();
insertAll.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void insertImmediatelyAfter(Song song, int afterIndex) {
try {
GetSongsThreadSafe getSongsThreadSafe = new GetSongsThreadSafe(queueDao);
Thread getSongsThread = new Thread(getSongsThreadSafe);
getSongsThread.start();
getSongsThread.join();
List<Song> songs = getSongsThreadSafe.getSongs();
songs.add(afterIndex, song);
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
@ -99,14 +72,19 @@ public class QueueRepository {
}
}
public void insertAllImmediatelyAfter(List<Song> toAdd, int afterIndex) {
public void insertAll(List<Song> toAdd, boolean reset, int afterIndex) {
try {
GetSongsThreadSafe getSongsThreadSafe = new GetSongsThreadSafe(queueDao);
Thread getSongsThread = new Thread(getSongsThreadSafe);
getSongsThread.start();
getSongsThread.join();
List<Song> songs = new ArrayList<>();
if (!reset) {
GetSongsThreadSafe getSongsThreadSafe = new GetSongsThreadSafe(queueDao);
Thread getSongsThread = new Thread(getSongsThreadSafe);
getSongsThread.start();
getSongsThread.join();
songs = getSongsThreadSafe.getSongs();
}
List<Song> songs = getSongsThreadSafe.getSongs();
songs.addAll(afterIndex, toAdd);
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
@ -121,8 +99,8 @@ public class QueueRepository {
}
}
public void deleteByPosition(int position) {
DeleteByPositionThreadSafe delete = new DeleteByPositionThreadSafe(queueDao, position);
public void delete(int position) {
DeleteThreadSafe delete = new DeleteThreadSafe(queueDao, position);
Thread thread = new Thread(delete);
thread.start();
}
@ -174,21 +152,6 @@ public class QueueRepository {
}
}
private static class InsertThreadSafe implements Runnable {
private final QueueDao queueDao;
private final Song song;
public InsertThreadSafe(QueueDao queueDao, Song song) {
this.queueDao = queueDao;
this.song = song;
}
@Override
public void run() {
queueDao.insert(MappingUtil.mapSongToQueue(song));
}
}
private static class InsertAllThreadSafe implements Runnable {
private final QueueDao queueDao;
private final List<Song> songs;
@ -204,18 +167,18 @@ public class QueueRepository {
}
}
private static class DeleteByPositionThreadSafe implements Runnable {
private static class DeleteThreadSafe implements Runnable {
private final QueueDao queueDao;
private final int position;
public DeleteByPositionThreadSafe(QueueDao queueDao, int position) {
public DeleteThreadSafe(QueueDao queueDao, int position) {
this.queueDao = queueDao;
this.position = position;
}
@Override
public void run() {
queueDao.deleteByPosition(position);
queueDao.delete(position);
}
}

View file

@ -4,7 +4,6 @@ import android.content.Context;
import android.util.Log;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.MediaController;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.model.Song;
@ -99,6 +98,8 @@ public class MediaManager {
mediaBrowserListenableFuture.get().prepare();
mediaBrowserListenableFuture.get().seekTo(startIndex, 0);
mediaBrowserListenableFuture.get().play();
enqueueDatabase(songs, true, 0);
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, e.getMessage());
@ -116,6 +117,8 @@ public class MediaManager {
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(context, song));
mediaBrowserListenableFuture.get().prepare();
mediaBrowserListenableFuture.get().play();
enqueueDatabase(song, true, 0);
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, e.getMessage());
@ -130,10 +133,14 @@ public class MediaManager {
try {
if (mediaBrowserListenableFuture.isDone()) {
if (playImmediatelyAfter) {
mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getCurrentMediaItemIndex(), MappingUtil.mapMediaItems(context, songs));
enqueueDatabase(songs, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItems(context, songs));
} else {
enqueueDatabase(songs, false, mediaBrowserListenableFuture.get().getMediaItemCount());
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(context, songs));
}
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, e.getMessage());
@ -148,8 +155,10 @@ public class MediaManager {
try {
if (mediaBrowserListenableFuture.isDone()) {
if (playImmediatelyAfter) {
mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getCurrentMediaItemIndex(), MappingUtil.mapMediaItem(context, song));
enqueueDatabase(song, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItem(context, song));
} else {
enqueueDatabase(song, false, mediaBrowserListenableFuture.get().getMediaItemCount());
mediaBrowserListenableFuture.get().addMediaItem(MappingUtil.mapMediaItem(context, song));
}
}
@ -160,11 +169,60 @@ public class MediaManager {
}
}
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Song> songs, int from, int to) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().moveMediaItem(from, to);
swapDatabase(songs);
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, e.getMessage());
}
}, MoreExecutors.directExecutor());
}
}
public static void remove(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Song> songs, int toRemove) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
if (mediaBrowserListenableFuture.get().getMediaItemCount() > 1 && mediaBrowserListenableFuture.get().getCurrentMediaItemIndex() != toRemove) {
mediaBrowserListenableFuture.get().removeMediaItem(toRemove);
removeDatabase(songs, toRemove);
} else {
removeDatabase(songs, -1);
}
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, e.getMessage());
}
}, MoreExecutors.directExecutor());
}
}
private static QueueRepository getQueueRepository() {
return new QueueRepository(App.getInstance());
}
private static int getCurrentMediaIndex(MediaController mediaController) {
return mediaController.getCurrentMediaItemIndex();
private static void enqueueDatabase(List<Song> songs, boolean reset, int afterIndex) {
getQueueRepository().insertAll(songs, reset, afterIndex);
}
private static void enqueueDatabase(Song song, boolean reset, int afterIndex) {
getQueueRepository().insert(song, reset, afterIndex);
}
private static void swapDatabase(List<Song> songs) {
getQueueRepository().insertAll(songs, true, 0);
}
private static void removeDatabase(List<Song> songs, int toRemove) {
if (toRemove != -1) {
songs.remove(toRemove);
getQueueRepository().insertAll(songs, true, 0);
}
}
}

View file

@ -18,7 +18,6 @@ import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.session.MediaBrowser;
@ -36,6 +35,7 @@ import com.cappielloantonio.play.adapter.PlayerNowPlayingSongAdapter;
import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
import com.cappielloantonio.play.glide.CustomGlideRequest;
import com.cappielloantonio.play.service.MediaManager;
import com.cappielloantonio.play.service.MediaService;
import com.cappielloantonio.play.ui.activity.MainActivity;
import com.cappielloantonio.play.ui.dialog.RatingDialog;
@ -46,7 +46,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Collections;
import java.util.Objects;
public class PlayerBottomSheetFragment extends Fragment {
private static final String TAG = "PlayerBottomSheetFragment";
@ -63,7 +62,7 @@ public class PlayerBottomSheetFragment extends Fragment {
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaController> mediaControllerListenableFuture;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private PlayerSongQueueAdapter playerSongQueueAdapter;
@ -96,13 +95,13 @@ public class PlayerBottomSheetFragment extends Fragment {
public void onStart() {
super.onStart();
initializeMediaController();
initializeMediaBrowser();
bindMediaController();
}
@Override
public void onStop() {
releaseMediaController();
releaseMediaBrowser();
super.onStop();
}
@ -128,24 +127,24 @@ public class PlayerBottomSheetFragment extends Fragment {
}
@SuppressLint("UnsafeOptInUsageError")
private void initializeMediaController() {
mediaControllerListenableFuture = new MediaController.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaController() {
MediaController.releaseFuture(mediaControllerListenableFuture);
private void releaseMediaBrowser() {
MediaController.releaseFuture(mediaBrowserListenableFuture);
}
@SuppressLint("UnsafeOptInUsageError")
private void bindMediaController() {
mediaControllerListenableFuture.addListener(() -> {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaController mediaController = mediaControllerListenableFuture.get();
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
bind.playerBodyLayout.setPlayer(mediaController);
bind.playerBodyLayout.setPlayer(mediaBrowser);
setMediaControllerListener(mediaController);
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
@ -153,18 +152,18 @@ public class PlayerBottomSheetFragment extends Fragment {
}
@SuppressLint("UnsafeOptInUsageError")
private void setMediaControllerListener(MediaController mediaController) {
setMetadata(mediaController.getMediaMetadata());
setContentDuration(mediaController.getContentDuration());
setPlayingState(mediaController.isPlaying());
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
setMetadata(mediaBrowser.getMediaMetadata());
setContentDuration(mediaBrowser.getContentDuration());
setPlayingState(mediaBrowser.isPlaying());
setHeaderMediaController();
setHeaderNextButtonState(mediaController.hasNextMediaItem());
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
mediaController.addListener(new Player.Listener() {
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setMetadata(mediaMetadata);
setContentDuration(mediaController.getContentDuration());
setContentDuration(mediaBrowser.getContentDuration());
}
@Override
@ -175,7 +174,7 @@ public class PlayerBottomSheetFragment extends Fragment {
//TODO: Temporary solution. Too many events are caught in this way.
@Override
public void onEvents(Player player, Player.Events events) {
setHeaderNextButtonState(mediaController.hasNextMediaItem());
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
}
});
}
@ -244,7 +243,11 @@ public class PlayerBottomSheetFragment extends Fragment {
playerSongQueueAdapter = new PlayerSongQueueAdapter(requireContext(), this);
playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), queue -> playerSongQueueAdapter.setItems(MappingUtil.mapQueue(queue)));
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), queue -> {
if (queue != null) {
playerSongQueueAdapter.setItems(MappingUtil.mapQueue(queue));
}
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
@ -253,8 +256,9 @@ public class PlayerBottomSheetFragment extends Fragment {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
if (originalPosition == -1)
if (originalPosition == -1) {
originalPosition = viewHolder.getBindingAdapterPosition();
}
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
@ -271,7 +275,7 @@ public class PlayerBottomSheetFragment extends Fragment {
*/
Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition);
Objects.requireNonNull(recyclerView.getAdapter()).notifyItemMoved(fromPosition, toPosition);
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
return false;
}
@ -280,13 +284,9 @@ public class PlayerBottomSheetFragment extends Fragment {
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
/*
* Qui vado a riscivere tutta la table Queue, quando teoricamente potrei solo swappare l'ordine degli elementi interessati
* Nel caso la coda contenesse parecchi brani, potrebbero verificarsi rallentamenti pesanti
*/
// playerBottomSheetViewModel.orderSongAfterSwap(playerSongQueueAdapter.getItems());
// MusicPlayerRemote.moveSong(originalPosition, toPosition);
// playerSongCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition(), false);
if (originalPosition != -1 && fromPosition != -1 && toPosition != -1) {
MediaManager.swap(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), originalPosition, toPosition);
}
originalPosition = -1;
fromPosition = -1;
@ -295,17 +295,10 @@ public class PlayerBottomSheetFragment extends Fragment {
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
/*if (!(viewHolder.getBindingAdapterPosition() == MusicPlayerRemote.getPosition()) && !(MusicPlayerRemote.getPlayingQueue().size() <= 1)) {
MusicPlayerRemote.removeFromQueue(viewHolder.getBindingAdapterPosition());
playerBottomSheetViewModel.removeSong(viewHolder.getBindingAdapterPosition());
Objects.requireNonNull(playerQueueRecyclerView.getAdapter()).notifyItemRemoved(viewHolder.getBindingAdapterPosition());
playerSongCoverViewPager.setCurrentItem(MusicPlayerRemote.getPosition(), false);
} else {
playerQueueRecyclerView.getAdapter().notifyDataSetChanged();
}*/
MediaManager.remove(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), viewHolder.getBindingAdapterPosition());
viewHolder.getBindingAdapter().notifyDataSetChanged();
}
}
).attachToRecyclerView(playerQueueRecyclerView);
}).attachToRecyclerView(playerQueueRecyclerView);
}
private void initFavoriteButtonClick() {

View file

@ -100,15 +100,15 @@ public class MappingUtil {
return songs;
}
public static Queue mapSongToQueue(Song song) {
return new Queue(song.getId(), song.getTitle(), song.getAlbumId(), song.getAlbumName(), song.getArtistId(), song.getArtistName(), song.getPrimary(), song.getDuration(), 0);
public static Queue mapSongToQueue(Song song, int trackOrder) {
return new Queue(trackOrder, song.getId(), song.getTitle(), song.getAlbumId(), song.getAlbumName(), song.getArtistId(), song.getArtistName(), song.getPrimary(), song.getDuration(), 0);
}
public static List<Queue> mapSongsToQueue(List<Song> songs) {
List<Queue> queue = new ArrayList<>();
for (Song song : songs) {
queue.add(mapSongToQueue(song));
for (int counter = 0; counter < songs.size(); counter++) {
queue.add(mapSongToQueue(songs.get(counter), counter));
}
return queue;

View file

@ -15,10 +15,8 @@ import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.QueueRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.util.DownloadUtil;
import com.cappielloantonio.play.util.PreferenceUtil;
import java.util.Collections;
import java.util.List;
public class PlayerBottomSheetViewModel extends AndroidViewModel {
@ -31,20 +29,16 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
private final MutableLiveData<Song> songLiveData = new MutableLiveData<>(null);
private final LiveData<List<Queue>> queueSong;
public PlayerBottomSheetViewModel(@NonNull Application application) {
super(application);
songRepository = new SongRepository(application);
artistRepository = new ArtistRepository(application);
queueRepository = new QueueRepository(application);
queueSong = queueRepository.getLiveQueue();
}
public LiveData<List<Queue>> getQueueSong() {
return queueSong;
return queueRepository.getLiveQueue();
}
public Song getCurrentSong() {
@ -63,21 +57,13 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
songRepository.star(song.getId());
song.setFavorite(true);
if(PreferenceUtil.getInstance(context).isStarredSyncEnabled()) {
if (PreferenceUtil.getInstance(context).isStarredSyncEnabled()) {
// DownloadUtil.getDownloadTracker(context).download(Collections.singletonList(song), null, null);
}
}
}
}
public void orderSongAfterSwap(List<Song> songs) {
queueRepository.insertAllAndStartNew(songs);
}
public void removeSong(int position) {
queueRepository.deleteByPosition(position);
}
public LiveData<Artist> getArtist() {
Song song = getCurrentSong();
return artistRepository.getArtist(song.getArtistId());

View file

@ -31,8 +31,6 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
super(application);
playlistRepository = new PlaylistRepository(application);
Log.d(TAG, "PlaylistEditorViewModel()");
}
public void createPlaylist(String name) {