diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/SongHorizontalAdapter.java index 86c57a67..92029f88 100644 --- a/app/src/main/java/com/cappielloantonio/play/adapter/SongHorizontalAdapter.java +++ b/app/src/main/java/com/cappielloantonio/play/adapter/SongHorizontalAdapter.java @@ -19,6 +19,8 @@ import com.cappielloantonio.play.glide.CustomGlideRequest; import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.service.MediaManager; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.MusicUtil; import com.google.common.util.concurrent.ListenableFuture; @@ -59,11 +61,11 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter downloads, @Requirements.RequirementFlags int notMetRequirements) { + protected Notification getForegroundNotification(@NonNull List downloads, @Requirements.RequirementFlags int notMetRequirements) { return DownloadUtil.getDownloadNotificationHelper(this).buildProgressNotification(this, R.drawable.ic_download, null, null, downloads, notMetRequirements); } - private static final class TerminalStateNotificationHelper implements DownloadManager.Listener { private final Context context; private final DownloadNotificationHelper notificationHelper; @@ -63,7 +66,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa @SuppressLint("UnsafeOptInUsageError") @Override - public void onDownloadChanged(DownloadManager downloadManager, Download download, @Nullable Exception finalException) { + public void onDownloadChanged(@NonNull DownloadManager downloadManager, Download download, @Nullable Exception finalException) { Notification notification; if (download.state == Download.STATE_COMPLETED) { diff --git a/app/src/main/java/com/cappielloantonio/play/service/DownloaderTracker.java b/app/src/main/java/com/cappielloantonio/play/service/DownloaderTracker.java index 3111f686..82a32d61 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/DownloaderTracker.java +++ b/app/src/main/java/com/cappielloantonio/play/service/DownloaderTracker.java @@ -4,17 +4,12 @@ import static androidx.media3.common.util.Assertions.checkNotNull; import android.annotation.SuppressLint; import android.content.Context; -import android.content.DialogInterface; import android.net.Uri; -import android.widget.Toast; import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; import androidx.media3.common.MediaItem; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; -import androidx.media3.datasource.HttpDataSource; -import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.offline.Download; import androidx.media3.exoplayer.offline.DownloadCursor; import androidx.media3.exoplayer.offline.DownloadHelper; @@ -22,39 +17,31 @@ import androidx.media3.exoplayer.offline.DownloadIndex; import androidx.media3.exoplayer.offline.DownloadManager; import androidx.media3.exoplayer.offline.DownloadRequest; import androidx.media3.exoplayer.offline.DownloadService; -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; -import androidx.media3.exoplayer.trackselection.MappingTrackSelector; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; public class DownloaderTracker { private static final String TAG = "DownloadTracker"; private final Context context; - private final HttpDataSource.Factory httpDataSourceFactory; private final CopyOnWriteArraySet listeners; private final HashMap downloads; private final DownloadIndex downloadIndex; - private final DefaultTrackSelector.Parameters trackSelectorParameters; - - @Nullable - private StartDownloadDialogHelper startDownloadDialogHelper; public interface Listener { void onDownloadsChanged(); } @SuppressLint("UnsafeOptInUsageError") - public DownloaderTracker(Context context, HttpDataSource.Factory httpDataSourceFactory, DownloadManager downloadManager) { + public DownloaderTracker(Context context, DownloadManager downloadManager) { this.context = context.getApplicationContext(); - this.httpDataSourceFactory = httpDataSourceFactory; listeners = new CopyOnWriteArraySet<>(); downloads = new HashMap<>(); downloadIndex = downloadManager.getDownloadIndex(); - trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context); downloadManager.addListener(new DownloadManagerListener()); loadDownloads(); @@ -70,28 +57,48 @@ public class DownloaderTracker { listeners.remove(listener); } + @SuppressLint("UnsafeOptInUsageError") + private DownloadRequest buildDownloadRequest(MediaItem mediaItem) { + return DownloadHelper.forMediaItem(context, mediaItem).getDownloadRequest(Util.getUtf8Bytes(checkNotNull(mediaItem.mediaId))); + } + @SuppressLint("UnsafeOptInUsageError") public boolean isDownloaded(MediaItem mediaItem) { @Nullable Download download = downloads.get(checkNotNull(mediaItem.localConfiguration).uri); return download != null && download.state != Download.STATE_FAILED; } - @Nullable - public DownloadRequest getDownloadRequest(Uri uri) { - @Nullable Download download = downloads.get(uri); - return download != null && download.state != Download.STATE_FAILED ? download.request : null; + @SuppressLint("UnsafeOptInUsageError") + public boolean areDownloaded(List mediaItems) { + for (MediaItem mediaItem : mediaItems) { + @Nullable Download download = downloads.get(checkNotNull(mediaItem.localConfiguration).uri); + if (download != null && download.state != Download.STATE_FAILED) { + return true; + } + } + + return false; } @SuppressLint("UnsafeOptInUsageError") - public void toggleDownload(FragmentManager fragmentManager, MediaItem mediaItem, RenderersFactory renderersFactory) { - @Nullable Download download = downloads.get(checkNotNull(mediaItem.localConfiguration).uri); - if (download != null && download.state != Download.STATE_FAILED) { - androidx.media3.exoplayer.offline.DownloadService.sendRemoveDownload(context, DownloaderService.class, download.request.id, false); - } else { - if (startDownloadDialogHelper != null) { - startDownloadDialogHelper.release(); - } - startDownloadDialogHelper = new StartDownloadDialogHelper(fragmentManager, DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, httpDataSourceFactory), mediaItem); + public void download(MediaItem mediaItem) { + DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false); + } + + public void download(List mediaItems) { + for (MediaItem mediaItem : mediaItems) { + download(mediaItem); + } + } + + @SuppressLint("UnsafeOptInUsageError") + public void remove(MediaItem mediaItem) { + DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false); + } + + public void remove(List mediaItems) { + for (MediaItem mediaItem : mediaItems) { + remove(mediaItem); } } @@ -124,109 +131,4 @@ public class DownloaderTracker { } } } - - private final class StartDownloadDialogHelper implements DownloadHelper.Callback, DialogInterface.OnClickListener, DialogInterface.OnDismissListener { - private final FragmentManager fragmentManager; - private final DownloadHelper downloadHelper; - private final MediaItem mediaItem; - - private TrackSelectionDialog trackSelectionDialog; - private MappingTrackSelector.MappedTrackInfo mappedTrackInfo; - - @SuppressLint("UnsafeOptInUsageError") - public StartDownloadDialogHelper(FragmentManager fragmentManager, DownloadHelper downloadHelper, MediaItem mediaItem) { - this.fragmentManager = fragmentManager; - this.downloadHelper = downloadHelper; - this.mediaItem = mediaItem; - downloadHelper.prepare(this); - } - - @SuppressLint("UnsafeOptInUsageError") - public void release() { - downloadHelper.release(); - if (trackSelectionDialog != null) { - trackSelectionDialog.dismiss(); - } - } - - @SuppressLint("UnsafeOptInUsageError") - @Override - public void onPrepared(DownloadHelper helper) { - onDownloadPrepared(helper); - } - - @SuppressLint("UnsafeOptInUsageError") - @Override - public void onPrepareError(DownloadHelper helper, IOException e) { - Toast.makeText(context, R.string.download_start_error, Toast.LENGTH_LONG).show(); - Log.e(TAG, e.getMessage()); - } - - // DialogInterface.OnClickListener implementation. - - @SuppressLint("UnsafeOptInUsageError") - @Override - public void onClick(DialogInterface dialog, int which) { - for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) { - downloadHelper.clearTrackSelections(periodIndex); - for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { - if (!trackSelectionDialog.getIsDisabled(i)) { - downloadHelper.addTrackSelectionForSingleRenderer(periodIndex, i, trackSelectorParameters, trackSelectionDialog.getOverrides(i)); - } - } - } - - DownloadRequest downloadRequest = buildDownloadRequest(); - - if (downloadRequest.streamKeys.isEmpty()) { - return; - } - - startDownload(downloadRequest); - } - - @SuppressLint("UnsafeOptInUsageError") - @Override - public void onDismiss(DialogInterface dialogInterface) { - trackSelectionDialog = null; - downloadHelper.release(); - } - - @SuppressLint("UnsafeOptInUsageError") - private void onDownloadPrepared(DownloadHelper helper) { - if (helper.getPeriodCount() == 0) { - Log.d(TAG, "No periods found. Downloading entire stream."); - startDownload(); - downloadHelper.release(); - return; - } - - mappedTrackInfo = downloadHelper.getMappedTrackInfo(0); - - if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) { - Log.d(TAG, "No dialog content. Downloading entire stream."); - startDownload(); - downloadHelper.release(); - return; - } - - trackSelectionDialog = TrackSelectionDialog.createForMappedTrackInfoAndParameters(R.string.exo_download_description, mappedTrackInfo, trackSelectorParameters, false, true, this, this); - - trackSelectionDialog.show(fragmentManager, null); - } - - private void startDownload() { - startDownload(buildDownloadRequest()); - } - - @SuppressLint("UnsafeOptInUsageError") - private void startDownload(DownloadRequest downloadRequest) { - DownloadService.sendAddDownload(context, DownloaderService.class, downloadRequest, false); - } - - @SuppressLint("UnsafeOptInUsageError") - private DownloadRequest buildDownloadRequest() { - return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(checkNotNull(mediaItem.mediaMetadata.title.toString()))).copyWithKeySetId(keySetId); - } - } } diff --git a/app/src/main/java/com/cappielloantonio/play/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/play/service/MediaManager.java index a4118225..68969a29 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/play/service/MediaManager.java @@ -61,7 +61,7 @@ public class MediaManager { try { if (mediaBrowserListenableFuture.isDone()) { mediaBrowserListenableFuture.get().clearMediaItems(); - mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(context, songs)); + mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(context, songs, true)); mediaBrowserListenableFuture.get().seekTo(getQueueRepository().getLastPlayedSongIndex(), 0); mediaBrowserListenableFuture.get().prepare(); } @@ -148,7 +148,7 @@ public class MediaManager { try { if (mediaBrowserListenableFuture.isDone()) { mediaBrowserListenableFuture.get().clearMediaItems(); - mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(context, songs)); + mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(context, songs, true)); mediaBrowserListenableFuture.get().prepare(); mediaBrowserListenableFuture.get().seekTo(startIndex, 0); mediaBrowserListenableFuture.get().play(); @@ -167,7 +167,7 @@ public class MediaManager { try { if (mediaBrowserListenableFuture.isDone()) { mediaBrowserListenableFuture.get().clearMediaItems(); - mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(context, song)); + mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(context, song, true)); mediaBrowserListenableFuture.get().prepare(); mediaBrowserListenableFuture.get().play(); enqueueDatabase(song, true, 0); @@ -186,10 +186,10 @@ public class MediaManager { if (mediaBrowserListenableFuture.isDone()) { if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) { enqueueDatabase(songs, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex()); - mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItems(context, songs)); + mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItems(context, songs, true)); } else { enqueueDatabase(songs, false, mediaBrowserListenableFuture.get().getMediaItemCount()); - mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(context, songs)); + mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(context, songs, true)); } } } catch (ExecutionException | InterruptedException e) { @@ -206,10 +206,10 @@ public class MediaManager { if (mediaBrowserListenableFuture.isDone()) { if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) { enqueueDatabase(song, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex()); - mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItem(context, song)); + mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItem(context, song, true)); } else { enqueueDatabase(song, false, mediaBrowserListenableFuture.get().getMediaItemCount()); - mediaBrowserListenableFuture.get().addMediaItem(MappingUtil.mapMediaItem(context, song)); + mediaBrowserListenableFuture.get().addMediaItem(MappingUtil.mapMediaItem(context, song, true)); } } } catch (ExecutionException | InterruptedException e) { diff --git a/app/src/main/java/com/cappielloantonio/play/service/MediaService.java b/app/src/main/java/com/cappielloantonio/play/service/MediaService.java index 694e5917..3b8530d1 100644 --- a/app/src/main/java/com/cappielloantonio/play/service/MediaService.java +++ b/app/src/main/java/com/cappielloantonio/play/service/MediaService.java @@ -9,11 +9,15 @@ import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.Player; +import androidx.media3.datasource.DataSource; import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSourceFactory; import androidx.media3.session.MediaLibraryService; import androidx.media3.session.MediaSession; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.DownloadUtil; public class MediaService extends MediaLibraryService { private static final String TAG = "MediaService"; @@ -21,11 +25,14 @@ public class MediaService extends MediaLibraryService { public static final int REQUEST_CODE = 432; private ExoPlayer player; + private DataSource.Factory dataSourceFactory; + private MediaSourceFactory mediaSourceFactory; private MediaLibrarySession mediaLibrarySession; @Override public void onCreate() { super.onCreate(); + initializeMediaSource(); initializePlayer(); initializePlayerListener(); } @@ -42,9 +49,16 @@ public class MediaService extends MediaLibraryService { return mediaLibrarySession; } + @SuppressLint("UnsafeOptInUsageError") + private void initializeMediaSource() { + dataSourceFactory = DownloadUtil.getDataSourceFactory(this); + mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory); + } + @SuppressLint("UnsafeOptInUsageError") private void initializePlayer() { player = new ExoPlayer.Builder(this) + .setMediaSourceFactory(mediaSourceFactory) .setAudioAttributes(AudioAttributes.DEFAULT, true) .setHandleAudioBecomingNoisy(true) .setWakeMode(C.WAKE_MODE_NETWORK) diff --git a/app/src/main/java/com/cappielloantonio/play/ui/activity/base/BaseActivity.java b/app/src/main/java/com/cappielloantonio/play/ui/activity/base/BaseActivity.java index 549d1d15..f185c713 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/activity/base/BaseActivity.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/activity/base/BaseActivity.java @@ -3,34 +3,41 @@ package com.cappielloantonio.play.ui.activity.base; import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.os.PowerManager; import android.provider.Settings; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.media3.common.MediaItem; -import androidx.media3.common.MediaMetadata; +import androidx.media3.exoplayer.offline.DownloadService; import androidx.media3.session.MediaBrowser; import androidx.media3.session.SessionToken; import com.cappielloantonio.play.R; +import com.cappielloantonio.play.service.DownloaderService; +import com.cappielloantonio.play.service.DownloaderTracker; import com.cappielloantonio.play.service.MediaService; +import com.cappielloantonio.play.util.DownloadUtil; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import java.util.concurrent.ExecutionException; - -public class BaseActivity extends AppCompatActivity { +public class BaseActivity extends AppCompatActivity implements DownloaderTracker.Listener { private static final String TAG = "BaseActivity"; private ListenableFuture mediaBrowserListenableFuture; + private DownloaderTracker downloaderTracker; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initializeDownloader(); + } @Override protected void onStart() { super.onStart(); initializeBrowser(); + addDownloadListener(); } @Override @@ -42,9 +49,16 @@ public class BaseActivity extends AppCompatActivity { @Override protected void onStop() { releaseBrowser(); + removeDownloadListener(); super.onStop(); } + @Override + public void onDownloadsChanged() { + // TODO Notificare all'item scaricato che lo stato di download รจ cambiato + // sampleAdapter.notifyDataSetChanged(); + } + private void checkBatteryOptimization() { if (detectBatteryOptimization()) { showBatteryOptimizationDialog(); @@ -85,4 +99,23 @@ public class BaseActivity extends AppCompatActivity { public ListenableFuture getMediaBrowserListenableFuture() { return mediaBrowserListenableFuture; } + + @SuppressLint("UnsafeOptInUsageError") + private void initializeDownloader() { + downloaderTracker = DownloadUtil.getDownloadTracker(this); + + try { + DownloadService.start(this, DownloaderService.class); + } catch (IllegalStateException e) { + DownloadService.startForeground(this, DownloaderService.class); + } + } + + private void addDownloadListener() { + downloaderTracker.addListener(this); + } + + private void removeDownloadListener() { + downloaderTracker.removeListener(this); + } } diff --git a/app/src/main/java/com/cappielloantonio/play/ui/dialog/StarredSyncDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/dialog/StarredSyncDialog.java index eeb14105..845130de 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/dialog/StarredSyncDialog.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/dialog/StarredSyncDialog.java @@ -13,6 +13,7 @@ import androidx.lifecycle.ViewModelProvider; import com.cappielloantonio.play.R; import com.cappielloantonio.play.databinding.DialogConnectionAlertBinding; import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.PreferenceUtil; import com.cappielloantonio.play.viewmodel.StarredSyncViewModel; @@ -34,8 +35,10 @@ public class StarredSyncDialog extends DialogFragment { builder.setView(bind.getRoot()) .setTitle(R.string.starred_sync_dialog_title) - .setPositiveButton(R.string.starred_sync_dialog_positive_button, (dialog, id) -> { }) - .setNegativeButton(R.string.starred_sync_dialog_negative_button, (dialog, id) -> { }); + .setPositiveButton(R.string.starred_sync_dialog_positive_button, (dialog, id) -> { + }) + .setNegativeButton(R.string.starred_sync_dialog_negative_button, (dialog, id) -> { + }); return builder.create(); } @@ -60,7 +63,7 @@ public class StarredSyncDialog extends DialogFragment { ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { starredSyncViewModel.getStarredTracks(requireActivity()).observe(requireActivity(), songs -> { if (songs != null) { - // DownloadUtil.getDownloadTracker(context).download(songs, null, null); + DownloadUtil.getDownloadTracker(context).download(MappingUtil.mapMediaItems(context, songs, false)); } }); diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/AlbumPageFragment.java index 4590ffba..5265fa91 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/AlbumPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/AlbumPageFragment.java @@ -27,6 +27,8 @@ 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.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.MusicUtil; import com.cappielloantonio.play.viewmodel.AlbumPageViewModel; import com.google.common.util.concurrent.ListenableFuture; @@ -106,7 +108,7 @@ public class AlbumPageFragment extends Fragment { if (item.getItemId() == R.id.action_download_album) { albumPageViewModel.getAlbumSongLiveList(requireActivity()).observe(requireActivity(), songs -> { if (isVisible() && getActivity() != null) { - // DownloadUtil.getDownloadTracker(requireContext()).download(songs, null, null); + DownloadUtil.getDownloadTracker(requireContext()).download(MappingUtil.mapMediaItems(requireContext(), songs, false)); } }); return true; diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlaylistPageFragment.java index b52355f7..b9a8fc3f 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlaylistPageFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/PlaylistPageFragment.java @@ -19,16 +19,13 @@ import androidx.media3.session.SessionToken; import androidx.recyclerview.widget.LinearLayoutManager; import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners; -import com.cappielloantonio.play.App; import com.cappielloantonio.play.R; import com.cappielloantonio.play.adapter.SongHorizontalAdapter; import com.cappielloantonio.play.databinding.FragmentPlaylistPageBinding; import com.cappielloantonio.play.glide.CustomGlideRequest; -import com.cappielloantonio.play.repository.QueueRepository; import com.cappielloantonio.play.service.MediaManager; import com.cappielloantonio.play.service.MediaService; import com.cappielloantonio.play.ui.activity.MainActivity; -import com.cappielloantonio.play.util.DownloadUtil; import com.cappielloantonio.play.util.MusicUtil; import com.cappielloantonio.play.viewmodel.PlaylistPageViewModel; import com.google.common.util.concurrent.ListenableFuture; diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java index 1d5eb7f0..eebeb16a 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java @@ -14,6 +14,7 @@ import android.widget.ToggleButton; import androidx.annotation.Nullable; import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.MediaItem; import androidx.media3.session.MediaBrowser; import androidx.media3.session.SessionToken; import androidx.navigation.fragment.NavHostFragment; @@ -29,6 +30,8 @@ import com.cappielloantonio.play.repository.AlbumRepository; import com.cappielloantonio.play.service.MediaManager; import com.cappielloantonio.play.service.MediaService; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.MusicUtil; import com.cappielloantonio.play.viewmodel.AlbumBottomSheetViewModel; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; @@ -153,19 +156,21 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements TextView removeAll = view.findViewById(R.id.remove_all_text_view); albumBottomSheetViewModel.getAlbumTracks().observe(requireActivity(), songs -> { + List mediaItems = MappingUtil.mapMediaItems(requireContext(), songs, false); + downloadAll.setOnClickListener(v -> { - // DownloadUtil.getDownloadTracker(requireContext()).download(songs, null, null); + DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems); dismissBottomSheet(); }); - /*if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(songs)) { + if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) { removeAll.setOnClickListener(v -> { - DownloadUtil.getDownloadTracker(requireContext()).remove(songs); + DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems); dismissBottomSheet(); }); } else { removeAll.setVisibility(View.GONE); - }*/ + } }); TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view); diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java index aca9f5cb..5458ecff 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java @@ -30,6 +30,8 @@ import com.cappielloantonio.play.service.MediaService; import com.cappielloantonio.play.ui.activity.MainActivity; import com.cappielloantonio.play.ui.dialog.PlaylistChooserDialog; import com.cappielloantonio.play.ui.dialog.RatingDialog; +import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.MusicUtil; import com.cappielloantonio.play.viewmodel.SongBottomSheetViewModel; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; @@ -155,13 +157,13 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements TextView download = view.findViewById(R.id.download_text_view); download.setOnClickListener(v -> { - // DownloadUtil.getDownloadTracker(requireContext()).download(Collections.singletonList(song), null, null); + DownloadUtil.getDownloadTracker(requireContext()).download(MappingUtil.mapMediaItem(requireContext(), song, false)); dismissBottomSheet(); }); TextView remove = view.findViewById(R.id.remove_text_view); remove.setOnClickListener(v -> { - // DownloadUtil.getDownloadTracker(requireContext()).remove(Collections.singletonList(song)); + DownloadUtil.getDownloadTracker(requireContext()).remove(MappingUtil.mapMediaItem(requireContext(), song, false)); dismissBottomSheet(); }); @@ -214,13 +216,13 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements } private void initDownloadUI(TextView download, TextView remove) { - /*if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song)) { + if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(MappingUtil.mapMediaItem(requireContext(), song, false))) { download.setVisibility(View.GONE); remove.setVisibility(View.VISIBLE); } else { download.setVisibility(View.VISIBLE); remove.setVisibility(View.GONE); - }*/ + } } @SuppressLint("UnsafeOptInUsageError") diff --git a/app/src/main/java/com/cappielloantonio/play/util/DownloadUtil.java b/app/src/main/java/com/cappielloantonio/play/util/DownloadUtil.java index 9e8bae81..f779a13d 100644 --- a/app/src/main/java/com/cappielloantonio/play/util/DownloadUtil.java +++ b/app/src/main/java/com/cappielloantonio/play/util/DownloadUtil.java @@ -14,8 +14,6 @@ import androidx.media3.datasource.cache.Cache; import androidx.media3.datasource.cache.CacheDataSource; import androidx.media3.datasource.cache.NoOpCacheEvictor; import androidx.media3.datasource.cache.SimpleCache; -import androidx.media3.exoplayer.DefaultRenderersFactory; -import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.offline.ActionFileUpgradeUtil; import androidx.media3.exoplayer.offline.DefaultDownloadIndex; import androidx.media3.exoplayer.offline.DownloadManager; @@ -48,26 +46,7 @@ public final class DownloadUtil { private static DownloaderTracker downloaderTracker; private static DownloadNotificationHelper downloadNotificationHelper; - /** - * Returns whether extension renderers should be used. - */ - public static boolean useExtensionRenderers() { - return false; - } - - @SuppressLint("UnsafeOptInUsageError") - public static RenderersFactory buildRenderersFactory(Context context, boolean preferExtensionRenderer) { - @DefaultRenderersFactory.ExtensionRendererMode - int extensionRendererMode = useExtensionRenderers() - ? (preferExtensionRenderer - ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) - : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; - - return new DefaultRenderersFactory(context.getApplicationContext()).setExtensionRendererMode(extensionRendererMode); - } - - public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) { + public static synchronized HttpDataSource.Factory getHttpDataSourceFactory() { if (httpDataSourceFactory == null) { CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); @@ -81,7 +60,7 @@ public final class DownloadUtil { public static synchronized DataSource.Factory getDataSourceFactory(Context context) { if (dataSourceFactory == null) { context = context.getApplicationContext(); - DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory(context)); + DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory()); dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context)); } @@ -113,6 +92,7 @@ public final class DownloadUtil { File downloadContentDirectory = new File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY); downloadCache = new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider(context)); } + return downloadCache; } @@ -120,10 +100,10 @@ public final class DownloadUtil { private static synchronized void ensureDownloadManagerInitialized(Context context) { if (downloadManager == null) { DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider(context)); - upgradeActionFile(context, DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false); + upgradeActionFile(context, DOWNLOAD_ACTION_FILE, downloadIndex, false); upgradeActionFile(context, DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex, true); - downloadManager = new DownloadManager(context, getDatabaseProvider(context), getDownloadCache(context), getHttpDataSourceFactory(context), Executors.newFixedThreadPool(/* nThreads= */ 6)); - downloaderTracker = new DownloaderTracker(context, getHttpDataSourceFactory(context), downloadManager); + downloadManager = new DownloadManager(context, getDatabaseProvider(context), getDownloadCache(context), getHttpDataSourceFactory(), Executors.newFixedThreadPool(6)); + downloaderTracker = new DownloaderTracker(context, downloadManager); } } @@ -141,12 +121,13 @@ public final class DownloadUtil { if (databaseProvider == null) { databaseProvider = new StandaloneDatabaseProvider(context); } + return databaseProvider; } private static synchronized File getDownloadDirectory(Context context) { if (downloadDirectory == null) { - downloadDirectory = context.getExternalFilesDir(/* type= */ null); + downloadDirectory = context.getExternalFilesDir(null); if (downloadDirectory == null) { downloadDirectory = context.getFilesDir(); } diff --git a/app/src/main/java/com/cappielloantonio/play/util/MappingUtil.java b/app/src/main/java/com/cappielloantonio/play/util/MappingUtil.java index 63d5c910..d4261a36 100644 --- a/app/src/main/java/com/cappielloantonio/play/util/MappingUtil.java +++ b/app/src/main/java/com/cappielloantonio/play/util/MappingUtil.java @@ -197,7 +197,7 @@ public class MappingUtil { return genres; } - public static MediaItem mapMediaItem(Context context, Song song) { + public static MediaItem mapMediaItem(Context context, Song song, boolean isForStreaming) { Bundle bundle = new Bundle(); bundle.putString("id", song.getId()); bundle.putString("albumId", song.getAlbumId()); @@ -207,7 +207,7 @@ public class MappingUtil { .setMediaId(song.getId()) .setMediaMetadata( new MediaMetadata.Builder() - .setMediaUri(MusicUtil.getSongStreamUri(context, song)) + .setMediaUri(isForStreaming ? MusicUtil.getSongStreamUri(context, song) : MusicUtil.getSongDownloadUri(song)) .setTitle(MusicUtil.getReadableString(song.getTitle())) .setTrackNumber(song.getTrackNumber()) .setDiscNumber(song.getDiscNumber()) @@ -217,14 +217,15 @@ public class MappingUtil { .setExtras(bundle) .build() ) + .setUri(isForStreaming ? MusicUtil.getSongStreamUri(context, song) : MusicUtil.getSongDownloadUri(song)) .build(); } - public static ArrayList mapMediaItems(Context context, List songs) { + public static ArrayList mapMediaItems(Context context, List songs, boolean isForStreaming) { ArrayList mediaItems = new ArrayList(); for (Song song : songs) { - mediaItems.add(mapMediaItem(context, song)); + mediaItems.add(mapMediaItem(context, song, isForStreaming)); } return mediaItems; diff --git a/app/src/main/java/com/cappielloantonio/play/util/MusicUtil.java b/app/src/main/java/com/cappielloantonio/play/util/MusicUtil.java index 2ab46d7a..3bcf5cf1 100644 --- a/app/src/main/java/com/cappielloantonio/play/util/MusicUtil.java +++ b/app/src/main/java/com/cappielloantonio/play/util/MusicUtil.java @@ -7,8 +7,6 @@ import android.net.Uri; import android.text.Html; import android.util.Log; -import androidx.media3.common.MediaItem; - import com.cappielloantonio.play.App; import com.cappielloantonio.play.R; import com.cappielloantonio.play.glide.CustomGlideRequest; @@ -45,7 +43,7 @@ public class MusicUtil { } @SuppressLint("UnsafeOptInUsageError") - public static MediaItem getSongDownloadItem(Song song) { + public static Uri getSongDownloadUri(Song song) { Map params = App.getSubsonicClientInstance(App.getInstance(), false).getParams(); String uri = App.getSubsonicClientInstance(App.getInstance(), false).getUrl() + @@ -57,7 +55,9 @@ public class MusicUtil { "&c=" + params.get("c") + "&id=" + song.getId(); - return MediaItem.fromUri(uri); + Log.d(TAG, "getSongDownloadUri(): " + uri); + + return Uri.parse(uri); } public static String getReadableDurationString(long duration, boolean millis) { diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java index 6e75ea90..658330fd 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/PlayerBottomSheetViewModel.java @@ -16,6 +16,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.MappingUtil; import com.cappielloantonio.play.util.PreferenceUtil; import java.util.List; @@ -45,11 +47,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel { return queueRepository.getLiveQueue(); } - public Song getCurrentSong() { - // return MusicPlayerRemote.getCurrentSong(); - return null; - } - public void setFavorite(Context context, Song song) { if (song != null) { if (song.isFavorite()) { @@ -60,7 +57,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel { song.setFavorite(true); if (PreferenceUtil.getInstance(context).isStarredSyncEnabled()) { - // DownloadUtil.getDownloadTracker(context).download(Collections.singletonList(song), null, null); + DownloadUtil.getDownloadTracker(context).download(MappingUtil.mapMediaItem(context, song, false)); } } } @@ -71,7 +68,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel { } public void refreshSongInfo(LifecycleOwner owner, Song song) { - // songLiveData.postValue(song); songRepository.getSongLyrics(song).observe(owner, lyricsLiveData::postValue); } diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/SongBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/SongBottomSheetViewModel.java index 90e7355f..81739d25 100644 --- a/app/src/main/java/com/cappielloantonio/play/viewmodel/SongBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/SongBottomSheetViewModel.java @@ -6,7 +6,6 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; -import androidx.preference.Preference; import com.cappielloantonio.play.model.Album; import com.cappielloantonio.play.model.Artist; @@ -15,10 +14,9 @@ import com.cappielloantonio.play.repository.AlbumRepository; import com.cappielloantonio.play.repository.ArtistRepository; import com.cappielloantonio.play.repository.SongRepository; import com.cappielloantonio.play.util.DownloadUtil; +import com.cappielloantonio.play.util.MappingUtil; import com.cappielloantonio.play.util.PreferenceUtil; -import java.util.Collections; - public class SongBottomSheetViewModel extends AndroidViewModel { private final SongRepository songRepository; private final AlbumRepository albumRepository; @@ -50,8 +48,8 @@ public class SongBottomSheetViewModel extends AndroidViewModel { songRepository.star(song.getId()); song.setFavorite(true); - if(PreferenceUtil.getInstance(context).isStarredSyncEnabled()) { - // DownloadUtil.getDownloadTracker(context).download(Collections.singletonList(song), null, null); + if (PreferenceUtil.getInstance(context).isStarredSyncEnabled()) { + DownloadUtil.getDownloadTracker(context).download(MappingUtil.mapMediaItem(context, song, false)); } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f399f530..eb36a6d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,6 +49,7 @@ Access to the Subsonic server on connections other than Wi-Fi has been restricted. To prevent this alert dialod from reappearing, disable the connection check in the app settings. Once you download a song, you\'ll find it here No downloads yet! + Failed to start download Albums See all Artists