Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe2c2b28b | ||
|
|
eb29dc2fb2 | ||
|
|
287e4a2b10 | ||
|
|
b7d56c2d70 | ||
|
|
5a6d101bdf | ||
|
|
969f0b5b21 | ||
|
|
14939d20fd | ||
|
|
2e29e9537a | ||
|
|
bc0adfe8e0 | ||
|
|
5261ca317b | ||
|
|
bf4ff3f1f9 | ||
|
|
cd195dbba0 | ||
|
|
7ec78991a5 | ||
|
|
e1c5a60805 | ||
|
|
f74813ef69 | ||
|
|
040558198e | ||
|
|
5ab68e4a98 | ||
|
|
aa8fac43a6 | ||
|
|
905bb3e3c5 | ||
|
|
d810010090 | ||
|
|
52ba783a90 | ||
|
|
d72855e160 | ||
|
|
82a9f00173 | ||
|
|
3f5749f7e1 | ||
|
|
cd2ab36351 | ||
|
|
a6688f897a | ||
|
|
64658dda1f | ||
|
|
d9f701d9d3 | ||
|
|
60fee3c77c | ||
|
|
b89086c5be | ||
|
|
d3dd236054 | ||
|
|
2e3330b63f | ||
|
|
e604c9ba86 | ||
|
|
2bf39d846e | ||
|
|
06066f1f66 | ||
|
|
7c0d44680f | ||
|
|
d4cb6c5c9a | ||
|
|
fab18c130e | ||
|
|
bd753f4489 | ||
|
|
e43a2b6fe5 | ||
|
|
c62d2ace4d | ||
|
|
6d403f808c | ||
|
|
b0ddd5388b | ||
|
|
92f79a8e3d | ||
|
|
473d7e4e9c | ||
|
|
7ca0415274 | ||
|
|
cf7feacdc0 | ||
|
|
4740028a44 | ||
|
|
fe2c163aaa | ||
|
|
c6f08d9cec | ||
|
|
59b40df9ef | ||
|
|
81726baa08 | ||
|
|
af92a7b11a | ||
|
|
0578745bee | ||
|
|
e24063e460 | ||
|
|
4f1b1b603e | ||
|
|
7279c62944 | ||
|
|
a59d46f884 | ||
|
|
10285b308d | ||
|
|
8e2c5d1fee | ||
|
|
f59a360eb7 | ||
|
|
accf5fddc2 | ||
|
|
cc61d1cd48 | ||
|
|
6a16159cf0 | ||
|
|
eaf2710054 | ||
|
|
31d91f7215 | ||
|
|
f854f49686 | ||
|
|
9e8870a86a | ||
|
|
a0040c52a0 | ||
|
|
65f6347faf | ||
|
|
85fa2f768e | ||
|
|
cc5abd150a | ||
|
|
1ed6ac6cff | ||
|
|
cc6cb077b4 | ||
|
|
4be0acf76c | ||
|
|
7d843390db | ||
|
|
5c5316055c | ||
|
|
f1a179e7f8 | ||
|
|
0377c5e939 | ||
|
|
614ce8b466 | ||
|
|
9e87b53bc9 | ||
|
|
08023026b4 | ||
|
|
1bbcf6c790 | ||
|
|
698ca3b22b | ||
|
|
b2d875ac98 | ||
|
|
02eef97171 | ||
|
|
26a5fb029a | ||
|
|
c38c7c3deb | ||
|
|
8ed0a4642b | ||
|
|
6cfa04d368 | ||
|
|
af98f8d1a9 | ||
|
|
469204daac | ||
|
|
8943faf44c | ||
|
|
fea6366d84 | ||
|
|
92ac2e5684 | ||
|
|
06a52afa18 | ||
|
|
87f6db9e79 | ||
|
|
5fa46cc49b | ||
|
|
4da967910a | ||
|
|
3b18f39948 | ||
|
|
c9e0581815 | ||
|
|
b0fcc31f7b | ||
|
|
e98c9483c8 | ||
|
|
98a45b6059 | ||
|
|
6e070dfef0 | ||
|
|
910cce90f5 | ||
|
|
1a70ccd8f4 | ||
|
|
7830657fe1 | ||
|
|
0351ccfc95 | ||
|
|
10af6fb4ce | ||
|
|
61ec15e696 | ||
|
|
d21bd475a1 | ||
|
|
7e34f6ee64 | ||
|
|
7cfefe76cc | ||
|
|
4585533740 | ||
|
|
07c1760c39 | ||
|
|
24d4e67872 | ||
|
|
bec840620c | ||
|
|
2fa4ddf874 | ||
|
|
e16f88cb73 | ||
|
|
f79b05cb67 | ||
|
|
ed7c572578 | ||
|
|
e891214831 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -14,4 +14,7 @@
|
||||
.cxx
|
||||
/.idea/
|
||||
.env
|
||||
.vscode/settings.json
|
||||
.vscode/settings.json
|
||||
# release / debug files
|
||||
tempus-release-key.jks
|
||||
app/tempo/
|
||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -2,6 +2,51 @@
|
||||
|
||||
***This log is for this fork to detail updates since 3.9.0 from the main repo.***
|
||||
|
||||
## [3.14.1](https://github.com/eddyizm/tempo/releases/tag/v3.14.1) (2025-08-30)
|
||||
## What's Changed
|
||||
* feat: rating dialog added to album page by @eddyizm in https://github.com/eddyizm/tempo/pull/52
|
||||
* style: Add song rating bar in landscape player controller layout by @jaime-grj in https://github.com/eddyizm/tempo/pull/57
|
||||
* feat: setting to show/hide 5 star rating on playerview by @eddyizm in https://github.com/eddyizm/tempo/pull/59
|
||||
* chore: setting-to-hide-song-rating by @eddyizm in https://github.com/eddyizm/tempo/pull/60
|
||||
* fix: catches null value and prepares bundle appropriately adding sing… by @eddyizm in https://github.com/eddyizm/tempo/pull/64
|
||||
* fix: artist filtering in library view browse artist resolves #45 by @eddyizm in https://github.com/eddyizm/tempo/pull/69
|
||||
* chore: Update French localization by @benoit-smith in https://github.com/eddyizm/tempo/pull/70
|
||||
* feat: adds sync starred albums functionality #66 by @eddyizm in https://github.com/eddyizm/tempo/pull/73
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempo/compare/v3.13.0...v3.14.1
|
||||
|
||||
## [3.13.0](https://github.com/eddyizm/tempo/releases/tag/v3.13.0) (2025-08-23)
|
||||
## What's Changed
|
||||
* style: Change position and size of rating container by @jaime-grj in https://github.com/eddyizm/tempo/pull/44
|
||||
* feat: Add Turkish localization (values-tr) by @mucahit-kaya in https://github.com/eddyizm/tempo/pull/50
|
||||
* chore: adding a note/not fully baked label to the sync user play queue setting by @eddyizm in https://github.com/eddyizm/tempo/commit/8ed0a4642bd0cd637c65e3115142596331fa7ef7
|
||||
* fix: moved hardcoded italian save text to string template, updated with english and italian language xmls by @eddyizm in https://github.com/eddyizm/tempo/commit/26a5fb029a07752c9c0db0d08a89afd638772579
|
||||
|
||||
|
||||
## New Contributors
|
||||
* @mucahit-kaya made their first contribution in https://github.com/eddyizm/tempo/pull/50
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempo/compare/v3.12.0...v3.13.0
|
||||
|
||||
## [3.12.0](https://github.com/eddyizm/tempo/releases/tag/v3.12.0) (2025-08-15)
|
||||
### What's Changed
|
||||
* [chore]: add German translations for track info and home section strings (#29) by @BreadWare92 in https://github.com/eddyizm/tempo/pull/31
|
||||
* [chore]: increased "Offline mode" text size, changed its color in dark theme by @jaime-grj in https://github.com/eddyizm/tempo/pull/33
|
||||
* [chore]: Translations for sections by @skajmer in https://github.com/eddyizm/tempo/pull/30
|
||||
* [chore]: Update French localization by @benoit-smith in https://github.com/eddyizm/tempo/pull/36
|
||||
* [fix]: Show placeholder string in TrackInfoDialog fields when there is no data by @jaime-grj in https://github.com/eddyizm/tempo/pull/37
|
||||
* [feat]: added transcoding codec and bitrate info to PlayerControllerFragment, replace hardcoded strings by @jaime-grj in https://github.com/eddyizm/tempo/pull/38
|
||||
* [chore]: Update French localization by @benoit-smith in https://github.com/eddyizm/tempo/pull/39
|
||||
* [feat]: show rating on song view by @eddyizm in https://github.com/eddyizm/tempo/pull/40
|
||||
|
||||
### New Contributors
|
||||
* @BreadWare92 made their first contribution in https://github.com/eddyizm/tempo/pull/31
|
||||
* @skajmer made their first contribution in https://github.com/eddyizm/tempo/pull/30
|
||||
* @benoit-smith made their first contribution in https://github.com/eddyizm/tempo/pull/36
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempo/compare/v3.11.2...v3.12.0
|
||||
|
||||
## [3.11.2](https://github.com/eddyizm/tempo/releases/tag/v3.11.2) (2025-08-09)
|
||||
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -28,6 +28,12 @@ This fork is my attempt to keep development moving forward and merge in PR's tha
|
||||
|
||||
Moved details to [CHANGELOG.md](https://github.com/eddyizm/tempo/blob/main/CHANGELOG.md)
|
||||
|
||||
Fork [**sponsorship here**](https://ko-fi.com/eddyizm).
|
||||
|
||||
## Usage
|
||||
|
||||
[Documentation](USAGE.md) (work in progress)
|
||||
|
||||
## Features
|
||||
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
|
||||
- **Sleek and Intuitive UI**: Enjoy a clean and user-friendly interface designed to enhance your music listening experience, tailored to your preferences and listening history.
|
||||
@@ -41,21 +47,11 @@ Moved details to [CHANGELOG.md](https://github.com/eddyizm/tempo/blob/main/CHANG
|
||||
- **Transcoding Support**: Activate transcoding of tracks on your Subsonic server, allowing you to set a transcoding profile for optimized streaming directly from the app. This feature requires support from your Subsonic server.
|
||||
- **Android Auto Support**: Enjoy your favorite music on the go with full Android Auto integration, allowing you to seamlessly control and listen to your tracks directly from your mobile device while driving.
|
||||
|
||||
<p align="center">
|
||||
<img src="mockup/feat/1_screenshot.png" width=200>
|
||||
<img src="mockup/feat/2_screenshot.png" width=200>
|
||||
<img src="mockup/feat/3_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/6_screenshot.png" width=200>
|
||||
<img src="mockup/feat/7_screenshot.png" width=200>
|
||||
<img src="mockup/feat/8_screenshot.png" width=200>
|
||||
</p>
|
||||
|
||||
## Sponsors
|
||||
Thanks to the original repo/creator [CappielloAntonio](https://github.com/CappielloAntonio) (3.9.0)
|
||||
|
||||
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.
|
||||
|
||||
If you would like to sponsor the project and show your support, you can make a donation or contribution by visiting the [**sponsorship page**](https://www.buymeacoffee.com/a.cappiello). Your generosity will help cover the costs of development and further enhancements.
|
||||
|
||||
## Screenshot
|
||||
|
||||
|
||||
146
USAGE.md
Normal file
146
USAGE.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Tempo Usage Guide
|
||||
[<- back home](README.md)
|
||||
|
||||
## Table of Contents
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Server Configuration](#server-configuration)
|
||||
- [Main Features](#main-features)
|
||||
- [Navigation](#navigation)
|
||||
- [Playback Controls](#playback-controls)
|
||||
- [Favorites](#favorites)
|
||||
- [Playlist Management](#playlist-management)
|
||||
- [Android Auto](#android-auto)
|
||||
- [Settings](#settings)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Important Notice**: This app is a Subsonic-compatible client and does not provide any music content itself. To use this application, you must have:
|
||||
|
||||
- An active Subsonic API server (or compatible service) already set up
|
||||
- Valid login credentials for your Subsonic server
|
||||
- Music content uploaded and organized on your server
|
||||
|
||||
### Verified backends
|
||||
This app works with any service that implements the Subsonic API, including:
|
||||
- [LMS - Lightweight Music Server](https://github.com/epoupon/lms) - *personal fave and my backend*
|
||||
- [Navidrome](https://www.navidrome.org/)
|
||||
- [Gonic](https://github.com/sentriz/gonic)
|
||||
|
||||
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
1. Download the APK from the [Releases](https://github.com/eddyizm/tempo/releases) section
|
||||
2. Enable "Install from unknown sources" in your Android settings
|
||||
3. Install the application
|
||||
|
||||
### First Launch
|
||||
1. Open the application
|
||||
2. You will be prompted to configure your server connection
|
||||
3. Grant necessary permissions for media playback and background operation
|
||||
|
||||
## Server Configuration
|
||||
|
||||
### Initial Setup
|
||||
**IN PROGRESS**
|
||||
1. Enter your server URL (e.g., `https://your-subsonic-server.com`)
|
||||
2. Provide your username and password
|
||||
3. Test the connection to ensure proper configuration
|
||||
|
||||
### Advanced Settings
|
||||
**TODO**
|
||||
|
||||
## Main Features
|
||||
|
||||
### Library View
|
||||
**TODO**
|
||||
|
||||
### Now Playing Screen
|
||||
**TODO**
|
||||
|
||||
## Navigation
|
||||
|
||||
### Bottom Navigation Bar
|
||||
**IN PROGRESS**
|
||||
- **Home**: Recently played and server recommendations
|
||||
- **Library**: Your server's complete music collection
|
||||
- **Download**: Locally downloaded files from server
|
||||
|
||||
## Playback Controls
|
||||
|
||||
### Streaming Controls
|
||||
**TODO**
|
||||
|
||||
### Advanced Controls
|
||||
**TODO**
|
||||
|
||||
## Favorites
|
||||
|
||||
### Favorites (aka heart aka star) to albums and artists
|
||||
- Long pressing on an album gives you access to heart/unheart an album
|
||||
|
||||
<p align="center">
|
||||
<img src="mockup/usage/fave_album.png" width=376>
|
||||
</p>
|
||||
|
||||
- Long pressing on an artist cover gets you the same access to to heart/unheart an album
|
||||
|
||||
<p align="center">
|
||||
<img src="mockup/usage/fave_artist.png" width=376>
|
||||
</p>
|
||||
|
||||
|
||||
## Playlist Management
|
||||
|
||||
### Server Playlists
|
||||
**TODO**
|
||||
|
||||
### Creating Playlists
|
||||
**TODO**
|
||||
|
||||
## Settings
|
||||
|
||||
|
||||
## Android Auto
|
||||
|
||||
### Enabling on your head unit
|
||||
- You have to enable Android Auto developer options, which are different from actual Android dev options. Then you have to enable "Unknown sources" in Android Auto, otherwise the app won't appear as it isn't downloaded from Play Store. (screenshots needed)
|
||||
|
||||
|
||||
### Server Settings
|
||||
**IN PROGRESS**
|
||||
- Manage multiple server connections
|
||||
- Configure sync intervals
|
||||
- Set data usage limits for streaming
|
||||
|
||||
### Audio Settings
|
||||
**IN PROGRESS**
|
||||
- Streaming quality settings
|
||||
- Offline caching preferences
|
||||
|
||||
### Appearance
|
||||
**TODO**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
|
||||
**TODO**
|
||||
|
||||
### Common Issues
|
||||
|
||||
**TODO**
|
||||
|
||||
### Support
|
||||
For additional help:
|
||||
- Question? Start a [Discussion](https://github.com/eddyizm/tempo/discussions)
|
||||
- Open an [issue](https://github.com/eddyizm/tempo/issues) if you don't find a discussion solving your issue.
|
||||
- Consult your Subsonic server's documentation
|
||||
|
||||
---
|
||||
|
||||
*Note: This app requires a pre-existing Subsonic-compatible server with music content.*
|
||||
@@ -10,9 +10,8 @@ android {
|
||||
minSdkVersion 24
|
||||
targetSdk 35
|
||||
|
||||
versionCode 27
|
||||
versionName '3.11.2'
|
||||
|
||||
versionCode 32
|
||||
versionName '3.15.0'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
@@ -23,8 +22,21 @@ android {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
//noinspection ChromeOsAbiSupport
|
||||
include 'armeabi-v7a', 'arm64-v8a'
|
||||
universalApk false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
flavorDimensions += "default"
|
||||
|
||||
productFlavors {
|
||||
@@ -50,6 +62,12 @@ android {
|
||||
minifyEnabled true
|
||||
debuggable false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
universalApk true
|
||||
}
|
||||
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
debuggable true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.media.audiofx.Equalizer
|
||||
|
||||
class EqualizerManager {
|
||||
|
||||
private var equalizer: Equalizer? = null
|
||||
|
||||
fun attachToSession(audioSessionId: Int): Boolean {
|
||||
release()
|
||||
if (audioSessionId != 0 && audioSessionId != -1) {
|
||||
try {
|
||||
equalizer = Equalizer(0, audioSessionId).apply {
|
||||
enabled = true
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
// Some devices may not support Equalizer or audio session may be invalid
|
||||
equalizer = null
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun setBandLevel(band: Short, level: Short) {
|
||||
equalizer?.setBandLevel(band, level)
|
||||
}
|
||||
|
||||
fun getNumberOfBands(): Short = equalizer?.numberOfBands ?: 0
|
||||
|
||||
fun getBandLevelRange(): ShortArray? = equalizer?.bandLevelRange
|
||||
|
||||
fun getCenterFreq(band: Short): Int? =
|
||||
equalizer?.getCenterFreq(band)?.div(1000)
|
||||
|
||||
fun getBandLevel(band: Short): Short? =
|
||||
equalizer?.getBandLevel(band)
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
equalizer?.enabled = enabled
|
||||
}
|
||||
|
||||
fun release() {
|
||||
equalizer?.release()
|
||||
equalizer = null
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
@@ -21,14 +27,79 @@ import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MediaManager {
|
||||
private static final String TAG = "MediaManager";
|
||||
private static WeakReference<MediaBrowser> attachedBrowserRef = new WeakReference<>(null);
|
||||
|
||||
public static void registerPlaybackObserver(
|
||||
ListenableFuture<MediaBrowser> browserFuture,
|
||||
PlaybackViewModel playbackViewModel
|
||||
) {
|
||||
if (browserFuture == null) return;
|
||||
|
||||
Futures.addCallback(browserFuture, new FutureCallback<MediaBrowser>() {
|
||||
@Override
|
||||
public void onSuccess(MediaBrowser browser) {
|
||||
MediaBrowser current = attachedBrowserRef.get();
|
||||
if (current != browser) {
|
||||
browser.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onEvents(@NonNull Player player, @NonNull Player.Events events) {
|
||||
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)
|
||||
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
|
||||
|| events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
||||
|
||||
String mediaId = player.getCurrentMediaItem() != null
|
||||
? player.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
|
||||
boolean playing = player.getPlaybackState() == Player.STATE_READY
|
||||
&& player.getPlayWhenReady();
|
||||
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String mediaId = browser.getCurrentMediaItem() != null
|
||||
? browser.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady();
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
|
||||
attachedBrowserRef = new WeakReference<>(browser);
|
||||
} else {
|
||||
String mediaId = browser.getCurrentMediaItem() != null
|
||||
? browser.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady();
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Throwable t) {
|
||||
Log.e(TAG, "Failed to get MediaBrowser instance", t);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
public static void onBrowserReleased(@Nullable MediaBrowser released) {
|
||||
MediaBrowser attached = attachedBrowserRef.get();
|
||||
if (attached == released) {
|
||||
attachedBrowserRef.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
@@ -107,11 +178,24 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(media));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().seekTo(startIndex, 0);
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
browser.clearMediaItems();
|
||||
browser.setMediaItems(MappingUtil.mapMediaItems(media));
|
||||
browser.prepare();
|
||||
|
||||
Player.Listener timelineListener = new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
int itemCount = browser.getMediaItemCount();
|
||||
if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) {
|
||||
browser.seekTo(startIndex, 0);
|
||||
browser.play();
|
||||
browser.removeListener(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
browser.addListener(timelineListener);
|
||||
|
||||
enqueueDatabase(media, true, 0);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
|
||||
@@ -316,6 +316,7 @@ public class MainActivity extends BaseActivity {
|
||||
Preferences.setSkipSilenceMode(false);
|
||||
Preferences.setDataSavingMode(false);
|
||||
Preferences.setStarredSyncEnabled(false);
|
||||
Preferences.setStarredAlbumsSyncEnabled(false);
|
||||
}
|
||||
|
||||
private void resetMusicSession() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -23,17 +24,24 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
|
||||
private static final String TAG = "PlayerSongQueueAdapter";
|
||||
private final ClickCallback click;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private List<Child> songs;
|
||||
|
||||
private String currentPlayingId;
|
||||
private boolean isPlaying;
|
||||
private List<Integer> currentPlayingPositions = Collections.emptyList();
|
||||
|
||||
public PlayerSongQueueAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.songs = Collections.emptyList();
|
||||
@@ -104,6 +112,46 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
} else {
|
||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
int pos = holder.getBindingAdapterPosition();
|
||||
Child s = songs.get(pos);
|
||||
if (currentPlayingId != null && currentPlayingId.equals(s.getId())) {
|
||||
if (isPlaying) {
|
||||
mediaBrowser.pause();
|
||||
} else {
|
||||
mediaBrowser.play();
|
||||
}
|
||||
} else {
|
||||
mediaBrowser.seekTo(pos, 0);
|
||||
mediaBrowser.play();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error obtaining MediaBrowser", e);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
|
||||
});
|
||||
bindPlaybackState(holder, song);
|
||||
}
|
||||
|
||||
private void bindPlaybackState(@NonNull PlayerSongQueueAdapter.ViewHolder holder, @NonNull Child song) {
|
||||
boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId());
|
||||
|
||||
if (isCurrent) {
|
||||
holder.item.playPauseIcon.setVisibility(View.VISIBLE);
|
||||
if (isPlaying) {
|
||||
holder.item.playPauseIcon.setImageResource(R.drawable.ic_pause);
|
||||
} else {
|
||||
holder.item.playPauseIcon.setImageResource(R.drawable.ic_play);
|
||||
}
|
||||
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
|
||||
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Child> getItems() {
|
||||
@@ -132,6 +180,46 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
|
||||
}
|
||||
|
||||
public void setPlaybackState(String mediaId, boolean playing) {
|
||||
String oldId = this.currentPlayingId;
|
||||
boolean oldPlaying = this.isPlaying;
|
||||
List<Integer> oldPositions = currentPlayingPositions;
|
||||
|
||||
this.currentPlayingId = mediaId;
|
||||
this.isPlaying = playing;
|
||||
|
||||
if (Objects.equals(oldId, mediaId) && oldPlaying == playing) {
|
||||
List<Integer> newPositionsCheck = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList();
|
||||
if (oldPositions.equals(newPositionsCheck)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentPlayingPositions = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList();
|
||||
|
||||
for (int pos : oldPositions) {
|
||||
if (pos >= 0 && pos < songs.size()) {
|
||||
notifyItemChanged(pos, "payload_playback");
|
||||
}
|
||||
}
|
||||
for (int pos : currentPlayingPositions) {
|
||||
if (!oldPositions.contains(pos) && pos >= 0 && pos < songs.size()) {
|
||||
notifyItemChanged(pos, "payload_playback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> findPositionsById(String id) {
|
||||
if (id == null) return Collections.emptyList();
|
||||
List<Integer> positions = new ArrayList<>();
|
||||
for (int i = 0; i < songs.size(); i++) {
|
||||
if (id.equals(songs.get(i).getId())) {
|
||||
positions.add(i);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
public Child getItem(int id) {
|
||||
return songs.get(id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.cappielloantonio.tempo.ui.adapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -10,6 +12,7 @@ import android.widget.Filterable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
@@ -23,6 +26,7 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -30,6 +34,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@UnstableApi
|
||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable {
|
||||
@@ -42,6 +47,11 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
private List<Child> songs;
|
||||
private String currentFilter;
|
||||
|
||||
private String currentPlayingId;
|
||||
private boolean isPlaying;
|
||||
private List<Integer> currentPlayingPositions = Collections.emptyList();
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
@@ -70,6 +80,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
songs = (List<Child>) results.values;
|
||||
notifyDataSetChanged();
|
||||
|
||||
for (int pos : currentPlayingPositions) {
|
||||
if (pos >= 0 && pos < songs.size()) {
|
||||
notifyItemChanged(pos, "payload_playback");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,6 +97,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
this.songsFull = Collections.emptyList();
|
||||
this.currentFilter = "";
|
||||
this.album = album;
|
||||
setHasStableIds(false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -91,7 +108,16 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
|
||||
if (!payloads.isEmpty() && payloads.contains("payload_playback")) {
|
||||
bindPlaybackState(holder, songs.get(position));
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position, payloads);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.searchResultSongTitleTextView.setText(song.getTitle());
|
||||
@@ -165,6 +191,33 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
} else {
|
||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
bindPlaybackState(holder, song);
|
||||
}
|
||||
|
||||
private void bindPlaybackState(@NonNull ViewHolder holder, @NonNull Child song) {
|
||||
boolean isCurrent = currentPlayingId != null && currentPlayingId.equals(song.getId());
|
||||
|
||||
if (isCurrent) {
|
||||
holder.item.playPauseIcon.setVisibility(View.VISIBLE);
|
||||
if (isPlaying) {
|
||||
holder.item.playPauseIcon.setImageResource(R.drawable.ic_pause);
|
||||
} else {
|
||||
holder.item.playPauseIcon.setImageResource(R.drawable.ic_play);
|
||||
}
|
||||
if (!showCoverArt) {
|
||||
holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
holder.item.coverArtOverlay.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
holder.item.playPauseIcon.setVisibility(View.INVISIBLE);
|
||||
if (!showCoverArt) {
|
||||
holder.item.trackNumberTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -188,6 +241,46 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPlaybackState(String mediaId, boolean playing) {
|
||||
String oldId = this.currentPlayingId;
|
||||
boolean oldPlaying = this.isPlaying;
|
||||
List<Integer> oldPositions = currentPlayingPositions;
|
||||
|
||||
this.currentPlayingId = mediaId;
|
||||
this.isPlaying = playing;
|
||||
|
||||
if (Objects.equals(oldId, mediaId) && oldPlaying == playing) {
|
||||
List<Integer> newPositionsCheck = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList();
|
||||
if (oldPositions.equals(newPositionsCheck)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
currentPlayingPositions = mediaId != null ? findPositionsById(mediaId) : Collections.emptyList();
|
||||
|
||||
for (int pos : oldPositions) {
|
||||
if (pos >= 0 && pos < songs.size()) {
|
||||
notifyItemChanged(pos, "payload_playback");
|
||||
}
|
||||
}
|
||||
for (int pos : currentPlayingPositions) {
|
||||
if (!oldPositions.contains(pos) && pos >= 0 && pos < songs.size()) {
|
||||
notifyItemChanged(pos, "payload_playback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> findPositionsById(String id) {
|
||||
if (id == null) return Collections.emptyList();
|
||||
List<Integer> positions = new ArrayList<>();
|
||||
for (int i = 0; i < songs.size(); i++) {
|
||||
if (id.equals(songs.get(i).getId())) {
|
||||
positions.add(i);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return filtering;
|
||||
@@ -215,11 +308,29 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
int pos = getBindingAdapterPosition();
|
||||
Child tappedSong = songs.get(pos);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
||||
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
|
||||
|
||||
click.onMediaClick(bundle);
|
||||
if (tappedSong.getId().equals(currentPlayingId)) {
|
||||
Log.i("SongHorizontalAdapter", "Tapping on currently playing song, toggling playback");
|
||||
try{
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
Log.i("SongHorizontalAdapter", "MediaBrowser retrieved, isPlaying: " + isPlaying);
|
||||
if (isPlaying) {
|
||||
mediaBrowser.pause();
|
||||
} else {
|
||||
mediaBrowser.play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e("SongHorizontalAdapter", "Error getting MediaBrowser", e);
|
||||
}
|
||||
} else {
|
||||
click.onMediaClick(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
@@ -247,4 +358,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setMediaBrowserListenableFuture(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.dialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -97,8 +98,12 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
|
||||
@Override
|
||||
public void onPlaylistClick(Bundle bundle) {
|
||||
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
|
||||
playlistChooserViewModel.addSongsToPlaylist(playlist.getId());
|
||||
dismiss();
|
||||
if (playlistChooserViewModel.getSongsToAdd() != null && !playlistChooserViewModel.getSongsToAdd().isEmpty()) {
|
||||
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
|
||||
playlistChooserViewModel.addSongsToPlaylist(playlist.getId());
|
||||
dismiss();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), R.string.playlist_chooser_dialog_toast_add_failure, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,11 @@ public class RatingDialog extends DialogFragment {
|
||||
bind.ratingBar.setRating(song.getUserRating() != null ? song.getUserRating() : 0);
|
||||
});
|
||||
} else if (ratingViewModel.getAlbum() != null) {
|
||||
ratingViewModel.getLiveAlbum().observe(this, album -> bind.ratingBar.setRating(/*album.getRating()*/ 0));
|
||||
ratingViewModel.getLiveAlbum().observe(this, album -> {
|
||||
if (album != null) {
|
||||
bind.ratingBar.setRating(album.getUserRating() != null ? album.getUserRating() : 0);
|
||||
}
|
||||
});
|
||||
} else if (ratingViewModel.getArtist() != null) {
|
||||
ratingViewModel.getLiveArtist().observe(this, artist -> bind.ratingBar.setRating(/*artist.getRating()*/ 0));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogStarredAlbumSyncBinding;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.StarredAlbumsSyncViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class StarredAlbumSyncDialog extends DialogFragment {
|
||||
private StarredAlbumsSyncViewModel starredAlbumsSyncViewModel;
|
||||
|
||||
private Runnable onCancel;
|
||||
|
||||
public StarredAlbumSyncDialog(Runnable onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
DialogStarredAlbumSyncBinding bind = DialogStarredAlbumSyncBinding.inflate(getLayoutInflater());
|
||||
|
||||
starredAlbumsSyncViewModel = new ViewModelProvider(requireActivity()).get(StarredAlbumsSyncViewModel.class);
|
||||
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.starred_album_sync_dialog_title)
|
||||
.setPositiveButton(R.string.starred_sync_dialog_positive_button, null)
|
||||
.setNeutralButton(R.string.starred_sync_dialog_neutral_button, null)
|
||||
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction(requireContext());
|
||||
}
|
||||
|
||||
private void setButtonAction(Context context) {
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
starredAlbumsSyncViewModel.getStarredAlbumSongs(requireActivity()).observe(this, allSongs -> {
|
||||
if (allSongs != null && !allSongs.isEmpty()) {
|
||||
DownloadUtil.getDownloadTracker(context).download(
|
||||
MappingUtil.mapDownloads(allSongs),
|
||||
allSongs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
dialog.dismiss();
|
||||
});
|
||||
});
|
||||
|
||||
Button neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL);
|
||||
neutralButton.setOnClickListener(v -> {
|
||||
Preferences.setStarredAlbumsSyncEnabled(true);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
Preferences.setStarredAlbumsSyncEnabled(false);
|
||||
if (onCancel != null) onCancel.run();
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,12 @@ import java.util.stream.Collectors;
|
||||
public class StarredSyncDialog extends DialogFragment {
|
||||
private StarredSyncViewModel starredSyncViewModel;
|
||||
|
||||
private Runnable onCancel;
|
||||
|
||||
public StarredSyncDialog(Runnable onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
@@ -75,6 +81,7 @@ public class StarredSyncDialog extends DialogFragment {
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
Preferences.setStarredSyncEnabled(false);
|
||||
if (onCancel != null) onCancel.run();
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TrackInfoDialog extends DialogFragment {
|
||||
private DialogTrackInfoBinding bind;
|
||||
|
||||
@@ -51,7 +53,12 @@ public class TrackInfoDialog extends DialogFragment {
|
||||
|
||||
private void setTrackInfo() {
|
||||
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
|
||||
bind.trakArtistInfoTextView.setText(mediaMetadata.artist);
|
||||
bind.trakArtistInfoTextView.setText(
|
||||
mediaMetadata.artist != null
|
||||
? mediaMetadata.artist
|
||||
: mediaMetadata.extras != null && Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO)
|
||||
? mediaMetadata.extras.getString("uri", getString(R.string.label_placeholder))
|
||||
: "");
|
||||
|
||||
if (mediaMetadata.extras != null) {
|
||||
CustomGlideRequest.Builder
|
||||
@@ -62,20 +69,20 @@ public class TrackInfoDialog extends DialogFragment {
|
||||
bind.titleValueSector.setText(mediaMetadata.extras.getString("title", getString(R.string.label_placeholder)));
|
||||
bind.albumValueSector.setText(mediaMetadata.extras.getString("album", getString(R.string.label_placeholder)));
|
||||
bind.artistValueSector.setText(mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder)));
|
||||
bind.trackNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("track", 0)));
|
||||
bind.yearValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("year", 0)));
|
||||
bind.trackNumberValueSector.setText(mediaMetadata.extras.getInt("track", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("track", 0)) : getString(R.string.label_placeholder));
|
||||
bind.yearValueSector.setText(mediaMetadata.extras.getInt("year", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("year", 0)) : getString(R.string.label_placeholder));
|
||||
bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder)));
|
||||
bind.sizeValueSector.setText(MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)));
|
||||
bind.sizeValueSector.setText(mediaMetadata.extras.getLong("size", 0) != 0 ? MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)) : getString(R.string.label_placeholder));
|
||||
bind.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
|
||||
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", getString(R.string.label_placeholder)));
|
||||
bind.transcodedContentTypeValueSector.setText(mediaMetadata.extras.getString("transcodedContentType", getString(R.string.label_placeholder)));
|
||||
bind.transcodedSuffixValueSector.setText(mediaMetadata.extras.getString("transcodedSuffix", getString(R.string.label_placeholder)));
|
||||
bind.durationValueSector.setText(MusicUtil.getReadableDurationString(mediaMetadata.extras.getInt("duration", 0), false));
|
||||
bind.bitrateValueSector.setText(mediaMetadata.extras.getInt("bitrate", 0) + " kbps");
|
||||
bind.durationValueSector.setText(mediaMetadata.extras.getInt("duration", 0) != 0 ? MusicUtil.getReadableDurationString(mediaMetadata.extras.getInt("duration", 0), false) : getString(R.string.label_placeholder));
|
||||
bind.bitrateValueSector.setText(mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + " kbps" : getString(R.string.label_placeholder));
|
||||
bind.samplingRateValueSector.setText(mediaMetadata.extras.getInt("samplingRate", 0) != 0 ? mediaMetadata.extras.getInt("samplingRate", 0) + " Hz" : getString(R.string.label_placeholder));
|
||||
bind.bitDepthValueSector.setText(mediaMetadata.extras.getInt("bitDepth", 0) != 0 ? mediaMetadata.extras.getInt("bitDepth", 0) + " bits" : getString(R.string.label_placeholder));
|
||||
bind.pathValueSector.setText(mediaMetadata.extras.getString("path", getString(R.string.label_placeholder)));
|
||||
bind.discNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)));
|
||||
bind.discNumberValueSector.setText(mediaMetadata.extras.getInt("discNumber", 0) != 0 ? String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)) : getString(R.string.label_placeholder));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SearchView;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -145,7 +145,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.toolbar_menu, menu);
|
||||
inflater.inflate(R.menu.artist_list_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -27,16 +28,19 @@ import com.cappielloantonio.tempo.databinding.FragmentAlbumPageBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -49,6 +53,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentAlbumPageBinding bind;
|
||||
private MainActivity activity;
|
||||
private AlbumPageViewModel albumPageViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@@ -71,6 +76,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentAlbumPageBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
@@ -88,6 +94,14 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
super.onStart();
|
||||
|
||||
initializeMediaBrowser();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,6 +118,16 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_rate_album) {
|
||||
Bundle bundle = new Bundle();
|
||||
AlbumID3 album = albumPageViewModel.getAlbum().getValue();
|
||||
bundle.putParcelable(Constants.ALBUM_OBJECT, (Parcelable) album);
|
||||
RatingDialog dialog = new RatingDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.action_download_album) {
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(MappingUtil.mapDownloads(songs), songs.stream().map(Download::new).collect(Collectors.toList()));
|
||||
@@ -258,8 +282,13 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
setMediaBrowserListenableFuture();
|
||||
reapplyPlayback();
|
||||
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
reapplyPlayback();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -282,4 +311,31 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -24,6 +25,8 @@ import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentArtistCatalogueBinding;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
|
||||
@@ -32,6 +35,10 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.ArtistCatalogueViewModel;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
@@ -125,23 +132,50 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
|
||||
searchView.setQueryHint(getString(R.string.filter_artist));
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
return false;
|
||||
// this toast may be overkill...
|
||||
Toast.makeText(requireContext(), "Search: " + query, Toast.LENGTH_SHORT).show();
|
||||
filterArtists(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
artistAdapter.getFilter().filter(newText);
|
||||
return false;
|
||||
filterArtists(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setPadding(-32, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void filterArtists(String query) {
|
||||
List<ArtistID3> allArtists = artistCatalogueViewModel.getArtistList().getValue();
|
||||
|
||||
if (allArtists == null || allArtists.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (query == null || query.trim().isEmpty()) {
|
||||
artistAdapter.setItems(allArtists);
|
||||
} else {
|
||||
String searchQuery = query.toLowerCase().trim();
|
||||
List<ArtistID3> filteredArtists = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : allArtists) {
|
||||
if (artist.getName() != null &&
|
||||
artist.getName().toLowerCase().contains(searchQuery)) {
|
||||
filteredArtists.add(artist);
|
||||
}
|
||||
}
|
||||
artistAdapter.setItems(filteredArtists);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
|
||||
@@ -29,19 +29,16 @@ import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
@@ -49,6 +46,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentArtistPageBinding bind;
|
||||
private MainActivity activity;
|
||||
private ArtistPageViewModel artistPageViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
private AlbumCatalogueAdapter albumCatalogueAdapter;
|
||||
@@ -63,6 +61,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentArtistPageBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
@@ -80,6 +79,13 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
super.onStart();
|
||||
|
||||
initializeMediaBrowser();
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,6 +180,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null);
|
||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
setMediaBrowserListenableFuture();
|
||||
reapplyPlayback();
|
||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
|
||||
@@ -183,6 +191,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
if (bind != null)
|
||||
bind.artistPageShuffleButton.setEnabled(!songs.isEmpty());
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
reapplyPlayback();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -273,4 +282,31 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
public void onArtistLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.cappielloantonio.tempo.R
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager
|
||||
import com.cappielloantonio.tempo.service.MediaService
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
|
||||
class EqualizerFragment : Fragment() {
|
||||
|
||||
private var equalizerManager: EqualizerManager? = null
|
||||
private lateinit var eqBandsContainer: LinearLayout
|
||||
private lateinit var eqSwitch: Switch
|
||||
private lateinit var resetButton: Button
|
||||
private lateinit var safeSpace: Space
|
||||
private val bandSeekBars = mutableListOf<SeekBar>()
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as MediaService.LocalBinder
|
||||
equalizerManager = binder.getEqualizerManager()
|
||||
initUI()
|
||||
restoreEqualizerPreferences()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
equalizerManager = null
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
Intent(requireContext(), MediaService::class.java).also { intent ->
|
||||
intent.action = MediaService.ACTION_BIND_EQUALIZER
|
||||
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
requireActivity().unbindService(connection)
|
||||
equalizerManager = null
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val root = inflater.inflate(R.layout.fragment_equalizer, container, false)
|
||||
eqSwitch = root.findViewById(R.id.equalizer_switch)
|
||||
eqSwitch.isChecked = Preferences.isEqualizerEnabled()
|
||||
eqSwitch.jumpDrawablesToCurrentState()
|
||||
return root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
eqBandsContainer = view.findViewById(R.id.eq_bands_container)
|
||||
resetButton = view.findViewById(R.id.equalizer_reset_button)
|
||||
safeSpace = view.findViewById(R.id.equalizer_bottom_space)
|
||||
}
|
||||
|
||||
private fun initUI() {
|
||||
val manager = equalizerManager
|
||||
val notSupportedView = view?.findViewById<LinearLayout>(R.id.equalizer_not_supported_container)
|
||||
val switchRow = view?.findViewById<View>(R.id.equalizer_switch_row)
|
||||
|
||||
if (manager == null || manager.getNumberOfBands().toInt() == 0) {
|
||||
switchRow?.visibility = View.GONE
|
||||
resetButton.visibility = View.GONE
|
||||
eqBandsContainer.visibility = View.GONE
|
||||
safeSpace.visibility = View.GONE
|
||||
notSupportedView?.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
notSupportedView?.visibility = View.GONE
|
||||
switchRow?.visibility = View.VISIBLE
|
||||
resetButton.visibility = View.VISIBLE
|
||||
eqBandsContainer.visibility = View.VISIBLE
|
||||
safeSpace.visibility = View.VISIBLE
|
||||
|
||||
eqSwitch.setOnCheckedChangeListener(null)
|
||||
updateUiEnabledState(eqSwitch.isChecked)
|
||||
eqSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
manager.setEnabled(isChecked)
|
||||
Preferences.setEqualizerEnabled(isChecked)
|
||||
updateUiEnabledState(isChecked)
|
||||
}
|
||||
|
||||
createBandSliders()
|
||||
|
||||
resetButton.setOnClickListener {
|
||||
resetEqualizer()
|
||||
saveBandLevelsToPreferences()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUiEnabledState(isEnabled: Boolean) {
|
||||
resetButton.isEnabled = isEnabled
|
||||
bandSeekBars.forEach { it.isEnabled = isEnabled }
|
||||
}
|
||||
|
||||
private fun formatDb(value: Int): String = if (value > 0) "+$value dB" else "$value dB"
|
||||
|
||||
private fun createBandSliders() {
|
||||
val manager = equalizerManager ?: return
|
||||
eqBandsContainer.removeAllViews()
|
||||
bandSeekBars.clear()
|
||||
val bands = manager.getNumberOfBands()
|
||||
val bandLevelRange = manager.getBandLevelRange() ?: shortArrayOf(-1500, 1500)
|
||||
val minLevelDb = bandLevelRange[0] / 100
|
||||
val maxLevelDb = bandLevelRange[1] / 100
|
||||
|
||||
val savedLevels = Preferences.getEqualizerBandLevels(bands)
|
||||
for (i in 0 until bands) {
|
||||
val band = i.toShort()
|
||||
val freq = manager.getCenterFreq(band) ?: 0
|
||||
|
||||
val row = LinearLayout(requireContext()).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
val topBottomMarginDp = 16
|
||||
topMargin = topBottomMarginDp.dpToPx(context)
|
||||
bottomMargin = topBottomMarginDp.dpToPx(context)
|
||||
}
|
||||
setPadding(0, 8, 0, 8)
|
||||
}
|
||||
|
||||
val freqLabel = TextView(requireContext(), null, 0, R.style.LabelSmall).apply {
|
||||
text = if (freq >= 1000) {
|
||||
if (freq % 1000 == 0) {
|
||||
"${freq / 1000} kHz"
|
||||
} else {
|
||||
String.format("%.1f kHz", freq / 1000f)
|
||||
}
|
||||
} else {
|
||||
"$freq Hz"
|
||||
}
|
||||
gravity = Gravity.START
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)
|
||||
}
|
||||
row.addView(freqLabel)
|
||||
|
||||
val initialLevelDb = (savedLevels.getOrNull(i) ?: (manager.getBandLevel(band) ?: 0)) / 100
|
||||
val dbLabel = TextView(requireContext(), null, 0, R.style.LabelSmall).apply {
|
||||
text = formatDb(initialLevelDb)
|
||||
setPadding(12, 0, 0, 0)
|
||||
gravity = Gravity.END
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)
|
||||
}
|
||||
|
||||
val seekBar = SeekBar(requireContext()).apply {
|
||||
max = maxLevelDb - minLevelDb
|
||||
progress = initialLevelDb - minLevelDb
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 6f)
|
||||
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
val thisLevelDb = progress + minLevelDb
|
||||
if (fromUser) {
|
||||
manager.setBandLevel(band, (thisLevelDb * 100).toShort())
|
||||
saveBandLevelsToPreferences()
|
||||
}
|
||||
dbLabel.text = formatDb(thisLevelDb)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
bandSeekBars.add(seekBar)
|
||||
row.addView(seekBar)
|
||||
row.addView(dbLabel)
|
||||
eqBandsContainer.addView(row)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetEqualizer() {
|
||||
val manager = equalizerManager ?: return
|
||||
val bands = manager.getNumberOfBands()
|
||||
val bandLevelRange = manager.getBandLevelRange() ?: shortArrayOf(-1500, 1500)
|
||||
val minLevelDb = bandLevelRange[0] / 100
|
||||
val midLevelDb = 0
|
||||
|
||||
for (i in 0 until bands) {
|
||||
manager.setBandLevel(i.toShort(), (0).toShort())
|
||||
bandSeekBars.getOrNull(i)?.progress = midLevelDb - minLevelDb
|
||||
}
|
||||
Preferences.setEqualizerBandLevels(ShortArray(bands.toInt()))
|
||||
}
|
||||
|
||||
private fun saveBandLevelsToPreferences() {
|
||||
val manager = equalizerManager ?: return
|
||||
val bands = manager.getNumberOfBands()
|
||||
val levels = ShortArray(bands.toInt()) { i -> manager.getBandLevel(i.toShort()) ?: 0 }
|
||||
Preferences.setEqualizerBandLevels(levels)
|
||||
}
|
||||
|
||||
private fun restoreEqualizerPreferences() {
|
||||
val manager = equalizerManager ?: return
|
||||
eqSwitch.isChecked = Preferences.isEqualizerEnabled()
|
||||
updateUiEnabledState(eqSwitch.isChecked)
|
||||
|
||||
val bands = manager.getNumberOfBands()
|
||||
val bandLevelRange = manager.getBandLevelRange() ?: shortArrayOf(-1500, 1500)
|
||||
val minLevelDb = bandLevelRange[0] / 100
|
||||
|
||||
val savedLevels = Preferences.getEqualizerBandLevels(bands)
|
||||
for (i in 0 until bands) {
|
||||
val savedDb = savedLevels[i] / 100
|
||||
manager.setBandLevel(i.toShort(), (savedDb * 100).toShort())
|
||||
bandSeekBars.getOrNull(i)?.progress = savedDb - minLevelDb
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Int.dpToPx(context: Context): Int =
|
||||
(this * context.resources.displayMetrics.density).toInt()
|
||||
@@ -39,6 +39,7 @@ import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||
@@ -59,6 +60,7 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -73,6 +75,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
private FragmentHomeTabMusicBinding bind;
|
||||
private MainActivity activity;
|
||||
private HomeViewModel homeViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
|
||||
private DiscoverSongAdapter discoverSongAdapter;
|
||||
private SimilarTrackAdapter similarMusicAdapter;
|
||||
@@ -100,6 +103,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
|
||||
@@ -111,6 +115,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
initSyncStarredView();
|
||||
initSyncStarredAlbumsView();
|
||||
initDiscoverSongSlideView();
|
||||
initSimilarSongView();
|
||||
initArtistRadio();
|
||||
@@ -136,12 +141,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
super.onStart();
|
||||
|
||||
initializeMediaBrowser();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observeStarredSongsPlayback();
|
||||
observeTopSongsPlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshSharesView();
|
||||
if (topSongAdapter != null) setTopSongsMediaBrowserListenableFuture();
|
||||
if (starredSongAdapter != null) setStarredSongsMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -314,6 +325,63 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
private void initSyncStarredAlbumsView() {
|
||||
if (Preferences.isStarredAlbumsSyncEnabled()) {
|
||||
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observeForever(new Observer<List<AlbumID3>>() {
|
||||
@Override
|
||||
public void onChanged(List<AlbumID3> albums) {
|
||||
if (albums != null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
List<String> albumsToSync = new ArrayList<>();
|
||||
int albumCount = 0;
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
boolean needsSync = false;
|
||||
albumCount++;
|
||||
albumsToSync.add(album.getName());
|
||||
}
|
||||
|
||||
if (albumCount > 0) {
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.VISIBLE);
|
||||
String message = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_albums_count,
|
||||
albumCount,
|
||||
albumCount
|
||||
);
|
||||
bind.homeSyncStarredAlbumsToSync.setText(message);
|
||||
}
|
||||
}
|
||||
|
||||
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind.homeSyncStarredAlbumsCancel.setOnClickListener(v -> {
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||
});
|
||||
|
||||
bind.homeSyncStarredAlbumsDownload.setOnClickListener(v -> {
|
||||
homeViewModel.getAllStarredAlbumSongs().observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> allSongs) {
|
||||
if (allSongs != null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
|
||||
for (Child song : allSongs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
homeViewModel.getAllStarredAlbumSongs().removeObserver(this);
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void initDiscoverSongSlideView() {
|
||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
|
||||
|
||||
@@ -418,6 +486,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
topSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.topSongsRecyclerView.setAdapter(topSongAdapter);
|
||||
setTopSongsMediaBrowserListenableFuture();
|
||||
reapplyTopSongsPlayback();
|
||||
homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||
if (chronologies == null || chronologies.isEmpty()) {
|
||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
|
||||
@@ -433,6 +503,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
topSongAdapter.setItems(topSongs);
|
||||
reapplyTopSongsPlayback();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -456,6 +527,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
starredSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||
setStarredSongsMediaBrowserListenableFuture();
|
||||
reapplyStarredSongsPlayback();
|
||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
|
||||
@@ -466,6 +539,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
starredSongAdapter.setItems(songs);
|
||||
reapplyStarredSongsPlayback();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -895,6 +969,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
activity.setBottomSheetInPeek(true);
|
||||
}
|
||||
topSongAdapter.notifyDataSetChanged();
|
||||
starredSongAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -984,4 +1060,58 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
public void onShareLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observeStarredSongsPlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (starredSongAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
starredSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (starredSongAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
starredSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void observeTopSongsPlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (topSongAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
topSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (topSongAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
topSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyStarredSongsPlayback() {
|
||||
if (starredSongAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
starredSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void reapplyTopSongsPlayback() {
|
||||
if (topSongAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
topSongAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTopSongsMediaBrowserListenableFuture() {
|
||||
topSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
private void setStarredSongsMediaBrowserListenableFuture() {
|
||||
starredSongAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,12 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
playerBottomSheetViewModel.setLiveDescription(mediaMetadata.extras.getString("description", null));
|
||||
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(mediaMetadata.extras.getString("title"));
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(mediaMetadata.extras.getString("artist"));
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(
|
||||
mediaMetadata.artist != null
|
||||
? mediaMetadata.artist
|
||||
: Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO)
|
||||
? mediaMetadata.extras.getString("uri", getString(R.string.label_placeholder))
|
||||
: "");
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"), CustomGlideRequest.ResourceType.Song)
|
||||
@@ -182,7 +187,11 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage);
|
||||
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setVisibility(mediaMetadata.extras.getString("title") != null && !Objects.equals(mediaMetadata.extras.getString("title"), "") ? View.VISIBLE : View.GONE);
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setVisibility(mediaMetadata.extras.getString("artist") != null && !Objects.equals(mediaMetadata.extras.getString("artist"), "") ? View.VISIBLE : View.GONE);
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setVisibility(
|
||||
(mediaMetadata.extras.getString("artist") != null && !Objects.equals(mediaMetadata.extras.getString("artist"), ""))
|
||||
|| (Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO) && mediaMetadata.extras.getString("uri") != null)
|
||||
? View.VISIBLE
|
||||
: View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
import android.widget.RatingBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
@@ -22,11 +28,14 @@ import androidx.media3.common.util.RepeatModeUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBinding;
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||
@@ -36,6 +45,7 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.elevation.SurfaceColors;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@@ -53,6 +63,8 @@ public class PlayerControllerFragment extends Fragment {
|
||||
private InnerFragmentPlayerControllerBinding bind;
|
||||
private ViewPager2 playerMediaCoverViewPager;
|
||||
private ToggleButton buttonFavorite;
|
||||
private RatingViewModel ratingViewModel;
|
||||
private RatingBar songRatingBar;
|
||||
private TextView playerMediaTitleLabel;
|
||||
private TextView playerArtistNameLabel;
|
||||
private Button playbackSpeedButton;
|
||||
@@ -62,11 +74,16 @@ public class PlayerControllerFragment extends Fragment {
|
||||
private ConstraintLayout playerQuickActionView;
|
||||
private ImageButton playerOpenQueueButton;
|
||||
private ImageButton playerTrackInfo;
|
||||
private LinearLayout ratingContainer;
|
||||
private ImageButton equalizerButton;
|
||||
|
||||
private MainActivity activity;
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private MediaService.LocalBinder mediaServiceBinder;
|
||||
private boolean isServiceBound = false;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -75,6 +92,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
View view = bind.getRoot();
|
||||
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
ratingViewModel = new ViewModelProvider(requireActivity()).get(RatingViewModel.class);
|
||||
|
||||
init();
|
||||
initQuickActionView();
|
||||
@@ -82,6 +100,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
initMediaListenable();
|
||||
initMediaLabelButton();
|
||||
initArtistLabelButton();
|
||||
initEqualizerButton();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -117,6 +136,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
|
||||
playerOpenQueueButton = bind.getRoot().findViewById(R.id.player_open_queue_button);
|
||||
playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
|
||||
songRatingBar = bind.getRoot().findViewById(R.id.song_rating_bar);
|
||||
ratingContainer = bind.getRoot().findViewById(R.id.rating_container);
|
||||
equalizerButton = bind.getRoot().findViewById(R.id.player_open_equalizer_button);
|
||||
checkAndSetRatingContainerVisibility();
|
||||
}
|
||||
|
||||
private void initQuickActionView() {
|
||||
@@ -146,7 +169,6 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser);
|
||||
mediaBrowser.setShuffleModeEnabled(Preferences.isShuffleModeEnabled());
|
||||
mediaBrowser.setRepeatMode(Preferences.getRepeatMode());
|
||||
|
||||
setMediaControllerListener(mediaBrowser);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -181,18 +203,27 @@ public class PlayerControllerFragment extends Fragment {
|
||||
|
||||
private void setMetadata(MediaMetadata mediaMetadata) {
|
||||
playerMediaTitleLabel.setText(String.valueOf(mediaMetadata.title));
|
||||
playerArtistNameLabel.setText(String.valueOf(mediaMetadata.artist));
|
||||
playerArtistNameLabel.setText(
|
||||
mediaMetadata.artist != null
|
||||
? String.valueOf(mediaMetadata.artist)
|
||||
: mediaMetadata.extras != null && Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO)
|
||||
? mediaMetadata.extras.getString("uri", getString(R.string.label_placeholder))
|
||||
: "");
|
||||
|
||||
playerMediaTitleLabel.setSelected(true);
|
||||
playerArtistNameLabel.setSelected(true);
|
||||
|
||||
playerMediaTitleLabel.setVisibility(mediaMetadata.title != null && !Objects.equals(mediaMetadata.title, "") ? View.VISIBLE : View.GONE);
|
||||
playerArtistNameLabel.setVisibility(mediaMetadata.artist != null && !Objects.equals(mediaMetadata.artist, "") ? View.VISIBLE : View.GONE);
|
||||
playerArtistNameLabel.setVisibility(
|
||||
(mediaMetadata.artist != null && !Objects.equals(mediaMetadata.artist, ""))
|
||||
|| mediaMetadata.extras != null && Objects.equals(mediaMetadata.extras.getString("type"), Constants.MEDIA_TYPE_RADIO) && mediaMetadata.extras.getString("uri") != null
|
||||
? View.VISIBLE
|
||||
: View.GONE);
|
||||
}
|
||||
|
||||
private void setMediaInfo(MediaMetadata mediaMetadata) {
|
||||
if (mediaMetadata.extras != null) {
|
||||
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
|
||||
String extension = mediaMetadata.extras.getString("suffix", getString(R.string.player_unknown_format));
|
||||
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
|
||||
String samplingRate = mediaMetadata.extras.getInt("samplingRate", 0) != 0 ? new DecimalFormat("0.#").format(mediaMetadata.extras.getInt("samplingRate", 0) / 1000.0) + "kHz" : "";
|
||||
String bitDepth = mediaMetadata.extras.getInt("bitDepth", 0) != 0 ? mediaMetadata.extras.getInt("bitDepth", 0) + "b" : "";
|
||||
@@ -218,8 +249,8 @@ public class PlayerControllerFragment extends Fragment {
|
||||
boolean isTranscodingBitrate = !MusicUtil.getBitratePreference().equals("0");
|
||||
|
||||
if (isTranscodingExtension || isTranscodingBitrate) {
|
||||
playerMediaExtension.setText("Transcoding");
|
||||
playerMediaBitrate.setText("requested");
|
||||
playerMediaExtension.setText(MusicUtil.getTranscodingFormatPreference() + " (" + getString(R.string.player_transcoding) + ")");
|
||||
playerMediaBitrate.setText(!MusicUtil.getBitratePreference().equals("0") ? MusicUtil.getBitratePreference() + "kbps" : getString(R.string.player_transcoding_requested));
|
||||
}
|
||||
|
||||
playerTrackInfo.setOnClickListener(view -> {
|
||||
@@ -305,6 +336,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
private void initMediaListenable() {
|
||||
playerBottomSheetViewModel.getLiveMedia().observe(getViewLifecycleOwner(), media -> {
|
||||
if (media != null) {
|
||||
ratingViewModel.setSong(media);
|
||||
buttonFavorite.setChecked(media.getStarred() != null);
|
||||
buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite(requireContext(), media));
|
||||
buttonFavorite.setOnLongClickListener(v -> {
|
||||
@@ -315,9 +347,29 @@ public class PlayerControllerFragment extends Fragment {
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Integer currentRating = media.getUserRating();
|
||||
|
||||
if (currentRating != null) {
|
||||
songRatingBar.setRating(currentRating);
|
||||
} else {
|
||||
songRatingBar.setRating(0);
|
||||
}
|
||||
|
||||
songRatingBar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
|
||||
@Override
|
||||
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
ratingViewModel.rate((int) rating);
|
||||
media.setUserRating((int) rating);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (getActivity() != null) {
|
||||
playerBottomSheetViewModel.refreshMediaInfo(requireActivity(), media);
|
||||
}
|
||||
@@ -387,6 +439,18 @@ public class PlayerControllerFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void initEqualizerButton() {
|
||||
equalizerButton.setOnClickListener(v -> {
|
||||
NavController navController = NavHostFragment.findNavController(this);
|
||||
NavOptions navOptions = new NavOptions.Builder()
|
||||
.setLaunchSingleTop(true)
|
||||
.setPopUpTo(R.id.equalizerFragment, true)
|
||||
.build();
|
||||
navController.navigate(R.id.equalizerFragment, null, navOptions);
|
||||
if (activity != null) activity.collapseBottomSheetDelayed();
|
||||
});
|
||||
}
|
||||
|
||||
public void goToControllerPage() {
|
||||
playerMediaCoverViewPager.setCurrentItem(0, false);
|
||||
}
|
||||
@@ -395,6 +459,17 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerMediaCoverViewPager.setCurrentItem(1, true);
|
||||
}
|
||||
|
||||
private void checkAndSetRatingContainerVisibility() {
|
||||
if (ratingContainer == null) return;
|
||||
|
||||
if (Preferences.showItemStarRating()) {
|
||||
ratingContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else {
|
||||
ratingContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setPlaybackParameters(MediaBrowser mediaBrowser) {
|
||||
Button playbackSpeedButton = bind.getRoot().findViewById(R.id.player_playback_speed_button);
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
@@ -411,4 +486,66 @@ public class PlayerControllerFragment extends Fragment {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
// TODO Resettare lo skip del silenzio
|
||||
}
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mediaServiceBinder = (MediaService.LocalBinder) service;
|
||||
isServiceBound = true;
|
||||
checkEqualizerBands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mediaServiceBinder = null;
|
||||
isServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
private void bindMediaService() {
|
||||
Intent intent = new Intent(requireActivity(), MediaService.class);
|
||||
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
|
||||
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
isServiceBound = true;
|
||||
}
|
||||
|
||||
private void checkEqualizerBands() {
|
||||
if (mediaServiceBinder != null) {
|
||||
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
|
||||
short numBands = eqManager.getNumberOfBands();
|
||||
|
||||
if (equalizerButton != null) {
|
||||
if (numBands == 0) {
|
||||
equalizerButton.setVisibility(View.GONE);
|
||||
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) playerOpenQueueButton.getLayoutParams();
|
||||
params.startToEnd = ConstraintLayout.LayoutParams.UNSET;
|
||||
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
playerOpenQueueButton.setLayoutParams(params);
|
||||
} else {
|
||||
equalizerButton.setVisibility(View.VISIBLE);
|
||||
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) playerOpenQueueButton.getLayoutParams();
|
||||
params.startToStart = ConstraintLayout.LayoutParams.UNSET;
|
||||
params.startToEnd = R.id.player_open_equalizer_button;
|
||||
playerOpenQueueButton.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
bindMediaService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (isServiceBound) {
|
||||
requireActivity().unbindService(serviceConnection);
|
||||
isServiceBound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import android.transition.TransitionManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -19,6 +20,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerCoverBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
@@ -30,6 +32,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
@@ -119,8 +122,10 @@ public class PlayerCoverFragment extends Fragment {
|
||||
});
|
||||
|
||||
bind.innerButtonTopRight.setOnClickListener(view -> {
|
||||
ArrayList<Child> tracks = new ArrayList<>();
|
||||
tracks.add(song);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, song);
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, tracks);
|
||||
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
@@ -136,7 +141,7 @@ public class PlayerCoverFragment extends Fragment {
|
||||
|
||||
bind.innerButtonBottomRight.setOnClickListener(view -> {
|
||||
if (playerBottomSheetViewModel.savePlayQueue()) {
|
||||
Snackbar.make(requireView(), "Salvato", Snackbar.LENGTH_LONG).show();
|
||||
Snackbar.make(requireView(), R.string.player_queue_save_queue_success, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
@@ -38,6 +39,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
private InnerFragmentPlayerQueueBinding bind;
|
||||
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
||||
@@ -48,6 +50,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
View view = bind.getRoot();
|
||||
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
initQueueRecyclerView();
|
||||
|
||||
@@ -59,6 +62,9 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
super.onStart();
|
||||
initializeBrowser();
|
||||
bindMediaController();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,9 +116,12 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
playerSongQueueAdapter = new PlayerSongQueueAdapter(this);
|
||||
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
||||
reapplyPlayback();
|
||||
|
||||
playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> {
|
||||
if (queue != null) {
|
||||
playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList()));
|
||||
reapplyPlayback();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -216,4 +225,27 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (playerSongQueueAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
playerSongQueueAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (playerSongQueueAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
playerSongQueueAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (playerSongQueueAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
playerSongQueueAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -49,6 +50,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentPlaylistPageBinding bind;
|
||||
private MainActivity activity;
|
||||
private PlaylistPageViewModel playlistPageViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
|
||||
@@ -94,6 +96,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentPlaylistPageBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
@@ -109,6 +112,15 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
super.onStart();
|
||||
|
||||
initializeMediaBrowser();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -248,8 +260,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
setMediaBrowserListenableFuture();
|
||||
reapplyPlayback();
|
||||
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
reapplyPlayback();
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
@@ -270,4 +287,31 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,11 @@ import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -34,6 +31,7 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -46,6 +44,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
private FragmentSearchBinding bind;
|
||||
private MainActivity activity;
|
||||
private SearchViewModel searchViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
|
||||
private ArtistAdapter artistAdapter;
|
||||
private AlbumAdapter albumAdapter;
|
||||
@@ -61,6 +60,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentSearchBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
initSearchResultView();
|
||||
initSearchView();
|
||||
@@ -73,6 +73,15 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeMediaBrowser();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (songHorizontalAdapter != null) setMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,6 +122,9 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
setMediaBrowserListenableFuture();
|
||||
reapplyPlayback();
|
||||
|
||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
}
|
||||
|
||||
@@ -260,6 +272,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
songHorizontalAdapter.notifyDataSetChanged();
|
||||
activity.setBottomSheetInPeek(true);
|
||||
}
|
||||
|
||||
@@ -287,4 +300,31 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
public void onArtistLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,19 +22,26 @@ import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
@@ -49,6 +60,9 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private ActivityResultLauncher<Intent> someActivityResultLauncher;
|
||||
|
||||
private MediaService.LocalBinder mediaServiceBinder;
|
||||
private boolean isServiceBound = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -84,7 +98,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
checkEqualizer();
|
||||
checkSystemEqualizer();
|
||||
checkCacheStorage();
|
||||
checkStorage();
|
||||
|
||||
@@ -94,11 +108,15 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
actionLogout();
|
||||
actionScan();
|
||||
actionSyncStarredAlbums();
|
||||
actionSyncStarredTracks();
|
||||
actionChangeStreamingCacheStorage();
|
||||
actionChangeDownloadStorage();
|
||||
actionDeleteDownloadStorage();
|
||||
actionKeepScreenOn();
|
||||
|
||||
bindMediaService();
|
||||
actionAppEqualizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,8 +139,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEqualizer() {
|
||||
Preference equalizer = findPreference("equalizer");
|
||||
private void checkSystemEqualizer() {
|
||||
Preference equalizer = findPreference("system_equalizer");
|
||||
|
||||
if (equalizer == null) return;
|
||||
|
||||
@@ -255,14 +273,30 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredSyncDialog dialog = new StarredSyncDialog();
|
||||
StarredSyncDialog dialog = new StarredSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredAlbums() {
|
||||
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void actionChangeStreamingCacheStorage() {
|
||||
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
|
||||
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
|
||||
@@ -334,4 +368,63 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mediaServiceBinder = (MediaService.LocalBinder) service;
|
||||
isServiceBound = true;
|
||||
checkEqualizerBands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mediaServiceBinder = null;
|
||||
isServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
private void bindMediaService() {
|
||||
Intent intent = new Intent(requireActivity(), MediaService.class);
|
||||
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
|
||||
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
isServiceBound = true;
|
||||
}
|
||||
|
||||
private void checkEqualizerBands() {
|
||||
if (mediaServiceBinder != null) {
|
||||
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
|
||||
short numBands = eqManager.getNumberOfBands();
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setVisible(numBands > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionAppEqualizer() {
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setOnPreferenceClickListener(preference -> {
|
||||
NavController navController = NavHostFragment.findNavController(this);
|
||||
NavOptions navOptions = new NavOptions.Builder()
|
||||
.setLaunchSingleTop(true)
|
||||
.setPopUpTo(R.id.equalizerFragment, true)
|
||||
.build();
|
||||
activity.setBottomNavigationBarVisibility(true);
|
||||
activity.setBottomSheetVisibility(true);
|
||||
navController.navigate(R.id.equalizerFragment, null, navOptions);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (isServiceBound) {
|
||||
requireActivity().unbindService(serviceConnection);
|
||||
isServiceBound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -49,6 +50,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentSongListPageBinding bind;
|
||||
private MainActivity activity;
|
||||
private SongListPageViewModel songListPageViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
|
||||
@@ -69,6 +71,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentSongListPageBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
@@ -82,6 +85,15 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeMediaBrowser();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setMediaBrowserListenableFuture();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,9 +203,12 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
setMediaBrowserListenableFuture();
|
||||
reapplyPlayback();
|
||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
isLoading = false;
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
reapplyPlayback();
|
||||
setSongListPageSubtitle(songs);
|
||||
});
|
||||
|
||||
@@ -325,4 +340,31 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
private void observePlayback() {
|
||||
playbackViewModel.getCurrentSongId().observe(getViewLifecycleOwner(), id -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
playbackViewModel.getIsPlaying().observe(getViewLifecycleOwner(), playing -> {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reapplyPlayback() {
|
||||
if (songHorizontalAdapter != null) {
|
||||
String id = playbackViewModel.getCurrentSongId().getValue();
|
||||
Boolean playing = playbackViewModel.getIsPlaying().getValue();
|
||||
songHorizontalAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
songHorizontalAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
albumBottomSheetViewModel.setFavorite();
|
||||
albumBottomSheetViewModel.setFavorite(requireContext());
|
||||
});
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
|
||||
@@ -78,32 +78,26 @@ public final class DownloadUtil {
|
||||
return httpDataSourceFactory;
|
||||
}
|
||||
|
||||
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
|
||||
if (dataSourceFactory == null) {
|
||||
context = context.getApplicationContext();
|
||||
public static synchronized DataSource.Factory getUpstreamDataSourceFactory(Context context) {
|
||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
return dataSourceFactory;
|
||||
}
|
||||
|
||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||
|
||||
if (Preferences.getStreamingCacheSize() > 0) {
|
||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||
.setCache(getStreamingCache(context))
|
||||
.setUpstreamDataSourceFactory(upstreamFactory);
|
||||
|
||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||
dataSpec -> {
|
||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||
return builder.build();
|
||||
}
|
||||
);
|
||||
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||
} else {
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
}
|
||||
}
|
||||
public static synchronized DataSource.Factory getCacheDataSourceFactory(Context context) {
|
||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||
.setCache(getStreamingCache(context))
|
||||
.setUpstreamDataSourceFactory(getUpstreamDataSourceFactory(context));
|
||||
|
||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||
dataSpec -> {
|
||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||
return builder.build();
|
||||
}
|
||||
);
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||
return dataSourceFactory;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider
|
||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory
|
||||
import androidx.media3.extractor.ExtractorsFactory
|
||||
|
||||
@UnstableApi
|
||||
class DynamicMediaSourceFactory(
|
||||
private val context: Context
|
||||
) : MediaSource.Factory {
|
||||
|
||||
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
||||
val mediaType: String? = mediaItem.mediaMetadata.extras?.getString("type", "")
|
||||
|
||||
val streamingCacheSize = Preferences.getStreamingCacheSize()
|
||||
val bypassCache = mediaType == Constants.MEDIA_TYPE_RADIO
|
||||
|
||||
val useUpstream = when {
|
||||
streamingCacheSize.toInt() == 0 -> true
|
||||
streamingCacheSize > 0 && bypassCache -> true
|
||||
streamingCacheSize > 0 && !bypassCache -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
val dataSourceFactory: DataSource.Factory = if (useUpstream) {
|
||||
DownloadUtil.getUpstreamDataSourceFactory(context)
|
||||
} else {
|
||||
DownloadUtil.getCacheDataSourceFactory(context)
|
||||
}
|
||||
|
||||
return when {
|
||||
mediaItem.localConfiguration?.mimeType == MimeTypes.APPLICATION_M3U8 ||
|
||||
mediaItem.localConfiguration?.uri?.lastPathSegment?.endsWith(".m3u8", ignoreCase = true) == true -> {
|
||||
HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory()
|
||||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getSupportedTypes(): IntArray {
|
||||
return intArrayOf(
|
||||
C.CONTENT_TYPE_HLS,
|
||||
C.CONTENT_TYPE_OTHER
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,6 @@ public class MappingUtil {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", internetRadioStation.getId());
|
||||
bundle.putString("title", internetRadioStation.getName());
|
||||
bundle.putString("artist", uri.toString());
|
||||
bundle.putString("uri", uri.toString());
|
||||
bundle.putString("type", Constants.MEDIA_TYPE_RADIO);
|
||||
|
||||
@@ -149,7 +148,6 @@ public class MappingUtil {
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(internetRadioStation.getName())
|
||||
.setArtist(internetRadioStation.getStreamUrl())
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
|
||||
@@ -37,6 +37,7 @@ object Preferences {
|
||||
private const val WIFI_ONLY = "wifi_only"
|
||||
private const val DATA_SAVING_MODE = "data_saving_mode"
|
||||
private const val SERVER_UNREACHABLE = "server_unreachable"
|
||||
private const val SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE = "sync_starred_albums_for_offline_use"
|
||||
private const val SYNC_STARRED_TRACKS_FOR_OFFLINE_USE = "sync_starred_tracks_for_offline_use"
|
||||
private const val QUEUE_SYNCING = "queue_syncing"
|
||||
private const val QUEUE_SYNCING_COUNTDOWN = "queue_syncing_countdown"
|
||||
@@ -63,11 +64,13 @@ object Preferences {
|
||||
private const val ALWAYS_ON_DISPLAY = "always_on_display"
|
||||
private const val AUDIO_QUALITY_PER_ITEM = "audio_quality_per_item"
|
||||
private const val HOME_SECTOR_LIST = "home_sector_list"
|
||||
private const val SONG_RATING_PER_ITEM = "song_rating_per_item"
|
||||
private const val RATING_PER_ITEM = "rating_per_item"
|
||||
private const val NEXT_UPDATE_CHECK = "next_update_check"
|
||||
private const val CONTINUOUS_PLAY = "continuous_play"
|
||||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
|
||||
private const val EQUALIZER_ENABLED = "equalizer_enabled"
|
||||
private const val EQUALIZER_BAND_LEVELS = "equalizer_band_levels"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
@@ -300,6 +303,18 @@ object Preferences {
|
||||
.apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isStarredAlbumsSyncEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setStarredAlbumsSyncEnabled(isStarredSyncEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(
|
||||
SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE, isStarredSyncEnabled
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isStarredSyncEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, false)
|
||||
@@ -486,6 +501,11 @@ object Preferences {
|
||||
App.getInstance().preferences.edit().putString(HOME_SECTOR_LIST, Gson().toJson(extension)).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showItemStarRating(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SONG_RATING_PER_ITEM, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showItemRating(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(RATING_PER_ITEM, false)
|
||||
@@ -519,4 +539,31 @@ object Preferences {
|
||||
LAST_INSTANT_MIX, 0
|
||||
) + 5000 < System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerEnabled(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(EQUALIZER_ENABLED, enabled).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isEqualizerEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(EQUALIZER_ENABLED, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerBandLevels(bandLevels: ShortArray) {
|
||||
val asString = bandLevels.joinToString(",")
|
||||
App.getInstance().preferences.edit().putString(EQUALIZER_BAND_LEVELS, asString).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEqualizerBandLevels(bandCount: Short): ShortArray {
|
||||
val str = App.getInstance().preferences.getString(EQUALIZER_BAND_LEVELS, null)
|
||||
if (str.isNullOrBlank()) {
|
||||
return ShortArray(bandCount.toInt())
|
||||
}
|
||||
val parts = str.split(",")
|
||||
if (parts.size < bandCount) return ShortArray(bandCount.toInt())
|
||||
return ShortArray(bandCount.toInt()) { i -> parts[i].toShortOrNull() ?: 0 }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
@@ -16,10 +19,14 @@ import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
@@ -54,7 +61,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
return albumRepository.getAlbumTracks(album.getId());
|
||||
}
|
||||
|
||||
public void setFavorite() {
|
||||
public void setFavorite(Context context) {
|
||||
if (album.getStarred() != null) {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline();
|
||||
@@ -65,7 +72,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
setFavoriteOffline();
|
||||
} else {
|
||||
setFavoriteOnline();
|
||||
setFavoriteOnline(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +90,6 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
// album.setStarred(new Date());
|
||||
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||
}
|
||||
});
|
||||
@@ -96,15 +102,31 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
album.setStarred(new Date());
|
||||
}
|
||||
|
||||
private void setFavoriteOnline() {
|
||||
private void setFavoriteOnline(Context context) {
|
||||
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
// album.setStarred(null);
|
||||
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||
}
|
||||
});
|
||||
|
||||
album.setStarred(new Date());
|
||||
if (Preferences.isStarredAlbumsSyncEnabled()) {
|
||||
AlbumRepository albumRepository = new AlbumRepository();
|
||||
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
|
||||
|
||||
tracksLiveData.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
DownloadUtil.getDownloadTracker(context).download(
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
tracksLiveData.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
private final PlaylistRepository playlistRepository;
|
||||
private final SharingRepository sharingRepository;
|
||||
|
||||
private final StarredAlbumsSyncViewModel albumsSyncViewModel;
|
||||
|
||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<Child>> starredTracksSample = new MutableLiveData<>(null);
|
||||
@@ -82,6 +84,8 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
playlistRepository = new PlaylistRepository();
|
||||
sharingRepository = new SharingRepository();
|
||||
|
||||
albumsSyncViewModel = new StarredAlbumsSyncViewModel(application);
|
||||
|
||||
setOfflineFavorite();
|
||||
}
|
||||
|
||||
@@ -166,6 +170,10 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
return starredAlbums;
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getAllStarredAlbumSongs() {
|
||||
return albumsSyncViewModel.getAllStarredAlbumSongs();
|
||||
}
|
||||
|
||||
public LiveData<List<ArtistID3>> getStarredArtists(LifecycleOwner owner) {
|
||||
if (starredArtists.getValue() == null) {
|
||||
artistRepository.getStarredArtists(true, 20).observe(owner, starredArtists::postValue);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PlaybackViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<String> currentSongId = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> isPlaying = new MutableLiveData<>(false);
|
||||
|
||||
public LiveData<String> getCurrentSongId() {
|
||||
return currentSongId;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsPlaying() {
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
public void update(String songId, boolean playing) {
|
||||
if (!Objects.equals(currentSongId.getValue(), songId)) {
|
||||
currentSongId.postValue(songId);
|
||||
}
|
||||
if (!Objects.equals(isPlaying.getValue(), playing)) {
|
||||
isPlaying.postValue(playing);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
currentSongId.postValue(null);
|
||||
isPlaying.postValue(false);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||
}
|
||||
});
|
||||
|
||||
media.setStarred(null);
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<String> getLiveLyrics() {
|
||||
public LiveData<String> getLiveLyrics() {
|
||||
return lyricsLiveData;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PlaylistChooserViewModel extends AndroidViewModel {
|
||||
private final PlaylistRepository playlistRepository;
|
||||
|
||||
private final MutableLiveData<List<Playlist>> playlists = new MutableLiveData<>(null);
|
||||
private ArrayList<Child> toAdd;
|
||||
private ArrayList<Child> toAdd = new ArrayList<>();
|
||||
|
||||
public PlaylistChooserViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class StarredAlbumsSyncViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
|
||||
private final MutableLiveData<List<AlbumID3>> starredAlbums = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<Child>> starredAlbumSongs = new MutableLiveData<>(null);
|
||||
|
||||
public StarredAlbumsSyncViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
albumRepository = new AlbumRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<AlbumID3>> getStarredAlbums(LifecycleOwner owner) {
|
||||
albumRepository.getStarredAlbums(false, -1).observe(owner, starredAlbums::postValue);
|
||||
return starredAlbums;
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getAllStarredAlbumSongs() {
|
||||
albumRepository.getStarredAlbums(false, -1).observeForever(new Observer<List<AlbumID3>>() {
|
||||
@Override
|
||||
public void onChanged(List<AlbumID3> albums) {
|
||||
if (albums != null && !albums.isEmpty()) {
|
||||
collectAllAlbumSongs(albums, starredAlbumSongs::postValue);
|
||||
} else {
|
||||
starredAlbumSongs.postValue(new ArrayList<>());
|
||||
}
|
||||
albumRepository.getStarredAlbums(false, -1).removeObserver(this);
|
||||
}
|
||||
});
|
||||
|
||||
return starredAlbumSongs;
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getStarredAlbumSongs(Activity activity) {
|
||||
albumRepository.getStarredAlbums(false, -1).observe((LifecycleOwner) activity, albums -> {
|
||||
if (albums != null && !albums.isEmpty()) {
|
||||
collectAllAlbumSongs(albums, starredAlbumSongs::postValue);
|
||||
} else {
|
||||
starredAlbumSongs.postValue(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
return starredAlbumSongs;
|
||||
}
|
||||
|
||||
private void collectAllAlbumSongs(List<AlbumID3> albums, AlbumSongsCallback callback) {
|
||||
List<Child> allSongs = new ArrayList<>();
|
||||
CountDownLatch latch = new CountDownLatch(albums.size());
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
LiveData<List<Child>> albumTracks = albumRepository.getAlbumTracks(album.getId());
|
||||
albumTracks.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null) {
|
||||
allSongs.addAll(songs);
|
||||
}
|
||||
latch.countDown();
|
||||
|
||||
if (latch.getCount() == 0) {
|
||||
callback.onSongsCollected(allSongs);
|
||||
albumTracks.removeObserver(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private interface AlbumSongsCallback {
|
||||
void onSongsCollected(List<Child> songs);
|
||||
}
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_eq.xml
Normal file
11
app/src/main/res/drawable/ic_eq.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:autoMirrored="true">
|
||||
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M160,800L160,480L320,480L320,800L160,800ZM400,800L400,160L560,160L560,800L400,800ZM640,800L640,360L800,360L800,800L640,800Z"/>
|
||||
</vector>
|
||||
93
app/src/main/res/drawable/ui_eq_not_supported.xml
Normal file
93
app/src/main/res/drawable/ui_eq_not_supported.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="757.96dp"
|
||||
android:height="743.73dp"
|
||||
android:viewportWidth="757.96"
|
||||
android:viewportHeight="743.73">
|
||||
<path
|
||||
android:pathData="M91.45,0a32.04,32.04 0,0 0,-32 32L59.45,710.43a32.04,32.04 0,0 0,32 32h297a32.04,32.04 0,0 0,32 -32L420.45,32a32.04,32.04 0,0 0,-32 -32Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M400.66,156.98v-54.44a125.25,125.25 0,0 1,-80.86 -60.19h0a23.79,23.79 0,0 1,-14.22 4.68L262.35,47.03A178.55,178.55 0,0 0,400.66 156.98Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M400.66,99.42v-52.3a29.12,29.12 0,0 0,-29.13 -29.13h-41.97v5.05a23.92,23.92 0,0 1,-7.4 17.33,122.3 122.3,0 0,0 78.5,59.05Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M198.77,47.03L171.74,47.03a23.99,23.99 0,0 1,-23.98 -23.99v-5.05L108.38,17.99a29.13,29.13 0,0 0,-29.13 29.13v648.2a29.08,29.08 0,0 0,29.13 29.11h263.15a28.36,28.36 0,0 0,3.59 -0.22,29.15 29.15,0 0,0 25.54,-28.89L400.66,218.15C304.95,207.07 225.2,138.77 198.77,47.03Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M259.07,47.03h-57.14c26.3,90.04 104.68,157.03 198.73,168.07v-55.02A181.67,181.67 0,0 1,259.07 47.03Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M380.61,532.78h-270a5.01,5.01 0,0 1,-5 -5L105.61,460.81a5.01,5.01 0,0 1,5 -5h270a5.01,5.01 0,0 1,5 5v66.98A5.01,5.01 0,0 1,380.61 532.78ZM110.61,457.8a3,3 0,0 0,-3 3v66.98a3,3 0,0 0,3 3h270a3,3 0,0 0,3 -3L383.61,460.81a3,3 0,0 0,-3 -3Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M145.61,494.29m-21,0a21,21 0,1 1,42 0a21,21 0,1 1,-42 0"
|
||||
android:fillColor="#3f3d56"/>
|
||||
<path
|
||||
android:pathData="M194.11,480.29a3.5,3.5 0,0 0,0 7h165a3.5,3.5 0,1 0,0 -7Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M194.11,501.29a3.5,3.5 0,0 0,0 7h165a3.5,3.5 0,1 0,0 -7Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M380.61,644.78h-270a5.01,5.01 0,0 1,-5 -5L105.61,572.81a5.01,5.01 0,0 1,5 -5h270a5.01,5.01 0,0 1,5 5v66.98A5.01,5.01 0,0 1,380.61 644.78ZM110.61,569.8a3,3 0,0 0,-3 3v66.98a3,3 0,0 0,3 3h270a3,3 0,0 0,3 -3L383.61,572.81a3,3 0,0 0,-3 -3Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M145.61,606.29m-21,0a21,21 0,1 1,42 0a21,21 0,1 1,-42 0"
|
||||
android:fillColor="#3f3d56"/>
|
||||
<path
|
||||
android:pathData="M194.11,592.29a3.5,3.5 0,0 0,0 7h165a3.5,3.5 0,1 0,0 -7Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M194.11,613.29a3.5,3.5 0,0 0,0 7h165a3.5,3.5 0,1 0,0 -7Z"
|
||||
android:fillColor="#e6e6e6"/>
|
||||
<path
|
||||
android:pathData="M239.93,394a94.96,94.96 0,0 1,-95 -95c0,-0.2 0,-0.41 0.01,-0.61 0.29,-52.03 42.9,-94.39 94.99,-94.39a95,95 0,1 1,0 190ZM239.93,206a93.2,93.2 0,0 0,-92.99 92.46c-0.01,0.21 -0.01,0.38 -0.01,0.54a93.01,93.01 0,1 0,93 -93Z"
|
||||
android:fillColor="#3f3d56"/>
|
||||
<path
|
||||
android:pathData="M282.95,296.81l-65.02,-37.54a2,2 0,0 0,-3 1.73L214.93,336.08a2,2 0,0 0,3 1.73l65.02,-37.54a2,2 0,0 0,0 -3.46l-65.02,-37.54a2,2 0,0 0,-3 1.73L214.93,336.08a2,2 0,0 0,3 1.73l65.02,-37.54a2,2 0,0 0,0 -3.46Z"
|
||||
android:fillColor="#6c63ff"/>
|
||||
<path
|
||||
android:pathData="M757.57,743.73H0v-2.18H757.96Z"
|
||||
android:fillColor="#3f3d56"/>
|
||||
<path
|
||||
android:pathData="M590.68,338.14m-27.94,0a27.94,27.94 0,1 1,55.87 0a27.94,27.94 0,1 1,-55.87 0"
|
||||
android:fillColor="#ffb8b8"/>
|
||||
<path
|
||||
android:pathData="M588.87,494.75a12.51,12.51 0,0 1,9.47 -16.1,11.89 11.89,0 0,1 1.66,-0.2l29.43,-47.23L602.55,405.66A10.73,10.73 0,1 1,617.47 390.25l37.11,36.6 0.08,0.09a9.72,9.72 0,0 1,-0.68 11.58L612.75,487.28a11.73,11.73 0,0 1,0.31 1.19,12.51 12.51,0 0,1 -11.23,14.92q-0.53,0.05 -1.06,0.05A12.55,12.55 0,0 1,588.87 494.75Z"
|
||||
android:fillColor="#ffb8b8"/>
|
||||
<path
|
||||
android:pathData="M544.67,726.93L530.72,726.93l-6.63,-53.79 20.58,0Z"
|
||||
android:fillColor="#ffb8b8"/>
|
||||
<path
|
||||
android:pathData="M548.79,741.02l-46.1,0L502.69,739.88a18.07,18.07 0,0 1,18.07 -18.07h28.03Z"
|
||||
android:fillColor="#2f2e41"/>
|
||||
<path
|
||||
android:pathData="M683.27,707.66l-11.98,7.14 -33.22,-42.82 17.68,-10.53Z"
|
||||
android:fillColor="#ffb8b8"/>
|
||||
<path
|
||||
android:pathData="M654.41,741.23l-0.58,-0.98a18.07,18.07 0,0 1,6.28 -24.77l24.08,-14.34 9.83,16.5Z"
|
||||
android:fillColor="#2f2e41"/>
|
||||
<path
|
||||
android:pathData="M522.33,703.25c-9.34,-109.99 -14.9,-212.18 19.25,-253.86l0.26,-0.32 57.47,22.99 0.09,0.2c0.19,0.42 19.31,42.46 14.85,70.74l14.18,65.21 46.22,77.39a5.12,5.12 0,0 1,-2.33 7.31l-20.09,8.84a5.14,5.14 0,0 1,-6.42 -2.01L595.53,617.75l-28.4,-62.88a1.71,1.71 0,0 0,-3.25 0.52L548.14,703.36a5.11,5.11 0,0 1,-5.09 4.58L527.43,707.94A5.15,5.15 0,0 1,522.33 703.25Z"
|
||||
android:fillColor="#2f2e41"/>
|
||||
<path
|
||||
android:pathData="M541.77,450.26l-0.27,-0.13 -0.04,-0.3c-2.15,-15.02 0.39,-31.72 7.55,-49.62a39.4,39.4 0,0 1,45.73 -23.59h0a39.35,39.35 0,0 1,25.09 19.3,38.92 38.92,0 0,1 2.7,31.19c-9.02,26.39 -20.73,51.08 -20.85,51.32l-0.25,0.51Z"
|
||||
android:fillColor="#6c63ff"/>
|
||||
<path
|
||||
android:pathData="M500.42,512.57a12.78,12.78 0,0 1,9.16 -13.94l53.74,-103.17a10.3,10.3 0,1 1,17.52 10.82L525.84,508.73a12.42,12.42 0,0 1,0.2 1.89,12.86 12.86,0 0,1 -13.03,13.21h0a12.87,12.87 0,0 1,-9.87 -4.83,12.71 12.71,0 0,1 -2.71,-6.43Z"
|
||||
android:fillColor="#ffb8b8"/>
|
||||
<path
|
||||
android:pathData="M556.81,322.35h44.36L601.16,303.02c-9.74,-3.87 -19.26,-7.16 -25.02,0a19.34,19.34 0,0 0,-19.34 19.34Z"
|
||||
android:fillColor="#2f2e41"/>
|
||||
<path
|
||||
android:pathData="M603.62,299.61c26.52,0 33.94,33.24 33.94,51.99 0,10.46 -4.73,14.2 -12.16,15.46l-2.63,-14 -6.15,14.6c-2.09,0.01 -4.28,-0.03 -6.55,-0.07l-2.08,-4.29 -4.65,4.22c-18.62,0.03 -33.66,2.74 -33.66,-15.92C569.68,332.85 576.19,299.61 603.62,299.61Z"
|
||||
android:fillColor="#2f2e41"/>
|
||||
<path
|
||||
android:pathData="M595.72,327L595.72,301.13a2.33,2.33 0,0 0,-2.33 -2.33h-4.67a2.33,2.33 0,0 0,-2.33 2.33L586.39,325.44a14.74,14.74 0,1 0,9.33 1.56Z"
|
||||
android:fillColor="#6c63ff"/>
|
||||
<path
|
||||
android:pathData="M589.5,340.01m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
||||
@@ -75,6 +75,39 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rating_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/vertical_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/player_media_quality_sector">
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/song_rating_bar"
|
||||
style="?android:attr/ratingBarStyleIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:numStars="5"
|
||||
android:stepSize="1"
|
||||
android:rating="0"
|
||||
android:isIndicator="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rating_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?attr/colorOnSurfaceVariant"
|
||||
android:text=""/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_media_title_label"
|
||||
style="@style/HeadlineLarge"
|
||||
@@ -349,11 +382,23 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/player_open_equalizer_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/ic_queue" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_open_equalizer_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/player_open_queue_button"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/ic_eq" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -47,6 +47,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/activity_info_offline_mode"
|
||||
android:textSize="6sp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
14
app/src/main/res/layout/dialog_starred_album_sync.xml
Normal file
14
app/src/main/res/layout/dialog_starred_album_sync.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/starred_album_sync_dialog_summary" />
|
||||
</LinearLayout>
|
||||
@@ -49,8 +49,9 @@
|
||||
<TextView
|
||||
android:id="@+id/subtitle_empty_description_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="56dp"
|
||||
android:paddingEnd="56dp"
|
||||
android:text="@string/download_info_empty_subtitle" />
|
||||
|
||||
105
app/src/main/res/layout/fragment_equalizer.xml
Normal file
105
app/src/main/res/layout/fragment_equalizer.xml
Normal file
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/eq_frame_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/eq_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/eq_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/equalizer_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/equalizer_fragment_title"
|
||||
style="@style/HeadlineSmall"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/equalizer_switch_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/equalizer_switch_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/LabelMedium"
|
||||
android:text="@string/equalizer_enable" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/equalizer_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/eq_bands_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/equalizer_reset_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/equalizer_reset"
|
||||
android:layout_gravity="center_horizontal"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_marginTop="24dp"/>
|
||||
|
||||
<Space
|
||||
android:id="@+id/equalizer_bottom_space"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:layout_marginTop="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/equalizer_not_supported_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/equalizer_not_supported_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:maxWidth="240dp"
|
||||
android:maxHeight="240dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ui_eq_not_supported" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/equalizer_not_supported_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/equalizer_not_supported"
|
||||
android:gravity="center"
|
||||
style="@style/BodyMedium"
|
||||
android:layout_marginTop="16dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -106,6 +106,98 @@
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Download/Sync starred albums -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/home_sync_starred_albums_card"
|
||||
style="?attr/materialCardViewOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<!-- Title, secondary and supporting text -->
|
||||
<TextView
|
||||
android:id="@+id/home_sync_starred_albums_title"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_sync_starred_albums_title"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
||||
android:textFontWeight="600"
|
||||
app:layout_constraintEnd_toStartOf="@id/vertical_guideline_albums"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_sync_starred_albums_subtitle"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_sync_starred_albums_subtitle"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/home_sync_starred_albums_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_sync_starred_albums_to_sync"
|
||||
style="@style/TitleSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:text="@string/home_sync_starred_albums_subtitle"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/home_sync_starred_albums_subtitle" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="end"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/home_sync_starred_albums_to_sync">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/home_sync_starred_albums_cancel"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/home_sync_starred_cancel" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/home_sync_starred_albums_download"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_sync_starred_download" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/vertical_guideline_albums"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.90" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Discover music -->
|
||||
<LinearLayout
|
||||
android:id="@+id/home_discover_sector"
|
||||
|
||||
@@ -81,14 +81,14 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintEnd_toStartOf="@+id/button_favorite"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/guideline" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/rating_container" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_artist_name_label"
|
||||
@@ -104,6 +104,39 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/player_media_title_label" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rating_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:scaleX="0.8"
|
||||
android:scaleY="0.8"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/guideline">
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/song_rating_bar"
|
||||
style="?android:attr/ratingBarStyleIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:numStars="5"
|
||||
android:stepSize="1"
|
||||
android:rating="0"
|
||||
android:isIndicator="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rating_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?attr/colorOnSurfaceVariant"
|
||||
android:text=""/>
|
||||
</LinearLayout>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/button_favorite"
|
||||
android:layout_width="26dp"
|
||||
@@ -136,7 +169,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:bar_height="2dp"
|
||||
app:buffered_color="?attr/colorOnSecondaryContainer"
|
||||
@@ -348,11 +381,23 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/player_open_equalizer_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/ic_queue" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_open_equalizer_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/player_open_queue_button"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/ic_eq" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -55,6 +55,27 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_art_overlay"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="#80000000"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/song_cover_image_view"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/play_pause_icon"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="28dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/different_disk_divider_sector" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/track_number_text_view"
|
||||
style="@style/LabelLarge"
|
||||
|
||||
@@ -20,6 +20,27 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_art_overlay"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:background="#80000000"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/play_pause_icon"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="14dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/queue_song_title_text_view"
|
||||
style="@style/LabelMedium"
|
||||
|
||||
@@ -11,4 +11,9 @@
|
||||
android:icon="@drawable/ic_add"
|
||||
android:title="@string/menu_add_to_playlist_button"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_rate_album"
|
||||
android:icon="@drawable/ic_add"
|
||||
android:title="@string/menu_rate_album"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
12
app/src/main/res/menu/artist_list_menu.xml
Normal file
12
app/src/main/res/menu/artist_list_menu.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:title="@string/search_title_artist"
|
||||
android:icon="@drawable/ic_search"
|
||||
app:showAsAction="ifRoom|collapseActionView"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView" />
|
||||
|
||||
</menu>
|
||||
@@ -151,6 +151,9 @@
|
||||
app:destination="@id/loginFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_equalizerFragment"
|
||||
app:destination="@id/equalizerFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/searchFragment"
|
||||
@@ -300,6 +303,20 @@
|
||||
android:id="@+id/action_indexFragment_to_directoryFragment"
|
||||
app:destination="@id/directoryFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/playerControllerFragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.PlayerControllerFragment"
|
||||
android:label="PlayerControllerFragment"
|
||||
tools:layout="@layout/inner_fragment_player_controller">
|
||||
<action
|
||||
android:id="@+id/action_playerControllerFragment_to_equalizerFragment"
|
||||
app:destination="@id/equalizerFragment"/>
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/equalizerFragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.EqualizerFragment"
|
||||
android:label="EqualizerFragment"
|
||||
tools:layout="@layout/fragment_equalizer" />
|
||||
<dialog
|
||||
android:id="@+id/songBottomSheetDialog"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog.SongBottomSheetDialog"
|
||||
|
||||
@@ -103,6 +103,9 @@
|
||||
<string name="home_rearrangement_dialog_positive_button">Sichern</string>
|
||||
<string name="home_rearrangement_dialog_title">Startseite anpassen</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Die Anwendung muss neu gestartet werden, um die Änderungen auszuführen.</string>
|
||||
<string name="home_section_music">Musik</string>
|
||||
<string name="home_section_podcast">Podcast</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Top Tracks Deiner Lieblingskünstler</string>
|
||||
<string name="home_subtitle_made_for_you">Ein Mix von einem deiner Lieblingslieder erstellen</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Radio hinzufügen</string>
|
||||
@@ -279,13 +282,14 @@
|
||||
<string name="settings_delete_download_storage_summary">Wenn Du weitermachst werden alle gespeicherten Inhalte unwiderruflich gelöscht.</string>
|
||||
<string name="settings_delete_download_storage_title">Gespeicherte Inhalte löschen</string>
|
||||
<string name="settings_download_storage_title">Download storage</string>
|
||||
<string name="settings_equalizer_summary">Audio Einstellungen anpassen</string>
|
||||
<string name="settings_equalizer_title">Equalizer</string>
|
||||
<string name="settings_system_equalizer_summary">Audio Einstellungen anpassen</string>
|
||||
<string name="settings_system_equalizer_title">System-Equalizer</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_title">Github</string>
|
||||
<string name="settings_image_size">Bilder Auflösung anpassen</string>
|
||||
<string name="settings_language">Sprache</string>
|
||||
<string name="settings_system_language">Systemsprache</string>
|
||||
<string name="settings_logout_title">Abmelden</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate für Downloads</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate bei mobiler Nutzung</string>
|
||||
@@ -392,6 +396,7 @@
|
||||
<string name="track_info_title">Titel</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Künstler</string>
|
||||
<string name="track_info_bit_depth">Bit-Tiefe</string>
|
||||
<string name="track_info_track_number">Track Nummer</string>
|
||||
<string name="track_info_year">Jahr</string>
|
||||
<string name="track_info_genre">Genre</string>
|
||||
@@ -402,6 +407,7 @@
|
||||
<string name="track_info_transcoded_suffix">Transkodiertes Suffix</string>
|
||||
<string name="track_info_duration">Länge</string>
|
||||
<string name="track_info_bitrate">Bitrate</string>
|
||||
<string name="track_info_sampling_rate">Abtastrate</string>
|
||||
<string name="track_info_path">Pfad</string>
|
||||
<string name="track_info_disc_number">Disk Nummer</string>
|
||||
<string name="track_info_summary_downloaded_file">Diese Datei wurde mit den Subsonic APIs heruntergeladen. Der Codec und die Bitrate sind unverändert zur original Datei.</string>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<string name="activity_battery_optimizations_summary">Por favor, desactive las optimizaciones de batería para continuar la reproducción multimedia mientras la pantalla está apagada.</string>
|
||||
<string name="activity_battery_optimizations_title">Optimizaciones de batería</string>
|
||||
<string name="activity_info_offline_mode">Modo sin conexión</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Añadir a la lista de reproducción</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Añadir a la cola</string>
|
||||
<string name="album_bottom_sheet_download_all">Descargar todo</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Ir al artista</string>
|
||||
@@ -68,7 +69,7 @@
|
||||
<string name="download_directory_dialog_positive_button">Descargar</string>
|
||||
<string name="download_directory_dialog_summary">Se descargarán todas las pistas de esta carpeta. Las pistas en las subcarpetas no se descargarán.</string>
|
||||
<string name="download_directory_dialog_title">Descargar las pistas</string>
|
||||
<string name="download_info_empty_subtitle">Una vez que descargues un tema, lo encontrarás aquí</string>
|
||||
<string name="download_info_empty_subtitle">Una vez que descargues una pista, la encontrarás aquí</string>
|
||||
<string name="download_info_empty_title">No hay descargas</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elementos</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s elementos</string>
|
||||
@@ -89,6 +90,7 @@
|
||||
<string name="exo_download_notification_channel_name">Descargas</string>
|
||||
<string name="filter_info_selection">Selecciona dos o más filtros</string>
|
||||
<string name="filter_title">Filtrar</string>
|
||||
<string name="filter_artist">Filtrar artistas</string>
|
||||
<string name="filter_title_expanded">Filtrar géneros</string>
|
||||
<string name="generic_list_page_count">(%1$d)</string>
|
||||
<string name="generic_list_page_count_unknown">(+%1$d)</string>
|
||||
@@ -115,6 +117,7 @@
|
||||
<string name="home_sync_starred_download">Descargar</string>
|
||||
<string name="home_sync_starred_subtitle">Descargar estas pistas usará una gran cantidad de datos</string>
|
||||
<string name="home_sync_starred_title">Parece que hay algunas pistas destacadas para sincronizar</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Los álbumes marcados como favoritos estarán disponibles en el modo sin conexión.</string>
|
||||
<string name="home_title_best_of">Lo mejor de</string>
|
||||
<string name="home_title_discovery">Descubrir</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Todo en aleatorio</string>
|
||||
@@ -159,7 +162,9 @@
|
||||
<string name="login_title_expanded">Servidores de Subsonic</string>
|
||||
<string name="media_route_menu_title">Emitir</string>
|
||||
<string name="menu_add_button">Añadir</string>
|
||||
<string name="menu_add_to_playlist_button">Añadir a la lista de reproducción</string>
|
||||
<string name="menu_download_all_button">Descargar todo</string>
|
||||
<string name="menu_rate_album">Valorar álbum</string>
|
||||
<string name="menu_download_label">Descargas</string>
|
||||
<string name="menu_filter_all">Todo</string>
|
||||
<string name="menu_filter_download">Descargado</string>
|
||||
@@ -195,13 +200,18 @@
|
||||
<string name="menu_sort_year">Año</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Limpiar la cola de reproducción</string>
|
||||
<string name="player_queue_save_queue_success">Cola de reproducción guardada</string>
|
||||
<string name="player_server_priority">Prioridad del servidor</string>
|
||||
<string name="player_unknown_format">Formato desconocido</string>
|
||||
<string name="player_transcoding">Transcodificando</string>
|
||||
<string name="player_transcoding_requested">solicitado</string>
|
||||
<string name="playlist_catalogue_title">Catálogo de listas de reproducción</string>
|
||||
<string name="playlist_catalogue_title_expanded">Explorar listas de reproducción</string>
|
||||
<string name="playlist_chooser_dialog_empty">No hay listas de reproducción</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Cancelar</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Crear</string>
|
||||
<string name="playlist_chooser_dialog_title">Añadir a una lista de reproducción</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Error al añadir a la lista</string>
|
||||
<string name="playlist_counted_tracks">%1$d pistas • %2$s</string>
|
||||
<string name="playlist_duration">Duración • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Pulsación larga para eliminar</string>
|
||||
@@ -244,11 +254,11 @@
|
||||
<string name="rating_dialog_negative_button">Cancelar</string>
|
||||
<string name="rating_dialog_positive_button">Guardar</string>
|
||||
<string name="rating_dialog_title">Valorar</string>
|
||||
<string name="search_hint">Buscar tema, artistas o álbumes</string>
|
||||
<string name="search_hint">Buscar pista, artistas o álbumes</string>
|
||||
<string name="search_info_minimum_characters">Introduzca al menos tres caracteres</string>
|
||||
<string name="search_title_album">Álbumes</string>
|
||||
<string name="settings_equalizer_summary">Ajustes de audio</string>
|
||||
<string name="settings_equalizer_title">Ecualizador</string>
|
||||
<string name="settings_system_equalizer_summary">Ajustes de audio</string>
|
||||
<string name="settings_system_equalizer_title">Ecualizador del sistema</string>
|
||||
<string name="search_title_artist">Artistas</string>
|
||||
<string name="search_title_song">Pistas</string>
|
||||
<string name="server_signup_dialog_action_low_security">Baja seguridad</string>
|
||||
@@ -303,6 +313,7 @@
|
||||
<string name="settings_podcast_summary">Si está habilitada, se mostrará la sección de pódcasts. Reinicia la aplicación para que los cambios surtan efecto.</string>
|
||||
<string name="settings_audio_quality">Mostrar calidad de audio</string>
|
||||
<string name="settings_audio_quality_summary">La tasa de bits y el formato de audio se mostrarán para cada pista de audio.</string>
|
||||
<string name="settings_song_rating_summary">Si está habilitada, muestra la valoración de la pista como barra de 5 estrellas en la página del control de reproducción.\n\n*Requiere reiniciar la aplicación</string>
|
||||
<string name="settings_item_rating">Mostrar valoración de los elementos</string>
|
||||
<string name="settings_queue_syncing_title">Sincronizar cola de reproducción para este usuario</string>
|
||||
<string name="settings_radio">Mostrar emisoras de radio</string>
|
||||
@@ -311,6 +322,7 @@
|
||||
<string name="settings_rounded_corner_size">Tamaño de las esquinas</string>
|
||||
<string name="settings_rounded_corner_summary">Si está habilitada, establece un ángulo de curvatura para todas las portadas de álbumes. Los cambios se aplicarán después de reiniciar la app.</string>
|
||||
<string name="settings_scan_title">Escanear biblioteca</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Cambiar la ubicación de los archivos en caché a otro almacenamiento puede causar el borrado de todos los archivos en caché en el anterior almacenamiento.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Seleccióna un tipo de almacenamiento</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Externo</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Interno</string>
|
||||
@@ -407,13 +419,26 @@
|
||||
<string name="settings_summary_transcoding">La prioridad que se aplica al modo de transcodificación. Si se selecciona \"Reproducción directa\", la tasa de bits del archivo no cambiará.</string>
|
||||
<string name="starred_sync_dialog_summary">Descargar las pistas destacadas podría consumir una gran cantidad de datos</string>
|
||||
<string name="starred_sync_dialog_title">Sincronizar las pistas destacadas</string>
|
||||
<string name="starred_album_sync_dialog_title">Sincronizar álbumes favoritos</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Para que los cambios tengan efecto, reinicia la app.</string>
|
||||
<string name="settings_summary_transcoding_download">Descarga los archivos multimedia transcodificados. Si esta opción está habilitada, no se usará el endpoint de descarga, sino las siguientes opciones.\n\nSi el formato de transcodificación para las descargas se establece en \"Descarga directa\", no se modificará la tasa de bits del archivo.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Cuando el archivo se transcodifica en tiempo real, el cliente normalmente no muestra la duración de la pista. Es posible solicitar a los servidores que soporten esta característica, que calculen la duración de la pista que se está reproduciendo, pero los tiempos de respuesta podrían aumentar.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sincronizar álbumes favoritos para uso sin conexión</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si está habilitada, las pistas destacadas se descargarán para uso sin conexión.</string>
|
||||
<string name="track_info_summary_downloaded_file">El archivo se ha descargado usando las APIs de Subsonic. El códec y la tasa de bits del archivo se mantienen sin cambios respecto al archivo de origen.</string>
|
||||
<string name="track_info_summary_full_transcode">La aplicación pedirá al servidor transcodificar el archivo y modificar su tasa de bits. El códec pedido por el usuario es %1$s, con una tasa de bits de %2$s. Cualquier cambio en el códec y tasa de bits del archivo en el formato elegido será manejado por el servidor, que puede, o no, soportar esta operación.</string>
|
||||
<string name="track_info_summary_original_file">La aplicación solo leerá el archivo original ofrecido por el servidor. La app pedirá al servidor, de forma explícita, el archivo sin transcodificar con la tasa de bits de origen.</string>
|
||||
<string name="track_info_summary_server_prioritized">La calidad del archivo a reproducir queda a decisión del servidor. La app no forzará la elección del códec y la tasa de bits para ninguna posible transcodificación.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">La aplicación pedirá al servidor modificar la tasa de bits del archivo. El usuario ha pedido una tasa de bits de %1$s, mientras que el códec del archivo origen se mantendrá sin cambios. Cualquier cambio en la tasa de bits del archivo en el formato seleccionado será realizado por el servidor, que podrá soportar, o no, la operación.</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Se ha añadido a la lista</string>
|
||||
<string name="settings_song_rating">Mostrar valoración de las pistas</string>
|
||||
<string name="home_sync_starred_albums_title">Sincronizar álbumes favoritos</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si está habilitada, los álbumes favoritos se descargarán para uso sin conexión.</string>
|
||||
<string name="starred_album_sync_dialog_summary">Descargar los álbumes favoritos puede consumir una gran cantidad de datos.</string>
|
||||
<string name="equalizer_fragment_title">Ecualizador</string>
|
||||
<string name="equalizer_reset">Restablecer</string>
|
||||
<string name="equalizer_enable">Habilitar</string>
|
||||
<string name="equalizer_not_supported">No disponible en este dispositivo</string>
|
||||
<string name="settings_app_equalizer">Ecualizador</string>
|
||||
<string name="settings_app_equalizer_summary">Abrir el ecualizador integrado</string>
|
||||
</resources>
|
||||
@@ -32,6 +32,21 @@
|
||||
<item>300</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="streaming_cache_size_titles">
|
||||
<item>Désactivé</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>Original</item>
|
||||
<item>32 kbps</item>
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
<string name="album_list_page_title">Albums</string>
|
||||
<string name="album_page_extra_info_button">Similaire</string>
|
||||
<string name="album_page_play_button">Lire</string>
|
||||
<string name="album_page_release_date_label">Sorti le %1$s</string>
|
||||
<string name="album_page_release_dates_label">Sorti le %1$s, initialement %2$s</string>
|
||||
<string name="album_page_shuffle_button">Mélanger</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d titres • %2$d minutes</string>
|
||||
<string name="app_name">Tempo</string>
|
||||
<string name="artist_adapter_radio_station_starting">Recherche…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Mix instantané</string>
|
||||
@@ -51,13 +54,16 @@
|
||||
<string name="connection_alert_dialog_negative_button">Annuler</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Activer l\'économie de données</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
<string name="connection_alert_dialog_summary">L\'accès au serveur Subsonic sur des connexions autres que le Wi-Fi ont été bloquées. Pour empêcher cette alerte de réapparaître, désactiver la vérification de la connexion dans les paramètres de l\'app.</string>
|
||||
<string name="connection_alert_dialog_summary">L\'accès au serveur Subsonic sur des connexions autres que le Wi-Fi a été bloqué. Pour empêcher cette alerte de réapparaître, désactiver la vérification de la connexion dans les paramètres de l\'app.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi déconnecté</string>
|
||||
<string name="content_description_shuffle_button">Mélanger</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Annuler</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continuer</string>
|
||||
<string name="delete_download_storage_dialog_summary">Sachez que la poursuite de cette action entraînera la suppression permanente de tous les éléments sauvegardés et téléchargés à partir de tous les serveurs</string>
|
||||
<string name="delete_download_storage_dialog_summary">Attention, la poursuite de cette action entraînera la suppression définitive de tous les éléments sauvegardés et téléchargés à partir de tous les serveurs</string>
|
||||
<string name="delete_download_storage_dialog_title">Supprimer les éléments téléchargés</string>
|
||||
<string name="description_empty_title">Aucune description disponible</string>
|
||||
<string name="disc_titlefull">Disque %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disque %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Annuler</string>
|
||||
<string name="download_directory_dialog_positive_button">Télécharger</string>
|
||||
<string name="download_directory_dialog_summary">Toutes les pistes dans ce dossier seront téléchargées. Les pistes dans les sous-dossiers ne seront pas téléchargées.</string>
|
||||
@@ -66,8 +72,9 @@
|
||||
<string name="download_info_empty_title">Aucun téléchargement pour l\'instant</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s éléments</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s éléments</string>
|
||||
<string name="download_shuffle_all_subtitle">Tout mélanger</string>
|
||||
<string name="download_storage_dialog_sub_summary">Redémarrez l\'application pour appliquer les changements.</string>
|
||||
<string name="download_storage_dialog_summary">Changer la destination des téléchargements d\'un espace de stockage à un autre résultera en la suppression immédiate de tous les fichiers précédemment téléchargés dans l\'autre espace de stockage.</string>
|
||||
<string name="download_storage_dialog_summary">Modifier le chemin de stockage des téléchargements entraînera la suppression immédiate de tous les fichiers précédemment téléchargés dans le nouvel espace de stockage.</string>
|
||||
<string name="download_storage_dialog_title">Sélectionnez l\'option de stockage</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Externe</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Interne</string>
|
||||
@@ -83,9 +90,25 @@
|
||||
<string name="exo_download_notification_channel_name">Téléchargements</string>
|
||||
<string name="filter_info_selection">Sélectionnez deux filtres ou plus</string>
|
||||
<string name="filter_title">Filtrer</string>
|
||||
<string name="filter_artist">Filtrer par artiste</string>
|
||||
<string name="filter_title_expanded">Filtrer par genre</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">Catalogue des Genres</string>
|
||||
<string name="genre_catalogue_title_expanded">Parcourir les Genres</string>
|
||||
<string name="github_update_dialog_negative_button">Me rappeler plus tard</string>
|
||||
<string name="github_update_dialog_neutral_button">Me soutenir</string>
|
||||
<string name="github_update_dialog_positive_button">Télécharger maintenant</string>
|
||||
<string name="github_update_dialog_summary">Une version plus récente de l\'app est disponible sur Github.</string>
|
||||
<string name="github_update_dialog_title">Mise à jour disponible</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">Annuler</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Réinitialiser</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Sauvegarder</string>
|
||||
<string name="home_rearrangement_dialog_title">Réorganiser l\'écran d\'accueil</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Veuillez noter que ces changements ne s\'appliqueront qu\'après redémarrage de l\'application.</string>
|
||||
<string name="home_section_music">Musique</string>
|
||||
<string name="home_section_podcast">Podcast</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Meilleurs morceaux de vos artistes préférés</string>
|
||||
<string name="home_subtitle_made_for_you">Commencez le mix à partir d\'une chanson que vous aimez</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Ajouter une radio</string>
|
||||
@@ -94,6 +117,8 @@
|
||||
<string name="home_sync_starred_download">Télécharger</string>
|
||||
<string name="home_sync_starred_subtitle">Télécharger ces titres peut entraîner une utilisation importante de données</string>
|
||||
<string name="home_sync_starred_title">On dirait qu\'il y a des titres favoris à synchroniser</string>
|
||||
<string name="home_sync_starred_albums_title">Synchroniser les albums favoris</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Les albums marqués d\'une étoile seront disponibles hors-ligne</string>
|
||||
<string name="home_title_best_of">Best of</string>
|
||||
<string name="home_title_discovery">Découverte</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Tout mélanger</string>
|
||||
@@ -102,11 +127,14 @@
|
||||
<string name="home_title_last_played">Écouté dernièrement</string>
|
||||
<string name="home_title_last_played_see_all_button">Voir tout</string>
|
||||
<string name="home_title_last_week">Sur la dernière semaine</string>
|
||||
<string name="home_title_last_month">Sur le dernier mois</string>
|
||||
<string name="home_title_last_year">Sur la dernière année</string>
|
||||
<string name="home_title_made_for_you">Faits pour vous</string>
|
||||
<string name="home_title_most_played">Les plus écoutés</string>
|
||||
<string name="home_title_most_played_see_all_button">Voir tout</string>
|
||||
<string name="home_title_new_releases">Nouvelles sorties</string>
|
||||
<string name="home_title_newest_podcasts">Nouveau podcasts</string>
|
||||
<string name="home_title_pinned_playlists">Playlists</string>
|
||||
<string name="home_title_podcast_channels">Chaînes</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Voir tout</string>
|
||||
<string name="home_title_radio_station">Stations radio</string>
|
||||
@@ -120,6 +148,7 @@
|
||||
<string name="home_title_starred_tracks">★ Titres favoris</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Voir tout</string>
|
||||
<string name="home_title_top_songs">Vos morceaux préférés</string>
|
||||
<string name="home_option_reorganize">Réorganiser</string>
|
||||
<string name="library_title_album">Albums</string>
|
||||
<string name="library_title_album_see_all_button">Voir tout</string>
|
||||
<string name="library_title_artist">Artistes</string>
|
||||
@@ -136,6 +165,7 @@
|
||||
<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_rate_album">Noter l\'album</string>
|
||||
<string name="menu_download_label">Téléchargé</string>
|
||||
<string name="menu_filter_all">Tout</string>
|
||||
<string name="menu_filter_download">Téléchargé</string>
|
||||
@@ -144,25 +174,42 @@
|
||||
<string name="menu_group_by_genre">Genre</string>
|
||||
<string name="menu_group_by_track">Piste</string>
|
||||
<string name="menu_group_by_year">Année</string>
|
||||
<string name="menu_home_label">Home</string>
|
||||
<string name="menu_library_label">Librairie</string>
|
||||
<string name="menu_home_label">Accueil</string>
|
||||
<string name="menu_last_week_name">Sur la dernière semaine</string>
|
||||
<string name="menu_last_month_name">Sur le dernier mois</string>
|
||||
<string name="menu_last_year_name">Sur la dernière année</string>
|
||||
<string name="menu_library_label">Bibliothèque</string>
|
||||
<string name="menu_search_button">Rechercher</string>
|
||||
<string name="menu_settings_button">Paramètres</string>
|
||||
<string name="menu_sort_artist">Artiste</string>
|
||||
<string name="menu_sort_name">Nom</string>
|
||||
<string name="menu_sort_random">Aléatoire</string>
|
||||
<string name="menu_sort_recently_added">Récemment ajoutés</string>
|
||||
<string name="menu_sort_recently_played">Récemment lus</string>
|
||||
<string name="menu_sort_most_played">Plus lus</string>
|
||||
<string name="menu_sort_most_recently_starred">Favoris les plus récents</string>
|
||||
<string name="menu_sort_least_recently_starred">Favoris les plus anciens</string>
|
||||
<string name="menu_pin_button">Ajouter à l\'écran d\'accueil</string>
|
||||
<string name="menu_unpin_button">Retirer de l\'écran d\'accueil</string>
|
||||
<string name="menu_sort_year">Année</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Vider la file d\'attente</string>
|
||||
<string name="player_queue_save_queue_success">File d\'attente sauvegardée</string>
|
||||
<string name="player_server_priority">Priorité serveur</string>
|
||||
<string name="player_unknown_format">Format inconnu</string>
|
||||
<string name="player_transcoding">Transcodage</string>
|
||||
<string name="player_transcoding_requested">demandé</string>
|
||||
<string name="playlist_catalogue_title">Catalogue des Playlists</string>
|
||||
<string name="playlist_catalogue_title_expanded">Parcourir les playlists</string>
|
||||
<string name="playlist_chooser_dialog_empty">Pas de playlist</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Annuler</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Créer</string>
|
||||
<string name="playlist_chooser_dialog_title">Ajouter à une playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Titre ajouté à la playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Échec d\'ajout du titre à la playlist</string>
|
||||
<string name="playlist_counted_tracks">%1$d titres • %2$s</string>
|
||||
<string name="playlist_duration">Durée • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Appui long pour supprimer</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nom de la playlist</string>
|
||||
<string name="playlist_editor_dialog_negative_button">Annuler</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Supprimer</string>
|
||||
@@ -208,6 +255,8 @@
|
||||
<string name="search_title_artist">Artistes</string>
|
||||
<string name="search_title_song">Pistes</string>
|
||||
<string name="server_signup_dialog_action_low_security">Sécurité basse</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Appui long pour supprimer</string>
|
||||
<string name="server_signup_dialog_hint_local_address">URL local</string>
|
||||
<string name="server_signup_dialog_hint_name">Nom du serveur</string>
|
||||
<string name="server_signup_dialog_hint_password">Mot de passe</string>
|
||||
<string name="server_signup_dialog_hint_url">URL du serveur</string>
|
||||
@@ -221,8 +270,9 @@
|
||||
<string name="server_unreachable_dialog_positive_button">Continuer quand même</string>
|
||||
<string name="server_unreachable_dialog_summary">Le serveur est injoignable. Si vous décidez de continuer, cette fenêtre n\'apparaîtra plus pendant une heure.</string>
|
||||
<string name="server_unreachable_dialog_title">Serveur injoignable</string>
|
||||
<string name="settings_about_summary">Tempo est un client open source et léger pour Subsonic, développé et build nativement pour Android.</string>
|
||||
<string name="settings_about_summary">Tempo est un client open source et léger pour Subsonic, développé et compilé nativement pour Android.</string>
|
||||
<string name="settings_about_title">À propos</string>
|
||||
<string name="settings_always_on_display">Toujours visible</string>
|
||||
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempo ne forcera pas le téléchargement de la piste avec les paramètres de transcodage ci-dessous.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Prioriser les paramètres du serveurs, utilisés pour le streaming, dans les téléchargements</string>
|
||||
@@ -238,28 +288,36 @@
|
||||
<string name="settings_audio_transcode_priority_toast">La priorité au transcodage de la piste est donnée au serveur</string>
|
||||
<string name="settings_buffering_strategy">Stratégie de mise en mémoire tampon</string>
|
||||
<string name="settings_buffering_strategy_summary">Redémarrez l\'application pour appliquer les changements.</string>
|
||||
<string name="settings_continuous_play_summary">Permet de prolonger la lecture après la fin d\'une playlist avec des titres similaires</string>
|
||||
<string name="settings_continuous_play_title">Lecture continue</string>
|
||||
<string name="settings_covers_cache">Taille du cache des illustrations</string>
|
||||
<string name="settings_data_saving_mode_summary">Pour réduire la consommation de données, éviter de télécharger les illustrations.</string>
|
||||
<string name="settings_data_saving_mode_title">Limiter l\'utilisation des données mobiles</string>
|
||||
<string name="settings_delete_download_storage_summary">Continuer entraînera la suppression irréversible de tous les éléments sauvegardés.</string>
|
||||
<string name="settings_delete_download_storage_title">Supprimer les éléments sauvegardés</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_title">Égaliseur</string>
|
||||
<string name="settings_system_equalizer_summary">Ajuster les paramètres audios</string>
|
||||
<string name="settings_system_equalizer_title">Égaliseur du système</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_title">Github</string>
|
||||
<string name="settings_image_size">Définir la résolution des images</string>
|
||||
<string name="settings_language">Langue</string>
|
||||
<string name="settings_logout_title">Se déconnecter</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate pour les téléchargements</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate en données mobile</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate en Wi-Fi</string>
|
||||
<string name="settings_media_cache">Taille du cache des fichiers audios</string>
|
||||
<string name="settings_max_bitrate_download">Débit binaire pour les téléchargements</string>
|
||||
<string name="settings_max_bitrate_mobile">Débit binaire en données mobile</string>
|
||||
<string name="settings_max_bitrate_wifi">Débit binaire en Wi-Fi</string>
|
||||
<string name="settings_media_cache">Taille du cache des fichiers audio</string>
|
||||
<string name="settings_music_directory">Afficher les dossiers</string>
|
||||
<string name="settings_music_directory_summary">Si activé, rend possible la navigation dans les répertoires. À noter que pour que la navigation dans les dossiers fonctionne correctement, le serveur doit supporter cette fonctionnalité.</string>
|
||||
<string name="settings_podcast">Voir les podcasts</string>
|
||||
<string name="settings_podcast_summary">Si activé, rend visible la section Podcast</string>
|
||||
<string name="settings_podcast_summary">Si activé, rend visible la section Podcast. Redémarrez l\'application pour appliquer ce paramètre.</string>
|
||||
<string name="settings_audio_quality">Afficher la qualité audio</string>
|
||||
<string name="settings_audio_quality_summary">Le débit binaire et le format audio seront affichés pour chaque piste.</string>
|
||||
<string name="settings_song_rating">Afficher la note de la piste</string>
|
||||
<string name="settings_song_rating_summary">Si activé, rend visible la note de la piste sur sa page\n\n*Nécessite le redémarrage de l\'application</string>
|
||||
<string name="settings_item_rating">Afficher la note</string>
|
||||
<string name="settings_item_rating_summary">Si activé, la note et le statut de mise en favori de l\'élément seront affichés.</string>
|
||||
<string name="settings_queue_syncing_countdown">Minuteur de synchronisation</string>
|
||||
<string name="settings_queue_syncing_summary">Si activé, l\'utilisateur pourra sauvegarder sa file d\'attente et la recharger au démarrage de l\'application.</string>
|
||||
<string name="settings_queue_syncing_title">Synchroniser la file d\'attente pour cet utilisateur</string>
|
||||
@@ -272,16 +330,22 @@
|
||||
<string name="settings_rounded_corner_summary">Si activé, arrondi les angles des illustrations. Les modifications prendront effet au redémarrage.</string>
|
||||
<string name="settings_scan_title">Scanner la bibliothèque</string>
|
||||
<string name="settings_scrobble_title">Activer le scrobbling</string>
|
||||
<string name="settings_system_language">Langue du système</string>
|
||||
<string name="settings_share_title">Activer le partage de musique</string>
|
||||
<string name="settings_streaming_cache_size">Taille du cache de streaming</string>
|
||||
<string name="settings_streaming_cache_storage_title">Emplacement du cache de streaming</string>
|
||||
<string name="settings_sub_summary_scrobble">À noter que le scrobbling doit être activé sur le serveur pour qu\'il puisse recevoir ces données</string>
|
||||
<string name="settings_summary_skip_min_star_rating">Lors de l\'écoute de la radio d\'un artiste, d\'un mix instantané ou de tout la bibliothèque en aléatoire, les pistes en dessous d\'une certaine note seront ignorées.</string>
|
||||
<string name="settings_summary_replay_gain">Le Replay Gain est une fonctionnalité qui vous permet d\'ajuster le volume des pistes audio pour une expérience d\'écoute cohérente. Fonctionne uniquement si la piste contient les métadonnées nécessaires.</string>
|
||||
<string name="settings_summary_scrobble">Le scrobbling permet à votre appareil d\'envoyer des informations sur les musiques que vous écoutez au serveur afin de créer des recommendations personnalisées basées sur vos préférences musicales.</string>
|
||||
<string name="settings_summary_share">Permet à l\'utilisateur de partager de la musique via un lien. Cette fonctionnalité doit être supportée et activée sur le serveur et est limitée aux pistes, albums et playlists individuellement.</string>
|
||||
<string name="settings_summary_syncing">Renvoie l\'état de la file d\'attente de cet utilisateur. Cela inclut les pistes dans la file, la piste actuellement écoutée et la position dans la piste. Cette fonctionnalité doit être supportée par le serveur.</string>
|
||||
<string name="settings_summary_transcoding">Le mode de transcodage à prioriser. Si reglé sur \"Lecture directe\", le bitrate du fichier ne sera pas modifié.</string>
|
||||
<string name="settings_summary_transcoding_download">Télécharge les médias transcodés. Si activé, les paramètres de transcodage suivants seront utilisés pour les téléchargements.\n\n Si le format de transcodage est reglé à \"Téléchargement direct\", le bitrate du fichier ne sera pas modifé.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quand le fichier est transcodé à la volé, en général, le client n\'affiche pas la durée de la piste. Il est possible de demander aux serveurs qui le supportent d\'estimer la durée de la piste écoutée, mais les temps de réponses peuvent être plus longs.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nUtilisé actuellement : %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Le mode de transcodage à prioriser. Si réglé sur \"Lecture directe\", le débit binaire du fichier ne sera pas modifié.</string>
|
||||
<string name="settings_summary_transcoding_download">Télécharge les médias transcodés. Si activé, les paramètres de transcodage suivants seront utilisés pour les téléchargements.\n\n Si le format de transcodage est reglé à \"Téléchargement direct\", le débit binaire du fichier ne sera pas modifé.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quand le fichier est transcodé à la volée, en général, le client n\'affiche pas la durée de la piste. Il est possible de demander aux serveurs qui le supportent d\'estimer la durée de la piste écoutée, mais les temps de réponses peuvent être plus longs.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si activé, les albums favoris seront téléchargés pour l\'écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Synchronisation des albums favoris pour écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si activé, les pistes favorites seront téléchargées pour l\'écoute hors-ligne</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Synchronisation des pistes favorites pour écoute hors-ligne</string>
|
||||
<string name="settings_theme">Thème</string>
|
||||
@@ -291,7 +355,7 @@
|
||||
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
<string name="settings_title_skip_min_star_rating">Ignorer des musiques selon leur note</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Musiques avec une note de:</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Musiques avec une note de :</string>
|
||||
<string name="settings_title_share">Partage</string>
|
||||
<string name="settings_title_syncing">Synchronisation</string>
|
||||
<string name="settings_title_transcoding">Transcodage</string>
|
||||
@@ -334,11 +398,20 @@
|
||||
<string name="starred_sync_dialog_negative_button">Annuler</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continuer</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continuer et télécharger</string>
|
||||
<string name="starred_sync_dialog_summary">Le téléchargement des titres favoris pourrer utiliser beaucoup de données.</string>
|
||||
<string name="starred_sync_dialog_summary">Le téléchargement des titres favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_sync_dialog_title">Synchroniser les titres favoris</string>
|
||||
<string name="starred_album_sync_dialog_summary">Le téléchargement des titres favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_album_sync_dialog_title">Synchroniser les albums favoris</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Veuillez redémarrer l\'app pour appliquer les changements.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Modifier le chemin de stockage des fichiers mis en cache risque de provoquer la suppression de tous les fichiers précédemment mis en cache dans le nouvel espace de stockage.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Sélectionner une option de stockage</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Externe</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Interne</string>
|
||||
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Artiste</string>
|
||||
<string name="track_info_bitrate">Bitrate</string>
|
||||
<string name="track_info_bit_depth">Résolution audio</string>
|
||||
<string name="track_info_bitrate">Débit binaire</string>
|
||||
<string name="track_info_content_type">Type de contenu</string>
|
||||
<string name="track_info_dialog_positive_button">OK</string>
|
||||
<string name="track_info_dialog_title">Infos piste</string>
|
||||
@@ -346,14 +419,15 @@
|
||||
<string name="track_info_duration">Durée</string>
|
||||
<string name="track_info_genre">Genre</string>
|
||||
<string name="track_info_path">Chemin</string>
|
||||
<string name="track_info_sampling_rate">Fréquence d\'échantillonnage</string>
|
||||
<string name="track_info_size">Taille</string>
|
||||
<string name="track_info_suffix">Suffixe</string>
|
||||
<string name="track_info_summary_downloaded_file">Le fichier a été téléchargé depuis les APIs Subsonic. Le codec et le bitrate du fichier demeure inchangé du fichier d\'origine.</string>
|
||||
<string name="track_info_summary_full_transcode">L\'application demandera au serveur de transcoder le fichier et de modifier son bitrate. Le codec demandé par l\'utilisateur est %1$s, avec un bitrate de %2$s. Toute modification éventuelle du codec et du bitrate du fichier dans le format choisi sera gérée par le serveur, qui peut ou non prendre en charge l\'opération.</string>
|
||||
<string name="track_info_summary_original_file">L\'application ne lira que le fichier original tel que fourni par le serveur. L\'application demandera explicitement au serveur le fichier non transcodé avec le bitrate de la source originale.</string>
|
||||
<string name="track_info_summary_server_prioritized">La qualité du fichier à lire est laissée à l\'appréciation du serveur. L\'application n\'impose pas le choix du codec et du bitrate pour un éventuel transcodage.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">L\'application demandera au serveur de modifier le bitrate du fichier. L\'utilisateur a choisi un bitrate de %1$s, tandis que le codec du fichier restera le même. Toute modification du bitrate du fichier dans le format choisi sera effectuée par le serveur, qui peut ou non prendre en charge l\'opération. </string>
|
||||
<string name="track_info_summary_transcoding_codec">L\'application demandera au serveur de transcoder le fichier. Le codec choisi par l\'utilisateur est le %1$s, tandis que le bitrate sera le même que celui du fichier source. Le transcodage éventuel du fichier dans le codec choisi dépend du serveur, qui peut ou non prendre en charge l\'opération.</string>
|
||||
<string name="track_info_summary_downloaded_file">Le fichier a été téléchargé depuis les APIs Subsonic. Le codec et le débit binaire du fichier demeurent identiques à ceux du fichier d\'origine.</string>
|
||||
<string name="track_info_summary_full_transcode">L\'application demandera au serveur de transcoder le fichier et de modifier son débit binaire. Le codec demandé par l\'utilisateur est %1$s, avec un débit binaire de %2$s. Toute modification éventuelle du codec et du débit binaire du fichier dans le format choisi sera gérée par le serveur, qui peut ou non prendre en charge l\'opération.</string>
|
||||
<string name="track_info_summary_original_file">L\'application ne lira que le fichier original tel que fourni par le serveur. L\'application demandera explicitement au serveur le fichier non transcodé avec le débit binaire de la source originale.</string>
|
||||
<string name="track_info_summary_server_prioritized">La qualité du fichier à lire est laissée à l\'appréciation du serveur. L\'application n\'impose pas le choix du codec et du débit binaire pour un éventuel transcodage.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">L\'application demandera au serveur de modifier le débit binaire du fichier. L\'utilisateur a choisi un débit binaire de %1$s, tandis que le codec du fichier restera le même. Toute modification du débit binaire du fichier dans le format choisi sera effectuée par le serveur, qui peut ou non prendre en charge l\'opération. </string>
|
||||
<string name="track_info_summary_transcoding_codec">L\'application demandera au serveur de transcoder le fichier. Le codec choisi par l\'utilisateur est le %1$s, tandis que le débit binaire sera le même que celui du fichier source. Le transcodage éventuel du fichier dans le codec choisi dépend du serveur, qui peut ou non prendre en charge l\'opération.</string>
|
||||
<string name="track_info_title">Titre</string>
|
||||
<string name="track_info_track_number">Numéro de piste</string>
|
||||
<string name="track_info_transcoded_content_type">Transcodé type de contenu</string>
|
||||
@@ -362,4 +436,8 @@
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Un grand merci à unDraw, nous n\'aurions pas pu rendre cette application aussi belle sans leurs illustrations.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album à synchroniser</item>
|
||||
<item quantity="other">%d albums à synchroniser</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
<string name="menu_sort_year">Anno</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Svuota coda di riproduzione</string>
|
||||
<string name="player_queue_save_queue_success">Salvato</string>
|
||||
<string name="player_server_priority">Priorità server</string>
|
||||
<string name="playlist_catalogue_title">Catalogo playlist</string>
|
||||
<string name="playlist_catalogue_title_expanded">Sfoglia le playlist</string>
|
||||
@@ -281,8 +282,8 @@
|
||||
<string name="settings_delete_download_storage_summary">Continuando, tutti gli elementi salvati verranno eliminati in modo irreversibile.</string>
|
||||
<string name="settings_delete_download_storage_title">Elimina elementi salvati</string>
|
||||
<string name="settings_download_storage_title">Archivio download</string>
|
||||
<string name="settings_equalizer_summary">Regola le impostazioni audio</string>
|
||||
<string name="settings_equalizer_title">Equalizzatore</string>
|
||||
<string name="settings_system_equalizer_summary">Regola le impostazioni audio</string>
|
||||
<string name="settings_system_equalizer_title">Equalizzatore di sistema</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_title">Github</string>
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
<string name="album_list_page_title">앨범</string>
|
||||
<string name="album_page_extra_info_button">유사항목 더 보기</string>
|
||||
<string name="album_page_play_button">재생</string>
|
||||
<string name="album_page_release_dates_label">%1$s에 발매, %2$s에 최초 발매됨</string>
|
||||
<string name="album_page_shuffle_button">셔플</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d 곡 • %2$d 분</string>
|
||||
<string name="app_name">Tempo</string>
|
||||
<string name="artist_adapter_radio_station_starting">탐색 중…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">인스턴트 믹스</string>
|
||||
@@ -53,11 +55,14 @@
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
<string name="connection_alert_dialog_summary">Wi-Fi가 연결되지 않은 상태에서 Subsonic 서버에 대한 액세스가 제한되었습니다. 이 경고를 다시 보지 않으려면 앱 설정에서 연결 확인을 비활성화 해주세요.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi가 연결되지 않음</string>
|
||||
<string name="content_description_shuffle_button">셔플</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">취소</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">계속</string>
|
||||
<string name="delete_download_storage_dialog_summary">계속할 시 서버에서 다운로드한 모든 저장 항목이 영구적으로 삭제됩니다.</string>
|
||||
<string name="delete_download_storage_dialog_title">저장된 항목 삭제</string>
|
||||
<string name="description_empty_title">설명 란이 비어있습니다.</string>
|
||||
<string name="disc_titlefull">디스크 %1$s - %2$s</string>
|
||||
<string name="disc_titleless">디스크 %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">취소</string>
|
||||
<string name="download_directory_dialog_positive_button">다운로드</string>
|
||||
<string name="download_directory_dialog_summary">하위 폴더를 제외한 해당 폴더의 모든 트랙이 다운로드됩니다.</string>
|
||||
@@ -66,9 +71,10 @@
|
||||
<string name="download_info_empty_title">다운로드 하지 않음</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s 항목</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s 항목</string>
|
||||
<string name="download_shuffle_all_subtitle">모두 셔플</string>
|
||||
<string name="download_storage_dialog_sub_summary">변경 사항을 저장하려면 앱을 다시 시작하세요.</string>
|
||||
<string name="download_storage_dialog_summary">>다운로드한 파일을 다른 저장소로 변경하면 기존 저장소에서 다운로드한 파일은 즉시 삭제됩니다.</string>
|
||||
<string name="download_storage_dialog_title">저장소 선택 옵션</string>
|
||||
<string name="download_storage_dialog_summary">다운로드한 파일을 다른 저장소로 변경하면 기존 저장소에서 다운로드한 파일은 즉시 삭제됩니다.</string>
|
||||
<string name="download_storage_dialog_title">저장소 옵션 선택</string>
|
||||
<string name="download_storage_external_dialog_positive_button">외부</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">내부</string>
|
||||
<string name="download_title_section">다운로드</string>
|
||||
@@ -83,9 +89,11 @@
|
||||
<string name="exo_download_notification_channel_name">다운로드</string>
|
||||
<string name="filter_info_selection">둘 이상의 필터를 선택해 주세요.</string>
|
||||
<string name="filter_title">필터</string>
|
||||
<string name="filter_artist">아티스트 필터링</string>
|
||||
<string name="filter_title_expanded">장르 필터링</string>
|
||||
<string name="genre_catalogue_title">장르 카탈로그</string>
|
||||
<string name="genre_catalogue_title_expanded">장르 찾아보기</string>
|
||||
<string name="home_section_radio">라디오</string>
|
||||
<string name="home_subtitle_best_of">최애 아티스트의 인기곡</string>
|
||||
<string name="home_subtitle_made_for_you">좋아하는 음악으로 믹스를 시작해 보세요.</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">새 라디오 추가</string>
|
||||
@@ -102,11 +110,13 @@
|
||||
<string name="home_title_last_played">최근 재생</string>
|
||||
<string name="home_title_last_played_see_all_button">모두 보기</string>
|
||||
<string name="home_title_last_week">지난 주</string>
|
||||
<string name="home_title_last_year">지난 해</string>
|
||||
<string name="home_title_made_for_you">Made for you</string>
|
||||
<string name="home_title_most_played">가장 많이 재생</string>
|
||||
<string name="home_title_most_played_see_all_button">모두 보기</string>
|
||||
<string name="home_title_new_releases">New releases</string>
|
||||
<string name="home_title_newest_podcasts">새로운 팟캐스트</string>
|
||||
<string name="home_title_pinned_playlists">재생목록</string>
|
||||
<string name="home_title_podcast_channels">채널</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">모두 보기</string>
|
||||
<string name="home_title_radio_station">라디오 스테이션</string>
|
||||
@@ -120,8 +130,7 @@
|
||||
<string name="home_title_starred_tracks">★ 즐겨찾기한 트랙</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">모두 보기</string>
|
||||
<string name="home_title_top_songs">자주 플레이한 음악</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="home_option_reorganize">다시 정렬</string>
|
||||
<string name="library_title_album">앨범</string>
|
||||
<string name="library_title_album_see_all_button">모두 보기</string>
|
||||
<string name="library_title_artist">아티스트</string>
|
||||
@@ -138,6 +147,7 @@
|
||||
<string name="menu_add_button">추가</string>
|
||||
<string name="menu_add_to_playlist_button">플레이리스트에 추가</string>
|
||||
<string name="menu_download_all_button">모두 다운로드</string>
|
||||
<string name="menu_rate_album">앨범 평점 매기기</string>
|
||||
<string name="menu_download_label">다운로드</string>
|
||||
<string name="menu_filter_all">모두</string>
|
||||
<string name="menu_filter_download">다운로드한</string>
|
||||
@@ -146,13 +156,15 @@
|
||||
<string name="menu_group_by_genre">장르</string>
|
||||
<string name="menu_group_by_track">트랙</string>
|
||||
<string name="menu_group_by_year">년도</string>
|
||||
<string name="menu_home_label">홈으로</string>
|
||||
<string name="menu_home_label">홈</string>
|
||||
<string name="menu_last_year_name">지난 해</string>
|
||||
<string name="menu_library_label">라이브러리</string>
|
||||
<string name="menu_search_button">검색</string>
|
||||
<string name="menu_settings_button">셋팅</string>
|
||||
<string name="menu_settings_button">설정</string>
|
||||
<string name="menu_sort_artist">아티스트</string>
|
||||
<string name="menu_sort_name">이름</string>
|
||||
<string name="menu_sort_random">랜덤</string>
|
||||
<string name="menu_unpin_button">홈화면에서 제거</string>
|
||||
<string name="menu_sort_year">년도</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">재생목록 비우기</string>
|
||||
@@ -163,10 +175,11 @@
|
||||
<string name="playlist_chooser_dialog_negative_button">취소</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">생성</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_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_duration">재생시간 • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">길게 눌러 삭제하기</string>
|
||||
<string name="playlist_editor_dialog_hint_name">플레이리스트 이름</string>
|
||||
<string name="playlist_editor_dialog_negative_button">취소</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">삭제</string>
|
||||
@@ -248,8 +261,8 @@
|
||||
<string name="settings_delete_download_storage_summary">계속하면 저장된 모든 항목을 완전히 삭제합니다.</string>
|
||||
<string name="settings_delete_download_storage_title">저장된 항목 삭제</string>
|
||||
<string name="settings_download_storage_title">스토리지 다운로드</string>
|
||||
<string name="settings_equalizer_summary">오디오 설정 적용</string>
|
||||
<string name="settings_equalizer_title">이퀄라이저</string>
|
||||
<string name="settings_system_equalizer_summary">오디오 설정 적용</string>
|
||||
<string name="settings_system_equalizer_title">시스템 이퀄라이저</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_title">Github</string>
|
||||
@@ -264,6 +277,7 @@
|
||||
<string name="settings_music_directory_summary">활성화 시, 음악 디렉터리 섹션을 표시합니다. 폴더 탐색이 제대로 작동하려면 서버가 이 기능을 지원해야 합니다.</string>
|
||||
<string name="settings_podcast">팟캐스트 보기</string>
|
||||
<string name="settings_podcast_summary">활성화 시, 팟캐스트 섹션을 표시합니다.</string>
|
||||
<string name="settings_item_rating_summary">활성화 시 평점과 즐겨찾기 여부가 표시됩니다</string>
|
||||
<string name="settings_queue_syncing_countdown">동기화 타이머</string>
|
||||
<string name="settings_queue_syncing_summary">활성화 시, 재생목록을 저장하여 재실행 시 상태를 불러올 수 있습니다.</string>
|
||||
<string name="settings_queue_syncing_title">사용자의 재생목록 동기화</string>
|
||||
@@ -276,16 +290,21 @@
|
||||
<string name="settings_rounded_corner_summary">활성화 시, 렌더링된 모든 앨범 커버의 곡률 각도를 설정합니다. 다시 시작하면 적용됩니다.</string>
|
||||
<string name="settings_scan_title">라이브러리 스캔</string>
|
||||
<string name="settings_scrobble_title">음악 스크로블링 활성화</string>
|
||||
<string name="settings_system_language">시스템 언어</string>
|
||||
<string name="settings_share_title">음악 공유 활성화</string>
|
||||
<string name="settings_streaming_cache_storage_title">스트리밍 캐시 저장공간</string>
|
||||
<string name="settings_sub_summary_scrobble">스크로블링은 이 데이터를 수신할 수 있는 서버에 의존합니다.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">아티스트의 라디오를 들을 때, 인스턴트 믹스를 들을 때, 전체를 셔플할 때 특정 별점 이하의 트랙은 무시됩니다.</string>
|
||||
<string name="settings_summary_replay_gain">Replay gain은 일관된 청취 경험을 위해 오디오 트랙의 볼륨 레벨을 조정할 수 있는 기능입니다. 이 설정은 필요한 메타데이터가 트랙에 포함된 경우에만 유효합니다.</string>
|
||||
<string name="settings_summary_scrobble">스크로블링은 기기에서 들은 음악 정보를 음악 서버로 보내는 기능입니다. 이 정보는 음악 선호도에 따른 맞춤 추천을 생성하는 데 사용합니다.</string>
|
||||
<string name="settings_summary_share">링크를 통해 음악을 공유할 수 있습니다. 이 기능은 서버 측에서 지원 및 활성화되어야 하며 개별 트랙, 앨범, 재생 목록으로 제한됩니다.</string>
|
||||
<string name="settings_summary_syncing">사용자의 재생목록의 상태를 반환합니다. 재생목록의 트랙, 현재 재생 중인 트랙, 트랙 번호가 포함됩니다. 서버가 이 기능을 지원해야 합니다.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \n사용 중: %2$s MiB </string>
|
||||
<string name="settings_summary_transcoding">트랜스코딩 모드에 우선순위가 부여됩니다. \"직접 재생\"으로 설정하면 파일의 비트 전송률이 변경되지 않습니다.</string>
|
||||
<string name="settings_summary_transcoding_download">트랜스코딩된 미디어를 다운로드합니다. 활성화하면 다운로드 endpoint를 사용하지 않고 다음 설정이 사용됩니다. \n\n \"다운로드용 트랜스코딩 포맷\"이 \"직접 다운로드\"로 설정된 경우 파일의 비트 전송률은 변경되지 않습니다.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">파일이 즉시 트랜스코딩되면 일반적으로 트랙 길이를 표시하지 않습니다. 트랙의 재생시간을 추정하는 기능을 지원한다면 서버에 요청할 수 있지만 응답 시간이 필요할 수 있습니다.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">활성화 시, 즐겨찾기 앨범을 오프라인으로 사용할 수 있도록 다운로드합니다.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">오프라인 사용을 위해 즐겨찾기 앨범 동기화</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">활성화 시, 즐겨찾기 트랙을 오프라인으로 사용할 수 있도록 다운로드합니다.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">오프라인 사용을 위해 즐겨찾기 트랙 동기화</string>
|
||||
<string name="settings_theme">테마</string>
|
||||
@@ -302,7 +321,6 @@
|
||||
<string name="settings_title_transcoding_download">트랜스코딩 다운로드</string>
|
||||
<string name="settings_title_ui">UI</string>
|
||||
<string name="settings_transcoded_download">트랜스코딩된 다운로드</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">버전</string>
|
||||
<string name="settings_wifi_only_summary">모바일 데이터로 스트리밍하려 할 시 확인창을 띄웁니다.</string>
|
||||
<string name="settings_wifi_only_title">Wi-Fi로만 스트리밍 확인창</string>
|
||||
@@ -351,6 +369,7 @@
|
||||
<string name="track_info_duration">재생 시간</string>
|
||||
<string name="track_info_genre">장르</string>
|
||||
<string name="track_info_path">경로</string>
|
||||
<string name="track_info_sampling_rate">샘플링 레이트</string>
|
||||
<string name="track_info_size">크기</string>
|
||||
<string name="track_info_suffix">접미사</string>
|
||||
<string name="track_info_summary_downloaded_file">파일은 Subsonic API를 사용하여 다운로드되었습니다. 파일의 코덱과 비트 전송률은 소스 파일과 동일하게 유지됩니다.</string>
|
||||
@@ -367,4 +386,42 @@
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">이 앱을 일러스트로 더 다채롭게 꾸밀 수 있도록 해준 unDraw 에 특별히 감사드립니다.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">이 작업은 시간이 소요되며, 다시 시작 후 적용됩니다</string>
|
||||
<string name="home_section_music">음악</string>
|
||||
<string name="home_section_podcast">팟캐스트</string>
|
||||
<string name="home_title_last_month">지난 달</string>
|
||||
<string name="menu_last_week_name">지난 주</string>
|
||||
<string name="menu_last_month_name">지난 달</string>
|
||||
<string name="menu_sort_recently_added">최근에 추가됨</string>
|
||||
<string name="menu_sort_recently_played">최근에 재생됨</string>
|
||||
<string name="menu_sort_most_played">많이 재생됨</string>
|
||||
<string name="menu_sort_least_recently_starred">즐겨찾기 오래된순</string>
|
||||
<string name="menu_pin_button">홈화면에 추가</string>
|
||||
<string name="album_page_release_date_label">%1$s에 발매됨</string>
|
||||
<string name="settings_audio_quality">오디오 품질 표시하기</string>
|
||||
<string name="settings_audio_quality_summary">오디오 트랙에 비트레이트와 포맷이 표시됩니다.</string>
|
||||
<string name="settings_song_rating">음악 별점 표시하기</string>
|
||||
<string name="settings_streaming_cache_size">스트리밍 캐시 크기</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">변경 사항을 저장하려면 앱을 다시 시작하세요.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">캐시 파일을 다른 저장소로 변경하면 기존의 캐시 파일이 삭제될 수 있습니다.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">저장소 옵션 선택</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">외부</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">내부</string>
|
||||
<string name="github_update_dialog_negative_button">나중에 다시 알려주기</string>
|
||||
<string name="github_update_dialog_positive_button">지금 다운로드 받기</string>
|
||||
<string name="github_update_dialog_summary">Github에 최신 버전이 존재합니다</string>
|
||||
<string name="github_update_dialog_title">업데이트 가능</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">취소</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">초기화</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">저장</string>
|
||||
<string name="home_rearrangement_dialog_title">홈 다시 정렬</string>
|
||||
<string name="home_sync_starred_albums_title">즐겨찾기한 앨범 동기화</string>
|
||||
<string name="menu_sort_most_recently_starred">즐겨찾기 최신순</string>
|
||||
<string name="player_unknown_format">즐겨찾기 오래된순</string>
|
||||
<string name="player_transcoding">트랜스코딩</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">길게 눌러 삭제하기</string>
|
||||
<string name="settings_song_rating_summary">활성화 시, 별점이 음악 페이지에서 숨겨집니다 \n*다시 시작이 필요합니다</string>
|
||||
<string name="starred_album_sync_dialog_summary">즐겨찾기한 앨범을 다운로드할 시 많은 양의 데이터가 필요할 수 있습니다.</string>
|
||||
<string name="starred_album_sync_dialog_title">즐겨찾기 한 앨범 동기화</string>
|
||||
<string name="settings_item_rating">평점 표시하기</string>
|
||||
</resources>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</style>
|
||||
|
||||
<style name="NoConnectionTextView">
|
||||
<item name="background">?attr/colorError</item>
|
||||
<item name="android:textColor">?attr/colorOnError</item>
|
||||
<item name="background">?attr/colorErrorContainer</item>
|
||||
<item name="android:textColor">?attr/colorOnErrorContainer</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -103,7 +103,14 @@
|
||||
<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_rearrangement_dialog_subtitle">Weź pod uwagę to że, żeby zmiany nastąpiły, musisz zrestartować aplikację.</string>
|
||||
<string name="home_section_music">Muzyka</string>
|
||||
<string name="home_section_podcast">Podcasty</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="player_queue_save_queue_success">Zapisano kolejkę odtwarzania</string>
|
||||
<string name="track_info_bit_depth">Głębia bitowa</string>
|
||||
<string name="track_info_sampling_rate">Częstotliwość próbkowania</string>
|
||||
<string name="settings_system_language">Język systemu</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>
|
||||
@@ -283,8 +290,8 @@
|
||||
<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_system_equalizer_summary">Zmień ustawienia audio</string>
|
||||
<string name="settings_system_equalizer_title">Korektor systemowy</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>
|
||||
@@ -415,4 +422,14 @@
|
||||
<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>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album do zsynchronizowania </item>
|
||||
<item quantity="other">%d albumów do zsynchrpnizowania</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Korektor dźwięku</string>
|
||||
<string name="equalizer_reset">Reset</string>
|
||||
<string name="equalizer_enable">Włączony</string>
|
||||
<string name="equalizer_not_supported">Nie wspierane na tym urządzeniu</string>
|
||||
<string name="settings_app_equalizer">Korektor dźwięku</string>
|
||||
<string name="settings_app_equalizer_summary">Otwórz wbudowany korektor dźwięku</string>
|
||||
</resources>
|
||||
|
||||
@@ -248,8 +248,8 @@
|
||||
<string name="settings_delete_download_storage_summary">O processo resultará na exclusão irreversível de todos os itens salvos.</string>
|
||||
<string name="settings_delete_download_storage_title">Excluir itens salvos</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_title">Equalizador</string>
|
||||
<string name="settings_system_equalizer_summary">Ajustar configurações de áudio</string>
|
||||
<string name="settings_system_equalizer_title">Equalizador do sistema</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_title">Github</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<string name="activity_battery_optimizations_conclusion">Если у вас возникли проблемы, посетите https://dontkillmyapp.com. Он содержит подробные инструкции о том, как отключить любые функции энергосбережения, которые могут повлиять на производительность приложения.</string>
|
||||
<string name="activity_battery_optimizations_summary">Пожалуйста, отключите оптимизацию батареи для воспроизведения мультимедиа при выключенном экране.</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_download_all">Скачать все</string>
|
||||
@@ -90,6 +90,7 @@
|
||||
<string name="exo_download_notification_channel_name">Загрузки</string>
|
||||
<string name="filter_info_selection">Выберите два или более фильтров</string>
|
||||
<string name="filter_title">Фильтр</string>
|
||||
<string name="filter_artist">Фильтровать исполнителей</string>
|
||||
<string name="filter_title_expanded">Фильтровать жанры</string>
|
||||
<string name="genre_catalogue_title">Каталог жанров</string>
|
||||
<string name="genre_catalogue_title_expanded">Просмотр жанров</string>
|
||||
@@ -103,6 +104,9 @@
|
||||
<string name="home_rearrangement_dialog_positive_button">Сохранять</string>
|
||||
<string name="home_rearrangement_dialog_title">Настроить главную</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Обратите внимание, чтобы внесенные изменения вступили в силу, необходимо перезапустить приложение.</string>
|
||||
<string name="home_section_music">Музыка</string>
|
||||
<string name="home_section_podcast">Подкасты</string>
|
||||
<string name="home_section_radio">Радио</string>
|
||||
<string name="home_subtitle_best_of">Лучшие треки любимых исполнителей</string>
|
||||
<string name="home_subtitle_made_for_you">Запустите микс с понравившимся вам треком</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Добавить новое радио</string>
|
||||
@@ -111,6 +115,8 @@
|
||||
<string name="home_sync_starred_download">Скачать</string>
|
||||
<string name="home_sync_starred_subtitle">Загрузка этих треков может потребовать значительного использования данных</string>
|
||||
<string name="home_sync_starred_title">Похоже, есть несколько отмеченных треков для синхронизации.</string>
|
||||
<string name="home_sync_starred_albums_title">Синхронизировать отмеченные альбомы</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Отмеченные альбомы будут доступны в автономном режиме</string>
|
||||
<string name="home_title_best_of">Лучшее из</string>
|
||||
<string name="home_title_discovery">Открытие</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Перемешать все</string>
|
||||
@@ -126,6 +132,7 @@
|
||||
<string name="home_title_most_played_see_all_button">Увидеть все</string>
|
||||
<string name="home_title_new_releases">Новые релизы</string>
|
||||
<string name="home_title_newest_podcasts">Новейшие подкасты</string>
|
||||
<string name="home_title_pinned_playlists">Плейлисты</string>
|
||||
<string name="home_title_podcast_channels">Каналы</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Увидеть все</string>
|
||||
<string name="home_title_radio_station">Радиостанции</string>
|
||||
@@ -156,6 +163,7 @@
|
||||
<string name="menu_add_button">Добавить</string>
|
||||
<string name="menu_add_to_playlist_button">Добавить в плейлист</string>
|
||||
<string name="menu_download_all_button">Скачать все</string>
|
||||
<string name="menu_rate_album">Оценить альбом</string>
|
||||
<string name="menu_download_label">Скачать</string>
|
||||
<string name="menu_filter_all">Все</string>
|
||||
<string name="menu_filter_download">Загружено</string>
|
||||
@@ -165,6 +173,9 @@
|
||||
<string name="menu_group_by_track">Трек</string>
|
||||
<string name="menu_group_by_year">Год</string>
|
||||
<string name="menu_home_label">Главная</string>
|
||||
<string name="menu_last_week_name">Прошлая неделя</string>
|
||||
<string name="menu_last_month_name">Прошлый месяц</string>
|
||||
<string name="menu_last_year_name">Прошлый год</string>
|
||||
<string name="menu_library_label">Библиотека</string>
|
||||
<string name="menu_search_button">Поиск</string>
|
||||
<string name="menu_settings_button">Настройки</string>
|
||||
@@ -181,7 +192,11 @@
|
||||
<string name="menu_unpin_button">Убрать с главного экрана</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Очистить очередь воспроизведения</string>
|
||||
<string name="player_queue_save_queue_success">Очередь сохранена</string>
|
||||
<string name="player_server_priority">Приоритет сервера</string>
|
||||
<string name="player_unknown_format">Неизвестный форма</string>
|
||||
<string name="player_transcoding">Транскодирование</string>
|
||||
<string name="player_transcoding_requested">запрошено</string>
|
||||
<string name="playlist_catalogue_title">Каталог плейлистов</string>
|
||||
<string name="playlist_catalogue_title_expanded">Просмотр плейлистов</string>
|
||||
<string name="playlist_chooser_dialog_empty">Плейлисты не созданы</string>
|
||||
@@ -271,14 +286,16 @@
|
||||
<string name="settings_audio_transcode_priority_toast">Приоритет при перекодировании трека отдается серверу</string>
|
||||
<string name="settings_buffering_strategy">Стратегия буферизации</string>
|
||||
<string name="settings_buffering_strategy_summary">Чтобы изменения вступили в силу, необходимо вручную перезапустить приложение.</string>
|
||||
<string name="settings_continuous_play_summary">Разрешить играть включать треки после окончания плейлиста</string>
|
||||
<string name="settings_continuous_play_title">Продолжать играть</string>
|
||||
<string name="settings_covers_cache">Размер кэша обложек</string>
|
||||
<string name="settings_data_saving_mode_summary">Чтобы сократить потребление данных, избегайте загрузки обложек.</string>
|
||||
<string name="settings_data_saving_mode_title">Ограничить использование мобильных данных</string>
|
||||
<string name="settings_delete_download_storage_summary">Продолжение приведет к необратимому удалению всех сохраненных элементов.</string>
|
||||
<string name="settings_delete_download_storage_title">Удалить сохраненные элементы</string>
|
||||
<string name="settings_download_storage_title">Загрузить хранилище</string>
|
||||
<string name="settings_equalizer_summary">Отрегулируйте настройки звука</string>
|
||||
<string name="settings_equalizer_title">Эквалайзер</string>
|
||||
<string name="settings_system_equalizer_summary">Отрегулируйте настройки звука</string>
|
||||
<string name="settings_system_equalizer_title">Системный эквалайзер</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempo</string>
|
||||
<string name="settings_github_summary">Следите за развитием</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
@@ -295,6 +312,8 @@
|
||||
<string name="settings_podcast_summary">Если включено, показывать раздел подкаста. Перезапустите приложение, чтобы оно вступило в силу.</string>
|
||||
<string name="settings_audio_quality">Показать качество звука (битрейт)</string>
|
||||
<string name="settings_audio_quality_summary">Битрейт и аудиоформат будут показаны для каждой аудиодорожки.</string>
|
||||
<string name="settings_song_rating">Показать рейтинг трека</string>
|
||||
<string name="settings_song_rating_summary">Если эта функция включена, будет отображаться пятизвездочный рейтинг трека на странице воспроизведения\n\n*Требует перезапуска приложения</string>
|
||||
<string name="settings_item_rating">Показать рейтинг</string>
|
||||
<string name="settings_item_rating_summary">Если эта функция включена, будет отображаться рейтинг элемента и то, отмечен ли он как избранный.</string>
|
||||
<string name="settings_queue_syncing_countdown">Таймер синхронизации</string>
|
||||
@@ -309,18 +328,24 @@
|
||||
<string name="settings_rounded_corner_summary">Если этот параметр включен, задает угол кривизны для всех отображаемых обложек. Изменения вступят в силу при перезапуске.</string>
|
||||
<string name="settings_scan_title">Сканировать библиотеку</string>
|
||||
<string name="settings_scrobble_title">Включить скробблинг музыки Last.FM и т.д.</string>
|
||||
<string name="settings_system_language">Язык системы</string>
|
||||
<string name="settings_share_title">Включить обмен музыкой</string>
|
||||
<string name="settings_streaming_cache_size">Размер кэша стриминга</string>
|
||||
<string name="settings_streaming_cache_storage_title">Хранилище кэша стриминга</string>
|
||||
<string name="settings_sub_summary_scrobble">Важно отметить, что скробблинг также зависит от того, настроен ли сервер для получения этих данных.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">При прослушивании радио исполнителя, мгновенном миксе или перемешивании всех, треки ниже определенного пользовательского рейтинга будут игнорироваться.</string>
|
||||
<string name="settings_summary_replay_gain">Усиление воспроизведения — это функция, которая позволяет регулировать уровень громкости звуковых дорожек для обеспечения единообразного качества прослушивания. Этот параметр действует только в том случае, если трек содержит необходимые метаданные.</string>
|
||||
<string name="settings_summary_scrobble">Скробблинг — это функция, которая позволяет вашему устройству отправлять информацию о песнях, которые вы слушаете, на музыкальный сервер. Эта информация помогает создавать персональные рекомендации на основе ваших музыкальных предпочтений.</string>
|
||||
<string name="settings_summary_share">Позволяет пользователю делиться музыкой по ссылке. Функциональность должна поддерживаться и включаться на стороне сервера и ограничивается отдельными треками, альбомами и плейлистами.</string>
|
||||
<string name="settings_summary_syncing">Возвращает состояние очереди воспроизведения для этого пользователя. Сюда входят треки в очереди воспроизведения, воспроизводимый в данный момент трек и позиция внутри этого трека. Сервер должен поддерживать эту функцию.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nСейчас используется: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Приоритет отдается режиму перекодирования. Если установлено «Прямое воспроизведение», битрейт файла не изменится.</string>
|
||||
<string name="settings_summary_transcoding_download">Загрузите перекодированные медиафайлы. Если этот параметр включен, будет использоваться не конечная точка загрузки, а следующие настройки. Если для параметра «Формат перекодирования для загрузки» установлено значение «Прямая загрузка», битрейт файла не изменится.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Когда файл перекодируется на лету, клиент обычно не показывает длину трека. Можно запросить у серверов, поддерживающих данную функцию, оценку длительности воспроизводимого трека, но время ответа может занять больше времени.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Если этот параметр включен, помеченные альбомы будут загружены для использования в автономном режиме.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Синхронизировать помеченные альбомы для использования в автономном режиме.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Если этот параметр включен, помеченные треки будут загружены для использования в автономном режиме.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Синхронизируйте помеченные треки для использования в автономном режиме.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Синхронизировать помеченные треки для использования в автономном режиме.</string>
|
||||
<string name="settings_theme">Тема</string>
|
||||
<string name="settings_title_data">Данные</string>
|
||||
<string name="settings_title_general">Общий</string>
|
||||
@@ -332,7 +357,7 @@
|
||||
<string name="settings_title_share">Поделиться</string>
|
||||
<string name="settings_title_syncing">Синхронизации</string>
|
||||
<string name="settings_title_transcoding">Транскодирование</string>
|
||||
<string name="settings_title_transcoding_download">Транскодирование Скачать</string>
|
||||
<string name="settings_title_transcoding_download">Скачивание с транскодированием</string>
|
||||
<string name="settings_title_ui">UI (Пользовательский интерфейс)</string>
|
||||
<string name="settings_transcoded_download">Перекодированная загрузка</string>
|
||||
<string name="settings_version_title">Версия</string>
|
||||
@@ -373,8 +398,16 @@
|
||||
<string name="starred_sync_dialog_positive_button">Продолжить и скачать</string>
|
||||
<string name="starred_sync_dialog_summary">Для скачивания рейтинговых треков может потребоваться большой объем данных.</string>
|
||||
<string name="starred_sync_dialog_title">Синхронизировать отмеченные треки</string>
|
||||
<string name="starred_album_sync_dialog_summary">Для скачивания рейтинговых альбомов может потребоваться большой объем данных.</string>
|
||||
<string name="starred_album_sync_dialog_title">Синхронизировать отмеченные альбомы</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Чтобы изменения вступили в силу необходимо перезапустить приложение.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Изменение места сохранения кэшированных файлов с одного на другое может привести к удалению файлов в старом хранилище.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Выберите способ сохранения</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Внешний</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Внутренний</string>
|
||||
<string name="track_info_album">Альбом</string>
|
||||
<string name="track_info_artist">Исполнитель</string>
|
||||
<string name="track_info_bit_depth">Разрядность</string>
|
||||
<string name="track_info_bitrate">Битрейт</string>
|
||||
<string name="track_info_content_type">Тип содержимого</string>
|
||||
<string name="track_info_dialog_positive_button">OK</string>
|
||||
@@ -383,6 +416,7 @@
|
||||
<string name="track_info_duration">Продолжительность</string>
|
||||
<string name="track_info_genre">Жанр</string>
|
||||
<string name="track_info_path">Путь</string>
|
||||
<string name="track_info_sampling_rate">Частота сэмплирования</string>
|
||||
<string name="track_info_size">Размер</string>
|
||||
<string name="track_info_suffix">Суффикс</string>
|
||||
<string name="track_info_summary_downloaded_file">Файл был загружен с использованием API Subsonic. Кодек и битрейт файла остаются неизменными по сравнению с исходным файлом.</string>
|
||||
@@ -399,4 +433,8 @@
|
||||
<string name="undraw_page">Развернуть</string>
|
||||
<string name="undraw_thanks">Особая благодарность — команде unDraw, без иллюстраций которой мы не смогли бы сделать это приложение красивее.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">Альбомов для синхронизации: %d</item>
|
||||
<item quantity="other">Альбомов для синхронизации: %d</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
257
app/src/main/res/values-tr/arrays.xml
Normal file
257
app/src/main/res/values-tr/arrays.xml
Normal file
@@ -0,0 +1,257 @@
|
||||
<resources>
|
||||
<string-array name="theme_list_titles">
|
||||
<item>Açık</item>
|
||||
<item>Koyu</item>
|
||||
<item>Sistem varsayılanı</item>
|
||||
</string-array>
|
||||
<string-array name="theme_list_values">
|
||||
<item>light</item>
|
||||
<item>dark</item>
|
||||
<item>default</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_cache_size_titles">
|
||||
<item>Yüksek</item>
|
||||
<item>Orta</item>
|
||||
<item>Düşük</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>Yüksek</item>
|
||||
<item>Orta</item>
|
||||
<item>Düşük</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>Kapalı</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>Orjinal</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>Orjinal</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>Orjinal</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>Doğrudan çal</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>Doğrudan çal</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>Doğrudan indir</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>On saniye</item>
|
||||
<item>Beş saniye</item>
|
||||
<item>İki saniye</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>Yüksek</item>
|
||||
<item>Orta</item>
|
||||
<item>Düşük</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>Kapalı</item>
|
||||
<item>Parça</item>
|
||||
<item>Albüm</item>
|
||||
<item>Otomatik</item>
|
||||
</string-array>
|
||||
<string-array name="replay_gain_values">
|
||||
<item>kapalı</item>
|
||||
<item>parça</item>
|
||||
<item>albüm</item>
|
||||
<item>otomatik</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transcoded_download_option_list_titles">
|
||||
<item>Format dönüştürme yapma</item>
|
||||
<item>Sunucu ayarları</item>
|
||||
<item>Wi-fi dönüştürme ayarları</item>
|
||||
<item>Mobil dönüştürme formatı</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>Minimum</item>
|
||||
<item>Moderate</item>
|
||||
<item>Agrasif</item>
|
||||
<item>Aşırı</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>En az 0 yıldız</item>
|
||||
<item>En az 1 yıldız</item>
|
||||
<item>En az 2 yıldız</item>
|
||||
<item>En az 3 yıldız</item>
|
||||
<item>En az 4 yıldız</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>
|
||||
436
app/src/main/res/values-tr/strings.xml
Normal file
436
app/src/main/res/values-tr/strings.xml
Normal file
@@ -0,0 +1,436 @@
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_conclusion">Sorun yaşarsanız https://dontkillmyapp.com adresini ziyaret edin. Uygulamanın performansını etkileyebilecek güç tasarrufu özelliklerinin nasıl devre dışı bırakılacağına dair ayrıntılı talimatlar sağlar.</string>
|
||||
<string name="activity_battery_optimizations_summary">Ekran kapalıyken medya oynatma için lütfen pil optimizasyonlarını devre dışı bırakın.</string>
|
||||
<string name="activity_battery_optimizations_title">Pil optimizasyonu</string>
|
||||
<string name="activity_info_offline_mode">Çevrimdışı mod</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Çalma listesine ekle</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Sıraya ekle</string>
|
||||
<string name="album_bottom_sheet_download_all">Tümünü indir</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Sanatçıya git</string>
|
||||
<string name="album_bottom_sheet_instant_mix">Anında karışım</string>
|
||||
<string name="album_bottom_sheet_play_next">Sıradakini çal</string>
|
||||
<string name="album_bottom_sheet_remove_all">Tümünü kaldır</string>
|
||||
<string name="album_bottom_sheet_share">Paylaş</string>
|
||||
<string name="album_bottom_sheet_shuffle">Karıştır</string>
|
||||
<string name="album_catalogue_title">Albümler</string>
|
||||
<string name="album_catalogue_title_expanded">Albümleri görüntüle</string>
|
||||
<string name="album_error_retrieving_artist">Sanatçı getirilirken hata oluştu</string>
|
||||
<string name="album_list_page_downloaded">İndirilen albümler</string>
|
||||
<string name="album_list_page_most_played">En çok çalınan albümler</string>
|
||||
<string name="album_list_page_new_releases">Yeni çıkanlar</string>
|
||||
<string name="album_list_page_recently_added">Yakın zamanda eklenen albümler</string>
|
||||
<string name="album_list_page_recently_played">Yakın zamanda çalınan albümler</string>
|
||||
<string name="album_list_page_starred">Yıldızlı albümler</string>
|
||||
<string name="album_list_page_title">Albümler</string>
|
||||
<string name="album_page_extra_info_button">Buna benzer daha fazla</string>
|
||||
<string name="album_page_play_button">Çal</string>
|
||||
<string name="album_page_release_date_label">%1$s tarihinde yayınlandı</string>
|
||||
<string name="album_page_release_dates_label">%1$s tarihinde yayınlandı, orijinali %2$s</string>
|
||||
<string name="album_page_shuffle_button">Karıştır</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d parça • %2$d dakika</string>
|
||||
<string name="app_name">Tempo</string>
|
||||
<string name="artist_adapter_radio_station_starting">Aranıyor…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Anında karışım</string>
|
||||
<string name="artist_bottom_sheet_shuffle">Karıştır</string>
|
||||
<string name="artist_catalogue_title">Sanatçılar</string>
|
||||
<string name="artist_catalogue_title_expanded">Sanatçılara göz at</string>
|
||||
<string name="artist_error_retrieving_radio">Sanatçının radyosu alınırken hata oluştu</string>
|
||||
<string name="artist_error_retrieving_tracks">Sanatçının parçaları alınırken hata oluştu</string>
|
||||
<string name="artist_list_page_downloaded">İndirilen sanatçılar</string>
|
||||
<string name="artist_list_page_starred">Yıldızlı sanatçılar</string>
|
||||
<string name="artist_list_page_title">Sanatçılar</string>
|
||||
<string name="artist_page_radio_button">Radyo</string>
|
||||
<string name="artist_page_shuffle_button">Karıştır</string>
|
||||
<string name="artist_page_switch_layout_button">Düzeni değiştir</string>
|
||||
<string name="artist_page_title_album_more_like_this_button">Buna benzer daha fazla</string>
|
||||
<string name="artist_page_title_album_section">Albümler</string>
|
||||
<string name="artist_page_title_biography_more_button">Daha fazla</string>
|
||||
<string name="artist_page_title_biography_section">Biyografi</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">En Çok Dinlenen Şarkılar</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">Tümünü gör</string>
|
||||
<string name="battery_optimization_negative_button">Yok say</string>
|
||||
<string name="battery_optimization_neutral_button">Bir daha sorma</string>
|
||||
<string name="battery_optimization_positive_button">Devre dışı bırak</string>
|
||||
<string name="connection_alert_dialog_negative_button">İptal</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Veri tasarrufunu etkinleştir</string>
|
||||
<string name="connection_alert_dialog_positive_button">Tamam</string>
|
||||
<string name="connection_alert_dialog_summary">Wi-Fi dışındaki bağlantılarda Subsonic sunucusuna erişim kısıtlandı. Bu uyarı penceresinin tekrar görünmemesi için, uygulama ayarlarından bağlantı denetimini devre dışı bırakın.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi bağlı değil</string>
|
||||
<string name="content_description_shuffle_button">Karıştır</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">İptal</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Devam ettir</string>
|
||||
<string name="delete_download_storage_dialog_summary">Lütfen dikkat edin, bu işleme devam etmek tüm sunuculardan indirilen kayıtlı öğelerin kalıcı olarak silinmesine yol açacaktır.</string>
|
||||
<string name="delete_download_storage_dialog_title">Kayıtlı öğeleri sil</string>
|
||||
<string name="description_empty_title">Açıklama yok</string>
|
||||
<string name="disc_titlefull">Disk %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disk %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">İptal</string>
|
||||
<string name="download_directory_dialog_positive_button">İndir</string>
|
||||
<string name="download_directory_dialog_summary">Bu klasördeki tüm parçalar indirilecektir. Alt klasörlerde bulunan parçalar indirilmeyecektir.</string>
|
||||
<string name="download_directory_dialog_title">Parçaları indir</string>
|
||||
<string name="download_info_empty_subtitle">Bir şarkı indirdiğinde burada görünecek</string>
|
||||
<string name="download_info_empty_title">Henüz indirme yok!</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s öğe</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s öğe</string>
|
||||
<string name="download_shuffle_all_subtitle">Tümünü karıştır</string>
|
||||
<string name="download_storage_dialog_sub_summary">Değişikliklerin geçerli olması için uygulamayı yeniden başlatın.</string>
|
||||
<string name="download_storage_dialog_summary">İndirilen dosyaların hedefi bir depolamadan diğerine değiştirildiğinde, önceki depolamada bulunan tüm indirilmiş dosyalar anında silinecektir.</string>
|
||||
<string name="download_storage_dialog_title">Depolama seçeneğini seç</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Harici</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Dahili</string>
|
||||
<string name="download_title_section">İndirilenler</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Sıraya ekle</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Sonra çal</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Kaldır</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">Tümünü kaldır</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">Karıştır</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Zorunlu</string>
|
||||
<string name="error_server_prefix">http veya https öneki gerekli</string>
|
||||
<string name="exo_download_notification_channel_name">İndirilenler</string>
|
||||
<string name="filter_info_selection">İki veya daha fazla filtre seçin</string>
|
||||
<string name="filter_title">Filtre</string>
|
||||
<string name="filter_title_expanded">Türleri filtrele</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">Tür Kataloğu</string>
|
||||
<string name="genre_catalogue_title_expanded">Türlere göz at</string>
|
||||
<string name="github_update_dialog_negative_button">Bana sonra hatırlat</string>
|
||||
<string name="github_update_dialog_neutral_button">Destek ol</string>
|
||||
<string name="github_update_dialog_positive_button">Hemen indir</string>
|
||||
<string name="github_update_dialog_summary">Uygulamanın yeni bir sürümü Github’da mevcut.</string>
|
||||
<string name="github_update_dialog_title">Güncelleme mevcut</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">İptal</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Sıfırla</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Kaydet</string>
|
||||
<string name="home_rearrangement_dialog_title">Ana sayfayı düzenle</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Yapılan değişikliklerin etkili olması için uygulamayı yeniden başlatmanız gerekir.</string>
|
||||
<string name="home_section_music">Müzik</string>
|
||||
<string name="home_section_podcast">Podcast</string>
|
||||
<string name="home_section_radio">Radyo</string>
|
||||
<string name="home_subtitle_best_of">Favori sanatçılarının en iyi şarkıları</string>
|
||||
<string name="home_subtitle_made_for_you">Beğendiğin bir şarkıdan karışım başlat</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Yeni radyo ekle</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Yeni podcast kanalı ekle</string>
|
||||
<string name="home_sync_starred_cancel">İptal</string>
|
||||
<string name="home_sync_starred_download">İndir</string>
|
||||
<string name="home_sync_starred_subtitle">Bu parçaların indirilmesi önemli miktarda veri kullanabilir</string>
|
||||
<string name="home_sync_starred_title">Eşitlenecek bazı yıldızlı parçalar var gibi görünüyor</string>
|
||||
<string name="home_title_best_of">En iyiler</string>
|
||||
<string name="home_title_discovery">Keşfet</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Tümünü karıştır</string>
|
||||
<string name="home_title_flashback">Geçmişe dönüş</string>
|
||||
<string name="home_title_internet_radio_station">İnternet radyo istasyonları</string>
|
||||
<string name="home_title_last_played">Son çalınanlar</string>
|
||||
<string name="home_title_last_played_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_last_week">Geçen hafta</string>
|
||||
<string name="home_title_last_month">Geçen ay</string>
|
||||
<string name="home_title_last_year">Geçen yıl</string>
|
||||
<string name="home_title_made_for_you">Senin için</string>
|
||||
<string name="home_title_most_played">En çok çalınanlar</string>
|
||||
<string name="home_title_most_played_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_new_releases">Yeni çıkışlar</string>
|
||||
<string name="home_title_newest_podcasts">En yeni podcastler</string>
|
||||
<string name="home_title_pinned_playlists">Çalma listeleri</string>
|
||||
<string name="home_title_podcast_channels">Kanallar</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_radio_station">Radyo istasyonları</string>
|
||||
<string name="home_title_recently_added">Son eklenenler</string>
|
||||
<string name="home_title_recently_added_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_shares">Paylaşımlar</string>
|
||||
<string name="home_title_starred_albums">★ Yıldızlı albümler</string>
|
||||
<string name="home_title_starred_albums_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_starred_artists">★ Yıldızlı sanatçılar</string>
|
||||
<string name="home_title_starred_artists_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_starred_tracks">★ Yıldızlı parçalar</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Tümünü gör</string>
|
||||
<string name="home_title_top_songs">En iyi şarkıların</string>
|
||||
<string name="home_option_reorganize">Yeniden düzenle</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="library_title_album">Albümler</string>
|
||||
<string name="library_title_album_see_all_button">Tümünü gör</string>
|
||||
<string name="library_title_artist">Sanatçılar</string>
|
||||
<string name="library_title_artist_see_all_button">Tümünü gör</string>
|
||||
<string name="library_title_genre">Türler</string>
|
||||
<string name="library_title_genre_see_all_button">Tümünü gör</string>
|
||||
<string name="library_title_music_folder">Müzik klasörleri</string>
|
||||
<string name="library_title_playlist">Çalma listeleri</string>
|
||||
<string name="library_title_playlist_see_all_button">Tümünü gör</string>
|
||||
<string name="login_empty">Sunucu eklenmedi</string>
|
||||
<string name="login_title">Subsonic sunucuları</string>
|
||||
<string name="login_title_expanded">Subsonic sunucuları</string>
|
||||
<string name="media_route_menu_title">Yayınla</string>
|
||||
<string name="menu_add_button">Ekle</string>
|
||||
<string name="menu_add_to_playlist_button">Çalma listesine ekle</string>
|
||||
<string name="menu_download_all_button">Tümünü indir</string>
|
||||
<string name="menu_download_label">İndir</string>
|
||||
<string name="menu_filter_all">Tümü</string>
|
||||
<string name="menu_filter_download">İndirilenler</string>
|
||||
<string name="menu_group_by_album">Albüm</string>
|
||||
<string name="menu_group_by_artist">Sanatçı</string>
|
||||
<string name="menu_group_by_genre">Tür</string>
|
||||
<string name="menu_group_by_track">Parça</string>
|
||||
<string name="menu_group_by_year">Yıl</string>
|
||||
<string name="menu_home_label">Ana sayfa</string>
|
||||
<string name="menu_last_week_name">Geçen hafta</string>
|
||||
<string name="menu_last_month_name">Geçen ay</string>
|
||||
<string name="menu_last_year_name">Geçen yıl</string>
|
||||
<string name="menu_library_label">Kütüphane</string>
|
||||
<string name="menu_search_button">Ara</string>
|
||||
<string name="menu_settings_button">Ayarlar</string>
|
||||
<string name="menu_sort_artist">Sanatçı</string>
|
||||
<string name="menu_sort_name">Ad</string>
|
||||
<string name="menu_sort_random">Rastgele</string>
|
||||
<string name="menu_sort_recently_added">Son eklenenler</string>
|
||||
<string name="menu_sort_recently_played">Son çalınanlar</string>
|
||||
<string name="menu_sort_most_played">En çok çalınanlar</string>
|
||||
<string name="menu_sort_most_recently_starred">En son yıldızlananlar</string>
|
||||
<string name="menu_sort_least_recently_starred">En eski yıldızlananlar</string>
|
||||
<string name="menu_pin_button">Ana ekrana ekle</string>
|
||||
<string name="menu_unpin_button">Ana ekrandan kaldır</string>
|
||||
<string name="menu_sort_year">Yıl</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Çalma sırasını temizle</string>
|
||||
<string name="player_server_priority">Sunucu önceliği</string>
|
||||
<string name="player_unknown_format">Bilinmeyen format</string>
|
||||
<string name="player_transcoding">Dönüştürme</string>
|
||||
<string name="player_transcoding_requested">talep edildi</string>
|
||||
<string name="playlist_catalogue_title">Çalma Listesi Kataloğu</string>
|
||||
<string name="playlist_catalogue_title_expanded">Çalma listelerine göz at</string>
|
||||
<string name="playlist_chooser_dialog_empty">Henüz çalma listesi oluşturulmadı</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">İptal</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Oluştur</string>
|
||||
<string name="playlist_chooser_dialog_title">Çalma listesine ekle</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Şarkı çalma listesine eklendi</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Şarkı çalma listesine eklenemedi</string>
|
||||
<string name="playlist_counted_tracks">%1$d parça • %2$s</string>
|
||||
<string name="playlist_duration">Süre • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Silmek için uzun basın</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Çalma listesi adı</string>
|
||||
<string name="playlist_editor_dialog_negative_button">İptal</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Sil</string>
|
||||
<string name="playlist_editor_dialog_positive_button">Kaydet</string>
|
||||
<string name="playlist_editor_dialog_title">Çalma listesini düzenle</string>
|
||||
<string name="playlist_page_play_button">Çal</string>
|
||||
<string name="playlist_page_shuffle_button">Karıştır</string>
|
||||
<string name="playlist_song_count">Çalma listesi • %1$d şarkı</string>
|
||||
<string name="podcast_bottom_sheet_add_to_queue">Sıraya ekle</string>
|
||||
<string name="podcast_bottom_sheet_delete">Sil</string>
|
||||
<string name="podcast_bottom_sheet_download">İndir</string>
|
||||
<string name="podcast_bottom_sheet_go_to_channel">Kanala git</string>
|
||||
<string name="podcast_bottom_sheet_play_next">Sonra çal</string>
|
||||
<string name="podcast_bottom_sheet_remove">Kaldır</string>
|
||||
<string name="podcast_channel_catalogue_title">Kanallar</string>
|
||||
<string name="podcast_channel_catalogue_title_expanded">Kanallara göz at</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS URL</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Podcast Kanalı</string>
|
||||
<string name="podcast_channel_page_title_description_section">Açıklama</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Bölümler</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Bölüm mevcut değil</string>
|
||||
<string name="podcast_episode_download_request_snackbar">İsteğiniz sunucuya gönderildi</string>
|
||||
<string name="podcast_info_empty_button">Bölümü gizlemek için tıklayın\nDeğişiklikler yeniden başlatıldığında görülecek</string>
|
||||
<string name="podcast_info_empty_subtitle">Bir kanal eklediğinizde burada göreceksiniz</string>
|
||||
<string name="podcast_info_empty_title">Podcast bulunamadı!</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">Radyo Anasayfa URL’si</string>
|
||||
<string name="radio_editor_dialog_hint_name">Radyo Adı</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">Radyo Yayın URL’si</string>
|
||||
<string name="radio_editor_dialog_negative_button">İptal</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Sil</string>
|
||||
<string name="radio_editor_dialog_positive_button">Kaydet</string>
|
||||
<string name="radio_editor_dialog_title">İnternet Radyo İstasyonu</string>
|
||||
<string name="radio_station_info_empty_button">Bölümü gizlemek için tıklayın\nDeğişiklikler yeniden başlatıldığında görülecek</string>
|
||||
<string name="radio_station_info_empty_subtitle">Bir radyo istasyonu eklediğinizde burada göreceksiniz</string>
|
||||
<string name="radio_station_info_empty_title">İstasyon bulunamadı!</string>
|
||||
<string name="rating_dialog_negative_button">İptal</string>
|
||||
<string name="rating_dialog_positive_button">Kaydet</string>
|
||||
<string name="rating_dialog_title">Değerlendir</string>
|
||||
<string name="search_hint">Başlık, sanatçı veya albüm ara</string>
|
||||
<string name="search_info_minimum_characters">En az üç karakter girin</string>
|
||||
<string name="search_title_album">Albümler</string>
|
||||
<string name="search_title_artist">Sanatçılar</string>
|
||||
<string name="search_title_song">Şarkılar</string>
|
||||
<string name="server_signup_dialog_action_low_security">Düşük güvenlik</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Silmek için uzun basın</string>
|
||||
<string name="server_signup_dialog_hint_local_address">Yerel URL</string>
|
||||
<string name="server_signup_dialog_hint_name">Sunucu Adı</string>
|
||||
<string name="server_signup_dialog_hint_password">Parola</string>
|
||||
<string name="server_signup_dialog_hint_url">Sunucu URL’si</string>
|
||||
<string name="server_signup_dialog_hint_username">Kullanıcı adı</string>
|
||||
<string name="server_signup_dialog_negative_button">İptal</string>
|
||||
<string name="server_signup_dialog_neutral_button">Sil</string>
|
||||
<string name="server_signup_dialog_positive_button">Kaydet</string>
|
||||
<string name="server_signup_dialog_title">Sunucu ekle</string>
|
||||
<string name="server_unreachable_dialog_negative_button">İptal</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Girişe git</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Yine de devam et</string>
|
||||
<string name="server_unreachable_dialog_summary">İstenen sunucuya ulaşılamıyor. Devam etmeyi seçerseniz bu iletişim penceresi bir saat boyunca tekrar görünmez.</string>
|
||||
<string name="server_unreachable_dialog_title">Sunucuya ulaşılamıyor</string>
|
||||
<string name="settings_about_summary">Tempo, Subsonic için açık kaynaklı ve hafif bir müzik istemcisidir, Android için yerel olarak tasarlanıp geliştirilmiştir.</string>
|
||||
<string name="settings_about_title">Hakkında</string>
|
||||
<string name="settings_always_on_display">Her zaman açık ekran</string>
|
||||
<string name="settings_audio_transcode_download_format">Dönüştürme formatı</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Etkinleştirildiğinde, Tempo parçayı aşağıdaki dönüştürme ayarlarıyla indirmeye zorlamaz.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">İndirmelerde yayın için kullanılan sunucu ayarlarına öncelik ver</string>
|
||||
<string name="settings_audio_transcode_download_summary">Etkinleştirildiğinde, Tempo dönüştürülmüş parçaları indirir.</string>
|
||||
<string name="settings_audio_transcode_download_title">Dönüştürülmüş parçaları indir</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Etkinleştirildiğinde, parçanın tahmini süresi sunucudan istenir.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">İçerik uzunluğunu tahmin et</string>
|
||||
<string name="settings_audio_transcode_format_download">İndirmeler için dönüştürme formatı</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Mobilde dönüştürme formatı</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Wi-Fi’da dönüştürme formatı</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Etkinleştirildiğinde, Tempo parçayı aşağıdaki dönüştürme ayarlarıyla yayınlamayacaktır.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Sunucu dönüştürme ayarlarına öncelik ver</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Parçanın dönüştürülmesinde öncelik sunucuya verildi</string>
|
||||
<string name="settings_buffering_strategy">Önbellekleme stratejisi</string>
|
||||
<string name="settings_buffering_strategy_summary">Değişikliğin geçerli olması için uygulamayı elle yeniden başlatmalısınız.</string>
|
||||
<string name="settings_continuous_play_summary">Bir çalma listesi bittiğinde benzer şarkılar çalarak müziğin devam etmesine izin verir</string>
|
||||
<string name="settings_continuous_play_title">Sürekli çalma</string>
|
||||
<string name="settings_covers_cache">Albüm kapağı önbelleği boyutu</string>
|
||||
<string name="settings_data_saving_mode_summary">Veri tüketimini azaltmak için kapak görsellerinin indirilmesinden kaçının.</string>
|
||||
<string name="settings_data_saving_mode_title">Mobil veri kullanımını sınırla</string>
|
||||
<string name="settings_delete_download_storage_summary">Devam ederseniz tüm kayıtlı öğeler geri alınamaz şekilde silinecektir.</string>
|
||||
<string name="settings_delete_download_storage_title">Kayıtlı öğeleri sil</string>
|
||||
<string name="settings_download_storage_title">İndirme depolaması</string>
|
||||
<string name="settings_system_equalizer_summary">Ses ayarlarını düzenle</string>
|
||||
<string name="settings_system_equalizer_title">Sistem ekolayzır</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempo</string>
|
||||
<string name="settings_github_summary">Gelişmeleri takip et</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_image_size">Görsel çözünürlüğünü ayarla</string>
|
||||
<string name="settings_language">Dil</string>
|
||||
<string name="settings_logout_title">Çıkış yap</string>
|
||||
<string name="settings_max_bitrate_download">İndirmeler için bit hızı</string>
|
||||
<string name="settings_max_bitrate_mobile">Mobilde bit hızı</string>
|
||||
<string name="settings_max_bitrate_wifi">Wi-Fi’da bit hızı</string>
|
||||
<string name="settings_media_cache">Ortam dosyası önbelleği boyutu</string>
|
||||
<string name="settings_music_directory">Müzik dizinlerini göster</string>
|
||||
<string name="settings_music_directory_summary">Etkinleştirildiğinde müzik dizini bölümü görüntülenir. Klasörlerde gezinmenin doğru çalışması için sunucunun bu özelliği desteklemesi gerekir.</string>
|
||||
<string name="settings_podcast">Podcast’i göster</string>
|
||||
<string name="settings_podcast_summary">Etkinleştirildiğinde podcast bölümü görüntülenir. Tam etkili olması için uygulamayı yeniden başlatın.</string>
|
||||
<string name="settings_audio_quality">Ses kalitesini göster</string>
|
||||
<string name="settings_audio_quality_summary">Her ses parçası için bit hızı ve ses formatı gösterilecektir.</string>
|
||||
<string name="settings_item_rating">Öğe değerlemesini göster</string>
|
||||
<string name="settings_item_rating_summary">Etkinleştirildiğinde, öğenin puanı ve favori olarak işaretlenip işaretlenmediği görüntülenir.</string>
|
||||
<string name="settings_queue_syncing_countdown">Eşitleme zamanlayıcısı</string>
|
||||
<string name="settings_queue_syncing_summary">Etkinleştirildiğinde, kullanıcı çalma sırasını kaydedebilir ve uygulamayı açtığında bu durumu yükleyebilir.</string>
|
||||
<string name="settings_queue_syncing_title">Bu kullanıcı için çalma sırasını eşitle</string>
|
||||
<string name="settings_radio">Radyoyu göster</string>
|
||||
<string name="settings_radio_summary">Etkinleştirildiğinde radyo bölümü görüntülenir. Tam etkili olması için uygulamayı yeniden başlatın.</string>
|
||||
<string name="settings_replay_gain">Yeniden çalma kazanç modunu ayarla</string>
|
||||
<string name="settings_rounded_corner">Yuvarlatılmış köşeler</string>
|
||||
<string name="settings_rounded_corner_size">Köşe boyutu</string>
|
||||
<string name="settings_rounded_corner_size_summary">Eğrilik açısının büyüklüğünü ayarlar.</string>
|
||||
<string name="settings_rounded_corner_summary">Etkinleştirildiğinde, tüm kapak görselleri için bir eğme açısı uygulanır. Değişiklikler yeniden başlatıldığında geçerli olur.</string>
|
||||
<string name="settings_scan_title">Kütüphaneyi tara</string>
|
||||
<string name="settings_scrobble_title">Müzik scrobbling özelliğini etkinleştir</string>
|
||||
<string name="settings_system_language">Sistem dili</string>
|
||||
<string name="settings_share_title">Müzik paylaşımını etkinleştir</string>
|
||||
<string name="settings_streaming_cache_size">Yayın önbelleği boyutu</string>
|
||||
<string name="settings_streaming_cache_storage_title">Yayın önbellek depolaması</string>
|
||||
<string name="settings_sub_summary_scrobble">Scrobbling özelliğinin çalışabilmesi için sunucunun bu verileri alacak şekilde etkinleştirilmiş olması gerekir.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">Bir sanatçı radyosu dinlerken, anında karışımda veya tümünü karıştırırken belirli bir kullanıcı puanının altındaki parçalar yok sayılacaktır.</string>
|
||||
<string name="settings_summary_replay_gain">Replay gain, ses parçalarının ses seviyesini ayarlayarak tutarlı bir dinleme deneyimi sağlayan bir özelliktir. Bu ayar yalnızca parçada gerekli meta veriler varsa etkilidir.</string>
|
||||
<string name="settings_summary_scrobble">Scrobbling, cihazınızın dinlediğiniz şarkılar hakkında bilgileri müzik sunucusuna göndermesini sağlayan bir özelliktir. Bu bilgiler müzik tercihlerinize göre kişisel öneriler oluşturulmasına yardımcı olur.</string>
|
||||
<string name="settings_summary_share">Kullanıcının müziği bir bağlantı üzerinden paylaşmasına olanak tanır. Bu işlevin sunucu tarafından desteklenmesi ve etkinleştirilmesi gerekir ve yalnızca tekil parçalar, albümler ve çalma listeleriyle sınırlıdır.</string>
|
||||
<string name="settings_summary_syncing">Bu kullanıcının çalma sırasının durumunu geri yükler. Buna çalma sırasındaki parçalar, o anda çalan parça ve bu parçadaki konum dahildir. Sunucunun bu özelliği desteklemesi gerekir.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nŞu anda kullanımda: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Dönüştürme moduna öncelik verilir. “Doğrudan çal” olarak ayarlanırsa dosyanın bit hızı değiştirilmez.</string>
|
||||
<string name="settings_summary_transcoding_download">Dönüştürülmüş medyayı indir. Etkinleştirilirse indirme uç noktası kullanılmaz, bunun yerine aşağıdaki ayarlar geçerli olur. \n\n “İndirmeler için dönüştürme formatı” “Doğrudan indir” olarak ayarlanırsa dosyanın bit hızı değiştirilmez.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Dosya anlık olarak dönüştürüldüğünde, istemci genellikle parçanın süresini göstermez. Bu işlevi destekleyen sunuculardan çalınan parçanın süresini tahmin etmeleri istenebilir,
|
||||
ancak yanıt süreleri daha uzun olabilir.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Etkinleştirildiğinde, yıldızlı parçalar çevrimdışı kullanım için indirilecektir.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Çevrimdışı kullanım için yıldızlı parçaları eşitle</string>
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="settings_title_data">Veri</string>
|
||||
<string name="settings_title_general">Genel</string>
|
||||
<string name="settings_title_rating">Değerlendirme</string>
|
||||
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
<string name="settings_title_skip_min_star_rating">Değerlendirmeye göre parçaları yok say</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Şu puana sahip şarkılar:</string>
|
||||
<string name="settings_title_share">Paylaş</string>
|
||||
<string name="settings_title_syncing">Eşitleme</string>
|
||||
<string name="settings_title_transcoding">Dönüştürme</string>
|
||||
<string name="settings_title_transcoding_download">Dönüştürme İndir</string>
|
||||
<string name="settings_title_ui">Arayüz</string>
|
||||
<string name="settings_transcoded_download">Dönüştürülmüş indirme</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Sürüm</string>
|
||||
<string name="settings_wifi_only_summary">Mobil ağ üzerinden yayın yapmadan önce kullanıcı onayı iste.</string>
|
||||
<string name="settings_wifi_only_title">Yalnızca Wi-Fi ile yayın uyarısı</string>
|
||||
<string name="share_bottom_sheet_copy_link">Bağlantıyı kopyala</string>
|
||||
<string name="share_bottom_sheet_delete">Paylaşımı sil</string>
|
||||
<string name="share_bottom_sheet_update">Paylaşımı güncelle</string>
|
||||
<string name="share_subtitle_item">Bitiş tarihi: %1$s</string>
|
||||
<string name="share_unsupported_error">Paylaşım desteklenmiyor veya etkin değil</string>
|
||||
<string name="share_update_dialog_hint_description">Açıklama</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Bitiş tarihi</string>
|
||||
<string name="share_update_dialog_negative_button">İptal</string>
|
||||
<string name="share_update_dialog_positive_button">Kaydet</string>
|
||||
<string name="share_update_dialog_title">Paylaş</string>
|
||||
<string name="song_bottom_sheet_add_to_playlist">Çalma listesine ekle</string>
|
||||
<string name="song_bottom_sheet_add_to_queue">Sıraya ekle</string>
|
||||
<string name="song_bottom_sheet_download">İndir</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_album">Albüm alınırken hata oluştu</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_artist">Sanatçı alınırken hata oluştu</string>
|
||||
<string name="song_bottom_sheet_go_to_album">Albüme git</string>
|
||||
<string name="song_bottom_sheet_go_to_artist">Sanatçıya git</string>
|
||||
<string name="song_bottom_sheet_instant_mix">Anında karışım</string>
|
||||
<string name="song_bottom_sheet_play_next">Sonra çal</string>
|
||||
<string name="song_bottom_sheet_rate">Değerlendir</string>
|
||||
<string name="song_bottom_sheet_remove">Kaldır</string>
|
||||
<string name="song_bottom_sheet_share">Paylaş</string>
|
||||
<string name="song_list_page_downloaded">İndirilenler</string>
|
||||
<string name="song_list_page_most_played">En çok çalınan parçalar</string>
|
||||
<string name="song_list_page_recently_added">Son eklenen parçalar</string>
|
||||
<string name="song_list_page_recently_played">Son çalınan parçalar</string>
|
||||
<string name="song_list_page_starred">Yıldızlı parçalar</string>
|
||||
<string name="song_list_page_top">%1$s’in en iyi parçaları</string>
|
||||
<string name="song_list_page_year">%1$d yılı</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<string name="starred_sync_dialog_negative_button">İptal</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Devam et</string>
|
||||
<string name="starred_sync_dialog_positive_button">Devam et ve indir</string>
|
||||
<string name="starred_sync_dialog_summary">Yıldızlı parçaların indirilmesi yüksek miktarda veri gerektirebilir.</string>
|
||||
<string name="starred_sync_dialog_title">Yıldızlı parçaları eşitle</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Değişikliklerin geçerli olması için uygulamayı yeniden başlatın.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Önbelleğe alınmış dosyaların hedefini bir depolamadan diğerine değiştirmek, önceki depolamadaki önbellek dosyalarının silinmesine yol açabilir.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Depolama seçeneğini seç</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Harici</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Dahili</string>
|
||||
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
|
||||
<string name="track_info_album">Albüm</string>
|
||||
<string name="track_info_artist">Sanatçı</string>
|
||||
<string name="track_info_bit_depth">Bit derinliği</string>
|
||||
<string name="track_info_bitrate">Bit hızı</string>
|
||||
<string name="track_info_content_type">İçerik türü</string>
|
||||
<string name="track_info_dialog_positive_button">Tamam</string>
|
||||
<string name="track_info_dialog_title">Parça bilgisi</string>
|
||||
<string name="track_info_disc_number">Disk numarası</string>
|
||||
<string name="track_info_duration">Süre</string>
|
||||
<string name="track_info_genre">Tür</string>
|
||||
<string name="track_info_path">Yol</string>
|
||||
<string name="track_info_sampling_rate">Örnekleme oranı</string>
|
||||
<string name="track_info_size">Boyut</string>
|
||||
<string name="track_info_suffix">Uzantı</string>
|
||||
<string name="track_info_summary_downloaded_file">Dosya Subsonic API’leri kullanılarak indirildi. Dosyanın dönüştürme ve bit hızı, kaynak dosyadan değişmeden kaldı.</string>
|
||||
<string name="track_info_summary_full_transcode">Uygulama, sunucudan dosyayı dönüştürmesini ve bit hızını değiştirmesini talep edecektir. Kullanıcının istediği kodek %1$s ve bit hızı %2$s. Seçilen formatta kodek ve bit hızındaki olası değişiklikler
|
||||
sunucu tarafından yapılır ve bu işlem sunucu tarafından desteklenebilir veya desteklenmeyebilir.</string>
|
||||
<string name="track_info_summary_original_file"> Uygulama yalnızca sunucunun sağladığı orijinal dosyayı okuyacaktır.
|
||||
Uygulama, dönüştürülmemiş dosyayı orijinal kaynağın bit hızıyla sunucudan açıkça talep eder.</string>
|
||||
<string name="track_info_summary_server_prioritized">Çalınacak dosyanın kalitesi sunucunun kararına bırakılır. Uygulama, olası dönüştürmeler için kodek ve bit hızı seçimini zorlamaz.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">Uygulama, sunucudan dosyanın bit hızını değiştirmesini talep edecektir.
|
||||
Kullanıcının istediği bit hızı %1$s, kaynak dosyanın kodeği aynı kalacaktır. Seçilen formatta dosyanın bit hızındaki olası değişiklikler sunucu tarafından yapılır ve bu işlem sunucu tarafından desteklenebilir veya desteklenmeyebilir.</string>
|
||||
<string name="track_info_summary_transcoding_codec">Uygulama, sunucudan dosyayı dönüştürmesini talep edecektir.
|
||||
Kullanıcının istediği kodek %1$s, bit hızı ise kaynak dosyayla aynı kalacaktır. Dosyanın seçilen formata olası dönüştürülmesi sunucuya bağlıdır, destekleyebilir ya da desteklemeyebilir.</string>
|
||||
<string name="track_info_title">Başlık</string>
|
||||
<string name="track_info_track_number">Parça numarası</string>
|
||||
<string name="track_info_transcoded_content_type">Dönüştürülmüş içerik türü</string>
|
||||
<string name="track_info_transcoded_suffix">Dönüştürülmüş uzantı</string>
|
||||
<string name="track_info_year">Yıl</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">İllüstrasyonlarıyla bu uygulamayı daha güzel hale getirmemize yardımcı olan unDraw’a özel teşekkürler.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
</resources>
|
||||
@@ -255,8 +255,8 @@
|
||||
<string name="settings_delete_download_storage_summary">继续当前操作将导致所有已保存的项目被永久删除。</string>
|
||||
<string name="settings_delete_download_storage_title">删除已保存的项目</string>
|
||||
<string name="settings_download_storage_title">下载存储</string>
|
||||
<string name="settings_equalizer_summary">调整音频设置</string>
|
||||
<string name="settings_equalizer_title">均衡器</string>
|
||||
<string name="settings_system_equalizer_summary">调整音频设置</string>
|
||||
<string name="settings_system_equalizer_title">系统均衡器</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempo</string>
|
||||
<string name="settings_github_summary">关注开发进展</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
<string name="exo_download_notification_channel_name">Downloads</string>
|
||||
<string name="filter_info_selection">Select two or more filters</string>
|
||||
<string name="filter_title">Filter</string>
|
||||
<string name="filter_artist">Filter artists</string>
|
||||
<string name="filter_title_expanded">Filter Genres</string>
|
||||
<string name="generic_list_page_count">(%1$d)</string>
|
||||
<string name="generic_list_page_count_unknown">(+%1$d)</string>
|
||||
@@ -116,6 +117,8 @@
|
||||
<string name="home_sync_starred_download">Download</string>
|
||||
<string name="home_sync_starred_subtitle">Downloading these tracks may involve significant data usage</string>
|
||||
<string name="home_sync_starred_title">Looks like there are some starred tracks to sync</string>
|
||||
<string name="home_sync_starred_albums_title">Sync Starred Albums</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Albums marked with a star will be available offline</string>
|
||||
<string name="home_title_best_of">Best of</string>
|
||||
<string name="home_title_discovery">Discovery</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Shuffle all</string>
|
||||
@@ -164,6 +167,7 @@
|
||||
<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_rate_album">Rate album</string>
|
||||
<string name="menu_download_label">Download</string>
|
||||
<string name="menu_filter_all">All</string>
|
||||
<string name="menu_filter_download">Downloaded</string>
|
||||
@@ -192,7 +196,11 @@
|
||||
<string name="menu_sort_year">Year</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Clean play queue</string>
|
||||
<string name="player_queue_save_queue_success">Saved play queue</string>
|
||||
<string name="player_server_priority">Server Priority</string>
|
||||
<string name="player_unknown_format">Unknown format</string>
|
||||
<string name="player_transcoding">Transcoding</string>
|
||||
<string name="player_transcoding_requested">requested</string>
|
||||
<string name="playlist_catalogue_title">Playlist Catalogue</string>
|
||||
<string name="playlist_catalogue_title_expanded">Browse Playlists</string>
|
||||
<string name="playlist_chooser_dialog_empty">No playlists created</string>
|
||||
@@ -290,8 +298,8 @@
|
||||
<string name="settings_delete_download_storage_summary">Proceeding will result in the irreversible deletion of all saved items.</string>
|
||||
<string name="settings_delete_download_storage_title">Delete saved items</string>
|
||||
<string name="settings_download_storage_title">Download storage</string>
|
||||
<string name="settings_equalizer_summary">Adjust audio settings</string>
|
||||
<string name="settings_equalizer_title">Equalizer</string>
|
||||
<string name="settings_system_equalizer_summary">Adjust audio settings</string>
|
||||
<string name="settings_system_equalizer_title">System equalizer</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_title">Github</string>
|
||||
@@ -308,11 +316,13 @@
|
||||
<string name="settings_podcast_summary">If enabled, show the podcast section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_audio_quality">Show audio quality</string>
|
||||
<string name="settings_audio_quality_summary">The bitrate and audio format will be shown for each audio track.</string>
|
||||
<string name="settings_song_rating">Show song star rating</string>
|
||||
<string name="settings_song_rating_summary">If enabled, shows 5 star rating for track on song page\n\n*Requires App restart</string>
|
||||
<string name="settings_item_rating">Show item rating</string>
|
||||
<string name="settings_item_rating_summary">If enabled, the item\'s rating and whether it is marked as a favorite will be displayed.</string>
|
||||
<string name="settings_queue_syncing_countdown">Sync timer</string>
|
||||
<string name="settings_queue_syncing_summary">If enabled, the user will have the ability to save their play queue and will have the ability to load state when opening the application.</string>
|
||||
<string name="settings_queue_syncing_title">Sync play queue for this user</string>
|
||||
<string name="settings_queue_syncing_title">Sync play queue for this user [Not Fully Baked]</string>
|
||||
<string name="settings_radio">Show radio</string>
|
||||
<string name="settings_radio_summary">If enabled, show the radio section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_replay_gain">Set replay gain mode</string>
|
||||
@@ -331,11 +341,13 @@
|
||||
<string name="settings_summary_replay_gain">Replay gain is a feature that allows you to adjust the volume level of audio tracks for a consistent listening experience. This setting is only effective if the track contains the necessary metadata.</string>
|
||||
<string name="settings_summary_scrobble">Scrobbling is a feature that allows your device to send information about the songs you listen to the music server. This information helps create personalized recommendations based on your music preferences.</string>
|
||||
<string name="settings_summary_share">Allows the user to share music via a link. The functionality must be supported and enabled server-side and is limited to individual tracks, albums and playlists.</string>
|
||||
<string name="settings_summary_syncing">Returns the state of the play queue for this user. This includes the tracks in the play queue, the currently playing track, and the position within this track. The server must support this feature.</string>
|
||||
<string name="settings_summary_syncing">Returns the state of the play queue for this user. This includes the tracks in the play queue, the currently playing track, and the position within this track. The server must support this feature.\n*This setting is not 100% working on all servers/devices.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nCurrently in use: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_download">Download transcoded media. If enabled, the download endpoint will not be used, but the following settings. \n\n If \"Transcode format for donwloads\" is set to \"Direct download\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">When the file is transcoded on the fly, the client usually does not show the track length. It is possible to request the servers that support the functionality to estimate the duration of the track being played, but the response times may take longer.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">If enabled, starred albums will be downloaded for offline use.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sync starred albums for offline use</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">If enabled, starred tracks will be downloaded for offline use.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Sync starred tracks for offline use</string>
|
||||
<string name="settings_theme">Theme</string>
|
||||
@@ -389,8 +401,10 @@
|
||||
<string name="starred_sync_dialog_negative_button">Cancel</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continue</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continue and download</string>
|
||||
<string name="starred_sync_dialog_summary">Downloading starry tracks may require a large amount of data.</string>
|
||||
<string name="starred_sync_dialog_summary">Downloading starred tracks may require a large amount of data.</string>
|
||||
<string name="starred_sync_dialog_title">Sync starred tracks</string>
|
||||
<string name="starred_album_sync_dialog_summary">Downloading starred albums may require a large amount of data.</string>
|
||||
<string name="starred_album_sync_dialog_title">Sync starred albums</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">For the changes to take effect, restart the app.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Changing the destination of cached files from one storage to another may result in the deletion of any previously cached files in the other storage.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Select storage option</string>
|
||||
@@ -425,4 +439,14 @@
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album to sync</item>
|
||||
<item quantity="other">%d albums to sync</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Equalizer</string>
|
||||
<string name="equalizer_reset">Reset</string>
|
||||
<string name="equalizer_enable">Enable</string>
|
||||
<string name="equalizer_not_supported">Not supported on this device</string>
|
||||
<string name="settings_app_equalizer">Equalizer</string>
|
||||
<string name="settings_app_equalizer_summary">Open the built-in equalizer</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory app:title="@string/settings_title_general">
|
||||
<Preference
|
||||
android:key="equalizer"
|
||||
android:title="@string/settings_equalizer_title"
|
||||
android:summary="@string/settings_equalizer_summary" />
|
||||
android:key="system_equalizer"
|
||||
android:title="@string/settings_system_equalizer_title"
|
||||
android:summary="@string/settings_system_equalizer_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="app_equalizer"
|
||||
android:title="@string/settings_app_equalizer"
|
||||
android:summary="@string/settings_app_equalizer_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="scan_library"
|
||||
@@ -57,6 +62,12 @@
|
||||
android:summary="@string/settings_audio_quality_summary"
|
||||
android:key="audio_quality_per_item" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_song_rating"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_song_rating_summary"
|
||||
android:key="song_rating_per_item" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_item_rating"
|
||||
android:defaultValue="false"
|
||||
@@ -133,6 +144,12 @@
|
||||
android:summary="@string/settings_sync_starred_tracks_for_offline_use_summary"
|
||||
android:key="sync_starred_tracks_for_offline_use" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_sync_starred_albums_for_offline_use_title"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_sync_starred_albums_for_offline_use_summary"
|
||||
android:key="sync_starred_albums_for_offline_use" />
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="1"
|
||||
app:dialogTitle="@string/settings_buffering_strategy"
|
||||
|
||||
@@ -10,4 +10,5 @@
|
||||
<locale android:name="ru-RU"/> <!-- Russian -->
|
||||
<locale android:name="es-ES"/> <!-- Spanish (Spain) -->
|
||||
<locale android:name="pl-PL"/> <!-- Polish -->
|
||||
<locale android:name="tr-TR"/> <!-- Turkish -->
|
||||
</locale-config>
|
||||
|
||||
@@ -5,12 +5,13 @@ import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import androidx.media3.common.*
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray
|
||||
import androidx.media3.session.*
|
||||
@@ -19,6 +20,7 @@ import com.cappielloantonio.tempo.R
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.google.common.collect.ImmutableList
|
||||
@@ -34,9 +36,18 @@ class MediaService : MediaLibraryService() {
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var shuffleCommands: List<CommandButton>
|
||||
private lateinit var repeatCommands: List<CommandButton>
|
||||
lateinit var equalizerManager: EqualizerManager
|
||||
|
||||
private var customLayout = ImmutableList.of<CommandButton>()
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getEqualizerManager(): EqualizerManager {
|
||||
return this@MediaService.equalizerManager
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
|
||||
companion object {
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||
"android.media3.session.demo.SHUFFLE_ON"
|
||||
@@ -48,6 +59,7 @@ class MediaService : MediaLibraryService() {
|
||||
"android.media3.session.demo.REPEAT_ONE"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
|
||||
"android.media3.session.demo.REPEAT_ALL"
|
||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -57,6 +69,7 @@ class MediaService : MediaLibraryService() {
|
||||
initializePlayer()
|
||||
initializeMediaLibrarySession()
|
||||
initializePlayerListener()
|
||||
initializeEqualizerManager()
|
||||
|
||||
setPlayer(player)
|
||||
}
|
||||
@@ -66,10 +79,20 @@ class MediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
equalizerManager.release()
|
||||
releasePlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
// Check if the intent is for our custom equalizer binder
|
||||
if (intent?.action == ACTION_BIND_EQUALIZER) {
|
||||
return binder
|
||||
}
|
||||
// Otherwise, handle it as a normal MediaLibraryService connection
|
||||
return super.onBind(intent)
|
||||
}
|
||||
|
||||
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
|
||||
|
||||
override fun onConnect(
|
||||
@@ -138,10 +161,19 @@ class MediaService : MediaLibraryService() {
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
val updatedMediaItems = mediaItems.map {
|
||||
it.buildUpon()
|
||||
.setUri(it.requestMetadata.mediaUri)
|
||||
.setMediaMetadata(it.mediaMetadata)
|
||||
val updatedMediaItems = mediaItems.map { mediaItem ->
|
||||
val mediaMetadata = mediaItem.mediaMetadata
|
||||
|
||||
val newMetadata = mediaMetadata.buildUpon()
|
||||
.setArtist(
|
||||
if (mediaMetadata.artist != null) mediaMetadata.artist
|
||||
else mediaMetadata.extras?.getString("uri") ?: ""
|
||||
)
|
||||
.build()
|
||||
|
||||
mediaItem.buildUpon()
|
||||
.setUri(mediaItem.requestMetadata.mediaUri)
|
||||
.setMediaMetadata(newMetadata)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.build()
|
||||
}
|
||||
@@ -177,7 +209,7 @@ class MediaService : MediaLibraryService() {
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setMediaSourceFactory(DynamicMediaSourceFactory(this))
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
@@ -188,6 +220,21 @@ class MediaService : MediaLibraryService() {
|
||||
player.repeatMode = Preferences.getRepeatMode()
|
||||
}
|
||||
|
||||
private fun initializeEqualizerManager() {
|
||||
equalizerManager = EqualizerManager()
|
||||
val audioSessionId = player.audioSessionId
|
||||
if (equalizerManager.attachToSession(audioSessionId)) {
|
||||
val enabled = Preferences.isEqualizerEnabled()
|
||||
equalizerManager.setEnabled(enabled)
|
||||
|
||||
val bands = equalizerManager.getNumberOfBands()
|
||||
val savedLevels = Preferences.getEqualizerBandLevels(bands)
|
||||
for (i in 0 until bands) {
|
||||
equalizerManager.setBandLevel(i.toShort(), savedLevels[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
@@ -217,7 +264,10 @@ class MediaService : MediaLibraryService() {
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null && currentMediaItem.mediaMetadata.extras != null) {
|
||||
MediaManager.scrobble(currentMediaItem, false)
|
||||
}
|
||||
|
||||
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
|
||||
MediaManager.continuousPlay(player.currentMediaItem)
|
||||
@@ -337,7 +387,4 @@ class MediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory() =
|
||||
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.common.AudioAttributes
|
||||
@@ -14,13 +16,13 @@ import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
@@ -34,6 +36,19 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
private lateinit var castPlayer: CastPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
|
||||
lateinit var equalizerManager: EqualizerManager
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getEqualizerManager(): EqualizerManager {
|
||||
return this@MediaService.equalizerManager
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
|
||||
companion object {
|
||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -43,10 +58,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
initializeCastPlayer()
|
||||
initializeMediaLibrarySession()
|
||||
initializePlayerListener()
|
||||
initializeEqualizerManager()
|
||||
|
||||
setPlayer(
|
||||
null,
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||
null,
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,10 +79,20 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
equalizerManager.release()
|
||||
releasePlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
// Check if the intent is for our custom equalizer binder
|
||||
if (intent?.action == ACTION_BIND_EQUALIZER) {
|
||||
return binder
|
||||
}
|
||||
// Otherwise, handle it as a normal MediaLibraryService connection
|
||||
return super.onBind(intent)
|
||||
}
|
||||
|
||||
private fun initializeRepository() {
|
||||
automotiveRepository = AutomotiveRepository()
|
||||
}
|
||||
@@ -74,7 +100,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setMediaSourceFactory(DynamicMediaSourceFactory(this))
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
@@ -85,9 +111,24 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
player.repeatMode = Preferences.getRepeatMode()
|
||||
}
|
||||
|
||||
private fun initializeEqualizerManager() {
|
||||
equalizerManager = EqualizerManager()
|
||||
val audioSessionId = player.audioSessionId
|
||||
if (equalizerManager.attachToSession(audioSessionId)) {
|
||||
val enabled = Preferences.isEqualizerEnabled()
|
||||
equalizerManager.setEnabled(enabled)
|
||||
|
||||
val bands = equalizerManager.getNumberOfBands()
|
||||
val savedLevels = Preferences.getEqualizerBandLevels(bands)
|
||||
for (i in 0 until bands) {
|
||||
equalizerManager.setBandLevel(i.toShort(), savedLevels[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCastPlayer() {
|
||||
if (GoogleApiAvailability.getInstance()
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
) {
|
||||
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
||||
castPlayer.setSessionAvailabilityListener(this)
|
||||
@@ -96,16 +137,16 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
librarySessionCallback = createLibrarySessionCallback()
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
|
||||
@@ -124,7 +165,10 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null && currentMediaItem.mediaMetadata.extras != null) {
|
||||
MediaManager.scrobble(currentMediaItem, false)
|
||||
}
|
||||
|
||||
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
|
||||
MediaManager.continuousPlay(player.currentMediaItem)
|
||||
@@ -133,8 +177,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
} else {
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
@@ -145,8 +189,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
) {
|
||||
MediaManager.scrobble(player.currentMediaItem, true)
|
||||
MediaManager.saveChronology(player.currentMediaItem)
|
||||
@@ -154,9 +198,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
@@ -175,14 +219,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -190,17 +234,26 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
private fun initializeLoadControl(): DefaultLoadControl {
|
||||
return DefaultLoadControl.Builder()
|
||||
.setBufferDurationsMs(
|
||||
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||
)
|
||||
.build()
|
||||
.setBufferDurationsMs(
|
||||
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getQueueFromPlayer(player: Player): List<MediaItem> {
|
||||
val queue = mutableListOf<MediaItem>()
|
||||
for (i in 0 until player.mediaItemCount) {
|
||||
queue.add(player.getMediaItemAt(i))
|
||||
}
|
||||
return queue
|
||||
}
|
||||
|
||||
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||||
if (oldPlayer === newPlayer) return
|
||||
|
||||
oldPlayer?.stop()
|
||||
mediaLibrarySession.player = newPlayer
|
||||
}
|
||||
@@ -211,19 +264,33 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
automotiveRepository.deleteMetadata()
|
||||
clearListener()
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory() =
|
||||
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||
|
||||
override fun onCastSessionAvailable() {
|
||||
val currentQueue = getQueueFromPlayer(player)
|
||||
val currentIndex = player.currentMediaItemIndex
|
||||
val currentPosition = player.currentPosition
|
||||
val isPlaying = player.playWhenReady
|
||||
|
||||
setPlayer(player, castPlayer)
|
||||
|
||||
castPlayer.setMediaItems(currentQueue, currentIndex, currentPosition)
|
||||
castPlayer.playWhenReady = isPlaying
|
||||
castPlayer.prepare()
|
||||
}
|
||||
|
||||
override fun onCastSessionUnavailable() {
|
||||
val currentQueue = getQueueFromPlayer(castPlayer)
|
||||
val currentIndex = castPlayer.currentMediaItemIndex
|
||||
val currentPosition = castPlayer.currentPosition
|
||||
val isPlaying = castPlayer.playWhenReady
|
||||
|
||||
setPlayer(castPlayer, player)
|
||||
|
||||
player.setMediaItems(currentQueue, currentIndex, currentPosition)
|
||||
player.playWhenReady = isPlaying
|
||||
player.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.common.AudioAttributes
|
||||
@@ -14,13 +16,13 @@ import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
@@ -34,6 +36,19 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
private lateinit var castPlayer: CastPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
|
||||
lateinit var equalizerManager: EqualizerManager
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getEqualizerManager(): EqualizerManager {
|
||||
return this@MediaService.equalizerManager
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
|
||||
companion object {
|
||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -43,10 +58,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
initializeCastPlayer()
|
||||
initializeMediaLibrarySession()
|
||||
initializePlayerListener()
|
||||
initializeEqualizerManager()
|
||||
|
||||
setPlayer(
|
||||
null,
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||
null,
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,18 +79,43 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
equalizerManager.release()
|
||||
releasePlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
// Check if the intent is for our custom equalizer binder
|
||||
if (intent?.action == ACTION_BIND_EQUALIZER) {
|
||||
return binder
|
||||
}
|
||||
// Otherwise, handle it as a normal MediaLibraryService connection
|
||||
return super.onBind(intent)
|
||||
}
|
||||
|
||||
private fun initializeRepository() {
|
||||
automotiveRepository = AutomotiveRepository()
|
||||
}
|
||||
|
||||
private fun initializeEqualizerManager() {
|
||||
equalizerManager = EqualizerManager()
|
||||
val audioSessionId = player.audioSessionId
|
||||
if (equalizerManager.attachToSession(audioSessionId)) {
|
||||
val enabled = Preferences.isEqualizerEnabled()
|
||||
equalizerManager.setEnabled(enabled)
|
||||
|
||||
val bands = equalizerManager.getNumberOfBands()
|
||||
val savedLevels = Preferences.getEqualizerBandLevels(bands)
|
||||
for (i in 0 until bands) {
|
||||
equalizerManager.setBandLevel(i.toShort(), savedLevels[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setMediaSourceFactory(DynamicMediaSourceFactory(this))
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
@@ -87,7 +128,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
private fun initializeCastPlayer() {
|
||||
if (GoogleApiAvailability.getInstance()
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
) {
|
||||
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
||||
castPlayer.setSessionAvailabilityListener(this)
|
||||
@@ -96,16 +137,16 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
librarySessionCallback = createLibrarySessionCallback()
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
|
||||
@@ -124,7 +165,11 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null && currentMediaItem.mediaMetadata.extras != null) {
|
||||
MediaManager.scrobble(currentMediaItem, false)
|
||||
}
|
||||
|
||||
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
|
||||
MediaManager.continuousPlay(player.currentMediaItem)
|
||||
@@ -133,8 +178,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
} else {
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
@@ -145,8 +190,8 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
) {
|
||||
MediaManager.scrobble(player.currentMediaItem, true)
|
||||
MediaManager.saveChronology(player.currentMediaItem)
|
||||
@@ -154,9 +199,9 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
@@ -175,14 +220,14 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -190,13 +235,21 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
|
||||
private fun initializeLoadControl(): DefaultLoadControl {
|
||||
return DefaultLoadControl.Builder()
|
||||
.setBufferDurationsMs(
|
||||
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||
)
|
||||
.build()
|
||||
.setBufferDurationsMs(
|
||||
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getQueueFromPlayer(player: Player): List<MediaItem> {
|
||||
val queue = mutableListOf<MediaItem>()
|
||||
for (i in 0 until player.mediaItemCount) {
|
||||
queue.add(player.getMediaItemAt(i))
|
||||
}
|
||||
return queue
|
||||
}
|
||||
|
||||
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||||
@@ -211,19 +264,33 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
automotiveRepository.deleteMetadata()
|
||||
clearListener()
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory() =
|
||||
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||
|
||||
override fun onCastSessionAvailable() {
|
||||
val currentQueue = getQueueFromPlayer(player)
|
||||
val currentIndex = player.currentMediaItemIndex
|
||||
val currentPosition = player.currentPosition
|
||||
val isPlaying = player.playWhenReady
|
||||
|
||||
setPlayer(player, castPlayer)
|
||||
|
||||
castPlayer.setMediaItems(currentQueue, currentIndex, currentPosition)
|
||||
castPlayer.playWhenReady = isPlaying
|
||||
castPlayer.prepare()
|
||||
}
|
||||
|
||||
override fun onCastSessionUnavailable() {
|
||||
val currentQueue = getQueueFromPlayer(castPlayer)
|
||||
val currentIndex = castPlayer.currentMediaItemIndex
|
||||
val currentPosition = castPlayer.currentPosition
|
||||
val isPlaying = castPlayer.playWhenReady
|
||||
|
||||
setPlayer(castPlayer, player)
|
||||
|
||||
player.setMediaItems(currentQueue, currentIndex, currentPosition)
|
||||
player.playWhenReady = isPlaying
|
||||
player.prepare()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
mockup/usage/fave_album.png
Normal file
BIN
mockup/usage/fave_album.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
mockup/usage/fave_artist.png
Normal file
BIN
mockup/usage/fave_artist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Reference in New Issue
Block a user