Merge pull request #10 from eddyizm/rc

Rc
This commit is contained in:
eddyizm 2025-08-04 19:53:35 -07:00 committed by GitHub
commit 74cce9c867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 1166 additions and 142 deletions

1
.gitignore vendored
View file

@ -14,3 +14,4 @@
.cxx .cxx
/.idea/ /.idea/
.env .env
.vscode/settings.json

1
.idea/misc.xml generated
View file

@ -192,6 +192,7 @@
</map> </map>
</option> </option>
</component> </component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>

View file

@ -7,12 +7,12 @@
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/CappielloAntonio/tempo/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a> <a href="https://github.com/eddyizm/tempo/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
</p> </p>
<p align="center"> <!-- <p align="center">
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a> <a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.cappielloantonio.tempo"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a> <a href="https://apt.izzysoft.de/fdroid/index/apk/com.cappielloantonio.tempo"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
</p> </p> -->
**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. **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.** **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 ## Features
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go. - **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. - **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
<img src="mockup/feat/4_screenshot.png" width=200> <img src="mockup/feat/4_screenshot.png" width=200>
<img src="mockup/feat/5_screenshot.png" width=200> <img src="mockup/feat/5_screenshot.png" width=200>
<img src="mockup/feat/6_screenshot.png" width=200> <img src="mockup/feat/6_screenshot.png" width=200>
<img src="mockup/feat/7_screenshot.png" width=200> <img src="mockup/feat/7_scregetRecentSearchSuggestionenshot.png" width=200>
<img src="mockup/feat/8_screenshot.png" width=200> <img src="mockup/feat/8_screenshot.png" width=200>
</p> </p>

View file

@ -11,7 +11,7 @@ android {
targetSdk 35 targetSdk 35
versionCode 26 versionCode 26
versionName '3.9.0' versionName '3.10.0-alpha.1'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@ -107,4 +107,9 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0' implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0' implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
} }

View file

@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

View file

@ -1,10 +1,16 @@
package com.cappielloantonio.tempo.repository; 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.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.database.AppDatabase; import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.PlaylistDao; import com.cappielloantonio.tempo.database.dao.PlaylistDao;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse; import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
@ -20,6 +26,7 @@ import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
public class PlaylistRepository { public class PlaylistRepository {
@androidx.media3.common.util.UnstableApi
private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao(); private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao();
public MutableLiveData<List<Playlist>> getPlaylists(boolean random, int size) { public MutableLiveData<List<Playlist>> getPlaylists(boolean random, int size) {
MutableLiveData<List<Playlist>> listLivePlaylists = new MutableLiveData<>(new ArrayList<>()); MutableLiveData<List<Playlist>> listLivePlaylists = new MutableLiveData<>(new ArrayList<>());
@ -80,12 +87,12 @@ public class PlaylistRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_success), Toast.LENGTH_SHORT).show();
} }
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> 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<List<Playlist>> getPinnedPlaylists() { public LiveData<List<Playlist>> getPinnedPlaylists() {
return playlistDao.getAll(); return playlistDao.getAll();
} }
@androidx.media3.common.util.UnstableApi
public void insert(Playlist playlist) { public void insert(Playlist playlist) {
InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist); InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist);
Thread thread = new Thread(insert); Thread thread = new Thread(insert);
thread.start(); thread.start();
} }
@androidx.media3.common.util.UnstableApi
public void delete(Playlist playlist) { public void delete(Playlist playlist) {
DeleteThreadSafe delete = new DeleteThreadSafe(playlistDao, playlist); DeleteThreadSafe delete = new DeleteThreadSafe(playlistDao, playlist);
Thread thread = new Thread(delete); Thread thread = new Thread(delete);

View file

@ -157,7 +157,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
albums.sort(Comparator.comparing(AlbumID3::getName)); albums.sort(Comparator.comparing(AlbumID3::getName));
break; break;
case Constants.ALBUM_ORDER_BY_ARTIST: case Constants.ALBUM_ORDER_BY_ARTIST:
albums.sort(Comparator.comparing(AlbumID3::getArtist)); albums.sort(Comparator.comparing(AlbumID3::getArtist, Comparator.nullsLast(Comparator.naturalOrder())));
break; break;
case Constants.ALBUM_ORDER_BY_YEAR: case Constants.ALBUM_ORDER_BY_YEAR:
albums.sort(Comparator.comparing(AlbumID3::getYear)); albums.sort(Comparator.comparing(AlbumID3::getYear));

View file

@ -1,5 +1,6 @@
package com.cappielloantonio.tempo.ui.adapter; package com.cappielloantonio.tempo.ui.adapter;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -10,6 +11,7 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.media3.session.MediaBrowser; import androidx.media3.session.MediaBrowser;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestBuilder;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding; import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.glide.CustomGlideRequest;
@ -46,7 +48,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
@Override @Override
public void onBindViewHolder(ViewHolder holder, int position) { public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position); Child song = songs.get(holder.getLayoutPosition());
holder.item.queueSongTitleTextView.setText(song.getTitle()); holder.item.queueSongTitleTextView.setText(song.getTitle());
holder.item.queueSongSubtitleTextView.setText( holder.item.queueSongSubtitleTextView.setText(
@ -58,15 +60,21 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
) )
); );
RequestBuilder<Drawable> thumbnail = CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.sizeMultiplier(0.1f);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.thumbnail(thumbnail)
.into(holder.item.queueSongCoverImageView); .into(holder.item.queueSongCoverImageView);
MediaManager.getCurrentIndex(mediaBrowserListenableFuture, new MediaIndexCallback() { MediaManager.getCurrentIndex(mediaBrowserListenableFuture, new MediaIndexCallback() {
@Override @Override
public void onRecovery(int index) { public void onRecovery(int index) {
if (position < index) { if (holder.getLayoutPosition() < index) {
holder.item.queueSongTitleTextView.setAlpha(0.2f); holder.item.queueSongTitleTextView.setAlpha(0.2f);
holder.item.queueSongSubtitleTextView.setAlpha(0.2f); holder.item.queueSongSubtitleTextView.setAlpha(0.2f);
holder.item.ratingIndicatorImageView.setAlpha(0.2f); holder.item.ratingIndicatorImageView.setAlpha(0.2f);

View file

@ -57,14 +57,14 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
} }
private void setSongInfo() { private void setSongInfo() {
playlistChooserViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT)); playlistChooserViewModel.setSongsToAdd(requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT));
} }
private void setButtonAction() { private void setButtonAction() {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog()); androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd()); bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, playlistChooserViewModel.getSongsToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null); PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle); dialog.setArguments(bundle);
@ -98,7 +98,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
@Override @Override
public void onPlaylistClick(Bundle bundle) { public void onPlaylistClick(Bundle bundle) {
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT); Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
playlistChooserViewModel.addSongToPlaylist(playlist.getId()); playlistChooserViewModel.addSongsToPlaylist(playlist.getId());
dismiss(); dismiss();
} }
} }

View file

@ -74,11 +74,11 @@ public class PlaylistEditorDialog extends DialogFragment {
} }
private void setParameterInfo() { private void setParameterInfo() {
if (requireArguments().getParcelable(Constants.TRACK_OBJECT) != null) { if (requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT) != null) {
playlistEditorViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT)); playlistEditorViewModel.setSongsToAdd(requireArguments().getParcelableArrayList(Constants.TRACKS_OBJECT));
playlistEditorViewModel.setPlaylistToEdit(null); playlistEditorViewModel.setPlaylistToEdit(null);
} else if (requireArguments().getParcelable(Constants.PLAYLIST_OBJECT) != null) { } else if (requireArguments().getParcelable(Constants.PLAYLIST_OBJECT) != null) {
playlistEditorViewModel.setSongToAdd(null); playlistEditorViewModel.setSongsToAdd(null);
playlistEditorViewModel.setPlaylistToEdit(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT)); playlistEditorViewModel.setPlaylistToEdit(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
if (playlistEditorViewModel.getPlaylistToEdit() != null) { if (playlistEditorViewModel.getPlaylistToEdit() != null) {
@ -92,7 +92,7 @@ public class PlaylistEditorDialog extends DialogFragment {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) { if (validateInput()) {
if (playlistEditorViewModel.getSongToAdd() != null) { if (playlistEditorViewModel.getSongsToAdd() != null) {
playlistEditorViewModel.createPlaylist(playlistName); playlistEditorViewModel.createPlaylist(playlistName);
} else if (playlistEditorViewModel.getPlaylistToEdit() != null) { } else if (playlistEditorViewModel.getPlaylistToEdit() != null) {
playlistEditorViewModel.updatePlaylist(playlistName); playlistEditorViewModel.updatePlaylist(playlistName);

View file

@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; 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.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MappingUtil;
@ -38,6 +39,7 @@ import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel; import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -108,6 +110,17 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
}); });
return true; 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; return false;
} }

View file

@ -112,6 +112,9 @@ public class PlayerBottomSheetFragment extends Fragment {
try { try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get(); MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
mediaBrowser.setShuffleModeEnabled(Preferences.isShuffleModeEnabled());
mediaBrowser.setRepeatMode(Preferences.getRepeatMode());
setMediaControllerListener(mediaBrowser); setMediaControllerListener(mediaBrowser);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -150,6 +153,16 @@ public class PlayerBottomSheetFragment extends Fragment {
public void onEvents(Player player, Player.Events events) { public void onEvents(Player player, Player.Events events) {
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem()); setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
} }
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
Preferences.setShuffleModeEnabled(shuffleModeEnabled);
}
@Override
public void onRepeatModeChanged(int repeatMode) {
Preferences.setRepeatMode(repeatMode);
}
}); });
} }

View file

@ -140,6 +140,8 @@ public class PlayerControllerFragment extends Fragment {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get(); MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser); bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser);
mediaBrowser.setShuffleModeEnabled(Preferences.isShuffleModeEnabled());
mediaBrowser.setRepeatMode(Preferences.getRepeatMode());
setMediaControllerListener(mediaBrowser); setMediaControllerListener(mediaBrowser);
} catch (Exception e) { } catch (Exception e) {
@ -160,6 +162,16 @@ public class PlayerControllerFragment extends Fragment {
setMetadata(mediaMetadata); setMetadata(mediaMetadata);
setMediaInfo(mediaMetadata); setMediaInfo(mediaMetadata);
} }
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
Preferences.setShuffleModeEnabled(shuffleModeEnabled);
}
@Override
public void onRepeatModeChanged(int repeatMode) {
Preferences.setRepeatMode(repeatMode);
}
}); });
} }

View file

@ -193,13 +193,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> { playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) { if (bind != null) {
bind.playlistPagePlayButton.setOnClickListener(v -> { bind.playlistPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0); MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true); activity.setBottomSheetInPeek(true);
}); });
bind.playlistPageShuffleButton.setOnClickListener(v -> { bind.playlistPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs); Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0); MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true); activity.setBottomSheetInPeek(true);
}); });
} }
@ -270,4 +270,4 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
public void onMediaLongClick(Bundle bundle) { public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
} }
} }

View file

@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity; 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.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil; 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); TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> { albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs); List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);

View file

@ -39,6 +39,9 @@ import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
@UnstableApi @UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener { public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel; private HomeViewModel homeViewModel;
@ -177,7 +180,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view); TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
addToPlaylist.setOnClickListener(v -> { addToPlaylist.setOnClickListener(v -> {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song); bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(Collections.singletonList(song)));
PlaylistChooserDialog dialog = new PlaylistChooserDialog(); PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle); dialog.setArguments(bundle);

View file

@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.util package com.cappielloantonio.tempo.util
import android.util.Log import android.util.Log
import androidx.media3.common.Player
import com.cappielloantonio.tempo.App import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.model.HomeSector import com.cappielloantonio.tempo.model.HomeSector
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension 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 NEXT_SERVER_SWITCH = "next_server_switch"
private const val PLAYBACK_SPEED = "playback_speed" private const val PLAYBACK_SPEED = "playback_speed"
private const val SKIP_SILENCE = "skip_silence" 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 IMAGE_CACHE_SIZE = "image_cache_size"
private const val STREAMING_CACHE_SIZE = "streaming_cache_size" private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
private const val IMAGE_SIZE = "image_size" private const val IMAGE_SIZE = "image_size"
@ -226,6 +229,26 @@ object Preferences {
App.getInstance().preferences.edit().putBoolean(SKIP_SILENCE, isSkipSilenceMode).apply() 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 @JvmStatic
fun getImageCacheSize(): Int { fun getImageCacheSize(): Int {
return App.getInstance().preferences.getString(IMAGE_CACHE_SIZE, "500")!!.toInt() return App.getInstance().preferences.getString(IMAGE_CACHE_SIZE, "500")!!.toInt()

View file

@ -21,6 +21,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository; private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private String albumId; private String albumId;
private String artistId;
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null); private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
public AlbumPageViewModel(@NonNull Application application) { public AlbumPageViewModel(@NonNull Application application) {
@ -41,6 +42,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
public void setAlbum(LifecycleOwner owner, AlbumID3 album) { public void setAlbum(LifecycleOwner owner, AlbumID3 album) {
this.albumId = album.getId(); this.albumId = album.getId();
this.album.postValue(album); this.album.postValue(album);
this.artistId = album.getArtistId();
albumRepository.getAlbum(album.getId()).observe(owner, albums -> { albumRepository.getAlbum(album.getId()).observe(owner, albums -> {
if (albums != null) this.album.setValue(albums); if (albums != null) this.album.setValue(albums);
@ -48,7 +50,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
} }
public LiveData<ArtistID3> getArtist() { public LiveData<ArtistID3> getArtist() {
return artistRepository.getArtistInfo(albumId); return artistRepository.getArtistInfo(artistId);
} }
public LiveData<AlbumInfo> getAlbumInfo() { public LiveData<AlbumInfo> getAlbumInfo() {

View file

@ -94,7 +94,7 @@ public class HomeViewModel extends AndroidViewModel {
} }
public LiveData<List<Child>> getRandomShuffleSample() { public LiveData<List<Child>> getRandomShuffleSample() {
return songRepository.getRandomSample(100, null, null); return songRepository.getRandomSample(1000, null, null);
} }
public LiveData<List<Chronology>> getChronologySample(LifecycleOwner owner) { public LiveData<List<Chronology>> getChronologySample(LifecycleOwner owner) {

View file

@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.repository.PlaylistRepository; import com.cappielloantonio.tempo.repository.PlaylistRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist; import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -20,7 +21,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
private final PlaylistRepository playlistRepository; private final PlaylistRepository playlistRepository;
private final MutableLiveData<List<Playlist>> playlists = new MutableLiveData<>(null); private final MutableLiveData<List<Playlist>> playlists = new MutableLiveData<>(null);
private Child toAdd; private ArrayList<Child> toAdd;
public PlaylistChooserViewModel(@NonNull Application application) { public PlaylistChooserViewModel(@NonNull Application application) {
super(application); super(application);
@ -33,15 +34,15 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
return playlists; return playlists;
} }
public void addSongToPlaylist(String playlistId) { public void addSongsToPlaylist(String playlistId) {
playlistRepository.addSongToPlaylist(playlistId, new ArrayList(Collections.singletonList(toAdd.getId()))); playlistRepository.addSongToPlaylist(playlistId, new ArrayList<>(Lists.transform(toAdd, Child::getId)));
} }
public void setSongToAdd(Child song) { public void setSongsToAdd(ArrayList<Child> songs) {
toAdd = song; toAdd = songs;
} }
public Child getSongToAdd() { public ArrayList<Child> getSongsToAdd() {
return toAdd; return toAdd;
} }
} }

View file

@ -12,6 +12,7 @@ import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist; import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.Share; import com.cappielloantonio.tempo.subsonic.models.Share;
import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -24,7 +25,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
private final PlaylistRepository playlistRepository; private final PlaylistRepository playlistRepository;
private final SharingRepository sharingRepository; private final SharingRepository sharingRepository;
private Child toAdd; private ArrayList<Child> toAdd;
private Playlist toEdit; private Playlist toEdit;
private MutableLiveData<List<Child>> songLiveList = new MutableLiveData<>(); private MutableLiveData<List<Child>> songLiveList = new MutableLiveData<>();
@ -37,7 +38,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
} }
public void createPlaylist(String name) { 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) { public void updatePlaylist(String name) {
@ -48,12 +49,12 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
if (toEdit != null) playlistRepository.deletePlaylist(toEdit.getId()); if (toEdit != null) playlistRepository.deletePlaylist(toEdit.getId());
} }
public Child getSongToAdd() { public void setSongsToAdd(ArrayList<Child> songs) {
return toAdd; toAdd = songs;
} }
public void setSongToAdd(Child song) { public ArrayList<Child> getSongsToAdd() {
this.toAdd = song; return toAdd;
} }
public Playlist getPlaylistToEdit() { public Playlist getPlaylistToEdit() {

View file

@ -146,6 +146,19 @@
android:paddingBottom="12dp" android:paddingBottom="12dp"
android:text="@string/album_bottom_sheet_download_all" /> android:text="@string/album_bottom_sheet_download_all" />
<TextView
android:id="@+id/add_to_playlist_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/album_bottom_sheet_add_to_playlist" />
<TextView <TextView
android:id="@+id/remove_all_text_view" android:id="@+id/remove_all_text_view"
style="@style/LabelMedium" style="@style/LabelMedium"

View file

@ -4,39 +4,27 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/player_clean_queue_button"
style="@style/TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/player_queue_clean_all_button" />
<com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost <com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost
android:id="@+id/player_queue_nested_scrollable_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/player_queue_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:paddingTop="8dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="@dimen/global_padding_bottom">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/player_queue_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<TextView
android:id="@+id/player_clean_queue_button"
style="@style/TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="24dp"
android:text="@string/player_queue_clean_all_button"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost> </com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton

View file

@ -6,4 +6,9 @@
android:icon="@drawable/ic_file_download" android:icon="@drawable/ic_file_download"
android:title="@string/menu_download_all_button" android:title="@string/menu_download_all_button"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_add_to_playlist"
android:icon="@drawable/ic_add"
android:title="@string/menu_add_to_playlist_button"
app:showAsAction="never" />
</menu> </menu>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">Bitte deaktiviere die Batterieoptimierung, damit die Medienwiedergabe bei ausgeschaltetem Bildschirm richtig funktioniert.</string> <string name="activity_battery_optimizations_summary">Bitte deaktiviere die Batterieoptimierung, damit die Medienwiedergabe bei ausgeschaltetem Bildschirm richtig funktioniert.</string>
<string name="activity_battery_optimizations_title">Batterie Optimierung</string> <string name="activity_battery_optimizations_title">Batterie Optimierung</string>
<string name="activity_info_offline_mode">Offlinebetrieb</string> <string name="activity_info_offline_mode">Offlinebetrieb</string>
<string name="album_bottom_sheet_add_to_playlist">Zu Playliste hinzufügen</string>
<string name="album_bottom_sheet_add_to_queue">Zur Warteschlange hinzufügen</string> <string name="album_bottom_sheet_add_to_queue">Zur Warteschlange hinzufügen</string>
<string name="album_bottom_sheet_download_all">Alle herunterladen</string> <string name="album_bottom_sheet_download_all">Alle herunterladen</string>
<string name="album_bottom_sheet_go_to_artist">Gehe zu Künstler</string> <string name="album_bottom_sheet_go_to_artist">Gehe zu Künstler</string>
@ -154,6 +155,7 @@
<string name="login_title_expanded">Subsonic Server</string> <string name="login_title_expanded">Subsonic Server</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">Hinzufügen</string> <string name="menu_add_button">Hinzufügen</string>
<string name="menu_add_to_playlist_button">Zu Playliste hinzufügen</string>
<string name="menu_download_all_button">Alle Herunterladen</string> <string name="menu_download_all_button">Alle Herunterladen</string>
<string name="menu_download_label">Downloads</string> <string name="menu_download_label">Downloads</string>
<string name="menu_filter_all">Alle</string> <string name="menu_filter_all">Alle</string>
@ -186,6 +188,8 @@
<string name="playlist_chooser_dialog_negative_button">Abbrechen</string> <string name="playlist_chooser_dialog_negative_button">Abbrechen</string>
<string name="playlist_chooser_dialog_neutral_button">Erstellen</string> <string name="playlist_chooser_dialog_neutral_button">Erstellen</string>
<string name="playlist_chooser_dialog_title">Zu einer Playliste hinzufügen</string> <string name="playlist_chooser_dialog_title">Zu einer Playliste hinzufügen</string>
<string name="playlist_chooser_dialog_toast_add_success">Lied zu Playlist hinzugefügt</string>
<string name="playlist_chooser_dialog_toast_add_failure">Titel kann nicht zur Playlist hinzugefügt werden</string>
<string name="playlist_counted_tracks">%1$d Tracks • %2$s</string> <string name="playlist_counted_tracks">%1$d Tracks • %2$s</string>
<string name="playlist_duration">Länge • %1$s</string> <string name="playlist_duration">Länge • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Zum Löschen lange drücken</string> <string name="playlist_editor_dialog_action_delete_toast">Zum Löschen lange drücken</string>
@ -277,7 +281,7 @@
<string name="settings_download_storage_title">Download storage</string> <string name="settings_download_storage_title">Download storage</string>
<string name="settings_equalizer_summary">Audio Einstellungen anpassen</string> <string name="settings_equalizer_summary">Audio Einstellungen anpassen</string>
<string name="settings_equalizer_title">Equalizer</string> <string name="settings_equalizer_title">Equalizer</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Verfolge die Entwicklung</string> <string name="settings_github_summary">Verfolge die Entwicklung</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Bilder Auflösung anpassen</string> <string name="settings_image_size">Bilder Auflösung anpassen</string>

View file

@ -165,7 +165,7 @@
<string name="settings_image_size">Resolución de la imagen</string> <string name="settings_image_size">Resolución de la imagen</string>
<string name="settings_language">Idioma</string> <string name="settings_language">Idioma</string>
<string name="settings_logout_title">Cerrar sesión</string> <string name="settings_logout_title">Cerrar sesión</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Siga el desarrollo</string> <string name="settings_github_summary">Siga el desarrollo</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="menu_group_by_genre">Género</string> <string name="menu_group_by_genre">Género</string>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">Veuillez désactiver les optimisations de la batterie pour permettre la lecture des médias lorsque l\'écran est éteint.</string> <string name="activity_battery_optimizations_summary">Veuillez désactiver les optimisations de la batterie pour permettre la lecture des médias lorsque l\'écran est éteint.</string>
<string name="activity_battery_optimizations_title">Optimisations de la batterie</string> <string name="activity_battery_optimizations_title">Optimisations de la batterie</string>
<string name="activity_info_offline_mode">Mode hors-ligne</string> <string name="activity_info_offline_mode">Mode hors-ligne</string>
<string name="album_bottom_sheet_add_to_playlist">Ajouter à une playlist</string>
<string name="album_bottom_sheet_add_to_queue">Ajouter à la file d\'attente</string> <string name="album_bottom_sheet_add_to_queue">Ajouter à la file d\'attente</string>
<string name="album_bottom_sheet_download_all">Télécharger tout</string> <string name="album_bottom_sheet_download_all">Télécharger tout</string>
<string name="album_bottom_sheet_go_to_artist">Aller à l\'artiste</string> <string name="album_bottom_sheet_go_to_artist">Aller à l\'artiste</string>
@ -133,6 +134,7 @@
<string name="login_title_expanded">Serveurs Subsonic</string> <string name="login_title_expanded">Serveurs Subsonic</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">Ajouter</string> <string name="menu_add_button">Ajouter</string>
<string name="menu_add_to_playlist_button">Ajouter à une playlist</string>
<string name="menu_download_all_button">Télécharger tout</string> <string name="menu_download_all_button">Télécharger tout</string>
<string name="menu_download_label">Téléchargé</string> <string name="menu_download_label">Téléchargé</string>
<string name="menu_filter_all">Tout</string> <string name="menu_filter_all">Tout</string>
@ -244,7 +246,7 @@
<string name="settings_download_storage_title">Stockage des téléchargements</string> <string name="settings_download_storage_title">Stockage des téléchargements</string>
<string name="settings_equalizer_summary">Ajuster les paramètres audios</string> <string name="settings_equalizer_summary">Ajuster les paramètres audios</string>
<string name="settings_equalizer_title">Égaliseur</string> <string name="settings_equalizer_title">Égaliseur</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Suivre le développement</string> <string name="settings_github_summary">Suivre le développement</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Définir la résolution des images</string> <string name="settings_image_size">Définir la résolution des images</string>

View file

@ -3,7 +3,8 @@
<string name="activity_battery_optimizations_summary">Per favore, disabilita le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.</string> <string name="activity_battery_optimizations_summary">Per favore, disabilita le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.</string>
<string name="activity_battery_optimizations_title">Ottimizzazioni della Batteria</string> <string name="activity_battery_optimizations_title">Ottimizzazioni della Batteria</string>
<string name="activity_info_offline_mode">Modalità offline</string> <string name="activity_info_offline_mode">Modalità offline</string>
<string name="album_bottom_sheet_add_to_queue">Aggiungi alla coda</string> <string name="album_bottom_sheet_add_to_playlist">Aggiungi alla playlist</string>
<string name="album_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
<string name="album_bottom_sheet_download_all">Scarica tutto</string> <string name="album_bottom_sheet_download_all">Scarica tutto</string>
<string name="album_bottom_sheet_go_to_artist">Vai all\'artista</string> <string name="album_bottom_sheet_go_to_artist">Vai all\'artista</string>
<string name="album_bottom_sheet_instant_mix">Mix istantaneo</string> <string name="album_bottom_sheet_instant_mix">Mix istantaneo</string>
@ -156,7 +157,8 @@
<string name="login_title_expanded">Server Subsonic</string> <string name="login_title_expanded">Server Subsonic</string>
<string name="media_route_menu_title">Trasmetti</string> <string name="media_route_menu_title">Trasmetti</string>
<string name="menu_add_button">Aggiungi</string> <string name="menu_add_button">Aggiungi</string>
<string name="menu_download_all_button">Scarica tutto</string> <string name="menu_add_to_playlist_button">Aggiungi alla playlist</string>
<string name="menu_download_all_button">Scarica tutto</string>
<string name="menu_download_label">Scarica</string> <string name="menu_download_label">Scarica</string>
<string name="menu_filter_all">Tutti</string> <string name="menu_filter_all">Tutti</string>
<string name="menu_filter_download">Scaricati</string> <string name="menu_filter_download">Scaricati</string>
@ -188,6 +190,8 @@
<string name="playlist_chooser_dialog_negative_button">Annulla</string> <string name="playlist_chooser_dialog_negative_button">Annulla</string>
<string name="playlist_chooser_dialog_neutral_button">Crea</string> <string name="playlist_chooser_dialog_neutral_button">Crea</string>
<string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string> <string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string>
<string name="playlist_chooser_dialog_toast_add_success">Aggiunta di un brano alla playlist</string>
<string name="playlist_chooser_dialog_toast_add_failure">Impossibile aggiungere un brano alla playlist</string>
<string name="playlist_counted_tracks">%1$d brani • %2$s</string> <string name="playlist_counted_tracks">%1$d brani • %2$s</string>
<string name="playlist_duration">Durata • %1$s</string> <string name="playlist_duration">Durata • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string> <string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string>
@ -279,7 +283,7 @@
<string name="settings_download_storage_title">Archivio download</string> <string name="settings_download_storage_title">Archivio download</string>
<string name="settings_equalizer_summary">Regola le impostazioni audio</string> <string name="settings_equalizer_summary">Regola le impostazioni audio</string>
<string name="settings_equalizer_title">Equalizzatore</string> <string name="settings_equalizer_title">Equalizzatore</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Segui lo sviluppo</string> <string name="settings_github_summary">Segui lo sviluppo</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Imposta risoluzione delle immagini</string> <string name="settings_image_size">Imposta risoluzione delle immagini</string>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">화면이 꺼진 상태에서 음악 재생을 하기 위해서는 배터리 최적화를 비활성화 해주세요.</string> <string name="activity_battery_optimizations_summary">화면이 꺼진 상태에서 음악 재생을 하기 위해서는 배터리 최적화를 비활성화 해주세요.</string>
<string name="activity_battery_optimizations_title">배터리 최적화</string> <string name="activity_battery_optimizations_title">배터리 최적화</string>
<string name="activity_info_offline_mode">오프라인 모드</string> <string name="activity_info_offline_mode">오프라인 모드</string>
<string name="album_bottom_sheet_add_to_playlist">플레이리스트에 추가</string>
<string name="album_bottom_sheet_add_to_queue">재생목록에 추가</string> <string name="album_bottom_sheet_add_to_queue">재생목록에 추가</string>
<string name="album_bottom_sheet_download_all">모두 다운로드</string> <string name="album_bottom_sheet_download_all">모두 다운로드</string>
<string name="album_bottom_sheet_go_to_artist">아티스트로 이동</string> <string name="album_bottom_sheet_go_to_artist">아티스트로 이동</string>
@ -135,6 +136,7 @@
<string name="login_title_expanded">Subsonic 서버</string> <string name="login_title_expanded">Subsonic 서버</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">추가</string> <string name="menu_add_button">추가</string>
<string name="menu_add_to_playlist_button">플레이리스트에 추가</string>
<string name="menu_download_all_button">모두 다운로드</string> <string name="menu_download_all_button">모두 다운로드</string>
<string name="menu_download_label">다운로드</string> <string name="menu_download_label">다운로드</string>
<string name="menu_filter_all">모두</string> <string name="menu_filter_all">모두</string>
@ -161,6 +163,8 @@
<string name="playlist_chooser_dialog_negative_button">취소</string> <string name="playlist_chooser_dialog_negative_button">취소</string>
<string name="playlist_chooser_dialog_neutral_button">생성</string> <string name="playlist_chooser_dialog_neutral_button">생성</string>
<string name="playlist_chooser_dialog_title">플레이리스트 추가</string> <string name="playlist_chooser_dialog_title">플레이리스트 추가</string>
<string name="playlist_chooser_dialog_toast_add_success">재생 목록에 노래 추가</string>
<string name="playlist_chooser_dialog_toast_add_failure">재생 목록에 노래를 추가하지 못했습니다.</string>
<string name="playlist_counted_tracks">%1$d 트랙 • %2$s</string> <string name="playlist_counted_tracks">%1$d 트랙 • %2$s</string>
<string name="playlist_duration">재생시간 • %1$s</string> <string name="playlist_duration">재생시간 • %1$s</string>
<string name="playlist_editor_dialog_hint_name">플레이리스트 이름</string> <string name="playlist_editor_dialog_hint_name">플레이리스트 이름</string>
@ -246,7 +250,7 @@
<string name="settings_download_storage_title">스토리지 다운로드</string> <string name="settings_download_storage_title">스토리지 다운로드</string>
<string name="settings_equalizer_summary">오디오 설정 적용</string> <string name="settings_equalizer_summary">오디오 설정 적용</string>
<string name="settings_equalizer_title">이퀄라이저</string> <string name="settings_equalizer_title">이퀄라이저</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Follow the development</string> <string name="settings_github_summary">Follow the development</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">이미지 해상도 설정</string> <string name="settings_image_size">이미지 해상도 설정</string>

View file

@ -0,0 +1,257 @@
<resources>
<string-array name="theme_list_titles">
<item>Jasny</item>
<item>Ciemny</item>
<item>Domyślny systemu</item>
</string-array>
<string-array name="theme_list_values">
<item>jasny</item>
<item>ciemny</item>
<item>domyślny</item>
</string-array>
<string-array name="pref_cache_size_titles">
<item>Duży</item>
<item>Średni</item>
<item>Mały</item>
</string-array>
<string-array name="pref_cache_size_values">
<item>500</item>
<item>250</item>
<item>125</item>
</string-array>
<string-array name="pref_image_size_titles">
<item>Duża</item>
<item>Średnia</item>
<item>Mała</item>
</string-array>
<string-array name="pref_image_size_values">
<item>-1</item>
<item>500</item>
<item>300</item>
</string-array>
<string-array name="streaming_cache_size_titles">
<item>Wyłączone</item>
<item>128 MiB</item>
<item>256 MiB</item>
<item>512 MiB</item>
<item>1024 MiB</item>
</string-array>
<string-array name="streaming_cache_size_values">
<item>0</item>
<item>128</item>
<item>256</item>
<item>512</item>
<item>1024</item>
</string-array>
<string-array name="max_bitrate_wifi_list_titles">
<item>Oryginalny</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_wifi_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="max_bitrate_mobile_list_titles">
<item>Oryginalny</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_mobile_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="max_bitrate_download_list_titles">
<item>Oryginalny</item>
<item>32 kbps</item>
<item>48 kbps</item>
<item>64 kbps</item>
<item>80 kbps</item>
<item>96 kbps</item>
<item>112 kbps</item>
<item>128 kbps</item>
<item>160 kbps</item>
<item>192 kbps</item>
<item>256 kbps</item>
<item>320 kbps</item>
</string-array>
<string-array name="max_bitrate_download_list_values">
<item>0</item>
<item>32</item>
<item>48</item>
<item>64</item>
<item>80</item>
<item>96</item>
<item>112</item>
<item>128</item>
<item>160</item>
<item>192</item>
<item>256</item>
<item>320</item>
</string-array>
<string-array name="audio_transcode_format_wifi_list_titles">
<item>Odtwarzanie bezpośrednie</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_wifi_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="audio_transcode_format_mobile_list_titles">
<item>Odtwarzanie bezpośrednie</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_mobile_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="audio_transcode_format_download_list_titles">
<item>Pobieranie bezpośrednie</item>
<item>Opus</item>
<item>AAC</item>
<item>Mp3</item>
<item>Flac</item>
</string-array>
<string-array name="audio_transcode_format_download_list_values">
<item>raw</item>
<item>opus</item>
<item>aac</item>
<item>mp3</item>
<item>flac</item>
</string-array>
<string-array name="queue_syncing_countdown_titles">
<item>Dziesięć sekund</item>
<item>Pięć sekund</item>
<item>Dwie sekundy</item>
</string-array>
<string-array name="queue_syncing_countdown_values">
<item>10</item>
<item>5</item>
<item>2</item>
</string-array>
<string-array name="rounded_corner_size_titles">
<item>Duży</item>
<item>Średni</item>
<item>Mały</item>
</string-array>
<string-array name="rounded_corner_size_values">
<item>18</item>
<item>12</item>
<item>6</item>
</string-array>
<string-array name="replay_gain_titles">
<item>Wyłączony</item>
<item>Utwór</item>
<item>Album</item>
<item>Auto</item>
</string-array>
<string-array name="replay_gain_values">
<item>wyłączony</item>
<item>utwór</item>
<item>album</item>
<item>auto</item>
</string-array>
<string-array name="transcoded_download_option_list_titles">
<item>Nie transkoduj</item>
<item>Ustawienia serwera</item>
<item>Format transkodowania Wi-FI</item>
<item>Format transkodowania sieci komórkowej</item>
</string-array>
<string-array name="transcoded_download_option_list_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="buffering_strategy_titles">
<item>Minimalna</item>
<item>Średnia</item>
<item>Agresywna</item>
<item>Ekstremalna</item>
</string-array>
<string-array name="buffering_strategy_values">
<item>.1</item>
<item>1</item>
<item>4</item>
<item>8</item>
</string-array>
<string-array name="skip_min_star_rating_titles">
<item>Minimum 0 gwiazdek</item>
<item>Minimum 1 gwiazdka</item>
<item>Minimum 2 gwiazdki</item>
<item>Minimum 3 gwiazdki</item>
<item>Minimum 4 gwiazdki</item>
</string-array>
<string-array name="skip_min_star_rating_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
</resources>

View file

@ -0,0 +1,418 @@
<resources>
<string name="activity_battery_optimizations_conclusion">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.</string>
<string name="activity_battery_optimizations_summary">Wyłącz optymalizacje baterii aby odtwarzać media przy wyłączonym ekranie.</string>
<string name="activity_battery_optimizations_title">Optymalizcje Baterii</string>
<string name="activity_info_offline_mode">Tryb offline</string>
<string name="album_bottom_sheet_add_to_queue">Dodaj do kolejki</string>
<string name="album_bottom_sheet_download_all">Pobierz wszystkie</string>
<string name="album_bottom_sheet_go_to_artist">Przejdź do wykonawcy</string>
<string name="album_bottom_sheet_instant_mix">Natychmiastowy mix</string>
<string name="album_bottom_sheet_play_next">Odtwórz jako następne</string>
<string name="album_bottom_sheet_remove_all">Usuń wszystkie</string>
<string name="album_bottom_sheet_share">Udostępnij</string>
<string name="album_bottom_sheet_shuffle">Odtwórz losowo</string>
<string name="album_catalogue_title">Albumy</string>
<string name="album_catalogue_title_expanded">Przeglądaj Albumy</string>
<string name="album_error_retrieving_artist">Błąd podczas pobierania wykonawcy</string>
<string name="album_list_page_downloaded">Pobrane albumy</string>
<string name="album_list_page_most_played">Najczęściej odtwarzane albumy</string>
<string name="album_list_page_new_releases">Nowe wydania</string>
<string name="album_list_page_recently_added">Albumy dodane niedawno</string>
<string name="album_list_page_recently_played">Albumy odtwarzane niedawno</string>
<string name="album_list_page_starred">Albumy oznaczone gwiazdką</string>
<string name="album_list_page_title">Albumy</string>
<string name="album_page_extra_info_button">Więcej podobnych</string>
<string name="album_page_play_button">Odtwarzaj</string>
<string name="album_page_release_date_label">Wydane %1$s</string>
<string name="album_page_release_dates_label">Wydane %1$s, oryginalnie %2$s</string>
<string name="album_page_shuffle_button">Odtwarzaj losowo</string>
<string name="album_page_tracks_count_and_duration">%1$d utworów • %2$d minut</string>
<string name="app_name">Tempo</string>
<string name="artist_adapter_radio_station_starting">Szukanie…</string>
<string name="artist_bottom_sheet_instant_mix">Natychmiastowy mix mix</string>
<string name="artist_bottom_sheet_shuffle">Odtwórz losowo</string>
<string name="artist_catalogue_title">Wykonawcy</string>
<string name="artist_catalogue_title_expanded">Przeglądaj wykonawców</string>
<string name="artist_error_retrieving_radio">Błąd podczas pobierania radia wykonawcy</string>
<string name="artist_error_retrieving_tracks">Błąd podczas pobierania utworów wykonawcy</string>
<string name="artist_list_page_downloaded">Pobrani wykonawcy</string>
<string name="artist_list_page_starred">Wykonawcy oznaczeni gwiazdką</string>
<string name="artist_list_page_title">Wykonawcy</string>
<string name="artist_page_radio_button">Radio</string>
<string name="artist_page_shuffle_button">Odtwarzanie losowe</string>
<string name="artist_page_switch_layout_button">Zmień układ</string>
<string name="artist_page_title_album_more_like_this_button">Więcej podobnych</string>
<string name="artist_page_title_album_section">Albumy</string>
<string name="artist_page_title_biography_more_button">Więcej</string>
<string name="artist_page_title_biography_section">Biografia</string>
<string name="artist_page_title_most_streamed_song_section">Najczęsciej słuchane utwory</string>
<string name="artist_page_title_most_streamed_song_see_all_button">Zobacz wszystkie</string>
<string name="battery_optimization_negative_button">Ignoruj</string>
<string name="battery_optimization_neutral_button">Nie pytaj ponownie</string>
<string name="battery_optimization_positive_button">Wyłącz</string>
<string name="connection_alert_dialog_negative_button">Anuluj</string>
<string name="connection_alert_dialog_neutral_button">Włącz oszczędzanie danych</string>
<string name="connection_alert_dialog_positive_button">OK</string>
<string name="connection_alert_dialog_summary">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.</string>
<string name="connection_alert_dialog_title">Nie połączono z Wi-Fi</string>
<string name="content_description_shuffle_button">Odtwarzanie losowe</string>
<string name="delete_download_storage_dialog_negative_button">Anuluj</string>
<string name="delete_download_storage_dialog_positive_button">Kontynuuj</string>
<string name="delete_download_storage_dialog_summary">Miej na uwadze to że kontynuowanie tej operacji spowoduje usunięcie wszystkich pobranych plików z wszystkich serwerów.</string>
<string name="delete_download_storage_dialog_title">Usuwanie zapisanych plików</string>
<string name="description_empty_title">Brak opisu</string>
<string name="disc_titlefull">Płyta %1$s - %2$s</string>
<string name="disc_titleless">Płyta %1$s</string>
<string name="download_directory_dialog_negative_button">Anuluj</string>
<string name="download_directory_dialog_positive_button">Pobierz</string>
<string name="download_directory_dialog_summary">Wszystkie utwory w tym folderze zostaną pobrane. Utwory dostępne w subfolderach nie zostaną pobrane.</string>
<string name="download_directory_dialog_title">Pobierz utwory</string>
<string name="download_info_empty_subtitle">Gdy pobierzesz piosenkę, znajdziesz ją tutaj</string>
<string name="download_info_empty_title">Narazie brak pobranych!</string>
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elementów</string>
<string name="download_item_single_subtitle_formatter">%1$s element</string>
<string name="download_shuffle_all_subtitle">Odtwórz losowo wszystkie</string>
<string name="download_storage_dialog_sub_summary">Aby zmiany przyniosły efekt, zrestartuj aplikację.</string>
<string name="download_storage_dialog_summary">Zmiana lokalizacji pobieranych plików z jednej na drugą spowoduje natychmiastowe usunięcie wcześniej pobranych plików w drugiej lokalizacji</string>
<string name="download_storage_dialog_title">Wybieranie pamięci</string>
<string name="download_storage_external_dialog_positive_button">Zewnętrzna</string>
<string name="download_storage_internal_dialog_negative_button">Wewnętrzna</string>
<string name="download_title_section">Pobrane</string>
<string name="downloaded_bottom_sheet_add_to_queue">Dodaj do kolejki</string>
<string name="downloaded_bottom_sheet_play_next">Odtwarzaj jako następne</string>
<string name="downloaded_bottom_sheet_remove">Usuń</string>
<string name="downloaded_bottom_sheet_remove_all">Usuń wszystkie</string>
<string name="downloaded_bottom_sheet_shuffle">Odtwarzaj losowo</string>
<string name="empty_string" />
<string name="error_required">Wymagane</string>
<string name="error_server_prefix">wymagany jest prefiks http lub https</string>
<string name="exo_download_notification_channel_name">Pobieranie</string>
<string name="filter_info_selection">Wybierz dwa lub więcej filtrów</string>
<string name="filter_title">Filtry</string>
<string name="filter_title_expanded">Filtruj Gatunki</string>
<string name="generic_list_page_count">(%1$d)</string>
<string name="generic_list_page_count_unknown">(+%1$d)</string>
<string name="genre_catalogue_title">Katalog Gatunków</string>
<string name="genre_catalogue_title_expanded">Przeglądaj Gatunki</string>
<string name="github_update_dialog_negative_button">Przypomnij mi później</string>
<string name="github_update_dialog_neutral_button">Wesprzyj mnie</string>
<string name="github_update_dialog_positive_button">Pobierz teraz</string>
<string name="github_update_dialog_summary">Nowa wersja aplikacji jest dostępna na GitHubie.</string>
<string name="github_update_dialog_title">Dostępna aktualizacja</string>
<string name="home_rearrangement_dialog_negative_button">Anuluj</string>
<string name="home_rearrangement_dialog_neutral_button">Reset</string>
<string name="home_rearrangement_dialog_positive_button">Zapisz</string>
<string name="home_rearrangement_dialog_title">Zmień układ strony głównej</string>
<string name="home_rearrangement_dialog_subtitle">Weź pod uwagę to że, żeby zmiany nastąpiły, musisz zrestartować aplikację.</string>
<string name="home_subtitle_best_of">Top piosenki od twoich ulubionych wykonawców</string>
<string name="home_subtitle_made_for_you">Stwórz miks z piosenki którą lubisz</string>
<string name="home_subtitle_new_internet_radio_station">Dodaj nowe radio</string>
<string name="home_subtitle_new_podcast_channel">Dodaj nowy kanał podcastów</string>
<string name="home_sync_starred_cancel">Anuluj</string>
<string name="home_sync_starred_download">Pobierz</string>
<string name="home_sync_starred_subtitle">Pobieranie tych utworów może zużyć dużo danych</string>
<string name="home_sync_starred_title">Wygląda na to że, są utwory oznaczone gwiazdką</string>
<string name="home_title_best_of">Najlepsze</string>
<string name="home_title_discovery">Odkrywanie</string>
<string name="home_title_discovery_shuffle_all_button">Odtwórz wszystkie losowo</string>
<string name="home_title_flashback">Podróż w czasie</string>
<string name="home_title_internet_radio_station">Internetowe stacje radiowe</string>
<string name="home_title_last_played">Ostatnio odtwarzane</string>
<string name="home_title_last_played_see_all_button">Zobacz wszystkie</string>
<string name="home_title_last_week">Ostatni tydzień</string>
<string name="home_title_last_month">Ostatni miesiąc</string>
<string name="home_title_last_year">Ostatni rok</string>
<string name="home_title_made_for_you">Stworzone dla ciebie</string>
<string name="home_title_most_played">Najczęściej odtwarzane</string>
<string name="home_title_most_played_see_all_button">Zobacz wszystkie</string>
<string name="home_title_new_releases">Nowe wydania</string>
<string name="home_title_newest_podcasts">Najnowsze podcasty</string>
<string name="home_title_pinned_playlists">Playlisty</string>
<string name="home_title_podcast_channels">Kanały</string>
<string name="home_title_podcast_channels_see_all_button">Zobacz wszystkie</string>
<string name="home_title_radio_station">Stacje radiowe</string>
<string name="home_title_recently_added">Ostatnio dodane</string>
<string name="home_title_recently_added_see_all_button">Zobacz wszystkie</string>
<string name="home_title_shares">Udostępnienia</string>
<string name="home_title_starred_albums">★ Albumy oznaczone gwiazdką</string>
<string name="home_title_starred_albums_see_all_button">Zobacz wszystkie</string>
<string name="home_title_starred_artists">★ Wykonawcy oznaczeni gwiazdką</string>
<string name="home_title_starred_artists_see_all_button">Zobacz wszystkich</string>
<string name="home_title_starred_tracks">★ Utwory oznaczone gwiazdką</string>
<string name="home_title_starred_tracks_see_all_button">Zobacz wszystkie</string>
<string name="home_title_top_songs">Twoje top piosenki</string>
<string name="home_option_reorganize">Zmiana układu</string>
<string name="label_dot_separator" translatable="false"></string>
<string name="label_placeholder" translatable="false">--</string>
<string name="library_title_album">Albumy</string>
<string name="library_title_album_see_all_button">Zobacz wszystkie</string>
<string name="library_title_artist">Wykonawcy</string>
<string name="library_title_artist_see_all_button">Zobacz wszystkich</string>
<string name="library_title_genre">Gatunki</string>
<string name="library_title_genre_see_all_button">Zobacz wszystkie</string>
<string name="library_title_music_folder">Foldery z muzyką</string>
<string name="library_title_playlist">Playlisty</string>
<string name="library_title_playlist_see_all_button">Zobacz wszystkie</string>
<string name="login_empty">Brak dodanych serwerów</string>
<string name="login_title">Serwery Subsonic</string>
<string name="login_title_expanded">Serwery Subsonic</string>
<string name="media_route_menu_title">Przesyłanie</string>
<string name="menu_add_button">Dodaj</string>
<string name="menu_download_all_button">Pobierz wszystko</string>
<string name="menu_download_label">Pobrane</string>
<string name="menu_filter_all">Wszystko</string>
<string name="menu_filter_download">Pobrane</string>
<string name="menu_group_by_album">Albumy</string>
<string name="menu_group_by_artist">Wykonawcy</string>
<string name="menu_group_by_genre">Gatkunki</string>
<string name="menu_group_by_track">Utwory</string>
<string name="menu_group_by_year">Rok</string>
<string name="menu_home_label">Strona główna</string>
<string name="menu_last_week_name">Ostatni tydzień</string>
<string name="menu_last_month_name">Ostatni miesiąc</string>
<string name="menu_last_year_name">Ostatni rok</string>
<string name="menu_library_label">Biblioteka</string>
<string name="menu_search_button">Szukaj</string>
<string name="menu_settings_button">Ustawienia</string>
<string name="menu_sort_artist">Wykonawca</string>
<string name="menu_sort_name">Nazwa</string>
<string name="menu_sort_random">Losowo</string>
<string name="menu_sort_recently_added">Ostatnio dodane</string>
<string name="menu_sort_recently_played">Ostatnio odtwarzane</string>
<string name="menu_sort_most_played">Najczęściej odtwarzane</string>
<string name="menu_sort_most_recently_starred">Ostatnio oznaczone największą liczbą gwiazdek</string>
<string name="menu_sort_least_recently_starred">Ostatnio oznaczone najniższą liczbą gwiazdek</string>
<string name="menu_pin_button">Dodaj do ekranu głównego</string>
<string name="menu_unpin_button">Usuń z ekranu głównego</string>
<string name="menu_sort_year">Rok</string>
<string name="player_playback_speed">%1$.2fx</string>
<string name="player_queue_clean_all_button">Wyczyść kolejkę odtwarzania</string>
<string name="player_server_priority">Priorytet Serwerów</string>
<string name="playlist_catalogue_title">Katalog Playlist</string>
<string name="playlist_catalogue_title_expanded">Przeglądaj Playlisty</string>
<string name="playlist_chooser_dialog_empty">Nie utworzono playlist</string>
<string name="playlist_chooser_dialog_negative_button">Anuluj</string>
<string name="playlist_chooser_dialog_neutral_button">Utwórz</string>
<string name="playlist_chooser_dialog_title">Dodaj do playlisty</string>
<string name="playlist_counted_tracks">%1$d utworów • %2$s</string>
<string name="playlist_duration">Długość • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Przytrzymaj aby usunąć</string>
<string name="playlist_editor_dialog_hint_name">Nazwa Playlisty</string>
<string name="playlist_editor_dialog_negative_button">Anuluj</string>
<string name="playlist_editor_dialog_neutral_button">Usuń</string>
<string name="playlist_editor_dialog_positive_button">Zapisz</string>
<string name="playlist_editor_dialog_title">Edytuj playlistę</string>
<string name="playlist_page_play_button">Odtwórz</string>
<string name="playlist_page_shuffle_button">Odtwarzaj losowo</string>
<string name="playlist_song_count">Playlista • %1$d piosenek</string>
<string name="podcast_bottom_sheet_add_to_queue">Dodaj do kolejki</string>
<string name="podcast_bottom_sheet_delete">Usuń</string>
<string name="podcast_bottom_sheet_download">Pobierz</string>
<string name="podcast_bottom_sheet_go_to_channel">Przejdź do kanału</string>
<string name="podcast_bottom_sheet_play_next">Odtwórz jako następny</string>
<string name="podcast_bottom_sheet_remove">Usuń</string>
<string name="podcast_channel_catalogue_title">Kanały</string>
<string name="podcast_channel_catalogue_title_expanded">Przeglądaj Kanały</string>
<string name="podcast_channel_editor_dialog_hint_rss_url">Url RSS</string>
<string name="podcast_channel_editor_dialog_title">Kanał Podcastu</string>
<string name="podcast_channel_page_title_description_section">Opis</string>
<string name="podcast_channel_page_title_episode_section">Odcinki</string>
<string name="podcast_channel_page_title_no_episode_available">Brak dostępnych odcinków</string>
<string name="podcast_episode_download_request_snackbar">Twoje zapytanie zostało wysłane do serwera</string>
<string name="podcast_info_empty_button">Naciśnij aby ukryć tę sekcję\nEfekty będą widoczne po restarcie</string>
<string name="podcast_info_empty_subtitle">Gdy dodasz kanał, znajdziesz go tutaj</string>
<string name="podcast_info_empty_title">Nie znaleziono podcastów!</string>
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
<string name="radio_editor_dialog_hint_homepage_url">URL Strony Radia</string>
<string name="radio_editor_dialog_hint_name">Nazwa Radia</string>
<string name="radio_editor_dialog_hint_stream_url">URL Z Strumieniem Radia</string>
<string name="radio_editor_dialog_negative_button">Anuluj</string>
<string name="radio_editor_dialog_neutral_button">Usuń</string>
<string name="radio_editor_dialog_positive_button">Zapisz</string>
<string name="radio_editor_dialog_title">Internetowa Stacja Radiowa</string>
<string name="radio_station_info_empty_button">Naciśnij aby ukryć tę sekcję\nEfekty będą widoczne po restarcie</string>
<string name="radio_station_info_empty_subtitle">Gdy dodasz stację radiową, znajdziesz ją tutaj</string>
<string name="radio_station_info_empty_title">Nie znaleziono stacji!</string>
<string name="rating_dialog_negative_button">Anuluj</string>
<string name="rating_dialog_positive_button">Zapisz</string>
<string name="rating_dialog_title">Oceń</string>
<string name="search_hint">Wyszukaj tytuł, wykonawców lub albumy</string>
<string name="search_info_minimum_characters">Wpisz co najmniej trzy znaki</string>
<string name="search_title_album">Albumy</string>
<string name="search_title_artist">Wykonawcy</string>
<string name="search_title_song">Piosenki</string>
<string name="server_signup_dialog_action_low_security">Niskie bezpieczeństwo</string>
<string name="server_signup_dialog_action_delete_toast">Przytrzymaj aby usunąć</string>
<string name="server_signup_dialog_hint_local_address">Lokalny URL</string>
<string name="server_signup_dialog_hint_name">Nazwa Serwera</string>
<string name="server_signup_dialog_hint_password">Hasło</string>
<string name="server_signup_dialog_hint_url">URL Serwera</string>
<string name="server_signup_dialog_hint_username">Nazwa użytkownika</string>
<string name="server_signup_dialog_negative_button">Anuluj</string>
<string name="server_signup_dialog_neutral_button">Usuń</string>
<string name="server_signup_dialog_positive_button">Zapisz</string>
<string name="server_signup_dialog_title">Dodaj serwer</string>
<string name="server_unreachable_dialog_negative_button">Anuluj</string>
<string name="server_unreachable_dialog_neutral_button">Przejdź do logowania</string>
<string name="server_unreachable_dialog_positive_button">Kontynuuj mimo wszystko</string>
<string name="server_unreachable_dialog_summary">Wybrany serwer jest niedostępny. Jeżeli wybierzesz żeby kontynuować ta informacja nie będzie się wyświetlać przez następną godzinę.</string>
<string name="server_unreachable_dialog_title">Serwer jest niedostępny</string>
<string name="settings_about_summary">Tempo jest otwarto-źródłowym i lekkim klientem muzycznym dla Subsonic, stworzonym i zbudowanym natywnie dla Androida.</string>
<string name="settings_about_title">O aplikacji</string>
<string name="settings_always_on_display">Always on display</string>
<string name="settings_audio_transcode_download_format">Format transkodowania</string>
<string name="settings_audio_transcode_download_priority_summary">Jeżeli włączone, Tempo nie będzię wymuszał pobierania utworu z ustawieniami transkodowania wybranymi poniżej.</string>
<string name="settings_audio_transcode_download_priority_title">Priorytetyzuj ustawienia serwera używanego do strumieniowania w pobieraniach</string>
<string name="settings_audio_transcode_download_summary">Jeżeli włączone, Tempo będzie pobierał transkodowane utwory.</string>
<string name="settings_audio_transcode_download_title">Pobieraj transkodowane utwory</string>
<string name="settings_audio_transcode_estimate_content_length_summary">Jeżeli włączone, serwer bedzię odpytywany o przybliżoną długość utworu.</string>
<string name="settings_audio_transcode_estimate_content_length_title">Szacuj długość treści</string>
<string name="settings_audio_transcode_format_download">Format transkodowania dla pobierania</string>
<string name="settings_audio_transcode_format_mobile">Format transkodowania w sieci komórkowej</string>
<string name="settings_audio_transcode_format_wifi">Format transkodowania w sieci Wi-Fi</string>
<string name="settings_audio_transcode_priority_summary">Jeżeli włączone, Tempo nie będzie wymuszał strumieniowania utworu z ustawieniami transkodowania wybranymi poniżej.</string>
<string name="settings_audio_transcode_priority_title">Priorytetyzuj ustawienia transkodowania serwera</string>
<string name="settings_audio_transcode_priority_toast">Priorytet przy transkodowaniu utworu danego serwerowi</string>
<string name="settings_buffering_strategy">Strategia buforowania</string>
<string name="settings_buffering_strategy_summary">Aby zmiany przyniosły efekt, musisz ręcznie zrestartować aplikację.</string>
<string name="settings_continuous_play_summary">Pozwala muzyce odtwarzać się dalej po końcu playlisty, odtwarza podobne piosenki</string>
<string name="settings_continuous_play_title">Odtwarzanie bez przerwy</string>
<string name="settings_covers_cache">Rozmiar cache dla okładek</string>
<string name="settings_data_saving_mode_summary">Aby zmniejszyć zużycie danych, unikaj pobierania okładek.</string>
<string name="settings_data_saving_mode_title">Ogranicz zużycie danych komórkowych</string>
<string name="settings_delete_download_storage_summary">Zatwierdzenie nieodwracalnie usunie wszystkie zapisane elementy</string>
<string name="settings_delete_download_storage_title">Usuń zapisane elementy</string>
<string name="settings_download_storage_title">Pamięć do pobierania</string>
<string name="settings_equalizer_summary">Zmień ustawienia audio</string>
<string name="settings_equalizer_title">Equalizer</string>
<string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Śledź tworzenie aplikacji</string>
<string name="settings_github_title">GitHub</string>
<string name="settings_image_size">Rozdzielczość obrazów</string>
<string name="settings_language">Język</string>
<string name="settings_logout_title">Wyloguj</string>
<string name="settings_max_bitrate_download">Bitrate dla pobierania</string>
<string name="settings_max_bitrate_mobile">Bitrate dla danych komórkowych</string>
<string name="settings_max_bitrate_wifi">Bitrate dla Wi-Fi</string>
<string name="settings_media_cache">Rozmiar plików cache dla mediów</string>
<string name="settings_music_directory">Pokaż foldery z muzyką</string>
<string name="settings_music_directory_summary">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ę.</string>
<string name="settings_podcast">Pokazuj podcasty</string>
<string name="settings_podcast_summary">Jeżeli włączone, widoczna będzie sekcja z podcastami. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.</string>
<string name="settings_audio_quality">Pokaż jakość audio</string>
<string name="settings_audio_quality_summary">Bitrate i format audio będzie pokazywany dla każdego utworu.</string>
<string name="settings_item_rating">Pokaż oceny elementów</string>
<string name="settings_item_rating_summary">Jeżeli włączone, ocena elementów oraz czy jest oznaczony jako ulubiony będą pokazywane.</string>
<string name="settings_queue_syncing_countdown">Timer synchronizacji</string>
<string name="settings_queue_syncing_summary">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.</string>
<string name="settings_queue_syncing_title">Synchronizuj kolejkę odtwarzania dla tego użytkownika</string>
<string name="settings_radio">Pokaż radio</string>
<string name="settings_radio_summary">Jeżeli włączone, widoczna będzie sekcja radia. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.</string>
<string name="settings_replay_gain">Tryb wzmocnienia głośności przy ponownym odtwarzaniu</string>
<string name="settings_rounded_corner">Zaokrąglone rogi</string>
<string name="settings_rounded_corner_size">Rozmiar rogów</string>
<string name="settings_rounded_corner_size_summary">Ustawia wielkość krzywizny kąta.</string>
<string name="settings_rounded_corner_summary">Jeżeli włączone, ustawia kąt krzywizny dla wszystkich renderowanych okładek. Zmiany przyniosą efekt po restarcie.</string>
<string name="settings_scan_title">Skanuj bibliotekę</string>
<string name="settings_scrobble_title">Włącz scrobbling muzyki</string>
<string name="settings_share_title">Włącz udostępnianie muzyki</string>
<string name="settings_streaming_cache_size">Rozmiar cache dla strumieniowania</string>
<string name="settings_streaming_cache_storage_title">Pamięć cache dla strumieniowania</string>
<string name="settings_sub_summary_scrobble">Ważne jest to że scrobbling polega też na byciu włączonym na serwerze aby otrzymywać te dane.</string>
<string name="settings_summary_skip_min_star_rating">Podczas słuchania radia wykonawcy, natychmiastowego miksu albo podczas odtwarzania losowego, utwory poniżej określonej oceny użytkownika będą ignorowane.</string>
<string name="settings_summary_replay_gain">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.</string>
<string name="settings_summary_scrobble">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.</string>
<string name="settings_summary_share">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.</string>
<string name="settings_summary_syncing">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ę.</string>
<string name="settings_summary_streaming_cache_size">%1$s \nAktualnie w użyciu: %2$s MiB</string>
<string name="settings_summary_transcoding">Priorytet dawany trybowi transkodowania. Jeżeli ustawiony na \"Odtwarzanie bezpośrednie\" bitrate pliku nie zostanie zmieniony.</string>
<string name="settings_summary_transcoding_download">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.</string>
<string name="settings_summary_transcoding_estimate_content_length">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.</string>
<string name="settings_sync_starred_tracks_for_offline_use_summary">Jeżeli włączone, utwory oznaczone gwiazdką będą pobrane do użycia offline.</string>
<string name="settings_sync_starred_tracks_for_offline_use_title">Zsynchronizuj utwory oznaczone gwiazdką do użycia offline</string>
<string name="settings_theme">Motyw</string>
<string name="settings_title_data">Dane</string>
<string name="settings_title_general">Ogólne</string>
<string name="settings_title_rating">Oceny</string>
<string name="settings_title_replay_gain">Wzmocnienie głośności przy ponownym odtwarzaniu</string>
<string name="settings_title_scrobble">Scrobble</string>
<string name="settings_title_skip_min_star_rating">Ignoruj utwory na podstawie oceny</string>
<string name="settings_title_skip_min_star_rating_dialog">Piosenki z oceną:</string>
<string name="settings_title_share">Udostępnianie</string>
<string name="settings_title_syncing">Synchronizacja</string>
<string name="settings_title_transcoding">Transkodowanie</string>
<string name="settings_title_transcoding_download">Transkodowanie Pobrań</string>
<string name="settings_title_ui">Interfejs</string>
<string name="settings_transcoded_download">Transkodowane pobieranie</string>
<string name="settings_version_summary" translatable="false">3.1.0</string>
<string name="settings_version_title">Wersja</string>
<string name="settings_wifi_only_summary">Pytaj o potwierdzenie od użytkownika przed strumieniowaniem przez sieć komórkową.</string>
<string name="settings_wifi_only_title">Alert o strumieniowaniu tylko przez Wi-Fi</string>
<string name="share_bottom_sheet_copy_link">Kopiuj link</string>
<string name="share_bottom_sheet_delete">Usuń udostępnianie</string>
<string name="share_bottom_sheet_update">Zaktualizuj udostępnianie</string>
<string name="share_subtitle_item">Data wygaśnięcia: %1$s</string>
<string name="share_unsupported_error">Udostępnianie nie jest wspierane lub włączone</string>
<string name="share_update_dialog_hint_description">Opis</string>
<string name="share_update_dialog_hint_expiration_date">Data wygaśnięcia</string>
<string name="share_update_dialog_negative_button">Anuluj</string>
<string name="share_update_dialog_positive_button">Zapisz</string>
<string name="share_update_dialog_title">Udostępnij</string>
<string name="song_bottom_sheet_add_to_playlist">Dodaj do playlisty</string>
<string name="song_bottom_sheet_add_to_queue">Dodaj do kolejki</string>
<string name="song_bottom_sheet_download">Pobierz</string>
<string name="song_bottom_sheet_error_retrieving_album">Błąd podczas pobierania albumu</string>
<string name="song_bottom_sheet_error_retrieving_artist">Błąd podczas pobierania wykonawcy</string>
<string name="song_bottom_sheet_go_to_album">Przejdź do albumu</string>
<string name="song_bottom_sheet_go_to_artist">Przejdź do wykonawcy</string>
<string name="song_bottom_sheet_instant_mix">Natychmiastowy miks</string>
<string name="song_bottom_sheet_play_next">Odtwarzaj jako następne</string>
<string name="song_bottom_sheet_rate">Oceń</string>
<string name="song_bottom_sheet_remove">Usuń</string>
<string name="song_bottom_sheet_share">Udostępnij</string>
<string name="song_list_page_downloaded">Pobrane</string>
<string name="song_list_page_most_played">Najczęściej odtwarzane utwory</string>
<string name="song_list_page_recently_added">Utwory dodane ostatnio</string>
<string name="song_list_page_recently_played">Utwory odtwarzane ostatnio</string>
<string name="song_list_page_starred">Utwory oznaczone gwiazdką</string>
<string name="song_list_page_top">%1$s\ top utwory</string>
<string name="song_list_page_year">Rok %1$d</string>
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
<string name="starred_sync_dialog_negative_button">Anuluj</string>
<string name="starred_sync_dialog_neutral_button">Kontynuuj</string>
<string name="starred_sync_dialog_positive_button">Kontynuuj i pobierz</string>
<string name="starred_sync_dialog_summary">Pobieranie utworów oznaczonych gwiazdką może wymagać dużej ilośći danych.</string>
<string name="starred_sync_dialog_title">Synchronizuj utwory oznaczone gwiazdką</string>
<string name="streaming_cache_storage_dialog_sub_summary">Aby zmiany przyniosły efekt, zrestartuj aplikację.</string>
<string name="streaming_cache_storage_dialog_summary">Zmiana lokalizacji plików cache z jednej na drugą spowoduje natychmiastowe usunięcie wcześniej pobranych plików cache w drugiej lokalizacji.</string>
<string name="streaming_cache_storage_dialog_title">Wybieranie pamięci</string>
<string name="streaming_cache_storage_external_dialog_positive_button">Zewnętrzna</string>
<string name="streaming_cache_storage_internal_dialog_negative_button">Wewnętrzna</string>
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
<string name="track_info_album">Album</string>
<string name="track_info_artist">Wykonawca</string>
<string name="track_info_bitrate">Bitrate</string>
<string name="track_info_content_type">Typ Treści</string>
<string name="track_info_dialog_positive_button">OK</string>
<string name="track_info_dialog_title">Informacje o utworze</string>
<string name="track_info_disc_number">Numer płyty</string>
<string name="track_info_duration">Długość</string>
<string name="track_info_genre">Gatunek</string>
<string name="track_info_path">Ścieżka</string>
<string name="track_info_size">Rozmiar</string>
<string name="track_info_suffix">Sufiks</string>
<string name="track_info_summary_downloaded_file">Plik został pobrany przy użyciu API Subsonic. Kodek i bitrate pliku pozostaje nie zmieniony względem pliku źródłowego.</string>
<string name="track_info_summary_full_transcode">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.</string>
<string name="track_info_summary_original_file">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.</string>
<string name="track_info_summary_server_prioritized">\Jakość odtwarzanego pliku zależy od decyzji serwera. Aplikacja nie będzie monitorować wyboru kodeku i bitrateu dla jakiegokolwiek potencjalnego transkodowania.</string>
<string name="track_info_summary_transcoding_bitrate">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.</string>
<string name="track_info_summary_transcoding_codec">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</string>
<string name="track_info_title">Tytuł</string>
<string name="track_info_track_number">Numer utworu</string>
<string name="track_info_transcoded_content_type">Typ transkodowanej treści</string>
<string name="track_info_transcoded_suffix">Sufiks transkodowania</string>
<string name="track_info_year">Rok</string>
<string name="undraw_page">unDraw</string>
<string name="undraw_thanks">Specjalne podziękowania dla unDraw bez którego ilustracji nie mogliśmy uczynić tej aplikacji jeszcze piękniejszą.</string>
<string name="undraw_url">https://undraw.co/</string>
</resources>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">Por favor, desative as otimizações de bateria para a reprodução de mídia enquanto a tela estiver desligada.</string> <string name="activity_battery_optimizations_summary">Por favor, desative as otimizações de bateria para a reprodução de mídia enquanto a tela estiver desligada.</string>
<string name="activity_battery_optimizations_title">Otimizações de bateria</string> <string name="activity_battery_optimizations_title">Otimizações de bateria</string>
<string name="activity_info_offline_mode">Modo offline</string> <string name="activity_info_offline_mode">Modo offline</string>
<string name="album_bottom_sheet_add_to_playlist">Adicionar a uma playlist</string>
<string name="album_bottom_sheet_add_to_queue">Adicionar à fila</string> <string name="album_bottom_sheet_add_to_queue">Adicionar à fila</string>
<string name="album_bottom_sheet_download_all">Baixar todos</string> <string name="album_bottom_sheet_download_all">Baixar todos</string>
<string name="album_bottom_sheet_go_to_artist">Ir para o(a) artista</string> <string name="album_bottom_sheet_go_to_artist">Ir para o(a) artista</string>
@ -134,6 +135,7 @@
<string name="login_title_expanded">Servidores Subsonic</string> <string name="login_title_expanded">Servidores Subsonic</string>
<string name="media_route_menu_title">Transmitir</string> <string name="media_route_menu_title">Transmitir</string>
<string name="menu_add_button">Adicionar</string> <string name="menu_add_button">Adicionar</string>
<string name="menu_add_to_playlist_button">Adicionar a uma playlist</string>
<string name="menu_download_all_button">Baixar todos</string> <string name="menu_download_all_button">Baixar todos</string>
<string name="menu_download_label">Download</string> <string name="menu_download_label">Download</string>
<string name="menu_filter_all">Todos</string> <string name="menu_filter_all">Todos</string>
@ -160,6 +162,8 @@
<string name="playlist_chooser_dialog_negative_button">Cancelar</string> <string name="playlist_chooser_dialog_negative_button">Cancelar</string>
<string name="playlist_chooser_dialog_neutral_button">Criar</string> <string name="playlist_chooser_dialog_neutral_button">Criar</string>
<string name="playlist_chooser_dialog_title">Adicionar a uma playlist</string> <string name="playlist_chooser_dialog_title">Adicionar a uma playlist</string>
<string name="playlist_chooser_dialog_toast_add_success">Adicionada playlist de reprodução</string>
<string name="playlist_chooser_dialog_toast_add_failure">Falha ao adicionar uma playlist de reprodução</string>
<string name="playlist_counted_tracks">%1$d faixas • %2$s</string> <string name="playlist_counted_tracks">%1$d faixas • %2$s</string>
<string name="playlist_duration">Duração • %1$s</string> <string name="playlist_duration">Duração • %1$s</string>
<string name="playlist_editor_dialog_hint_name">Nome da playlist</string> <string name="playlist_editor_dialog_hint_name">Nome da playlist</string>
@ -246,7 +250,7 @@
<string name="settings_download_storage_title">Armazenamento dos downloads</string> <string name="settings_download_storage_title">Armazenamento dos downloads</string>
<string name="settings_equalizer_summary">Ajustar configurações de áudio</string> <string name="settings_equalizer_summary">Ajustar configurações de áudio</string>
<string name="settings_equalizer_title">Equalizador</string> <string name="settings_equalizer_title">Equalizador</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Acompanhe o desenvolvimento</string> <string name="settings_github_summary">Acompanhe o desenvolvimento</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Definir resolução da imagem</string> <string name="settings_image_size">Definir resolução da imagem</string>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране.</string> <string name="activity_battery_optimizations_summary">Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране.</string>
<string name="activity_battery_optimizations_title">Оптимизация батареи</string> <string name="activity_battery_optimizations_title">Оптимизация батареи</string>
<string name="activity_info_offline_mode">Офлайн-режим</string> <string name="activity_info_offline_mode">Офлайн-режим</string>
<string name="album_bottom_sheet_add_to_playlist">Добавить в плейлист</string>
<string name="album_bottom_sheet_add_to_queue">Добавить в очередь</string> <string name="album_bottom_sheet_add_to_queue">Добавить в очередь</string>
<string name="album_bottom_sheet_download_all">Скачать все</string> <string name="album_bottom_sheet_download_all">Скачать все</string>
<string name="album_bottom_sheet_go_to_artist">Перейти к исполнителю</string> <string name="album_bottom_sheet_go_to_artist">Перейти к исполнителю</string>
@ -153,6 +154,7 @@
<string name="login_title_expanded">Subsonic серверы</string> <string name="login_title_expanded">Subsonic серверы</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">Добавить</string> <string name="menu_add_button">Добавить</string>
<string name="menu_add_to_playlist_button">Добавить в плейлист</string>
<string name="menu_download_all_button">Скачать все</string> <string name="menu_download_all_button">Скачать все</string>
<string name="menu_download_label">Скачать</string> <string name="menu_download_label">Скачать</string>
<string name="menu_filter_all">Все</string> <string name="menu_filter_all">Все</string>
@ -186,6 +188,8 @@
<string name="playlist_chooser_dialog_negative_button">Отмена</string> <string name="playlist_chooser_dialog_negative_button">Отмена</string>
<string name="playlist_chooser_dialog_neutral_button">Создать</string> <string name="playlist_chooser_dialog_neutral_button">Создать</string>
<string name="playlist_chooser_dialog_title">Добавить в плейлист</string> <string name="playlist_chooser_dialog_title">Добавить в плейлист</string>
<string name="playlist_chooser_dialog_toast_add_success">Добавьте песню в плейлист</string>
<string name="playlist_chooser_dialog_toast_add_failure">Не удалось добавить песню в список воспроизведения</string>
<string name="playlist_counted_tracks">%1$d треков • %2$s</string> <string name="playlist_counted_tracks">%1$d треков • %2$s</string>
<string name="playlist_duration">Продолжительность • %1$s</string> <string name="playlist_duration">Продолжительность • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Долгое нажатие для удаления</string> <string name="playlist_editor_dialog_action_delete_toast">Долгое нажатие для удаления</string>
@ -275,7 +279,7 @@
<string name="settings_download_storage_title">Загрузить хранилище</string> <string name="settings_download_storage_title">Загрузить хранилище</string>
<string name="settings_equalizer_summary">Отрегулируйте настройки звука</string> <string name="settings_equalizer_summary">Отрегулируйте настройки звука</string>
<string name="settings_equalizer_title">Эквалайзер</string> <string name="settings_equalizer_title">Эквалайзер</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Следите за развитием</string> <string name="settings_github_summary">Следите за развитием</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Установить разрешение изображения</string> <string name="settings_image_size">Установить разрешение изображения</string>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">请禁用针对媒体锁屏播放的电池优化。</string> <string name="activity_battery_optimizations_summary">请禁用针对媒体锁屏播放的电池优化。</string>
<string name="activity_battery_optimizations_title">电池优化</string> <string name="activity_battery_optimizations_title">电池优化</string>
<string name="activity_info_offline_mode">离线模式</string> <string name="activity_info_offline_mode">离线模式</string>
<string name="album_bottom_sheet_add_to_playlist">添加到播放列表</string>
<string name="album_bottom_sheet_add_to_queue">添加到队列</string> <string name="album_bottom_sheet_add_to_queue">添加到队列</string>
<string name="album_bottom_sheet_download_all">全部下载</string> <string name="album_bottom_sheet_download_all">全部下载</string>
<string name="album_bottom_sheet_go_to_artist">查看该艺术家</string> <string name="album_bottom_sheet_go_to_artist">查看该艺术家</string>
@ -137,6 +138,7 @@
<string name="login_title_expanded">Subsonic 服务器</string> <string name="login_title_expanded">Subsonic 服务器</string>
<string name="media_route_menu_title">投送</string> <string name="media_route_menu_title">投送</string>
<string name="menu_add_button">添加</string> <string name="menu_add_button">添加</string>
<string name="menu_add_to_playlist_button">添加到播放列表</string>
<string name="menu_download_all_button">全部下载</string> <string name="menu_download_all_button">全部下载</string>
<string name="menu_download_label">下载</string> <string name="menu_download_label">下载</string>
<string name="menu_filter_all">全部</string> <string name="menu_filter_all">全部</string>
@ -165,6 +167,8 @@
<string name="playlist_chooser_dialog_negative_button">取消</string> <string name="playlist_chooser_dialog_negative_button">取消</string>
<string name="playlist_chooser_dialog_neutral_button">新建</string> <string name="playlist_chooser_dialog_neutral_button">新建</string>
<string name="playlist_chooser_dialog_title">添加到播放列表</string> <string name="playlist_chooser_dialog_title">添加到播放列表</string>
<string name="playlist_chooser_dialog_toast_add_success">将歌曲添加到播放列表</string>
<string name="playlist_chooser_dialog_toast_add_failure">未能将歌曲添加到播放列表</string>
<string name="playlist_counted_tracks">%1$d 首曲目 • %2$s</string> <string name="playlist_counted_tracks">%1$d 首曲目 • %2$s</string>
<string name="playlist_duration">持续时间 • %1$s</string> <string name="playlist_duration">持续时间 • %1$s</string>
<string name="playlist_editor_dialog_hint_name">播放列表名称</string> <string name="playlist_editor_dialog_hint_name">播放列表名称</string>
@ -253,7 +257,7 @@
<string name="settings_download_storage_title">下载存储</string> <string name="settings_download_storage_title">下载存储</string>
<string name="settings_equalizer_summary">调整音频设置</string> <string name="settings_equalizer_summary">调整音频设置</string>
<string name="settings_equalizer_title">均衡器</string> <string name="settings_equalizer_title">均衡器</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">关注开发进展</string> <string name="settings_github_summary">关注开发进展</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">设置图像分辨率</string> <string name="settings_image_size">设置图像分辨率</string>

View file

@ -3,6 +3,7 @@
<string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string> <string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string>
<string name="activity_battery_optimizations_title">Battery Optimizations</string> <string name="activity_battery_optimizations_title">Battery Optimizations</string>
<string name="activity_info_offline_mode">Offline mode</string> <string name="activity_info_offline_mode">Offline mode</string>
<string name="album_bottom_sheet_add_to_playlist">Add to playlist</string>
<string name="album_bottom_sheet_add_to_queue">Add to queue</string> <string name="album_bottom_sheet_add_to_queue">Add to queue</string>
<string name="album_bottom_sheet_download_all">Download all</string> <string name="album_bottom_sheet_download_all">Download all</string>
<string name="album_bottom_sheet_go_to_artist">Go to artist</string> <string name="album_bottom_sheet_go_to_artist">Go to artist</string>
@ -158,6 +159,7 @@
<string name="login_title_expanded">Subsonic servers</string> <string name="login_title_expanded">Subsonic servers</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="menu_add_button">Add</string> <string name="menu_add_button">Add</string>
<string name="menu_add_to_playlist_button">Add to playlist</string>
<string name="menu_download_all_button">Download all</string> <string name="menu_download_all_button">Download all</string>
<string name="menu_download_label">Download</string> <string name="menu_download_label">Download</string>
<string name="menu_filter_all">All</string> <string name="menu_filter_all">All</string>
@ -194,6 +196,8 @@
<string name="playlist_chooser_dialog_negative_button">Cancel</string> <string name="playlist_chooser_dialog_negative_button">Cancel</string>
<string name="playlist_chooser_dialog_neutral_button">Create</string> <string name="playlist_chooser_dialog_neutral_button">Create</string>
<string name="playlist_chooser_dialog_title">Add to a playlist</string> <string name="playlist_chooser_dialog_title">Add to a playlist</string>
<string name="playlist_chooser_dialog_toast_add_success">Added song to playlist</string>
<string name="playlist_chooser_dialog_toast_add_failure">Failed to add song to playlist</string>
<string name="playlist_counted_tracks">%1$d tracks • %2$s</string> <string name="playlist_counted_tracks">%1$d tracks • %2$s</string>
<string name="playlist_duration">Duration • %1$s</string> <string name="playlist_duration">Duration • %1$s</string>
<string name="playlist_editor_dialog_action_delete_toast">Long press to delete</string> <string name="playlist_editor_dialog_action_delete_toast">Long press to delete</string>
@ -285,7 +289,7 @@
<string name="settings_download_storage_title">Download storage</string> <string name="settings_download_storage_title">Download storage</string>
<string name="settings_equalizer_summary">Adjust audio settings</string> <string name="settings_equalizer_summary">Adjust audio settings</string>
<string name="settings_equalizer_title">Equalizer</string> <string name="settings_equalizer_title">Equalizer</string>
<string name="settings_github_link">https://github.com/CappielloAntonio/tempo</string> <string name="settings_github_link">https://github.com/eddyizm/tempo</string>
<string name="settings_github_summary">Follow the development</string> <string name="settings_github_summary">Follow the development</string>
<string name="settings_github_title">Github</string> <string name="settings_github_title">Github</string>
<string name="settings_image_size">Set image resolution</string> <string name="settings_image_size">Set image resolution</string>

View file

@ -9,4 +9,5 @@
<locale android:name="it-IT"/> <!-- Italian --> <locale android:name="it-IT"/> <!-- Italian -->
<locale android:name="ru-RU"/> <!-- Russian --> <locale android:name="ru-RU"/> <!-- Russian -->
<locale android:name="es-ES"/> <!-- Spanish (Spain) --> <locale android:name="es-ES"/> <!-- Spanish (Spain) -->
<locale android:name="pl-PL"/> <!-- Polish -->
</locale-config> </locale-config>

View file

@ -32,7 +32,8 @@ class MediaService : MediaLibraryService() {
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var customCommands: List<CommandButton> private lateinit var shuffleCommands: List<CommandButton>
private lateinit var repeatCommands: List<CommandButton>
private var customLayout = ImmutableList.of<CommandButton>() private var customLayout = ImmutableList.of<CommandButton>()
@ -41,6 +42,12 @@ class MediaService : MediaLibraryService() {
"android.media3.session.demo.SHUFFLE_ON" "android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_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() { override fun onCreate() {
@ -72,7 +79,7 @@ class MediaService : MediaLibraryService() {
val connectionResult = super.onConnect(session, controller) val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
customCommands.forEach { commandButton -> shuffleCommands.forEach { commandButton ->
// TODO: Aggiungere i comandi personalizzati // TODO: Aggiungere i comandi personalizzati
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) } // commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
} }
@ -89,22 +96,40 @@ class MediaService : MediaLibraryService() {
} }
} }
fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
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( override fun onCustomCommand(
session: MediaSession, session: MediaSession,
controller: ControllerInfo, controller: ControllerInfo,
customCommand: SessionCommand, customCommand: SessionCommand,
args: Bundle args: Bundle
): ListenableFuture<SessionResult> { ): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { when (customCommand.customAction) {
player.shuffleModeEnabled = true CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> player.shuffleModeEnabled = true
customLayout = ImmutableList.of(customCommands[1]) CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> player.shuffleModeEnabled = false
session.setCustomLayout(customLayout) CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
player.shuffleModeEnabled = false CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
customLayout = ImmutableList.of(customCommands[0]) val nextMode = when (player.repeatMode) {
session.setCustomLayout(customLayout) 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)) return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} }
@ -125,17 +150,28 @@ class MediaService : MediaLibraryService() {
} }
private fun initializeCustomCommands() { private fun initializeCustomCommands() {
customCommands = shuffleCommands = listOf(
listOf( getShuffleCommandButton(
getShuffleCommandButton( SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY) ),
), getShuffleCommandButton(
getShuffleCommandButton( SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
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() { private fun initializePlayer() {
@ -147,6 +183,9 @@ class MediaService : MediaLibraryService() {
.setWakeMode(C.WAKE_MODE_NETWORK) .setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl()) .setLoadControl(initializeLoadControl())
.build() .build()
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
player.repeatMode = Preferences.getRepeatMode()
} }
private fun initializeMediaLibrarySession() { 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() .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<SessionResult>) { private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
/* Do nothing. */ /* Do nothing. */
} }

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult import androidx.media3.session.LibraryResult
@ -27,7 +28,7 @@ open class MediaLibrarySessionCallback(
MediaBrowserTree.initialize(automotiveRepository) MediaBrowserTree.initialize(automotiveRepository)
} }
private val customLayoutCommandButtons: List<CommandButton> = listOf( private val shuffleCommandButtons: List<CommandButton> = listOf(
CommandButton.Builder() CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description)) .setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand( .setSessionCommand(
@ -45,15 +46,46 @@ open class MediaLibrarySessionCallback(
).setIconResId(R.drawable.exo_icon_shuffle_on).build() ).setIconResId(R.drawable.exo_icon_shuffle_on).build()
) )
private val repeatCommandButtons: List<CommandButton> = 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<CommandButton> =
shuffleCommandButtons + repeatCommandButtons
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
val mediaNotificationSessionCommands = val mediaNotificationSessionCommands =
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder -> .also { builder ->
customLayoutCommandButtons.forEach { commandButton -> (shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) } commandButton.sessionCommand?.let { builder.add(it) }
} }
}.build() }.build()
fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
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) @OptIn(UnstableApi::class)
override fun onConnect( override fun onConnect(
session: MediaSession, controller: MediaSession.ControllerInfo session: MediaSession, controller: MediaSession.ControllerInfo
@ -62,12 +94,11 @@ open class MediaLibrarySessionCallback(
controller controller
) || session.isAutoCompanionController(controller) ) || session.isAutoCompanionController(controller)
) { ) {
val customLayout = val customLayout = buildCustomLayout(session.player)
customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
return MediaSession.ConnectionResult.AcceptedResultBuilder(session) return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands) .setAvailableSessionCommands(mediaNotificationSessionCommands)
.setCustomLayout(ImmutableList.of(customLayout)).build() .setCustomLayout(customLayout).build()
} }
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
@ -80,25 +111,28 @@ open class MediaLibrarySessionCallback(
customCommand: SessionCommand, customCommand: SessionCommand,
args: Bundle args: Bundle
): ListenableFuture<SessionResult> { ): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { when (customCommand.customAction) {
session.player.shuffleModeEnabled = true CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
session.setCustomLayout( CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
session.mediaNotificationControllerInfo!!, CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
ImmutableList.of(customLayoutCommandButtons[1]) CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
) CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
val nextMode = when (session.player.repeatMode) {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
session.player.shuffleModeEnabled = false else -> Player.REPEAT_MODE_OFF
session.setCustomLayout( }
session.mediaNotificationControllerInfo!!, session.player.repeatMode = nextMode
ImmutableList.of(customLayoutCommandButtons[0]) }
) else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} }
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( override fun onGetLibraryRoot(
@ -158,5 +192,11 @@ open class MediaLibrarySessionCallback(
"android.media3.session.demo.SHUFFLE_ON" "android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_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"
} }
} }

View file

@ -33,6 +33,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -79,6 +80,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
.setWakeMode(C.WAKE_MODE_NETWORK) .setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl()) .setLoadControl(initializeLoadControl())
.build() .build()
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
player.repeatMode = Preferences.getRepeatMode()
} }
private fun initializeCastPlayer() { private fun initializeCastPlayer() {
@ -97,13 +101,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT) getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
} }
librarySessionCallback = createLibrarySessionCallback()
mediaLibrarySession = mediaLibrarySession =
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback()) MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent) .setSessionActivity(sessionActivityPendingIntent)
.build() .build()
} }
private fun createLibrarySessionCallback(): MediaLibrarySession.Callback { private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
return MediaLibrarySessionCallback(this, automotiveRepository) 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)
)
}
}) })
} }

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton import androidx.media3.session.CommandButton
import androidx.media3.session.LibraryResult import androidx.media3.session.LibraryResult
@ -27,7 +28,7 @@ open class MediaLibrarySessionCallback(
MediaBrowserTree.initialize(automotiveRepository) MediaBrowserTree.initialize(automotiveRepository)
} }
private val customLayoutCommandButtons: List<CommandButton> = listOf( private val shuffleCommandButtons: List<CommandButton> = listOf(
CommandButton.Builder() CommandButton.Builder()
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description)) .setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
.setSessionCommand( .setSessionCommand(
@ -45,15 +46,46 @@ open class MediaLibrarySessionCallback(
).setIconResId(R.drawable.exo_icon_shuffle_on).build() ).setIconResId(R.drawable.exo_icon_shuffle_on).build()
) )
private val repeatCommandButtons: List<CommandButton> = 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<CommandButton> =
shuffleCommandButtons + repeatCommandButtons
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
val mediaNotificationSessionCommands = val mediaNotificationSessionCommands =
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder -> .also { builder ->
customLayoutCommandButtons.forEach { commandButton -> (shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) } commandButton.sessionCommand?.let { builder.add(it) }
} }
}.build() }.build()
fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
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) @OptIn(UnstableApi::class)
override fun onConnect( override fun onConnect(
session: MediaSession, controller: MediaSession.ControllerInfo session: MediaSession, controller: MediaSession.ControllerInfo
@ -62,12 +94,11 @@ open class MediaLibrarySessionCallback(
controller controller
) || session.isAutoCompanionController(controller) ) || session.isAutoCompanionController(controller)
) { ) {
val customLayout = val customLayout = buildCustomLayout(session.player)
customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
return MediaSession.ConnectionResult.AcceptedResultBuilder(session) return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands) .setAvailableSessionCommands(mediaNotificationSessionCommands)
.setCustomLayout(ImmutableList.of(customLayout)).build() .setCustomLayout(customLayout).build()
} }
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
@ -80,25 +111,28 @@ open class MediaLibrarySessionCallback(
customCommand: SessionCommand, customCommand: SessionCommand,
args: Bundle args: Bundle
): ListenableFuture<SessionResult> { ): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { when (customCommand.customAction) {
session.player.shuffleModeEnabled = true CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
session.setCustomLayout( CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
session.mediaNotificationControllerInfo!!, CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
ImmutableList.of(customLayoutCommandButtons[1]) CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
) CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
val nextMode = when (session.player.repeatMode) {
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
session.player.shuffleModeEnabled = false else -> Player.REPEAT_MODE_OFF
session.setCustomLayout( }
session.mediaNotificationControllerInfo!!, session.player.repeatMode = nextMode
ImmutableList.of(customLayoutCommandButtons[0]) }
) else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} }
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( override fun onGetLibraryRoot(
@ -158,5 +192,11 @@ open class MediaLibrarySessionCallback(
"android.media3.session.demo.SHUFFLE_ON" "android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_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"
} }
} }

View file

@ -33,6 +33,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private lateinit var player: ExoPlayer private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -79,6 +80,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
.setWakeMode(C.WAKE_MODE_NETWORK) .setWakeMode(C.WAKE_MODE_NETWORK)
.setLoadControl(initializeLoadControl()) .setLoadControl(initializeLoadControl())
.build() .build()
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
player.repeatMode = Preferences.getRepeatMode()
} }
private fun initializeCastPlayer() { private fun initializeCastPlayer() {
@ -97,13 +101,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT) getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
} }
librarySessionCallback = createLibrarySessionCallback()
mediaLibrarySession = mediaLibrarySession =
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback()) MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent) .setSessionActivity(sessionActivityPendingIntent)
.build() .build()
} }
private fun createLibrarySessionCallback(): MediaLibrarySession.Callback { private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
return MediaLibrarySessionCallback(this, automotiveRepository) 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)
)
}
}) })
} }

0
gradlew vendored Normal file → Executable file
View file

View file

@ -1,2 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
include ':app' include ':app'
rootProject.name = "Tempo" rootProject.name = "Tempo"