diff --git a/.gitignore b/.gitignore
index 74483036..0f48176e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
.cxx
/.idea/
.env
+.vscode/settings.json
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 1d411cd3..d5950937 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -192,6 +192,7 @@
+
diff --git a/README.md b/README.md
index 84b4b864..79b2081d 100644
--- a/README.md
+++ b/README.md
@@ -7,12 +7,12 @@
-
+
-
+
**Tempo** is an open-source and lightweight music client for Subsonic, designed and built natively for Android. It provides a seamless and intuitive music streaming experience, allowing you to access and play your Subsonic music library directly from your Android device.
@@ -22,6 +22,21 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
**Use the Github version of the app for full Android Auto and Chromecast support.**
+## Fork
+
+This fork is my attempt to keep development moving forward and merge in PR's that have been sitting for a while in the main repo. Thankful to @CappielloAntonio for the amazing app and hopefully we can continue to build on top of it. I will only be releasing on github and if I am not able to merge back to the main repo, I plan to rename the app to be able to publish it to fdroid and possibly google play? We will see.
+
+v3.10.0 applies the following PR's (fix/feat/chore):
+fix: [379](https://github.com/CappielloAntonio/tempo/pull/379) -Fix: redirection to artist fragment on artist label click
+fix: [385](https://github.com/CappielloAntonio/tempo/pull/385) -Player queue lag, limits
+fix: [389](https://github.com/CappielloAntonio/tempo/pull/389) -Fix crash when sorting albums with a null artist
+feat: [371](https://github.com/CappielloAntonio/tempo/pull/371) -Display toast message after adding a song to a playlist
+feat: [367](https://github.com/CappielloAntonio/tempo/pull/367) -Album add to playlist context menu item
+chore: [374](https://github.com/CappielloAntonio/tempo/pull/374) -Spanish translation
+feat: [397](https://github.com/CappielloAntonio/tempo/pull/397) -Store and retrieve replay and shuffle states in preferences
+feat:[400](https://github.com/CappielloAntonio/tempo/pull/400) - enhance Android media player notification window
+chore: [378](https://github.com/CappielloAntonio/tempo/pull/378) Polish translation
+
## Features
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
- **Sleek and Intuitive UI**: Enjoy a clean and user-friendly interface designed to enhance your music listening experience, tailored to your preferences and listening history.
@@ -42,7 +57,7 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
-
+
diff --git a/app/build.gradle b/app/build.gradle
index b6ee7b49..17540307 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,7 +11,7 @@ android {
targetSdk 35
versionCode 26
- versionName '3.9.0'
+ versionName '3.10.0-alpha.1'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -107,4 +107,9 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
+}
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 986481bd..cc9990e7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,6 @@
-
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
index bcf0c732..7884159f 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
@@ -1,10 +1,16 @@
package com.cappielloantonio.tempo.repository;
+import static android.provider.Settings.System.getString;
+
+import android.provider.Settings;
+import android.widget.Toast;
+
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
+import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.PlaylistDao;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
@@ -20,6 +26,7 @@ import retrofit2.Callback;
import retrofit2.Response;
public class PlaylistRepository {
+ @androidx.media3.common.util.UnstableApi
private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao();
public MutableLiveData> getPlaylists(boolean random, int size) {
MutableLiveData> listLivePlaylists = new MutableLiveData<>(new ArrayList<>());
@@ -80,12 +87,12 @@ public class PlaylistRepository {
.enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
-
+ Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_success), Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(@NonNull Call call, @NonNull Throwable t) {
-
+ Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_failure), Toast.LENGTH_SHORT).show();
}
});
}
@@ -157,17 +164,19 @@ public class PlaylistRepository {
}
});
}
-
+ @androidx.media3.common.util.UnstableApi
public LiveData> getPinnedPlaylists() {
return playlistDao.getAll();
}
+ @androidx.media3.common.util.UnstableApi
public void insert(Playlist playlist) {
InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist);
Thread thread = new Thread(insert);
thread.start();
}
+ @androidx.media3.common.util.UnstableApi
public void delete(Playlist playlist) {
DeleteThreadSafe delete = new DeleteThreadSafe(playlistDao, playlist);
Thread thread = new Thread(delete);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
index 1fc970ab..1f360c80 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
@@ -157,7 +157,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter thumbnail = CustomGlideRequest.Builder
+ .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
+ .build()
+ .sizeMultiplier(0.1f);
+
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
+ .thumbnail(thumbnail)
.into(holder.item.queueSongCoverImageView);
MediaManager.getCurrentIndex(mediaBrowserListenableFuture, new MediaIndexCallback() {
@Override
public void onRecovery(int index) {
- if (position < index) {
+ if (holder.getLayoutPosition() < index) {
holder.item.queueSongTitleTextView.setAlpha(0.2f);
holder.item.queueSongSubtitleTextView.setAlpha(0.2f);
holder.item.ratingIndicatorImageView.setAlpha(0.2f);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
index f0266c84..5c0b119c 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
@@ -57,14 +57,14 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
}
private void setSongInfo() {
- playlistChooserViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT));
+ playlistChooserViewModel.setSongsToAdd(requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT));
}
private void setButtonAction() {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Bundle bundle = new Bundle();
- bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
+ bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, playlistChooserViewModel.getSongsToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle);
@@ -98,7 +98,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
@Override
public void onPlaylistClick(Bundle bundle) {
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
- playlistChooserViewModel.addSongToPlaylist(playlist.getId());
+ playlistChooserViewModel.addSongsToPlaylist(playlist.getId());
dismiss();
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
index 4d265beb..dea70d7d 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
@@ -74,11 +74,11 @@ public class PlaylistEditorDialog extends DialogFragment {
}
private void setParameterInfo() {
- if (requireArguments().getParcelable(Constants.TRACK_OBJECT) != null) {
- playlistEditorViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT));
+ if (requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT) != null) {
+ playlistEditorViewModel.setSongsToAdd(requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT));
playlistEditorViewModel.setPlaylistToEdit(null);
} else if (requireArguments().getParcelable(Constants.PLAYLIST_OBJECT) != null) {
- playlistEditorViewModel.setSongToAdd(null);
+ playlistEditorViewModel.setSongsToAdd(null);
playlistEditorViewModel.setPlaylistToEdit(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
if (playlistEditorViewModel.getPlaylistToEdit() != null) {
@@ -92,7 +92,7 @@ public class PlaylistEditorDialog extends DialogFragment {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
- if (playlistEditorViewModel.getSongToAdd() != null) {
+ if (playlistEditorViewModel.getSongsToAdd() != null) {
playlistEditorViewModel.createPlaylist(playlistName);
} else if (playlistEditorViewModel.getPlaylistToEdit() != null) {
playlistEditorViewModel.updatePlaylist(playlistName);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
index 8162b16b..f830888e 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
@@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
+import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -38,6 +39,7 @@ import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -108,6 +110,17 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
});
return true;
}
+ if (item.getItemId() == R.id.action_add_to_playlist) {
+ albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
+
+ PlaylistChooserDialog dialog = new PlaylistChooserDialog();
+ dialog.setArguments(bundle);
+ dialog.show(requireActivity().getSupportFragmentManager(), null);
+ });
+ return true;
+ }
return false;
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
index dafd1507..b3755ee3 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
@@ -112,6 +112,9 @@ public class PlayerBottomSheetFragment extends Fragment {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
+ mediaBrowser.setShuffleModeEnabled(Preferences.isShuffleModeEnabled());
+ mediaBrowser.setRepeatMode(Preferences.getRepeatMode());
+
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
e.printStackTrace();
@@ -150,6 +153,16 @@ public class PlayerBottomSheetFragment extends Fragment {
public void onEvents(Player player, Player.Events events) {
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
}
+
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ Preferences.setShuffleModeEnabled(shuffleModeEnabled);
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ Preferences.setRepeatMode(repeatMode);
+ }
});
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
index 1d474db9..d1daae9b 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
@@ -140,6 +140,8 @@ public class PlayerControllerFragment extends Fragment {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser);
+ mediaBrowser.setShuffleModeEnabled(Preferences.isShuffleModeEnabled());
+ mediaBrowser.setRepeatMode(Preferences.getRepeatMode());
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
@@ -160,6 +162,16 @@ public class PlayerControllerFragment extends Fragment {
setMetadata(mediaMetadata);
setMediaInfo(mediaMetadata);
}
+
+ @Override
+ public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ Preferences.setShuffleModeEnabled(shuffleModeEnabled);
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ Preferences.setRepeatMode(repeatMode);
+ }
});
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java
index 39c72468..55b46ff2 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlaylistPageFragment.java
@@ -193,13 +193,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
bind.playlistPagePlayButton.setOnClickListener(v -> {
- MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
+ MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
bind.playlistPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs);
- MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
+ MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
}
@@ -270,4 +270,4 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java
index a498f473..cfcefe9d 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java
@@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
+import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -167,6 +168,20 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
});
});
+ TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
+ addToPlaylist.setOnClickListener(v -> {
+ albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
+
+ PlaylistChooserDialog dialog = new PlaylistChooserDialog();
+ dialog.setArguments(bundle);
+ dialog.show(requireActivity().getSupportFragmentManager(), null);
+
+ dismissBottomSheet();
+ });
+ });
+
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List mediaItems = MappingUtil.mapDownloads(songs);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
index b22830eb..0d15ad2f 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java
@@ -39,6 +39,9 @@ import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.Collections;
+
@UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel;
@@ -177,7 +180,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
addToPlaylist.setOnClickListener(v -> {
Bundle bundle = new Bundle();
- bundle.putParcelable(Constants.TRACK_OBJECT, song);
+ bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(Collections.singletonList(song)));
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt
index 5cbe9035..e7a4c459 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt
+++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt
@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.util
import android.util.Log
+import androidx.media3.common.Player
import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.model.HomeSector
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension
@@ -24,6 +25,8 @@ object Preferences {
private const val NEXT_SERVER_SWITCH = "next_server_switch"
private const val PLAYBACK_SPEED = "playback_speed"
private const val SKIP_SILENCE = "skip_silence"
+ private const val SHUFFLE_MODE = "shuffle_mode"
+ private const val REPEAT_MODE = "repeat_mode"
private const val IMAGE_CACHE_SIZE = "image_cache_size"
private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
private const val IMAGE_SIZE = "image_size"
@@ -226,6 +229,26 @@ object Preferences {
App.getInstance().preferences.edit().putBoolean(SKIP_SILENCE, isSkipSilenceMode).apply()
}
+ @JvmStatic
+ fun isShuffleModeEnabled(): Boolean {
+ return App.getInstance().preferences.getBoolean(SHUFFLE_MODE, false)
+ }
+
+ @JvmStatic
+ fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {
+ App.getInstance().preferences.edit().putBoolean(SHUFFLE_MODE, shuffleModeEnabled).apply()
+ }
+
+ @JvmStatic
+ fun getRepeatMode(): Int {
+ return App.getInstance().preferences.getInt(REPEAT_MODE, Player.REPEAT_MODE_OFF)
+ }
+
+ @JvmStatic
+ fun setRepeatMode(repeatMode: Int) {
+ App.getInstance().preferences.edit().putInt(REPEAT_MODE, repeatMode).apply()
+ }
+
@JvmStatic
fun getImageCacheSize(): Int {
return App.getInstance().preferences.getString(IMAGE_CACHE_SIZE, "500")!!.toInt()
diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumPageViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumPageViewModel.java
index 6c244505..0979f408 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumPageViewModel.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumPageViewModel.java
@@ -21,6 +21,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository;
private String albumId;
+ private String artistId;
private final MutableLiveData album = new MutableLiveData<>(null);
public AlbumPageViewModel(@NonNull Application application) {
@@ -41,6 +42,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
public void setAlbum(LifecycleOwner owner, AlbumID3 album) {
this.albumId = album.getId();
this.album.postValue(album);
+ this.artistId = album.getArtistId();
albumRepository.getAlbum(album.getId()).observe(owner, albums -> {
if (albums != null) this.album.setValue(albums);
@@ -48,7 +50,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
}
public LiveData getArtist() {
- return artistRepository.getArtistInfo(albumId);
+ return artistRepository.getArtistInfo(artistId);
}
public LiveData getAlbumInfo() {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java
index a780d1cf..b646bcf1 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/HomeViewModel.java
@@ -94,7 +94,7 @@ public class HomeViewModel extends AndroidViewModel {
}
public LiveData> getRandomShuffleSample() {
- return songRepository.getRandomSample(100, null, null);
+ return songRepository.getRandomSample(1000, null, null);
}
public LiveData> getChronologySample(LifecycleOwner owner) {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java
index cb8833fe..fdee85c6 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistChooserViewModel.java
@@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.repository.PlaylistRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
+import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -20,7 +21,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
private final PlaylistRepository playlistRepository;
private final MutableLiveData> playlists = new MutableLiveData<>(null);
- private Child toAdd;
+ private ArrayList toAdd;
public PlaylistChooserViewModel(@NonNull Application application) {
super(application);
@@ -33,15 +34,15 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
return playlists;
}
- public void addSongToPlaylist(String playlistId) {
- playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId())));
+ public void addSongsToPlaylist(String playlistId) {
+ playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(Lists.transform(toAdd, Child::getId)));
}
- public void setSongToAdd(Child song) {
- toAdd = song;
+ public void setSongsToAdd(ArrayList songs) {
+ toAdd = songs;
}
- public Child getSongToAdd() {
+ public ArrayList getSongsToAdd() {
return toAdd;
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistEditorViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistEditorViewModel.java
index 8f9181a0..aceedb95 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistEditorViewModel.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlaylistEditorViewModel.java
@@ -12,6 +12,7 @@ import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.Share;
+import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -24,7 +25,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
private final PlaylistRepository playlistRepository;
private final SharingRepository sharingRepository;
- private Child toAdd;
+ private ArrayList toAdd;
private Playlist toEdit;
private MutableLiveData> songLiveList = new MutableLiveData<>();
@@ -37,7 +38,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
}
public void createPlaylist(String name) {
- playlistRepository.createPlaylist(null, name, new ArrayList(Collections.singletonList(toAdd.getId())));
+ playlistRepository.createPlaylist(null, name, new ArrayList(Lists.transform(toAdd, Child::getId)));
}
public void updatePlaylist(String name) {
@@ -48,12 +49,12 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
if (toEdit != null) playlistRepository.deletePlaylist(toEdit.getId());
}
- public Child getSongToAdd() {
- return toAdd;
+ public void setSongsToAdd(ArrayList songs) {
+ toAdd = songs;
}
- public void setSongToAdd(Child song) {
- this.toAdd = song;
+ public ArrayList getSongsToAdd() {
+ return toAdd;
}
public Playlist getPlaylistToEdit() {
diff --git a/app/src/main/res/layout/bottom_sheet_album_dialog.xml b/app/src/main/res/layout/bottom_sheet_album_dialog.xml
index a6ed37a5..b37a5f90 100644
--- a/app/src/main/res/layout/bottom_sheet_album_dialog.xml
+++ b/app/src/main/res/layout/bottom_sheet_album_dialog.xml
@@ -146,6 +146,19 @@
android:paddingBottom="12dp"
android:text="@string/album_bottom_sheet_download_all" />
+
+
+
+
-
+ android:layout_height="match_parent"
+ android:layout_marginTop="40dp"
+ android:paddingTop="8dp"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 5c627b9a..1df280e0 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -3,6 +3,7 @@
Bitte deaktiviere die Batterieoptimierung, damit die Medienwiedergabe bei ausgeschaltetem Bildschirm richtig funktioniert.
Batterie Optimierung
Offlinebetrieb
+ Zu Playliste hinzufügen
Zur Warteschlange hinzufügen
Alle herunterladen
Gehe zu Künstler
@@ -154,6 +155,7 @@
Subsonic Server
Cast
Hinzufügen
+ Zu Playliste hinzufügen
Alle Herunterladen
Downloads
Alle
@@ -186,6 +188,8 @@
Abbrechen
Erstellen
Zu einer Playliste hinzufügen
+ Lied zu Playlist hinzugefügt
+ Titel kann nicht zur Playlist hinzugefügt werden
%1$d Tracks • %2$s
Länge • %1$s
Zum Löschen lange drücken
@@ -277,7 +281,7 @@
Download storage
Audio Einstellungen anpassen
Equalizer
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Verfolge die Entwicklung
Github
Bilder Auflösung anpassen
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index 693e3661..d2c21d36 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -165,7 +165,7 @@
Resolución de la imagen
Idioma
Cerrar sesión
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Siga el desarrollo
Github
Género
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 03a2f0db..aebaee19 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -3,6 +3,7 @@
Veuillez désactiver les optimisations de la batterie pour permettre la lecture des médias lorsque l\'écran est éteint.
Optimisations de la batterie
Mode hors-ligne
+ Ajouter à une playlist
Ajouter à la file d\'attente
Télécharger tout
Aller à l\'artiste
@@ -133,6 +134,7 @@
Serveurs Subsonic
Cast
Ajouter
+ Ajouter à une playlist
Télécharger tout
Téléchargé
Tout
@@ -244,7 +246,7 @@
Stockage des téléchargements
Ajuster les paramètres audios
Égaliseur
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Suivre le développement
Github
Définir la résolution des images
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 2cc5bcb6..a5f3be74 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -3,7 +3,8 @@
Per favore, disabilita le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.
Ottimizzazioni della Batteria
Modalità offline
- Aggiungi alla coda
+ Aggiungi alla playlist
+ Aggiungi alla coda
Scarica tutto
Vai all\'artista
Mix istantaneo
@@ -156,7 +157,8 @@
Server Subsonic
Trasmetti
Aggiungi
- Scarica tutto
+ Aggiungi alla playlist
+ Scarica tutto
Scarica
Tutti
Scaricati
@@ -188,6 +190,8 @@
Annulla
Crea
Aggiungi a una playlist
+ Aggiunta di un brano alla playlist
+ Impossibile aggiungere un brano alla playlist
%1$d brani • %2$s
Durata • %1$s
Premi a lungo per eliminare
@@ -279,7 +283,7 @@
Archivio download
Regola le impostazioni audio
Equalizzatore
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Segui lo sviluppo
Github
Imposta risoluzione delle immagini
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 679f333a..1953612f 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -3,6 +3,7 @@
화면이 꺼진 상태에서 음악 재생을 하기 위해서는 배터리 최적화를 비활성화 해주세요.
배터리 최적화
오프라인 모드
+ 플레이리스트에 추가
재생목록에 추가
모두 다운로드
아티스트로 이동
@@ -135,6 +136,7 @@
Subsonic 서버
Cast
추가
+ 플레이리스트에 추가
모두 다운로드
다운로드
모두
@@ -161,6 +163,8 @@
취소
생성
플레이리스트 추가
+ 재생 목록에 노래 추가
+ 재생 목록에 노래를 추가하지 못했습니다.
%1$d 트랙 • %2$s
재생시간 • %1$s
플레이리스트 이름
@@ -246,7 +250,7 @@
스토리지 다운로드
오디오 설정 적용
이퀄라이저
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Follow the development
Github
이미지 해상도 설정
diff --git a/app/src/main/res/values-pl/arrays.xml b/app/src/main/res/values-pl/arrays.xml
new file mode 100644
index 00000000..f3105914
--- /dev/null
+++ b/app/src/main/res/values-pl/arrays.xml
@@ -0,0 +1,257 @@
+
+
+ - Jasny
+ - Ciemny
+ - Domyślny systemu
+
+
+ - jasny
+ - ciemny
+ - domyślny
+
+
+
+ - Duży
+ - Średni
+ - Mały
+
+
+ - 500
+ - 250
+ - 125
+
+
+
+ - Duża
+ - Średnia
+ - Mała
+
+
+ - -1
+ - 500
+ - 300
+
+
+
+ - Wyłączone
+ - 128 MiB
+ - 256 MiB
+ - 512 MiB
+ - 1024 MiB
+
+
+ - 0
+ - 128
+ - 256
+ - 512
+ - 1024
+
+
+
+ - Oryginalny
+ - 32 kbps
+ - 48 kbps
+ - 64 kbps
+ - 80 kbps
+ - 96 kbps
+ - 112 kbps
+ - 128 kbps
+ - 160 kbps
+ - 192 kbps
+ - 256 kbps
+ - 320 kbps
+
+
+ - 0
+ - 32
+ - 48
+ - 64
+ - 80
+ - 96
+ - 112
+ - 128
+ - 160
+ - 192
+ - 256
+ - 320
+
+
+
+ - Oryginalny
+ - 32 kbps
+ - 48 kbps
+ - 64 kbps
+ - 80 kbps
+ - 96 kbps
+ - 112 kbps
+ - 128 kbps
+ - 160 kbps
+ - 192 kbps
+ - 256 kbps
+ - 320 kbps
+
+
+ - 0
+ - 32
+ - 48
+ - 64
+ - 80
+ - 96
+ - 112
+ - 128
+ - 160
+ - 192
+ - 256
+ - 320
+
+
+
+ - Oryginalny
+ - 32 kbps
+ - 48 kbps
+ - 64 kbps
+ - 80 kbps
+ - 96 kbps
+ - 112 kbps
+ - 128 kbps
+ - 160 kbps
+ - 192 kbps
+ - 256 kbps
+ - 320 kbps
+
+
+ - 0
+ - 32
+ - 48
+ - 64
+ - 80
+ - 96
+ - 112
+ - 128
+ - 160
+ - 192
+ - 256
+ - 320
+
+
+
+ - Odtwarzanie bezpośrednie
+ - Opus
+ - AAC
+ - Mp3
+ - Flac
+
+
+ - raw
+ - opus
+ - aac
+ - mp3
+ - flac
+
+
+
+ - Odtwarzanie bezpośrednie
+ - Opus
+ - AAC
+ - Mp3
+ - Flac
+
+
+ - raw
+ - opus
+ - aac
+ - mp3
+ - flac
+
+
+
+ - Pobieranie bezpośrednie
+ - Opus
+ - AAC
+ - Mp3
+ - Flac
+
+
+ - raw
+ - opus
+ - aac
+ - mp3
+ - flac
+
+
+
+ - Dziesięć sekund
+ - Pięć sekund
+ - Dwie sekundy
+
+
+ - 10
+ - 5
+ - 2
+
+
+
+ - Duży
+ - Średni
+ - Mały
+
+
+ - 18
+ - 12
+ - 6
+
+
+
+ - Wyłączony
+ - Utwór
+ - Album
+ - Auto
+
+
+ - wyłączony
+ - utwór
+ - album
+ - auto
+
+
+
+ - Nie transkoduj
+ - Ustawienia serwera
+ - Format transkodowania Wi-FI
+ - Format transkodowania sieci komórkowej
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+
+
+
+ - Minimalna
+ - Średnia
+ - Agresywna
+ - Ekstremalna
+
+
+ - .1
+ - 1
+ - 4
+ - 8
+
+
+
+ - Minimum 0 gwiazdek
+ - Minimum 1 gwiazdka
+ - Minimum 2 gwiazdki
+ - Minimum 3 gwiazdki
+ - Minimum 4 gwiazdki
+
+
+ - 0
+ - 1
+ - 2
+ - 3
+ - 4
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 00000000..37f1685a
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,418 @@
+
+ Jeżeli masz problemy odwiedź stronę https://dontkillmyapp.com. Podaje ona dokładne instrukcje na temat tego jak wyłączyć funkcje oszczędzania energii które mogą wpływać na wydajność aplikacji.
+ Wyłącz optymalizacje baterii aby odtwarzać media przy wyłączonym ekranie.
+ Optymalizcje Baterii
+ Tryb offline
+ Dodaj do kolejki
+ Pobierz wszystkie
+ Przejdź do wykonawcy
+ Natychmiastowy mix
+ Odtwórz jako następne
+ Usuń wszystkie
+ Udostępnij
+ Odtwórz losowo
+ Albumy
+ Przeglądaj Albumy
+ Błąd podczas pobierania wykonawcy
+ Pobrane albumy
+ Najczęściej odtwarzane albumy
+ Nowe wydania
+ Albumy dodane niedawno
+ Albumy odtwarzane niedawno
+ Albumy oznaczone gwiazdką
+ Albumy
+ Więcej podobnych
+ Odtwarzaj
+ Wydane %1$s
+ Wydane %1$s, oryginalnie %2$s
+ Odtwarzaj losowo
+ %1$d utworów • %2$d minut
+ Tempo
+ Szukanie…
+ Natychmiastowy mix mix
+ Odtwórz losowo
+ Wykonawcy
+ Przeglądaj wykonawców
+ Błąd podczas pobierania radia wykonawcy
+ Błąd podczas pobierania utworów wykonawcy
+ Pobrani wykonawcy
+ Wykonawcy oznaczeni gwiazdką
+ Wykonawcy
+ Radio
+ Odtwarzanie losowe
+ Zmień układ
+ Więcej podobnych
+ Albumy
+ Więcej
+ Biografia
+ Najczęsciej słuchane utwory
+ Zobacz wszystkie
+ Ignoruj
+ Nie pytaj ponownie
+ Wyłącz
+ Anuluj
+ Włącz oszczędzanie danych
+ OK
+ Dostęp do serwera Subsonic na połaczeniach innych niż Wi-Fi został ograniczony. Aby zapobiec ponownemu pojawieniu się tej informacji, wyłącz sprawdzanie połączenia w ustawieniach aplikacji.
+ Nie połączono z Wi-Fi
+ Odtwarzanie losowe
+ Anuluj
+ Kontynuuj
+ Miej na uwadze to że kontynuowanie tej operacji spowoduje usunięcie wszystkich pobranych plików z wszystkich serwerów.
+ Usuwanie zapisanych plików
+ Brak opisu
+ Płyta %1$s - %2$s
+ Płyta %1$s
+ Anuluj
+ Pobierz
+ Wszystkie utwory w tym folderze zostaną pobrane. Utwory dostępne w subfolderach nie zostaną pobrane.
+ Pobierz utwory
+ Gdy pobierzesz piosenkę, znajdziesz ją tutaj
+ Narazie brak pobranych!
+ %1$s • %2$s elementów
+ %1$s element
+ Odtwórz losowo wszystkie
+ Aby zmiany przyniosły efekt, zrestartuj aplikację.
+ Zmiana lokalizacji pobieranych plików z jednej na drugą spowoduje natychmiastowe usunięcie wcześniej pobranych plików w drugiej lokalizacji
+ Wybieranie pamięci
+ Zewnętrzna
+ Wewnętrzna
+ Pobrane
+ Dodaj do kolejki
+ Odtwarzaj jako następne
+ Usuń
+ Usuń wszystkie
+ Odtwarzaj losowo
+
+ Wymagane
+ wymagany jest prefiks http lub https
+ Pobieranie
+ Wybierz dwa lub więcej filtrów
+ Filtry
+ Filtruj Gatunki
+ (%1$d)
+ (+%1$d)
+ Katalog Gatunków
+ Przeglądaj Gatunki
+ Przypomnij mi później
+ Wesprzyj mnie
+ Pobierz teraz
+ Nowa wersja aplikacji jest dostępna na GitHubie.
+ Dostępna aktualizacja
+ Anuluj
+ Reset
+ Zapisz
+ Zmień układ strony głównej
+ Weź pod uwagę to że, żeby zmiany nastąpiły, musisz zrestartować aplikację.
+ Top piosenki od twoich ulubionych wykonawców
+ Stwórz miks z piosenki którą lubisz
+ Dodaj nowe radio
+ Dodaj nowy kanał podcastów
+ Anuluj
+ Pobierz
+ Pobieranie tych utworów może zużyć dużo danych
+ Wygląda na to że, są utwory oznaczone gwiazdką
+ Najlepsze
+ Odkrywanie
+ Odtwórz wszystkie losowo
+ Podróż w czasie
+ Internetowe stacje radiowe
+ Ostatnio odtwarzane
+ Zobacz wszystkie
+ Ostatni tydzień
+ Ostatni miesiąc
+ Ostatni rok
+ Stworzone dla ciebie
+ Najczęściej odtwarzane
+ Zobacz wszystkie
+ Nowe wydania
+ Najnowsze podcasty
+ Playlisty
+ Kanały
+ Zobacz wszystkie
+ Stacje radiowe
+ Ostatnio dodane
+ Zobacz wszystkie
+ Udostępnienia
+ ★ Albumy oznaczone gwiazdką
+ Zobacz wszystkie
+ ★ Wykonawcy oznaczeni gwiazdką
+ Zobacz wszystkich
+ ★ Utwory oznaczone gwiazdką
+ Zobacz wszystkie
+ Twoje top piosenki
+ Zmiana układu
+ •
+ --
+ Albumy
+ Zobacz wszystkie
+ Wykonawcy
+ Zobacz wszystkich
+ Gatunki
+ Zobacz wszystkie
+ Foldery z muzyką
+ Playlisty
+ Zobacz wszystkie
+ Brak dodanych serwerów
+ Serwery Subsonic
+ Serwery Subsonic
+ Przesyłanie
+ Dodaj
+ Pobierz wszystko
+ Pobrane
+ Wszystko
+ Pobrane
+ Albumy
+ Wykonawcy
+ Gatkunki
+ Utwory
+ Rok
+ Strona główna
+ Ostatni tydzień
+ Ostatni miesiąc
+ Ostatni rok
+ Biblioteka
+ Szukaj
+ Ustawienia
+ Wykonawca
+ Nazwa
+ Losowo
+ Ostatnio dodane
+ Ostatnio odtwarzane
+ Najczęściej odtwarzane
+ Ostatnio oznaczone największą liczbą gwiazdek
+ Ostatnio oznaczone najniższą liczbą gwiazdek
+ Dodaj do ekranu głównego
+ Usuń z ekranu głównego
+ Rok
+ %1$.2fx
+ Wyczyść kolejkę odtwarzania
+ Priorytet Serwerów
+ Katalog Playlist
+ Przeglądaj Playlisty
+ Nie utworzono playlist
+ Anuluj
+ Utwórz
+ Dodaj do playlisty
+ %1$d utworów • %2$s
+ Długość • %1$s
+ Przytrzymaj aby usunąć
+ Nazwa Playlisty
+ Anuluj
+ Usuń
+ Zapisz
+ Edytuj playlistę
+ Odtwórz
+ Odtwarzaj losowo
+ Playlista • %1$d piosenek
+ Dodaj do kolejki
+ Usuń
+ Pobierz
+ Przejdź do kanału
+ Odtwórz jako następny
+ Usuń
+ Kanały
+ Przeglądaj Kanały
+ Url RSS
+ Kanał Podcastu
+ Opis
+ Odcinki
+ Brak dostępnych odcinków
+ Twoje zapytanie zostało wysłane do serwera
+ Naciśnij aby ukryć tę sekcję\nEfekty będą widoczne po restarcie
+ Gdy dodasz kanał, znajdziesz go tutaj
+ Nie znaleziono podcastów!
+ %1$s • %2$s
+ URL Strony Radia
+ Nazwa Radia
+ URL Z Strumieniem Radia
+ Anuluj
+ Usuń
+ Zapisz
+ Internetowa Stacja Radiowa
+ Naciśnij aby ukryć tę sekcję\nEfekty będą widoczne po restarcie
+ Gdy dodasz stację radiową, znajdziesz ją tutaj
+ Nie znaleziono stacji!
+ Anuluj
+ Zapisz
+ Oceń
+ Wyszukaj tytuł, wykonawców lub albumy
+ Wpisz co najmniej trzy znaki
+ Albumy
+ Wykonawcy
+ Piosenki
+ Niskie bezpieczeństwo
+ Przytrzymaj aby usunąć
+ Lokalny URL
+ Nazwa Serwera
+ Hasło
+ URL Serwera
+ Nazwa użytkownika
+ Anuluj
+ Usuń
+ Zapisz
+ Dodaj serwer
+ Anuluj
+ Przejdź do logowania
+ Kontynuuj mimo wszystko
+ Wybrany serwer jest niedostępny. Jeżeli wybierzesz żeby kontynuować ta informacja nie będzie się wyświetlać przez następną godzinę.
+ Serwer jest niedostępny
+ Tempo jest otwarto-źródłowym i lekkim klientem muzycznym dla Subsonic, stworzonym i zbudowanym natywnie dla Androida.
+ O aplikacji
+ Always on display
+ Format transkodowania
+ Jeżeli włączone, Tempo nie będzię wymuszał pobierania utworu z ustawieniami transkodowania wybranymi poniżej.
+ Priorytetyzuj ustawienia serwera używanego do strumieniowania w pobieraniach
+ Jeżeli włączone, Tempo będzie pobierał transkodowane utwory.
+ Pobieraj transkodowane utwory
+ Jeżeli włączone, serwer bedzię odpytywany o przybliżoną długość utworu.
+ Szacuj długość treści
+ Format transkodowania dla pobierania
+ Format transkodowania w sieci komórkowej
+ Format transkodowania w sieci Wi-Fi
+ Jeżeli włączone, Tempo nie będzie wymuszał strumieniowania utworu z ustawieniami transkodowania wybranymi poniżej.
+ Priorytetyzuj ustawienia transkodowania serwera
+ Priorytet przy transkodowaniu utworu danego serwerowi
+ Strategia buforowania
+ Aby zmiany przyniosły efekt, musisz ręcznie zrestartować aplikację.
+ Pozwala muzyce odtwarzać się dalej po końcu playlisty, odtwarza podobne piosenki
+ Odtwarzanie bez przerwy
+ Rozmiar cache dla okładek
+ Aby zmniejszyć zużycie danych, unikaj pobierania okładek.
+ Ogranicz zużycie danych komórkowych
+ Zatwierdzenie nieodwracalnie usunie wszystkie zapisane elementy
+ Usuń zapisane elementy
+ Pamięć do pobierania
+ Zmień ustawienia audio
+ Equalizer
+ https://github.com/eddyizm/tempo
+ Śledź tworzenie aplikacji
+ GitHub
+ Rozdzielczość obrazów
+ Język
+ Wyloguj
+ Bitrate dla pobierania
+ Bitrate dla danych komórkowych
+ Bitrate dla Wi-Fi
+ Rozmiar plików cache dla mediów
+ Pokaż foldery z muzyką
+ Jeżeli włączone, widoczna będzie sekcja z folderami z muzyką. Weź pod uwagę że żeby funkcja nawigacji po folderach działała poprawnie, serwer musi wspierać tę funkcję.
+ Pokazuj podcasty
+ Jeżeli włączone, widoczna będzie sekcja z podcastami. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.
+ Pokaż jakość audio
+ Bitrate i format audio będzie pokazywany dla każdego utworu.
+ Pokaż oceny elementów
+ Jeżeli włączone, ocena elementów oraz czy jest oznaczony jako ulubiony będą pokazywane.
+ Timer synchronizacji
+ Jeżeli włączone, użytkownik będzie miał możliwość zapisania kolejki i będzie miał możliwość załadowania jej stanu przy otwarciu aplikacji.
+ Synchronizuj kolejkę odtwarzania dla tego użytkownika
+ Pokaż radio
+ Jeżeli włączone, widoczna będzie sekcja radia. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.
+ Tryb wzmocnienia głośności przy ponownym odtwarzaniu
+ Zaokrąglone rogi
+ Rozmiar rogów
+ Ustawia wielkość krzywizny kąta.
+ Jeżeli włączone, ustawia kąt krzywizny dla wszystkich renderowanych okładek. Zmiany przyniosą efekt po restarcie.
+ Skanuj bibliotekę
+ Włącz scrobbling muzyki
+ Włącz udostępnianie muzyki
+ Rozmiar cache dla strumieniowania
+ Pamięć cache dla strumieniowania
+ Ważne jest to że scrobbling polega też na byciu włączonym na serwerze aby otrzymywać te dane.
+ Podczas słuchania radia wykonawcy, natychmiastowego miksu albo podczas odtwarzania losowego, utwory poniżej określonej oceny użytkownika będą ignorowane.
+ Wzmocnienie głośności jest funkcją która pozwala tobie na ustawienia poziomu głośności dla utworów aby słuchanie brzmiało cały czas tak samo. To ustawienia działa tylko wtedy kiedy utwór zawiera potrzebne metadane.
+ Scrobbling jest funkcją która pozwala twojemu urządzeniu na wysyłanie informacji na temat piosenek których słuchasz do serwera muzyki. Te informacje pomagają tworzyć spersonalizowane rekomendacje na podstawie twojego gustu muzycznego.
+ Pozwala udostępnić użytkownikowi muzykę przez link. Ta funkcjonalność musi być wspierana i włączona na serwerze i jest ograniczona do pojedyńczych utworów, albumów i playlist.
+ Przywraca stan kolejki odtwarzania dla tego użytkownika. Zawiera utwory w kolejce, aktualnie odtwarzany utwór i pozycję w nim. Serwer musi wspierać tę funkcję.
+ %1$s \nAktualnie w użyciu: %2$s MiB
+ Priorytet dawany trybowi transkodowania. Jeżeli ustawiony na \"Odtwarzanie bezpośrednie\" bitrate pliku nie zostanie zmieniony.
+ Pobieraj transkdowane media. Jeżeli włączone, endpoint pobierania nie będzie używnany, poza następującymi ustawieniami. \n\n Jeżeli \"Format transkodowania dla pobierania\" jest ustawiony na \"Pobieranie bezpośrednie\" bitrate pliku nie zostanie zmieniony.
+ Kiedy plik jest transkodowany w locie, klient nie pokazuje zwykle długości utworu.Jest możliwe odpytanie serwera który wspiera tą funkcjonalność aby oszacował długość odtwarzanego utworu, ale czasy odpowiedzi mogą być dłuższe.
+ Jeżeli włączone, utwory oznaczone gwiazdką będą pobrane do użycia offline.
+ Zsynchronizuj utwory oznaczone gwiazdką do użycia offline
+ Motyw
+ Dane
+ Ogólne
+ Oceny
+ Wzmocnienie głośności przy ponownym odtwarzaniu
+ Scrobble
+ Ignoruj utwory na podstawie oceny
+ Piosenki z oceną:
+ Udostępnianie
+ Synchronizacja
+ Transkodowanie
+ Transkodowanie Pobrań
+ Interfejs
+ Transkodowane pobieranie
+ 3.1.0
+ Wersja
+ Pytaj o potwierdzenie od użytkownika przed strumieniowaniem przez sieć komórkową.
+ Alert o strumieniowaniu tylko przez Wi-Fi
+ Kopiuj link
+ Usuń udostępnianie
+ Zaktualizuj udostępnianie
+ Data wygaśnięcia: %1$s
+ Udostępnianie nie jest wspierane lub włączone
+ Opis
+ Data wygaśnięcia
+ Anuluj
+ Zapisz
+ Udostępnij
+ Dodaj do playlisty
+ Dodaj do kolejki
+ Pobierz
+ Błąd podczas pobierania albumu
+ Błąd podczas pobierania wykonawcy
+ Przejdź do albumu
+ Przejdź do wykonawcy
+ Natychmiastowy miks
+ Odtwarzaj jako następne
+ Oceń
+ Usuń
+ Udostępnij
+ Pobrane
+ Najczęściej odtwarzane utwory
+ Utwory dodane ostatnio
+ Utwory odtwarzane ostatnio
+ Utwory oznaczone gwiazdką
+ %1$s\ top utwory
+ Rok %1$d
+ %1$s • %2$s %3$s
+ Anuluj
+ Kontynuuj
+ Kontynuuj i pobierz
+ Pobieranie utworów oznaczonych gwiazdką może wymagać dużej ilośći danych.
+ Synchronizuj utwory oznaczone gwiazdką
+ Aby zmiany przyniosły efekt, zrestartuj aplikację.
+ Zmiana lokalizacji plików cache z jednej na drugą spowoduje natychmiastowe usunięcie wcześniej pobranych plików cache w drugiej lokalizacji.
+ Wybieranie pamięci
+ Zewnętrzna
+ Wewnętrzna
+ https://buymeacoffee.com/a.cappiello
+ Album
+ Wykonawca
+ Bitrate
+ Typ Treści
+ OK
+ Informacje o utworze
+ Numer płyty
+ Długość
+ Gatunek
+ Ścieżka
+ Rozmiar
+ Sufiks
+ Plik został pobrany przy użyciu API Subsonic. Kodek i bitrate pliku pozostaje nie zmieniony względem pliku źródłowego.
+ Aplikacja poprosi serwer żeby transkodować plik i zmienić jego bitrate. Użytkownik poprosił o kodek %1$s, z bitratem %2$s. Wszystkie potencjalne zmiany w kodeku i bitratecie pliku w wybranym formacie będą wykonywane przez serwer, który może ale nie musi obsługiwać tych operacji.
+ Aplikacja będzie tylko odczytywać oryginalne pliki jakie daje serwer. Aplikacja będzie wyłącznie prosić serwer o pliki bez transkodowania z bitratem oryginalnego źródła.
+ \Jakość odtwarzanego pliku zależy od decyzji serwera. Aplikacja nie będzie monitorować wyboru kodeku i bitrateu dla jakiegokolwiek potencjalnego transkodowania.
+ Aplikacja poprosi serwer o zmodyfikowanie bitrateu pliku. Użytkownik poprosił o bitrate %1$s, ale kodek pliku źródłowego pozostanie ten sam. Wszystkie potencjalne zmiany w bitracie pliku w wybranym formacie będą wykonywane przez serwer, który może ale nie musi obsługiwać tych operacji.
+ Aplikacja poprosi serwer żeby transkodować plik. Kodek wybrany przez użytkownika to %1$s, ale bitrate pliku źródłowego pozostanie ten sam. Wszystkie potencjalne zmiany w kodeku pliku w wybranym formacie będą wykonywane przez serwer, który może ale nie musi obsługiwać tych operacji
+ Tytuł
+ Numer utworu
+ Typ transkodowanej treści
+ Sufiks transkodowania
+ Rok
+ unDraw
+ Specjalne podziękowania dla unDraw bez którego ilustracji nie mogliśmy uczynić tej aplikacji jeszcze piękniejszą.
+ https://undraw.co/
+
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 12f04a31..7e4f2de7 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -3,6 +3,7 @@
Por favor, desative as otimizações de bateria para a reprodução de mídia enquanto a tela estiver desligada.
Otimizações de bateria
Modo offline
+ Adicionar a uma playlist
Adicionar à fila
Baixar todos
Ir para o(a) artista
@@ -134,6 +135,7 @@
Servidores Subsonic
Transmitir
Adicionar
+ Adicionar a uma playlist
Baixar todos
Download
Todos
@@ -160,6 +162,8 @@
Cancelar
Criar
Adicionar a uma playlist
+ Adicionada playlist de reprodução
+ Falha ao adicionar uma playlist de reprodução
%1$d faixas • %2$s
Duração • %1$s
Nome da playlist
@@ -246,7 +250,7 @@
Armazenamento dos downloads
Ajustar configurações de áudio
Equalizador
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Acompanhe o desenvolvimento
Github
Definir resolução da imagem
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 0f893b90..43448d65 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -3,6 +3,7 @@
Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране.
Оптимизация батареи
Офлайн-режим
+ Добавить в плейлист
Добавить в очередь
Скачать все
Перейти к исполнителю
@@ -153,6 +154,7 @@
Subsonic серверы
Cast
Добавить
+ Добавить в плейлист
Скачать все
Скачать
Все
@@ -186,6 +188,8 @@
Отмена
Создать
Добавить в плейлист
+ Добавьте песню в плейлист
+ Не удалось добавить песню в список воспроизведения
%1$d треков • %2$s
Продолжительность • %1$s
Долгое нажатие для удаления
@@ -275,7 +279,7 @@
Загрузить хранилище
Отрегулируйте настройки звука
Эквалайзер
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Следите за развитием
Github
Установить разрешение изображения
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 915b33d1..b0933d57 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -3,6 +3,7 @@
请禁用针对媒体锁屏播放的电池优化。
电池优化
离线模式
+ 添加到播放列表
添加到队列
全部下载
查看该艺术家
@@ -137,6 +138,7 @@
Subsonic 服务器
投送
添加
+ 添加到播放列表
全部下载
下载
全部
@@ -165,6 +167,8 @@
取消
新建
添加到播放列表
+ 将歌曲添加到播放列表
+ 未能将歌曲添加到播放列表
%1$d 首曲目 • %2$s
持续时间 • %1$s
播放列表名称
@@ -253,7 +257,7 @@
下载存储
调整音频设置
均衡器
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
关注开发进展
Github
设置图像分辨率
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5fd1ac33..8ecb78af 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,6 +3,7 @@
Please disable battery optimizations for media playback while the screen is off.
Battery Optimizations
Offline mode
+ Add to playlist
Add to queue
Download all
Go to artist
@@ -158,6 +159,7 @@
Subsonic servers
Cast
Add
+ Add to playlist
Download all
Download
All
@@ -194,6 +196,8 @@
Cancel
Create
Add to a playlist
+ Added song to playlist
+ Failed to add song to playlist
%1$d tracks • %2$s
Duration • %1$s
Long press to delete
@@ -285,7 +289,7 @@
Download storage
Adjust audio settings
Equalizer
- https://github.com/CappielloAntonio/tempo
+ https://github.com/eddyizm/tempo
Follow the development
Github
Set image resolution
diff --git a/app/src/main/res/xml/locale_config.xml b/app/src/main/res/xml/locale_config.xml
index 9e9c43d1..ac6e6d0e 100644
--- a/app/src/main/res/xml/locale_config.xml
+++ b/app/src/main/res/xml/locale_config.xml
@@ -9,4 +9,5 @@
+
diff --git a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
index 7125b9fd..b8a7179b 100644
--- a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -32,7 +32,8 @@ class MediaService : MediaLibraryService() {
private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
- private lateinit var customCommands: List
+ private lateinit var shuffleCommands: List
+ private lateinit var repeatCommands: List
private var customLayout = ImmutableList.of()
@@ -41,6 +42,12 @@ class MediaService : MediaLibraryService() {
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
+ "android.media3.session.demo.REPEAT_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
+ "android.media3.session.demo.REPEAT_ONE"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
+ "android.media3.session.demo.REPEAT_ALL"
}
override fun onCreate() {
@@ -72,7 +79,7 @@ class MediaService : MediaLibraryService() {
val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
- customCommands.forEach { commandButton ->
+ shuffleCommands.forEach { commandButton ->
// TODO: Aggiungere i comandi personalizzati
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
}
@@ -89,22 +96,40 @@ class MediaService : MediaLibraryService() {
}
}
+ fun buildCustomLayout(player: Player): ImmutableList {
+ val shuffle = shuffleCommands[if (player.shuffleModeEnabled) 1 else 0]
+ val repeat = when (player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> repeatCommands[1]
+ Player.REPEAT_MODE_ALL -> repeatCommands[2]
+ else -> repeatCommands[0]
+ }
+ return ImmutableList.of(shuffle, repeat)
+ }
+
override fun onCustomCommand(
session: MediaSession,
controller: ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture {
- if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
- player.shuffleModeEnabled = true
- customLayout = ImmutableList.of(customCommands[1])
- session.setCustomLayout(customLayout)
- } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
- player.shuffleModeEnabled = false
- customLayout = ImmutableList.of(customCommands[0])
- session.setCustomLayout(customLayout)
+ when (customCommand.customAction) {
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> player.shuffleModeEnabled = true
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> player.shuffleModeEnabled = false
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
+ val nextMode = when (player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
+ Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
+ else -> Player.REPEAT_MODE_OFF
+ }
+ player.repeatMode = nextMode
+ }
}
+ customLayout = librarySessionCallback.buildCustomLayout(player)
+ session.setCustomLayout(customLayout)
+
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
@@ -125,17 +150,28 @@ class MediaService : MediaLibraryService() {
}
private fun initializeCustomCommands() {
- customCommands =
- listOf(
- getShuffleCommandButton(
- SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
- ),
- getShuffleCommandButton(
- SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
- )
+ shuffleCommands = listOf(
+ getShuffleCommandButton(
+ SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
+ ),
+ getShuffleCommandButton(
+ SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
)
+ )
- customLayout = ImmutableList.of(customCommands[0])
+ repeatCommands = listOf(
+ getRepeatCommandButton(
+ SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY)
+ ),
+ getRepeatCommandButton(
+ SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY)
+ ),
+ getRepeatCommandButton(
+ SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY)
+ )
+ )
+
+ customLayout = ImmutableList.of(shuffleCommands[0], repeatCommands[0])
}
private fun initializePlayer() {
@@ -147,6 +183,9 @@ class MediaService : MediaLibraryService() {
.setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl())
.build()
+
+ player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
+ player.repeatMode = Preferences.getRepeatMode()
}
private fun initializeMediaLibrarySession() {
@@ -224,6 +263,18 @@ class MediaService : MediaLibraryService() {
}
}
}
+
+ override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
+ Preferences.setShuffleModeEnabled(shuffleModeEnabled)
+ customLayout = librarySessionCallback.buildCustomLayout(player)
+ mediaLibrarySession.setCustomLayout(customLayout)
+ }
+
+ override fun onRepeatModeChanged(repeatMode: Int) {
+ Preferences.setRepeatMode(repeatMode)
+ customLayout = librarySessionCallback.buildCustomLayout(player)
+ mediaLibrarySession.setCustomLayout(customLayout)
+ }
})
}
@@ -251,6 +302,25 @@ class MediaService : MediaLibraryService() {
.build()
}
+ @SuppressLint("PrivateResource")
+ private fun getRepeatCommandButton(sessionCommand: SessionCommand): CommandButton {
+ val icon = when (sessionCommand.customAction) {
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> R.drawable.exo_icon_repeat_one
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> R.drawable.exo_icon_repeat_all
+ else -> R.drawable.exo_icon_repeat_off
+ }
+ val description = when (sessionCommand.customAction) {
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> R.string.exo_controls_repeat_one_description
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> R.string.exo_controls_repeat_all_description
+ else -> R.string.exo_controls_repeat_off_description
+ }
+ return CommandButton.Builder()
+ .setDisplayName(getString(description))
+ .setSessionCommand(sessionCommand)
+ .setIconResId(icon)
+ .build()
+ }
+
private fun ignoreFuture(customLayout: ListenableFuture) {
/* Do nothing. */
}
diff --git a/app/src/play/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt b/app/src/play/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
index 747a45b2..099ae672 100644
--- a/app/src/play/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
+++ b/app/src/play/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.os.Bundle
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult
@@ -27,7 +28,7 @@ open class MediaLibrarySessionCallback(
MediaBrowserTree.initialize(automotiveRepository)
}
- private val customLayoutCommandButtons: List = listOf(
+ private val shuffleCommandButtons: List = listOf(
CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand(
@@ -45,15 +46,46 @@ open class MediaLibrarySessionCallback(
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
)
+ private val repeatCommandButtons: List = listOf(
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_off_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_off)
+ .build(),
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_one_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_one)
+ .build(),
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_all_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_all)
+ .build()
+ )
+
+ private val customLayoutCommandButtons: List =
+ shuffleCommandButtons + repeatCommandButtons
+
@OptIn(UnstableApi::class)
val mediaNotificationSessionCommands =
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder ->
- customLayoutCommandButtons.forEach { commandButton ->
+ (shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) }
}
}.build()
+ fun buildCustomLayout(player: Player): ImmutableList {
+ val shuffle = shuffleCommandButtons[if (player.shuffleModeEnabled) 1 else 0]
+ val repeat = when (player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> repeatCommandButtons[1]
+ Player.REPEAT_MODE_ALL -> repeatCommandButtons[2]
+ else -> repeatCommandButtons[0]
+ }
+ return ImmutableList.of(shuffle, repeat)
+ }
+
@OptIn(UnstableApi::class)
override fun onConnect(
session: MediaSession, controller: MediaSession.ControllerInfo
@@ -62,12 +94,11 @@ open class MediaLibrarySessionCallback(
controller
) || session.isAutoCompanionController(controller)
) {
- val customLayout =
- customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
+ val customLayout = buildCustomLayout(session.player)
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands)
- .setCustomLayout(ImmutableList.of(customLayout)).build()
+ .setCustomLayout(customLayout).build()
}
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
@@ -80,25 +111,28 @@ open class MediaLibrarySessionCallback(
customCommand: SessionCommand,
args: Bundle
): ListenableFuture {
- if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
- session.player.shuffleModeEnabled = true
- session.setCustomLayout(
- session.mediaNotificationControllerInfo!!,
- ImmutableList.of(customLayoutCommandButtons[1])
- )
-
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
- } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
- session.player.shuffleModeEnabled = false
- session.setCustomLayout(
- session.mediaNotificationControllerInfo!!,
- ImmutableList.of(customLayoutCommandButtons[0])
- )
-
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
+ when (customCommand.customAction) {
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
+ val nextMode = when (session.player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
+ Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
+ else -> Player.REPEAT_MODE_OFF
+ }
+ session.player.repeatMode = nextMode
+ }
+ else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
}
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
+ session.setCustomLayout(
+ session.mediaNotificationControllerInfo!!,
+ buildCustomLayout(session.player)
+ )
+
+ return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
override fun onGetLibraryRoot(
@@ -158,5 +192,11 @@ open class MediaLibrarySessionCallback(
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
+ "android.media3.session.demo.REPEAT_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
+ "android.media3.session.demo.REPEAT_ONE"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
+ "android.media3.session.demo.REPEAT_ALL"
}
}
\ No newline at end of file
diff --git a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
index b993aaba..2391a2bb 100644
--- a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -33,6 +33,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
+ private lateinit var librarySessionCallback: MediaLibrarySessionCallback
override fun onCreate() {
super.onCreate()
@@ -79,6 +80,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
.setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl())
.build()
+
+ player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
+ player.repeatMode = Preferences.getRepeatMode()
}
private fun initializeCastPlayer() {
@@ -97,13 +101,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
}
+ librarySessionCallback = createLibrarySessionCallback()
mediaLibrarySession =
- MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
+ MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent)
.build()
}
- private fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
+ private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
return MediaLibrarySessionCallback(this, automotiveRepository)
}
@@ -166,6 +171,20 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
}
}
}
+
+ override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
+ Preferences.setShuffleModeEnabled(shuffleModeEnabled)
+ mediaLibrarySession.setCustomLayout(
+ librarySessionCallback.buildCustomLayout(player)
+ )
+ }
+
+ override fun onRepeatModeChanged(repeatMode: Int) {
+ Preferences.setRepeatMode(repeatMode)
+ mediaLibrarySession.setCustomLayout(
+ librarySessionCallback.buildCustomLayout(player)
+ )
+ }
})
}
diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
index 747a45b2..099ae672 100644
--- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
+++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.os.Bundle
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult
@@ -27,7 +28,7 @@ open class MediaLibrarySessionCallback(
MediaBrowserTree.initialize(automotiveRepository)
}
- private val customLayoutCommandButtons: List = listOf(
+ private val shuffleCommandButtons: List = listOf(
CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand(
@@ -45,15 +46,46 @@ open class MediaLibrarySessionCallback(
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
)
+ private val repeatCommandButtons: List = listOf(
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_off_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_off)
+ .build(),
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_one_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_one)
+ .build(),
+ CommandButton.Builder()
+ .setDisplayName(context.getString(R.string.exo_controls_repeat_all_description))
+ .setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY))
+ .setIconResId(R.drawable.exo_icon_repeat_all)
+ .build()
+ )
+
+ private val customLayoutCommandButtons: List =
+ shuffleCommandButtons + repeatCommandButtons
+
@OptIn(UnstableApi::class)
val mediaNotificationSessionCommands =
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder ->
- customLayoutCommandButtons.forEach { commandButton ->
+ (shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) }
}
}.build()
+ fun buildCustomLayout(player: Player): ImmutableList {
+ val shuffle = shuffleCommandButtons[if (player.shuffleModeEnabled) 1 else 0]
+ val repeat = when (player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> repeatCommandButtons[1]
+ Player.REPEAT_MODE_ALL -> repeatCommandButtons[2]
+ else -> repeatCommandButtons[0]
+ }
+ return ImmutableList.of(shuffle, repeat)
+ }
+
@OptIn(UnstableApi::class)
override fun onConnect(
session: MediaSession, controller: MediaSession.ControllerInfo
@@ -62,12 +94,11 @@ open class MediaLibrarySessionCallback(
controller
) || session.isAutoCompanionController(controller)
) {
- val customLayout =
- customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
+ val customLayout = buildCustomLayout(session.player)
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands)
- .setCustomLayout(ImmutableList.of(customLayout)).build()
+ .setCustomLayout(customLayout).build()
}
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
@@ -80,25 +111,28 @@ open class MediaLibrarySessionCallback(
customCommand: SessionCommand,
args: Bundle
): ListenableFuture {
- if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
- session.player.shuffleModeEnabled = true
- session.setCustomLayout(
- session.mediaNotificationControllerInfo!!,
- ImmutableList.of(customLayoutCommandButtons[1])
- )
-
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
- } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
- session.player.shuffleModeEnabled = false
- session.setCustomLayout(
- session.mediaNotificationControllerInfo!!,
- ImmutableList.of(customLayoutCommandButtons[0])
- )
-
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
+ when (customCommand.customAction) {
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
+ CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
+ CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
+ val nextMode = when (session.player.repeatMode) {
+ Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
+ Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
+ else -> Player.REPEAT_MODE_OFF
+ }
+ session.player.repeatMode = nextMode
+ }
+ else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
}
- return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
+ session.setCustomLayout(
+ session.mediaNotificationControllerInfo!!,
+ buildCustomLayout(session.player)
+ )
+
+ return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
override fun onGetLibraryRoot(
@@ -158,5 +192,11 @@ open class MediaLibrarySessionCallback(
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
+ "android.media3.session.demo.REPEAT_OFF"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
+ "android.media3.session.demo.REPEAT_ONE"
+ private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
+ "android.media3.session.demo.REPEAT_ALL"
}
}
\ No newline at end of file
diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
index b993aaba..2391a2bb 100644
--- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
+++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt
@@ -33,6 +33,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
+ private lateinit var librarySessionCallback: MediaLibrarySessionCallback
override fun onCreate() {
super.onCreate()
@@ -79,6 +80,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
.setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl())
.build()
+
+ player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
+ player.repeatMode = Preferences.getRepeatMode()
}
private fun initializeCastPlayer() {
@@ -97,13 +101,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
}
+ librarySessionCallback = createLibrarySessionCallback()
mediaLibrarySession =
- MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
+ MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent)
.build()
}
- private fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
+ private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
return MediaLibrarySessionCallback(this, automotiveRepository)
}
@@ -166,6 +171,20 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
}
}
}
+
+ override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
+ Preferences.setShuffleModeEnabled(shuffleModeEnabled)
+ mediaLibrarySession.setCustomLayout(
+ librarySessionCallback.buildCustomLayout(player)
+ )
+ }
+
+ override fun onRepeatModeChanged(repeatMode: Int) {
+ Preferences.setRepeatMode(repeatMode)
+ mediaLibrarySession.setCustomLayout(
+ librarySessionCallback.buildCustomLayout(player)
+ )
+ }
})
}
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/settings.gradle b/settings.gradle
index a7c6dd41..1cd079ae 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,5 @@
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
+}
include ':app'
rootProject.name = "Tempo"
\ No newline at end of file