Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd41395ab8 | ||
|
|
269066e036 | ||
|
|
488460ea9d | ||
|
|
d16a9c234f | ||
|
|
07b507691c | ||
|
|
bde34d3df0 | ||
|
|
e5b7756f96 | ||
|
|
04e692e5e9 | ||
|
|
a23a663d32 | ||
|
|
023bd8071a | ||
|
|
72b1517f61 | ||
|
|
e62ea72c2f | ||
|
|
a24ccf2556 | ||
|
|
49838e2e0f | ||
|
|
8ed1248ee1 | ||
|
|
e9d54957ae | ||
|
|
3cd5843c4b | ||
|
|
75513d3bd4 | ||
|
|
c0c84269ef | ||
|
|
fa2e029f9f | ||
|
|
f1bfb095b7 | ||
|
|
4328415efc | ||
|
|
092ae14ea2 | ||
|
|
26af8a692f | ||
|
|
b870f4c866 | ||
|
|
cf4e78eafc | ||
|
|
83e23c44d9 | ||
|
|
c0959c7ca4 | ||
|
|
e77f3bf9b3 | ||
|
|
55265615e6 | ||
|
|
bd872fc23d | ||
|
|
64a1966ad8 | ||
|
|
5ef5731fe3 | ||
|
|
c5cece8477 | ||
|
|
bae9221070 | ||
|
|
c0dbe01bf9 | ||
|
|
5f550b0df4 | ||
|
|
6100c3e7f1 | ||
|
|
f01ca9fed0 | ||
|
|
d232ebfa6f | ||
|
|
53ca88989f | ||
|
|
a82cf70433 | ||
|
|
89aa18b5f0 | ||
|
|
431014adc4 | ||
|
|
6110a9c8e7 | ||
|
|
993374e56c | ||
|
|
a2801f3168 | ||
|
|
99c31f4318 | ||
|
|
05785979e3 | ||
|
|
586a1a160e | ||
|
|
d04ed8d430 | ||
|
|
193447d07e | ||
|
|
1725b0de2e | ||
|
|
a2401302ed | ||
|
|
f39891dd2c | ||
|
|
8c5390bfef | ||
|
|
10673a49d4 | ||
|
|
3ce34fb874 | ||
|
|
5c94e9122c | ||
|
|
8140e80d61 | ||
|
|
c1b2ec09a4 | ||
|
|
3b3f55c5de | ||
|
|
17020e5192 | ||
|
|
f22aea7b1d | ||
|
|
844b57054b | ||
|
|
8de9aff1f6 | ||
|
|
f59f572e5c | ||
|
|
da2221540e | ||
|
|
9fa29c183a | ||
|
|
d034171d92 | ||
|
|
3a30b3d379 | ||
|
|
2624f396e5 | ||
|
|
8ae32a3a22 | ||
|
|
3c1975f6bf | ||
|
|
43a96faca4 | ||
|
|
bbd6d0864c | ||
|
|
ccea7674bd | ||
|
|
7f332c26ad | ||
|
|
206a7f38ca | ||
|
|
16e0a5e12e | ||
|
|
c6896939e2 | ||
|
|
526253723b | ||
|
|
9350a9cc2e | ||
|
|
e2ec2e4602 | ||
|
|
bca2e8fcae | ||
|
|
43674ea1f9 | ||
|
|
373a1f87a1 | ||
|
|
e14a595fba | ||
|
|
727e137008 | ||
|
|
883d853129 | ||
|
|
0d329aff64 | ||
|
|
94cb6fa279 | ||
|
|
257d80ecac | ||
|
|
d0f77fe0fc | ||
|
|
e95b504dbb | ||
|
|
0b68799507 | ||
|
|
9167be2cf2 | ||
|
|
d426c08cdd | ||
|
|
972c32b9d8 | ||
|
|
a279e20a49 | ||
|
|
fe60fea928 | ||
|
|
c6df43da9c | ||
|
|
475ed3e7c8 | ||
|
|
fb4c762655 | ||
|
|
a110faabe3 | ||
|
|
df2bf43492 | ||
|
|
b46fea6890 | ||
|
|
213a0d5293 | ||
|
|
08b6379601 | ||
|
|
3fbadc2521 | ||
|
|
9e78caeda4 | ||
|
|
e072a49288 | ||
|
|
b89e18eebf | ||
|
|
63607794d6 | ||
|
|
37842fd897 | ||
|
|
a1397a224b | ||
|
|
804d6af6c3 | ||
|
|
e315169005 | ||
|
|
ea76afee09 | ||
|
|
45dda3af9b | ||
|
|
3d70b51244 | ||
|
|
22f196c8c0 | ||
|
|
540aa9ba73 | ||
|
|
1ff0b83a19 | ||
|
|
27f5a47cc9 | ||
|
|
732b6ad09d | ||
|
|
0df7346a14 | ||
|
|
786697109d | ||
|
|
1bfadb0669 | ||
|
|
79dc1cc93b | ||
|
|
38fb2c69f1 | ||
|
|
b34f827bc0 | ||
|
|
97d1b408e1 | ||
|
|
a5065578ca | ||
|
|
aac5c6067d | ||
|
|
cfd7cf314b | ||
|
|
c4b73f6014 | ||
|
|
35d377ce31 | ||
|
|
5e330ac451 | ||
|
|
8188ef169c | ||
|
|
3496918ce6 | ||
|
|
c72f368f6a | ||
|
|
eb089847e0 | ||
|
|
8aaa6b207e | ||
|
|
72d560e4eb | ||
|
|
31219ea754 | ||
|
|
52c411ead0 | ||
|
|
0edbd15d47 | ||
|
|
342241963a | ||
|
|
f5b381eb35 | ||
|
|
be33401b6f | ||
|
|
c415db0cc5 | ||
|
|
35576c3d6f | ||
|
|
a11fbfa829 | ||
|
|
26d1b144e4 | ||
|
|
16b63bf13c | ||
|
|
52434f3aa9 | ||
|
|
7aa325f914 | ||
|
|
5a8a631449 | ||
|
|
e6bbd7b2bf | ||
|
|
3721484dff | ||
|
|
6698052ba5 | ||
|
|
f6f24acfdf | ||
|
|
ca8bcba0d7 | ||
|
|
9a36f8541f | ||
|
|
0026dc287f | ||
|
|
c65077172d | ||
|
|
6e6c261f35 | ||
|
|
130cbbd7dd | ||
|
|
63668f5a8c | ||
|
|
193b551773 | ||
|
|
76a0e12222 | ||
|
|
887d8c85ee | ||
|
|
6a90f06084 | ||
|
|
f7a21cbb52 | ||
|
|
3c6c240b9d | ||
|
|
2553c06a9f | ||
|
|
62a10d142e | ||
|
|
955dc1b015 | ||
|
|
6124ec66f3 | ||
|
|
2c6287405e | ||
|
|
0be309fb22 | ||
|
|
a79543569d | ||
|
|
e3d7120193 | ||
|
|
80a3a54476 | ||
|
|
6448cc598d | ||
|
|
c4bd30d512 | ||
|
|
b2e3596d87 | ||
|
|
ccfe74a6ea | ||
|
|
f4ffdc985e | ||
|
|
33981f9885 | ||
|
|
1bc93cce0e | ||
|
|
ec0eee9d3f | ||
|
|
140546ca4d | ||
|
|
fd075a02c5 | ||
|
|
4d1d953a3a | ||
|
|
9dd509be22 | ||
|
|
efaae35976 | ||
|
|
35784216bc | ||
|
|
8ce0a82506 | ||
|
|
51883cd82b | ||
|
|
829c5d85f6 | ||
|
|
748a19ef44 | ||
|
|
e987226954 | ||
|
|
59e2f4a7fa | ||
|
|
817b53efaa | ||
|
|
12c7ec86a9 | ||
|
|
42b7441467 | ||
|
|
8ad35ce83a | ||
|
|
4464b5b34d | ||
|
|
7267c13ee0 |
137
CHANGELOG.md
137
CHANGELOG.md
@@ -1,10 +1,141 @@
|
||||
# Changelog
|
||||
|
||||
## Pending release..
|
||||
* fix: reverts change causing album disc/track list to get out of order by @eddyizm in https://github.com/eddyizm/tempus/pull/237
|
||||
## Pending release
|
||||
|
||||
## What's Changed
|
||||
## [4.9.1](https://github.com/eddyizm/tempo/releases/tag/v4.9.1) (2026-01-24)
|
||||
* chore: i18n: Add Romanian translation (including locale_config this time!) by @DevMatei in https://github.com/eddyizm/tempus/pull/357
|
||||
* French localization update by @benoit-smith in https://github.com/eddyizm/tempus/pull/356
|
||||
* chore(i18n): Update Spanish translation by @jaime-grj in https://github.com/eddyizm/tempus/pull/364
|
||||
* docs: updated readme and added known issues for airsonic work around by @eddyizm in https://github.com/eddyizm/tempus/pull/366
|
||||
* fix: toast for made for you click indication by @eddyizm in https://github.com/eddyizm/tempus/pull/365
|
||||
* fix: sort playlist view by @eddyizm in https://github.com/eddyizm/tempus/pull/368
|
||||
* feat: sort preference for playlists by @eddyizm in https://github.com/eddyizm/tempus/pull/370
|
||||
* fix: use existing future when adding tracks, dialed random album tracks off in instant mix by @eddyizm in https://github.com/eddyizm/tempus/pull/373
|
||||
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/374
|
||||
* fix: Check for OpenSubsonic extensions also with password authentication by @pgrit in https://github.com/eddyizm/tempus/pull/375
|
||||
* feat: Implement duration and seeking for transcodes by @drakeerv in https://github.com/eddyizm/tempus/pull/358
|
||||
* feat: Playback speed controls for music by @pgrit in https://github.com/eddyizm/tempus/pull/376
|
||||
|
||||
## New Contributors
|
||||
* @pgrit made their first contribution in https://github.com/eddyizm/tempus/pull/375
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.6.4...v4.9.1
|
||||
|
||||
## What's Changed
|
||||
## [4.6.4](https://github.com/eddyizm/tempo/releases/tag/v4.6.4) (2026-01-13)
|
||||
* fix: instant mix random songs and broken continuous play by @eddyizm in https://github.com/eddyizm/tempus/pull/354
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.6.3...v4.6.4
|
||||
|
||||
## What's Changed
|
||||
## [4.6.3](https://github.com/eddyizm/tempo/releases/tag/v4.6.3) (2026-01-10)
|
||||
* fix: give user feedback when trying to add podcast/radio on unsupport… by @eddyizm in https://github.com/eddyizm/tempus/pull/328
|
||||
* docs: Clarify Android Auto enablement by @Forage in https://github.com/eddyizm/tempus/pull/336
|
||||
* fix: instant mix gets a big refactor, with cascading fallbacks to produce a larger queue by @eddyizm in https://github.com/eddyizm/tempus/pull/330
|
||||
* chore(i18n): add missing keys, update Chinese translation and alphabetize by @hongwei1203 in https://github.com/eddyizm/tempus/pull/332
|
||||
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/339
|
||||
* feat: Ability to toggle visibility of artist biography by @kmarius in https://github.com/eddyizm/tempus/pull/338
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.6.0...v4.6.3
|
||||
|
||||
## [4.6.0](https://github.com/eddyizm/tempo/releases/tag/v4.6.0) (2025-12-22)
|
||||
## What's Changed
|
||||
* chore: Update description_empty_title in English and Polish by @tyren234 in https://github.com/eddyizm/tempus/pull/307
|
||||
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/310
|
||||
* fix: checks preference and writes files externally, updates the ui by @eddyizm in https://github.com/eddyizm/tempus/pull/312
|
||||
* chore: Update description_empty_title in Italian by @pochopsp in https://github.com/eddyizm/tempus/pull/314
|
||||
* chore: Update description_empty_title in French and Spanish by @pochopsp in https://github.com/eddyizm/tempus/pull/315
|
||||
* feat: added regular playlist to home view by @eddyizm in https://github.com/eddyizm/tempus/pull/322
|
||||
|
||||
## New Contributors
|
||||
* @tyren234 made their first contribution in https://github.com/eddyizm/tempus/pull/307
|
||||
* @pochopsp made their first contribution in https://github.com/eddyizm/tempus/pull/314
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.5.0...v4.6.0
|
||||
|
||||
## [4.5.0](https://github.com/eddyizm/tempo/releases/tag/v4.5.0) (2025-12-12)
|
||||
## What's Changed
|
||||
* fix: updates starred syncing downloads to user defined directory by @eddyizm in https://github.com/eddyizm/tempus/pull/298
|
||||
* fix: handle empty albums and null mappings by @eddyizm in https://github.com/eddyizm/tempus/pull/301
|
||||
* feat: integrate sort recent searches chronologically by @J4mm3ris in https://github.com/eddyizm/tempus/pull/300
|
||||
* feat: add heart to artist/album pages, fixed artist cover art failing by @eddyizm in https://github.com/eddyizm/tempus/pull/303
|
||||
|
||||
## New Contributors
|
||||
* @J4mm3ris made their first contribution in https://github.com/eddyizm/tempus/pull/300
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.4.0...v4.5.0
|
||||
|
||||
## [4.4.0](https://github.com/eddyizm/tempo/releases/tag/v4.4.0) (2025-11-29)
|
||||
## What's Changed
|
||||
* chore: bringing in media service refactor previously reverted after more testing by @eddyizm in https://github.com/eddyizm/tempus/pull/286
|
||||
* fix: refactor start queue to put the db writing in the background to address instant mix bug by @eddyizm in https://github.com/eddyizm/tempus/pull/287
|
||||
* Feat: playerqueue fab allows playqueue actions -> saving to playlist, download all, load queue, shuffle, clean queue by @eddyizm in https://github.com/eddyizm/tempus/pull/288
|
||||
* chore(i18n): Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/291
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.3.0...v4.4.0
|
||||
|
||||
## [4.3.0](https://github.com/eddyizm/tempo/releases/tag/v4.3.0) (2025-11-23)
|
||||
## What's Changed
|
||||
* chore: Add Obtainium badge to README by @mikaeldui in https://github.com/eddyizm/tempus/pull/280
|
||||
* fix: Revert "refactor MediaService" by @eddyizm in https://github.com/eddyizm/tempus/pull/282
|
||||
* feat: add play functionality to library folder/index items by @antebudimir in https://github.com/eddyizm/tempus/pull/276
|
||||
* fix: start queue blocking UI by @eddyizm in https://github.com/eddyizm/tempus/pull/283
|
||||
|
||||
## New Contributors
|
||||
* @mikaeldui made their first contribution in https://github.com/eddyizm/tempus/pull/280
|
||||
* @antebudimir made their first contribution in https://github.com/eddyizm/tempus/pull/276
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.2.6...v4.3.0
|
||||
|
||||
## [4.2.6](https://github.com/eddyizm/tempo/releases/tag/v4.2.6) (2025-11-22)
|
||||
## What's Changed
|
||||
* fix: Fix player queue soft-lock by @shrapnelnet in https://github.com/eddyizm/tempus/pull/266
|
||||
* chore: Add Catalan i18n by @marcriera in https://github.com/eddyizm/tempus/pull/268
|
||||
* chore: Refactor MediaService by @pca006132 in https://github.com/eddyizm/tempus/pull/267
|
||||
* chore(i18n): Update Spanish translation by @jaime-grj in https://github.com/eddyizm/tempus/pull/272
|
||||
* chore(i18n): Update Italian translation by @66Bunz in https://github.com/eddyizm/tempus/pull/278
|
||||
|
||||
## New Contributors
|
||||
* @marcriera made their first contribution in https://github.com/eddyizm/tempus/pull/268
|
||||
* @66Bunz made their first contribution in https://github.com/eddyizm/tempus/pull/278
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.2.4...v4.2.6
|
||||
|
||||
## [4.2.4](https://github.com/eddyizm/tempo/releases/tag/v4.2.4) (2025-11-15)
|
||||
## What's Changed
|
||||
* chore: Update russian strings.xml by @Sevinfolds in https://github.com/eddyizm/tempus/pull/249
|
||||
* fix: disallow duplicate songs in queue by @eddyizm in https://github.com/eddyizm/tempus/pull/252
|
||||
* fix:github release check by @eddyizm in https://github.com/eddyizm/tempus/pull/253
|
||||
* fix: Fixed crash when viewing share by @drakeerv in https://github.com/eddyizm/tempus/pull/255
|
||||
* chore: Update Polish translation by @skajmer in https://github.com/eddyizm/tempus/pull/257
|
||||
* fix: add podcast/radio channel visible when empty podcasts/radio by @eddyizm in https://github.com/eddyizm/tempus/pull/260
|
||||
|
||||
## New Contributors
|
||||
* @Sevinfolds made their first contribution in https://github.com/eddyizm/tempus/pull/249
|
||||
* @drakeerv made their first contribution in https://github.com/eddyizm/tempus/pull/255
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.2.0...v4.2.4
|
||||
## [4.2.0](https://github.com/eddyizm/tempo/releases/tag/v4.2.0) (2025-11-09)
|
||||
## What's Changed
|
||||
* fix: Equalizer fix in main build variant by @jaime-grj in https://github.com/eddyizm/tempus/pull/239
|
||||
* fix: Images not filling holder by @eddyizm in https://github.com/eddyizm/tempus/pull/244
|
||||
* feat: Make artist and album clickable by @eddyizm in https://github.com/eddyizm/tempus/pull/243
|
||||
* feat: implement scroll to currently playing feature by @shrapnelnet in https://github.com/eddyizm/tempus/pull/247
|
||||
* fix: shuffling genres only queuing 25 songs by @shrapnelnet in https://github.com/eddyizm/tempus/pull/246
|
||||
|
||||
## New Contributors
|
||||
* @shrapnelnet made their first contribution in https://github.com/eddyizm/tempus/pull/247
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.1.3...v4.2.0
|
||||
|
||||
## [4.1.3](https://github.com/eddyizm/tempo/releases/tag/v4.1.3) (2025-11-06)
|
||||
## What's Changed
|
||||
* [fix: equalizer missing referenced value](https://github.com/eddyizm/tempus/commit/923cfd5bc97ed7db28c90348e3619d0a784fc434)
|
||||
* Fix: Album track list bug by @eddyizm in https://github.com/eddyizm/tempus/pull/237
|
||||
* fix: Add listener to enable equalizer when audioSessionId changes by @jaime-grj in https://github.com/eddyizm/tempus/pull/235
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.1.0...v4.1.2
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.1.0...v4.1.3
|
||||
|
||||
## [4.1.0](https://github.com/eddyizm/tempo/releases/tag/v4.1.0) (2025-11-05)
|
||||
## What's Changed
|
||||
|
||||
39
README.md
39
README.md
@@ -10,14 +10,21 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
<!-- Reproducible build -->
|
||||
<!-- [<img src="https://shields.rbtlog.dev/simple/com.eddyizm.degoogled.tempus" alt="RB Status">](https://shields.rbtlog.dev/com.eddyizm.degoogled.tempus) -->
|
||||
|
||||
<a href="https://github.com/eddyizm/tempus/releases/">
|
||||
<img alt="Releases" src="https://img.shields.io/github/downloads/eddyizm/tempus/total.svg?color=4B95DE&style=flat">
|
||||
</a>
|
||||
<!-- Reproducible build -->
|
||||
<a href="https://shields.rbtlog.dev/com.eddyizm.degoogled.tempus"><img src="https://shields.rbtlog.dev/simple/com.eddyizm.degoogled.tempus" alt="RB Status"></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0">
|
||||
<img src="https://img.shields.io/badge/license-GPL%20v3-2B6DBE.svg?style=flat">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/eddyizm/tempus/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.eddyizm.degoogled.tempus"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
|
||||
<a href="https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.eddyizm.tempus%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2Feddyizm%2Ftempus%22%2C%22author%22%3A%22eddyizm%22%2C%22name%22%3A%22Tempus%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22includePrereleases%5C%22%3Afalse%2C%5C%22fallbackToOlderReleases%5C%22%3Atrue%2C%5C%22filterReleaseTitlesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22filterReleaseNotesByRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22verifyLatestTag%5C%22%3Afalse%2C%5C%22sortMethodChoice%5C%22%3A%5C%22date%5C%22%2C%5C%22useLatestAssetDateAsReleaseDate%5C%22%3Afalse%2C%5C%22releaseTitleAsVersion%5C%22%3Afalse%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Atrue%2C%5C%22releaseDateAsVersion%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22tempus%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%2C%5C%22includeZips%5C%22%3Afalse%2C%5C%22zippedApkFilterRegEx%5C%22%3A%5C%22%5C%22%7D%22%2C%22overrideSource%22%3A%22GitHub%22%7D"><img width="200" src="https://github.com/user-attachments/assets/119e7ff4-2636-43cb-ab7f-1b6a58ac3570" /></a>
|
||||
<a href="https://www.openapk.net/tempus/com.eddyizm.degoogled.tempus/"><img src="https://camo.githubusercontent.com/cd56895b28a73ebd781a65b4f567add5419e45797a5cf1485ce408e851c2318e/68747470733a2f2f7777772e6f70656e61706b2e6e65742f696d616765732f6f70656e61706b2d62616467652e706e67" width="200"></a>
|
||||
</p>
|
||||
<!--
|
||||
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
|
||||
@@ -26,10 +33,14 @@
|
||||
|
||||
**Tempus** is an open-source and lightweight music client for Subsonic, designed and built natively for Android. It provides a seamless and intuitive music streaming experience, allowing you to access and play your Subsonic music library directly from your Android device.
|
||||
|
||||
Tempus does not rely on magic algorithms to decide what you should listen to. Instead, the interface is built around your listening history, randomness, and optionally integrates with services like Last.fm to personalize your music experience.
|
||||
Tempus does not rely on magic algorithms to decide what you should listen to. Instead, the interface is built around your listening history, randomness, and optionally integrates with services like Listenbrainz.org and Last.fm to personalize your music experience (These must be supported by your backend).
|
||||
|
||||
The project is a fork of [Tempo](#credits).
|
||||
|
||||
[Changelog](CHANGELOG.md)
|
||||
[Wiki](USAGE.md)
|
||||
[Donate](https://github.com/eddyizm/tempus#donate)
|
||||
|
||||
**If you find Tempus useful, please consider starring the project on GitHub. It would mean a lot to me and help promote the app to a wider audience.**
|
||||
|
||||
**Use the Github version of the app for full Android Auto and Chromecast support.**
|
||||
@@ -45,11 +56,6 @@ Please note the two variants in the release assets include release/debug and 32/
|
||||
|
||||
`app-degoogled*` <- The izzyOnDroid release that goes without any of the google stuff. It is now available on izzyOnDroid (64bit) I am releasing the both 32/64bit apk's here on github for those who need a 32bit version.
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## Usage
|
||||
|
||||
[Documentation](USAGE.md) (work in progress)
|
||||
|
||||
## Features
|
||||
- **Subsonic Integration**: Tempus seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
|
||||
@@ -58,16 +64,19 @@ Please note the two variants in the release assets include release/debug and 32/
|
||||
- **Streaming and Offline Mode**: Stream music directly from your Subsonic server. Offline mode is currently under active development and may have limitations when using multiple servers.
|
||||
- **Playlist Management**: Create, edit, and manage playlists to curate your perfect music collection.
|
||||
- **Gapless Playback**: Experience uninterrupted playback with gapless listening mode.
|
||||
- **Chromecast Support**: Stream your music to Chromecast devices. The support is currently in a rudimentary state.
|
||||
- **Chromecast Support**: Stream your music to Chromecast devices. The support is currently in a rudimentary state.*
|
||||
- **Scrobbling Integration**: Optionally integrate Tempus with Last.fm or Listenbrainz.org to scrobble your played tracks, gather music insights, and further personalize your music recommendations, if supported by your Subsonic server.
|
||||
- **Podcasts and Radio**: If your Subsonic server supports it, listen to podcasts and radio shows directly within Tempus, expanding your audio entertainment options.
|
||||
- **Instant Mix**: Full refactor of instant mix function which leverages subsonics similarSongs2 by artist/album and similarSongs endpoints to server a larger play queue more reliably.
|
||||
- **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.
|
||||
- **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.*
|
||||
- **Multiple Libraries**: Tempus handles multi-library setups gracefully. They are displayed as Library folders.
|
||||
- **Equalizer**: Option to use in app equalizer.
|
||||
- **Widget**: New widget to keeping the basic controls on your screen at all times.
|
||||
- **Available in 11 languages**: Currently in Chinese, French, German, Italian, Korean, Polish, Portuguese, Russion, Spanish and Turkish
|
||||
|
||||
**Github version only*
|
||||
|
||||
## Screenshot
|
||||
|
||||
<p align="center">
|
||||
@@ -111,7 +120,10 @@ Currently there are no tests but I would love to start on some unit tests.
|
||||
|
||||
Not a hard requirement but any new feature/change should ideally include an update to the nacent documention.
|
||||
|
||||
## Support
|
||||
*Special Thanks*
|
||||
All the amazing [contributors](https://github.com/eddyizm/tempus/graphs/contributors)❤️
|
||||
|
||||
## Donate
|
||||
|
||||
[**Buy me a coffee**](https://ko-fi.com/eddyizm)
|
||||
bitcoin: `3QVHSSCJvn6yXEcJ3A3cxYLMmbvFsrnUs5`
|
||||
@@ -120,7 +132,8 @@ bitcoin: `3QVHSSCJvn6yXEcJ3A3cxYLMmbvFsrnUs5`
|
||||
|
||||
Tempus is released under the [GNU General Public License v3.0](LICENSE). Feel free to modify, distribute, and use the app in accordance with the terms of the license. Contributions to the project are also welcome.
|
||||
|
||||
|
||||
## Credits
|
||||
Thanks to the original repo/creator [CappielloAntonio](https://github.com/CappielloAntonio) (forked from v3.9.0)
|
||||
|
||||
[Opensvg.org](https://opensvg.org) for the new turntable logo.
|
||||
[Opensvg.org](https://opensvg.org) for the new turntable logo.
|
||||
|
||||
61
USAGE.md
61
USAGE.md
@@ -12,7 +12,7 @@
|
||||
- [Playlist Management](#playlist-management)
|
||||
- [Android Auto](#android-auto)
|
||||
- [Settings](#settings)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Known Issues](#known-issues)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -27,7 +27,9 @@ 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)
|
||||
|
||||
- [Ampache](https://github.com/ampache/ampache)
|
||||
- [NextCloud Music](https://apps.nextcloud.com/apps/music)
|
||||
- [Airsonic Advanced](https://github.com/kagemomiji/airsonic-advanced)
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +68,21 @@ However, if you want to limit or change libraries you could use a workaround, if
|
||||
|
||||
You can create multiple users , one for each library, and save each of them in Tempus app.
|
||||
|
||||
### Folder or index playback
|
||||
|
||||
If your Subsonic-compatible server exposes the folder tree **or** provides an artist index (for example Gonic, Navidrome, or any backend with folder browsing enabled), Tempus lets you play an entire folder from anywhere in the library hierarchy:
|
||||
|
||||
<p align="left">
|
||||
<img src="mockup/usage/music_folders_root.png" width=317 style="margin-right:16px;">
|
||||
<img src="mockup/usage/music_folders_playback.png" width=317>
|
||||
</p>
|
||||
|
||||
- The **Library ▸ Music folders** screen shows each top-level folder with a play icon only after you drill into it. The root entry remains a simple navigator.
|
||||
- When viewing **inner folders** **or artist index entries**, tap the new play button to immediately enqueue every audio track inside that folder/index and all nested subfolders.
|
||||
- Video files are excluded automatically, so only playable audio ends up in the queue.
|
||||
|
||||
No extra config is needed—Tempus adjusts based on the connected backend.
|
||||
|
||||
### Now Playing Screen
|
||||
|
||||
On the main player control screen, tapping on the artwork will reveal a small collection of 4 buttons/icons.
|
||||
@@ -84,6 +101,17 @@ On the main player control screen, tapping on the artwork will reveal a small co
|
||||
4. Saves play queue (if the feature is enabled in the settings)
|
||||
* if the setting is not enabled, it toggles a view of the lyrics if available (slides to the right)
|
||||
|
||||
### Podcasts
|
||||
If your server supports it - add a podcast rss feed
|
||||
<p align="left">
|
||||
<img src="mockup/usage/add_podcast_feed.png" width=317>
|
||||
</p>
|
||||
|
||||
### Radio Stations
|
||||
If your server supports it - add a internet radio station feed
|
||||
<p align="left">
|
||||
<img src="mockup/usage/add_radio_station.png" width=326>
|
||||
</p>
|
||||
|
||||
## Navigation
|
||||
|
||||
@@ -131,7 +159,23 @@ On the main player control screen, tapping on the artwork will reveal a small co
|
||||
## 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)
|
||||
To allow the Tempus app on your car's head unit, "Unknown sources" needs to be enabled in the Android Auto "Developer settings". This is because Tempus isn't installed through Play Store. Note that the Android Auto developer settings are different from the global Android "Developer options".
|
||||
1. Switch to developer mode in the Android Auto settings by tapping ten times on the "Version" item at the bottom, followed by giving your permission.
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="1a" src="https://github.com/user-attachments/assets/f09f6999-9761-4b05-8ec7-bf221a15dda3" />
|
||||
<img width="270" height="600" alt="1b" src="https://github.com/user-attachments/assets/0795e508-ba01-41c5-96a7-7c03b0156591" />
|
||||
<img width="270" height="600" alt="1c" src="https://github.com/user-attachments/assets/51c15f67-fddb-452e-b5d3-5092edeab390" />
|
||||
</p>
|
||||
|
||||
2. Go to the "Developer settings" by the menu at the top right.
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="2" src="https://github.com/user-attachments/assets/1ecd1f3e-026d-4d25-87f2-be7f12efbac6" />
|
||||
</p>
|
||||
|
||||
3. Scroll down to the bottom and check "Unknown sources".
|
||||
<p align="left">
|
||||
<img width="270" height="600" alt="3" src="https://github.com/user-attachments/assets/37db88e9-1b76-417f-9c47-da9f3a750fff" />
|
||||
</p>
|
||||
|
||||
|
||||
### Server Settings
|
||||
@@ -148,15 +192,12 @@ On the main player control screen, tapping on the artwork will reveal a small co
|
||||
### Appearance
|
||||
**TODO**
|
||||
|
||||
## Troubleshooting
|
||||
## Known Issues
|
||||
|
||||
### Connection Issues
|
||||
### Airsonic Distorted Playback
|
||||
|
||||
**TODO**
|
||||
|
||||
### Common Issues
|
||||
|
||||
**TODO**
|
||||
First reported in issue [#226](https://github.com/eddyizm/tempus/issues/226)
|
||||
The work around is to disable the cache in the settings, (set to 0), and if needed, cleaning the (Android) cache fixes the problem.
|
||||
|
||||
### Support
|
||||
For additional help:
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
minSdkVersion 24
|
||||
targetSdk 35
|
||||
|
||||
versionCode 4
|
||||
versionName '4.1.3'
|
||||
versionCode 14
|
||||
versionName '4.9.1'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
|
||||
1158
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/13.json
Normal file
1158
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/13.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,561 +1,6 @@
|
||||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
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.MediaSource
|
||||
import androidx.media3.session.*
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.R
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||
import com.cappielloantonio.tempo.util.MappingUtil
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.cappielloantonio.tempo.widget.WidgetUpdateManager
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
|
||||
@UnstableApi
|
||||
class MediaService : MediaLibraryService() {
|
||||
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
||||
|
||||
private lateinit var player: ExoPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var shuffleCommands: List<CommandButton>
|
||||
private lateinit var repeatCommands: List<CommandButton>
|
||||
private lateinit var networkCallback: CustomNetworkCallback
|
||||
lateinit var equalizerManager: EqualizerManager
|
||||
|
||||
private var customLayout = ImmutableList.of<CommandButton>()
|
||||
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
|
||||
private var widgetUpdateScheduled = false
|
||||
private val widgetUpdateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (!player.isPlaying) {
|
||||
widgetUpdateScheduled = false
|
||||
return
|
||||
}
|
||||
updateWidget()
|
||||
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||
"android.media3.session.demo.SHUFFLE_OFF"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
|
||||
"android.media3.session.demo.REPEAT_OFF"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
|
||||
"android.media3.session.demo.REPEAT_ONE"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
|
||||
"android.media3.session.demo.REPEAT_ALL"
|
||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||
const val ACTION_EQUALIZER_UPDATED = "com.cappielloantonio.tempo.service.EQUALIZER_UPDATED"
|
||||
}
|
||||
|
||||
fun updateMediaItems() {
|
||||
Log.d("MediaService", "update items");
|
||||
val n = player.mediaItemCount
|
||||
val k = player.currentMediaItemIndex
|
||||
val current = player.currentPosition
|
||||
val items = (0 .. n-1).map{i -> MappingUtil.mapMediaItem(player.getMediaItemAt(i))}
|
||||
player.clearMediaItems()
|
||||
player.setMediaItems(items, k, current)
|
||||
}
|
||||
|
||||
inner class CustomNetworkCallback : ConnectivityManager.NetworkCallback() {
|
||||
var wasWifi = false
|
||||
|
||||
init {
|
||||
val manager = getSystemService(ConnectivityManager::class.java)
|
||||
val network = manager.activeNetwork
|
||||
val capabilities = manager.getNetworkCapabilities(network)
|
||||
if (capabilities != null)
|
||||
wasWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
|
||||
val isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
if (isWifi != wasWifi) {
|
||||
wasWifi = isWifi
|
||||
widgetUpdateHandler.post(Runnable {
|
||||
updateMediaItems()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
initializeCustomCommands()
|
||||
initializePlayer()
|
||||
initializeMediaLibrarySession()
|
||||
restorePlayerFromQueue()
|
||||
initializePlayerListener()
|
||||
initializeEqualizerManager()
|
||||
initializeNetworkListener()
|
||||
|
||||
setPlayer(player)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releaseNetworkCallback()
|
||||
equalizerManager.release()
|
||||
stopWidgetUpdates()
|
||||
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(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
val connectionResult = super.onConnect(session, controller)
|
||||
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
|
||||
|
||||
(shuffleCommands + repeatCommands).forEach { commandButton ->
|
||||
commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
|
||||
}
|
||||
|
||||
customLayout = buildCustomLayout(session.player)
|
||||
|
||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(availableSessionCommands.build())
|
||||
.setAvailablePlayerCommands(connectionResult.availablePlayerCommands)
|
||||
.setCustomLayout(customLayout)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
|
||||
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
|
||||
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
|
||||
}
|
||||
}
|
||||
|
||||
fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
|
||||
val shuffle = shuffleCommands[if (player.shuffleModeEnabled) 1 else 0]
|
||||
val repeat = when (player.repeatMode) {
|
||||
Player.REPEAT_MODE_ONE -> repeatCommands[1]
|
||||
Player.REPEAT_MODE_ALL -> repeatCommands[2]
|
||||
else -> repeatCommands[0]
|
||||
}
|
||||
return ImmutableList.of(shuffle, repeat)
|
||||
}
|
||||
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
when (customCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> player.shuffleModeEnabled = true
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> player.shuffleModeEnabled = false
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
|
||||
val nextMode = when (player.repeatMode) {
|
||||
Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
|
||||
Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
|
||||
else -> Player.REPEAT_MODE_OFF
|
||||
}
|
||||
player.repeatMode = nextMode
|
||||
}
|
||||
}
|
||||
|
||||
customLayout = librarySessionCallback.buildCustomLayout(player)
|
||||
session.setCustomLayout(customLayout)
|
||||
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
override fun onAddMediaItems(
|
||||
mediaSession: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
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()
|
||||
}
|
||||
return Futures.immediateFuture(updatedMediaItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCustomCommands() {
|
||||
shuffleCommands = listOf(
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
|
||||
),
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
|
||||
)
|
||||
)
|
||||
|
||||
repeatCommands = listOf(
|
||||
getRepeatCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY)
|
||||
),
|
||||
getRepeatCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY)
|
||||
),
|
||||
getRepeatCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY)
|
||||
)
|
||||
)
|
||||
|
||||
customLayout = ImmutableList.of(shuffleCommands[0], repeatCommands[0])
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.setLoadControl(initializeLoadControl())
|
||||
.build()
|
||||
|
||||
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
|
||||
player.repeatMode = Preferences.getRepeatMode()
|
||||
}
|
||||
|
||||
private fun initializeEqualizerManager() {
|
||||
equalizerManager = EqualizerManager()
|
||||
val audioSessionId = player.audioSessionId
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
|
||||
if (!customLayout.isEmpty()) {
|
||||
mediaLibrarySession.setCustomLayout(customLayout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeNetworkListener() {
|
||||
networkCallback = CustomNetworkCallback()
|
||||
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(networkCallback)
|
||||
updateMediaItems()
|
||||
}
|
||||
|
||||
private fun restorePlayerFromQueue() {
|
||||
if (player.mediaItemCount > 0) return
|
||||
|
||||
val queueRepository = QueueRepository()
|
||||
val storedQueue = queueRepository.media
|
||||
if (storedQueue.isNullOrEmpty()) return
|
||||
|
||||
val mediaItems = MappingUtil.mapMediaItems(storedQueue)
|
||||
if (mediaItems.isEmpty()) return
|
||||
|
||||
val lastIndex = try {
|
||||
queueRepository.lastPlayedMediaIndex
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}.coerceIn(0, mediaItems.size - 1)
|
||||
|
||||
val lastPosition = try {
|
||||
queueRepository.lastPlayedMediaTimestamp
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}.let { if (it < 0L) 0L else it }
|
||||
|
||||
player.setMediaItems(mediaItems, lastIndex, lastPosition)
|
||||
player.prepare()
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
private fun initializePlayerListener() {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
if (mediaItem == null) return
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
} else {
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
}
|
||||
if (isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
} else {
|
||||
stopWidgetUpdates()
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
) {
|
||||
MediaManager.scrobble(player.currentMediaItem, true)
|
||||
MediaManager.saveChronology(player.currentMediaItem)
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.scrobble(oldPosition.mediaItem, true)
|
||||
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||
}
|
||||
|
||||
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
customLayout = librarySessionCallback.buildCustomLayout(player)
|
||||
mediaLibrarySession.setCustomLayout(customLayout)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
customLayout = librarySessionCallback.buildCustomLayout(player)
|
||||
mediaLibrarySession.setCustomLayout(customLayout)
|
||||
}
|
||||
|
||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
})
|
||||
if (player.isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPlayer(player: Player) {
|
||||
mediaLibrarySession.player = player
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
}
|
||||
|
||||
private fun releaseNetworkCallback() {
|
||||
getSystemService(ConnectivityManager::class.java).unregisterNetworkCallback(networkCallback)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||
return CommandButton.Builder()
|
||||
.setDisplayName(
|
||||
getString(
|
||||
if (isOn) R.string.exo_controls_shuffle_on_description
|
||||
else R.string.exo_controls_shuffle_off_description
|
||||
)
|
||||
)
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
|
||||
.build()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getRepeatCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||
val icon = when (sessionCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> R.drawable.exo_icon_repeat_one
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> R.drawable.exo_icon_repeat_all
|
||||
else -> R.drawable.exo_icon_repeat_off
|
||||
}
|
||||
val description = when (sessionCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> R.string.exo_controls_repeat_one_description
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> R.string.exo_controls_repeat_all_description
|
||||
else -> R.string.exo_controls_repeat_off_description
|
||||
}
|
||||
return CommandButton.Builder()
|
||||
.setDisplayName(getString(description))
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setIconResId(icon)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun ignoreFuture(@Suppress("UNUSED_PARAMETER") customLayout: ListenableFuture<SessionResult>) {
|
||||
/* Do nothing. */
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun updateWidget() {
|
||||
val mi = player.currentMediaItem
|
||||
val title = mi?.mediaMetadata?.title?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("title")
|
||||
val artist = mi?.mediaMetadata?.artist?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("artist")
|
||||
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("album")
|
||||
val extras = mi?.mediaMetadata?.extras
|
||||
val coverId = extras?.getString("coverArtId")
|
||||
val songLink = extras?.getString("assetLinkSong")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras?.getString("id"))
|
||||
val albumLink = extras?.getString("assetLinkAlbum")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras?.getString("albumId"))
|
||||
val artistLink = extras?.getString("assetLinkArtist")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
||||
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
WidgetUpdateManager.updateFromState(
|
||||
this,
|
||||
title ?: "",
|
||||
artist ?: "",
|
||||
album ?: "",
|
||||
coverId,
|
||||
player.isPlaying,
|
||||
player.shuffleModeEnabled,
|
||||
player.repeatMode,
|
||||
position,
|
||||
duration,
|
||||
songLink,
|
||||
albumLink,
|
||||
artistLink
|
||||
)
|
||||
}
|
||||
|
||||
private fun scheduleWidgetUpdates() {
|
||||
if (widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
|
||||
widgetUpdateScheduled = true
|
||||
}
|
||||
|
||||
private fun stopWidgetUpdates() {
|
||||
if (!widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
|
||||
widgetUpdateScheduled = false
|
||||
}
|
||||
|
||||
private fun attachEqualizerIfPossible(audioSessionId: Int): Boolean {
|
||||
if (audioSessionId == 0 || audioSessionId == -1) return false
|
||||
val attached = equalizerManager.attachToSession(audioSessionId)
|
||||
if (attached) {
|
||||
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])
|
||||
}
|
||||
sendBroadcast(Intent(ACTION_EQUALIZER_UPDATED))
|
||||
}
|
||||
return attached
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory(): MediaSource.Factory = DynamicMediaSourceFactory(this)
|
||||
}
|
||||
|
||||
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L
|
||||
class MediaService : BaseMediaService()
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
|
||||
@UnstableApi
|
||||
@Database(
|
||||
version = 12,
|
||||
version = 13,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class, Playlist.class, LyricsCache.class},
|
||||
autoMigrations = {@AutoMigration(from = 10, to = 11), @AutoMigration(from = 11, to = 12)}
|
||||
)
|
||||
|
||||
@@ -12,9 +12,12 @@ import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface RecentSearchDao {
|
||||
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||
@Query("SELECT search FROM recent_search ORDER BY timestamp DESC")
|
||||
List<String> getRecent();
|
||||
|
||||
@Query("SELECT search FROM recent_search ORDER BY search DESC")
|
||||
List<String> getAlpha();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(RecentSearch search);
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ public class UpdateUtil {
|
||||
|
||||
public static boolean showUpdateDialog(LatestRelease release) {
|
||||
if (release.getTagName() == null) return false;
|
||||
String remoteTag = release.getTagName().replaceAll("^\\D+", "");
|
||||
|
||||
try {
|
||||
String[] local = BuildConfig.VERSION_NAME.split("\\.");
|
||||
String[] remote = release.getTagName().split("\\.");
|
||||
String[] remote = remoteTag.split("\\.");
|
||||
|
||||
for (int i = 0; i < local.length; i++) {
|
||||
int localPart = Integer.parseInt(local[i]);
|
||||
|
||||
@@ -27,8 +27,11 @@ public interface ClickCallback {
|
||||
default void onInternetRadioStationClick(Bundle bundle) {}
|
||||
default void onInternetRadioStationLongClick(Bundle bundle) {}
|
||||
default void onMusicFolderClick(Bundle bundle) {}
|
||||
default void onMusicFolderPlay(Bundle bundle) {}
|
||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||
default void onMusicDirectoryPlay(Bundle bundle) {}
|
||||
default void onMusicIndexClick(Bundle bundle) {}
|
||||
default void onMusicIndexPlay(Bundle bundle) {}
|
||||
default void onDownloadGroupLongClick(Bundle bundle) {}
|
||||
default void onShareClick(Bundle bundle) {}
|
||||
default void onShareLongClick(Bundle bundle) {}
|
||||
|
||||
@@ -13,5 +13,8 @@ import kotlinx.parcelize.Parcelize
|
||||
data class RecentSearch(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "search")
|
||||
var search: String
|
||||
var search: String,
|
||||
|
||||
@ColumnInfo(name = "timestamp", defaultValue = "0")
|
||||
var timestamp: Long
|
||||
) : Parcelable
|
||||
|
||||
@@ -2,15 +2,16 @@ package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.interfaces.DecadesCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@@ -204,29 +205,12 @@ public class AlbumRepository {
|
||||
return albumInfo;
|
||||
}
|
||||
|
||||
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(album.getId(), count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
|
||||
callback.onLoadMedia(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onLoadMedia(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
public MutableLiveData<List<Child>> getInstantMix(AlbumID3 album, int count) {
|
||||
// Delegate to the centralized SongRepository
|
||||
return new SongRepository().getInstantMix(album.getId(), SeedType.ALBUM, count);
|
||||
}
|
||||
|
||||
|
||||
public MutableLiveData<List<Integer>> getDecades() {
|
||||
MutableLiveData<List<Integer>> decades = new MutableLiveData<>();
|
||||
|
||||
@@ -237,7 +221,7 @@ public class AlbumRepository {
|
||||
@Override
|
||||
public void onLoadYear(int last) {
|
||||
if (first != -1 && last != -1) {
|
||||
List<Integer> decadeList = new ArrayList();
|
||||
List<Integer> decadeList = new ArrayList<>();
|
||||
|
||||
int startDecade = first - (first % 10);
|
||||
int lastDecade = last - (last % 10);
|
||||
@@ -298,4 +282,4 @@ public class AlbumRepository {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.IndexID3;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -149,7 +151,7 @@ public class ArtistRepository {
|
||||
|
||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
if(index != null && index.getArtists() != null) {
|
||||
if(index.getArtists() != null) {
|
||||
artists.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
@@ -287,26 +289,8 @@ public class ArtistRepository {
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(ArtistID3 artist, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(artist.getId(), count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
// Delegate to the centralized SongRepository
|
||||
return new SongRepository().getInstantMix(artist.getId(), SeedType.ARTIST, count);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSong(ArtistID3 artist, int count) {
|
||||
|
||||
@@ -66,88 +66,33 @@ public class PodcastRepository {
|
||||
return liveNewestPodcastEpisodes;
|
||||
}
|
||||
|
||||
public void refreshPodcasts() {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> refreshPodcasts() {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.refreshPodcasts()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.refreshPodcasts();
|
||||
}
|
||||
|
||||
public void createPodcastChannel(String url) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> createPodcastChannel(String url) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.createPodcastChannel(url)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.createPodcastChannel(url);
|
||||
}
|
||||
|
||||
public void deletePodcastChannel(String channelId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> deletePodcastChannel(String channelId) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.deletePodcastChannel(channelId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.deletePodcastChannel(channelId);
|
||||
}
|
||||
|
||||
public void deletePodcastEpisode(String episodeId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> deletePodcastEpisode(String episodeId) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.deletePodcastEpisode(episodeId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.deletePodcastEpisode(episodeId);
|
||||
}
|
||||
|
||||
public void downloadPodcastEpisode(String episodeId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> downloadPodcastEpisode(String episodeId) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.downloadPodcastEpisode(episodeId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.downloadPodcastEpisode(episodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
@@ -52,6 +55,8 @@ public class QueueRepository {
|
||||
public MutableLiveData<PlayQueue> getPlayQueue() {
|
||||
MutableLiveData<PlayQueue> playQueue = new MutableLiveData<>();
|
||||
|
||||
Log.d(TAG, "Getting play queue from server...");
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBookmarksClient()
|
||||
.getPlayQueue()
|
||||
@@ -59,12 +64,19 @@ public class QueueRepository {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlayQueue() != null) {
|
||||
playQueue.setValue(response.body().getSubsonicResponse().getPlayQueue());
|
||||
PlayQueue serverQueue = response.body().getSubsonicResponse().getPlayQueue();
|
||||
Log.d(TAG, "Server returned play queue with " +
|
||||
(serverQueue.getEntries() != null ? serverQueue.getEntries().size() : 0) + " items");
|
||||
playQueue.setValue(serverQueue);
|
||||
} else {
|
||||
Log.d(TAG, "Server returned no play queue");
|
||||
playQueue.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "Failed to get play queue", t);
|
||||
playQueue.setValue(null);
|
||||
}
|
||||
});
|
||||
@@ -73,18 +85,24 @@ public class QueueRepository {
|
||||
}
|
||||
|
||||
public void savePlayQueue(List<String> ids, String current, long position) {
|
||||
Log.d(TAG, "Saving play queue to server - Items: " + ids.size() + ", Current: " + current);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBookmarksClient()
|
||||
.savePlayQueue(ids, current, position)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
if (response.isSuccessful()) {
|
||||
Log.d(TAG, "Play queue saved successfully");
|
||||
} else {
|
||||
Log.d(TAG, "Play queue save failed with code: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
Log.e(TAG, "Play queue save failed", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -121,6 +139,14 @@ public class QueueRepository {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMediaInQueue(List<Queue> queue, Child media) {
|
||||
if (queue == null || media == null) return false;
|
||||
return queue.stream().anyMatch(queueItem ->
|
||||
queueItem != null && media.getId() != null &&
|
||||
queueItem.getId().equals(media.getId())
|
||||
);
|
||||
}
|
||||
|
||||
public void insertAll(List<Child> toAdd, boolean reset, int afterIndex) {
|
||||
try {
|
||||
List<Queue> media = new ArrayList<>();
|
||||
@@ -134,8 +160,14 @@ public class QueueRepository {
|
||||
media = getMediaThreadSafe.getMedia();
|
||||
}
|
||||
|
||||
for (int i = 0; i < toAdd.size(); i++) {
|
||||
Queue queueItem = new Queue(toAdd.get(i));
|
||||
List<Child> filteredToAdd = toAdd;
|
||||
final List<Queue> finalMedia = media;
|
||||
filteredToAdd = toAdd.stream()
|
||||
.filter(child -> !isMediaInQueue(finalMedia, child))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < filteredToAdd.size(); i++) {
|
||||
Queue queueItem = new Queue(filteredToAdd.get(i));
|
||||
media.add(afterIndex + i, queueItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,54 +38,22 @@ public class RadioRepository {
|
||||
return radioStation;
|
||||
}
|
||||
|
||||
public void createInternetRadioStation(String name, String streamURL, String homepageURL) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> createInternetRadioStation(String name, String streamURL, String homepageURL) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.createInternetRadioStation(streamURL, name, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.createInternetRadioStation(streamURL, name, homepageURL);
|
||||
}
|
||||
|
||||
public void updateInternetRadioStation(String id, String name, String streamURL, String homepageURL) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> updateInternetRadioStation(String id, String name, String streamURL, String homepageURL) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.updateInternetRadioStation(id, streamURL, name, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.updateInternetRadioStation(id, streamURL, name, homepageURL);
|
||||
}
|
||||
|
||||
public void deleteInternetRadioStation(String id) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
public Call<ApiResponse> deleteInternetRadioStation(String id) {
|
||||
return App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.deleteInternetRadioStation(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
.deleteInternetRadioStation(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -186,7 +187,12 @@ public class SearchingRepository {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recent = recentSearchDao.getRecent();
|
||||
if(Preferences.isSearchSortingChronologicallyEnabled()){
|
||||
recent = recentSearchDao.getRecent();
|
||||
}
|
||||
else {
|
||||
recent = recentSearchDao.getAlpha();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getRecent() {
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SongRepository {
|
||||
|
||||
private static final String TAG = "SongRepository";
|
||||
|
||||
public interface MediaCallbackInternal {
|
||||
void onSongsAvailable(List<Child> songs);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getStarredSongs(boolean random, int size) {
|
||||
MutableLiveData<List<Child>> starredSongs = new MutableLiveData<>(Collections.emptyList());
|
||||
|
||||
@@ -42,25 +54,202 @@ public class SongRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
|
||||
return starredSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
|
||||
/**
|
||||
* Used by ViewModels. Updates the LiveData list incrementally as songs are found.
|
||||
*/
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, SeedType type, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(new ArrayList<>());
|
||||
Set<String> trackIds = new HashSet<>();
|
||||
|
||||
performSmartMix(id, type, count, songs -> {
|
||||
List<Child> current = instantMix.getValue();
|
||||
if (current != null) {
|
||||
for (Child s : songs) {
|
||||
if (!trackIds.contains(s.getId())) {
|
||||
current.add(s);
|
||||
trackIds.add(s.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (current.size() < count / 2) {
|
||||
fetchSimilarOnly(id, count, remainder -> {
|
||||
for (Child r : remainder) {
|
||||
if (!trackIds.contains(r.getId())) {
|
||||
current.add(r);
|
||||
trackIds.add(r.getId());
|
||||
}
|
||||
}
|
||||
instantMix.postValue(current);
|
||||
});
|
||||
} else {
|
||||
instantMix.postValue(current);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded method used by other Repositories
|
||||
*/
|
||||
public void getInstantMix(String id, SeedType type, int count, MediaCallbackInternal callback) {
|
||||
new MediaCallbackAccumulator(callback, count).start(id, type);
|
||||
}
|
||||
|
||||
private class MediaCallbackAccumulator {
|
||||
private final MediaCallbackInternal originalCallback;
|
||||
private final int targetCount;
|
||||
private final List<Child> accumulatedSongs = new ArrayList<>();
|
||||
private final Set<String> trackIds = new HashSet<>();
|
||||
private boolean isComplete = false;
|
||||
|
||||
MediaCallbackAccumulator(MediaCallbackInternal callback, int count) {
|
||||
this.originalCallback = callback;
|
||||
this.targetCount = count;
|
||||
}
|
||||
|
||||
void start(String id, SeedType type) {
|
||||
performSmartMix(id, type, targetCount, this::onBatchReceived);
|
||||
}
|
||||
|
||||
private void onBatchReceived(List<Child> batch) {
|
||||
if (isComplete || batch == null || batch.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int added = 0;
|
||||
for (Child song : batch) {
|
||||
if (!trackIds.contains(song.getId()) && accumulatedSongs.size() < targetCount) {
|
||||
trackIds.add(song.getId());
|
||||
accumulatedSongs.add(song);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
if (accumulatedSongs.size() >= targetCount) {
|
||||
originalCallback.onSongsAvailable(new ArrayList<>(accumulatedSongs));
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void performSmartMix(final String id, final SeedType type, final int count, final MediaCallbackInternal callback) {
|
||||
switch (type) {
|
||||
case ARTIST:
|
||||
fetchSimilarByArtist(id, count, callback);
|
||||
break;
|
||||
case ALBUM:
|
||||
fetchAlbumSongs(id, count, callback);
|
||||
break;
|
||||
case TRACK:
|
||||
fetchSingleTrackThenSimilar(id, count, callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchAlbumSongs(String albumId, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getAlbum(albumId).enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null &&
|
||||
response.body().getSubsonicResponse().getAlbum() != null) {
|
||||
List<Child> albumSongs = response.body().getSubsonicResponse().getAlbum().getSongs();
|
||||
if (albumSongs != null && !albumSongs.isEmpty()) {
|
||||
int fromAlbum = Math.min(count, albumSongs.size());
|
||||
List<Child> limitedAlbumSongs = albumSongs.subList(0, fromAlbum);
|
||||
callback.onSongsAvailable(new ArrayList<>(limitedAlbumSongs));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "fetchAlbumSongsThenSimilar.onFailure()", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSimilarByArtist(String artistId, final int count, final MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(artistId, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> similar = extractSongs(response, "similarSongs2");
|
||||
Log.d(TAG, "fetchSimilarByArtist.onResponse() - similar songs: " + similar.size());
|
||||
|
||||
if (!similar.isEmpty()) {
|
||||
List<Child> limitedSimilar = similar.subList(0, Math.min(count, similar.size()));
|
||||
callback.onSongsAvailable(limitedSimilar);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "fetchSimilarByArtist.onFailure()", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSingleTrackThenSimilar(String trackId, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(trackId).enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
Child song = response.body().getSubsonicResponse().getSong();
|
||||
if (song != null) {
|
||||
callback.onSongsAvailable(Collections.singletonList(song));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "fetchSingleTrackThenSimilar.onFailure()", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchSimilarOnly(String id, int count, MediaCallbackInternal callback) {
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSimilarSongs(id, count).enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = extractSongs(response, "similarSongs");
|
||||
if (!songs.isEmpty()) {
|
||||
int limit = Math.min(count, songs.size());
|
||||
callback.onSongsAvailable(songs.subList(0, limit));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e(TAG, "fetchSimilarOnly.onFailure()", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public MutableLiveData<List<Child>> getContinuousMix(String id, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(id, count)
|
||||
.getSimilarSongs(id, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,161 +262,119 @@ public class SongRepository {
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
private List<Child> extractSongs(Response<ApiResponse> response, String type) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
SubsonicResponse res = response.body().getSubsonicResponse();
|
||||
List<Child> list = null;
|
||||
if (type.equals("similarSongs") && res.getSimilarSongs() != null) {
|
||||
list = res.getSimilarSongs().getSongs();
|
||||
} else if (type.equals("similarSongs2") && res.getSimilarSongs2() != null) {
|
||||
list = res.getSimilarSongs2().getSongs();
|
||||
}
|
||||
return (list != null) ? list : new ArrayList<>();
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(number, fromYear, toYear)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
public MutableLiveData<List<Child>> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear, genre).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||
}
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public void scrobble(String id, boolean submission) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.scrobble(id, submission)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
App.getSubsonicClientInstance(false).getMediaAnnotationClient().scrobble(id, submission).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
public void setRating(String id, int rating) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.setRating(id, rating)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
App.getSubsonicClientInstance(false).getMediaAnnotationClient().setRating(id, rating).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 100, 100 * page)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 100, 100 * page).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getSongsByGenres(ArrayList<String> genresId) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
for (String id : genresId)
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 500, 0)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
for (String id : genresId) {
|
||||
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 500, 0).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getSongsByGenre().getSongs()));
|
||||
}
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
}
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<Child> getSong(String id) {
|
||||
MutableLiveData<Child> song = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSong(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(id).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return song;
|
||||
}
|
||||
|
||||
public MutableLiveData<String> getSongLyrics(Child song) {
|
||||
MutableLiveData<String> lyrics = new MutableLiveData<>(null);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaRetrievalClient()
|
||||
.getLyrics(song.getArtist(), song.getTitle())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyrics() != null) {
|
||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
App.getSubsonicClientInstance(false).getMediaRetrievalClient().getLyrics(song.getArtist(), song.getTitle()).enqueue(new Callback<ApiResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyrics() != null) {
|
||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||
}
|
||||
}
|
||||
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||
});
|
||||
return lyrics;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
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.MediaSource
|
||||
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder
|
||||
import androidx.media3.session.*
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.R
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.*
|
||||
import com.cappielloantonio.tempo.widget.WidgetUpdateManager
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
@UnstableApi
|
||||
open class BaseMediaService : MediaLibraryService() {
|
||||
companion object {
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||
"android.media3.session.demo.SHUFFLE_ON"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||
"android.media3.session.demo.SHUFFLE_OFF"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
|
||||
"android.media3.session.demo.REPEAT_OFF"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
|
||||
"android.media3.session.demo.REPEAT_ONE"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
|
||||
"android.media3.session.demo.REPEAT_ALL"
|
||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||
const val ACTION_EQUALIZER_UPDATED = "com.cappielloantonio.tempo.service.EQUALIZER_UPDATED"
|
||||
}
|
||||
|
||||
protected lateinit var exoplayer: ExoPlayer
|
||||
protected lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var networkCallback: CustomNetworkCallback
|
||||
private lateinit var equalizerManager: EqualizerManager
|
||||
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
|
||||
private var widgetUpdateScheduled = false
|
||||
private val widgetUpdateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val player = mediaLibrarySession.player
|
||||
if (!player.isPlaying) {
|
||||
widgetUpdateScheduled = false
|
||||
return
|
||||
}
|
||||
updateWidget(player)
|
||||
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private val binder = LocalBinder()
|
||||
|
||||
open fun playerInitHook() {
|
||||
initializeExoPlayer()
|
||||
initializeMediaLibrarySession(exoplayer)
|
||||
initializePlayerListener(exoplayer)
|
||||
setPlayer(null, exoplayer)
|
||||
}
|
||||
|
||||
open fun getMediaLibrarySessionCallback(): MediaLibrarySession.Callback {
|
||||
return CustomMediaLibrarySessionCallback(baseContext)
|
||||
}
|
||||
|
||||
fun updateMediaItems(player: Player) {
|
||||
Log.d(javaClass.toString(), "update items")
|
||||
val n = player.mediaItemCount
|
||||
val k = player.currentMediaItemIndex
|
||||
val current = player.currentPosition
|
||||
val items = (0..n - 1).map { MappingUtil.mapMediaItem(player.getMediaItemAt(it)) }
|
||||
player.clearMediaItems()
|
||||
player.setMediaItems(items, k, current)
|
||||
}
|
||||
|
||||
fun restorePlayerFromQueue(player: Player) {
|
||||
if (player.mediaItemCount > 0) return
|
||||
|
||||
val queueRepository = QueueRepository()
|
||||
val storedQueue = queueRepository.media
|
||||
if (storedQueue.isNullOrEmpty()) return
|
||||
|
||||
val mediaItems = MappingUtil.mapMediaItems(storedQueue)
|
||||
if (mediaItems.isEmpty()) return
|
||||
|
||||
val lastIndex = try {
|
||||
queueRepository.lastPlayedMediaIndex
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}.coerceIn(0, mediaItems.size - 1)
|
||||
|
||||
val lastPosition = try {
|
||||
queueRepository.lastPlayedMediaTimestamp
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}.let { if (it < 0L) 0L else it }
|
||||
|
||||
player.setMediaItems(mediaItems, lastIndex, lastPosition)
|
||||
player.prepare()
|
||||
updateWidget(player)
|
||||
}
|
||||
|
||||
fun initializePlayerListener(player: Player) {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
Log.d(javaClass.toString(), "onMediaItemTransition" + player.currentMediaItemIndex)
|
||||
if (mediaItem == null) return
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||
}
|
||||
updateWidget(player)
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
Log.d(javaClass.toString(), "onTracksChanged " + player.currentMediaItemIndex)
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null) {
|
||||
val item = MappingUtil.mapMediaItem(currentMediaItem)
|
||||
if (item.mediaMetadata.extras != null)
|
||||
MediaManager.scrobble(item, false)
|
||||
|
||||
if (player.nextMediaItemIndex == C.INDEX_UNSET) {
|
||||
val browserFuture = MediaBrowser.Builder(
|
||||
this@BaseMediaService,
|
||||
SessionToken(this@BaseMediaService, ComponentName(this@BaseMediaService, this@BaseMediaService::class.java))
|
||||
).buildAsync()
|
||||
MediaManager.continuousPlay(player.currentMediaItem, browserFuture)
|
||||
}
|
||||
}
|
||||
|
||||
if (player is ExoPlayer) {
|
||||
// https://stackoverflow.com/questions/56937283/exoplayer-shuffle-doesnt-reproduce-all-the-songs
|
||||
if (MediaManager.justStarted.get()) {
|
||||
Log.d(javaClass.toString(), "update shuffle order")
|
||||
MediaManager.justStarted.set(false)
|
||||
val shuffledList = IntArray(player.mediaItemCount) { i -> i }
|
||||
shuffledList.shuffle()
|
||||
val index = shuffledList.indexOf(player.currentMediaItemIndex)
|
||||
// swap current media index to the first index
|
||||
if (index > -1 && shuffledList.isNotEmpty()) {
|
||||
val tmp = shuffledList[0]
|
||||
shuffledList[0] = shuffledList[index]
|
||||
shuffledList[index] = tmp
|
||||
}
|
||||
player.shuffleOrder =
|
||||
DefaultShuffleOrder(shuffledList, kotlin.random.Random.nextLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
Log.d(javaClass.toString(), "onIsPlayingChanged " + player.currentMediaItemIndex)
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
} else {
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
}
|
||||
if (isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
} else {
|
||||
stopWidgetUpdates()
|
||||
}
|
||||
updateWidget(player)
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
Log.d(javaClass.toString(), "onPlaybackStateChanged")
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
) {
|
||||
MediaManager.scrobble(player.currentMediaItem, true)
|
||||
MediaManager.saveChronology(player.currentMediaItem)
|
||||
}
|
||||
updateWidget(player)
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
Log.d(javaClass.toString(), "onPositionDiscontinuity")
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.scrobble(oldPosition.mediaItem, true)
|
||||
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||
}
|
||||
|
||||
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
}
|
||||
|
||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||
Log.d(javaClass.toString(), "onAudioSessionIdChanged")
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
})
|
||||
if (player.isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||||
if (oldPlayer === newPlayer) return
|
||||
if (oldPlayer != null) {
|
||||
val currentQueue = getQueueFromPlayer(oldPlayer)
|
||||
val currentIndex = oldPlayer.currentMediaItemIndex
|
||||
val currentPosition = oldPlayer.currentPosition
|
||||
val isPlaying = oldPlayer.playWhenReady
|
||||
oldPlayer.stop()
|
||||
newPlayer.setMediaItems(currentQueue, currentIndex, currentPosition)
|
||||
newPlayer.playWhenReady = isPlaying
|
||||
newPlayer.prepare()
|
||||
}
|
||||
mediaLibrarySession.player = newPlayer
|
||||
}
|
||||
|
||||
open fun releasePlayers() {
|
||||
exoplayer.release()
|
||||
}
|
||||
|
||||
fun getQueueFromPlayer(player: Player): List<MediaItem> {
|
||||
return (0..player.mediaItemCount - 1).map(player::getMediaItemAt)
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
val player = mediaLibrarySession.player
|
||||
|
||||
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
playerInitHook()
|
||||
initializeEqualizerManager()
|
||||
initializeNetworkListener()
|
||||
restorePlayerFromQueue(mediaLibrarySession.player)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releaseNetworkCallback()
|
||||
equalizerManager.release()
|
||||
stopWidgetUpdates()
|
||||
releasePlayers()
|
||||
mediaLibrarySession.release()
|
||||
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 initializeExoPlayer() {
|
||||
exoplayer = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.setLoadControl(initializeLoadControl())
|
||||
.build()
|
||||
|
||||
exoplayer.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
|
||||
exoplayer.repeatMode = Preferences.getRepeatMode()
|
||||
}
|
||||
|
||||
private fun initializeEqualizerManager() {
|
||||
equalizerManager = EqualizerManager()
|
||||
val audioSessionId = exoplayer.audioSessionId
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession(player: Player) {
|
||||
Log.d(javaClass.toString(), "initializeMediaLibrarySession")
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(baseContext, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, getMediaLibrarySessionCallback())
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun initializeNetworkListener() {
|
||||
networkCallback = CustomNetworkCallback()
|
||||
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(
|
||||
networkCallback
|
||||
)
|
||||
updateMediaItems(mediaLibrarySession.player)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
private fun releaseNetworkCallback() {
|
||||
getSystemService(ConnectivityManager::class.java).unregisterNetworkCallback(networkCallback)
|
||||
}
|
||||
|
||||
private fun updateWidget(player: Player) {
|
||||
val mi = player.currentMediaItem
|
||||
val title = mi?.mediaMetadata?.title?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("title")
|
||||
val artist = mi?.mediaMetadata?.artist?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("artist")
|
||||
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("album")
|
||||
val extras = mi?.mediaMetadata?.extras
|
||||
val coverId = extras?.getString("coverArtId")
|
||||
val songLink = extras?.getString("assetLinkSong")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras?.getString("id"))
|
||||
val albumLink = extras?.getString("assetLinkAlbum")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras?.getString("albumId"))
|
||||
val artistLink = extras?.getString("assetLinkArtist")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
||||
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
WidgetUpdateManager.updateFromState(
|
||||
this,
|
||||
title ?: "",
|
||||
artist ?: "",
|
||||
album ?: "",
|
||||
coverId,
|
||||
player.isPlaying,
|
||||
player.shuffleModeEnabled,
|
||||
player.repeatMode,
|
||||
position,
|
||||
duration,
|
||||
songLink,
|
||||
albumLink,
|
||||
artistLink
|
||||
)
|
||||
}
|
||||
|
||||
private fun scheduleWidgetUpdates() {
|
||||
if (widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
|
||||
widgetUpdateScheduled = true
|
||||
}
|
||||
|
||||
private fun stopWidgetUpdates() {
|
||||
if (!widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
|
||||
widgetUpdateScheduled = false
|
||||
}
|
||||
|
||||
private fun attachEqualizerIfPossible(audioSessionId: Int): Boolean {
|
||||
if (audioSessionId == 0 || audioSessionId == -1) return false
|
||||
val attached = equalizerManager.attachToSession(audioSessionId)
|
||||
if (attached) {
|
||||
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])
|
||||
}
|
||||
sendBroadcast(Intent(ACTION_EQUALIZER_UPDATED))
|
||||
}
|
||||
return attached
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory(): MediaSource.Factory = DynamicMediaSourceFactory(this)
|
||||
|
||||
@UnstableApi
|
||||
private class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
|
||||
private val shuffleCommands: List<CommandButton>
|
||||
private val repeatCommands: List<CommandButton>
|
||||
|
||||
constructor(ctx: Context) {
|
||||
shuffleCommands = listOf(
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON,
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF
|
||||
)
|
||||
.map { getShuffleCommandButton(SessionCommand(it, Bundle.EMPTY), ctx) }
|
||||
repeatCommands = listOf(
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL
|
||||
)
|
||||
.map { getRepeatCommandButton(SessionCommand(it, Bundle.EMPTY), ctx) }
|
||||
}
|
||||
|
||||
override fun onConnect(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
val connectionResult = super.onConnect(session, controller)
|
||||
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
|
||||
|
||||
(shuffleCommands + repeatCommands).forEach { commandButton ->
|
||||
commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
|
||||
}
|
||||
|
||||
val result = MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(availableSessionCommands.build())
|
||||
.setAvailablePlayerCommands(connectionResult.availablePlayerCommands)
|
||||
.setMediaButtonPreferences(buildCustomLayout(session.player))
|
||||
.build()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
Log.d(javaClass.toString(), "onCustomCommand")
|
||||
when (customCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
|
||||
val nextMode = when (session.player.repeatMode) {
|
||||
Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
|
||||
Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
|
||||
else -> Player.REPEAT_MODE_OFF
|
||||
}
|
||||
session.player.repeatMode = nextMode
|
||||
}
|
||||
}
|
||||
|
||||
session.setMediaButtonPreferences(buildCustomLayout(session.player))
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
override fun onAddMediaItems(
|
||||
mediaSession: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
Log.d(javaClass.toString(), "onAddMediaItems")
|
||||
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()
|
||||
}
|
||||
return Futures.immediateFuture(updatedMediaItems)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getShuffleCommandButton(
|
||||
sessionCommand: SessionCommand,
|
||||
ctx: Context
|
||||
): CommandButton {
|
||||
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||
return CommandButton.Builder(if (isOn) CommandButton.ICON_SHUFFLE_OFF else CommandButton.ICON_SHUFFLE_ON)
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setDisplayName(
|
||||
ctx.getString(
|
||||
if (isOn) R.string.exo_controls_shuffle_on_description
|
||||
else R.string.exo_controls_shuffle_off_description
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getRepeatCommandButton(
|
||||
sessionCommand: SessionCommand,
|
||||
ctx: Context
|
||||
): CommandButton {
|
||||
val icon = when (sessionCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> CommandButton.ICON_REPEAT_ONE
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> CommandButton.ICON_REPEAT_ALL
|
||||
else -> CommandButton.ICON_REPEAT_OFF
|
||||
}
|
||||
val description = when (sessionCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> R.string.exo_controls_repeat_one_description
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL -> R.string.exo_controls_repeat_all_description
|
||||
else -> R.string.exo_controls_repeat_off_description
|
||||
}
|
||||
return CommandButton.Builder(icon)
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setDisplayName(ctx.getString(description))
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
|
||||
val shuffle = shuffleCommands[if (player.shuffleModeEnabled) 1 else 0]
|
||||
val repeat = when (player.repeatMode) {
|
||||
Player.REPEAT_MODE_ONE -> repeatCommands[1]
|
||||
Player.REPEAT_MODE_ALL -> repeatCommands[2]
|
||||
else -> repeatCommands[0]
|
||||
}
|
||||
return ImmutableList.of(shuffle, repeat)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CustomNetworkCallback : ConnectivityManager.NetworkCallback() {
|
||||
var wasWifi = false
|
||||
|
||||
init {
|
||||
val manager = getSystemService(ConnectivityManager::class.java)
|
||||
val network = manager.activeNetwork
|
||||
val capabilities = manager.getNetworkCapabilities(network)
|
||||
if (capabilities != null)
|
||||
wasWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
networkCapabilities: NetworkCapabilities
|
||||
) {
|
||||
val isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
if (isWifi != wasWifi) {
|
||||
wasWifi = isWifi
|
||||
widgetUpdateHandler.post {
|
||||
updateMediaItems(mediaLibrarySession.player)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getEqualizerManager(): EqualizerManager {
|
||||
return equalizerManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
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;
|
||||
@@ -25,6 +26,7 @@ import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
@@ -36,10 +38,16 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class MediaManager {
|
||||
private static final String TAG = "MediaManager";
|
||||
private static WeakReference<MediaBrowser> attachedBrowserRef = new WeakReference<>(null);
|
||||
public static AtomicBoolean justStarted = new AtomicBoolean(false);
|
||||
|
||||
private static final ExecutorService backgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void registerPlaybackObserver(
|
||||
ListenableFuture<MediaBrowser> browserFuture,
|
||||
@@ -173,33 +181,46 @@ public class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
browser.clearMediaItems();
|
||||
browser.setMediaItems(MappingUtil.mapMediaItems(media));
|
||||
browser.prepare();
|
||||
final MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
final List<MediaItem> items = MappingUtil.mapMediaItems(media);
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
justStarted.set(true);
|
||||
browser.setMediaItems(items, startIndex, 0);
|
||||
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);
|
||||
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);
|
||||
} else {
|
||||
Log.d(TAG, "Cannot start playback: itemCount=" + itemCount + ", startIndex=" + startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
browser.addListener(timelineListener);
|
||||
};
|
||||
|
||||
browser.addListener(timelineListener);
|
||||
});
|
||||
|
||||
enqueueDatabase(media, true, 0);
|
||||
backgroundExecutor.execute(() -> {
|
||||
Log.d(TAG, "Background: enqueuing to database");
|
||||
enqueueDatabase(media, true, 0);
|
||||
});
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Error in startQueue: " + e.getMessage(), e);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
@@ -210,10 +231,11 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(media));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
justStarted.set(true);
|
||||
browser.setMediaItem(MappingUtil.mapMediaItem(media));
|
||||
browser.prepare();
|
||||
browser.play();
|
||||
enqueueDatabase(media, true, 0);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
@@ -229,7 +251,7 @@ public class MediaManager {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
mediaBrowser.clearMediaItems();
|
||||
justStarted.set(true);
|
||||
mediaBrowser.setMediaItem(mediaItem);
|
||||
mediaBrowser.prepare();
|
||||
mediaBrowser.play();
|
||||
@@ -247,10 +269,11 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapInternetRadioStation(internetRadioStation));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
justStarted.set(true);
|
||||
browser.setMediaItem(MappingUtil.mapInternetRadioStation(internetRadioStation));
|
||||
browser.prepare();
|
||||
browser.play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
@@ -264,10 +287,11 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(podcastEpisode));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
justStarted.set(true);
|
||||
browser.setMediaItem(MappingUtil.mapMediaItem(podcastEpisode));
|
||||
browser.prepare();
|
||||
browser.play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
@@ -281,9 +305,11 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
|
||||
mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItems(media));
|
||||
Log.e(TAG, "enqueue");
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
if (playImmediatelyAfter && browser.getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, browser.getNextMediaItemIndex());
|
||||
browser.addMediaItems(browser.getNextMediaItemIndex(), MappingUtil.mapMediaItems(media));
|
||||
} else {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getMediaItemCount());
|
||||
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media));
|
||||
@@ -301,9 +327,11 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
|
||||
mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItem(media));
|
||||
Log.e(TAG, "enqueue");
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
if (playImmediatelyAfter && browser.getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, browser.getNextMediaItemIndex());
|
||||
browser.addMediaItem(browser.getNextMediaItemIndex(), MappingUtil.mapMediaItem(media));
|
||||
} else {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getMediaItemCount());
|
||||
mediaBrowserListenableFuture.get().addMediaItem(MappingUtil.mapMediaItem(media));
|
||||
@@ -321,8 +349,10 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
|
||||
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
|
||||
Log.e(TAG, "shuffle");
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
browser.removeMediaItems(startIndex, endIndex + 1);
|
||||
browser.addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
|
||||
swapDatabase(media);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
@@ -337,6 +367,7 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
Log.e(TAG, "swap");
|
||||
mediaBrowserListenableFuture.get().moveMediaItem(from, to);
|
||||
swapDatabase(media);
|
||||
}
|
||||
@@ -352,6 +383,7 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
Log.e(TAG, "remove");
|
||||
if (mediaBrowserListenableFuture.get().getMediaItemCount() > 1 && mediaBrowserListenableFuture.get().getCurrentMediaItemIndex() != toRemove) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItem(toRemove);
|
||||
removeDatabase(media, toRemove);
|
||||
@@ -371,6 +403,7 @@ public class MediaManager {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
Log.e(TAG, "remove range");
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(fromItem, toItem);
|
||||
removeRangeDatabase(media, fromItem, toItem);
|
||||
}
|
||||
@@ -411,23 +444,20 @@ public class MediaManager {
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void continuousPlay(MediaItem mediaItem) {
|
||||
public static void continuousPlay(MediaItem mediaItem, ListenableFuture<MediaBrowser> existingBrowserFuture) {
|
||||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, 10);
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getContinuousMix(mediaItem.mediaId, 25);
|
||||
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media != null) {
|
||||
ListenableFuture<MediaBrowser> mediaBrowserListenableFuture = new MediaBrowser.Builder(
|
||||
App.getContext(),
|
||||
new SessionToken(App.getContext(), new ComponentName(App.getContext(), MediaService.class))
|
||||
).buildAsync();
|
||||
|
||||
enqueue(mediaBrowserListenableFuture, media, true);
|
||||
if (media != null && existingBrowserFuture != null) {
|
||||
Log.d(TAG, "Continuous play: adding " + media.size() + " tracks");
|
||||
enqueue(existingBrowserFuture, media, false);
|
||||
}
|
||||
|
||||
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -34,6 +34,11 @@ public class AlbumSongListClient {
|
||||
return albumSongListService.getRandomSongs(subsonic.getParams(), size, fromYear, toYear);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getRandomSongs(int size, Integer fromYear, Integer toYear, String genre) {
|
||||
Log.d(TAG, "getRandomSongs()");
|
||||
return albumSongListService.getRandomSongs(subsonic.getParams(), size, fromYear, toYear, genre);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getSongsByGenre(String genre, int count, int offset) {
|
||||
Log.d(TAG, "getSongsByGenre()");
|
||||
return albumSongListService.getSongsByGenre(subsonic.getParams(), genre, count, offset);
|
||||
|
||||
@@ -19,6 +19,9 @@ public interface AlbumSongListService {
|
||||
@GET("getRandomSongs")
|
||||
Call<ApiResponse> getRandomSongs(@QueryMap Map<String, String> params, @Query("size") int size, @Query("fromYear") Integer fromYear, @Query("toYear") Integer toYear);
|
||||
|
||||
@GET("getRandomSongs")
|
||||
Call<ApiResponse> getRandomSongs(@QueryMap Map<String, String> params, @Query("size") int size, @Query("fromYear") Integer fromYear, @Query("toYear") Integer toYear, @Query("genre") String genre);
|
||||
|
||||
@GET("getSongsByGenre")
|
||||
Call<ApiResponse> getSongsByGenre(@QueryMap Map<String, String> params, @Query("genre") String genre, @Query("count") int count, @Query("offset") int offset);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ open class Playlist(
|
||||
var name: String? = null,
|
||||
@ColumnInfo(name = "duration")
|
||||
var duration: Long = 0,
|
||||
@SerializedName("coverArt")
|
||||
@ColumnInfo(name = "coverArt")
|
||||
var coverArtId: String? = null,
|
||||
) : Parcelable {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
class SimilarSongs {
|
||||
@SerializedName("song")
|
||||
var songs: List<Child>? = null
|
||||
}
|
||||
@@ -354,7 +354,7 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
// TODO Enter all settings to be reset
|
||||
Preferences.setOpenSubsonic(false);
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
|
||||
Preferences.setPlaybackSpeed(1.0f);
|
||||
Preferences.setSkipSilenceMode(false);
|
||||
Preferences.setDataSavingMode(false);
|
||||
Preferences.setStarredSyncEnabled(false);
|
||||
@@ -384,7 +384,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void pingServer() {
|
||||
if (Preferences.getToken() == null) return;
|
||||
if (Preferences.getToken() == null && Preferences.getPassword() == null) return;
|
||||
|
||||
if (Preferences.isInUseServerAddressLocal()) {
|
||||
mainViewModel.ping().observe(this, subsonicResponse -> {
|
||||
@@ -428,7 +428,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void getOpenSubsonicExtensions() {
|
||||
if (Preferences.getToken() != null) {
|
||||
if (Preferences.getToken() != null || Preferences.getPassword() != null) {
|
||||
mainViewModel.getOpenSubsonicExtensions().observe(this, openSubsonicExtensions -> {
|
||||
if (openSubsonicExtensions != null) {
|
||||
Preferences.setOpenSubsonicExtensions(openSubsonicExtensions);
|
||||
@@ -438,7 +438,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void checkTempoUpdate() {
|
||||
if (BuildConfig.FLAVOR.equals("tempus") && Preferences.showTempoUpdateDialog()) {
|
||||
if (BuildConfig.FLAVOR.equals("tempus") && Preferences.isGithubUpdateEnabled() && Preferences.showTempusUpdateDialog()) {
|
||||
mainViewModel.checkTempoUpdate().observe(this, latestRelease -> {
|
||||
if (latestRelease != null && UpdateUtil.showUpdateDialog(latestRelease)) {
|
||||
GithubTempoUpdateDialog dialog = new GithubTempoUpdateDialog(latestRelease);
|
||||
|
||||
@@ -73,6 +73,11 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
|
||||
this.item = item;
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
onLongClick();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
@@ -82,6 +87,13 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
|
||||
|
||||
click.onMediaClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
||||
click.onMediaLongClick(bundle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startAnimation(ViewHolder holder) {
|
||||
|
||||
@@ -53,7 +53,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
.into(holder.item.musicDirectoryCoverImageView);
|
||||
|
||||
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
|
||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,6 +80,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
|
||||
item.musicDirectoryPlayButton.setOnClickListener(v -> onPlayClick());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
@@ -107,5 +108,13 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayClick() {
|
||||
if (children.get(getBindingAdapterPosition()).isDir()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
|
||||
click.onMusicDirectoryPlay(bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
item.musicIndexMoreButton.setOnClickListener(v -> onClick());
|
||||
item.musicIndexPlayButton.setOnClickListener(v -> onPlayClick());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
@@ -83,5 +84,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||
click.onMusicIndexClick(bundle);
|
||||
}
|
||||
|
||||
public void onPlayClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||
click.onMusicIndexPlay(bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@@ -29,7 +32,9 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
|
||||
private static final String TAG = "PlayerSongQueueAdapter";
|
||||
@@ -37,7 +42,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private List<Child> songs;
|
||||
|
||||
private final Map<String, Boolean> downloadStatusCache = new ConcurrentHashMap<>();
|
||||
private String currentPlayingId;
|
||||
private boolean isPlaying;
|
||||
private List<Integer> currentPlayingPositions = Collections.emptyList();
|
||||
@@ -78,7 +83,6 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
.build()
|
||||
.thumbnail(thumbnail)
|
||||
.into(holder.item.queueSongCoverImageView);
|
||||
|
||||
MediaManager.getCurrentIndex(mediaBrowserListenableFuture, new MediaIndexCallback() {
|
||||
@Override
|
||||
public void onRecovery(int index) {
|
||||
@@ -94,6 +98,23 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
}
|
||||
});
|
||||
|
||||
boolean isDownloaded = false;
|
||||
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager downloaderManager = DownloadUtil.getDownloadTracker(holder.itemView.getContext());
|
||||
if (downloaderManager != null) {
|
||||
isDownloaded = downloaderManager.isDownloaded(song.getId());
|
||||
}
|
||||
} else {
|
||||
isDownloaded = ExternalAudioReader.getUri(song) != null;
|
||||
}
|
||||
|
||||
if (isDownloaded) {
|
||||
holder.item.downloadIndicatorIcon.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.item.downloadIndicatorIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (Preferences.showItemRating()) {
|
||||
if (song.getStarred() == null && song.getUserRating() == null) {
|
||||
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
|
||||
@@ -153,7 +174,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
holder.item.coverArtOverlay.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<Child> getItems() {
|
||||
return this.songs;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class GithubTempoUpdateDialog extends DialogFragment {
|
||||
});
|
||||
|
||||
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(v -> {
|
||||
Preferences.setTempoUpdateReminder();
|
||||
Preferences.setTempusUpdateReminder();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ package com.cappielloantonio.tempo.ui.dialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogRadioEditorBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.RadioCallback;
|
||||
@@ -21,7 +23,6 @@ import java.util.Objects;
|
||||
public class RadioEditorDialog extends DialogFragment {
|
||||
private DialogRadioEditorBinding bind;
|
||||
private RadioEditorViewModel radioEditorViewModel;
|
||||
|
||||
private final RadioCallback radioCallback;
|
||||
|
||||
private String radioName;
|
||||
@@ -36,25 +37,26 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogRadioEditorBinding.inflate(getLayoutInflater());
|
||||
|
||||
radioEditorViewModel = new ViewModelProvider(requireActivity()).get(RadioEditorViewModel.class);
|
||||
|
||||
setupObservers();
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.radio_editor_dialog_title)
|
||||
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
|
||||
if (validateInput()) {
|
||||
if (radioEditorViewModel.getRadioToEdit() == null) {
|
||||
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
radioEditorViewModel.createRadio(radioName, radioStreamURL,
|
||||
radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
} else {
|
||||
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
radioEditorViewModel.updateRadio(radioName, radioStreamURL,
|
||||
radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
}
|
||||
dismissDialog();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> {
|
||||
radioEditorViewModel.deleteRadio();
|
||||
dismissDialog();
|
||||
})
|
||||
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> {
|
||||
dialog.cancel();
|
||||
@@ -62,6 +64,24 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
.create();
|
||||
}
|
||||
|
||||
private void setupObservers() {
|
||||
radioEditorViewModel.getIsSuccess().observe(this, isSuccess -> {
|
||||
if (isSuccess != null && isSuccess) {
|
||||
Toast.makeText(requireContext(),
|
||||
radioEditorViewModel.getRadioToEdit() == null ?
|
||||
App.getContext().getString(R.string.radio_editor_dialog_added) : App.getContext().getString(R.string.radio_editor_dialog_updated),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
dismissDialog();
|
||||
}
|
||||
});
|
||||
radioEditorViewModel.getErrorMessage().observe(this, error -> {
|
||||
if (error != null && !error.isEmpty()) {
|
||||
Toast.makeText(requireContext(), error, Toast.LENGTH_LONG).show();
|
||||
radioEditorViewModel.clearError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -77,7 +97,6 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
private void setParameterInfo() {
|
||||
if (getArguments() != null && getArguments().getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT) != null) {
|
||||
InternetRadioStation toEdit = requireArguments().getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT);
|
||||
|
||||
radioEditorViewModel.setRadioToEdit(toEdit);
|
||||
|
||||
bind.internetRadioStationNameTextView.setText(toEdit.getName());
|
||||
@@ -90,22 +109,21 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
radioName = Objects.requireNonNull(bind.internetRadioStationNameTextView.getText()).toString().trim();
|
||||
radioStreamURL = Objects.requireNonNull(bind.internetRadioStationStreamUrlTextView.getText()).toString().trim();
|
||||
radioHomepageURL = Objects.requireNonNull(bind.internetRadioStationHomepageUrlTextView.getText()).toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(radioName)) {
|
||||
bind.internetRadioStationNameTextView.setError(getString(R.string.error_required));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(radioStreamURL)) {
|
||||
bind.internetRadioStationStreamUrlTextView.setError(getString(R.string.error_required));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dismissDialog() {
|
||||
radioCallback.onDismiss();
|
||||
if (radioCallback != null) {
|
||||
radioCallback.onDismiss();
|
||||
}
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -12,6 +12,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -60,12 +61,14 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
/** @noinspection deprecation*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
/** @noinspection deprecation*/
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
@@ -81,7 +84,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
init(view);
|
||||
initAppBar();
|
||||
initAlbumInfoTextButton();
|
||||
initAlbumNotes();
|
||||
@@ -119,12 +122,13 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
/** @noinspection deprecation*/
|
||||
@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);
|
||||
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
|
||||
RatingDialog dialog = new RatingDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
@@ -159,8 +163,21 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
albumPageViewModel.setAlbum(getViewLifecycleOwner(), requireArguments().getParcelable(Constants.ALBUM_OBJECT));
|
||||
private void init(View view) {
|
||||
AlbumID3 albumArg = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
|
||||
assert albumArg != null;
|
||||
albumPageViewModel.setAlbum(getViewLifecycleOwner(), albumArg);
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(albumArg.getStarred() != null);
|
||||
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
albumPageViewModel.setFavorite();
|
||||
});
|
||||
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||
if (album != null) {
|
||||
favoriteToggle.setChecked(album.getStarred() != null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
|
||||
@@ -2,15 +2,21 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
@@ -28,18 +34,21 @@ import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
||||
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.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@UnstableApi
|
||||
public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
@@ -63,7 +72,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
init();
|
||||
init(view);
|
||||
initAppBar();
|
||||
initArtistInfo();
|
||||
initPlayButtons();
|
||||
@@ -100,7 +109,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
private void init(View view) {
|
||||
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
|
||||
|
||||
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
|
||||
@@ -109,6 +118,14 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
|
||||
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
|
||||
});
|
||||
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(artistPageViewModel.getArtist().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> artistPageViewModel.setFavorite(requireContext()));
|
||||
|
||||
Button bioToggle = view.findViewById(R.id.button_toggle_bio);
|
||||
bioToggle.setOnClickListener(v ->
|
||||
Toast.makeText(getActivity(), R.string.artist_no_artist_info_toast, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
@@ -126,53 +143,118 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
if (artistInfo == null) {
|
||||
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
|
||||
if (getContext() != null && bind != null) {
|
||||
ArtistID3 currentArtist = artistPageViewModel.getArtist();
|
||||
String primaryId = currentArtist.getCoverArtId() != null && !currentArtist.getCoverArtId().trim().isEmpty()
|
||||
? currentArtist.getCoverArtId()
|
||||
: currentArtist.getId();
|
||||
|
||||
final String fallbackId = (Objects.requireNonNull(primaryId).equals(currentArtist.getCoverArtId()) &&
|
||||
currentArtist.getId() != null &&
|
||||
!currentArtist.getId().equals(primaryId))
|
||||
? currentArtist.getId()
|
||||
: null;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), primaryId, CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.listener(new com.bumptech.glide.request.RequestListener<Drawable>() {
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable com.bumptech.glide.load.engine.GlideException e,
|
||||
Object model,
|
||||
@NonNull com.bumptech.glide.request.target.Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
if (e != null) {
|
||||
e.getMessage();
|
||||
if (e.getMessage().contains("400") && fallbackId != null) {
|
||||
|
||||
if (bind != null)
|
||||
bind.artistPageBioSector.setVisibility(!normalizedBio.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
|
||||
Log.d("ArtistCover", "Primary ID failed (400), trying fallback: " + fallbackId);
|
||||
|
||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
||||
.from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(bind.artistBackdropImageView);
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), fallbackId, CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(bind.artistBackdropImageView);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bind != null) bind.bioTextView.setText(normalizedBio);
|
||||
@Override
|
||||
public boolean onResourceReady(@NonNull Drawable resource,
|
||||
@NonNull Object model,
|
||||
com.bumptech.glide.request.target.Target<Drawable> target,
|
||||
@NonNull com.bumptech.glide.load.DataSource dataSource,
|
||||
boolean isFirstResource) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.into(bind.artistBackdropImageView);
|
||||
}
|
||||
|
||||
if (bind != null) bind.bioMoreTextViewClickable.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(artistInfo.getLastFmUrl()));
|
||||
startActivity(intent);
|
||||
});
|
||||
if (bind != null) {
|
||||
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography()).trim();
|
||||
String lastFmUrl = artistInfo.getLastFmUrl();
|
||||
|
||||
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
|
||||
if (normalizedBio.isEmpty()) {
|
||||
bind.bioTextView.setVisibility(View.GONE);
|
||||
} else {
|
||||
bind.bioTextView.setText(normalizedBio);
|
||||
}
|
||||
|
||||
if (lastFmUrl == null) {
|
||||
bind.bioMoreTextViewClickable.setVisibility(View.GONE);
|
||||
} else {
|
||||
bind.bioMoreTextViewClickable.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(artistInfo.getLastFmUrl()));
|
||||
startActivity(intent);
|
||||
});
|
||||
bind.bioMoreTextViewClickable.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (!normalizedBio.isEmpty() || lastFmUrl != null) {
|
||||
View view = bind.getRoot();
|
||||
|
||||
Button bioToggle = view.findViewById(R.id.button_toggle_bio);
|
||||
bioToggle.setOnClickListener(v -> {
|
||||
if (bind != null) {
|
||||
boolean displayBio = Preferences.getArtistDisplayBiography();
|
||||
Preferences.setArtistDisplayBiography(!displayBio);
|
||||
bind.artistPageBioSector.setVisibility(displayBio ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
boolean displayBio = Preferences.getArtistDisplayBiography();
|
||||
bind.artistPageBioSector.setVisibility(displayBio ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initPlayButtons() {
|
||||
bind.artistPageShuffleButton.setOnClickListener(v -> {
|
||||
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bind.artistPageRadioButton.setOnClickListener(v -> {
|
||||
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
|
||||
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
|
||||
artistPageViewModel.getArtistShuffleList().removeObserver(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
artistPageViewModel.getArtistInstantMix().removeObserver(this);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void initTopSongsView() {
|
||||
|
||||
@@ -27,7 +27,13 @@ import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadDirectoryDialog;
|
||||
@@ -53,6 +59,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
private MusicDirectoryAdapter musicDirectoryAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private DirectoryRepository directoryRepository;
|
||||
|
||||
private MenuItem menuItem;
|
||||
|
||||
@@ -77,6 +84,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentDirectoryBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
||||
directoryRepository = new DirectoryRepository();
|
||||
|
||||
initAppBar();
|
||||
initDirectoryListView();
|
||||
@@ -197,4 +205,57 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
public void onMusicDirectoryClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicDirectoryPlay(Bundle bundle) {
|
||||
String directoryId = bundle.getString(Constants.MUSIC_DIRECTORY_ID);
|
||||
if (directoryId != null) {
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_collecting), Toast.LENGTH_SHORT).show();
|
||||
collectAndPlayDirectorySongs(directoryId);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectAndPlayDirectorySongs(String directoryId) {
|
||||
List<Child> allSongs = new ArrayList<>();
|
||||
AtomicInteger pendingRequests = new AtomicInteger(0);
|
||||
|
||||
collectSongsFromDirectory(directoryId, allSongs, pendingRequests, () -> {
|
||||
if (!allSongs.isEmpty()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, allSongs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_playing, allSongs.size()), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
} else {
|
||||
activity.runOnUiThread(() -> {
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_no_songs), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void collectSongsFromDirectory(String directoryId, List<Child> allSongs, AtomicInteger pendingRequests, Runnable onComplete) {
|
||||
pendingRequests.incrementAndGet();
|
||||
|
||||
directoryRepository.getMusicDirectory(directoryId).observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null && directory.getChildren() != null) {
|
||||
for (Child child : directory.getChildren()) {
|
||||
if (child.isDir()) {
|
||||
// It's a subdirectory, recurse into it
|
||||
collectSongsFromDirectory(child.getId(), allSongs, pendingRequests, onComplete);
|
||||
} else if (!child.isVideo()) {
|
||||
// It's a song, add it to the list
|
||||
synchronized (allSongs) {
|
||||
allSongs.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement pending requests and check if we're done
|
||||
if (pendingRequests.decrementAndGet() == 0) {
|
||||
onComplete.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ 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.BaseMediaService
|
||||
import com.cappielloantonio.tempo.service.MediaService
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
|
||||
@@ -35,7 +36,7 @@ class EqualizerFragment : Fragment() {
|
||||
private val equalizerUpdatedReceiver = object : BroadcastReceiver() {
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == MediaService.ACTION_EQUALIZER_UPDATED) {
|
||||
if (intent?.action == BaseMediaService.ACTION_EQUALIZER_UPDATED) {
|
||||
initUI()
|
||||
restoreEqualizerPreferences()
|
||||
}
|
||||
@@ -45,7 +46,7 @@ class EqualizerFragment : Fragment() {
|
||||
private val connection = object : ServiceConnection {
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as MediaService.LocalBinder
|
||||
val binder = service as BaseMediaService.LocalBinder
|
||||
equalizerManager = binder.getEqualizerManager()
|
||||
initUI()
|
||||
restoreEqualizerPreferences()
|
||||
@@ -60,14 +61,14 @@ class EqualizerFragment : Fragment() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
Intent(requireContext(), MediaService::class.java).also { intent ->
|
||||
intent.action = MediaService.ACTION_BIND_EQUALIZER
|
||||
intent.action = BaseMediaService.ACTION_BIND_EQUALIZER
|
||||
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
if (!receiverRegistered) {
|
||||
ContextCompat.registerReceiver(
|
||||
requireContext(),
|
||||
equalizerUpdatedReceiver,
|
||||
IntentFilter(MediaService.ACTION_EQUALIZER_UPDATED),
|
||||
IntentFilter(BaseMediaService.ACTION_EQUALIZER_UPDATED),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||
)
|
||||
receiverRegistered = true
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -38,10 +40,10 @@ import com.cappielloantonio.tempo.model.HomeSector;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
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.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||
@@ -57,6 +59,8 @@ import com.cappielloantonio.tempo.ui.dialog.HomeRearrangementDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioWriter;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
@@ -66,8 +70,6 @@ import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import androidx.media3.common.MediaItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -228,6 +230,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
|
||||
});
|
||||
|
||||
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.PLAYLIST_ALL, Constants.PLAYLIST_ALL);
|
||||
activity.navController.navigate(R.id.action_homeFragment_to_playlistCatalogueFragment, bundle);
|
||||
});
|
||||
|
||||
bind.recentlyPlayedAlbumsTextViewClickable.setOnClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.ALBUM_RECENTLY_PLAYED, Constants.ALBUM_RECENTLY_PLAYED);
|
||||
@@ -279,51 +287,113 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSyncStarredView() {
|
||||
if (Preferences.isStarredSyncEnabled() && Preferences.getDownloadDirectoryUri() == null) {
|
||||
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
|
||||
if (Preferences.isStarredSyncEnabled()) {
|
||||
homeViewModel.getAllStarredTracks().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
List<String> toSync = new ArrayList<>();
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
int songsToSyncCount = 0;
|
||||
List<String> toSyncSample = new ArrayList<>();
|
||||
|
||||
for (Child song : songs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
toSync.add(song.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
if (!toSync.isEmpty()) {
|
||||
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
|
||||
bind.homeSyncStarredTracksToSync.setText(String.join(", ", toSync));
|
||||
}
|
||||
}
|
||||
|
||||
homeViewModel.getAllStarredTracks().removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind.homeSyncStarredCancel.setOnClickListener(v -> bind.homeSyncStarredCard.setVisibility(View.GONE));
|
||||
|
||||
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null) {
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
|
||||
for (Child song : songs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
songsToSyncCount++;
|
||||
if (toSyncSample.size() < 3) {
|
||||
toSyncSample.add(song.getTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Child song : songs) {
|
||||
if (ExternalAudioReader.getUri(song) == null) {
|
||||
songsToSyncCount++;
|
||||
if (toSyncSample.size() < 3) {
|
||||
toSyncSample.add(song.getTitle());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
homeViewModel.getAllStarredTracks().removeObserver(this);
|
||||
if (songsToSyncCount > 0) {
|
||||
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
|
||||
|
||||
StringBuilder displayText = new StringBuilder();
|
||||
if (!toSyncSample.isEmpty()) {
|
||||
displayText.append(String.join(", ", toSyncSample));
|
||||
if (songsToSyncCount > 3) {
|
||||
displayText.append("...");
|
||||
}
|
||||
}
|
||||
|
||||
String countText = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_songs_count,
|
||||
songsToSyncCount,
|
||||
songsToSyncCount
|
||||
);
|
||||
|
||||
if (displayText.length() > 0) {
|
||||
bind.homeSyncStarredTracksToSync.setText(displayText.toString() + "\n" + countText);
|
||||
} else {
|
||||
bind.homeSyncStarredTracksToSync.setText(countText);
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
} else {
|
||||
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind.homeSyncStarredCancel.setOnClickListener(v -> {
|
||||
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
});
|
||||
|
||||
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
homeViewModel.getAllStarredTracks().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
int downloadedCount = 0;
|
||||
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
for (Child song : songs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
downloadedCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Child song : songs) {
|
||||
if (ExternalAudioReader.getUri(song) == null) {
|
||||
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||
downloadedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadedCount > 0) {
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, downloadedCount, downloadedCount),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
bind.homeSyncStarredCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -331,6 +401,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSyncStarredAlbumsView() {
|
||||
|
||||
if (Preferences.isStarredAlbumsSyncEnabled()) {
|
||||
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), new Observer<List<AlbumID3>>() {
|
||||
@Override
|
||||
@@ -344,6 +415,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
bind.homeSyncStarredAlbumsCancel.setOnClickListener(v -> {
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
});
|
||||
|
||||
bind.homeSyncStarredAlbumsDownload.setOnClickListener(v -> {
|
||||
@@ -351,24 +425,36 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onChanged(List<Child> allSongs) {
|
||||
if (allSongs != null && !allSongs.isEmpty()) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
int songsToDownload = 0;
|
||||
|
||||
for (Child song : allSongs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
songsToDownload++;
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
for (Child song : allSongs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
songsToDownload++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Child song : allSongs) {
|
||||
if (ExternalAudioReader.getUri(song) == null) {
|
||||
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||
songsToDownload++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (songsToDownload > 0) {
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -379,33 +465,73 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onChanged(List<Child> allSongs) {
|
||||
if (allSongs != null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
int songsToDownload = 0;
|
||||
List<String> albumsNeedingSync = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
boolean albumNeedsSync = false;
|
||||
// Check if any songs from this album need downloading
|
||||
for (Child song : allSongs) {
|
||||
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
||||
!manager.isDownloaded(song.getId())) {
|
||||
songsToDownload++;
|
||||
albumNeedsSync = true;
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
boolean albumNeedsSync = false;
|
||||
for (Child song : allSongs) {
|
||||
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
||||
!manager.isDownloaded(song.getId())) {
|
||||
songsToDownload++;
|
||||
albumNeedsSync = true;
|
||||
}
|
||||
}
|
||||
if (albumNeedsSync) {
|
||||
albumsNeedingSync.add(album.getName());
|
||||
}
|
||||
}
|
||||
if (albumNeedsSync) {
|
||||
albumsNeedingSync.add(album.getName());
|
||||
} else {
|
||||
for (AlbumID3 album : albums) {
|
||||
boolean albumNeedsSync = false;
|
||||
for (Child song : allSongs) {
|
||||
if (song.getAlbumId() != null && song.getAlbumId().equals(album.getId()) &&
|
||||
ExternalAudioReader.getUri(song) == null) {
|
||||
songsToDownload++;
|
||||
albumNeedsSync = true;
|
||||
}
|
||||
}
|
||||
if (albumNeedsSync) {
|
||||
albumsNeedingSync.add(album.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (songsToDownload > 0) {
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.VISIBLE);
|
||||
String message = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_albums_count,
|
||||
albumsNeedingSync.size(),
|
||||
|
||||
StringBuilder displayText = new StringBuilder();
|
||||
List<String> sampleAlbums = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < Math.min(albumsNeedingSync.size(), 3); i++) {
|
||||
sampleAlbums.add(albumsNeedingSync.get(i));
|
||||
}
|
||||
|
||||
if (!sampleAlbums.isEmpty()) {
|
||||
displayText.append(String.join(", ", sampleAlbums));
|
||||
if (albumsNeedingSync.size() > 3) {
|
||||
displayText.append("...");
|
||||
}
|
||||
}
|
||||
|
||||
String countText = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_albums_count,
|
||||
albumsNeedingSync.size(),
|
||||
albumsNeedingSync.size()
|
||||
);
|
||||
bind.homeSyncStarredAlbumsToSync.setText(message);
|
||||
|
||||
if (displayText.length() > 0) {
|
||||
bind.homeSyncStarredAlbumsToSync.setText(displayText.toString() + "\n" + countText);
|
||||
} else {
|
||||
bind.homeSyncStarredAlbumsToSync.setText(countText);
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
} else {
|
||||
bind.homeSyncStarredAlbumsCard.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -428,6 +554,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
bind.homeSyncStarredArtistsCancel.setOnClickListener(v -> {
|
||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
});
|
||||
|
||||
bind.homeSyncStarredArtistsDownload.setOnClickListener(v -> {
|
||||
@@ -435,24 +564,36 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onChanged(List<Child> allSongs) {
|
||||
if (allSongs != null && !allSongs.isEmpty()) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
int songsToDownload = 0;
|
||||
|
||||
for (Child song : allSongs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
songsToDownload++;
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
for (Child song : allSongs) {
|
||||
if (!manager.isDownloaded(song.getId())) {
|
||||
manager.download(MappingUtil.mapDownload(song), new Download(song));
|
||||
songsToDownload++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Child song : allSongs) {
|
||||
if (ExternalAudioReader.getUri(song) == null) {
|
||||
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||
songsToDownload++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (songsToDownload > 0) {
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, songsToDownload, songsToDownload),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -463,33 +604,73 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onChanged(List<Child> allSongs) {
|
||||
if (allSongs != null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
int songsToDownload = 0;
|
||||
List<String> artistsNeedingSync = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
boolean artistNeedsSync = false;
|
||||
// Check if any songs from this artist need downloading
|
||||
for (Child song : allSongs) {
|
||||
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
||||
!manager.isDownloaded(song.getId())) {
|
||||
songsToDownload++;
|
||||
artistNeedsSync = true;
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
boolean artistNeedsSync = false;
|
||||
for (Child song : allSongs) {
|
||||
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
||||
!manager.isDownloaded(song.getId())) {
|
||||
songsToDownload++;
|
||||
artistNeedsSync = true;
|
||||
}
|
||||
}
|
||||
if (artistNeedsSync) {
|
||||
artistsNeedingSync.add(artist.getName());
|
||||
}
|
||||
}
|
||||
if (artistNeedsSync) {
|
||||
artistsNeedingSync.add(artist.getName());
|
||||
} else {
|
||||
for (ArtistID3 artist : artists) {
|
||||
boolean artistNeedsSync = false;
|
||||
for (Child song : allSongs) {
|
||||
if (song.getArtistId() != null && song.getArtistId().equals(artist.getId()) &&
|
||||
ExternalAudioReader.getUri(song) == null) {
|
||||
songsToDownload++;
|
||||
artistNeedsSync = true;
|
||||
}
|
||||
}
|
||||
if (artistNeedsSync) {
|
||||
artistsNeedingSync.add(artist.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (songsToDownload > 0) {
|
||||
bind.homeSyncStarredArtistsCard.setVisibility(View.VISIBLE);
|
||||
String message = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_artists_count,
|
||||
artistsNeedingSync.size(),
|
||||
|
||||
StringBuilder displayText = new StringBuilder();
|
||||
List<String> sampleArtists = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < Math.min(artistsNeedingSync.size(), 3); i++) {
|
||||
sampleArtists.add(artistsNeedingSync.get(i));
|
||||
}
|
||||
|
||||
if (!sampleArtists.isEmpty()) {
|
||||
displayText.append(String.join(", ", sampleArtists));
|
||||
if (artistsNeedingSync.size() > 3) {
|
||||
displayText.append("...");
|
||||
}
|
||||
}
|
||||
|
||||
String countText = getResources().getQuantityString(
|
||||
R.plurals.home_sync_starred_artists_count,
|
||||
artistsNeedingSync.size(),
|
||||
artistsNeedingSync.size()
|
||||
);
|
||||
bind.homeSyncStarredArtistsToSync.setText(message);
|
||||
|
||||
if (displayText.length() > 0) {
|
||||
bind.homeSyncStarredArtistsToSync.setText(displayText.toString() + "\n" + countText);
|
||||
} else {
|
||||
bind.homeSyncStarredArtistsToSync.setText(countText);
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> reorder());
|
||||
}
|
||||
} else {
|
||||
bind.homeSyncStarredArtistsCard.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -497,7 +678,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void initDiscoverSongSlideView() {
|
||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
|
||||
|
||||
@@ -962,6 +1143,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
if (bind != null && homeViewModel.getHomeSectorList() != null) {
|
||||
bind.homeLinearLayoutContainer.removeAllViews();
|
||||
|
||||
if (bind.homeSyncStarredCard.getVisibility() == View.VISIBLE) {
|
||||
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredCard);
|
||||
}
|
||||
|
||||
if (bind.homeSyncStarredAlbumsCard.getVisibility() == View.VISIBLE) {
|
||||
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredAlbumsCard);
|
||||
}
|
||||
|
||||
if (bind.homeSyncStarredArtistsCard.getVisibility() == View.VISIBLE) {
|
||||
bind.homeLinearLayoutContainer.addView(bind.homeSyncStarredArtistsCard);
|
||||
}
|
||||
|
||||
for (HomeSector sector : homeViewModel.getHomeSectorList()) {
|
||||
if (!sector.isVisible()) continue;
|
||||
|
||||
@@ -1062,20 +1255,25 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
if (bundle.containsKey(Constants.MEDIA_MIX)) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT));
|
||||
Child track = bundle.getParcelable(Constants.TRACK_OBJECT);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
final boolean[] playbackStarted = {false};
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), track)
|
||||
.observe(getViewLifecycleOwner(), songs -> {
|
||||
if (playbackStarted[0] || songs == null || songs.isEmpty()) return;
|
||||
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
}
|
||||
});
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (playbackStarted[0]) return;
|
||||
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
playbackStarted[0] = true;
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
|
||||
List<Child> media = bundle.getParcelableArrayList(Constants.TRACKS_OBJECT);
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentIndexBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||
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.MusicFolder;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.MusicIndexAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.IndexUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.IndexViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@UnstableApi
|
||||
public class IndexFragment extends Fragment implements ClickCallback {
|
||||
@@ -32,6 +45,8 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
private IndexViewModel indexViewModel;
|
||||
|
||||
private MusicIndexAdapter musicIndexAdapter;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private DirectoryRepository directoryRepository;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -40,6 +55,7 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentIndexBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
indexViewModel = new ViewModelProvider(requireActivity()).get(IndexViewModel.class);
|
||||
directoryRepository = new DirectoryRepository();
|
||||
|
||||
initAppBar();
|
||||
initDirectoryListView();
|
||||
@@ -48,6 +64,18 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeMediaBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
releaseMediaBrowser();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -107,4 +135,65 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
public void onMusicIndexClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicIndexPlay(Bundle bundle) {
|
||||
String directoryId = bundle.getString(Constants.MUSIC_DIRECTORY_ID);
|
||||
if (directoryId != null) {
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_collecting), Toast.LENGTH_SHORT).show();
|
||||
collectAndPlayDirectorySongs(directoryId);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
|
||||
private void releaseMediaBrowser() {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
private void collectAndPlayDirectorySongs(String directoryId) {
|
||||
List<Child> allSongs = new ArrayList<>();
|
||||
AtomicInteger pendingRequests = new AtomicInteger(0);
|
||||
|
||||
collectSongsFromDirectory(directoryId, allSongs, pendingRequests, () -> {
|
||||
if (!allSongs.isEmpty()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, allSongs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_playing, allSongs.size()), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
} else {
|
||||
activity.runOnUiThread(() -> {
|
||||
Toast.makeText(requireContext(), getString(R.string.folder_play_no_songs), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void collectSongsFromDirectory(String directoryId, List<Child> allSongs, AtomicInteger pendingRequests, Runnable onComplete) {
|
||||
pendingRequests.incrementAndGet();
|
||||
|
||||
directoryRepository.getMusicDirectory(directoryId).observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null && directory.getChildren() != null) {
|
||||
for (Child child : directory.getChildren()) {
|
||||
if (child.isDir()) {
|
||||
// It's a subdirectory, recurse into it
|
||||
collectSongsFromDirectory(child.getId(), allSongs, pendingRequests, onComplete);
|
||||
} else if (!child.isVideo()) {
|
||||
// It's a song, add it to the list
|
||||
synchronized (allSongs) {
|
||||
allSongs.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement pending requests and check if we're done
|
||||
if (pendingRequests.decrementAndGet() == 0) {
|
||||
onComplete.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,11 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
@@ -31,6 +35,8 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -49,6 +55,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||
|
||||
private MaterialToolbar materialToolbar;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -79,6 +86,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeMediaBrowser();
|
||||
activity.setBottomNavigationBarVisibility(true);
|
||||
}
|
||||
|
||||
@@ -292,4 +300,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
public void onMusicFolderClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,10 +413,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.getRoot().setShowNextButton(true);
|
||||
bind.getRoot().setShowFastForwardButton(false);
|
||||
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
|
||||
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.VISIBLE);
|
||||
resetPlaybackParameters(mediaBrowser);
|
||||
setPlaybackParameters(mediaBrowser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -524,31 +524,11 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_080) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_100) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_125));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_125));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_125);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_125) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_150));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_150));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_150);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_150) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_175));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_175));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_175);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_175) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_200));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_200));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_200);
|
||||
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_200) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_080));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_080));
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_080);
|
||||
}
|
||||
currentSpeed += 0.25f;
|
||||
if (currentSpeed > 2.0f) currentSpeed = 0.5f;
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(currentSpeed));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, currentSpeed));
|
||||
Preferences.setPlaybackSpeed(currentSpeed);
|
||||
});
|
||||
|
||||
skipSilenceToggleButton.setOnClickListener(view -> {
|
||||
@@ -600,7 +580,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void resetPlaybackParameters(MediaBrowser mediaBrowser) {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(1.0f));
|
||||
// TODO Resettare lo skip del silenzio
|
||||
}
|
||||
|
||||
|
||||
@@ -2,27 +2,41 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerQueueBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
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.PlayQueue;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioWriter;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
@@ -30,6 +44,7 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
@@ -38,6 +53,18 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private InnerFragmentPlayerQueueBinding bind;
|
||||
|
||||
private com.google.android.material.floatingactionbutton.FloatingActionButton fabMenuToggle;
|
||||
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabClearQueue;
|
||||
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabShuffleQueue;
|
||||
|
||||
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabSaveToPlaylist;
|
||||
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabDownloadAll;
|
||||
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabLoadQueue;
|
||||
|
||||
private boolean isMenuOpen = false;
|
||||
private final int ANIMATION_DURATION = 250;
|
||||
private final float FAB_VERTICAL_SPACING_DP = 70f;
|
||||
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
private PlaybackViewModel playbackViewModel;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
@@ -52,6 +79,27 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
fabMenuToggle = bind.fabMenuToggle;
|
||||
fabClearQueue = bind.fabClearQueue;
|
||||
fabShuffleQueue = bind.fabShuffleQueue;
|
||||
|
||||
fabSaveToPlaylist = bind.fabSaveToPlaylist;
|
||||
fabDownloadAll = bind.fabDownloadAll;
|
||||
fabLoadQueue = bind.fabLoadQueue;
|
||||
|
||||
fabMenuToggle.setOnClickListener(v -> toggleFabMenu());
|
||||
fabClearQueue.setOnClickListener(v -> handleClearQueueClick());
|
||||
fabShuffleQueue.setOnClickListener(v -> handleShuffleQueueClick());
|
||||
|
||||
fabSaveToPlaylist.setOnClickListener(v -> handleSaveToPlaylistClick());
|
||||
fabDownloadAll.setOnClickListener(v -> handleDownloadAllClick());
|
||||
fabLoadQueue.setOnClickListener(v -> handleLoadQueueClick());
|
||||
|
||||
// Hide Load Queue FAB if sync is disabled
|
||||
if (!Preferences.isSyncronizationEnabled()) {
|
||||
fabLoadQueue.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
initQueueRecyclerView();
|
||||
|
||||
return view;
|
||||
@@ -61,8 +109,6 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeBrowser();
|
||||
bindMediaController();
|
||||
|
||||
MediaManager.registerPlaybackObserver(mediaBrowserListenableFuture, playbackViewModel);
|
||||
observePlayback();
|
||||
}
|
||||
@@ -72,6 +118,16 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
super.onResume();
|
||||
setMediaBrowserListenableFuture();
|
||||
updateNowPlayingItem();
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
long position = mediaBrowserListenableFuture.get().getCurrentMediaItemIndex();
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
bind.playerQueueRecyclerView.scrollToPosition((int) position);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e("PlayerQueueFragment", "Failed to get mediaBrowserListenableFuture in onResume", e);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,18 +150,6 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
private void bindMediaController() {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
initShuffleButton(mediaBrowser);
|
||||
initCleanButton(mediaBrowser);
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
@@ -138,18 +182,6 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
fromPosition = viewHolder.getBindingAdapterPosition();
|
||||
toPosition = target.getBindingAdapterPosition();
|
||||
|
||||
/*
|
||||
* Per spostare un elemento nella coda devo:
|
||||
* - Spostare graficamente la traccia da una posizione all'altra con Collections.swap()
|
||||
* - Spostare nel db la traccia, tramite QueueRepository
|
||||
* - Notificare il Service dell'avvenuto spostamento con MusicPlayerRemote.moveSong()
|
||||
*
|
||||
* In onMove prendo la posizione di inizio e fine, ma solo al rilascio dell'elemento procedo allo spostamento
|
||||
* In questo modo evito che ad ogni cambio di posizione vada a riscrivere nel db
|
||||
* Al rilascio dell'elemento chiamo il metodo clearView()
|
||||
*/
|
||||
|
||||
Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition);
|
||||
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
|
||||
|
||||
@@ -177,46 +209,6 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
}).attachToRecyclerView(bind.playerQueueRecyclerView);
|
||||
}
|
||||
|
||||
private void initShuffleButton(MediaBrowser mediaBrowser) {
|
||||
bind.playerShuffleQueueFab.setOnClickListener(view -> {
|
||||
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
|
||||
|
||||
if (startPosition < endPosition) {
|
||||
ArrayList<Integer> pool = new ArrayList<>();
|
||||
|
||||
for (int i = startPosition; i <= endPosition; i++) {
|
||||
pool.add(i);
|
||||
}
|
||||
|
||||
while (pool.size() >= 2) {
|
||||
int fromPosition = (int) (Math.random() * (pool.size()));
|
||||
int positionA = pool.get(fromPosition);
|
||||
pool.remove(fromPosition);
|
||||
|
||||
int toPosition = (int) (Math.random() * (pool.size()));
|
||||
int positionB = pool.get(toPosition);
|
||||
pool.remove(toPosition);
|
||||
|
||||
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
|
||||
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
|
||||
}
|
||||
|
||||
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initCleanButton(MediaBrowser mediaBrowser) {
|
||||
bind.playerCleanQueueButton.setOnClickListener(view -> {
|
||||
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||
int endPosition = playerSongQueueAdapter.getItems().size();
|
||||
|
||||
MediaManager.removeRange(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||
bind.playerQueueRecyclerView.getAdapter().notifyItemRangeRemoved(startPosition, endPosition);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNowPlayingItem() {
|
||||
playerSongQueueAdapter.notifyDataSetChanged();
|
||||
}
|
||||
@@ -248,4 +240,250 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
playerSongQueueAdapter.setPlaybackState(id, playing != null && playing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility and animates all six secondary FABs.
|
||||
*/
|
||||
private void toggleFabMenu() {
|
||||
if (isMenuOpen) {
|
||||
// CLOSE MENU (Reverse order for visual effect)
|
||||
if (Preferences.isSyncronizationEnabled()) {
|
||||
closeFab(fabLoadQueue, 4);
|
||||
}
|
||||
closeFab(fabSaveToPlaylist, 3);
|
||||
closeFab(fabClearQueue, 2);
|
||||
closeFab(fabDownloadAll, 1);
|
||||
closeFab(fabShuffleQueue, 0);
|
||||
|
||||
fabMenuToggle.animate().rotation(0f).setDuration(ANIMATION_DURATION).start();
|
||||
} else {
|
||||
// OPEN MENU (lowest index at bottom)
|
||||
openFab(fabShuffleQueue, 0);
|
||||
openFab(fabDownloadAll, 1);
|
||||
openFab(fabClearQueue, 2);
|
||||
openFab(fabSaveToPlaylist, 3);
|
||||
if (Preferences.isSyncronizationEnabled()) {
|
||||
openFab(fabLoadQueue, 4);
|
||||
}
|
||||
fabMenuToggle.animate().rotation(45f).setDuration(ANIMATION_DURATION).start();
|
||||
}
|
||||
isMenuOpen = !isMenuOpen;
|
||||
}
|
||||
|
||||
private void openFab(View fab, int index) {
|
||||
final float displacement = getResources().getDisplayMetrics().density * (FAB_VERTICAL_SPACING_DP * (index + 1));
|
||||
|
||||
fab.setVisibility(View.VISIBLE);
|
||||
fab.setAlpha(0f);
|
||||
fab.setTranslationY(displacement); // Start at the hidden (closed) position
|
||||
|
||||
fab.animate()
|
||||
.translationY(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(ANIMATION_DURATION)
|
||||
.start();
|
||||
}
|
||||
|
||||
private void closeFab(View fab, int index) {
|
||||
final float displacement = getResources().getDisplayMetrics().density * (FAB_VERTICAL_SPACING_DP * (index + 1));
|
||||
|
||||
fab.animate()
|
||||
.translationY(displacement)
|
||||
.alpha(0f)
|
||||
.setDuration(ANIMATION_DURATION)
|
||||
.withEndAction(() -> fab.setVisibility(View.GONE))
|
||||
.start();
|
||||
}
|
||||
|
||||
private void handleShuffleQueueClick() {
|
||||
Log.d(TAG, "Shuffle Queue Clicked!");
|
||||
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
|
||||
|
||||
if (startPosition < endPosition) {
|
||||
ArrayList<Integer> pool = new ArrayList<>();
|
||||
|
||||
for (int i = startPosition; i <= endPosition; i++) {
|
||||
pool.add(i);
|
||||
}
|
||||
|
||||
while (pool.size() >= 2) {
|
||||
int fromPosition = (int) (Math.random() * (pool.size()));
|
||||
int positionA = pool.get(fromPosition);
|
||||
pool.remove(fromPosition);
|
||||
|
||||
int toPosition = (int) (Math.random() * (pool.size()));
|
||||
int positionB = pool.get(toPosition);
|
||||
pool.remove(toPosition);
|
||||
|
||||
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
|
||||
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
|
||||
}
|
||||
|
||||
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error shuffling queue", e);
|
||||
}
|
||||
|
||||
toggleFabMenu();
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void handleClearQueueClick() {
|
||||
Log.d(TAG, "Clear Queue Clicked!");
|
||||
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||
int endPosition = playerSongQueueAdapter.getItems().size();
|
||||
|
||||
MediaManager.removeRange(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||
bind.playerQueueRecyclerView.getAdapter().notifyItemRangeRemoved(startPosition, endPosition - startPosition);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error clearing queue", e);
|
||||
}
|
||||
|
||||
toggleFabMenu();
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void handleSaveToPlaylistClick() {
|
||||
Log.d(TAG, "Save to Playlist Clicked!");
|
||||
|
||||
List<Child> queueSongs = playerSongQueueAdapter.getItems();
|
||||
|
||||
if (queueSongs == null || queueSongs.isEmpty()) {
|
||||
Toast.makeText(requireContext(), "Queue is empty", Toast.LENGTH_SHORT).show();
|
||||
toggleFabMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(queueSongs));
|
||||
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
toggleFabMenu();
|
||||
}
|
||||
|
||||
private void handleDownloadAllClick() {
|
||||
Log.d(TAG, "Download All Clicked!");
|
||||
|
||||
List<Child> queueSongs = playerSongQueueAdapter.getItems();
|
||||
|
||||
if (queueSongs == null || queueSongs.isEmpty()) {
|
||||
Toast.makeText(requireContext(), "Queue is empty", Toast.LENGTH_SHORT).show();
|
||||
toggleFabMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
int downloadCount = 0;
|
||||
|
||||
if (Preferences.getDownloadDirectoryUri() == null) {
|
||||
List<MediaItem> mediaItemsToDownload = MappingUtil.mapMediaItems(queueSongs);
|
||||
List<com.cappielloantonio.tempo.model.Download> downloadModels = new ArrayList<>();
|
||||
|
||||
for (Child child : queueSongs) {
|
||||
com.cappielloantonio.tempo.model.Download downloadModel =
|
||||
new com.cappielloantonio.tempo.model.Download(child);
|
||||
downloadModel.setArtist(child.getArtist());
|
||||
downloadModel.setAlbum(child.getAlbum());
|
||||
downloadModel.setCoverArtId(child.getCoverArtId());
|
||||
downloadModels.add(downloadModel);
|
||||
}
|
||||
|
||||
DownloaderManager downloaderManager = DownloadUtil.getDownloadTracker(requireContext());
|
||||
|
||||
if (downloaderManager != null) {
|
||||
downloaderManager.download(mediaItemsToDownload, downloadModels);
|
||||
downloadCount = queueSongs.size();
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, downloadCount, downloadCount),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
if (playerSongQueueAdapter != null) {
|
||||
playerSongQueueAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
Log.e(TAG, "DownloaderManager not initialized. Check DownloadUtil.");
|
||||
Toast.makeText(requireContext(), "Download service unavailable.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
for (Child song : queueSongs) {
|
||||
if (ExternalAudioReader.getUri(song) == null) {
|
||||
ExternalAudioWriter.downloadToUserDirectory(requireContext(), song);
|
||||
downloadCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadCount > 0) {
|
||||
Toast.makeText(requireContext(),
|
||||
getResources().getQuantityString(R.plurals.songs_download_started, downloadCount, downloadCount),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
if (playerSongQueueAdapter != null) {
|
||||
playerSongQueueAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}, 2000);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "All songs already downloaded", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
toggleFabMenu();
|
||||
}
|
||||
|
||||
private void handleLoadQueueClick() {
|
||||
Log.d(TAG, "Load Queue Clicked!");
|
||||
if (!Preferences.isSyncronizationEnabled()) {
|
||||
toggleFabMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerBottomSheetViewModel playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
|
||||
playerBottomSheetViewModel.getPlayQueue().observe(getViewLifecycleOwner(), new Observer<PlayQueue>() {
|
||||
@Override
|
||||
public void onChanged(PlayQueue playQueue) {
|
||||
playerBottomSheetViewModel.getPlayQueue().removeObserver(this);
|
||||
|
||||
if (playQueue != null && playQueue.getEntries() != null && !playQueue.getEntries().isEmpty()) {
|
||||
int currentIndex = 0;
|
||||
for (int i = 0; i < playQueue.getEntries().size(); i++) {
|
||||
if (playQueue.getEntries().get(i).getId().equals(playQueue.getCurrent())) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), currentIndex);
|
||||
|
||||
Toast.makeText(requireContext(), "Queue loaded", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "No saved queue found", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
toggleFabMenu();
|
||||
}
|
||||
});
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
if (isMenuOpen) {
|
||||
toggleFabMenu();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
@@ -156,10 +156,10 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_playlist_sort_name) {
|
||||
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
|
||||
playlistHorizontalAdapter.sort(Constants.PLAYLIST_ORDER_BY_NAME);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_playlist_sort_random) {
|
||||
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
|
||||
playlistHorizontalAdapter.sort(Constants.PLAYLIST_ORDER_BY_RANDOM);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
@@ -77,6 +78,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
result -> {}
|
||||
);
|
||||
|
||||
if (!BuildConfig.FLAVOR.equals("tempus")) {
|
||||
PreferenceCategory githubUpdateCategory = findPreference("settings_github_update_category_key");
|
||||
if (githubUpdateCategory != null) {
|
||||
getPreferenceScreen().removePreference(githubUpdateCategory);
|
||||
}
|
||||
}
|
||||
|
||||
directoryPickerLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
|
||||
@@ -189,7 +189,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
bind.songListShuffleImageView.setOnClickListener(v -> {
|
||||
Collections.shuffle(songs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(500, songs.size())), 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
@@ -43,7 +42,6 @@ import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -61,7 +59,10 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private List<Child> currentAlbumTracks = Collections.emptyList();
|
||||
private List<MediaItem> currentAlbumMediaItems = Collections.emptyList();
|
||||
|
||||
private boolean isFirstBatch = true;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private static final String TAG = "AlbumBottomSheetDialog";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -114,33 +115,41 @@ 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(requireContext());
|
||||
});
|
||||
favoriteToggle.setOnClickListener(v -> albumBottomSheetViewModel.setFavorite(requireContext()));
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
AlbumRepository albumRepository = new AlbumRepository();
|
||||
albumRepository.getInstantMix(album, 20, new MediaCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
if (activity == null) return;
|
||||
|
||||
@Override
|
||||
public void onLoadMedia(List<?> media) {
|
||||
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||
ListenableFuture<MediaBrowser> activityBrowserFuture = activity.getMediaBrowserListenableFuture();
|
||||
if (activityBrowserFuture == null) return;
|
||||
|
||||
if (!media.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
isFirstBatch = true;
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
|
||||
albumBottomSheetViewModel.getAlbumInstantMix(activity, album).observe(activity, media -> {
|
||||
if (media == null || media.isEmpty()) return;
|
||||
if (getActivity() == null) return;
|
||||
|
||||
MusicUtil.ratingFilter(media);
|
||||
|
||||
if (isFirstBatch) {
|
||||
isFirstBatch = false;
|
||||
|
||||
MediaManager.startQueue(activityBrowserFuture, media, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
|
||||
if (isAdded()) {
|
||||
dismissBottomSheet();
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
} else {
|
||||
MediaManager.enqueue(activityBrowserFuture, media, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||
playRandom.setOnClickListener(v -> {
|
||||
AlbumRepository albumRepository = new AlbumRepository();
|
||||
@@ -186,18 +195,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
});
|
||||
|
||||
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
|
||||
addToPlaylist.setOnClickListener(v -> {
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||
addToPlaylist.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
dismissBottomSheet();
|
||||
}));
|
||||
|
||||
removeAllTextView = view.findViewById(R.id.remove_all_text_view);
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
@@ -291,4 +298,5 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private void refreshShares() {
|
||||
homeViewModel.refreshShares(requireActivity());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import com.cappielloantonio.tempo.viewmodel.ArtistBottomSheetViewModel;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
|
||||
@UnstableApi
|
||||
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||
private static final String TAG = "AlbumBottomSheetDialog";
|
||||
@@ -38,6 +39,8 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private boolean isFirstBatch = true;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -86,20 +89,31 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
ArtistRepository artistRepository = new ArtistRepository();
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
if (activity == null) return;
|
||||
|
||||
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
|
||||
// navidrome may return null for this
|
||||
if (songs == null)
|
||||
return;
|
||||
MusicUtil.ratingFilter(songs);
|
||||
ListenableFuture<MediaBrowser> activityBrowserFuture = activity.getMediaBrowserListenableFuture();
|
||||
if (activityBrowserFuture == null) return;
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
isFirstBatch = true;
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
|
||||
artistBottomSheetViewModel.getArtistInstantMix(activity, artist).observe(activity, media -> {
|
||||
if (media == null || media.isEmpty()) return;
|
||||
if (getActivity() == null) return;
|
||||
|
||||
MusicUtil.ratingFilter(media);
|
||||
|
||||
if (isFirstBatch) {
|
||||
isFirstBatch = false;
|
||||
MediaManager.startQueue(activityBrowserFuture, media, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
if (isAdded()) {
|
||||
dismissBottomSheet();
|
||||
}
|
||||
} else {
|
||||
MediaManager.enqueue(activityBrowserFuture, media, true);
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,16 +122,10 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
ArtistRepository artistRepository = new ArtistRepository();
|
||||
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
|
||||
dismissBottomSheet();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
@@ -139,4 +147,5 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
private void releaseMediaBrowser() {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.dialog.ShareUpdateDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
@@ -24,6 +25,8 @@ import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class ShareBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||
|
||||
@@ -50,8 +53,15 @@ public class ShareBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private void init(View view) {
|
||||
ImageView shareCover = view.findViewById(R.id.share_cover_image_view);
|
||||
|
||||
String coverArtId = null;
|
||||
List<Child> entries = shareBottomSheetViewModel.getShare().getEntries();
|
||||
|
||||
if (entries != null && !entries.isEmpty()) {
|
||||
coverArtId = entries.get(0).getCoverArtId();
|
||||
}
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), shareBottomSheetViewModel.getShare().getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Unknown)
|
||||
.from(requireContext(), coverArtId, CustomGlideRequest.ResourceType.Unknown)
|
||||
.build()
|
||||
.into(shareCover);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.ClipboardManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -44,8 +45,6 @@ import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import android.content.Intent;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -67,7 +66,9 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private AssetLinkUtil.AssetLink currentAlbumLink;
|
||||
private AssetLinkUtil.AssetLink currentArtistLink;
|
||||
|
||||
private boolean isFirstBatch = true;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
private static final String TAG = "SongBottomSheetDialog";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -143,20 +144,34 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
playRadio.setOnClickListener(v -> {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, song);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
if (activity == null) return;
|
||||
|
||||
songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
ListenableFuture<MediaBrowser> activityBrowserFuture = activity.getMediaBrowserListenableFuture();
|
||||
if (activityBrowserFuture == null) {
|
||||
Log.e(TAG, "MediaBrowser Future is null in MainActivity");
|
||||
return;
|
||||
}
|
||||
|
||||
if (songs == null) {
|
||||
dismissBottomSheet();
|
||||
return;
|
||||
}
|
||||
isFirstBatch = true;
|
||||
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
dismissBottomSheet();
|
||||
songBottomSheetViewModel.getInstantMix(activity, song).observe(activity, media -> {
|
||||
|
||||
if (media == null || media.isEmpty()) return;
|
||||
if (getActivity() == null) return;
|
||||
|
||||
MusicUtil.ratingFilter(media);
|
||||
|
||||
if (isFirstBatch) {
|
||||
isFirstBatch = false;
|
||||
MediaManager.startQueue(activityBrowserFuture, media, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
if (isAdded()) {
|
||||
dismissBottomSheet();
|
||||
}
|
||||
} else {
|
||||
MediaManager.enqueue(activityBrowserFuture, media, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -327,16 +342,12 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
chip.setVisibility(View.VISIBLE);
|
||||
|
||||
chip.setOnClickListener(v -> {
|
||||
if (assetLink != null) {
|
||||
((MainActivity) requireActivity()).openAssetLink(assetLink);
|
||||
}
|
||||
((MainActivity) requireActivity()).openAssetLink(assetLink);
|
||||
});
|
||||
|
||||
chip.setOnLongClickListener(v -> {
|
||||
if (assetLink != null) {
|
||||
AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
|
||||
Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, id), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
AssetLinkUtil.copyToClipboard(requireContext(), assetLink);
|
||||
Toast.makeText(requireContext(), getString(R.string.asset_link_copied_toast, id), Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -397,4 +408,5 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private void refreshShares() {
|
||||
homeViewModel.refreshShares(requireActivity());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,13 +61,6 @@ object Constants {
|
||||
const val MEDIA_TYPE_VIDEO = "video"
|
||||
const val MEDIA_TYPE_RADIO = "radio"
|
||||
|
||||
const val MEDIA_PLAYBACK_SPEED_080 = 0.8f
|
||||
const val MEDIA_PLAYBACK_SPEED_100 = 1.0f
|
||||
const val MEDIA_PLAYBACK_SPEED_125 = 1.25f
|
||||
const val MEDIA_PLAYBACK_SPEED_150 = 1.50f
|
||||
const val MEDIA_PLAYBACK_SPEED_175 = 1.75f
|
||||
const val MEDIA_PLAYBACK_SPEED_200 = 2.0f
|
||||
|
||||
const val MEDIA_RECENTLY_PLAYED = "MEDIA_RECENTLY_PLAYED"
|
||||
const val MEDIA_MOST_PLAYED = "MEDIA_MOST_PLAYED"
|
||||
const val MEDIA_RECENTLY_ADDED = "MEDIA_RECENTLY_ADDED"
|
||||
@@ -133,4 +126,7 @@ object Constants {
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
||||
enum class SeedType {
|
||||
ARTIST, ALBUM, TRACK
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,17 @@ class DynamicMediaSourceFactory(
|
||||
|
||||
else -> {
|
||||
val extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory()
|
||||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItem)
|
||||
val progressiveFactory = ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
|
||||
val uri = mediaItem.localConfiguration?.uri
|
||||
val isTranscoding = uri?.getQueryParameter("maxBitRate") != null ||
|
||||
(uri?.getQueryParameter("format") != null && uri?.getQueryParameter("format") != "raw")
|
||||
|
||||
if (isTranscoding && OpenSubsonicExtensionsUtil.isTranscodeOffsetExtensionAvailable()) {
|
||||
TranscodingMediaSource(mediaItem, dataSourceFactory, progressiveFactory)
|
||||
} else {
|
||||
progressiveFactory.createMediaSource(mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
@@ -35,84 +36,106 @@ public class MappingUtil {
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
private static final String TAG = "MappingUtil";
|
||||
|
||||
public static MediaItem mapMediaItem(Child media) {
|
||||
Uri uri = getUri(media);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
|
||||
try {
|
||||
Uri uri = getUri(media);
|
||||
String coverArtId = media.getCoverArtId();
|
||||
Uri artworkUri = null;
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", media.getId());
|
||||
bundle.putString("parentId", media.getParentId());
|
||||
bundle.putBoolean("isDir", media.isDir());
|
||||
bundle.putString("title", media.getTitle());
|
||||
bundle.putString("album", media.getAlbum());
|
||||
bundle.putString("artist", media.getArtist());
|
||||
bundle.putInt("track", media.getTrack() != null ? media.getTrack() : 0);
|
||||
bundle.putInt("year", media.getYear() != null ? media.getYear() : 0);
|
||||
bundle.putString("genre", media.getGenre());
|
||||
bundle.putString("coverArtId", media.getCoverArtId());
|
||||
bundle.putLong("size", media.getSize() != null ? media.getSize() : 0);
|
||||
bundle.putString("contentType", media.getContentType());
|
||||
bundle.putString("suffix", media.getSuffix());
|
||||
bundle.putString("transcodedContentType", media.getTranscodedContentType());
|
||||
bundle.putString("transcodedSuffix", media.getTranscodedSuffix());
|
||||
bundle.putInt("duration", media.getDuration() != null ? media.getDuration() : 0);
|
||||
bundle.putInt("bitrate", media.getBitrate() != null ? media.getBitrate() : 0);
|
||||
bundle.putInt("samplingRate", media.getSamplingRate() != null ? media.getSamplingRate() : 0);
|
||||
bundle.putInt("bitDepth", media.getBitDepth() != null ? media.getBitDepth() : 0);
|
||||
bundle.putString("path", media.getPath());
|
||||
bundle.putBoolean("isVideo", media.isVideo());
|
||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||
bundle.putString("albumId", media.getAlbumId());
|
||||
bundle.putString("artistId", media.getArtistId());
|
||||
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC);
|
||||
bundle.putLong("bookmarkPosition", media.getBookmarkPosition() != null ? media.getBookmarkPosition() : 0);
|
||||
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
|
||||
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
|
||||
bundle.putString("uri", uri.toString());
|
||||
bundle.putString("assetLinkSong", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, media.getId()));
|
||||
bundle.putString("assetLinkAlbum", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, media.getAlbumId()));
|
||||
bundle.putString("assetLinkArtist", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, media.getArtistId()));
|
||||
bundle.putString("assetLinkGenre", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_GENRE, media.getGenre()));
|
||||
Integer year = media.getYear();
|
||||
bundle.putString("assetLinkYear", year != null && year != 0 ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(year)) : null);
|
||||
if (coverArtId != null) {
|
||||
artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, Preferences.getImageSize()));
|
||||
}
|
||||
|
||||
return new MediaItem.Builder()
|
||||
.setMediaId(media.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(media.getTitle())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(new HeartRating(media.getStarred() != null))
|
||||
.setSupportedCommands(
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", media.getId());
|
||||
bundle.putString("parentId", media.getParentId());
|
||||
bundle.putBoolean("isDir", media.isDir());
|
||||
|
||||
bundle.putString("title", media.getTitle());
|
||||
bundle.putString("album", media.getAlbum());
|
||||
bundle.putString("artist", media.getArtist());
|
||||
|
||||
bundle.putInt("track", media.getTrack() != null ? media.getTrack() : 0);
|
||||
bundle.putInt("year", media.getYear() != null ? media.getYear() : 0);
|
||||
bundle.putString("genre", media.getGenre());
|
||||
bundle.putString("coverArtId", coverArtId);
|
||||
bundle.putLong("size", media.getSize() != null ? media.getSize() : 0);
|
||||
bundle.putString("contentType", media.getContentType());
|
||||
bundle.putString("suffix", media.getSuffix());
|
||||
bundle.putString("transcodedContentType", media.getTranscodedContentType());
|
||||
bundle.putString("transcodedSuffix", media.getTranscodedSuffix());
|
||||
bundle.putInt("duration", media.getDuration() != null ? media.getDuration() : 0);
|
||||
bundle.putInt("bitrate", media.getBitrate() != null ? media.getBitrate() : 0);
|
||||
bundle.putInt("samplingRate", media.getSamplingRate() != null ? media.getSamplingRate() : 0);
|
||||
bundle.putInt("bitDepth", media.getBitDepth() != null ? media.getBitDepth() : 0);
|
||||
bundle.putString("path", media.getPath());
|
||||
bundle.putBoolean("isVideo", media.isVideo());
|
||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||
bundle.putString("albumId", media.getAlbumId());
|
||||
bundle.putString("artistId", media.getArtistId());
|
||||
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC);
|
||||
bundle.putLong("bookmarkPosition", media.getBookmarkPosition() != null ? media.getBookmarkPosition() : 0);
|
||||
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
|
||||
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
|
||||
bundle.putString("uri", uri.toString());
|
||||
|
||||
bundle.putString("assetLinkSong", media.getId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, media.getId()) : null);
|
||||
bundle.putString("assetLinkAlbum", media.getAlbumId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, media.getAlbumId()) : null);
|
||||
bundle.putString("assetLinkArtist", media.getArtistId() != null ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, media.getArtistId()) : null);
|
||||
bundle.putString("assetLinkGenre", AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_GENRE, media.getGenre()));
|
||||
Integer year = media.getYear();
|
||||
bundle.putString("assetLinkYear", year != null && year != 0 ? AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_YEAR, String.valueOf(year)) : null);
|
||||
|
||||
return new MediaItem.Builder()
|
||||
.setMediaId(media.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(media.getTitle())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(new HeartRating(media.getStarred() != null))
|
||||
.setSupportedCommands(
|
||||
ImmutableList.of(
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||
)
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
new MediaItem.RequestMetadata.Builder()
|
||||
.setMediaUri(uri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
new MediaItem.RequestMetadata.Builder()
|
||||
.setMediaUri(uri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
String id = media != null ? media.getId() : "NULL_MEDIA_OBJECT";
|
||||
String title = media != null ? media.getTitle() : "N/A";
|
||||
|
||||
Log.e(TAG, "Instant Mix CRASH! Failed to map song to MediaItem. " +
|
||||
"Problematic Song ID: " + id +
|
||||
", Title: " + title +
|
||||
". Inspect this song's Subsonic data for missing fields.", e);
|
||||
throw new RuntimeException("Mapping failed for song ID: " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static MediaItem mapMediaItem(MediaItem old) {
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MusicUtil {
|
||||
private static final Pattern BITRATE_PATTERN = Pattern.compile("&maxBitRate=\\d+");
|
||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("&format=\\w+");
|
||||
|
||||
public static Uri getStreamUri(String id) {
|
||||
public static Uri getStreamUri(String id, int timeOffset) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
StringBuilder uri = new StringBuilder();
|
||||
@@ -58,6 +58,8 @@ public class MusicUtil {
|
||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||
if (Preferences.askForEstimateContentLength())
|
||||
uri.append("&estimateContentLength=true");
|
||||
if (timeOffset > 0)
|
||||
uri.append("&timeOffset=").append(timeOffset);
|
||||
|
||||
uri.append("&id=").append(id);
|
||||
|
||||
@@ -66,6 +68,10 @@ public class MusicUtil {
|
||||
return Uri.parse(uri.toString());
|
||||
}
|
||||
|
||||
public static Uri getStreamUri(String id) {
|
||||
return getStreamUri(id, 0);
|
||||
}
|
||||
|
||||
public static Uri updateStreamUri(Uri uri) {
|
||||
String s = uri.toString();
|
||||
Matcher m1 = BITRATE_PATTERN.matcher(s);
|
||||
|
||||
@@ -70,9 +70,12 @@ object Preferences {
|
||||
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 GITHUB_UPDATE_CHECK = "github_update_check"
|
||||
private const val CONTINUOUS_PLAY = "continuous_play"
|
||||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
private const val ALLOW_PLAYLIST_DUPLICATES = "allow_playlist_duplicates"
|
||||
private const val HOME_SORT_PLAYLISTS = "home_sort_playlists"
|
||||
private const val DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER = Constants.PLAYLIST_ORDER_BY_RANDOM
|
||||
private const val EQUALIZER_ENABLED = "equalizer_enabled"
|
||||
private const val EQUALIZER_BAND_LEVELS = "equalizer_band_levels"
|
||||
private const val MINI_SHUFFLE_BUTTON_VISIBILITY = "mini_shuffle_button_visibility"
|
||||
@@ -80,6 +83,8 @@ object Preferences {
|
||||
private const val ALBUM_SORT_ORDER = "album_sort_order"
|
||||
private const val DEFAULT_ALBUM_SORT_ORDER = Constants.ALBUM_ORDER_BY_NAME
|
||||
private const val ARTIST_SORT_BY_ALBUM_COUNT= "artist_sort_by_album_count"
|
||||
private const val SORT_SEARCH_CHRONOLOGICALLY= "sort_search_chronologically"
|
||||
private const val ARTIST_DISPLAY_BIOGRAPHY= "artist_display_biography"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
@@ -574,15 +579,21 @@ object Preferences {
|
||||
return App.getInstance().preferences.getBoolean(RATING_PER_ITEM, false)
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun showTempoUpdateDialog(): Boolean {
|
||||
fun isGithubUpdateEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(GITHUB_UPDATE_CHECK, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showTempusUpdateDialog(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
NEXT_UPDATE_CHECK, 0
|
||||
) + 86400000 < System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setTempoUpdateReminder() {
|
||||
fun setTempusUpdateReminder() {
|
||||
App.getInstance().preferences.edit().putLong(NEXT_UPDATE_CHECK, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
@@ -616,6 +627,16 @@ object Preferences {
|
||||
return App.getInstance().preferences.getBoolean(ALLOW_PLAYLIST_DUPLICATES, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getHomeSortPlaylists(): String {
|
||||
return App.getInstance().preferences.getString(HOME_SORT_PLAYLISTS, DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER) ?: DEFAULT_HOME_SORT_PLAYLISTS_SORT_ORDER
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getHomeSortPlaylists(sortOrder: String) {
|
||||
App.getInstance().preferences.edit().putString(HOME_SORT_PLAYLISTS, sortOrder).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerEnabled(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(EQUALIZER_ENABLED, enabled).apply()
|
||||
@@ -667,4 +688,19 @@ object Preferences {
|
||||
else
|
||||
return Constants.ARTIST_ORDER_BY_NAME
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isSearchSortingChronologicallyEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SORT_SEARCH_CHRONOLOGICALLY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getArtistDisplayBiography(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(ARTIST_DISPLAY_BIOGRAPHY, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setArtistDisplayBiography(displayBiographyEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(ARTIST_DISPLAY_BIOGRAPHY, displayBiographyEnabled).apply()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.common.Player;
|
||||
|
||||
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||
|
||||
@@ -17,7 +18,7 @@ import java.util.Objects;
|
||||
public class ReplayGainUtil {
|
||||
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||
|
||||
public static void setReplayGain(ExoPlayer player, Tracks tracks) {
|
||||
public static void setReplayGain(Player player, Tracks tracks) {
|
||||
List<Metadata> metadata = getMetadata(tracks);
|
||||
List<ReplayGain> gains = getReplayGains(metadata);
|
||||
|
||||
@@ -62,7 +63,7 @@ public class ReplayGainUtil {
|
||||
}
|
||||
}
|
||||
|
||||
if (gains.size() == 0) gains.add(0, new ReplayGain());
|
||||
if (gains.isEmpty()) gains.add(0, new ReplayGain());
|
||||
if (gains.size() == 1) gains.add(1, new ReplayGain());
|
||||
|
||||
return gains;
|
||||
@@ -108,7 +109,7 @@ public class ReplayGainUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
private static void applyReplayGain(Player player, List<ReplayGain> gains) {
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains == null || gains.isEmpty()) {
|
||||
setNoReplayGain(player);
|
||||
return;
|
||||
@@ -137,33 +138,33 @@ public class ReplayGainUtil {
|
||||
setNoReplayGain(player);
|
||||
}
|
||||
|
||||
private static void setNoReplayGain(ExoPlayer player) {
|
||||
private static void setNoReplayGain(Player player) {
|
||||
setReplayGain(player, 0f);
|
||||
}
|
||||
|
||||
private static void setTrackReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
private static void setTrackReplayGain(Player player, List<ReplayGain> gains) {
|
||||
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
|
||||
|
||||
setReplayGain(player, trackGain != 0f ? trackGain : 0f);
|
||||
}
|
||||
|
||||
private static void setAlbumReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
private static void setAlbumReplayGain(Player player, List<ReplayGain> gains) {
|
||||
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
|
||||
|
||||
setReplayGain(player, albumGain != 0f ? albumGain : 0f);
|
||||
}
|
||||
|
||||
private static void setAutoReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
private static void setAutoReplayGain(Player player, List<ReplayGain> gains) {
|
||||
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
|
||||
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
|
||||
|
||||
setReplayGain(player, albumGain != 0f ? albumGain : trackGain);
|
||||
}
|
||||
|
||||
private static boolean areTracksConsecutive(ExoPlayer player) {
|
||||
private static boolean areTracksConsecutive(Player player) {
|
||||
MediaItem currentMediaItem = player.getCurrentMediaItem();
|
||||
int currentMediaItemIndex = player.getCurrentMediaItemIndex();
|
||||
MediaItem pastMediaItem = currentMediaItemIndex > 0 ? player.getMediaItemAt(currentMediaItemIndex - 1) : null;
|
||||
int prevMediaItemIndex = player.getPreviousMediaItemIndex();
|
||||
MediaItem pastMediaItem = prevMediaItemIndex == C.INDEX_UNSET ? null : player.getMediaItemAt(prevMediaItemIndex);
|
||||
|
||||
return currentMediaItem != null &&
|
||||
pastMediaItem != null &&
|
||||
@@ -172,7 +173,7 @@ public class ReplayGainUtil {
|
||||
pastMediaItem.mediaMetadata.albumTitle.toString().equals(currentMediaItem.mediaMetadata.albumTitle.toString());
|
||||
}
|
||||
|
||||
private static void setReplayGain(ExoPlayer player, float gain) {
|
||||
private static void setReplayGain(Player player, float gain) {
|
||||
player.setVolume((float) Math.pow(10f, gain / 20f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.TransferListener
|
||||
import androidx.media3.decoder.DecoderInputBuffer
|
||||
import androidx.media3.exoplayer.FormatHolder
|
||||
import androidx.media3.exoplayer.LoadingInfo
|
||||
import androidx.media3.exoplayer.SeekParameters
|
||||
import androidx.media3.exoplayer.source.CompositeMediaSource
|
||||
import androidx.media3.exoplayer.source.ForwardingTimeline
|
||||
import androidx.media3.exoplayer.source.MediaPeriod
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.exoplayer.source.SampleStream
|
||||
import androidx.media3.exoplayer.trackselection.ExoTrackSelection
|
||||
import androidx.media3.exoplayer.upstream.Allocator
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class TranscodingMediaSource(
|
||||
private val mediaItem: MediaItem,
|
||||
private val dataSourceFactory: DataSource.Factory,
|
||||
private val progressiveMediaSourceFactory: ProgressiveMediaSource.Factory
|
||||
) : CompositeMediaSource<Void>() {
|
||||
|
||||
private var durationUs: Long = C.TIME_UNSET
|
||||
private var currentSource: MediaSource? = null
|
||||
|
||||
init {
|
||||
val extras = mediaItem.mediaMetadata.extras
|
||||
if (extras != null && extras.containsKey("duration")) {
|
||||
val seconds = extras.getInt("duration")
|
||||
if (seconds > 0) {
|
||||
durationUs = Util.msToUs(seconds * 1000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaItem() = mediaItem
|
||||
|
||||
override fun prepareSourceInternal(mediaTransferListener: TransferListener?) {
|
||||
super.prepareSourceInternal(mediaTransferListener)
|
||||
val initialSource = progressiveMediaSourceFactory.createMediaSource(mediaItem)
|
||||
currentSource = initialSource
|
||||
prepareChildSource(null, initialSource)
|
||||
}
|
||||
|
||||
override fun onChildSourceInfoRefreshed(
|
||||
childSourceId: Void?,
|
||||
mediaSource: MediaSource,
|
||||
newTimeline: Timeline
|
||||
) {
|
||||
val timeline =
|
||||
if (durationUs != C.TIME_UNSET) {
|
||||
DurationOverridingTimeline(newTimeline, durationUs)
|
||||
} else {
|
||||
newTimeline
|
||||
}
|
||||
refreshSourceInfo(timeline)
|
||||
}
|
||||
|
||||
override fun createPeriod(
|
||||
id: MediaSource.MediaPeriodId,
|
||||
allocator: Allocator,
|
||||
startPositionUs: Long
|
||||
): MediaPeriod {
|
||||
val source = currentSource ?: throw IllegalStateException("Source not ready")
|
||||
val childPeriod = source.createPeriod(id, allocator, startPositionUs)
|
||||
return TranscodingMediaPeriod(childPeriod, source, id, allocator)
|
||||
}
|
||||
|
||||
override fun releasePeriod(mediaPeriod: MediaPeriod) {
|
||||
val transcodingPeriod = mediaPeriod as TranscodingMediaPeriod
|
||||
transcodingPeriod.release()
|
||||
|
||||
if (transcodingPeriod.currentOffsetUs > 0) {
|
||||
releaseChildSource(null)
|
||||
val initialSource = progressiveMediaSourceFactory.createMediaSource(mediaItem)
|
||||
currentSource = initialSource
|
||||
prepareChildSource(null, initialSource)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaPeriodIdForChildMediaPeriodId(
|
||||
childSourceId: Void?,
|
||||
mediaPeriodId: MediaSource.MediaPeriodId
|
||||
) = mediaPeriodId
|
||||
|
||||
private inner class TranscodingMediaPeriod(
|
||||
private var currentPeriod: MediaPeriod,
|
||||
private var source: MediaSource,
|
||||
private val id: MediaSource.MediaPeriodId,
|
||||
private val allocator: Allocator
|
||||
) : MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
private var localCallback: MediaPeriod.Callback? = null
|
||||
internal var currentOffsetUs: Long = 0
|
||||
private var isReloading = false
|
||||
|
||||
private var lastSelections: Array<out ExoTrackSelection?>? = null
|
||||
private var lastMayRetainStreamFlags: BooleanArray? = null
|
||||
private var activeWrappers: Array<OffsetSampleStream?> = emptyArray()
|
||||
|
||||
fun release() {
|
||||
source.releasePeriod(currentPeriod)
|
||||
}
|
||||
|
||||
override fun prepare(callback: MediaPeriod.Callback, positionUs: Long) {
|
||||
localCallback = callback
|
||||
currentPeriod.prepare(this, positionUs)
|
||||
}
|
||||
|
||||
override fun maybeThrowPrepareError() {
|
||||
if (!isReloading) currentPeriod.maybeThrowPrepareError()
|
||||
}
|
||||
|
||||
override fun getTrackGroups() = currentPeriod.trackGroups
|
||||
|
||||
override fun getStreamKeys(trackSelections: MutableList<ExoTrackSelection>) =
|
||||
currentPeriod.getStreamKeys(trackSelections)
|
||||
|
||||
override fun selectTracks(
|
||||
selections: Array<out ExoTrackSelection?>,
|
||||
mayRetainStreamFlags: BooleanArray,
|
||||
streams: Array<SampleStream?>,
|
||||
streamResetFlags: BooleanArray,
|
||||
positionUs: Long
|
||||
): Long {
|
||||
lastSelections = selections
|
||||
lastMayRetainStreamFlags = mayRetainStreamFlags
|
||||
|
||||
val childStreams = arrayOfNulls<SampleStream>(streams.size)
|
||||
streams.forEachIndexed { i, stream ->
|
||||
childStreams[i] = (stream as? OffsetSampleStream)?.childStream
|
||||
}
|
||||
|
||||
val startPos =
|
||||
currentPeriod.selectTracks(
|
||||
selections,
|
||||
mayRetainStreamFlags,
|
||||
childStreams,
|
||||
streamResetFlags,
|
||||
positionUs - currentOffsetUs
|
||||
)
|
||||
|
||||
val newWrappers = arrayOfNulls<OffsetSampleStream>(streams.size)
|
||||
for (i in streams.indices) {
|
||||
val child = childStreams[i]
|
||||
if (child == null) {
|
||||
streams[i] = null
|
||||
} else {
|
||||
val existingWrapper = streams[i] as? OffsetSampleStream
|
||||
if (existingWrapper != null && existingWrapper.childStream === child) {
|
||||
newWrappers[i] = existingWrapper
|
||||
} else {
|
||||
val wrapper = OffsetSampleStream(child)
|
||||
newWrappers[i] = wrapper
|
||||
streams[i] = wrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
activeWrappers = newWrappers
|
||||
|
||||
return startPos + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun discardBuffer(positionUs: Long, toKeyframe: Boolean) {
|
||||
if (!isReloading) {
|
||||
currentPeriod.discardBuffer(positionUs - currentOffsetUs, toKeyframe)
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDiscontinuity(): Long {
|
||||
if (isReloading) return C.TIME_UNSET
|
||||
val discontinuity = currentPeriod.readDiscontinuity()
|
||||
return if (discontinuity == C.TIME_UNSET) C.TIME_UNSET
|
||||
else discontinuity + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun seekToUs(positionUs: Long): Long {
|
||||
if (positionUs == 0L && currentOffsetUs == 0L) {
|
||||
return currentPeriod.seekToUs(positionUs)
|
||||
}
|
||||
|
||||
reloadSource(positionUs)
|
||||
return positionUs
|
||||
}
|
||||
|
||||
override fun getAdjustedSeekPositionUs(positionUs: Long, seekParameters: SeekParameters) =
|
||||
positionUs
|
||||
|
||||
override fun getBufferedPositionUs(): Long {
|
||||
if (isReloading) return currentOffsetUs
|
||||
val buffered = currentPeriod.bufferedPositionUs
|
||||
if (buffered == C.TIME_END_OF_SOURCE) return C.TIME_END_OF_SOURCE
|
||||
return if (buffered == C.TIME_UNSET) C.TIME_UNSET else buffered + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun getNextLoadPositionUs(): Long {
|
||||
if (isReloading) return C.TIME_UNSET
|
||||
val next = currentPeriod.nextLoadPositionUs
|
||||
if (next == C.TIME_END_OF_SOURCE) return C.TIME_END_OF_SOURCE
|
||||
return if (next == C.TIME_UNSET) C.TIME_UNSET else next + currentOffsetUs
|
||||
}
|
||||
|
||||
override fun reevaluateBuffer(positionUs: Long) {
|
||||
if (!isReloading) currentPeriod.reevaluateBuffer(positionUs - currentOffsetUs)
|
||||
}
|
||||
|
||||
override fun continueLoading(isLoading: LoadingInfo): Boolean {
|
||||
if (isReloading) return false
|
||||
val builder = isLoading.buildUpon()
|
||||
builder.setPlaybackPositionUs(isLoading.playbackPositionUs - currentOffsetUs)
|
||||
return currentPeriod.continueLoading(builder.build())
|
||||
}
|
||||
|
||||
override fun isLoading() = isReloading || currentPeriod.isLoading
|
||||
|
||||
override fun onPrepared(mediaPeriod: MediaPeriod) {
|
||||
if (isReloading && mediaPeriod == currentPeriod) {
|
||||
isReloading = false
|
||||
restoreTracks()
|
||||
localCallback?.onContinueLoadingRequested(this)
|
||||
} else {
|
||||
localCallback?.onPrepared(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onContinueLoadingRequested(source: MediaPeriod) {
|
||||
if (!isReloading) localCallback?.onContinueLoadingRequested(this)
|
||||
}
|
||||
|
||||
private fun reloadSource(positionUs: Long) {
|
||||
isReloading = true
|
||||
currentOffsetUs = positionUs
|
||||
|
||||
activeWrappers.forEach { it?.childStream = null }
|
||||
|
||||
source.releasePeriod(currentPeriod)
|
||||
releaseChildSource(null)
|
||||
|
||||
val seconds = Util.usToMs(positionUs) / 1000
|
||||
val newUri = MusicUtil.getStreamUri(mediaItem.mediaId, seconds.toInt())
|
||||
val newMediaItem = mediaItem.buildUpon().setUri(newUri).build()
|
||||
|
||||
val newSource = progressiveMediaSourceFactory.createMediaSource(newMediaItem)
|
||||
|
||||
source = newSource
|
||||
currentSource = newSource
|
||||
prepareChildSource(null, newSource)
|
||||
|
||||
val newPeriod = newSource.createPeriod(id, allocator, 0)
|
||||
currentPeriod = newPeriod
|
||||
newPeriod.prepare(this, 0)
|
||||
}
|
||||
|
||||
private fun restoreTracks() {
|
||||
val selections = lastSelections ?: return
|
||||
val flags = lastMayRetainStreamFlags ?: return
|
||||
|
||||
val childStreams = arrayOfNulls<SampleStream>(activeWrappers.size)
|
||||
val streamResetFlags = BooleanArray(activeWrappers.size)
|
||||
|
||||
currentPeriod.selectTracks(selections, flags, childStreams, streamResetFlags, 0)
|
||||
|
||||
for (i in activeWrappers.indices) {
|
||||
activeWrappers[i]?.childStream = childStreams[i]
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OffsetSampleStream(var childStream: SampleStream?) : SampleStream {
|
||||
override fun isReady() = childStream?.isReady ?: false
|
||||
override fun maybeThrowError() {
|
||||
childStream?.maybeThrowError()
|
||||
}
|
||||
|
||||
override fun readData(
|
||||
formatHolder: FormatHolder,
|
||||
buffer: DecoderInputBuffer,
|
||||
readFlags: Int
|
||||
): Int {
|
||||
val stream = childStream ?: return C.RESULT_NOTHING_READ
|
||||
val result = stream.readData(formatHolder, buffer, readFlags)
|
||||
if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream) {
|
||||
buffer.timeUs += currentOffsetUs
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun skipData(positionUs: Long) =
|
||||
childStream?.skipData(positionUs - currentOffsetUs) ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
private class DurationOverridingTimeline(timeline: Timeline, private val durationUs: Long) :
|
||||
ForwardingTimeline(timeline) {
|
||||
|
||||
override fun getWindow(
|
||||
windowIndex: Int,
|
||||
window: Window,
|
||||
defaultPositionProjectionUs: Long
|
||||
): Window {
|
||||
super.getWindow(windowIndex, window, defaultPositionProjectionUs)
|
||||
window.durationUs = durationUs
|
||||
window.isSeekable = true
|
||||
window.isDynamic = false
|
||||
window.liveConfiguration = null
|
||||
return window
|
||||
}
|
||||
|
||||
override fun getPeriod(periodIndex: Int, period: Period, setIds: Boolean): Period {
|
||||
super.getPeriod(periodIndex, period, setIds)
|
||||
period.durationUs = durationUs
|
||||
return period
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
@@ -24,6 +27,7 @@ import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -33,8 +37,8 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private final SharingRepository sharingRepository;
|
||||
|
||||
private AlbumID3 album;
|
||||
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
|
||||
|
||||
public AlbumBottomSheetViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
@@ -116,6 +120,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
|
||||
|
||||
tracksLiveData.observeForever(new Observer<List<Child>>() {
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@Override
|
||||
public void onChanged(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
@@ -129,4 +134,12 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getAlbumInstantMix(LifecycleOwner owner, AlbumID3 album) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
albumRepository.getInstantMix(album, 30).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
@@ -21,7 +20,6 @@ import java.util.List;
|
||||
|
||||
public class AlbumListPageViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final DownloadRepository downloadRepository;
|
||||
|
||||
public String title;
|
||||
public ArtistID3 artist;
|
||||
@@ -32,9 +30,7 @@ public class AlbumListPageViewModel extends AndroidViewModel {
|
||||
|
||||
public AlbumListPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
albumRepository = new AlbumRepository();
|
||||
downloadRepository = new DownloadRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<AlbumID3>> getAlbumList(LifecycleOwner owner) {
|
||||
|
||||
@@ -8,18 +8,23 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumPageViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private String albumId;
|
||||
private String artistId;
|
||||
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
|
||||
@@ -29,6 +34,7 @@ public class AlbumPageViewModel extends AndroidViewModel {
|
||||
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getAlbumSongLiveList() {
|
||||
@@ -49,6 +55,61 @@ public class AlbumPageViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public void setFavorite() {
|
||||
AlbumID3 currentAlbum = album.getValue();
|
||||
if (currentAlbum == null) return;
|
||||
|
||||
if (currentAlbum.getStarred() != null) {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline(currentAlbum);
|
||||
} else {
|
||||
removeFavoriteOnline(currentAlbum);
|
||||
}
|
||||
} else {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
setFavoriteOffline(currentAlbum);
|
||||
} else {
|
||||
setFavoriteOnline(currentAlbum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFavoriteOffline(AlbumID3 album) {
|
||||
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||
album.setStarred(null);
|
||||
this.album.postValue(album);
|
||||
}
|
||||
|
||||
private void removeFavoriteOnline(AlbumID3 album) {
|
||||
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||
}
|
||||
});
|
||||
|
||||
album.setStarred(null);
|
||||
this.album.postValue(album);
|
||||
}
|
||||
|
||||
private void setFavoriteOffline(AlbumID3 album) {
|
||||
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||
album.setStarred(new Date());
|
||||
this.album.postValue(album);
|
||||
}
|
||||
|
||||
private void setFavoriteOnline(AlbumID3 album) {
|
||||
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||
}
|
||||
});
|
||||
|
||||
album.setStarred(new Date());
|
||||
this.album.postValue(album);
|
||||
}
|
||||
|
||||
public LiveData<ArtistID3> getArtist() {
|
||||
return artistRepository.getArtistInfo(artistId);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
@@ -17,6 +22,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.List;
|
||||
@@ -24,6 +30,7 @@ import java.util.List;
|
||||
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
|
||||
|
||||
private ArtistID3 artist;
|
||||
|
||||
@@ -95,6 +102,7 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||
Log.d("ArtistSync", "Starting artist sync for: " + artist.getName());
|
||||
|
||||
artistRepository.getArtistAllSongs(artist.getId(), new ArtistRepository.ArtistSongsCallback() {
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@Override
|
||||
public void onSongsCollected(List<Child> songs) {
|
||||
Log.d("ArtistSync", "Callback triggered with songs: " + (songs != null ? songs.size() : 0));
|
||||
@@ -114,5 +122,12 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||
Log.d("ArtistSync", "Artist sync preference is disabled");
|
||||
}
|
||||
}
|
||||
///
|
||||
|
||||
public LiveData<List<Child>> getArtistInstantMix(LifecycleOwner owner, ArtistID3 artist) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
artistRepository.getInstantMix(artist, 30).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
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 ArtistPageViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private ArtistID3 artist;
|
||||
|
||||
@@ -26,6 +40,7 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
||||
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<AlbumID3>> getAlbumList() {
|
||||
@@ -45,7 +60,7 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getArtistInstantMix() {
|
||||
return artistRepository.getInstantMix(artist, 20);
|
||||
return artistRepository.getInstantMix(artist, 30);
|
||||
}
|
||||
|
||||
public ArtistID3 getArtist() {
|
||||
@@ -55,4 +70,70 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
||||
public void setArtist(ArtistID3 artist) {
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public void setFavorite(Context context) {
|
||||
if (artist.getStarred() != null) {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline();
|
||||
} else {
|
||||
removeFavoriteOnline();
|
||||
}
|
||||
} else {
|
||||
if (NetworkUtil.isOffline()) {
|
||||
setFavoriteOffline();
|
||||
} else {
|
||||
setFavoriteOnline(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFavoriteOffline() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||
artist.setStarred(null);
|
||||
}
|
||||
|
||||
private void removeFavoriteOnline() {
|
||||
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||
}
|
||||
});
|
||||
|
||||
artist.setStarred(null);
|
||||
}
|
||||
|
||||
private void setFavoriteOffline() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||
artist.setStarred(new Date());
|
||||
}
|
||||
|
||||
private void setFavoriteOnline(Context context) {
|
||||
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||
}
|
||||
});
|
||||
|
||||
artist.setStarred(new Date());
|
||||
|
||||
if (Preferences.isStarredArtistsSyncEnabled()) {
|
||||
artistRepository.getArtistAllSongs(artist.getId(), new ArtistRepository.ArtistSongsCallback() {
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@Override
|
||||
public void onSongsCollected(List<Child> songs) {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
DownloadUtil.getDownloadTracker(context).download(
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d("ArtistSync", "Artist sync preference is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
@@ -34,7 +36,6 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HomeViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "HomeViewModel";
|
||||
@@ -100,7 +101,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getRandomShuffleSample() {
|
||||
return songRepository.getRandomSample(1000, null, null);
|
||||
return songRepository.getRandomSample(100, null, null);
|
||||
}
|
||||
|
||||
public LiveData<List<Chronology>> getChronologySample(LifecycleOwner owner) {
|
||||
@@ -223,7 +224,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
mediaInstantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, mediaInstantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, mediaInstantMix::postValue);
|
||||
|
||||
return mediaInstantMix;
|
||||
}
|
||||
@@ -248,15 +249,22 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
pinnedPlaylists.setValue(Collections.emptyList());
|
||||
|
||||
playlistRepository.getPlaylists(false, -1).observe(owner, remotes -> {
|
||||
playlistRepository.getPinnedPlaylists().observe(owner, locals -> {
|
||||
if (remotes != null && locals != null) {
|
||||
List<Playlist> toReturn = remotes.stream()
|
||||
.filter(remote -> locals.stream().anyMatch(local -> local.getId().equals(remote.getId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
pinnedPlaylists.setValue(toReturn);
|
||||
if (remotes != null && !remotes.isEmpty()) {
|
||||
List<Playlist> playlists = new ArrayList<>(remotes);
|
||||
String result = Preferences.getHomeSortPlaylists();
|
||||
if (Preferences.getHomeSortPlaylists().equals(Constants.PLAYLIST_ORDER_BY_RANDOM))
|
||||
{
|
||||
Collections.shuffle(playlists);
|
||||
}
|
||||
});
|
||||
else {
|
||||
playlists.sort(Comparator.comparing(Playlist::getName));
|
||||
}
|
||||
List<Playlist> subsetPlaylists = playlists.size() > 5
|
||||
? playlists.subList(0, 5)
|
||||
: playlists;
|
||||
|
||||
pinnedPlaylists.setValue(subsetPlaylists);
|
||||
}
|
||||
});
|
||||
|
||||
return pinnedPlaylists;
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.viewmodel;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
@@ -276,7 +277,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), Constants.SeedType.TRACK, 20).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
@@ -291,13 +292,13 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
List<String> ids = queue.stream().map(Child::getId).collect(Collectors.toList());
|
||||
|
||||
if (media != null) {
|
||||
queueRepository.savePlayQueue(ids, media.getId(), 0);
|
||||
// TODO: We need to get the actual playback position here
|
||||
Log.d(TAG, "Saving play queue - Current: " + media.getId() + ", Items: " + ids.size());
|
||||
queueRepository.savePlayQueue(ids, media.getId(), 0); // Still hardcoded to 0 for now
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void observeCachedLyrics(LifecycleOwner owner, String songId) {
|
||||
if (TextUtils.isEmpty(songId)) {
|
||||
return;
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class PodcastChannelBottomSheetViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "PodcastChannelBottomSheetViewModel";
|
||||
private final PodcastRepository podcastRepository;
|
||||
|
||||
private PodcastChannel podcastChannel;
|
||||
@@ -28,6 +38,59 @@ public class PodcastChannelBottomSheetViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public void deletePodcastChannel() {
|
||||
if (podcastChannel != null) podcastRepository.deletePodcastChannel(podcastChannel.getId());
|
||||
if (podcastChannel != null && podcastChannel.getId() != null) {
|
||||
podcastRepository.deletePodcastChannel(podcastChannel.getId())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.code() == 501) {
|
||||
Toast.makeText(getApplication(),
|
||||
"Podcasts are not supported by this server",
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse apiResponse = response.body();
|
||||
|
||||
String status = apiResponse.subsonicResponse.getStatus();
|
||||
|
||||
if ("ok".equals(status)) {
|
||||
Toast.makeText(getApplication(),
|
||||
"Podcast channel deleted",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
//TODO refresh the UI after deleting
|
||||
//podcastRepository.refreshPodcasts();
|
||||
}
|
||||
} else {
|
||||
handleHttpError(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Toast.makeText(getApplication(),
|
||||
"Network error: " + t.getMessage(),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleHttpError(Response<ApiResponse> response) {
|
||||
String errorMsg = "HTTP error: " + response.code();
|
||||
if (response.errorBody() != null) {
|
||||
try {
|
||||
String serverMsg = response.errorBody().string();
|
||||
if (!serverMsg.isEmpty()) {
|
||||
errorMsg += " - " + serverMsg;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading error body", e);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.makeText(getApplication(), errorMsg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,27 +1,99 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class PodcastChannelEditorViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "RadioEditorViewModel";
|
||||
private static final String TAG = "PodcastChannelEditorViewModel";
|
||||
|
||||
private final PodcastRepository podcastRepository;
|
||||
|
||||
private InternetRadioStation toEdit;
|
||||
private final MutableLiveData<Boolean> isSuccess = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||
|
||||
public PodcastChannelEditorViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
podcastRepository = new PodcastRepository();
|
||||
}
|
||||
|
||||
public void createChannel(String url) {
|
||||
podcastRepository.createPodcastChannel(url);
|
||||
public LiveData<Boolean> getIsSuccess() {
|
||||
return isSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<String> getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void clearError() {
|
||||
errorMessage.setValue(null);
|
||||
}
|
||||
|
||||
public void createChannel(String url) {
|
||||
errorMessage.setValue(null);
|
||||
isSuccess.setValue(false);
|
||||
|
||||
podcastRepository.createPodcastChannel(url)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.code() == 501) {
|
||||
showError(getApplication().getString(R.string.podcast_channel_not_supported_snackbar));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse apiResponse = response.body();
|
||||
|
||||
String status = apiResponse.subsonicResponse.getStatus();
|
||||
if ("ok".equals(status)) {
|
||||
isSuccess.setValue(true);
|
||||
}
|
||||
} else {
|
||||
handleHttpError(response);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
showError("Network error: " + t.getMessage());
|
||||
Log.e(TAG, "Network error", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleHttpError(Response<ApiResponse> response) {
|
||||
String errorMsg = "HTTP error: " + response.code();
|
||||
if (response.errorBody() != null) {
|
||||
try {
|
||||
String serverMsg = response.errorBody().string();
|
||||
if (!serverMsg.isEmpty()) {
|
||||
errorMsg += " - " + serverMsg;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading error body", e);
|
||||
}
|
||||
}
|
||||
showError(errorMsg);
|
||||
}
|
||||
|
||||
private void showError(String message) {
|
||||
Toast.makeText(getApplication(), message, Toast.LENGTH_LONG).show();
|
||||
errorMessage.setValue(message);
|
||||
Log.e(TAG, "Error shown: " + message);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,47 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.repository.RadioRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class RadioEditorViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "RadioEditorViewModel";
|
||||
|
||||
private final RadioRepository radioRepository;
|
||||
|
||||
private InternetRadioStation toEdit;
|
||||
|
||||
private final MutableLiveData<Boolean> isSuccess = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||
|
||||
public RadioEditorViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
radioRepository = new RadioRepository();
|
||||
}
|
||||
|
||||
|
||||
public LiveData<Boolean> getIsSuccess() { return isSuccess; }
|
||||
public LiveData<String> getErrorMessage() { return errorMessage; }
|
||||
|
||||
public void clearError() {
|
||||
errorMessage.setValue(null);
|
||||
}
|
||||
|
||||
public InternetRadioStation getRadioToEdit() {
|
||||
return toEdit;
|
||||
}
|
||||
@@ -30,14 +51,120 @@ public class RadioEditorViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public void createRadio(String name, String streamURL, String homepageURL) {
|
||||
radioRepository.createInternetRadioStation(name, streamURL, homepageURL);
|
||||
errorMessage.setValue(null);
|
||||
isSuccess.setValue(false);
|
||||
|
||||
radioRepository.createInternetRadioStation(name, streamURL, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
// Handle HTTP 501 (Not Implemented) from Navidrome
|
||||
if (response.code() == 501) {
|
||||
showError(getApplication().getString(R.string.radio_dialog_not_supported_snackbar));
|
||||
return;
|
||||
}
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse apiResponse = response.body();
|
||||
String status = apiResponse.subsonicResponse.getStatus();
|
||||
if ("ok".equals(status)) {
|
||||
isSuccess.setValue(true);
|
||||
} else if ("failed".equals(status)) {
|
||||
handleFailedResponse(apiResponse);
|
||||
}
|
||||
} else {
|
||||
errorMessage.setValue("HTTP error: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
errorMessage.setValue("Network error: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateRadio(String name, String streamURL, String homepageURL) {
|
||||
if (toEdit != null) radioRepository.updateInternetRadioStation(toEdit.getId(), name, streamURL, homepageURL);
|
||||
if (toEdit != null && toEdit.getId() != null) {
|
||||
errorMessage.setValue(null);
|
||||
isSuccess.setValue(false);
|
||||
|
||||
radioRepository.updateInternetRadioStation(toEdit.getId(), name, streamURL, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse apiResponse = response.body();
|
||||
if (apiResponse.subsonicResponse != null) {
|
||||
String status = apiResponse.subsonicResponse.getStatus();
|
||||
if ("ok".equals(status)) {
|
||||
isSuccess.setValue(true);
|
||||
} else if ("failed".equals(status)) {
|
||||
handleFailedResponse(apiResponse);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorMessage.setValue("HTTP error: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
errorMessage.setValue("Network error: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteRadio() {
|
||||
if (toEdit != null) radioRepository.deleteInternetRadioStation(toEdit.getId());
|
||||
if (toEdit != null && toEdit.getId() != null) {
|
||||
errorMessage.setValue(null);
|
||||
isSuccess.setValue(false);
|
||||
|
||||
radioRepository.deleteInternetRadioStation(toEdit.getId())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
ApiResponse apiResponse = response.body();
|
||||
|
||||
String status = apiResponse.subsonicResponse.getStatus();
|
||||
|
||||
if ("ok".equals(status)) {
|
||||
isSuccess.setValue(true);
|
||||
} else if ("failed".equals(status)) {
|
||||
handleFailedResponse(apiResponse);
|
||||
}
|
||||
} else {
|
||||
errorMessage.setValue("HTTP error: " + response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
errorMessage.setValue("Network error: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(String message) {
|
||||
Toast.makeText(getApplication(), message, Toast.LENGTH_LONG).show();
|
||||
errorMessage.setValue(message);
|
||||
}
|
||||
|
||||
private void handleFailedResponse(ApiResponse apiResponse) {
|
||||
String errorMsg = "Unknown server error";
|
||||
|
||||
if (apiResponse.subsonicResponse.getError() != null) {
|
||||
errorMsg = apiResponse.subsonicResponse.getError().getMessage();
|
||||
|
||||
if ("Not implemented".equals(errorMsg)) {
|
||||
errorMsg = getApplication().getString((R.string.radio_dialog_not_supported_snackbar));
|
||||
}
|
||||
}
|
||||
|
||||
Toast.makeText(getApplication(), errorMsg, Toast.LENGTH_LONG).show();
|
||||
|
||||
errorMessage.setValue(errorMsg);
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,11 @@ public class SearchViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public void insertNewSearch(String search) {
|
||||
searchingRepository.insert(new RecentSearch(search));
|
||||
searchingRepository.insert(new RecentSearch(search, System.currentTimeMillis() / 1000L));
|
||||
}
|
||||
|
||||
public void deleteRecentSearch(String search) {
|
||||
searchingRepository.delete(new RecentSearch(search));
|
||||
searchingRepository.delete(new RecentSearch(search, 0));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getSearchSuggestion(String query) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
@@ -21,6 +22,7 @@ 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.Constants.SeedType;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
@@ -128,11 +130,22 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 30).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
public void getInstantMix(Child media, int count, MediaCallback callback) {
|
||||
|
||||
songRepository.getInstantMix(media.getId(), SeedType.TRACK, count, songs -> {
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
callback.onLoadMedia(songs);
|
||||
} else {
|
||||
callback.onLoadMedia(Collections.emptyList());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<Share> shareTrack() {
|
||||
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class SongListPageViewModel extends AndroidViewModel {
|
||||
|
||||
public int year = 0;
|
||||
public int maxNumberByYear = 500;
|
||||
public int maxNumberByGenre = 100;
|
||||
public int maxNumberByGenre = 500;
|
||||
|
||||
public SongListPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
@@ -51,7 +51,7 @@ public class SongListPageViewModel extends AndroidViewModel {
|
||||
|
||||
switch (title) {
|
||||
case Constants.MEDIA_BY_GENRE:
|
||||
songList = songRepository.getSongsByGenre(genre.getGenre(), 0);
|
||||
songList = songRepository.getRandomSampleWithGenre(maxNumberByGenre, 0, 3000, genre.getGenre());
|
||||
break;
|
||||
case Constants.MEDIA_BY_ARTIST:
|
||||
songList = artistRepository.getTopSongs(artist.getName(), 50);
|
||||
|
||||
@@ -174,7 +174,6 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
@@ -188,43 +187,69 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_play_button"
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_play_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/button_favorite"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/button_favorite_selector"
|
||||
android:checked="false"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textOff=""
|
||||
android:textOn="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
@@ -239,7 +264,8 @@
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_page_button_layout" />
|
||||
app:layout_constraintTop_toBottomOf="@id/album_page_button_layout"
|
||||
tools:ignore="NotSibling" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_button_divider"
|
||||
@@ -249,7 +275,7 @@
|
||||
android:layout_marginBottom="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_page_button_layout" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_bio_label" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
@@ -63,40 +63,80 @@
|
||||
android:layout_marginEnd="18dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp">
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/artist_page_shuffle_button"
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/artist_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/artist_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/artist_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/artist_page_radio_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/artist_page_radio_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_feed"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/button_favorite"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/button_favorite_selector"
|
||||
android:checked="false"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textOff=""
|
||||
android:textOn="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/artist_page_radio_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/artist_page_radio_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_feed"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
android:id="@+id/button_toggle_bio"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/ic_info_stream"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textOff=""
|
||||
android:textOn="" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
||||
@@ -379,16 +379,6 @@
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Best of -->
|
||||
<LinearLayout
|
||||
android:id="@+id/home_best_of_artist_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/most_streamed_song_pre_text_view"
|
||||
@@ -400,6 +390,16 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_subtitle_best_of"
|
||||
android:textAllCaps="true" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Best of -->
|
||||
<LinearLayout
|
||||
android:id="@+id/home_best_of_artist_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/best_of_artist_text_view_refreshable"
|
||||
@@ -566,6 +566,7 @@
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!--Starred Albums-->
|
||||
<LinearLayout
|
||||
android:id="@+id/starred_albums_sector"
|
||||
android:layout_width="match_parent"
|
||||
@@ -615,6 +616,7 @@
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!--Starred Artists-->
|
||||
<LinearLayout
|
||||
android:id="@+id/starred_artists_sector"
|
||||
android:layout_width="match_parent"
|
||||
@@ -913,16 +915,36 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Label and button -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pinned_playlists_text_view"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/home_title_pinned_playlists" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_catalogue_text_view_clickable"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/library_title_playlist_see_all_button" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/pinned_playlists_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -20,6 +20,20 @@
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/podcast_channels_pre_text_view"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_subtitle_new_podcast_channel"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_podcast_channels_sector"
|
||||
android:layout_width="match_parent"
|
||||
@@ -29,17 +43,6 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/podcast_channels_pre_text_view"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_subtitle_new_podcast_channel"
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<!-- Label and button -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -169,4 +172,4 @@
|
||||
android:gravity="center"
|
||||
android:text="@string/podcast_info_empty_button" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,44 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/internet_radio_station_pre_text_view"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_subtitle_new_internet_radio_station"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/internet_radio_station_title_text_view"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_title_internet_radio_station"
|
||||
app:layout_constraintTop_toBottomOf="@id/internet_radio_station_pre_text_view"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/home_radio_station_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toBottomOf="@id/internet_radio_station_title_text_view">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/internet_radio_station_pre_text_view"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_subtitle_new_internet_radio_station"
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/internet_radio_station_title_text_view"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_title_internet_radio_station" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/internet_radio_station_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
@@ -61,7 +71,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toBottomOf="@id/internet_radio_station_title_text_view">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/empty_description_image_view"
|
||||
@@ -105,7 +115,4 @@
|
||||
android:gravity="center"
|
||||
android:text="@string/radio_station_info_empty_button" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -16,6 +16,23 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/player_playback_speed_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:cornerRadius="30dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:tint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/player_media_extension"
|
||||
style="@style/Widget.Material3.Chip.Suggestion"
|
||||
@@ -253,23 +270,6 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/placeholder_view_middle_right"
|
||||
app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_right" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/player_playback_speed_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:cornerRadius="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/placeholder_view_middle_left"
|
||||
app:layout_constraintEnd_toStartOf="@+id/placeholder_view_middle_left"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/placeholder_view_middle_left"
|
||||
app:tint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exo_shuffle"
|
||||
android:layout_width="32dp"
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_clean_queue_button"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/player_queue_clean_all_button" />
|
||||
|
||||
<com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@@ -21,20 +14,74 @@
|
||||
android:id="@+id/player_queue_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="40dp"
|
||||
android:paddingTop="8dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</com.cappielloantonio.tempo.helper.recyclerview.NestedScrollableHost>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/player_shuffle_queue_fab"
|
||||
<LinearLayout
|
||||
android:id="@+id/fab_menu_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/content_description_shuffle_button"
|
||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
|
||||
app:srcCompat="@drawable/ic_shuffle" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fab_save_to_playlist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/player_queue_save_to_playlist"
|
||||
app:icon="@android:drawable/ic_menu_edit" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fab_clear_queue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/player_queue_clean_all_button"
|
||||
app:icon="@android:drawable/ic_menu_delete" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fab_download_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/menu_download_all_button"
|
||||
app:icon="@android:drawable/stat_sys_download_done" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fab_load_queue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/player_queue_load_queue"
|
||||
app:icon="@android:drawable/ic_menu_revert" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fab_shuffle_queue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/content_description_shuffle_button"
|
||||
app:icon="@drawable/ic_shuffle" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_menu_toggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="Toggle FAB Action menu"
|
||||
tools:ignore="HardcodedText"
|
||||
app:srcCompat="@drawable/ic_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -7,7 +7,10 @@
|
||||
<ImageView
|
||||
android:id="@+id/discover_song_cover_image_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="196dp"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:background="?attr/colorSurfaceContainerHighest"
|
||||
android:foreground="@drawable/gradient_discover_background_image" />
|
||||
|
||||
|
||||
@@ -19,12 +19,15 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_image_separator"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="52dp"
|
||||
<ImageView
|
||||
android:id="@+id/music_directory_play_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@drawable/ic_play"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/music_directory_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_directory_title_text_view"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_directory_cover_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||
|
||||
@@ -33,13 +36,14 @@
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_directory_more_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_directory_play_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||
|
||||
<ImageView
|
||||
@@ -54,17 +58,4 @@
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_title_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_title_text_view" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_directory_play_button"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/ic_play"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_title_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_title_text_view" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -20,12 +20,14 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_image_separator"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="52dp"
|
||||
<ImageView
|
||||
android:id="@+id/music_index_play_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@drawable/ic_play"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/music_index_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_index_title_text_view"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_index_cover_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||
|
||||
@@ -34,13 +36,14 @@
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_index_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_index_more_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_index_play_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||
|
||||
<ImageView
|
||||
|
||||
@@ -139,6 +139,17 @@
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_indicator_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_download" app:layout_constraintBottom_toBottomOf="@+id/queue_song_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"
|
||||
app:layout_constraintTop_toTopOf="@+id/queue_song_cover_image_view"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/queue_song_holder_image"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_playlistPageFragment"
|
||||
app:destination="@id/playlistPageFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_playlistCatalogueFragment"
|
||||
app:destination="@id/playlistCatalogueFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_podcastChannelCatalogueFragment"
|
||||
app:destination="@id/podcastChannelCatalogueFragment" />
|
||||
|
||||
258
app/src/main/res/values-ca/arrays.xml
Normal file
258
app/src/main/res/values-ca/arrays.xml
Normal file
@@ -0,0 +1,258 @@
|
||||
<?xml version="1.0"?>
|
||||
<resources>
|
||||
<string-array name="theme_list_titles">
|
||||
<item>Clar</item>
|
||||
<item>Fosc</item>
|
||||
<item>Valor per defecte del sistema</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>Alta</item>
|
||||
<item>Mitjana</item>
|
||||
<item>Baixa</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>Alta</item>
|
||||
<item>Mitjana</item>
|
||||
<item>Baixa</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>Inhabilitada</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>
|
||||
<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>Original</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>Original</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>Reproducció directa</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>Reproducció directa</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>Baixada directa</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>10 segons</item>
|
||||
<item>5 segons</item>
|
||||
<item>2 segons</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>Alta</item>
|
||||
<item>Mitjana</item>
|
||||
<item>Baixa</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>Inhabilitat</item>
|
||||
<item>Pista</item>
|
||||
<item>Àlbum</item>
|
||||
<item>Automàtic</item>
|
||||
</string-array>
|
||||
<string-array name="replay_gain_values">
|
||||
<item>disabled</item>
|
||||
<item>track</item>
|
||||
<item>album</item>
|
||||
<item>auto</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transcoded_download_option_list_titles">
|
||||
<item>Sense transcodificació</item>
|
||||
<item>Paràmetres del servidor</item>
|
||||
<item>Format de transcodificació amb wifi</item>
|
||||
<item>Format de transcodificació amb dades mòbils</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>Mínima</item>
|
||||
<item>Moderada</item>
|
||||
<item>Agressiva</item>
|
||||
<item>Extrema</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>Mínim 0 estrelles</item>
|
||||
<item>Mínim 1 estrella</item>
|
||||
<item>Mínim 2 estrelles</item>
|
||||
<item>Mínim 3 estrelles</item>
|
||||
<item>Mínim 4 estrelles</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>
|
||||
537
app/src/main/res/values-ca/strings.xml
Normal file
537
app/src/main/res/values-ca/strings.xml
Normal file
@@ -0,0 +1,537 @@
|
||||
<?xml version="1.0"?>
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_conclusion">Si teniu problemes, visiteu https://dontkillmyapp.com. S\'hi proporcionen instruccions detallades sobre com inhabilitar qualsevol característica d\'estalvi de bateria que pugui afectar el rendiment de l\'aplicació.</string>
|
||||
<string name="activity_battery_optimizations_summary">Inhabiliteu les optimitzacions de la bateria per a la reproducció multimèdia mentre la pantalla està apagada.</string>
|
||||
<string name="activity_battery_optimizations_title">Optimitzacions de la bateria</string>
|
||||
<string name="activity_info_offline_mode">Mode fora de línia</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Afegeix a la llista de reproducció</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Afegeix a la cua</string>
|
||||
<string name="album_bottom_sheet_download_all">Baixa-ho tot</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Ves a l\'artista</string>
|
||||
<string name="album_bottom_sheet_instant_mix">Mescla instantània</string>
|
||||
<string name="album_bottom_sheet_play_next">Reprodueix a continuació</string>
|
||||
<string name="album_bottom_sheet_remove_all">Suprimeix-ho tot</string>
|
||||
<string name="album_bottom_sheet_share">Comparteix</string>
|
||||
<string name="album_bottom_sheet_shuffle">Reprodueix aleatòriament</string>
|
||||
<string name="album_catalogue_title">Àlbums</string>
|
||||
<string name="album_catalogue_title_expanded">Exploració d\'àlbums</string>
|
||||
<string name="album_error_retrieving_artist">S\'ha produït un error en recuperar l\'artista</string>
|
||||
<string name="album_list_page_downloaded">Àlbums baixats</string>
|
||||
<string name="album_list_page_most_played">Àlbums més reproduïts</string>
|
||||
<string name="album_list_page_new_releases">Llançaments nous</string>
|
||||
<string name="album_list_page_recently_added">Àlbums afegits recentment</string>
|
||||
<string name="album_list_page_recently_played">Àlbums reproduïts recentment</string>
|
||||
<string name="album_list_page_starred">Àlbums amb estrelles</string>
|
||||
<string name="album_list_page_title">Àlbums</string>
|
||||
<string name="album_page_extra_info_button">Més com això</string>
|
||||
<string name="album_page_play_button">Reprodueix</string>
|
||||
<string name="album_page_release_date_label">Publicació: %1$s</string>
|
||||
<string name="album_page_release_dates_label">Publicació: %1$s, originalment: %2$s</string>
|
||||
<string name="album_page_shuffle_button">Reprodueix aleatòriament</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d cançons • %2$d minuts</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="artist_adapter_radio_station_starting">S\'està cercant...</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Mescla instantània</string>
|
||||
<string name="artist_bottom_sheet_shuffle">Reprodueix aleatòriament</string>
|
||||
<string name="artist_catalogue_title">Artistes</string>
|
||||
<string name="artist_catalogue_title_expanded">Exploració d\'artistes</string>
|
||||
<string name="artist_error_retrieving_radio">S\'ha produït un error en recuperar la ràdio de l\'artista</string>
|
||||
<string name="artist_error_retrieving_tracks">S\'ha produït un error en recuperar les pistes de l\'artista</string>
|
||||
<string name="artist_list_page_downloaded">Artistes baixats</string>
|
||||
<string name="artist_list_page_starred">Artistes amb estrelles</string>
|
||||
<string name="artist_list_page_title">Artistes</string>
|
||||
<string name="artist_page_radio_button">Ràdio</string>
|
||||
<string name="artist_page_shuffle_button">Reprodueix aleatòriament</string>
|
||||
<string name="artist_page_switch_layout_button">Canvia de disposició</string>
|
||||
<string name="artist_page_title_album_more_like_this_button">Més com això</string>
|
||||
<string name="artist_page_title_album_section">Àlbums</string>
|
||||
<string name="artist_page_title_biography_more_button">Més</string>
|
||||
<string name="artist_page_title_biography_section">Biografia</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">Cançons més transmeses</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="battery_optimization_negative_button">Ignora</string>
|
||||
<string name="battery_optimization_neutral_button">No ho tornis a preguntar</string>
|
||||
<string name="battery_optimization_positive_button">Inhabilita</string>
|
||||
<string name="connection_alert_dialog_negative_button">Cancel·la</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Habilita l\'estalvi de dades</string>
|
||||
<string name="connection_alert_dialog_positive_button">D\'acord</string>
|
||||
<string name="connection_alert_dialog_summary">S\'ha restringit l\'accés al servidor del Subsonic en connexions que no són wifi. Per a impedir que torni a aparèixer aquesta alerta, inhabiliteu la comprovació de la connexió als paràmetres de l\'aplicació.</string>
|
||||
<string name="connection_alert_dialog_title">Xarxa wifi no connectada</string>
|
||||
<string name="content_description_shuffle_button">Reprodueix aleatòriament</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Cancel·la</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continua</string>
|
||||
<string name="delete_download_storage_dialog_summary">Tingueu en compte que, si continueu, se suprimiran permanentment tots els elements baixats de tots els servidors.</string>
|
||||
<string name="delete_download_storage_dialog_title">Suprimeix els elements desats</string>
|
||||
<string name="description_empty_title">No hi ha cap descripció disponible</string>
|
||||
<string name="disc_titlefull">Disc %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disc %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Cancel·la</string>
|
||||
<string name="download_directory_dialog_positive_button">Baixa</string>
|
||||
<string name="download_directory_dialog_summary">Es baixaran totes les pistes d\'aquesta carpeta. Les pistes en subcarpetes no es baixaran.</string>
|
||||
<string name="download_directory_dialog_title">Baixada de les pistes</string>
|
||||
<string name="download_directory_set">Defineix on es baixa la música</string>
|
||||
<string name="download_info_empty_subtitle">Quan baixeu una cançó, la trobareu aquí.</string>
|
||||
<string name="download_info_empty_title">Encara no hi ha cap baixada.</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elements</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s elements</string>
|
||||
<string name="download_shuffle_all_subtitle">Reprodueix-ho tot aleatòriament</string>
|
||||
<string name="download_storage_dialog_sub_summary">Perquè els canvis tinguin efecte, reinicieu l\'aplicació.</string>
|
||||
<string name="download_storage_dialog_summary">Si canvieu la destinació dels fitxers baixats d\'un emmagatzematge a un altre, se suprimiran immediatament tots els fitxers baixats anteriorment de l\'altre emmagatzematge.</string>
|
||||
<string name="download_storage_dialog_title">Selecció de l\'opció d\'emmagatzematge</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Extern</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Intern</string>
|
||||
<string name="download_storage_directory_dialog_neutral_button">Carpeta</string>
|
||||
<string name="download_title_section">Baixades</string>
|
||||
<string name="download_refresh_no_directory">Definiu una carpeta de baixades per a actualitzar les baixades.</string>
|
||||
<string name="download_refresh_no_changes">No s\'ha trobat cap baixada que falti.</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">S\'ha suprimit %d baixada que faltava.</item>
|
||||
<item quantity="other">S\'han suprimit %d baixades que faltaven.</item>
|
||||
</plurals>
|
||||
<string name="download_refresh_button_content_description">Actualitza els elements baixats</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Afegeix a la cua</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Reprodueix a continuació</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Suprimeix</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">Suprimeix-ho tot</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">Reprodueix aleatòriament</string>
|
||||
<string name="empty_string"/>
|
||||
<string name="error_required">Obligatori</string>
|
||||
<string name="error_server_prefix">El prefix «http» o «https» és obligatori</string>
|
||||
<string name="exo_download_notification_channel_name">Baixades</string>
|
||||
<string name="exo_controls_heart_off_description">Treu el cor</string>
|
||||
<string name="exo_controls_heart_on_description">Posa un cor</string>
|
||||
<string name="cast_expanded_controller_loading">S\'està carregant...</string>
|
||||
<string name="filter_info_selection">Seleccioneu dos o més filtres</string>
|
||||
<string name="filter_title">Filtre</string>
|
||||
<string name="filter_artist">Filtra per artistes</string>
|
||||
<string name="filter_title_expanded">Filtra per gèneres</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">Catàleg de gèneres</string>
|
||||
<string name="genre_catalogue_title_expanded">Exploració de gèneres</string>
|
||||
<string name="github_update_dialog_negative_button">Recorda-m\'ho més tard</string>
|
||||
<string name="github_update_dialog_neutral_button">Fes una aportació</string>
|
||||
<string name="github_update_dialog_positive_button">Baixa-ho ara</string>
|
||||
<string name="github_update_dialog_summary">Hi ha una versió nova de l\'aplicació disponible a Github.</string>
|
||||
<string name="github_update_dialog_title">Actualització disponible</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">Cancel·la</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Reinicialitza</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Desa</string>
|
||||
<string name="home_rearrangement_dialog_title">Reorganització de l\'inici</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Tingueu en compte que, perquè els canvis tinguin efecte, cal reiniciar l\'aplicació.</string>
|
||||
<string name="home_section_music">Música</string>
|
||||
<string name="home_section_podcast">Pòdcasts</string>
|
||||
<string name="home_section_radio">Ràdio</string>
|
||||
<string name="home_subtitle_best_of">Les millors cançons dels vostres artistes preferits</string>
|
||||
<string name="home_subtitle_made_for_you">Comenceu una mescla a partir d\'una cançó que us ha agradat</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Afegeix una ràdio nova</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Afegeix un canal de pòdcasts nou</string>
|
||||
<string name="home_sync_starred_cancel">Cancel·la</string>
|
||||
<string name="home_sync_starred_download">Baixa</string>
|
||||
<string name="home_sync_starred_subtitle">Baixar aquestes pistes pot suposar un ús de dades important</string>
|
||||
<string name="home_sync_starred_title">Sembla que hi ha pistes amb estrelles pendents de sincronitzar</string>
|
||||
<string name="home_sync_starred_albums_title">Sincronitza els àlbums amb estrelles</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Els àlbums marcats amb una estrella estaran disponibles fora de línia</string>
|
||||
<string name="home_sync_starred_artists_title">Sincronització dels artistes amb estrelles</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Teniu artistes amb estrella amb música sense baixar</string>
|
||||
<string name="home_title_best_of">El millor de</string>
|
||||
<string name="home_title_discovery">Descobriment</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Reprodueix-ho tot aleatòriament</string>
|
||||
<string name="home_title_flashback">Viatge al passat</string>
|
||||
<string name="home_title_internet_radio_station">Emissores de ràdio per Internet</string>
|
||||
<string name="home_title_last_played">Darreres reproduccions</string>
|
||||
<string name="home_title_last_played_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_last_week">La setmana passada</string>
|
||||
<string name="home_title_last_month">El mes passat</string>
|
||||
<string name="home_title_last_year">L\'any passat</string>
|
||||
<string name="home_title_made_for_you">Fet a mida</string>
|
||||
<string name="home_title_most_played">Més reproduccions</string>
|
||||
<string name="home_title_most_played_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_new_releases">Llançaments nous</string>
|
||||
<string name="home_title_newest_podcasts">Pòdcasts més recents</string>
|
||||
<string name="home_title_pinned_playlists">Llistes de reproducció</string>
|
||||
<string name="home_title_podcast_channels">Canals</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_radio_station">Emissores de ràdio</string>
|
||||
<string name="home_title_recently_added">Addicions recents</string>
|
||||
<string name="home_title_recently_added_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_shares">Elements compartits</string>
|
||||
<string name="home_title_starred_albums">★ Àlbums amb estrelles</string>
|
||||
<string name="home_title_starred_albums_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_starred_artists">★ Artistes amb estrelles</string>
|
||||
<string name="home_title_starred_artists_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_starred_tracks">★ Pistes amb estrelles</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="home_title_top_songs">Les vostres cançons més escoltades</string>
|
||||
<string name="home_option_reorganize">Reorganitza</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="library_title_album">Àlbums</string>
|
||||
<string name="library_title_album_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="library_title_artist">Artistes</string>
|
||||
<string name="library_title_artist_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="library_title_genre">Gèneres</string>
|
||||
<string name="library_title_genre_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="library_title_music_folder">Carpetes de música</string>
|
||||
<string name="library_title_playlist">Llistes de reproducció</string>
|
||||
<string name="library_title_playlist_see_all_button">Visualitza-ho tot</string>
|
||||
<string name="login_empty">No s\'ha afegit cap servidor</string>
|
||||
<string name="login_title">Servidors del Subsonic</string>
|
||||
<string name="login_title_expanded">Servidors del Subsonic</string>
|
||||
<string name="media_route_menu_title">Emissió</string>
|
||||
<string name="menu_add_button">Afegeix</string>
|
||||
<string name="menu_add_to_playlist_button">Afegeix a la llista de reproducció</string>
|
||||
<string name="menu_download_all_button">Baixa-ho tot</string>
|
||||
<string name="menu_rate_album">Valora l\'àlbum</string>
|
||||
<string name="menu_download_label">Baixa</string>
|
||||
<string name="menu_filter_all">Tot</string>
|
||||
<string name="menu_filter_download">Baixades</string>
|
||||
<string name="menu_group_by_album">Àlbum</string>
|
||||
<string name="menu_group_by_artist">Artista</string>
|
||||
<string name="menu_group_by_genre">Gènere</string>
|
||||
<string name="menu_group_by_track">Pista</string>
|
||||
<string name="menu_group_by_year">Any</string>
|
||||
<string name="menu_home_label">Inici</string>
|
||||
<string name="menu_last_week_name">La setmana passada</string>
|
||||
<string name="menu_last_month_name">El mes passat</string>
|
||||
<string name="menu_last_year_name">L\'any passat</string>
|
||||
<string name="menu_library_label">Biblioteca</string>
|
||||
<string name="menu_search_button">Cerca</string>
|
||||
<string name="menu_settings_button">Paràmetres</string>
|
||||
<string name="menu_sort_artist">Artista</string>
|
||||
<string name="menu_sort_name">Nom</string>
|
||||
<string name="menu_sort_random">Aleatori</string>
|
||||
<string name="menu_sort_album_count">Nombre d\'àlbums</string>
|
||||
<string name="menu_sort_recently_added">Addicions recents</string>
|
||||
<string name="menu_sort_recently_played">Reproduccions recents</string>
|
||||
<string name="menu_sort_most_played">Més reproduccions</string>
|
||||
<string name="menu_sort_most_recently_starred">Estrelles més recents</string>
|
||||
<string name="menu_sort_least_recently_starred">Estrelles menys recents</string>
|
||||
<string name="menu_pin_button">Afegeix a la pantalla d\'inici</string>
|
||||
<string name="menu_unpin_button">Suprimeix de la pantalla d\'inici</string>
|
||||
<string name="menu_sort_year">Any</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Esborra la cua de reproducció</string>
|
||||
<string name="player_queue_save_queue_success">S\'ha desat la cua de reproducció</string>
|
||||
<string name="player_lyrics_download_content_description">Baixa les lletres per a la reproducció fora de línia</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Lletres baixades per a la reproducció fora de línia</string>
|
||||
<string name="player_lyrics_download_success">S\'han desat les lletres per a la reproducció fora de línia.</string>
|
||||
<string name="player_lyrics_download_failure">No hi ha lletres disponibles que es puguin baixar.</string>
|
||||
<string name="player_server_priority">Prioritat dels servidors</string>
|
||||
<string name="player_unknown_format">Format desconegut</string>
|
||||
<string name="player_transcoding">Transcodificació</string>
|
||||
<string name="player_transcoding_requested">sol·licitat</string>
|
||||
<string name="playlist_catalogue_title">Catàleg de llistes de reproducció</string>
|
||||
<string name="playlist_catalogue_title_expanded">Exploració de llistes de reproducció</string>
|
||||
<string name="playlist_chooser_dialog_empty">No s\'ha creat cap llista de reproducció</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Cancel·la</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Crea</string>
|
||||
<string name="playlist_chooser_dialog_title">Addició a una llista de reproducció</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">S\'han afegit les cançons a la llista de reproducció</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">No s\'han pogut afegir les cançons a la llista de reproducció</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">S\'han omès totes les cançons com a duplicades</string>
|
||||
<string name="playlist_counted_tracks">%1$d pistes • %2$s</string>
|
||||
<string name="playlist_duration">Durada • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Manteniu-ho premut per a suprimir-ho</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nom de la llista de reproducció</string>
|
||||
<string name="playlist_editor_dialog_negative_button">Cancel·la</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Suprimeix</string>
|
||||
<string name="playlist_editor_dialog_positive_button">Desa</string>
|
||||
<string name="playlist_editor_dialog_title">Edició de la llista de reproducció</string>
|
||||
<string name="playlist_page_play_button">Reprodueix</string>
|
||||
<string name="playlist_page_shuffle_button">Reprodueix aleatòriament</string>
|
||||
<string name="playlist_song_count">Llista de reproducció • %1$d cançons</string>
|
||||
<string name="podcast_bottom_sheet_add_to_queue">Afegeix a la cua</string>
|
||||
<string name="podcast_bottom_sheet_delete">Suprimeix</string>
|
||||
<string name="podcast_bottom_sheet_download">Baixa</string>
|
||||
<string name="podcast_bottom_sheet_go_to_channel">Ves al canal</string>
|
||||
<string name="podcast_bottom_sheet_play_next">Reprodueix a continuació</string>
|
||||
<string name="podcast_bottom_sheet_remove">Suprimeix</string>
|
||||
<string name="podcast_channel_catalogue_title">Canals</string>
|
||||
<string name="podcast_channel_catalogue_title_expanded">Exploració de canals</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL de l\'RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canal de pòdcast</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descripció</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodis</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">No hi ha cap episodi disponible</string>
|
||||
<string name="podcast_episode_download_request_snackbar">S\'ha enviat la sol·licitud al servidor</string>
|
||||
<string name="podcast_info_empty_button">Feu clic per a ocultar la secció.\nEls efectes seran visibles quan reinicieu l\'aplicació.</string>
|
||||
<string name="podcast_info_empty_subtitle">Quan afegiu un canal, el trobareu aquí.</string>
|
||||
<string name="podcast_info_empty_title">No s\'ha trobat cap pòdcast.</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL de la pàgina d\'inici de la ràdio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nom de la ràdio</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">URL de la transmissió de la ràdio</string>
|
||||
<string name="radio_editor_dialog_negative_button">Cancel·la</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Suprimeix</string>
|
||||
<string name="radio_editor_dialog_positive_button">Desa</string>
|
||||
<string name="radio_editor_dialog_title">Emissora de ràdio per Internet</string>
|
||||
<string name="radio_station_info_empty_button">Feu clic per a ocultar la secció.\nEls efectes seran visibles quan reinicieu l\'aplicació.</string>
|
||||
<string name="radio_station_info_empty_subtitle">Quan afegiu una emissora de ràdio, la trobareu aquí.</string>
|
||||
<string name="radio_station_info_empty_title">No s\'ha trobat cap emissora.</string>
|
||||
<string name="rating_dialog_negative_button">Cancel·la</string>
|
||||
<string name="rating_dialog_positive_button">Desa</string>
|
||||
<string name="rating_dialog_title">Valoració</string>
|
||||
<string name="search_hint">Cerqueu títols, artistes o àlbums</string>
|
||||
<string name="search_info_minimum_characters">Introduïu com a mínim tres caràcters</string>
|
||||
<string name="search_title_album">Àlbums</string>
|
||||
<string name="search_title_artist">Artistes</string>
|
||||
<string name="search_title_song">Cançons</string>
|
||||
<string name="server_signup_dialog_action_low_security">Seguretat baixa</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Manteniu-ho premut per a suprimir-ho</string>
|
||||
<string name="server_signup_dialog_hint_local_address">URL local</string>
|
||||
<string name="server_signup_dialog_hint_name">Nom del servidor</string>
|
||||
<string name="server_signup_dialog_hint_password">Contrasenya</string>
|
||||
<string name="server_signup_dialog_hint_url">URL del servidor</string>
|
||||
<string name="server_signup_dialog_hint_username">Nom d\'usuari</string>
|
||||
<string name="server_signup_dialog_negative_button">Cancel·la</string>
|
||||
<string name="server_signup_dialog_neutral_button">Suprimeix</string>
|
||||
<string name="server_signup_dialog_positive_button">Desa</string>
|
||||
<string name="server_signup_dialog_title">Addició d\'un servidor</string>
|
||||
<string name="server_unreachable_dialog_negative_button">Cancel·la</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Ves a l\'inici de sessió</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continua igualment</string>
|
||||
<string name="server_unreachable_dialog_summary">El servidor sol·licitat no està disponible. Si trieu continuar, aquest quadre de diàleg no apareixerà durant una hora.</string>
|
||||
<string name="server_unreachable_dialog_title">Servidor no disponible</string>
|
||||
<string name="settings_about_summary">Tempus és un client de música lliure i lleuger per al Subsonic, dissenyat i creat nativament per a l\'Android.</string>
|
||||
<string name="settings_about_title">Quant a</string>
|
||||
<string name="settings_always_on_display">Pantalla sempre encesa</string>
|
||||
<string name="settings_allow_playlist_duplicates">Permet afegir duplicats a una llista de reproducció</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si s\'habilita, no es comprovarà si hi ha elements duplicats en afegir-los a una llista de reproducció.</string>
|
||||
<string name="settings_audio_transcode_download_format">Format de transcodificació</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si s\'habilita, Tempus no forçarà la baixada de la pista amb els paràmetres de transcodificació següents.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Prioritza els paràmetres del servidor per a la transmissió a les baixades</string>
|
||||
<string name="settings_audio_transcode_download_summary">Si s\'habilita, Tempus baixarà les pistes transcodificades.</string>
|
||||
<string name="settings_audio_transcode_download_title">Baixa les pistes transcodificades</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Si s\'habilita, es demanarà al servidor la durada estimada de la pista.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Estima la durada del contingut</string>
|
||||
<string name="settings_audio_transcode_format_download">Format de transcodificació per a les baixades</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Format de transcodificació amb dades mòbils</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Format de transcodificació amb wifi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Si s\'habilita, Tempus no forçarà la transmissió de la pista amb els paràmetres de transcodificació següents.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Prioritza els paràmetres de transcodificació del servidor</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Prioritat de transcodificació de la pista proporcionada al servidor</string>
|
||||
<string name="settings_buffering_strategy">Estratègia de memòria intermèdia</string>
|
||||
<string name="settings_buffering_strategy_summary">Perquè el canvi tingui efecte, heu de reiniciar l\'aplicació manualment.</string>
|
||||
<string name="settings_choose_download_folder">Trieu una carpeta per als fitxers de música baixats.</string>
|
||||
<string name="settings_clear_download_folder">Esborra la carpeta de baixades</string>
|
||||
<string name="settings_continuous_play_summary">Permet que segueixi sonant música quan acabi una llista de reproducció; es reproduiran cançons similars.</string>
|
||||
<string name="settings_continuous_play_title">Reproducció contínua</string>
|
||||
<string name="settings_covers_cache">Mida de la memòria cau de les caràtules</string>
|
||||
<string name="settings_data_saving_mode_summary">Per a reduir el consum de dades, evita la baixada de les caràtules.</string>
|
||||
<string name="settings_data_saving_mode_title">Limita l\'ús de les dades mòbils</string>
|
||||
<string name="settings_delete_download_storage_summary">Si continueu, se suprimiran permanentment tots els elements desats.</string>
|
||||
<string name="settings_delete_download_storage_title">Suprimeix els elements desats</string>
|
||||
<string name="settings_download_storage_title">Emmagatzematge per a les baixades.</string>
|
||||
<string name="settings_download_folder_cleared">S\'ha esborrat la carpeta de baixades.</string>
|
||||
<string name="settings_download_folder_set">S\'ha definit la carpeta de baixades.</string>
|
||||
<string name="settings_set_download_folder">Defineix la carpeta de baixades</string>
|
||||
<string name="settings_system_equalizer_summary">Ajusteu els paràmetres d\'àudio</string>
|
||||
<string name="settings_system_equalizer_title">Equalitzador del sistema</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Seguiu el desenvolupament</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Actualitzacions</string>
|
||||
<string name="settings_github_update_title">Comprova si hi ha actualitzacions a GitHub</string>
|
||||
<string name="settings_github_update_summary">Si feu servir la versió de GitHub, per defecte l\'aplicació comprovarà si hi ha noves versions en format APK. Canvieu-ho per a inhabilitar les comprovacions automàtiques de GitHub.</string>
|
||||
<string name="settings_support_summary">Uniu-vos a discussions de la comunitat i obteniu ajuda</string>
|
||||
<string name="settings_support_title">Ajuda als usuaris</string>
|
||||
<string name="settings_scan_result">S\'està analitzant: s\'han comptat %1$d pistes</string>
|
||||
<string name="settings_image_size">Resolució de les imatges</string>
|
||||
<string name="settings_language">Llengua</string>
|
||||
<string name="settings_logout_title">Tanca la sessió</string>
|
||||
<string name="settings_max_bitrate_download">Taxa de bits de les baixades</string>
|
||||
<string name="settings_max_bitrate_mobile">Taxa de bits amb dades mòbils</string>
|
||||
<string name="settings_max_bitrate_wifi">Taxa de bits amb wifi</string>
|
||||
<string name="settings_media_cache">Mida de la memòria cau de fitxers multimèdia</string>
|
||||
<string name="settings_music_directory">Mostra les carpetes de música</string>
|
||||
<string name="settings_music_directory_summary">Si s\'habilita, es mostra la secció de carpetes de música. Tingueu en compte que, perquè la navegació amb carpetes funcioni correctament, el servidor ha de ser compatible amb aquesta característica.</string>
|
||||
<string name="settings_podcast">Mostra els pòdcasts</string>
|
||||
<string name="settings_podcast_summary">Si s\'habilita, es mostra la secció de pòdcasts. Reinicieu l\'aplicació perquè tingui efecte completament.</string>
|
||||
<string name="settings_audio_quality">Mostra la qualitat de l\'àudio</string>
|
||||
<string name="settings_audio_quality_summary">Es mostraran la taxa de bits i el format d\'àudio per a cada pista d\'àudio.</string>
|
||||
<string name="settings_song_rating">Mostra la valoració amb estrelles de les cançons</string>
|
||||
<string name="settings_song_rating_summary">Si s\'habilita, es mostra la valoració de 5 estrelles d\'una pista a la pàgina de la cançó.\n\n*Cal reiniciar l\'aplicació</string>
|
||||
<string name="settings_item_rating">Mostra la valoració dels elements</string>
|
||||
<string name="settings_item_rating_summary">Si s\'habilita, es mostrarà la valoració dels elements i si s\'han marcat com a preferits.</string>
|
||||
<string name="settings_queue_syncing_countdown">Temporitzador de sincronització</string>
|
||||
<string name="settings_queue_syncing_summary">Si s\'habilita, l\'usuari tindrà la possibilitat de desar la cua de reproducció i carregar l\'estat en obrir l\'aplicació.</string>
|
||||
<string name="settings_queue_syncing_title">Sincronitza la cua de reproducció d\'aquest usuari (implantat parcialment)</string>
|
||||
<string name="settings_show_mini_shuffle_button">Mostra el botó de reproducció aleatòria</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si s\'habilita, es mostra el botó de reproducció aleatòria i se suprimeix el cor del minireproductor.</string>
|
||||
<string name="settings_radio">Mostra la ràdio</string>
|
||||
<string name="settings_radio_summary">Si s\'habilita, es mostra la secció de ràdio. Reinicieu l\'aplicació perquè tingui efecte completament.</string>
|
||||
<string name="settings_auto_download_lyrics">Baixa les lletres automàticament</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Desa automàticament les lletres quan estiguin disponibles perquè es puguin mostrar fora de línia.</string>
|
||||
<string name="settings_replay_gain">Mode de ReplayGain</string>
|
||||
<string name="settings_rounded_corner">Cantonades arrodonides</string>
|
||||
<string name="settings_rounded_corner_size">Mida de les cantonades</string>
|
||||
<string name="settings_rounded_corner_size_summary">Defineix la magnitud de l\'angle de curvatura.</string>
|
||||
<string name="settings_rounded_corner_summary">Si s\'habilita, defineix un angle de curvatura per a totes les caràtules representades. Els canvis tindran efecte quan reinicieu l\'aplicació.</string>
|
||||
<string name="settings_scan_title">Analitza la biblioteca</string>
|
||||
<string name="settings_scrobble_title">Habilita l\'anàlisi musical</string>
|
||||
<string name="settings_system_language">Llengua del sistema</string>
|
||||
<string name="settings_share_title">Habilita l\'ús compartit de música</string>
|
||||
<string name="settings_streaming_cache_size">Mida de la memòria cau de transmissió</string>
|
||||
<string name="settings_streaming_cache_storage_title">Emmagatzematge de la memòria cau de transmissió</string>
|
||||
<string name="settings_sub_summary_scrobble">Tingueu en compte que l\'anàlisi musical també depèn que el servidor tingui habilitada la recepció d\'aquestes dades.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">En escoltar la ràdio d\'un artista, una mescla instantània o totes les cançons aleatòriament, les pistes per sota d\'un llindar de l\'usuari s\'ignoraran.</string>
|
||||
<string name="settings_summary_replay_gain">ReplayGain és una característica que us permet ajustar el nivell de volum de les pistes d\'àudio perquè l\'escolta sigui coherent. Aquest paràmetre només és efectiu si la pista conté les metadades necessàries.</string>
|
||||
<string name="settings_summary_scrobble">L\'anàlisi musical és una característica que permet al vostre dispositiu enviar informació sobre les cançons que escolteu al servidor de música. Aquesta informació ajuda a crear recomanacions personalitzades a partir de les vostres preferències musicals.</string>
|
||||
<string name="settings_summary_share">Permet que l\'usuari comparteixi música mitjançant un enllaç. El servidor ha de ser compatible i tenir habilitada aquesta característica i es limita a pistes individuals, àlbums i llistes de reproducció.</string>
|
||||
<string name="settings_summary_syncing">Retorna l\'estat de la cua de reproducció per a aquest usuari. Això inclou les pistes a la cua de reproducció, la pista en reproducció actualment i la posició de la pista. El servidor ha de ser compatible amb aquesta característica.\n*Aquest paràmetre no funciona al 100% en tots els servidors/dispositius.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nEn ús actualment: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Es dona prioritat al mode de transcodificació. Si s\'estableix en «Reproducció directa», la taxa de bits del fitxer no canviarà.</string>
|
||||
<string name="settings_summary_transcoding_download">Baixa el contingut multimèdia transcodificat. Si s\'habilita, no es farà servir l\'extrem de baixada, sinó els paràmetres següents. \n\nSi s\'estableix «Format de transcodificació per a les baixades» en «Baixada directa», la taxa de bits del fitxer no canviarà.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quan el fitxer es transcodifica en temps real, el client normalment no mostra la durada de la pista. És possible sol·licitar una estimació de la durada de la pista en reproducció als servidors compatibles, però els temps de resposta poden ser més llargs.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si s\'habilita, els artistes amb estrelles es baixaran per a l\'ús fora de línia.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sincronitza els artistes amb estrelles per a l\'ús fora de línia</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Si s\'habilita, els àlbums amb estrelles es baixaran per a l\'ús fora de línia.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sincronitza els àlbums amb estrelles per a l\'ús fora de línia</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Si s\'habilita, les pistes amb estrelles es baixaran per a l\'ús fora de línia.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Sincronitza les pistes amb estrelles per a l\'ús fora de línia</string>
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="settings_title_data">Dades</string>
|
||||
<string name="settings_title_general">General</string>
|
||||
<string name="settings_title_playlist">Llista de reproducció</string>
|
||||
<string name="settings_title_rating">Valoració</string>
|
||||
<string name="settings_title_replay_gain">ReplayGain</string>
|
||||
<string name="settings_title_scrobble">Anàlisi musical</string>
|
||||
<string name="settings_title_skip_min_star_rating">Ignora les pistes segons la valoració</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Cançons amb una valoració de:</string>
|
||||
<string name="settings_title_share">Comparteix</string>
|
||||
<string name="settings_title_syncing">Sincronització</string>
|
||||
<string name="settings_title_transcoding">Transcodificació</string>
|
||||
<string name="settings_title_transcoding_download">Baixada transcodificada</string>
|
||||
<string name="settings_title_ui">Interfície d\'usuari</string>
|
||||
<string name="settings_transcoded_download">Baixada transcodificada</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Versió</string>
|
||||
<string name="settings_wifi_only_summary">Demana la confirmació de l\'usuari abans d\'iniciar la transmissió per la xarxa mòbil.</string>
|
||||
<string name="settings_wifi_only_title">Alerta de transmissió només per wifi</string>
|
||||
<string name="share_bottom_sheet_copy_link">Copia l\'enllaç</string>
|
||||
<string name="share_bottom_sheet_delete">Suprimeix l\'element compartit</string>
|
||||
<string name="share_bottom_sheet_update">Actualitza l\'element compartit</string>
|
||||
<string name="share_subtitle_item">Data de venciment: %1$s</string>
|
||||
<string name="share_no_expiration">Mai</string>
|
||||
<string name="share_unsupported_error">L\'ús compartit no s\'admet o no està habilitat</string>
|
||||
<string name="asset_link_clipboard_label">Enllaç a recurs de Tempus</string>
|
||||
<string name="asset_link_label_song">UID de la cançó</string>
|
||||
<string name="asset_link_label_album">UID de l\'àlbum</string>
|
||||
<string name="asset_link_label_artist">UID de l\'artista</string>
|
||||
<string name="asset_link_label_playlist">UID de la llista de reproducció</string>
|
||||
<string name="asset_link_label_genre">UID del gènere</string>
|
||||
<string name="asset_link_label_year">UID de l\'any</string>
|
||||
<string name="asset_link_label_unknown">UID del recurs</string>
|
||||
<string name="asset_link_error_unsupported">Enllaç a recurs no admès</string>
|
||||
<string name="asset_link_error_song">No s\'ha pogut obrir la cançó</string>
|
||||
<string name="asset_link_error_album">No s\'ha pogut obrir l\'àlbum</string>
|
||||
<string name="asset_link_error_artist">No s\'ha pogut obrir l\'artista</string>
|
||||
<string name="asset_link_error_playlist">No s\'ha pogut obrir la llista de reproducció</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">S\'ha copiat %1$s al porta-retalls</string>
|
||||
<string name="asset_link_debug_toast">Enllaç al recurs: %1$s</string>
|
||||
<string name="share_update_dialog_hint_description">Descripció</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Data de venciment</string>
|
||||
<string name="share_update_dialog_negative_button">Cancel·la</string>
|
||||
<string name="share_update_dialog_positive_button">Desa</string>
|
||||
<string name="share_update_dialog_title">Comparteix</string>
|
||||
<string name="song_bottom_sheet_add_to_playlist">Afegeix a la llista de reproducció</string>
|
||||
<string name="song_bottom_sheet_add_to_queue">Afegeix a la cua</string>
|
||||
<string name="song_bottom_sheet_download">Baixa</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_album">S\'ha produït un error en recuperar l\'àlbum</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_artist">S\'ha produït un error en recuperar l\'artista</string>
|
||||
<string name="song_bottom_sheet_go_to_album">Ves a l\'àlbum</string>
|
||||
<string name="song_bottom_sheet_go_to_artist">Ves a l\'artista</string>
|
||||
<string name="song_bottom_sheet_instant_mix">Mescla instantània</string>
|
||||
<string name="song_bottom_sheet_play_next">Reprodueix a continuació</string>
|
||||
<string name="song_bottom_sheet_rate">Valoració</string>
|
||||
<string name="song_bottom_sheet_remove">Suprimeix</string>
|
||||
<string name="song_bottom_sheet_share">Comparteix</string>
|
||||
<string name="song_list_page_downloaded">Baixades</string>
|
||||
<string name="song_list_page_most_played">Pistes més reproduïdes</string>
|
||||
<string name="song_list_page_recently_added">Pistes afegides recentment</string>
|
||||
<string name="song_list_page_recently_played">Pistes reproduïdes recentment</string>
|
||||
<string name="song_list_page_starred">Pistes amb estrelles</string>
|
||||
<string name="song_list_page_top">Les millors cançons de %1$s</string>
|
||||
<string name="song_list_page_year">Any %1$d</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<string name="starred_sync_dialog_negative_button">Cancel·la</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continua</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continua i baixa</string>
|
||||
<string name="starred_sync_dialog_summary">La baixada de les pistes amb estrelles pot requerir un ús de dades important.</string>
|
||||
<string name="starred_sync_dialog_title">Sincronitza les pistes amb estrelles</string>
|
||||
<string name="starred_artist_sync_dialog_summary">La baixada dels artistes amb estrelles pot requerir un ús de dades important.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Sincronitza els artistes amb estrelles</string>
|
||||
<string name="starred_album_sync_dialog_summary">La baixada dels àlbums amb estrelles pot requerir un ús de dades important.</string>
|
||||
<string name="starred_album_sync_dialog_title">Sincronitza els àlbums amb estrelles</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Perquè els canvis tinguin efecte, reinicieu l\'aplicació.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Si canvieu la destinació dels fitxers emmagatzemats a la memòria cau d\'un emmagatzematge a un altre, se suprimiran immediatament tots els fitxers emmagatzemats anteriorment a la memòria cau de l\'altre emmagatzematge.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Selecció de l\'opció d\'emmagatzematge</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Extern</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Intern</string>
|
||||
<string name="support_url">https://ko-fi.com/eddyizm</string>
|
||||
<string name="track_info_album">Àlbum</string>
|
||||
<string name="track_info_artist">Artista</string>
|
||||
<string name="track_info_bit_depth">Profunditat de bits</string>
|
||||
<string name="track_info_bitrate">Taxa de bits</string>
|
||||
<string name="track_info_content_type">Tipus de contingut</string>
|
||||
<string name="track_info_dialog_positive_button">D\'acord</string>
|
||||
<string name="track_info_dialog_title">Informació de la pista</string>
|
||||
<string name="track_info_disc_number">Número de disc</string>
|
||||
<string name="track_info_duration">Durada</string>
|
||||
<string name="track_info_genre">Gènere</string>
|
||||
<string name="track_info_path">Camí</string>
|
||||
<string name="track_info_sampling_rate">Freqüència de mostreig</string>
|
||||
<string name="track_info_size">Mida</string>
|
||||
<string name="track_info_suffix">Sufix</string>
|
||||
<string name="track_info_summary_downloaded_file">El fitxer s\'ha baixat amb les API del Subsonic. El còdec i la taxa de bits del fitxer són els mateixos que els del fitxer d\'origen.</string>
|
||||
<string name="track_info_summary_full_transcode">L\'aplicació sol·licitarà al servidor la transcodificació del fitxer i la modificació de la taxa de bits. El còdec sol·licitat per l\'usuari és %1$s, amb una taxa de bits de %2$s. Qualsevol canvi potencial del còdec i la taxa de bits del fitxer en el format triat el gestionarà el servidor, que pot admetre l\'operació o no.</string>
|
||||
<string name="track_info_summary_original_file">L\'aplicació només llegirà el fitxer original proporcionat pel servidor. L\'aplicació sol·licitarà explícitament al servidor el fitxer transcodificat amb la taxa de bits del fitxer original.</string>
|
||||
<string name="track_info_summary_server_prioritized">La qualitat del fitxer reproduït dependrà de la decisió del servidor. L\'aplicació no forçarà la tria de còdec i taxa de bits per a cap transcodificació potencial.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">L\'aplicació sol·licitarà al servidor la modificació de la taxa de bits del fitxer. L\'usuari ha sol·licitat una taxa de bits de %1$s, mentre que el còdec del fitxer d\'origen seguirà sent el mateix. Qualsevol canvi de la taxa de bits del fitxer en el format triat el gestionarà el servidor, que pot admetre l\'operació o no.</string>
|
||||
<string name="track_info_summary_transcoding_codec">L\'aplicació sol·licitarà al servidor la transcodificació del fitxer. El còdec sol·licitat per l\'usuari és %1$s, mentre que la taxa de bits serà la mateixa que la del fitxer d\'origen. La transcodificació potencial del fitxer en el format triat depèn del servidor, que pot admetre l\'operació o no.</string>
|
||||
<string name="track_info_title">Títol</string>
|
||||
<string name="track_info_track_number">Número de pista</string>
|
||||
<string name="track_info_transcoded_content_type">Tipus de contingut transcodificat</string>
|
||||
<string name="track_info_transcoded_suffix">Sufix de la transcodificació</string>
|
||||
<string name="track_info_year">Any</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Volem donar un agraïment especial a UnDraw; aquesta aplicació no seria tan bonica sense les seves il·lustracions.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="widget_label">Giny de Tempus</string>
|
||||
<string name="widget_not_playing">No hi ha res en reproducció</string>
|
||||
<string name="widget_placeholder_subtitle">Obre Tempus</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_content_desc_album_art">Caràtula de l\'àlbum</string>
|
||||
<string name="widget_content_desc_play_pause">Reprodueix o posa en pausa</string>
|
||||
<string name="widget_content_desc_next">Pista següent</string>
|
||||
<string name="widget_content_desc_prev">Pista anterior</string>
|
||||
<string name="widget_content_desc_shuffle">Reprodueix aleatòriament</string>
|
||||
<string name="widget_content_desc_repeat">Canvia el mode de repetició</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">Se sincronitzarà %d àlbum</item>
|
||||
<item quantity="other">Se sincronitzaran %d àlbums</item>
|
||||
</plurals>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">Se sincronitzarà %d artista</item>
|
||||
<item quantity="other">Se sincronitzaran %d artistes</item>
|
||||
</plurals>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">S\'està baixant %d cançó</item>
|
||||
<item quantity="other">S\'estan baixant %d cançons</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Equalitzador</string>
|
||||
<string name="equalizer_reset">Reinicialitza</string>
|
||||
<string name="equalizer_enable">Habilita</string>
|
||||
<string name="equalizer_not_supported">No és compatible amb aquest dispositiu</string>
|
||||
<string name="settings_app_equalizer">Equalitzador</string>
|
||||
<string name="settings_app_equalizer_summary">Obre l\'equalitzador integrat</string>
|
||||
|
||||
<string name="settings_album_detail">Mostra els detalls de l\'àlbum</string>
|
||||
<string name="settings_album_detail_summary">Si s\'habilita, es mostren els detalls de l\'àlbum, com el gènere i el nombre de cançons, a la pàgina de l\'àlbum.</string>
|
||||
<string name="settings_artist_sort_by_album_count">Ordena els artistes per nombre d\'àlbums</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Si s\'habilita, ordena els artistes per nombre d\'àlbums. Si no, s\'ordenen per nom.</string>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="album_page_tracks_count_and_duration">%1$d pistas • %2$d minutos</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="activity_battery_optimizations_conclusion">Si tienes problemas, visita https://dontkillmyapp.com. Ofrece instrucciones detalladas para desactivar características de ahorro de energía que podrían afectar al rendimiento de la app.</string>
|
||||
<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_summary">Por favor, desactiva 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>
|
||||
@@ -40,6 +40,7 @@
|
||||
<string name="artist_list_page_downloaded">Artistas descargados</string>
|
||||
<string name="artist_list_page_starred">Artistas destacados</string>
|
||||
<string name="artist_list_page_title">Artistas</string>
|
||||
<string name="artist_no_artist_info_toast">No hay más información del artista</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Aleatorio</string>
|
||||
<string name="artist_page_switch_layout_button">Cambiar disposición</string>
|
||||
@@ -52,6 +53,7 @@
|
||||
<string name="battery_optimization_negative_button">Ignorar</string>
|
||||
<string name="battery_optimization_neutral_button">No volver a preguntar</string>
|
||||
<string name="battery_optimization_positive_button">Desactivar</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">No se pudieron obtener las pistas del servidor.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Cancelar</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Habilitar el ahorro de datos</string>
|
||||
<string name="connection_alert_dialog_positive_button">Aceptar</string>
|
||||
@@ -60,9 +62,9 @@
|
||||
<string name="content_description_shuffle_button">Aleatorio</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Cancelar</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continuar</string>
|
||||
<string name="delete_download_storage_dialog_summary">Por favor, sea consciente de que si continúa, todos los elementos descargados de todos los servidores se eliminarán.</string>
|
||||
<string name="delete_download_storage_dialog_summary">Por favor, ten en cuenta que si continúas, todos los elementos descargados de todos los servidores se eliminarán.</string>
|
||||
<string name="delete_download_storage_dialog_title">Eliminar elementos guardados</string>
|
||||
<string name="description_empty_title">Descripción no disponible</string>
|
||||
<string name="description_empty_title">Letra no disponible</string>
|
||||
<string name="disc_titlefull">Disco %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disco %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Cancelar</string>
|
||||
@@ -111,12 +113,12 @@
|
||||
<string name="home_rearrangement_dialog_neutral_button">Restablecer</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Guardar</string>
|
||||
<string name="home_rearrangement_dialog_title">Reorganizar la página de inicio</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Tenga en cuenta que para que los cambios surtan efecto, hay que reiniciar la aplicación.</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Ten en cuenta que para que los cambios surtan efecto, hay que reiniciar la aplicación.</string>
|
||||
<string name="home_section_music">Música</string>
|
||||
<string name="home_section_podcast">Pódcasts</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Mejores pistas de tus artistas favoritos</string>
|
||||
<string name="home_subtitle_made_for_you">Iniciar mix desde una cación que te gustó</string>
|
||||
<string name="home_subtitle_made_for_you">Iniciar mix desde una canción que te gustó</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Añadir una nueva emisora de radio</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Añadir un nuevo canal de pódcasts</string>
|
||||
<string name="home_sync_starred_cancel">Cancelar</string>
|
||||
@@ -177,6 +179,7 @@
|
||||
<string name="menu_filter_download">Descargado</string>
|
||||
<string name="menu_group_by_album">Álbum</string>
|
||||
<string name="menu_group_by_artist">Artista</string>
|
||||
<string name="settings_github_update_title">Comprobar actualizaciones en GitHub</string>
|
||||
<string name="settings_scan_result">Escaneo: hay %1$d pistas</string>
|
||||
<string name="settings_support_title">Soporte al usuario</string>
|
||||
<string name="settings_image_size">Resolución de la imagen</string>
|
||||
@@ -185,7 +188,7 @@
|
||||
<string name="settings_logout_title">Cerrar sesión</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Siga el desarrollo</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_github_title">GitHub</string>
|
||||
<string name="menu_group_by_genre">Género</string>
|
||||
<string name="menu_group_by_track">Pista</string>
|
||||
<string name="menu_group_by_year">Año</string>
|
||||
@@ -199,6 +202,7 @@
|
||||
<string name="menu_sort_artist">Artista</string>
|
||||
<string name="menu_sort_name">Nombre</string>
|
||||
<string name="menu_sort_random">Aleatorio</string>
|
||||
<string name="menu_sort_album_count">Número de álbumes</string>
|
||||
<string name="menu_sort_recently_added">Añadido recientemente</string>
|
||||
<string name="menu_sort_recently_played">Reproducido recientemente</string>
|
||||
<string name="menu_sort_most_played">Lo más reproducido</string>
|
||||
@@ -244,6 +248,7 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Explorar canales</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL del feed RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canal del pódcast</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Este servidor no soporta pódcasts.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descripción</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodios</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">No hay episodios disponibles</string>
|
||||
@@ -258,10 +263,12 @@
|
||||
<string name="radio_editor_dialog_negative_button">Cancelar</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Eliminar</string>
|
||||
<string name="radio_editor_dialog_positive_button">Guardar</string>
|
||||
<string name="radio_editor_dialog_title">"Emisora "</string>
|
||||
<string name="radio_editor_dialog_updated">Emisora de radio actualizada</string>
|
||||
<string name="radio_editor_dialog_title">Emisora</string>
|
||||
<string name="radio_station_info_empty_button">Pulsa para ocultar la sección\nLos cambios serán visibles al reiniciar la app</string>
|
||||
<string name="radio_station_info_empty_subtitle">Una vez que añadas una emisora de radio, la encontrarás aquí</string>
|
||||
<string name="radio_station_info_empty_title">No hay emisoras de radio</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Este servidor no soporta emisoras de radio en Internet.</string>
|
||||
<string name="rating_dialog_negative_button">Cancelar</string>
|
||||
<string name="rating_dialog_positive_button">Guardar</string>
|
||||
<string name="rating_dialog_title">Valorar</string>
|
||||
@@ -287,34 +294,34 @@
|
||||
<string name="server_unreachable_dialog_negative_button">Cancelar</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Ir al inicio de sesión</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continuar de todas formas</string>
|
||||
<string name="server_unreachable_dialog_summary">El servidor no está disponible. Si decide continuar, este diálogo no aparecerá de nuevo durante una hora.</string>
|
||||
<string name="server_unreachable_dialog_summary">El servidor no está disponible. Si decides continuar, este diálogo no aparecerá de nuevo durante una hora.</string>
|
||||
<string name="server_unreachable_dialog_title">No se puede conectar con el servidor</string>
|
||||
<string name="settings_about_summary">Tempus es un cliente de música Subsonic ligero y de código abierto, diseñado nativamente para Android.</string>
|
||||
<string name="settings_about_title">Acerca de</string>
|
||||
<string name="settings_always_on_display">Pantalla siempre activa</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si está habilitada, no se comprobará si hay pistas repetidas cuando se añadan a la lista.</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si se habilita, no se comprobará si hay pistas repetidas cuando se añadan a la lista.</string>
|
||||
<string name="settings_audio_transcode_download_format">Formato de transcodificación</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si está habilitada, Tempus no descargará la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si se habilita, Tempus no descargará la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Dar prioridad a las opciones del servidor usadas para el streaming en las descargas</string>
|
||||
<string name="settings_audio_transcode_download_summary">Si está habilitada, Tempus descargará las pistas transcodificadas.</string>
|
||||
<string name="settings_audio_transcode_download_summary">Si se habilita, Tempus descargará las pistas transcodificadas.</string>
|
||||
<string name="settings_audio_transcode_download_title">Descargas pistas transcodificadas</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Si está habilitada, se pedirá al servidor la duración estimada de la pista.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Si se habilita, se pedirá al servidor la duración estimada de la pista.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Calcular la duración del contenido</string>
|
||||
<string name="settings_audio_transcode_format_download">Formato de transcodificación para las descargas</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Formato de transcodificación en red de datos móviles</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Formato de transcodificación en red Wi-Fi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Si está habilitada, Tempus no reproducirá la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Si se habilita, Tempus no reproducirá la pista con las opciones de transcodificación que aparecen a continuación.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Dar prioridad a las opciones de transcodificación del servidor</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Prioridad a la hora de transcodificar una pista</string>
|
||||
<string name="settings_buffering_strategy">Estrategia de buffer</string>
|
||||
<string name="settings_buffering_strategy_summary">Para que los cambios surtan efecto, debes reinciar la app.</string>
|
||||
<string name="settings_buffering_strategy_summary">Para que los cambios surtan efecto, debes reiniciar la app.</string>
|
||||
<string name="settings_choose_download_folder">Elige una carpeta para descargar los archivos de música</string>
|
||||
<string name="settings_clear_download_folder">Limpiar la carpeta de descargas</string>
|
||||
<string name="settings_continuous_play_summary">Permite que la música siga reproduciéndose una vez que la lista de reproducción ha terminado, reproduciendo pistas similares</string>
|
||||
<string name="settings_continuous_play_title">Reproducción continua</string>
|
||||
<string name="settings_covers_cache">Tamaño de la caché de portadas de álbumes</string>
|
||||
<string name="settings_data_saving_mode_summary">Evitar descargar las portadas de álbumes para reducir el uso de datos</string>
|
||||
<string name="settings_data_saving_mode_title">Limitr el uso de datos móviles</string>
|
||||
<string name="settings_data_saving_mode_title">Limitar el uso de datos móviles</string>
|
||||
<string name="settings_delete_download_storage_summary">Al continuar se eliminarán de forma irreversible todos los elementos guardados.</string>
|
||||
<string name="settings_delete_download_storage_title">Eliminar elementos guardados</string>
|
||||
<string name="settings_download_storage_title">Almacenamiento de descargas</string>
|
||||
@@ -323,21 +330,21 @@
|
||||
<string name="settings_max_bitrate_wifi">Tasa de bits en Wi-Fi</string>
|
||||
<string name="settings_media_cache">Tamaño de la caché multimedia</string>
|
||||
<string name="settings_music_directory">Mostrar carpetas de música</string>
|
||||
<string name="settings_music_directory_summary">Si está habilitada, se mostrará la sección de carpetas de música. Tenga en cuenta que para que la navegación funcione correctamente, el servidor debe soportar esta característica.</string>
|
||||
<string name="settings_music_directory_summary">Si se habilita, se mostrará la sección de carpetas de música. Tenga en cuenta que para que la navegación funcione correctamente, el servidor debe soportar esta característica.</string>
|
||||
<string name="settings_podcast">Mostrar pódcasts</string>
|
||||
<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_podcast_summary">Si se habilita, 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_song_rating_summary">Si se habilita, 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_show_mini_shuffle_button_summary">Si está habilitada, muestra el botón de reproducción aleatoria y oculta el botón de «Favoritos» en el minirreproductor</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si se habilita, muestra el botón de reproducción aleatoria y oculta el botón de «Favoritos» en el minirreproductor.</string>
|
||||
<string name="settings_radio">Mostrar emisoras de radio</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Descargar las letras automáticamente cuando estén disponibles para que se puedan mostrar cuando no hay conexión.</string>
|
||||
<string name="settings_replay_gain">Configurar el modo de ganancia de reproducción</string>
|
||||
<string name="settings_rounded_corner">Esquinas redondeadas</string>
|
||||
<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_rounded_corner_summary">Si se habilita, 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>
|
||||
@@ -366,10 +373,10 @@
|
||||
<string name="settings_summary_streaming_cache_size">%1$s\nEn uso: %2$s MiB</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="track_info_track_number">Número de pista</string>
|
||||
<string name="settings_item_rating_summary">Si está habilitada, se mostrará la valoración del elemento y si está marcado como favorito.</string>
|
||||
<string name="settings_item_rating_summary">Si se habilita, se mostrará la valoración del elemento y si está marcado como favorito.</string>
|
||||
<string name="settings_queue_syncing_countdown">Temporizador de sincronización</string>
|
||||
<string name="settings_queue_syncing_summary">Si está habilitada, el usuario podrá guardar la cola de reproducción y restaurarla cuando abra la aplicación.</string>
|
||||
<string name="settings_radio_summary">Si está habilitada, se mostrará la sección de emisoras de radio. Reinicia la app para que los cambios surtan efecto.</string>
|
||||
<string name="settings_queue_syncing_summary">Si se habilita, el usuario podrá guardar la cola de reproducción y restaurarla cuando abra la aplicación.</string>
|
||||
<string name="settings_radio_summary">Si se habilita, se mostrará la sección de emisoras de radio. Reinicia la app para que los cambios surtan efecto.</string>
|
||||
<string name="settings_rounded_corner_size_summary">Establece la proporción del ángulo de curvatura.</string>
|
||||
<string name="track_info_dialog_title">Información de la pista</string>
|
||||
<string name="track_info_disc_number">Número de disco</string>
|
||||
@@ -441,10 +448,10 @@
|
||||
<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_download">Descarga los archivos multimedia transcodificados. Si se habilita, 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="settings_sync_starred_tracks_for_offline_use_summary">Si se habilita, 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>
|
||||
@@ -454,7 +461,7 @@
|
||||
<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_artists_for_offline_use_title">Sincronizar artistas destacados para uso sin conexión</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="settings_sync_starred_albums_for_offline_use_summary">Si se habilita, los álbumes favoritos se descargarán para uso sin conexión.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Sincronizar artistas destacados</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>
|
||||
@@ -477,21 +484,23 @@
|
||||
<string name="widget_content_desc_prev">Pista anterior</string>
|
||||
<string name="download_refresh_no_directory">Establece una carpeta de descarga para actualizar tus descargas</string>
|
||||
<string name="home_sync_starred_artists_title">Sincronizar artistas destacados</string>
|
||||
<string name="player_queue_load_queue">Cargar cola de reproducción</string>
|
||||
<string name="player_lyrics_download_content_description">Descargar letras para uso sin conexión</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Letras descargadas para uso sin conexión</string>
|
||||
<string name="player_lyrics_download_success">Letra guardada para uso sin conexión</string>
|
||||
<string name="settings_allow_playlist_duplicates">Permitir añadir pistas repetidas a la lista</string>
|
||||
<string name="settings_github_update_summary">Si se usa la versión de GitHub, la app comprobará nuevas actualizaciones del APK.</string>
|
||||
<string name="settings_support_summary">Participa en las discusiones y el soporte de la comunidad</string>
|
||||
<string name="settings_show_mini_shuffle_button">Mostrar el botón «Aleatorio»</string>
|
||||
<string name="settings_auto_download_lyrics">Descargar automáticamente las letras</string>
|
||||
<string name="starred_artist_sync_dialog_summary">Descargar los artistas destacados podría consumir una gran cantidad de datos.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si está habilitada, los artistas destacados se descargarán para uso sin conexión.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Si se habilita, los artistas destacados se descargarán para uso sin conexión.</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="exo_controls_heart_off_description">Eliminar de favoritos</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">Copiado %1$s al portapapeles</string>
|
||||
<string name="settings_album_detail">Mostrar los detalles del álbum</string>
|
||||
<string name="settings_album_detail_summary">Si está habilitada, muestra los detalles del álbum, como el género, el número de pistas, etc. en la página de álbum</string>
|
||||
<string name="settings_album_detail_summary">Si se habilita, muestra los detalles del álbum, como el género, el número de pistas, etc. en la página de álbum</string>
|
||||
<string name="asset_link_clipboard_label">Enlace de recurso de Tempus</string>
|
||||
<string name="asset_link_label_song">UID de la pista</string>
|
||||
<string name="asset_link_label_album">UID del álbum</string>
|
||||
@@ -505,4 +514,15 @@
|
||||
<string name="asset_link_error_album">No se ha podido abrir el álbum</string>
|
||||
<string name="asset_link_error_artist">No se ha podido abrir el artista</string>
|
||||
<string name="asset_link_error_playlist">No se ha podido abrir la lista de reproducción</string>
|
||||
<string name="settings_github_update">Actualizaciones</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Generando mix instantáneo…</string>
|
||||
<string name="player_queue_save_to_playlist">Guardar cola en una lista de reproducción</string>
|
||||
<string name="radio_editor_dialog_added">Emisora de radio añadida</string>
|
||||
<string name="settings_artist_sort_by_album_count">Ordenar artistas por número de álbumes</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Si se habilita, ordena los artistas por número de álbumes. En caso contrario, se ordenan por nombre.</string>
|
||||
<string name="folder_play_collecting">Obteniendo pistas de la carpeta…</string>
|
||||
<string name="folder_play_playing">Reproduciendo %d pistas</string>
|
||||
<string name="folder_play_no_songs">No se encontraron pistas en la carpeta</string>
|
||||
<string name="search_sort_title">Ordenar las búsquedas recientes cronológicamente</string>
|
||||
<string name="search_sort_summary">Si se habilita, se ordenan las búsquedas en orden cronológico. En caso contrario, se ordenan por nombre.</string>
|
||||
</resources>
|
||||
@@ -39,6 +39,7 @@
|
||||
<string name="artist_list_page_downloaded">Artistes téléchargés</string>
|
||||
<string name="artist_list_page_starred">Artistes favoris</string>
|
||||
<string name="artist_list_page_title">Artistes</string>
|
||||
<string name="artist_no_artist_info_toast">Pas d\'autre information sur l\'artiste</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Mélanger</string>
|
||||
<string name="artist_page_switch_layout_button">Changer la disposition</string>
|
||||
@@ -51,6 +52,8 @@
|
||||
<string name="battery_optimization_negative_button">Ignorer</string>
|
||||
<string name="battery_optimization_neutral_button">Ne pas me redemander</string>
|
||||
<string name="battery_optimization_positive_button">Désactiver</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Génération du mix instantané…</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Echec de récupération de pistes du serveur Subsonic.</string>
|
||||
<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>
|
||||
@@ -61,13 +64,14 @@
|
||||
<string name="delete_download_storage_dialog_positive_button">Continuer</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="description_empty_title">Paroles non disponibles</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>
|
||||
<string name="download_directory_dialog_title">Télécharger toutes les pistes.</string>
|
||||
<string name="download_directory_set">Répertoire de téléchargement de la musique</string>
|
||||
<string name="download_info_empty_subtitle">Dès que vous téléchargerez une musique, vous la trouverez ici</string>
|
||||
<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>
|
||||
@@ -78,7 +82,15 @@
|
||||
<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>
|
||||
<string name="download_storage_directory_dialog_neutral_button">Répertoire</string>
|
||||
<string name="download_title_section">Téléchargements</string>
|
||||
<string name="download_refresh_no_directory">Sélectionner un répertoire pour rafraîchir vos téléchargements.</string>
|
||||
<string name="download_refresh_no_changes">Aucun téléchargement manquant détecté.</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">%d téléchargement manquant retiré.</item>
|
||||
<item quantity="other">%d téléchargements manquants retirés.</item>
|
||||
</plurals>
|
||||
<string name="download_refresh_button_content_description">Rafraîchir les téléchargements</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Ajouter à la liste d\'attente</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Lire juste après</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Retirer</string>
|
||||
@@ -88,6 +100,9 @@
|
||||
<string name="error_required">Requis</string>
|
||||
<string name="error_server_prefix">préfixe http ou https requis</string>
|
||||
<string name="exo_download_notification_channel_name">Téléchargements</string>
|
||||
<string name="exo_controls_heart_off_description">Désactiver les boutons Cœur</string>
|
||||
<string name="exo_controls_heart_on_description">Activer les boutons Cœur</string>
|
||||
<string name="cast_expanded_controller_loading">Chargement…</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>
|
||||
@@ -118,7 +133,13 @@
|
||||
<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_sync_starred_albums_subtitle">Les albums favoris seront disponibles hors-ligne</string>
|
||||
<string name="home_sync_starred_artists_title">Synchroniser les artistes favoris</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Pour certains de vos artistes favoris, il reste des titres à télécharger</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d titre doit être synchronisé</item>
|
||||
<item quantity="other">%d titres doivent être synchronisés</item>
|
||||
</plurals>
|
||||
<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>
|
||||
@@ -184,6 +205,7 @@
|
||||
<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_album_count">Nombre d\'albums</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>
|
||||
@@ -195,6 +217,12 @@
|
||||
<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_queue_save_to_playlist">Sauvegarder la file d\'attente dans une playlist</string>
|
||||
<string name="player_queue_load_queue">Charger la file d\'attente</string>
|
||||
<string name="player_lyrics_download_content_description">Télécharger les paroles pour lecture hors-ligne</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Paroles téléchargées pour lecture hors-ligne</string>
|
||||
<string name="player_lyrics_download_success">Paroles sauvegardées pour lecture hors-ligne.</string>
|
||||
<string name="player_lyrics_download_failure">Impossible de télécharger les paroles.</string>
|
||||
<string name="player_server_priority">Priorité serveur</string>
|
||||
<string name="player_unknown_format">Format inconnu</string>
|
||||
<string name="player_transcoding">Transcodage</string>
|
||||
@@ -207,6 +235,7 @@
|
||||
<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_chooser_dialog_toast_all_skipped">Tous les titres ont été traités comme des doublons et ignorés</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>
|
||||
@@ -228,13 +257,14 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Parcourir les chaînes</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">Url RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Chaîne</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Ce serveur ne supporte pas les podcasts.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Description</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Épisodes</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Aucun épisode disponible</string>
|
||||
<string name="podcast_episode_download_request_snackbar">Votre requête a été envoyée au serveur</string>
|
||||
<string name="podcast_info_empty_button">Cliquez pour cacher la section\nLes changements seront visibles au redémarrage de l\'app</string>
|
||||
<string name="podcast_info_empty_subtitle">Dès que vous ajouterez une chaîne, vous la retrouverez ici</string>
|
||||
<string name="podcast_info_empty_title">Aucun podcast trouvé!</string>
|
||||
<string name="podcast_info_empty_title">Aucun podcast trouvé !</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL de la page d\'accueil de la radio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nom de la radio</string>
|
||||
@@ -242,10 +272,13 @@
|
||||
<string name="radio_editor_dialog_negative_button">Annuler</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Supprimer</string>
|
||||
<string name="radio_editor_dialog_positive_button">Enregistrer</string>
|
||||
<string name="radio_editor_dialog_title">Station Radio Internet</string>
|
||||
<string name="radio_editor_dialog_added">Station de radio ajoutée</string>
|
||||
<string name="radio_editor_dialog_updated">Station de radio modifiée</string>
|
||||
<string name="radio_editor_dialog_title">Station de radio Internet</string>
|
||||
<string name="radio_station_info_empty_button">Cliquez pour cacher la section\nLes changements seront visibles au redémarrage de l\'app</string>
|
||||
<string name="radio_station_info_empty_subtitle">Dès que vous ajouterez une station radio, vous la retrouverez ici</string>
|
||||
<string name="radio_station_info_empty_title">Aucune station trouvée!</string>
|
||||
<string name="radio_station_info_empty_title">Aucune station trouvée !</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Ce serveur ne supporte pas la gestion de radios Internet.</string>
|
||||
<string name="rating_dialog_negative_button">Annuler</string>
|
||||
<string name="rating_dialog_positive_button">Enregistrer</string>
|
||||
<string name="rating_dialog_title">Noter</string>
|
||||
@@ -273,6 +306,8 @@
|
||||
<string name="settings_about_summary">Tempus 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_allow_playlist_duplicates">Autoriser l\'ajout de doublons à une playlist</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Si activé, les doublons ne seront pas détectés à l\'ajout d\'un titre à une playlist.</string>
|
||||
<string name="settings_audio_transcode_download_format">Format de transcodage</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Si activé, Tempus 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>
|
||||
@@ -288,6 +323,8 @@
|
||||
<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_choose_download_folder">Sélectionner un répertoire pour le téléchargement des fichiers de musique</string>
|
||||
<string name="settings_clear_download_folder">Vider le répertoire de téléchargement</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>
|
||||
@@ -296,16 +333,26 @@
|
||||
<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_system_equalizer_summary">Ajuster les paramètres audios</string>
|
||||
<string name="settings_download_folder_cleared">Répertoire de téléchargement vidé.</string>
|
||||
<string name="settings_download_folder_set">Répertoire de téléchargement réglé</string>
|
||||
<string name="settings_set_download_folder">Régler le répertoire de téléchargement</string>
|
||||
<string name="settings_system_equalizer_summary">Ajuster les paramètres audio</string>
|
||||
<string name="settings_system_equalizer_title">Égaliseur du système</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Suivre le développement</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Mises à jour</string>
|
||||
<string name="settings_github_update_title">Vérifier les mises à jour sur Github</string>
|
||||
<string name="settings_github_update_summary">Si vous utilisez l\'app publiée sur Github, elle vérifiera par défaut les sorties de nouvelles versions. Cliquer ici pour désactiver les vérifications automatiques</string>
|
||||
<string name="settings_support_summary">Rejoindre les discussions et le support de la communauté</string>
|
||||
<string name="settings_support_title">Support utilisateur</string>
|
||||
<string name="settings_scan_result">Analyse : comptage de %1$d pistes</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">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_mobile">Débit binaire en données mobiles</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>
|
||||
@@ -320,14 +367,18 @@
|
||||
<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>
|
||||
<string name="settings_queue_syncing_title">Synchroniser la file d\'attente pour cet utilisateur [fonctionnalité en évolution]</string>
|
||||
<string name="settings_show_mini_shuffle_button">Afficher le bouton Mélanger</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Si activé, le bouton Mélanger sera visible et le bouton Cœur caché sur le mini-lecteur</string>
|
||||
<string name="settings_radio">Voir les radios</string>
|
||||
<string name="settings_radio_summary">Si activé, rend visible la section Radio</string>
|
||||
<string name="settings_radio_summary">Si activé, rend visible la section Radio. Redémarrez l\'application pour appliquer ce paramètre.</string>
|
||||
<string name="settings_auto_download_lyrics">Téléchargement automatique des paroles</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Ce paramètre active la sauvegarde automatique des paroles pour affichage hors-ligne, si elles sont disponibles.</string>
|
||||
<string name="settings_replay_gain">Ajuster le Replay Gain</string>
|
||||
<string name="settings_rounded_corner">Coins arrondis</string>
|
||||
<string name="settings_rounded_corner_size">Taille des arrondis</string>
|
||||
<string name="settings_rounded_corner_size_summary">Définit l\'ampleur de l\'angle de courbure.</string>
|
||||
<string name="settings_rounded_corner_summary">Si activé, arrondi les angles des illustrations. Les modifications prendront effet au redémarrage.</string>
|
||||
<string name="settings_rounded_corner_summary">Si activé, arrondit 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>
|
||||
@@ -344,13 +395,16 @@
|
||||
<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_artists_for_offline_use_summary">Si activé, les titres des artistes favoris seront téléchargés pour l\'écoute hors-ligne.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Synchronisation des artistes favoris pour écoute hors-ligne</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_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>
|
||||
<string name="settings_title_data">Données</string>
|
||||
<string name="settings_title_general">Géneral</string>
|
||||
<string name="settings_title_playlist">Playlist</string>
|
||||
<string name="settings_title_rating">Note</string>
|
||||
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
@@ -369,7 +423,24 @@
|
||||
<string name="share_bottom_sheet_delete">Supprimer le partage</string>
|
||||
<string name="share_bottom_sheet_update">Mettre à jour le partage</string>
|
||||
<string name="share_subtitle_item">Date d\'expiration : %1$s</string>
|
||||
<string name="share_no_expiration">Jamais</string>
|
||||
<string name="share_unsupported_error">Le partage n\'est pas supporté ou pas activé</string>
|
||||
<string name="asset_link_clipboard_label">Lien ressource Tempus</string>
|
||||
<string name="asset_link_label_song">UID titre</string>
|
||||
<string name="asset_link_label_album">UID album</string>
|
||||
<string name="asset_link_label_artist">UID artiste</string>
|
||||
<string name="asset_link_label_playlist">UID playlist</string>
|
||||
<string name="asset_link_label_genre">UID genre</string>
|
||||
<string name="asset_link_label_year">UID année</string>
|
||||
<string name="asset_link_label_unknown">UID ressource</string>
|
||||
<string name="asset_link_error_unsupported">Lien ressource non supportée</string>
|
||||
<string name="asset_link_error_song">Echec d\'accès au titre</string>
|
||||
<string name="asset_link_error_album">Echec d\'accès à l\'album</string>
|
||||
<string name="asset_link_error_artist">Echec d\'accès à l\'artiste</string>
|
||||
<string name="asset_link_error_playlist">Echec d\'accès à la playlist</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">%1$s ressource(s) copiée(s) dans le presse-papier</string>
|
||||
<string name="asset_link_debug_toast">Lien ressource : %1$s</string>
|
||||
<string name="share_update_dialog_hint_description">Description</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Date d\'expiration</string>
|
||||
<string name="share_update_dialog_negative_button">Annuler</string>
|
||||
@@ -400,7 +471,9 @@
|
||||
<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 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_artist_sync_dialog_summary">Le téléchargement des titres d\'artistes favoris pourrait consommer beaucoup de données.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Synchroniser les artistes favoris</string>
|
||||
<string name="starred_album_sync_dialog_summary">Le téléchargement des albums 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>
|
||||
@@ -436,14 +509,45 @@
|
||||
<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>
|
||||
<string name="widget_label">Widget Tempus</string>
|
||||
<string name="widget_not_playing">Pas de lecture</string>
|
||||
<string name="widget_placeholder_subtitle">Ouvrir Tempus</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_content_desc_album_art">Couverture de l\'album</string>
|
||||
<string name="widget_content_desc_play_pause">Lecture ou pause</string>
|
||||
<string name="widget_content_desc_next">Piste suivante</string>
|
||||
<string name="widget_content_desc_prev">Piste précédente</string>
|
||||
<string name="widget_content_desc_shuffle">Activer/désactiver le mélange</string>
|
||||
<string name="widget_content_desc_repeat">Changer le mode de répétition</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album à synchroniser</item>
|
||||
<item quantity="other">%d albums à synchroniser</item>
|
||||
</plurals>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">%d artiste à synchroniser</item>
|
||||
<item quantity="other">%d artistes à synchroniser</item>
|
||||
</plurals>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">Téléchargement de %d titre</item>
|
||||
<item quantity="other">Téléchargement de %d titres</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Égaliseur</string>
|
||||
<string name="equalizer_reset">Réinitialiser</string>
|
||||
<string name="equalizer_enable">Activer</string>
|
||||
<string name="equalizer_not_supported">Non supporté sur cet appareil</string>
|
||||
<string name="settings_app_equalizer">Égaliseur</string>
|
||||
<string name="settings_app_equalizer_summary">Ouvrir l\'égaliseur intégré</string>
|
||||
|
||||
<string name="settings_album_detail">Afficher les détails de l\'album</string>
|
||||
<string name="settings_album_detail_summary">Si activé, affiche les détails de l\'album tels que le genre, le nombre de titres etc. sur l\'écran de l\'album</string>
|
||||
<string name="settings_artist_sort_by_album_count">Trier les artistes par nombre d\'albums</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Si activé, les artistes seront triés par ordre des nombres d\'albums. Si désactivé, ils le seront par ordre alphabétique.</string>
|
||||
|
||||
<string name="folder_play_collecting">Collecte des titres du répertoire…</string>
|
||||
<string name="folder_play_playing">Lecture de %d titres</string>
|
||||
<string name="folder_play_no_songs">Aucun titre trouvé dans le répertoire</string>
|
||||
|
||||
<string name="search_sort_title">Trier les recherches récentes par ordre chronologique</string>
|
||||
<string name="search_sort_summary">Si activé, les recherches récentes seront triées par ordre chronologique. Si désactivé, elles le seront par ordre alphabétique.</string>
|
||||
</resources>
|
||||
|
||||
@@ -254,4 +254,4 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -1,417 +1,536 @@
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_conclusion">Se hai problemi, visita https://dontkillmyapp.com. Qui trovi istruzioni dettagliate su come disabilitare le funzionalità di risparmio energetico che potrebbero influire sulle prestazioni dell\'app.</string>
|
||||
<string name="activity_battery_optimizations_summary">Per favore, disabilita le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.</string>
|
||||
<string name="activity_battery_optimizations_title">Ottimizzazioni della Batteria</string>
|
||||
<string name="activity_info_offline_mode">Modalità offline</string>
|
||||
<string name="activity_battery_optimizations_conclusion">Se hai problemi, visita https://dontkillmyapp.com. Qui trovi istruzioni dettagliate su come disabilitare le funzionalità di risparmio energetico che potrebbero influire sulle prestazioni dell\'app.</string>
|
||||
<string name="activity_battery_optimizations_summary">Disattiva le ottimizzazioni della batteria per la riproduzione multimediale quando lo schermo è spento.</string>
|
||||
<string name="activity_battery_optimizations_title">Ottimizzazioni della Batteria</string>
|
||||
<string name="activity_info_offline_mode">Modalità offline</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Aggiungi alla playlist</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="album_bottom_sheet_download_all">Scarica tutto</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Vai all\'artista</string>
|
||||
<string name="album_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="album_bottom_sheet_play_next">Riproduci successivo</string>
|
||||
<string name="album_bottom_sheet_remove_all">Rimuovi tutto</string>
|
||||
<string name="album_bottom_sheet_share">Condividi</string>
|
||||
<string name="album_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="album_catalogue_title">Album</string>
|
||||
<string name="album_catalogue_title_expanded">Sfoglia Album</string>
|
||||
<string name="album_error_retrieving_artist">Errore nel recupero dell\'artista</string>
|
||||
<string name="album_list_page_downloaded">Album scaricati</string>
|
||||
<string name="album_list_page_most_played">Album più riprodotti</string>
|
||||
<string name="album_list_page_new_releases">Nuove uscite</string>
|
||||
<string name="album_list_page_recently_added">Album aggiunti di recente</string>
|
||||
<string name="album_list_page_recently_played">Album riprodotti di recente</string>
|
||||
<string name="album_list_page_starred">Album preferiti</string>
|
||||
<string name="album_list_page_title">Album</string>
|
||||
<string name="album_page_extra_info_button">Simili a questo</string>
|
||||
<string name="album_page_play_button">Riproduci</string>
|
||||
<string name="album_page_release_date_label">Rilasciato il %1$s</string>
|
||||
<string name="album_page_release_dates_label">Rilasciato il %1$s, originariamente il %2$s</string>
|
||||
<string name="album_page_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d brani • %2$d minuti</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="artist_adapter_radio_station_starting">Ricerca in corso…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="artist_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="artist_catalogue_title">Artisti</string>
|
||||
<string name="artist_catalogue_title_expanded">Sfoglia Artisti</string>
|
||||
<string name="artist_error_retrieving_radio">Errore nel recupero della radio dell\'artista</string>
|
||||
<string name="artist_error_retrieving_tracks">Errore nel recupero dei brani dell\'artista</string>
|
||||
<string name="artist_list_page_downloaded">Artisti scaricati</string>
|
||||
<string name="artist_list_page_starred">Artisti preferiti</string>
|
||||
<string name="artist_list_page_title">Artisti</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="artist_page_switch_layout_button">Cambia layout</string>
|
||||
<string name="artist_page_title_album_more_like_this_button">Simili a questo</string>
|
||||
<string name="artist_page_title_album_section">Album</string>
|
||||
<string name="artist_page_title_biography_more_button">Altro</string>
|
||||
<string name="artist_page_title_biography_section">Biografia</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">Brani più ascoltati</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">Vedi tutto</string>
|
||||
<string name="battery_optimization_negative_button">Ignora</string>
|
||||
<string name="battery_optimization_neutral_button">Non chiedere di nuovo</string>
|
||||
<string name="battery_optimization_positive_button">Disabilita</string>
|
||||
<string name="connection_alert_dialog_negative_button">Annulla</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Attiva risparmio dati</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
<string name="connection_alert_dialog_summary">L\'accesso al server Subsonic è stato limitato alle connessioni Wi-Fi. Per evitare che questo avviso riappaia, disabilita il controllo connessione nelle impostazioni dell\'app.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi non connesso</string>
|
||||
<string name="content_description_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Annulla</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continua</string>
|
||||
<string name="delete_download_storage_dialog_summary">Attenzione, procedendo questa azione eliminerà definitivamente tutti gli elementi scaricati da tutti i server.</string>
|
||||
<string name="delete_download_storage_dialog_title">Elimina elementi salvati</string>
|
||||
<string name="description_empty_title">Descrizione non disponibile</string>
|
||||
<string name="disc_titlefull">Disco %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disco %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Annulla</string>
|
||||
<string name="download_directory_dialog_positive_button">Scarica</string>
|
||||
<string name="download_directory_dialog_summary">Tutti i brani in questa cartella verranno scaricati. I brani nelle sottocartelle non verranno scaricati.</string>
|
||||
<string name="download_directory_dialog_title">Scarica i brani</string>
|
||||
<string name="download_info_empty_subtitle">Una volta scaricato un brano, lo troverai qui</string>
|
||||
<string name="download_info_empty_title">Nessun download ancora!</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elementi</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s elementi</string>
|
||||
<string name="download_shuffle_all_subtitle">Riproduzione casuale di tutto</string>
|
||||
<string name="download_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
|
||||
<string name="download_storage_dialog_summary">Cambiare la destinazione dei file scaricati da una memoria all\'altra eliminerà immediatamente tutti i file scaricati precedentemente nella vecchia memoria.</string>
|
||||
<string name="download_storage_dialog_title">Seleziona opzione di memoria</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Esterna</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Interna</string>
|
||||
<string name="download_title_section">Download</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Riproduci successivo</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">Rimuovi tutto</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Obbligatorio</string>
|
||||
<string name="error_server_prefix">Prefisso http o https richiesto</string>
|
||||
<string name="exo_download_notification_channel_name">Download</string>
|
||||
<string name="filter_info_selection">Seleziona due o più filtri</string>
|
||||
<string name="filter_title">Filtro</string>
|
||||
<string name="filter_title_expanded">Filtra Generi</string>
|
||||
<string name="genre_catalogue_title">Catalogo dei Generi</string>
|
||||
<string name="genre_catalogue_title_expanded">Sfoglia Generi</string>
|
||||
<string name="github_update_dialog_negative_button">Ricordamelo più tardi</string>
|
||||
<string name="github_update_dialog_neutral_button">Supportami</string>
|
||||
<string name="github_update_dialog_positive_button">Scarica ora</string>
|
||||
<string name="github_update_dialog_summary">È disponibile una nuova versione dell\'app su Github.</string>
|
||||
<string name="github_update_dialog_title">Aggiornamento disponibile</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">Annulla</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Reimposta</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Salva</string>
|
||||
<string name="home_rearrangement_dialog_title">Riorganizza home</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Si prega di notare che per rendere effettive le modifiche è necessario riavviare l\'applicazione.</string>
|
||||
<string name="home_subtitle_best_of">Le migliori canzoni dei tuoi artisti preferiti</string>
|
||||
<string name="home_subtitle_made_for_you">Inizia un mix da una canzone che ti è piaciuta</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Aggiungi una nuova radio</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Aggiungi un nuovo canale podcast</string>
|
||||
<string name="home_sync_starred_cancel">Annulla</string>
|
||||
<string name="home_sync_starred_download">Scarica</string>
|
||||
<string name="home_sync_starred_subtitle">Scaricare questi brani potrebbe comportare un uso significativo di dati</string>
|
||||
<string name="home_sync_starred_title">Sembra che ci siano brani da sincronizzare con una stella</string>
|
||||
<string name="home_title_best_of">Il meglio di</string>
|
||||
<string name="home_title_discovery">Scoperta</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Mescola tutto</string>
|
||||
<string name="home_title_flashback">Flashback</string>
|
||||
<string name="home_title_internet_radio_station">Stazioni radio internet</string>
|
||||
<string name="home_title_last_played">Ultimi ascolti</string>
|
||||
<string name="home_title_last_played_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_last_week">La scorsa settimana</string>
|
||||
<string name="home_title_last_month">Il mese scorso</string>
|
||||
<string name="home_title_last_year">L\'anno scorso</string>
|
||||
<string name="home_title_made_for_you">Fatto per te</string>
|
||||
<string name="home_title_most_played">Più ascoltati</string>
|
||||
<string name="home_title_most_played_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_new_releases">Nuove uscite</string>
|
||||
<string name="home_title_newest_podcasts">Podcast più recenti</string>
|
||||
<string name="home_title_pinned_playlists">Playlist</string>
|
||||
<string name="home_title_podcast_channels">Canali</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_radio_station">Stazioni radio</string>
|
||||
<string name="home_title_recently_added">Aggiunti di recente</string>
|
||||
<string name="home_title_recently_added_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_shares">Condivisioni</string>
|
||||
<string name="home_title_starred_albums">★ Album con stella</string>
|
||||
<string name="home_title_starred_albums_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_starred_artists">★ Artisti con stella</string>
|
||||
<string name="home_title_starred_artists_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_starred_tracks">★ Brani con stella</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_top_songs">I tuoi migliori brani</string>
|
||||
<string name="home_option_reorganize">Riorganizza</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="library_title_album">Album</string>
|
||||
<string name="library_title_album_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_artist">Artisti</string>
|
||||
<string name="library_title_artist_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_genre">Generi</string>
|
||||
<string name="library_title_genre_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_music_folder">Cartelle musicali</string>
|
||||
<string name="library_title_playlist">Playlist</string>
|
||||
<string name="library_title_playlist_see_all_button">Vedi tutto</string>
|
||||
<string name="login_empty">Nessun server aggiunto</string>
|
||||
<string name="login_title">Server Subsonic</string>
|
||||
<string name="login_title_expanded">Server Subsonic</string>
|
||||
<string name="media_route_menu_title">Trasmetti</string>
|
||||
<string name="menu_add_button">Aggiungi</string>
|
||||
<string name="album_bottom_sheet_download_all">Scarica tutto</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Vai all\'artista</string>
|
||||
<string name="album_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="album_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="album_bottom_sheet_remove_all">Rimuovi tutto</string>
|
||||
<string name="album_bottom_sheet_share">Condividi</string>
|
||||
<string name="album_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="album_catalogue_title">Album</string>
|
||||
<string name="album_catalogue_title_expanded">Sfoglia Album</string>
|
||||
<string name="album_error_retrieving_artist">Errore nel recupero dell\'artista</string>
|
||||
<string name="album_list_page_downloaded">Album scaricati</string>
|
||||
<string name="album_list_page_most_played">Album più riprodotti</string>
|
||||
<string name="album_list_page_new_releases">Nuove uscite</string>
|
||||
<string name="album_list_page_recently_added">Album aggiunti di recente</string>
|
||||
<string name="album_list_page_recently_played">Album riprodotti di recente</string>
|
||||
<string name="album_list_page_starred">Album preferiti</string>
|
||||
<string name="album_list_page_title">Album</string>
|
||||
<string name="album_page_extra_info_button">Altri simili</string>
|
||||
<string name="album_page_play_button">Riproduci</string>
|
||||
<string name="album_page_release_date_label">Rilasciato il %1$s</string>
|
||||
<string name="album_page_release_dates_label">Rilasciato il %1$s, originariamente il %2$s</string>
|
||||
<string name="album_page_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d brani • %2$d minuti</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="artist_adapter_radio_station_starting">Cercando…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="artist_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="artist_catalogue_title">Artisti</string>
|
||||
<string name="artist_catalogue_title_expanded">Sfoglia Artisti</string>
|
||||
<string name="artist_error_retrieving_radio">Errore nel recupero della radio dell\'artista</string>
|
||||
<string name="artist_error_retrieving_tracks">Errore nel recupero dei brani dell\'artista</string>
|
||||
<string name="artist_list_page_downloaded">Artisti scaricati</string>
|
||||
<string name="artist_list_page_starred">Artisti preferiti</string>
|
||||
<string name="artist_list_page_title">Artisti</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="artist_page_switch_layout_button">Cambia layout</string>
|
||||
<string name="artist_page_title_album_more_like_this_button">Altri simili</string>
|
||||
<string name="artist_page_title_album_section">Album</string>
|
||||
<string name="artist_page_title_biography_more_button">Altro</string>
|
||||
<string name="artist_page_title_biography_section">Biografia</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">Brani più ascoltati</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">Vedi tutto</string>
|
||||
<string name="battery_optimization_negative_button">Ignora</string>
|
||||
<string name="battery_optimization_neutral_button">Non chiedere di nuovo</string>
|
||||
<string name="battery_optimization_positive_button">Disabilita</string>
|
||||
<string name="connection_alert_dialog_negative_button">Annulla</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Attiva risparmio dati</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
<string name="connection_alert_dialog_summary">L\'accesso al server Subsonic è stato limitato alle connessioni Wi-Fi. Per evitare che questo avviso riappaia, disabilita il controllo connessione nelle impostazioni dell\'app.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi non connesso</string>
|
||||
<string name="content_description_shuffle_button">Riproduzione casuale</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Annulla</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continua</string>
|
||||
<string name="delete_download_storage_dialog_summary">Attenzione, procedendo questa azione eliminerà definitivamente tutti gli elementi scaricati da tutti i server.</string>
|
||||
<string name="delete_download_storage_dialog_title">Elimina elementi salvati</string>
|
||||
<string name="description_empty_title">Testo non disponibile</string>
|
||||
<string name="disc_titlefull">Disco %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disco %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Annulla</string>
|
||||
<string name="download_directory_dialog_positive_button">Scarica</string>
|
||||
<string name="download_directory_dialog_summary">Tutti i brani in questa cartella verranno scaricati. I brani nelle sottocartelle non verranno scaricati.</string>
|
||||
<string name="download_directory_dialog_title">Scarica i brani</string>
|
||||
<string name="download_directory_set">Imposta dove scaricare la musica</string>
|
||||
<string name="download_info_empty_subtitle">Una volta scaricato un brano, lo troverai qui</string>
|
||||
<string name="download_info_empty_title">Ancora nessun download!</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s elementi</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s elementi</string>
|
||||
<string name="download_shuffle_all_subtitle">Riproduzione casuale di tutto</string>
|
||||
<string name="download_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
|
||||
<string name="download_storage_dialog_summary">Cambiare la destinazione dei file scaricati da una memoria all\'altra eliminerà immediatamente tutti i file scaricati precedentemente nella vecchia memoria.</string>
|
||||
<string name="download_storage_dialog_title">Seleziona opzione di memoria</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Esterna</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Interna</string>
|
||||
<string name="download_storage_directory_dialog_neutral_button">Cartella</string>
|
||||
<string name="download_title_section">Scarica</string>
|
||||
<string name="download_refresh_no_directory">Imposta una cartella di download per aggiornare i tuoi download.</string>
|
||||
<string name="download_refresh_no_changes">Nessun download mancante trovato.</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">Rimosso %d download mancante.</item>
|
||||
<item quantity="other">Rimossi %d download mancanti.</item>
|
||||
</plurals>
|
||||
<string name="download_refresh_button_content_description">Aggiorna gli elementi scaricati</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">Rimuovi tutto</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">Riproduzione casuale</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Obbligatorio</string>
|
||||
<string name="error_server_prefix">Prefisso http o https richiesto</string>
|
||||
<string name="exo_download_notification_channel_name">Download</string>
|
||||
<string name="exo_controls_heart_off_description">Aggiungi ai preferiti</string>
|
||||
<string name="exo_controls_heart_on_description">Rimuovi dai preferiti</string>
|
||||
<string name="cast_expanded_controller_loading">Caricamento…</string>
|
||||
<string name="filter_info_selection">Seleziona due o più filtri</string>
|
||||
<string name="filter_title">Filtro</string>
|
||||
<string name="filter_artist">Filtra artisti</string>
|
||||
<string name="filter_title_expanded">Filtra Generi</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">Catalogo dei Generi</string>
|
||||
<string name="genre_catalogue_title_expanded">Sfoglia Generi</string>
|
||||
<string name="github_update_dialog_negative_button">Ricordamelo più tardi</string>
|
||||
<string name="github_update_dialog_neutral_button">Supportami</string>
|
||||
<string name="github_update_dialog_positive_button">Scarica ora</string>
|
||||
<string name="github_update_dialog_summary">È disponibile una nuova versione dell\'app su Github.</string>
|
||||
<string name="github_update_dialog_title">Aggiornamento disponibile</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">Annulla</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Ripristina</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Salva</string>
|
||||
<string name="home_rearrangement_dialog_title">Riorganizza home</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Per rendere effettive le modifiche è necessario riavviare l\'applicazione.</string>
|
||||
<string name="home_section_music">Musica</string>
|
||||
<string name="home_section_podcast">Podcast</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Le migliori canzoni dei tuoi artisti preferiti</string>
|
||||
<string name="home_subtitle_made_for_you">Inizia un mix da una canzone che ti è piaciuta</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Aggiungi una nuova radio</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Aggiungi un nuovo canale podcast</string>
|
||||
<string name="home_sync_starred_cancel">Annulla</string>
|
||||
<string name="home_sync_starred_download">Scarica</string>
|
||||
<string name="home_sync_starred_subtitle">Scaricare questi brani potrebbe comportare un uso significativo di dati</string>
|
||||
<string name="home_sync_starred_title">Sembra che ci siano alcuni brani preferiti da sincronizzare</string>
|
||||
<string name="home_sync_starred_albums_title">Sincronizza Album Preferiti</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Gli album preferiti saranno disponibili offline</string>
|
||||
<string name="home_sync_starred_artists_title">Sincronizza Artisti Preferiti</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Hai artisti preferiti con musica non scaricata</string>
|
||||
<string name="home_title_best_of">Il meglio di</string>
|
||||
<string name="home_title_discovery">Scopri</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Mescola tutto</string>
|
||||
<string name="home_title_flashback">Flashback</string>
|
||||
<string name="home_title_internet_radio_station">Stazioni internet-radio</string>
|
||||
<string name="home_title_last_played">Ultimi ascolti</string>
|
||||
<string name="home_title_last_played_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_last_week">La scorsa settimana</string>
|
||||
<string name="home_title_last_month">Il mese scorso</string>
|
||||
<string name="home_title_last_year">L\'anno scorso</string>
|
||||
<string name="home_title_made_for_you">Fatto per te</string>
|
||||
<string name="home_title_most_played">Più ascoltati</string>
|
||||
<string name="home_title_most_played_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_new_releases">Nuove uscite</string>
|
||||
<string name="home_title_newest_podcasts">Podcast più recenti</string>
|
||||
<string name="home_title_pinned_playlists">Playlist</string>
|
||||
<string name="home_title_podcast_channels">Canali</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_radio_station">Stazioni radio</string>
|
||||
<string name="home_title_recently_added">Aggiunti di recente</string>
|
||||
<string name="home_title_recently_added_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_shares">Condivisioni</string>
|
||||
<string name="home_title_starred_albums">★ Album preferiti</string>
|
||||
<string name="home_title_starred_albums_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_starred_artists">★ Artisti preferiti</string>
|
||||
<string name="home_title_starred_artists_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_starred_tracks">★ Brani preferiti</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Vedi tutto</string>
|
||||
<string name="home_title_top_songs">I tuoi migliori brani</string>
|
||||
<string name="home_option_reorganize">Riorganizza</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="library_title_album">Album</string>
|
||||
<string name="library_title_album_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_artist">Artisti</string>
|
||||
<string name="library_title_artist_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_genre">Generi</string>
|
||||
<string name="library_title_genre_see_all_button">Vedi tutto</string>
|
||||
<string name="library_title_music_folder">Cartelle della musica</string>
|
||||
<string name="library_title_playlist">Playlist</string>
|
||||
<string name="library_title_playlist_see_all_button">Vedi tutto</string>
|
||||
<string name="login_empty">Nessun server aggiunto</string>
|
||||
<string name="login_title">Server Subsonic</string>
|
||||
<string name="login_title_expanded">Server Subsonic</string>
|
||||
<string name="media_route_menu_title">Trasmetti</string>
|
||||
<string name="menu_add_button">Aggiungi</string>
|
||||
<string name="menu_add_to_playlist_button">Aggiungi alla playlist</string>
|
||||
<string name="menu_download_all_button">Scarica tutto</string>
|
||||
<string name="menu_download_label">Scarica</string>
|
||||
<string name="menu_filter_all">Tutti</string>
|
||||
<string name="menu_filter_download">Scaricati</string>
|
||||
<string name="menu_group_by_album">Album</string>
|
||||
<string name="menu_group_by_artist">Artista</string>
|
||||
<string name="menu_group_by_genre">Genere</string>
|
||||
<string name="menu_group_by_track">Brano</string>
|
||||
<string name="menu_group_by_year">Anno</string>
|
||||
<string name="menu_home_label">Home</string>
|
||||
<string name="menu_last_week_name">La scorsa settimana</string>
|
||||
<string name="menu_last_month_name">Il mese scorso</string>
|
||||
<string name="menu_last_year_name">L\'anno scorso</string>
|
||||
<string name="menu_library_label">Libreria</string>
|
||||
<string name="menu_search_button">Cerca</string>
|
||||
<string name="menu_settings_button">Impostazioni</string>
|
||||
<string name="menu_sort_artist">Artista</string>
|
||||
<string name="menu_sort_name">Nome</string>
|
||||
<string name="menu_sort_random">Casuale</string>
|
||||
<string name="menu_sort_recently_added">Aggiunti di recente</string>
|
||||
<string name="menu_pin_button">Aggiungi alla schermata home</string>
|
||||
<string name="menu_unpin_button">Rimuovi dalla schermata home</string>
|
||||
<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="menu_rate_album">Valuta l\'album</string>
|
||||
<string name="menu_download_label">Scarica</string>
|
||||
<string name="menu_filter_all">Tutti</string>
|
||||
<string name="menu_filter_download">Scaricati</string>
|
||||
<string name="menu_group_by_album">Album</string>
|
||||
<string name="menu_group_by_artist">Artista</string>
|
||||
<string name="menu_group_by_genre">Genere</string>
|
||||
<string name="menu_group_by_track">Brano</string>
|
||||
<string name="menu_group_by_year">Anno</string>
|
||||
<string name="menu_home_label">Home</string>
|
||||
<string name="menu_last_week_name">La scorsa settimana</string>
|
||||
<string name="menu_last_month_name">Il mese scorso</string>
|
||||
<string name="menu_last_year_name">L\'anno scorso</string>
|
||||
<string name="menu_library_label">Libreria</string>
|
||||
<string name="menu_search_button">Cerca</string>
|
||||
<string name="menu_settings_button">Impostazioni</string>
|
||||
<string name="menu_sort_artist">Artista</string>
|
||||
<string name="menu_sort_name">Nome</string>
|
||||
<string name="menu_sort_random">Casuale</string>
|
||||
<string name="menu_sort_album_count">Numero di Album</string>
|
||||
<string name="menu_sort_recently_added">Aggiunti di recente</string>
|
||||
<string name="menu_sort_recently_played">Riprodotti di recente</string>
|
||||
<string name="menu_sort_most_played">Più riprodotti</string>
|
||||
<string name="menu_sort_most_recently_starred">Preferiti più recentemente</string>
|
||||
<string name="menu_sort_least_recently_starred">Preferiti meno recentemente</string>
|
||||
<string name="menu_pin_button">Aggiungi alla schermata home</string>
|
||||
<string name="menu_unpin_button">Rimuovi dalla schermata home</string>
|
||||
<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>
|
||||
<string name="playlist_chooser_dialog_empty">Nessuna playlist creata</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Annulla</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Crea</string>
|
||||
<string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Aggiunta di un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Impossibile aggiungere un brano alla playlist</string>
|
||||
<string name="playlist_counted_tracks">%1$d brani • %2$s</string>
|
||||
<string name="playlist_duration">Durata • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nome della playlist</string>
|
||||
<string name="playlist_editor_dialog_negative_button">Annulla</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Elimina</string>
|
||||
<string name="playlist_editor_dialog_positive_button">Salva</string>
|
||||
<string name="playlist_editor_dialog_title">Modifica playlist</string>
|
||||
<string name="playlist_page_play_button">Riproduci</string>
|
||||
<string name="playlist_page_shuffle_button">Mescola</string>
|
||||
<string name="playlist_song_count">Playlist • %1$d brani</string>
|
||||
<string name="podcast_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="podcast_bottom_sheet_delete">Elimina</string>
|
||||
<string name="podcast_bottom_sheet_download">Scarica</string>
|
||||
<string name="podcast_bottom_sheet_go_to_channel">Vai al canale</string>
|
||||
<string name="podcast_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="podcast_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="podcast_channel_catalogue_title">Canali</string>
|
||||
<string name="podcast_channel_catalogue_title_expanded">Sfoglia Canali</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canale Podcast</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descrizione</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodi</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Nessun episodio disponibile</string>
|
||||
<string name="podcast_episode_download_request_snackbar">La tua richiesta è stata inviata al server</string>
|
||||
<string name="podcast_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
|
||||
<string name="podcast_info_empty_subtitle">Una volta aggiunto un canale, lo troverai qui</string>
|
||||
<string name="podcast_info_empty_title">Nessun podcast trovato!</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL Homepage Radio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nome Radio</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">URL Stream Radio</string>
|
||||
<string name="radio_editor_dialog_negative_button">Annulla</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Elimina</string>
|
||||
<string name="radio_editor_dialog_positive_button">Salva</string>
|
||||
<string name="radio_editor_dialog_title">Stazione Radio Internet</string>
|
||||
<string name="radio_station_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
|
||||
<string name="radio_station_info_empty_subtitle">Una volta aggiunta una stazione radio, la troverai qui</string>
|
||||
<string name="radio_station_info_empty_title">Nessuna stazione trovata!</string>
|
||||
<string name="rating_dialog_negative_button">Annulla</string>
|
||||
<string name="rating_dialog_positive_button">Salva</string>
|
||||
<string name="rating_dialog_title">Valuta</string>
|
||||
<string name="search_hint">Cerca titolo, artisti o album</string>
|
||||
<string name="search_info_minimum_characters">Inserisci almeno tre caratteri</string>
|
||||
<string name="search_title_album">Album</string>
|
||||
<string name="search_title_artist">Artisti</string>
|
||||
<string name="search_title_song">Brani</string>
|
||||
<string name="server_signup_dialog_action_low_security">Bassa sicurezza</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Premi a lungo per eliminare</string>
|
||||
<string name="server_signup_dialog_hint_local_address">URL locale</string>
|
||||
<string name="server_signup_dialog_hint_name">Nome Server</string>
|
||||
<string name="server_signup_dialog_hint_password">Password</string>
|
||||
<string name="server_signup_dialog_hint_url">URL Server</string>
|
||||
<string name="server_signup_dialog_hint_username">Nome utente</string>
|
||||
<string name="server_signup_dialog_negative_button">Annulla</string>
|
||||
<string name="server_signup_dialog_neutral_button">Elimina</string>
|
||||
<string name="server_signup_dialog_positive_button">Salva</string>
|
||||
<string name="server_signup_dialog_title">Aggiungi server</string>
|
||||
<string name="server_unreachable_dialog_negative_button">Annulla</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Vai al login</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continua comunque</string>
|
||||
<string name="server_unreachable_dialog_summary">Il server richiesto non è disponibile. Se scegli di continuare, questo messaggio non apparirà per la prossima ora.</string>
|
||||
<string name="server_unreachable_dialog_title">Server irraggiungibile</string>
|
||||
<string name="settings_about_summary">Tempus è un client musicale open source e leggero per Subsonic, progettato e costruito nativamente per Android.</string>
|
||||
<string name="settings_about_title">Informazioni</string>
|
||||
<string name="settings_always_on_display">Sempre attivo</string>
|
||||
<string name="settings_audio_transcode_download_format">Formato transcodifica</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Se abilitato, Tempus non forzerà il download del brano con le impostazioni di transcodifica sottostanti.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Dare priorità alle impostazioni del server per lo streaming nei download</string>
|
||||
<string name="settings_audio_transcode_download_summary">Se abilitato, Tempus scaricherà i brani transcodificati.</string>
|
||||
<string name="settings_audio_transcode_download_title">Scarica brani transcodificati</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Se abilitato, verrà richiesto al server di fornire la durata stimata del brano.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Stima della lunghezza del contenuto</string>
|
||||
<string name="settings_audio_transcode_format_download">Formato transcodifica per download</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Formato transcodifica su mobile</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Formato transcodifica su Wi-Fi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Se abilitato, Tempus non forzerà lo streaming del brano con le impostazioni di transcodifica sottostanti.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Dare priorità alle impostazioni di transcodifica del server</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Priorità di transcodifica del brano assegnata al server</string>
|
||||
<string name="settings_buffering_strategy">Strategia di buffering</string>
|
||||
<string name="settings_buffering_strategy_summary">Perché la modifica abbia effetto è necessario riavviare manualmente l\'app.</string>
|
||||
<string name="settings_continuous_play_summary">Consente alla musica di continuare a suonare dopo la fine di una playlist, riproducendo brani simili</string>
|
||||
<string name="settings_continuous_play_title">Riproduzione continua</string>
|
||||
<string name="settings_covers_cache">Dimensione della cache delle copertine</string>
|
||||
<string name="settings_data_saving_mode_summary">Per ridurre il consumo di dati, evita di scaricare le copertine.</string>
|
||||
<string name="settings_data_saving_mode_title">Limita utilizzo dei dati mobili</string>
|
||||
<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_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/tempus</string>
|
||||
<string name="settings_github_summary">Segui lo sviluppo</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_image_size">Imposta risoluzione delle immagini</string>
|
||||
<string name="settings_language">Lingua</string>
|
||||
<string name="settings_logout_title">Esci</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate per download</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate su mobile</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate su Wi-Fi</string>
|
||||
<string name="settings_media_cache">Dimensione della cache dei file multimediali</string>
|
||||
<string name="settings_music_directory">Mostra directory musicali</string>
|
||||
<string name="settings_music_directory_summary">Se abilitato, mostra la sezione delle directory musicali. Nota che per la navigazione nelle cartelle è necessario che il server supporti questa funzionalità.</string>
|
||||
<string name="settings_podcast">Mostra podcast</string>
|
||||
<string name="settings_podcast_summary">Se abilitato, mostra la sezione podcast. Riavvia l\'app per rendere effettive le modifiche.</string>
|
||||
<string name="settings_audio_quality">Mostra qualità audio</string>
|
||||
<string name="settings_audio_quality_summary">Il bitrate e il formato audio saranno mostrati per ogni traccia.</string>
|
||||
<string name="settings_item_rating">Mostra valutazione</string>
|
||||
<string name="settings_item_rating_summary">Se abilitato, verrà mostrata la valutazione dell\'elemento e se è contrassegnato come preferito.</string>
|
||||
<string name="settings_queue_syncing_countdown">Timer sincronizzazione</string>
|
||||
<string name="settings_queue_syncing_summary">Se abilitato, l\'utente avrà la possibilità di salvare la propria coda di riproduzione e potrà caricare lo stato all\'apertura dell\'applicazione.</string>
|
||||
<string name="settings_queue_syncing_title">Sincronizza coda di riproduzione per questo utente</string>
|
||||
<string name="settings_radio">Mostra radio</string>
|
||||
<string name="settings_radio_summary">Se abilitato, mostra la sezione radio. Riavvia l\'app per applicare completamente le modifiche.</string>
|
||||
<string name="settings_replay_gain">Imposta modalità di guadagno di riproduzione</string>
|
||||
<string name="settings_rounded_corner">Angoli arrotondati</string>
|
||||
<string name="settings_rounded_corner_size">Dimensione angoli</string>
|
||||
<string name="settings_rounded_corner_size_summary">Imposta la magnitudine dell\'angolo di curvatura.</string>
|
||||
<string name="settings_rounded_corner_summary">Se abilitato, imposta un angolo di curvatura per tutte le copertine visualizzate. Le modifiche avranno effetto al riavvio.</string>
|
||||
<string name="settings_scan_title">Scansiona libreria</string>
|
||||
<string name="settings_scrobble_title">Abilita scrobbling musicale</string>
|
||||
<string name="settings_share_title">Abilita condivisione musicale</string>
|
||||
<string name="settings_streaming_cache_size">Dimensione cache streaming</string>
|
||||
<string name="settings_streaming_cache_storage_title">Archiviazione cache streaming</string>
|
||||
<string name="settings_sub_summary_scrobble">È importante notare che lo scrobbling si basa anche sul fatto che il server sia abilitato a ricevere questi dati.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">Quando si ascolta la radio di un artista, un mix istantaneo o quando si mescolano tutti i brani, i brani sotto una certa valutazione dell\'utente verranno ignorati.</string>
|
||||
<string name="settings_summary_replay_gain">Il guadagno di riproduzione è una funzionalità che consente di regolare il livello del volume delle tracce audio per un\'esperienza di ascolto coerente. Questa impostazione è efficace solo se la traccia contiene i metadati necessari.</string>
|
||||
<string name="settings_summary_scrobble">Lo scrobbling è una funzionalità che consente al tuo dispositivo di inviare informazioni sulle canzoni che ascolti al server musicale. Queste informazioni aiutano a creare raccomandazioni personalizzate in base alle tue preferenze musicali.</string>
|
||||
<string name="settings_summary_share">Permette all\'utente di condividere musica tramite un link. La funzionalità deve essere supportata e abilitata sul server ed è limitata a brani, album e playlist singoli.</string>
|
||||
<string name="settings_summary_syncing">Restituisce lo stato della coda di riproduzione per questo utente. Ciò include i brani nella coda di riproduzione, il brano attualmente in riproduzione e la posizione all\'interno di questo brano. Il server deve supportare questa funzionalità.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nAttualmente in uso: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Priorità data alla modalità di transcoding. Se impostato su "Riproduzione diretta", il bitrate del file non verrà modificato.</string>
|
||||
<string name="settings_summary_transcoding_download">Scarica media transcodificati. Se abilitato, l\'endpoint di download non verrà utilizzato, ma le impostazioni seguenti. \n\n Se "Formato di transcodifica per i download" è impostato su "Download diretto", il bitrate del file non verrà modificato.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quando il file viene transcodificato al volo, il client di solito non mostra la lunghezza della traccia. È possibile richiedere ai server che supportano la funzionalità di stimare la durata della traccia in riproduzione, ma i tempi di risposta possono essere più lunghi.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Se abilitato, le tracce contrassegnate verranno scaricate per l\'uso offline.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Sincronizza tracce contrassegnate per uso offline</string>
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="settings_title_data">Dati</string>
|
||||
<string name="settings_title_general">Generale</string>
|
||||
<string name="settings_title_rating">Valutazione</string>
|
||||
<string name="settings_title_replay_gain">Guadagno di riproduzione</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
<string name="settings_title_skip_min_star_rating">Ignora brani in base alla valutazione</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Brani con una valutazione di:</string>
|
||||
<string name="settings_title_share">Condividi</string>
|
||||
<string name="settings_title_syncing">Sincronizzazione</string>
|
||||
<string name="settings_title_transcoding">Transcoding</string>
|
||||
<string name="settings_title_transcoding_download">Download di Transcoding</string>
|
||||
<string name="settings_title_ui">Interfaccia utente</string>
|
||||
<string name="settings_transcoded_download">Download transcodificato</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Versione</string>
|
||||
<string name="settings_wifi_only_summary">Chiedi conferma all\'utente prima di effettuare streaming su rete mobile.</string>
|
||||
<string name="settings_wifi_only_title">Streaming solo tramite Wi-Fi avviso</string>
|
||||
<string name="share_bottom_sheet_copy_link">Copia link</string>
|
||||
<string name="share_bottom_sheet_delete">Elimina condivisione</string>
|
||||
<string name="share_bottom_sheet_update">Aggiorna condivisione</string>
|
||||
<string name="share_subtitle_item">Data di scadenza: %1$s</string>
|
||||
<string name="share_unsupported_error">La condivisione non è supportata o non è abilitata</string>
|
||||
<string name="share_update_dialog_hint_description">Descrizione</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Data di scadenza</string>
|
||||
<string name="share_update_dialog_negative_button">Annulla</string>
|
||||
<string name="share_update_dialog_positive_button">Salva</string>
|
||||
<string name="share_update_dialog_title">Condividi</string>
|
||||
<string name="song_bottom_sheet_add_to_playlist">Aggiungi alla playlist</string>
|
||||
<string name="song_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="song_bottom_sheet_download">Scarica</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_album">Errore nel recupero dell\'album</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_artist">Errore nel recupero dell\'artista</string>
|
||||
<string name="song_bottom_sheet_go_to_album">Vai all\'album</string>
|
||||
<string name="song_bottom_sheet_go_to_artist">Vai all\'artista</string>
|
||||
<string name="song_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="song_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="song_bottom_sheet_rate">Valuta</string>
|
||||
<string name="song_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="song_bottom_sheet_share">Condividi</string>
|
||||
<string name="song_list_page_downloaded">Scaricato</string>
|
||||
<string name="song_list_page_most_played">Tracce più riprodotte</string>
|
||||
<string name="song_list_page_recently_added">Tracce aggiunte di recente</string>
|
||||
<string name="song_list_page_recently_played">Tracce riprodotte di recente</string>
|
||||
<string name="song_list_page_starred">Tracce contrassegnate</string>
|
||||
<string name="song_list_page_top">Le migliori tracce di %1$s</string>
|
||||
<string name="song_list_page_year">Anno %1$d</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<string name="starred_sync_dialog_negative_button">Annulla</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continua</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continua e scarica</string>
|
||||
<string name="starred_sync_dialog_summary">Il download delle tracce contrassegnate potrebbe richiedere una grande quantità di dati.</string>
|
||||
<string name="starred_sync_dialog_title">Sincronizza tracce contrassegnate</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Cambiare la destinazione dei file memorizzati nella cache da un\'unità di archiviazione a un\'altra può comportare la cancellazione di eventuali file memorizzati nella cache in precedenza nell\'altra unità di archiviazione.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Seleziona opzione di archiviazione</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Esterno</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Interno</string>
|
||||
<string name="support_url">https://buymeacoffee.com/a.cappiello</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Artista</string>
|
||||
<string name="track_info_bitrate">Bitrate</string>
|
||||
<string name="track_info_content_type">Tipo di contenuto</string>
|
||||
<string name="track_info_dialog_positive_button">OK</string>
|
||||
<string name="track_info_dialog_title">Info traccia</string>
|
||||
<string name="track_info_disc_number">Numero del disco</string>
|
||||
<string name="track_info_duration">Durata</string>
|
||||
<string name="track_info_genre">Genere</string>
|
||||
<string name="track_info_path">Percorso</string>
|
||||
<string name="track_info_size">Dimensione</string>
|
||||
<string name="track_info_suffix">Suffisso</string>
|
||||
<string name="track_info_summary_downloaded_file">Il file è stato scaricato utilizzando le API Subsonic. Il codec e il bitrate del file rimangono invariati rispetto al file sorgente.</string>
|
||||
<string name="track_info_summary_full_transcode">L\'applicazione richiederà al server di transcodedare il file e modificare il suo bitrate. Il codec richiesto dall\'utente è %1$s, con un bitrate di %2$s. Eventuali modifiche al codec e al bitrate del file nel formato scelto saranno gestite dal server, che potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_summary_original_file">L\'applicazione leggerà solo il file originale fornito dal server. L\'app richiederà esplicitamente al server il file non transcodedato con il bitrate della sorgente originale.</string>
|
||||
<string name="track_info_summary_server_prioritized">La qualità del file da riprodurre è lasciata alla decisione del server. L\'app non imporrà la scelta di codec e bitrate per eventuali transcoding.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">L\'applicazione richiederà al server di modificare il bitrate del file. L\'utente ha richiesto un bitrate di %1$s, mentre il codec del file sorgente rimarrà lo stesso. Eventuali modifiche al bitrate del file nel formato scelto saranno effettuate dal server, che potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_summary_transcoding_codec">L\'applicazione richiederà al server di transcodedare il file. Il codec richiesto dall\'utente è %1$s, mentre il bitrate sarà lo stesso del file sorgente. L\'eventuale transcoding del file nel formato scelto dipende dal server, in quanto potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_title">Titolo</string>
|
||||
<string name="track_info_track_number">Numero traccia</string>
|
||||
<string name="track_info_transcoded_content_type">Tipo di contenuto transcodedato</string>
|
||||
<string name="track_info_transcoded_suffix">Suffisso transcodedato</string>
|
||||
<string name="track_info_year">Anno</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Un ringraziamento speciale va a unDraw, senza le cui illustrazioni non avremmo potuto rendere questa applicazione più bella.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="player_lyrics_download_content_description">Scarica i testi delle canzoni per riprodurli offline</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Testi scaricati per la riproduzione offline</string>
|
||||
<string name="player_lyrics_download_success">Testi salvati per la riproduzione offline.</string>
|
||||
<string name="player_lyrics_download_failure">I testi non sono disponibili per il download.</string>
|
||||
<string name="player_server_priority">Priorità server</string>
|
||||
<string name="player_unknown_format">Formato sconosciuto</string>
|
||||
<string name="player_transcoding">Transcodifica</string>
|
||||
<string name="player_transcoding_requested">richiesto</string>
|
||||
<string name="playlist_catalogue_title">Catalogo playlist</string>
|
||||
<string name="playlist_catalogue_title_expanded">Sfoglia le playlist</string>
|
||||
<string name="playlist_chooser_dialog_empty">Nessuna playlist creata</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Annulla</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Crea</string>
|
||||
<string name="playlist_chooser_dialog_title">Aggiungi a una playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Aggiunta di un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Impossibile aggiungere un brano alla playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Tutte le canzoni sono state saltate perché duplicate</string>
|
||||
<string name="playlist_counted_tracks">%1$d brani • %2$s</string>
|
||||
<string name="playlist_duration">Durata • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Premi a lungo per eliminare</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nome della playlist</string>
|
||||
<string name="playlist_editor_dialog_negative_button">Annulla</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Elimina</string>
|
||||
<string name="playlist_editor_dialog_positive_button">Salva</string>
|
||||
<string name="playlist_editor_dialog_title">Modifica playlist</string>
|
||||
<string name="playlist_page_play_button">Riproduci</string>
|
||||
<string name="playlist_page_shuffle_button">Mescola</string>
|
||||
<string name="playlist_song_count">Playlist • %1$d brani</string>
|
||||
<string name="podcast_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="podcast_bottom_sheet_delete">Elimina</string>
|
||||
<string name="podcast_bottom_sheet_download">Scarica</string>
|
||||
<string name="podcast_bottom_sheet_go_to_channel">Vai al canale</string>
|
||||
<string name="podcast_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="podcast_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="podcast_channel_catalogue_title">Canali</string>
|
||||
<string name="podcast_channel_catalogue_title_expanded">Sfoglia Canali</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canale Podcast</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descrizione</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodi</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Nessun episodio disponibile</string>
|
||||
<string name="podcast_episode_download_request_snackbar">La tua richiesta è stata inviata al server</string>
|
||||
<string name="podcast_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
|
||||
<string name="podcast_info_empty_subtitle">Una volta aggiunto un canale, lo troverai qui</string>
|
||||
<string name="podcast_info_empty_title">Nessun podcast trovato!</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL Homepage Radio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nome Radio</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">URL Stream Radio</string>
|
||||
<string name="radio_editor_dialog_negative_button">Annulla</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Elimina</string>
|
||||
<string name="radio_editor_dialog_positive_button">Salva</string>
|
||||
<string name="radio_editor_dialog_title">Stazione Internet-Radio</string>
|
||||
<string name="radio_station_info_empty_button">Clicca per nascondere la sezione\nGli effetti saranno visibili al riavvio</string>
|
||||
<string name="radio_station_info_empty_subtitle">Una volta aggiunta una stazione radio, la troverai qui</string>
|
||||
<string name="radio_station_info_empty_title">Nessuna stazione trovata!</string>
|
||||
<string name="rating_dialog_negative_button">Annulla</string>
|
||||
<string name="rating_dialog_positive_button">Salva</string>
|
||||
<string name="rating_dialog_title">Valuta</string>
|
||||
<string name="search_hint">Cerca titolo, artisti o album</string>
|
||||
<string name="search_info_minimum_characters">Inserisci almeno tre caratteri</string>
|
||||
<string name="search_title_album">Album</string>
|
||||
<string name="search_title_artist">Artisti</string>
|
||||
<string name="search_title_song">Brani</string>
|
||||
<string name="server_signup_dialog_action_low_security">Bassa sicurezza</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Premi a lungo per eliminare</string>
|
||||
<string name="server_signup_dialog_hint_local_address">URL locale</string>
|
||||
<string name="server_signup_dialog_hint_name">Nome Server</string>
|
||||
<string name="server_signup_dialog_hint_password">Password</string>
|
||||
<string name="server_signup_dialog_hint_url">URL Server</string>
|
||||
<string name="server_signup_dialog_hint_username">Nome utente</string>
|
||||
<string name="server_signup_dialog_negative_button">Annulla</string>
|
||||
<string name="server_signup_dialog_neutral_button">Elimina</string>
|
||||
<string name="server_signup_dialog_positive_button">Salva</string>
|
||||
<string name="server_signup_dialog_title">Aggiungi server</string>
|
||||
<string name="server_unreachable_dialog_negative_button">Annulla</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Vai al login</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continua comunque</string>
|
||||
<string name="server_unreachable_dialog_summary">Il server richiesto non è disponibile. Se scegli di continuare, questo messaggio non apparirà per la prossima ora.</string>
|
||||
<string name="server_unreachable_dialog_title">Server irraggiungibile</string>
|
||||
<string name="settings_about_summary">Tempus è un client musicale open source e leggero per Subsonic, progettato e costruito nativamente per Android.</string>
|
||||
<string name="settings_about_title">Informazioni</string>
|
||||
<string name="settings_always_on_display">Sempre attivo</string>
|
||||
<string name="settings_allow_playlist_duplicates">Allow adding duplicates to playlist</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">If enabled, duplicates won\'t be checked while adding to a playlist.</string>
|
||||
<string name="settings_audio_transcode_download_format">Formato transcodifica</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Se abilitato, Tempus non forzerà il download del brano con le impostazioni di transcodifica sottostanti.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Dare priorità alle impostazioni del server per lo streaming nei download</string>
|
||||
<string name="settings_audio_transcode_download_summary">Se abilitato, Tempus scaricherà i brani transcodificati.</string>
|
||||
<string name="settings_audio_transcode_download_title">Scarica brani transcodificati</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Se abilitato, verrà richiesto al server di fornire la durata stimata del brano.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Stima della lunghezza del contenuto</string>
|
||||
<string name="settings_audio_transcode_format_download">Formato transcodifica per download</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Formato transcodifica su mobile</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Formato transcodifica su Wi-Fi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Se abilitato, Tempus non forzerà lo streaming del brano con le impostazioni di transcodifica sottostanti.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Dare priorità alle impostazioni di transcodifica del server</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Priorità di transcodifica del brano assegnata al server</string>
|
||||
<string name="settings_buffering_strategy">Strategia di buffering</string>
|
||||
<string name="settings_buffering_strategy_summary">Perché la modifica abbia effetto è necessario riavviare manualmente l\'app.</string>
|
||||
<string name="settings_choose_download_folder">Scegli una cartella dove scaricare la musica</string>
|
||||
<string name="settings_clear_download_folder">Svuota la cartella di download</string>
|
||||
<string name="settings_continuous_play_summary">Consente alla musica di continuare a suonare dopo la fine di una playlist, riproducendo brani simili</string>
|
||||
<string name="settings_continuous_play_title">Riproduzione continua</string>
|
||||
<string name="settings_covers_cache">Dimensione della cache delle copertine</string>
|
||||
<string name="settings_data_saving_mode_summary">Per ridurre il consumo di dati, evita di scaricare le copertine.</string>
|
||||
<string name="settings_data_saving_mode_title">Limita utilizzo dei dati mobili</string>
|
||||
<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_download_folder_cleared">Cartella di download svuotata.</string>
|
||||
<string name="settings_download_folder_set">Cartella di download impostata</string>
|
||||
<string name="settings_set_download_folder">Imposta cartella di download</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/tempus</string>
|
||||
<string name="settings_github_summary">Segui lo sviluppo</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Aggiornamenti</string>
|
||||
<string name="settings_github_update_title">Controlla GitHub per aggiornamenti</string>
|
||||
<string name="settings_github_update_summary">Se si utilizza la versione GitHub, per impostazione predefinita l\'app controllerà la presenza di nuove versioni. Disattiva per disabilitare i controlli automatici su GitHub</string>
|
||||
<string name="settings_support_summary">Partecipa alle discussioni della community e al supporto</string>
|
||||
<string name="settings_support_title">Supporto utenti</string>
|
||||
<string name="settings_scan_result">Scansione: conteggio di %1$d brani</string>
|
||||
<string name="settings_image_size">Imposta risoluzione delle immagini</string>
|
||||
<string name="settings_language">Lingua</string>
|
||||
<string name="settings_logout_title">Esci</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate per download</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate su mobile</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate su Wi-Fi</string>
|
||||
<string name="settings_media_cache">Dimensione della cache dei file multimediali</string>
|
||||
<string name="settings_music_directory">Mostra directory musicali</string>
|
||||
<string name="settings_music_directory_summary">Se abilitato, mostra la sezione delle directory musicali. Nota che per la navigazione nelle cartelle è necessario che il server supporti questa funzionalità.</string>
|
||||
<string name="settings_podcast">Mostra podcast</string>
|
||||
<string name="settings_podcast_summary">Se abilitato, mostra la sezione podcast. Riavvia l\'app per rendere effettive le modifiche.</string>
|
||||
<string name="settings_audio_quality">Mostra qualità audio</string>
|
||||
<string name="settings_audio_quality_summary">Il bitrate e il formato audio saranno mostrati per ogni traccia.</string>
|
||||
<string name="settings_song_rating">Mostra valutazione della canzone</string>
|
||||
<string name="settings_song_rating_summary">Se abilitato, mostra la valutazione a 5 stelle per la traccia nella pagina della canzone\n\n*Richiede il riavvio dell\'app</string>
|
||||
<string name="settings_item_rating">Mostra valutazione</string>
|
||||
<string name="settings_item_rating_summary">Se abilitato, verrà mostrata la valutazione dell\'elemento e se è contrassegnato come preferito.</string>
|
||||
<string name="settings_queue_syncing_countdown">Timer sincronizzazione</string>
|
||||
<string name="settings_queue_syncing_summary">Se abilitato, l\'utente avrà la possibilità di salvare la propria coda di riproduzione e potrà caricare lo stato all\'apertura dell\'applicazione.</string>
|
||||
<string name="settings_queue_syncing_title">Sincronizza coda di riproduzione per questo utente [Not Fully Baked]</string>
|
||||
<string name="settings_show_mini_shuffle_button">Mostra il pulsante di riproduzione casuale</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Se abilitato, mostra il pulsante di riproduzione casuale, rimuove il cuore nel mini player</string>
|
||||
<string name="settings_radio">Mostra radio</string>
|
||||
<string name="settings_radio_summary">Se abilitato, mostra la sezione radio. Riavvia l\'app per applicare completamente le modifiche.</string>
|
||||
<string name="settings_auto_download_lyrics">Scarica automaticamente i testi</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Salva automaticamente i testi quando sono disponibili in modo che possano essere mostrati offline.</string>
|
||||
<string name="settings_replay_gain">Imposta modalità di guadagno di riproduzione</string>
|
||||
<string name="settings_rounded_corner">Angoli arrotondati</string>
|
||||
<string name="settings_rounded_corner_size">Dimensione angoli</string>
|
||||
<string name="settings_rounded_corner_size_summary">Imposta la grandezza dell\'angolo di curvatura.</string>
|
||||
<string name="settings_rounded_corner_summary">Se abilitato, imposta un angolo di curvatura per tutte le copertine visualizzate. Le modifiche avranno effetto al riavvio.</string>
|
||||
<string name="settings_scan_title">Scansiona libreria</string>
|
||||
<string name="settings_scrobble_title">Abilita scrobbling musicale</string>
|
||||
<string name="settings_system_language">Lingua di sistema</string>
|
||||
<string name="settings_share_title">Abilita condivisione musicale</string>
|
||||
<string name="settings_streaming_cache_size">Dimensione cache streaming</string>
|
||||
<string name="settings_streaming_cache_storage_title">Archiviazione cache streaming</string>
|
||||
<string name="settings_sub_summary_scrobble">È importante notare che lo scrobbling si basa anche sul fatto che il server sia abilitato a ricevere questi dati.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">Quando si ascolta la radio di un artista, un mix istantaneo o quando si mescolano tutti i brani, i brani sotto una certa valutazione dell\'utente verranno ignorati.</string>
|
||||
<string name="settings_summary_replay_gain">Il guadagno di riproduzione è una funzionalità che consente di regolare il livello del volume delle tracce audio per un\'esperienza di ascolto coerente. Questa impostazione è efficace solo se la traccia contiene i metadati necessari.</string>
|
||||
<string name="settings_summary_scrobble">Lo scrobbling è una funzionalità che consente al tuo dispositivo di inviare informazioni sulle canzoni che ascolti al server musicale. Queste informazioni aiutano a creare raccomandazioni personalizzate in base alle tue preferenze musicali.</string>
|
||||
<string name="settings_summary_share">Permette all\'utente di condividere musica tramite un link. La funzionalità deve essere supportata e abilitata sul server ed è limitata a brani, album e playlist singoli.</string>
|
||||
<string name="settings_summary_syncing">Restituisce lo stato della coda di riproduzione per questo utente. Ciò include i brani nella coda di riproduzione, il brano attualmente in riproduzione e la posizione all\'interno di questo brano. Il server deve supportare questa funzionalità.\n*This setting is not 100% working on all servers/devices.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nAttualmente in uso: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Priorità data alla modalità di transcoding. Se impostato su "Riproduzione diretta", il bitrate del file non verrà modificato.</string>
|
||||
<string name="settings_summary_transcoding_download">Scarica media transcodificati. Se abilitato, l\'endpoint di download non verrà utilizzato, ma le impostazioni seguenti. \n\n Se "Formato di transcodifica per i download" è impostato su "Download diretto", il bitrate del file non verrà modificato.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Quando il file viene transcodificato al volo, il client di solito non mostra la lunghezza della traccia. È possibile richiedere ai server che supportano la funzionalità di stimare la durata della traccia in riproduzione, ma i tempi di risposta possono essere più lunghi.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Se abilitato, gli artisti preferiti verranno scaricati per l\'uso offline.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sincronizza artisti preferiti per uso offline</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Se abilitato, gli album preferiti verranno scaricati per l\'uso offline.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sincronizza album preferiti per uso offline</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Se abilitato, le tracce preferite verranno scaricate per l\'uso offline.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Sincronizza tracce preferite per uso offline</string>
|
||||
<string name="settings_theme">Tema</string>
|
||||
<string name="settings_title_data">Dati</string>
|
||||
<string name="settings_title_general">Generale</string>
|
||||
<string name="settings_title_playlist">Playlist</string>
|
||||
<string name="settings_title_rating">Valutazione</string>
|
||||
<string name="settings_title_replay_gain">Guadagno di riproduzione</string>
|
||||
<string name="settings_title_scrobble">Scrobble</string>
|
||||
<string name="settings_title_skip_min_star_rating">Ignora brani in base alla valutazione</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Brani con una valutazione di:</string>
|
||||
<string name="settings_title_share">Condividi</string>
|
||||
<string name="settings_title_syncing">Sincronizzazione</string>
|
||||
<string name="settings_title_transcoding">Transcodifica</string>
|
||||
<string name="settings_title_transcoding_download">Transcodifica dei Download</string>
|
||||
<string name="settings_title_ui">Interfaccia utente</string>
|
||||
<string name="settings_transcoded_download">Download transcodificato</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Versione</string>
|
||||
<string name="settings_wifi_only_summary">Chiedi conferma all\'utente prima di effettuare streaming su rete mobile.</string>
|
||||
<string name="settings_wifi_only_title">Streaming solo tramite Wi-Fi avviso</string>
|
||||
<string name="share_bottom_sheet_copy_link">Copia link</string>
|
||||
<string name="share_bottom_sheet_delete">Elimina condivisione</string>
|
||||
<string name="share_bottom_sheet_update">Aggiorna condivisione</string>
|
||||
<string name="share_subtitle_item">Data di scadenza: %1$s</string>
|
||||
<string name="share_no_expiration">Mai</string>
|
||||
<string name="share_unsupported_error">La condivisione non è supportata o non è abilitata</string>
|
||||
<string name="asset_link_clipboard_label">Link asset Tempus</string>
|
||||
<string name="asset_link_label_song">UID Canzone</string>
|
||||
<string name="asset_link_label_album">UID Album</string>
|
||||
<string name="asset_link_label_artist">UID Artista</string>
|
||||
<string name="asset_link_label_playlist">UID Playlist</string>
|
||||
<string name="asset_link_label_genre">UID Genere</string>
|
||||
<string name="asset_link_label_year">UID Anno</string>
|
||||
<string name="asset_link_label_unknown">UID Asset</string>
|
||||
<string name="asset_link_error_unsupported">Link asset non supportato</string>
|
||||
<string name="asset_link_error_song">Impossibile aprire la canzone</string>
|
||||
<string name="asset_link_error_album">Impossibile aprire l\'album</string>
|
||||
<string name="asset_link_error_artist">Impossibile aprire l\'artista</string>
|
||||
<string name="asset_link_error_playlist">Impossibile aprire la playlist</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">Copiato %1$s negli appunti</string>
|
||||
<string name="asset_link_debug_toast">Link asset: %1$s</string>
|
||||
<string name="share_update_dialog_hint_description">Descrizione</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Data di scadenza</string>
|
||||
<string name="share_update_dialog_negative_button">Annulla</string>
|
||||
<string name="share_update_dialog_positive_button">Salva</string>
|
||||
<string name="share_update_dialog_title">Condividi</string>
|
||||
<string name="song_bottom_sheet_add_to_playlist">Aggiungi alla playlist</string>
|
||||
<string name="song_bottom_sheet_add_to_queue">Aggiungi alla coda</string>
|
||||
<string name="song_bottom_sheet_download">Scarica</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_album">Errore nel recupero dell\'album</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_artist">Errore nel recupero dell\'artista</string>
|
||||
<string name="song_bottom_sheet_go_to_album">Vai all\'album</string>
|
||||
<string name="song_bottom_sheet_go_to_artist">Vai all\'artista</string>
|
||||
<string name="song_bottom_sheet_instant_mix">Mix istantaneo</string>
|
||||
<string name="song_bottom_sheet_play_next">Riproduci dopo</string>
|
||||
<string name="song_bottom_sheet_rate">Valuta</string>
|
||||
<string name="song_bottom_sheet_remove">Rimuovi</string>
|
||||
<string name="song_bottom_sheet_share">Condividi</string>
|
||||
<string name="song_list_page_downloaded">Scaricato</string>
|
||||
<string name="song_list_page_most_played">Tracce più riprodotte</string>
|
||||
<string name="song_list_page_recently_added">Tracce aggiunte di recente</string>
|
||||
<string name="song_list_page_recently_played">Tracce riprodotte di recente</string>
|
||||
<string name="song_list_page_starred">Tracce contrassegnate</string>
|
||||
<string name="song_list_page_top">Le migliori tracce di %1$s</string>
|
||||
<string name="song_list_page_year">Anno %1$d</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<string name="starred_sync_dialog_negative_button">Annulla</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continua</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continua e scarica</string>
|
||||
<string name="starred_sync_dialog_summary">Il download delle tracce contrassegnate potrebbe richiedere una grande quantità di dati.</string>
|
||||
<string name="starred_sync_dialog_title">Sincronizza tracce contrassegnate</string>
|
||||
<string name="starred_artist_sync_dialog_summary">Scaricare gli artisti preferiti potrebbe richiedere una grande quantità di dati.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Sincronizza artisti preferiti</string>
|
||||
<string name="starred_album_sync_dialog_summary">Scaricare gli album preferiti potrebbe richiedere una grande quantità di dati.</string>
|
||||
<string name="starred_album_sync_dialog_title">Sincronizza album preferiti</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Per rendere effettive le modifiche, riavvia l\'app.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Cambiare la destinazione dei file memorizzati nella cache da un\'unità di archiviazione a un\'altra può comportare la cancellazione di eventuali file memorizzati nella cache in precedenza nell\'altra unità di archiviazione.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Seleziona opzione di archiviazione</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Esterno</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Interno</string>
|
||||
<string name="support_url">https://ko-fi.com/eddyizm</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Artista</string>
|
||||
<string name="track_info_bit_depth">Profondità bit</string>
|
||||
<string name="track_info_bitrate">Bitrate</string>
|
||||
<string name="track_info_content_type">Tipo di contenuto</string>
|
||||
<string name="track_info_dialog_positive_button">OK</string>
|
||||
<string name="track_info_dialog_title">Info traccia</string>
|
||||
<string name="track_info_disc_number">Numero del disco</string>
|
||||
<string name="track_info_duration">Durata</string>
|
||||
<string name="track_info_genre">Genere</string>
|
||||
<string name="track_info_path">Percorso</string>
|
||||
<string name="track_info_sampling_rate">Frequenza di campionamento</string>
|
||||
<string name="track_info_size">Dimensione</string>
|
||||
<string name="track_info_suffix">Suffisso</string>
|
||||
<string name="track_info_summary_downloaded_file">Il file è stato scaricato utilizzando le API Subsonic. Il codec e il bitrate del file rimangono invariati rispetto al file sorgente.</string>
|
||||
<string name="track_info_summary_full_transcode">L\'applicazione richiederà al server di transcodedare il file e modificare il suo bitrate. Il codec richiesto dall\'utente è %1$s, con un bitrate di %2$s. Eventuali modifiche al codec e al bitrate del file nel formato scelto saranno gestite dal server, che potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_summary_original_file">L\'applicazione leggerà solo il file originale fornito dal server. L\'app richiederà esplicitamente al server il file non transcodedato con il bitrate della sorgente originale.</string>
|
||||
<string name="track_info_summary_server_prioritized">La qualità del file da riprodurre è lasciata alla decisione del server. L\'app non imporrà la scelta di codec e bitrate per eventuali transcoding.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">L\'applicazione richiederà al server di modificare il bitrate del file. L\'utente ha richiesto un bitrate di %1$s, mentre il codec del file sorgente rimarrà lo stesso. Eventuali modifiche al bitrate del file nel formato scelto saranno effettuate dal server, che potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_summary_transcoding_codec">L\'applicazione richiederà al server di transcodedare il file. Il codec richiesto dall\'utente è %1$s, mentre il bitrate sarà lo stesso del file sorgente. L\'eventuale transcoding del file nel formato scelto dipende dal server, in quanto potrebbe o meno supportare l\'operazione.</string>
|
||||
<string name="track_info_title">Titolo</string>
|
||||
<string name="track_info_track_number">Numero traccia</string>
|
||||
<string name="track_info_transcoded_content_type">Tipo di contenuto transcodificato</string>
|
||||
<string name="track_info_transcoded_suffix">Suffisso transcodificato</string>
|
||||
<string name="track_info_year">Anno</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Un ringraziamento speciale va a unDraw, senza le cui illustrazioni non avremmo potuto rendere questa applicazione più bella.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="widget_label">Widget Tempus</string>
|
||||
<string name="widget_not_playing">Non in riproduzione</string>
|
||||
<string name="widget_placeholder_subtitle">Apri Tempus</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_content_desc_album_art">Immagine dell\'album</string>
|
||||
<string name="widget_content_desc_play_pause">Riproduci o metti in pausa</string>
|
||||
<string name="widget_content_desc_next">Traccia successiva</string>
|
||||
<string name="widget_content_desc_prev">Traccia precedente</string>
|
||||
<string name="widget_content_desc_shuffle">Attiva/disattiva riproduzione casuale</string>
|
||||
<string name="widget_content_desc_repeat">Cambia modalità di ripetizione</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album da sincronizzare</item>
|
||||
<item quantity="other">%d album da sincronizzare</item>
|
||||
</plurals>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">%d artista da sincronizzare</item>
|
||||
<item quantity="other">%d artisti da sincronizzare</item>
|
||||
</plurals>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">Scaricando %d canzone</item>
|
||||
<item quantity="other">Scaricando %d canzoni</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Equalizzatore</string>
|
||||
<string name="equalizer_reset">Reimposta</string>
|
||||
<string name="equalizer_enable">Abilita</string>
|
||||
<string name="equalizer_not_supported">Non supportato su questo dispositivo</string>
|
||||
<string name="settings_app_equalizer">Equalizzatore</string>
|
||||
<string name="settings_app_equalizer_summary">Apri l\'equalizzatore integrato</string>
|
||||
|
||||
<string name="settings_album_detail">Mostra dettagli album</string>
|
||||
<string name="settings_album_detail_summary">Se abilitato, mostra i dettagli dell\'album come genere, numero di canzoni, ecc. nella pagina dell\'album</string>
|
||||
<string name="settings_artist_sort_by_album_count">Ordina artisti per numero di album</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Se abilitato, ordina gli artisti per numero di album. Ordina per nome se disabilitato.</string>
|
||||
</resources>
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
<item name="android:statusBarColor">?attr/colorSurface</item>
|
||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
||||
<item name="android:scrollbars">none</item>
|
||||
|
||||
<item name="floatingActionButtonStyle">@style/FloatingActionButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
@@ -40,6 +42,21 @@
|
||||
<item name="android:background">@color/dividerColor</item>
|
||||
</style>
|
||||
|
||||
<style name="FloatingActionButtonStyle" parent="Widget.MaterialComponents.FloatingActionButton">
|
||||
<item name="backgroundTint">?attr/colorSecondary</item>
|
||||
<item name="tint">?attr/colorOnPrimary</item>
|
||||
<item name="shapeAppearanceOverlay">@style/FabShapeStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FabShapeStyle" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerSize">50%</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
<item name="cornerFamilyTopLeft">rounded</item>
|
||||
<item name="cornerFamilyTopRight">rounded</item>
|
||||
<item name="cornerFamilyBottomLeft">rounded</item>
|
||||
<item name="cornerFamilyBottomRight">rounded</item>
|
||||
</style>
|
||||
|
||||
<style name="NoConnectionTextView">
|
||||
<item name="background">?attr/colorErrorContainer</item>
|
||||
<item name="android:textColor">?attr/colorOnErrorContainer</item>
|
||||
|
||||
@@ -239,7 +239,16 @@
|
||||
<item>4</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="playlist_sort_option_titles">
|
||||
<item>Po nazwie</item>
|
||||
<item>Losowo</item>
|
||||
</string-array>
|
||||
<string-array name="playlist_sort_option_values">
|
||||
<item>ORDER_BY_NAME</item>
|
||||
<item>ORDER_BY_RANDOM</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>Minimum 0 gwiazdek</item>
|
||||
<item>Minimum 1 gwiazdka</item>
|
||||
@@ -254,4 +263,4 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<string name="artist_list_page_downloaded">Pobrani wykonawcy</string>
|
||||
<string name="artist_list_page_starred">Wykonawcy oznaczeni gwiazdką</string>
|
||||
<string name="artist_list_page_title">Wykonawcy</string>
|
||||
<string name="artist_no_artist_info_toast">Brak dodatkowych informacji o wykonawcy</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Odtwarzanie losowe</string>
|
||||
<string name="artist_page_switch_layout_button">Zmień układ</string>
|
||||
@@ -51,6 +52,8 @@
|
||||
<string name="battery_optimization_negative_button">Ignoruj</string>
|
||||
<string name="battery_optimization_neutral_button">Nie pytaj ponownie</string>
|
||||
<string name="battery_optimization_positive_button">Wyłącz</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Generowanie natychmiastowego mixu...</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Nie udało się pobrać utworów z serwera subsonic.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Anuluj</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Włącz oszczędzanie danych</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
@@ -61,7 +64,7 @@
|
||||
<string name="delete_download_storage_dialog_positive_button">Kontynuuj</string>
|
||||
<string name="delete_download_storage_dialog_summary">Miej na uwadze to że kontynuowanie tej operacji spowoduje usunięcie wszystkich pobranych plików z wszystkich serwerów.</string>
|
||||
<string name="delete_download_storage_dialog_title">Usuwanie zapisanych plików</string>
|
||||
<string name="description_empty_title">Brak opisu</string>
|
||||
<string name="description_empty_title">Brak tekstu</string>
|
||||
<string name="disc_titlefull">Płyta %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Płyta %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Anuluj</string>
|
||||
@@ -124,6 +127,10 @@
|
||||
<string name="home_sync_starred_albums_subtitle">Albumy oznaczone gwiazdką będą dostępne offline</string>
|
||||
<string name="home_sync_starred_artists_title">Synchronizacja wykonawców oznaczonych gwiazdką</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Masz wykonawców oznaczonych gwiazdką, bez pobranej muzyki</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d piosenka wymaga synchronizacji</item>
|
||||
<item quantity="other">%d piosenek wymaga synchronizacji</item>
|
||||
</plurals>
|
||||
<string name="home_title_best_of">Najlepsze</string>
|
||||
<string name="home_title_discovery">Odkrywanie</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Odtwórz wszystkie losowo</string>
|
||||
@@ -201,7 +208,9 @@
|
||||
<string name="menu_sort_year">Rok</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Wyczyść kolejkę odtwarzania</string>
|
||||
<string name="player_queue_save_queue_success">Zapisana kolejka odtwarzania</string>
|
||||
<string name="player_queue_save_queue_success">Zapisano kolejkę odtwarzania</string>
|
||||
<string name="player_queue_save_to_playlist">Zapisz kolejkę do playlisty</string>
|
||||
<string name="player_queue_load_queue">Wczytaj kolejkę</string>
|
||||
<string name="player_lyrics_download_content_description">Pobierz teksty do odtwarzania offline</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Teksty pobrane do odtwarzania offline</string>
|
||||
<string name="player_lyrics_download_success">Zapisano tekst do odtwarzania offline.</string>
|
||||
@@ -240,6 +249,7 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Przeglądaj Kanały</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">Url RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Kanał Podcastu</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Podcasty nie są obsługiwane przez ten serwer.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Opis</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Odcinki</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Brak dostępnych odcinków</string>
|
||||
@@ -254,10 +264,13 @@
|
||||
<string name="radio_editor_dialog_negative_button">Anuluj</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Usuń</string>
|
||||
<string name="radio_editor_dialog_positive_button">Zapisz</string>
|
||||
<string name="radio_editor_dialog_added">Dodano stację radiową</string>
|
||||
<string name="radio_editor_dialog_updated">Stacja radiowa uaktualniona</string>
|
||||
<string name="radio_editor_dialog_title">Internetowa Stacja Radiowa</string>
|
||||
<string name="radio_station_info_empty_button">Naciśnij aby ukryć tę sekcję\nEfekty będą widoczne po restarcie</string>
|
||||
<string name="radio_station_info_empty_subtitle">Gdy dodasz stację radiową, znajdziesz ją tutaj</string>
|
||||
<string name="radio_station_info_empty_title">Nie znaleziono stacji!</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Zarządzanie internetowymi stacjami radiowymi nie jest obsługiwane przez ten serwer.</string>
|
||||
<string name="rating_dialog_negative_button">Anuluj</string>
|
||||
<string name="rating_dialog_positive_button">Zapisz</string>
|
||||
<string name="rating_dialog_title">Oceń</string>
|
||||
@@ -321,6 +334,9 @@
|
||||
<string name="settings_github_summary">Śledź tworzenie aplikacji</string>
|
||||
<string name="settings_github_title">GitHub</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Aktualizacje</string>
|
||||
<string name="settings_github_update_title">Sprawdzaj dostępność nowych aktualizacji na githubie</string>
|
||||
<string name="settings_github_update_summary">Jeżeli używana jest wersja z githuba, domyślnie aplikacja będzie sprawdzać czy są dostępne nowe wydania apk. Kliknij przełącznik aby, wyłączyć automatyczne sprawdzanie</string>
|
||||
<string name="settings_support_summary">Dołącz do dyskusji i wsparcia społeczności</string>
|
||||
<string name="settings_support_title">Wsparcie użytkowników</string>
|
||||
<string name="settings_scan_result">Skanowanie: naliczono %1$d utworów</string>
|
||||
@@ -335,6 +351,7 @@
|
||||
<string name="settings_music_directory_summary">Jeżeli włączone, widoczna będzie sekcja z folderami z muzyką. Weź pod uwagę że żeby funkcja nawigacji po folderach działała poprawnie, serwer musi wspierać tę funkcję.</string>
|
||||
<string name="settings_podcast">Pokazuj podcasty</string>
|
||||
<string name="settings_podcast_summary">Jeżeli włączone, widoczna będzie sekcja z podcastami. Zrestartuj aplikację aby, zmiany przyniosły pełny efekt.</string>
|
||||
<string name="settings_playlist_sort">Sortowanie playlist</string>
|
||||
<string name="settings_audio_quality">Pokaż jakość audio</string>
|
||||
<string name="settings_audio_quality_summary">Bitrate i format audio będzie pokazywany dla każdego utworu.</string>
|
||||
<string name="settings_song_rating">Pokaż ocenę piosenek w gwiazdkach</string>
|
||||
@@ -400,6 +417,7 @@
|
||||
<string name="share_bottom_sheet_delete">Usuń udostępnianie</string>
|
||||
<string name="share_bottom_sheet_update">Zaktualizuj udostępnianie</string>
|
||||
<string name="share_subtitle_item">Data wygaśnięcia: %1$s</string>
|
||||
<string name="share_no_expiration">Nigdy</string>
|
||||
<string name="share_unsupported_error">Udostępnianie nie jest wspierane lub włączone</string>
|
||||
<string name="asset_link_clipboard_label">Link zasobu Tempus</string>
|
||||
<string name="asset_link_label_song">UID piosenki</string>
|
||||
@@ -516,4 +534,11 @@
|
||||
<string name="settings_app_equalizer_summary">Otwórz wbudowany korektor dźwięku</string>
|
||||
<string name="settings_album_detail">Pokaż szczegóły albumu</string>
|
||||
<string name="settings_album_detail_summary">Jeżeli włączone, pokaż szczegóły albumu takie jak gatunek, ilość piosenek itp. na stronie albumu</string>
|
||||
<string name="settings_artist_sort_by_album_count">Sortuj wykonawców po ilości albumów</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Jeżeli włączone, sortuje wykonawców po ilości albumów. Jeżeli wyłączone, sortuje albumy po nazwach.</string>
|
||||
<string name="folder_play_collecting">Zbieranie piosenek z folderu…</string>
|
||||
<string name="folder_play_playing">Odtwarzanie %d piosenek</string>
|
||||
<string name="folder_play_no_songs">Nie znaleziono piosenek w folderze</string>
|
||||
<string name="search_sort_title">Sortuj ostatnie wyszukiwania chronologicznie</string>
|
||||
<string name="search_sort_summary">Jeżeli włączone, sortuje wyszukiwania chronologicznie. Sortuje po naziwe jeżeli wyłączone.</string>
|
||||
</resources>
|
||||
|
||||
258
app/src/main/res/values-ro/arrays.xml
Normal file
258
app/src/main/res/values-ro/arrays.xml
Normal file
@@ -0,0 +1,258 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="theme_list_titles">
|
||||
<item>Luminos</item>
|
||||
<item>Întunecat</item>
|
||||
<item>Setare implicită sistem</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>Înalt</item>
|
||||
<item>Mediu</item>
|
||||
<item>Scăzut</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>Înalt</item>
|
||||
<item>Mediu</item>
|
||||
<item>Scăzut</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>Dezactivat</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>
|
||||
<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>Original</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>Original</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>Redare directă</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>Redare directă</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>Descărcare directă</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>Zece secunde</item>
|
||||
<item>Cinci secunde</item>
|
||||
<item>Două secunde</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>Înalt</item>
|
||||
<item>Mediu</item>
|
||||
<item>Scăzut</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>Dezactivat</item>
|
||||
<item>Piesă</item>
|
||||
<item>Album</item>
|
||||
<item>Automat</item>
|
||||
</string-array>
|
||||
<string-array name="replay_gain_values">
|
||||
<item>disabled</item>
|
||||
<item>track</item>
|
||||
<item>album</item>
|
||||
<item>auto</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transcoded_download_option_list_titles">
|
||||
<item>Nu transcodaţi</item>
|
||||
<item>Setări server</item>
|
||||
<item>Format transcodare Wi-Fi</item>
|
||||
<item>Format transcodare mobil</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>Minim</item>
|
||||
<item>Moderat</item>
|
||||
<item>Agresiv</item>
|
||||
<item>Extrem</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>0 stele minim</item>
|
||||
<item>1 stea minim</item>
|
||||
<item>2 stele minim</item>
|
||||
<item>3 stele minim</item>
|
||||
<item>4 stele minim</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>
|
||||
557
app/src/main/res/values-ro/strings.xml
Normal file
557
app/src/main/res/values-ro/strings.xml
Normal file
@@ -0,0 +1,557 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_conclusion">Dacă întâmpinați probleme acesați https://dontkillmyapp.com, unde veți găsi instrucțiuni detaliate despre cum să dezactivați funcțiile de economisire baterie care pot afecta performanța aplicației.</string>
|
||||
<string name="activity_battery_optimizations_summary">Dezactivați optimizările bateriei pentru redarea media în timp ce ecranul este oprit.</string>
|
||||
<string name="activity_battery_optimizations_title">Optimizări Baterie</string>
|
||||
<string name="activity_info_offline_mode">Modul offline</string>
|
||||
<string name="album_bottom_sheet_add_to_playlist">Adăugați la playlist</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Adăugați la coadă</string>
|
||||
<string name="album_bottom_sheet_download_all">Descărcați tot</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Mergeți la artist</string>
|
||||
<string name="album_bottom_sheet_instant_mix">Mix instant</string>
|
||||
<string name="album_bottom_sheet_play_next">Redați următoarea</string>
|
||||
<string name="album_bottom_sheet_remove_all">Eliminați tot</string>
|
||||
<string name="album_bottom_sheet_share">Partajați</string>
|
||||
<string name="album_bottom_sheet_shuffle">Amestecare</string>
|
||||
<string name="album_catalogue_title">Albume</string>
|
||||
<string name="album_catalogue_title_expanded">Răsfoiți Albume</string>
|
||||
<string name="album_error_retrieving_artist">Eroare la preluarea artistului</string>
|
||||
<string name="album_list_page_downloaded">Albume descărcate</string>
|
||||
<string name="album_list_page_most_played">Albume cele mai ascultate</string>
|
||||
<string name="album_list_page_new_releases">Lansări noi</string>
|
||||
<string name="album_list_page_recently_added">Albume adăugate recent</string>
|
||||
<string name="album_list_page_recently_played">Albume redate recent</string>
|
||||
<string name="album_list_page_starred">Albume marcate</string>
|
||||
<string name="album_list_page_title">Albume</string>
|
||||
<string name="album_page_extra_info_button">Mai mult ca aceasta</string>
|
||||
<string name="album_page_play_button">Redare</string>
|
||||
<string name="album_page_release_date_label">Lansat pe %1$s</string>
|
||||
<string name="album_page_release_dates_label">Lansat pe %1$s, inițial %2$s</string>
|
||||
<string name="album_page_shuffle_button">Amestecare</string>
|
||||
<string name="album_page_tracks_count_and_duration">%1$d cântece • %2$d minute</string>
|
||||
<string name="app_name">Tempus</string>
|
||||
<string name="artist_adapter_radio_station_starting">Se caută…</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">Mix instant</string>
|
||||
<string name="artist_bottom_sheet_shuffle">Amestecare</string>
|
||||
<string name="artist_catalogue_title">Artiști</string>
|
||||
<string name="artist_catalogue_title_expanded">Răsfoiți Artiști</string>
|
||||
<string name="artist_error_retrieving_radio">Eroare la preluarea radioului artistului</string>
|
||||
<string name="artist_error_retrieving_tracks">Eroare la preluarea cântecelor artistului</string>
|
||||
<string name="artist_list_page_downloaded">Artiști descărcați</string>
|
||||
<string name="artist_list_page_starred">Artiști marcați</string>
|
||||
<string name="artist_list_page_title">Artiști</string>
|
||||
<string name="artist_no_artist_info_toast">Nu sunt informații suplimentare despre artist</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Amestecare</string>
|
||||
<string name="artist_page_switch_layout_button">Schimbați aspectul</string>
|
||||
<string name="artist_page_title_album_more_like_this_button">Mai mult ca aceasta</string>
|
||||
<string name="artist_page_title_album_section">Albume</string>
|
||||
<string name="artist_page_title_biography_more_button">Mai mult</string>
|
||||
<string name="artist_page_title_biography_section">Biografie</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">Cele mai ascultate cântece</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">Vezi tot</string>
|
||||
<string name="battery_optimization_negative_button">Ignoraţi</string>
|
||||
<string name="battery_optimization_neutral_button">Nu mai întreba</string>
|
||||
<string name="battery_optimization_positive_button">Dezactivaţi</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Se generează mix instant...</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Nu s-au putut prelua cântecele din serverul subsonic.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Anulati</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Activaţi economisitor de date</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
<string name="connection_alert_dialog_summary">Accesul la serverul Subsonic pe conexiuni alte decât Wi-Fi a fost restricționat. Pentru a preveni reapariția acestui dialog, dezactivați verificarea conexiunii în setările aplicației.</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi nu este conectat</string>
|
||||
<string name="content_description_shuffle_button">Amestecare</string>
|
||||
<string name="delete_download_storage_dialog_negative_button">Anulati</string>
|
||||
<string name="delete_download_storage_dialog_positive_button">Continuaţi</string>
|
||||
<string name="delete_download_storage_dialog_summary">Vă rugăm să fiți conștienți că continuarea cu această acțiune va duce la ștergerea permanentă a tuturor articolelor salvate descărcate de pe toți serverele.</string>
|
||||
<string name="delete_download_storage_dialog_title">Ștergeți articolele salvate</string>
|
||||
<string name="description_empty_title">Nu sunt versuri disponibile</string>
|
||||
<string name="disc_titlefull">Disc %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disc %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Anulati</string>
|
||||
<string name="download_directory_dialog_positive_button">Descarcaţi</string>
|
||||
<string name="download_directory_dialog_summary">Toate piesele din acest folder vor fi descărcate. Piesele prezente în subfoldere nu vor fi descărcate.</string>
|
||||
<string name="download_directory_dialog_title">Descărcați piesele</string>
|
||||
<string name="download_directory_set">Setați unde sunt descărcate muzica</string>
|
||||
<string name="download_info_empty_subtitle">Odată ce descărcați o cântec, o veți găsi aici</string>
|
||||
<string name="download_info_empty_title">Nicio descărcare încă!</string>
|
||||
<string name="download_item_multiple_subtitle_formatter">%1$s • %2$s articole</string>
|
||||
<string name="download_item_single_subtitle_formatter">%1$s articole</string>
|
||||
<string name="download_shuffle_all_subtitle">Amestecare toate</string>
|
||||
<string name="download_storage_dialog_sub_summary">Pentru ca schimbările să aibă efect, reporniți aplicația.</string>
|
||||
<string name="download_storage_dialog_summary">Schimbarea destinației fișierelor descărcate de la un spațiu de stocare la altul va duce la ștergerea imediată a oricăror fișiere descărcate anterior în celălalt spațiu de stocare.</string>
|
||||
<string name="download_storage_dialog_title">Selectați opțiunea de stocare</string>
|
||||
<string name="download_storage_external_dialog_positive_button">Extern</string>
|
||||
<string name="download_storage_internal_dialog_negative_button">Intern</string>
|
||||
<string name="download_storage_directory_dialog_neutral_button">Director</string>
|
||||
<string name="download_title_section">Descărcări</string>
|
||||
<string name="download_refresh_no_directory">Setați un folder de descărcare pentru a vă reîmprospăta descărcările.</string>
|
||||
<string name="download_refresh_no_changes">Nu au fost găsite descărcări lipsă.</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">Eliminată %d descărcare lipsă.</item>
|
||||
<item quantity="other">Eliminate %d descărcări lipsă.</item>
|
||||
</plurals>
|
||||
<string name="download_refresh_button_content_description">Reîmprospătați articolele descărcate</string>
|
||||
<string name="downloaded_bottom_sheet_add_to_queue">Adăugați la coadă</string>
|
||||
<string name="downloaded_bottom_sheet_play_next">Redați următoarea</string>
|
||||
<string name="downloaded_bottom_sheet_remove">Eliminaţi</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">Eliminați tot</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">Amestecare</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Necesar</string>
|
||||
<string name="error_server_prefix">Prefixul http sau https este necesar</string>
|
||||
<string name="exo_download_notification_channel_name">Descărcări</string>
|
||||
<string name="exo_controls_heart_off_description">Comutați inima în stare inactivă</string>
|
||||
<string name="exo_controls_heart_on_description">Comutați inima în stare activă</string>
|
||||
<string name="cast_expanded_controller_loading">Se încarcă…</string>
|
||||
<string name="filter_info_selection">Selectați doi sau mai mulți filtre</string>
|
||||
<string name="filter_title">Filtru</string>
|
||||
<string name="filter_artist">Filtrați artiști</string>
|
||||
<string name="filter_title_expanded">Filtrați Genuri</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">Catalogul Genuri</string>
|
||||
<string name="genre_catalogue_title_expanded">Răsfoiți Genuri</string>
|
||||
<string name="github_update_dialog_negative_button">Reamintitor mai târziu</string>
|
||||
<string name="github_update_dialog_neutral_button">Susțineți-mă</string>
|
||||
<string name="github_update_dialog_positive_button">Descărcați acum</string>
|
||||
<string name="github_update_dialog_summary">Există o nouă versiune a aplicației disponibilă pe Github.</string>
|
||||
<string name="github_update_dialog_title">Actualizare disponibilă</string>
|
||||
<string name="home_rearrangement_dialog_negative_button">Anulati</string>
|
||||
<string name="home_rearrangement_dialog_neutral_button">Resetaţi</string>
|
||||
<string name="home_rearrangement_dialog_positive_button">Salvaţi</string>
|
||||
<string name="home_rearrangement_dialog_title">Rearanjați pagina de start</string>
|
||||
<string name="home_rearrangement_dialog_subtitle">Rețineți că pentru ca modificările să aibă efect, este necesar să reporniți aplicația.</string>
|
||||
<string name="home_section_music">Muzică</string>
|
||||
<string name="home_section_podcast">Podcast</string>
|
||||
<string name="home_section_radio">Radio</string>
|
||||
<string name="home_subtitle_best_of">Top piese din artiștii dvs. preferați</string>
|
||||
<string name="home_subtitle_made_for_you">Porniți mix-ul dintr-o cântec pe care ți-a plăcut</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">Adăugați o nouă radio</string>
|
||||
<string name="home_subtitle_new_podcast_channel">Adăugați un nou canal podcast</string>
|
||||
<string name="home_sync_starred_cancel">Anulati</string>
|
||||
<string name="home_sync_starred_download">Descarcaţi</string>
|
||||
<string name="home_sync_starred_subtitle">Descărcarea acestor piese poate implica o utilizare semnificativă a datelor</string>
|
||||
<string name="home_sync_starred_title">Se pare că sunt niște piese marcate de sincronizat</string>
|
||||
<string name="home_sync_starred_albums_title">Sincronizați Albume Marcate</string>
|
||||
<string name="home_sync_starred_albums_subtitle">Albumele marcate cu o stea vor fi disponibile offline</string>
|
||||
<string name="home_sync_starred_artists_title">Sincronizare Artiști Marcați</string>
|
||||
<string name="home_sync_starred_artists_subtitle">Aveți artiști marcați cu muzică care nu a fost descărcată</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d cântec trebuie sincronizată</item>
|
||||
<item quantity="other">%d cântece trebuie sincronizate</item>
|
||||
</plurals>
|
||||
<string name="home_title_best_of">Cel mai bun</string>
|
||||
<string name="home_title_discovery">Descoperire</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Amestecare toate</string>
|
||||
<string name="home_title_flashback">Retrospectivă</string>
|
||||
<string name="home_title_internet_radio_station">Stații radio internet</string>
|
||||
<string name="home_title_last_played">Redată ultima dată</string>
|
||||
<string name="home_title_last_played_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_last_week">Săptămâna trecută</string>
|
||||
<string name="home_title_last_month">Luna trecută</string>
|
||||
<string name="home_title_last_year">Anul trecut</string>
|
||||
<string name="home_title_made_for_you">Făcut pentru tine</string>
|
||||
<string name="home_title_most_played">Cele mai ascultate</string>
|
||||
<string name="home_title_most_played_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_new_releases">Lansări noi</string>
|
||||
<string name="home_title_newest_podcasts">Podcasturi cele mai noi</string>
|
||||
<string name="home_title_pinned_playlists">Playlisturi</string>
|
||||
<string name="home_title_podcast_channels">Canale</string>
|
||||
<string name="home_title_podcast_channels_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_radio_station">Stații radio</string>
|
||||
<string name="home_title_recently_added">Adăugate recent</string>
|
||||
<string name="home_title_recently_added_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_shares">Partajări</string>
|
||||
<string name="home_title_starred_albums">★ Albume marcate</string>
|
||||
<string name="home_title_starred_albums_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_starred_artists">★ Artiști marcați</string>
|
||||
<string name="home_title_starred_artists_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_starred_tracks">★ Piese marcate</string>
|
||||
<string name="home_title_starred_tracks_see_all_button">Vezi tot</string>
|
||||
<string name="home_title_top_songs">Piesa dvs. top</string>
|
||||
<string name="home_option_reorganize">Reorganizați</string>
|
||||
<string name="label_dot_separator" translatable="false">•</string>
|
||||
<string name="label_placeholder" translatable="false">--</string>
|
||||
<string name="library_title_album">Albume</string>
|
||||
<string name="library_title_album_see_all_button">Vezi tot</string>
|
||||
<string name="library_title_artist">Artiști</string>
|
||||
<string name="library_title_artist_see_all_button">Vezi tot</string>
|
||||
<string name="library_title_genre">Genuri</string>
|
||||
<string name="library_title_genre_see_all_button">Vezi tot</string>
|
||||
<string name="library_title_music_folder">Foldere de muzică</string>
|
||||
<string name="library_title_playlist">Playlisturi</string>
|
||||
<string name="library_title_playlist_see_all_button">Vezi tot</string>
|
||||
<string name="login_empty">Niciun server adăugat</string>
|
||||
<string name="login_title">Servere Subsonic</string>
|
||||
<string name="login_title_expanded">Servere Subsonic</string>
|
||||
<string name="media_route_menu_title">Cast</string>
|
||||
<string name="menu_add_button">Adăugaţi</string>
|
||||
<string name="menu_add_to_playlist_button">Adăugați la playlist</string>
|
||||
<string name="menu_download_all_button">Descărcați tot</string>
|
||||
<string name="menu_rate_album">Evaluați albumul</string>
|
||||
<string name="menu_download_label">Descarcaţi</string>
|
||||
<string name="menu_filter_all">Toate</string>
|
||||
<string name="menu_filter_download">Descarcate</string>
|
||||
<string name="menu_group_by_album">Album</string>
|
||||
<string name="menu_group_by_artist">Artist</string>
|
||||
<string name="menu_group_by_genre">Gen</string>
|
||||
<string name="menu_group_by_track">Piesă</string>
|
||||
<string name="menu_group_by_year">Anul</string>
|
||||
<string name="menu_home_label">Acasă</string>
|
||||
<string name="menu_last_week_name">Săptămâna trecută</string>
|
||||
<string name="menu_last_month_name">Luna trecută</string>
|
||||
<string name="menu_last_year_name">Anul trecut</string>
|
||||
<string name="menu_library_label">Bibliotecă</string>
|
||||
<string name="menu_search_button">Cautaţi</string>
|
||||
<string name="menu_settings_button">Setări</string>
|
||||
<string name="menu_sort_artist">Artist</string>
|
||||
<string name="menu_sort_name">Nume</string>
|
||||
<string name="menu_sort_random">Aleatoriu</string>
|
||||
<string name="menu_sort_album_count">Număr Album</string>
|
||||
<string name="menu_sort_recently_added">Adăugate recent</string>
|
||||
<string name="menu_sort_recently_played">Redate recent</string>
|
||||
<string name="menu_sort_most_played">Cele mai ascultate</string>
|
||||
<string name="menu_sort_most_recently_starred">Cel mai recent marcat</string>
|
||||
<string name="menu_sort_least_recently_starred">Cel mai puțin recent marcat</string>
|
||||
<string name="menu_pin_button">Adăugați pe ecranul de start</string>
|
||||
<string name="menu_unpin_button">Eliminați din ecranul de start</string>
|
||||
<string name="menu_sort_year">Anul</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">Curățați coada de redare</string>
|
||||
<string name="player_queue_save_queue_success">Coadă de redare salvată</string>
|
||||
<string name="player_queue_save_to_playlist">Salvați Coada în Playlist</string>
|
||||
<string name="player_queue_load_queue">Încărcați Coada</string>
|
||||
<string name="player_lyrics_download_content_description">Descărcați versuri pentru redare offline</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Versuri descărcate pentru redare offline</string>
|
||||
<string name="player_lyrics_download_success">Versuri salvate pentru redare offline.</string>
|
||||
<string name="player_lyrics_download_failure">Versurile nu sunt disponibile pentru descărcare.</string>
|
||||
<string name="player_server_priority">Prioritate Server</string>
|
||||
<string name="player_unknown_format">Format necunoscut</string>
|
||||
<string name="player_transcoding">Transcodare</string>
|
||||
<string name="player_transcoding_requested">cerut</string>
|
||||
<string name="playlist_catalogue_title">Catalogul Playlisturi</string>
|
||||
<string name="playlist_catalogue_title_expanded">Răsfoiți Playlisturi</string>
|
||||
<string name="playlist_chooser_dialog_empty">Niciun playlist creat</string>
|
||||
<string name="playlist_chooser_dialog_negative_button">Anulati</string>
|
||||
<string name="playlist_chooser_dialog_neutral_button">Creaţi</string>
|
||||
<string name="playlist_chooser_dialog_title">Adăugați la un playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">Piesa(e) adăugată(e) la playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_failure">Eșec la adăugarea piese(lor) la playlist</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">Toate piesele au fost omise ca duplicate</string>
|
||||
<string name="playlist_counted_tracks">%1$d piese • %2$s</string>
|
||||
<string name="playlist_duration">Durată • %1$s</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">Apasă lung pentru a șterge</string>
|
||||
<string name="playlist_editor_dialog_hint_name">Nume Playlist</string>
|
||||
<string name="playlist_editor_dialog_negative_button">Anulati</string>
|
||||
<string name="playlist_editor_dialog_neutral_button">Ștergeţi</string>
|
||||
<string name="playlist_editor_dialog_positive_button">Salvaţi</string>
|
||||
<string name="playlist_editor_dialog_title">Editați playlist</string>
|
||||
<string name="playlist_page_play_button">Redare</string>
|
||||
<string name="playlist_page_shuffle_button">Amestecare</string>
|
||||
<string name="playlist_song_count">Playlist • %1$d cântece</string>
|
||||
<string name="podcast_bottom_sheet_add_to_queue">Adăugați la coadă</string>
|
||||
<string name="podcast_bottom_sheet_delete">Ștergeţi</string>
|
||||
<string name="podcast_bottom_sheet_download">Descarcaţi</string>
|
||||
<string name="podcast_bottom_sheet_go_to_channel">Mergeți la canal</string>
|
||||
<string name="podcast_bottom_sheet_play_next">Redați următoarea</string>
|
||||
<string name="podcast_bottom_sheet_remove">Eliminaţi</string>
|
||||
<string name="podcast_channel_catalogue_title">Canale</string>
|
||||
<string name="podcast_channel_catalogue_title_expanded">Răsfoiți Canale</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">URL RSS</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Canal Podcast</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Podcasturile nu sunt acceptate de acest server.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Descriere</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episoade</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">Nu sunt episoade disponibile</string>
|
||||
<string name="podcast_episode_download_request_snackbar">Solicitarea dvs. a fost trimisă la server</string>
|
||||
<string name="podcast_info_empty_button">Faceți clic pentru a ascunde secțiunea\nEfectele vor fi vizibile la restart</string>
|
||||
<string name="podcast_info_empty_subtitle">Odată ce adăugați un canal, îl veți găsi aici</string>
|
||||
<string name="podcast_info_empty_title">Nu au fost găsite podcasturi!</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">URL Pagină de Start Radio</string>
|
||||
<string name="radio_editor_dialog_hint_name">Nume Radio</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">URL Flux Radio</string>
|
||||
<string name="radio_editor_dialog_negative_button">Anulati</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Ștergeţi</string>
|
||||
<string name="radio_editor_dialog_positive_button">Salvaţi</string>
|
||||
<string name="radio_editor_dialog_added">Stație radio adăugată</string>
|
||||
<string name="radio_editor_dialog_updated">Stație radio actualizată</string>
|
||||
<string name="radio_editor_dialog_title">Stație Radio Internet</string>
|
||||
<string name="radio_station_info_empty_button">Faceți clic pentru a ascunde secțiunea\nEfectele vor fi vizibile la restart</string>
|
||||
<string name="radio_station_info_empty_subtitle">Odată ce adăugați o stație radio, o veți găsi aici</string>
|
||||
<string name="radio_station_info_empty_title">Nu au fost găsite stații!</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Gestionarea radioului internet nu este acceptată de acest server.</string>
|
||||
<string name="rating_dialog_negative_button">Anulati</string>
|
||||
<string name="rating_dialog_positive_button">Salvaţi</string>
|
||||
<string name="rating_dialog_title">Evaluaţi</string>
|
||||
<string name="search_hint">Căutare titlu, artiști sau albume</string>
|
||||
<string name="search_info_minimum_characters">Introduceți cel puțin trei caractere</string>
|
||||
<string name="search_title_album">Albume</string>
|
||||
<string name="search_title_artist">Artiști</string>
|
||||
<string name="search_title_song">Cântece</string>
|
||||
<string name="server_signup_dialog_action_low_security">Securitate redusă</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">Apasă lung pentru a șterge</string>
|
||||
<string name="server_signup_dialog_hint_local_address">URL Local</string>
|
||||
<string name="server_signup_dialog_hint_name">Nume Server</string>
|
||||
<string name="server_signup_dialog_hint_password">Parolă</string>
|
||||
<string name="server_signup_dialog_hint_url">URL Server</string>
|
||||
<string name="server_signup_dialog_hint_username">Nume Utilizator</string>
|
||||
<string name="server_signup_dialog_negative_button">Anulati</string>
|
||||
<string name="server_signup_dialog_neutral_button">Ștergeţi</string>
|
||||
<string name="server_signup_dialog_positive_button">Salvaţi</string>
|
||||
<string name="server_signup_dialog_title">Adăugați server</string>
|
||||
<string name="server_unreachable_dialog_negative_button">Anulati</string>
|
||||
<string name="server_unreachable_dialog_neutral_button">Mergeți la conectare</string>
|
||||
<string name="server_unreachable_dialog_positive_button">Continuaţi oricum</string>
|
||||
<string name="server_unreachable_dialog_summary">Serverul solicitat este indisponibil. Dacă alegeți să continuați, acest dialog nu va apărea pentru următoarea oră.</string>
|
||||
<string name="server_unreachable_dialog_title">Server inaccesibil</string>
|
||||
<string name="settings_about_summary">Tempus este un client de muzică open source și ușor pentru Subsonic, proiectat și construit nativ pentru Android.</string>
|
||||
<string name="settings_about_title">Despre</string>
|
||||
<string name="settings_always_on_display">Afișaj mereu activ</string>
|
||||
<string name="settings_allow_playlist_duplicates">Permiteți adăugarea duplicatelor la playlist</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Dacă este activat, duplicatele nu vor fi verificate la adăugarea la un playlist.</string>
|
||||
<string name="settings_audio_transcode_download_format">Format transcodare</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Dacă este activat, Tempus nu va forța descărcarea pistei cu setările de transcodare de mai jos.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Prioritizați setările serverului utilizate pentru streaming în descărcări</string>
|
||||
<string name="settings_audio_transcode_download_summary">Dacă este activat, Tempus va descărca piste transcodate.</string>
|
||||
<string name="settings_audio_transcode_download_title">Descărcați piste transcodate</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">Dacă este activat, serverul va fi întrebat cu privire la durata estimată a piesei.</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">Estimare lungime conținut</string>
|
||||
<string name="settings_audio_transcode_format_download">Format transcodare pentru descărcări</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Format transcodare pe mobil</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Format transcodare pe Wi-Fi</string>
|
||||
<string name="settings_audio_transcode_priority_summary">Dacă este activat, Tempus nu va forța streamarea piesei cu setările de transcodare de mai jos.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Prioritizați setările de transcodare ale serverului</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Prioritate pentru transcodarea piesei acordată serverului</string>
|
||||
<string name="settings_buffering_strategy">Strategie de bufering</string>
|
||||
<string name="settings_buffering_strategy_summary">Pentru ca modificarea să aibă efect, trebuie să reporniți manual aplicația.</string>
|
||||
<string name="settings_choose_download_folder">Alegeți un folder pentru fișierele de muzică descărcate</string>
|
||||
<string name="settings_clear_download_folder">Curățați folderul de descărcare</string>
|
||||
<string name="settings_continuous_play_summary">Permite muzicii să continue redarea după ce o playlist s-a încheiat, redând piese similare</string>
|
||||
<string name="settings_continuous_play_title">Redare continuă</string>
|
||||
<string name="settings_covers_cache">Dimensiune cache-ul picturilor</string>
|
||||
<string name="settings_data_saving_mode_summary">Pentru a reduce consumul de date, evitați descărcarea coperii.</string>
|
||||
<string name="settings_data_saving_mode_title">Limitați utilizarea datelor mobile</string>
|
||||
<string name="settings_delete_download_storage_summary">Continuarea va duce la ștergerea ireversibilă a tuturor articolelor salvate.</string>
|
||||
<string name="settings_delete_download_storage_title">Ștergeți articolele salvate</string>
|
||||
<string name="settings_download_storage_title">Stocare descărcare</string>
|
||||
<string name="settings_download_folder_cleared">Folderul de descărcare a fost golit.</string>
|
||||
<string name="settings_download_folder_set">Folderul de descărcare a fost setat</string>
|
||||
<string name="settings_set_download_folder">Setați folderul de descărcare</string>
|
||||
<string name="settings_system_equalizer_summary">Ajustați setările audio</string>
|
||||
<string name="settings_system_equalizer_title">Egalizator sistem</string>
|
||||
<string name="settings_github_link">https://github.com/eddyizm/tempus</string>
|
||||
<string name="settings_github_summary">Urmăriți dezvoltarea</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Actualizări</string>
|
||||
<string name="settings_github_update_title">Verificați pe github lansări noi</string>
|
||||
<string name="settings_github_update_summary">Dacă utilizați versiunea github, prin implicare, aplicația va verifica noile versiuni de apk. Comutați pentru a dezactiva verificările automate pe github</string>
|
||||
<string name="settings_support_summary">Alăturați-vă discuțiilor comunității și asistență</string>
|
||||
<string name="settings_support_title">Asistență utilizator</string>
|
||||
<string name="settings_scan_result">Scanare: numărare %1$d piese</string>
|
||||
<string name="settings_image_size">Setați rezoluția imaginii</string>
|
||||
<string name="settings_language">Limbă</string>
|
||||
<string name="settings_logout_title">Deconectaţi</string>
|
||||
<string name="settings_max_bitrate_download">Bitrate pentru descărcări</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate pe mobil</string>
|
||||
<string name="settings_max_bitrate_wifi">Bitrate pe Wi-Fi</string>
|
||||
<string name="settings_media_cache">Dimensiune cache-ul fișierelor media</string>
|
||||
<string name="settings_music_directory">Arătați directoare de muzică</string>
|
||||
<string name="settings_music_directory_summary">Dacă este activat, afișează secțiunea directorului de muzică. Vă rugăm să rețineți că pentru ca navigarea folderului să funcționeze corect, serverul trebuie să suporte această funcție.</string>
|
||||
<string name="settings_podcast">Arătați podcast</string>
|
||||
<string name="settings_podcast_summary">Dacă este activat, afișează secțiunea podcast. Reporniți aplicația pentru ca aceasta să aibă efect complet.</string>
|
||||
<string name="settings_audio_quality">Arătați calitate audio</string>
|
||||
<string name="settings_audio_quality_summary">Bitrate-ul și formatul audio vor fi afișate pentru fiecare pistă audio.</string>
|
||||
<string name="settings_song_rating">Arătați evaluarea cu stele a cântecului</string>
|
||||
<string name="settings_song_rating_summary">Dacă este activat, afișează evaluarea cu 5 stele pentru piesă pe pagina cântecului\n\n*Necesită restartarea aplicației</string>
|
||||
<string name="settings_item_rating">Arătați evaluarea articolului</string>
|
||||
<string name="settings_item_rating_summary">Dacă este activat, va fi afișată evaluarea articolului și dacă este marcat ca favorit.</string>
|
||||
<string name="settings_queue_syncing_countdown">Cronometru sincronizare</string>
|
||||
<string name="settings_queue_syncing_summary">Dacă este activat, utilizatorul va avea capacitatea de a-și salva coada de redare și va putea să încărce starea la deschiderea aplicației.</string>
|
||||
<string name="settings_queue_syncing_title">Sincronizare coadă de redare pentru acest utilizator [Nu este complet finalizat]</string>
|
||||
<string name="settings_show_mini_shuffle_button">Arătați butonul Amestecare</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Dacă este activat, afișează butonul amestecare, elimină inima din mini player</string>
|
||||
<string name="settings_radio">Arătați radio</string>
|
||||
<string name="settings_radio_summary">Dacă este activat, afișează secțiunea radio. Reporniți aplicația pentru ca aceasta să aibă efect complet.</string>
|
||||
<string name="settings_auto_download_lyrics">Descarcă versuri automat</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Salvați automat versurile când sunt disponibile, astfel încât să poată fi afișate offline.</string>
|
||||
<string name="settings_replay_gain">Setați modul replay gain</string>
|
||||
<string name="settings_rounded_corner">Colțuri rotunjite</string>
|
||||
<string name="settings_rounded_corner_size">Dimensiunea colțuri</string>
|
||||
<string name="settings_rounded_corner_size_summary">Setează magnitudinea unghiului de curbură.</string>
|
||||
<string name="settings_rounded_corner_summary">Dacă este activat, stabilește un unghi de curbură pentru toate coperii redate. Modificările vor avea efect la restart.</string>
|
||||
<string name="settings_scan_title">Biblioteca de scanare</string>
|
||||
<string name="settings_scrobble_title">Activați scrobbling muzică</string>
|
||||
<string name="settings_system_language">Limba sistemului</string>
|
||||
<string name="settings_share_title">Activați partajarea muzicii</string>
|
||||
<string name="settings_streaming_cache_size">Dimensiune cache-ul de streaming</string>
|
||||
<string name="settings_streaming_cache_storage_title">Stocare cache-ul de streaming</string>
|
||||
<string name="settings_sub_summary_scrobble">Este important de remarcat că scrobbling se bazează și pe faptul că serverul este activat pentru a primi aceste date.</string>
|
||||
<string name="settings_summary_skip_min_star_rating">La ascultarea radioului unui artist, a unui mix instant sau la amestecare toate, piesele sub o anumită evaluare a utilizatorului vor fi ignorate.</string>
|
||||
<string name="settings_summary_replay_gain">Replay gain este o funcție care vă permite să ajustați nivelul de volum al pistelor audio pentru o experiență de ascultare consistentă. Această setare este eficace doar dacă piesa conține metadatele necesare.</string>
|
||||
<string name="settings_summary_scrobble">Scrobbling este o funcție care permite dispozitivului dvs. să trimită informații despre cântecele pe care le ascultați la serverul de muzică. Aceste informații ajută la crearea de recomandări personalizate pe baza preferințelor dvs. de muzică.</string>
|
||||
<string name="settings_summary_share">Permite utilizatorului să partajeze muzică printr-un link. Funcționalitatea trebuie să fie acceptată și activată pe server și este limitată la piese individuale, albume și playlisturi.</string>
|
||||
<string name="settings_summary_syncing">Returnează starea cozii de redare pentru acest utilizator. Aceasta include pistele din coada de redare, piesa în curs de redare și poziția din aceasta. Serverul trebuie să suporte această funcție.\n*Această setare nu funcționează 100% pe toate serverele/dispozitivele.</string>
|
||||
<string name="settings_summary_streaming_cache_size">%1$s \nActual în uz: %2$s MiB</string>
|
||||
<string name="settings_summary_transcoding">Prioritate acordată modului de transcodare. Dacă este setat la \"Redare directă\", bitrate-ul fișierului nu va fi modificat.</string>
|
||||
<string name="settings_summary_transcoding_download">Descărcați media transcodat. Dacă este activat, punctul final de descărcare nu va fi utilizat, ci următoarele setări. \n\n Dacă \"Format transcodare pentru descărcări\" este setat la \"Descărcare directă\", bitrate-ul fișierului nu va fi modificat.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">Când fișierul este transcodat în mișcare, clientul nu arată de obicei lungimea pistei. Este posibil să se ceară serverelor care acceptă funcționalitatea să estimeze durata piesei în curs de redare, dar timpii de răspuns pot fi mai lungi.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">Dacă este activat, artiștii marcați vor fi descărcați pentru utilizare offline.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sincronizați artiștii marcați pentru utilizare offline</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">Dacă este activat, albumele marcate vor fi descărcate pentru utilizare offline.</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_title">Sincronizați albumele marcate pentru utilizare offline</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">Dacă este activat, piesele marcate vor fi descărcate pentru utilizare offline.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_title">Sincronizați piesele marcate pentru utilizare offline</string>
|
||||
<string name="settings_theme">Temă</string>
|
||||
<string name="settings_title_data">Date</string>
|
||||
<string name="settings_title_general">General</string>
|
||||
<string name="settings_title_playlist">Playlist</string>
|
||||
<string name="settings_title_rating">Evaluare</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">Ignoraţi piesele pe baza evaluării</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">Cântece cu o evaluare de:</string>
|
||||
<string name="settings_title_share">Partajare</string>
|
||||
<string name="settings_title_syncing">Sincronizare</string>
|
||||
<string name="settings_title_transcoding">Transcodare</string>
|
||||
<string name="settings_title_transcoding_download">Descărcare Transcodare</string>
|
||||
<string name="settings_title_ui">UI</string>
|
||||
<string name="settings_transcoded_download">Descărcare transcodată</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Versiune</string>
|
||||
<string name="settings_wifi_only_summary">Cere confirmarea utilizatorului înainte de streaming pe rețeaua mobilă.</string>
|
||||
<string name="settings_wifi_only_title">Alerta stream numai Wi-Fi</string>
|
||||
<string name="share_bottom_sheet_copy_link">Copiaţi link</string>
|
||||
<string name="share_bottom_sheet_delete">Ștergeţi partajare</string>
|
||||
<string name="share_bottom_sheet_update">Actualizaţi partajare</string>
|
||||
<string name="share_subtitle_item">Data expirării: %1$s</string>
|
||||
<string name="share_no_expiration">Niciodată</string>
|
||||
<string name="share_unsupported_error">Partajarea nu este acceptată sau nu este activată</string>
|
||||
<string name="asset_link_clipboard_label">Link activ Tempus</string>
|
||||
<string name="asset_link_label_song">UID Cântec</string>
|
||||
<string name="asset_link_label_album">UID Album</string>
|
||||
<string name="asset_link_label_artist">UID Artist</string>
|
||||
<string name="asset_link_label_playlist">UID Playlist</string>
|
||||
<string name="asset_link_label_genre">UID Gen</string>
|
||||
<string name="asset_link_label_year">UID Anul</string>
|
||||
<string name="asset_link_label_unknown">UID Activ</string>
|
||||
<string name="asset_link_error_unsupported">Link activ neacceptat</string>
|
||||
<string name="asset_link_error_song">Cântecul nu a putut fi deschis</string>
|
||||
<string name="asset_link_error_album">Albumul nu a putut fi deschis</string>
|
||||
<string name="asset_link_error_artist">Artistul nu a putut fi deschis</string>
|
||||
<string name="asset_link_error_playlist">Playlistul nu a putut fi deschis</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_copied_toast">Copiat %1$s în clipboard</string>
|
||||
<string name="asset_link_debug_toast">Link activ: %1$s</string>
|
||||
<string name="share_update_dialog_hint_description">Descriere</string>
|
||||
<string name="share_update_dialog_hint_expiration_date">Data expirării</string>
|
||||
<string name="share_update_dialog_negative_button">Anulati</string>
|
||||
<string name="share_update_dialog_positive_button">Salvaţi</string>
|
||||
<string name="share_update_dialog_title">Partajare</string>
|
||||
<string name="song_bottom_sheet_add_to_playlist">Adăugați la playlist</string>
|
||||
<string name="song_bottom_sheet_add_to_queue">Adăugați la coadă</string>
|
||||
<string name="song_bottom_sheet_download">Descarcaţi</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_album">Eroare la preluarea albumului</string>
|
||||
<string name="song_bottom_sheet_error_retrieving_artist">Eroare la preluarea artistului</string>
|
||||
<string name="song_bottom_sheet_go_to_album">Mergeți la album</string>
|
||||
<string name="song_bottom_sheet_go_to_artist">Mergeți la artist</string>
|
||||
<string name="song_bottom_sheet_instant_mix">Mix instant</string>
|
||||
<string name="song_bottom_sheet_play_next">Redați următoarea</string>
|
||||
<string name="song_bottom_sheet_rate">Evaluaţi</string>
|
||||
<string name="song_bottom_sheet_remove">Eliminaţi</string>
|
||||
<string name="song_bottom_sheet_share">Partajați</string>
|
||||
<string name="song_list_page_downloaded">Descarcate</string>
|
||||
<string name="song_list_page_most_played">Piese cele mai ascultate</string>
|
||||
<string name="song_list_page_recently_added">Piese adăugate recent</string>
|
||||
<string name="song_list_page_recently_played">Piese redate recent</string>
|
||||
<string name="song_list_page_starred">Piese marcate</string>
|
||||
<string name="song_list_page_top">Piesele top ale lui %1$s</string>
|
||||
<string name="song_list_page_year">Anul %1$d</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<string name="starred_sync_dialog_negative_button">Anulati</string>
|
||||
<string name="starred_sync_dialog_neutral_button">Continuaţi</string>
|
||||
<string name="starred_sync_dialog_positive_button">Continuaţi și descarcaţi</string>
|
||||
<string name="starred_sync_dialog_summary">Descărcarea pieselor marcate poate necesita o cantitate mare de date.</string>
|
||||
<string name="starred_sync_dialog_title">Sincronizează piesele marcate</string>
|
||||
<string name="starred_artist_sync_dialog_summary">Descărcarea artiștilor marcați poate necesita o cantitate mare de date.</string>
|
||||
<string name="starred_artist_sync_dialog_title">Sincronizează artiștii marcați</string>
|
||||
<string name="starred_album_sync_dialog_summary">Descărcarea albumelor marcate poate necesita o cantitate mare de date.</string>
|
||||
<string name="starred_album_sync_dialog_title">Sincronizează albumele marcate</string>
|
||||
<string name="streaming_cache_storage_dialog_sub_summary">Pentru ca modificările să aibă efect, reporniți aplicația.</string>
|
||||
<string name="streaming_cache_storage_dialog_summary">Schimbarea destinației fișierelor cache-ului dintr-un spațiu de stocare la altul poate duce la ștergerea oricăror fișiere cache-ului anterior în celălalt spațiu de stocare.</string>
|
||||
<string name="streaming_cache_storage_dialog_title">Selectați opțiunea de stocare</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">Extern</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">Intern</string>
|
||||
<string name="support_url">https://ko-fi.com/eddyizm</string>
|
||||
<string name="track_info_album">Album</string>
|
||||
<string name="track_info_artist">Artist</string>
|
||||
<string name="track_info_bit_depth">Adâncime bit</string>
|
||||
<string name="track_info_bitrate">Bitrate</string>
|
||||
<string name="track_info_content_type">Tip conținut</string>
|
||||
<string name="track_info_dialog_positive_button">OK</string>
|
||||
<string name="track_info_dialog_title">Informații piesă</string>
|
||||
<string name="track_info_disc_number">Număr disc</string>
|
||||
<string name="track_info_duration">Durată</string>
|
||||
<string name="track_info_genre">Gen</string>
|
||||
<string name="track_info_path">Cale</string>
|
||||
<string name="track_info_sampling_rate">Rată de eșantionare</string>
|
||||
<string name="track_info_size">Dimensiune</string>
|
||||
<string name="track_info_suffix">Sufix</string>
|
||||
<string name="track_info_summary_downloaded_file">Fișierul a fost descărcat folosind API-urile Subsonic. Codecul și bitrate-ul fișierului rămân neschimbate din fișierul sursă.</string>
|
||||
<string name="track_info_summary_full_transcode">Aplicația va cere serverului să transcodeze fișierul și să-i modifice bitrate-ul. Codecul solicitat de utilizator este %1$s, cu un bitrate de %2$s. Orice posibile modificări ale codecului și bitrate-ul fișierului în formatul ales vor fi gestionate de server, care poate sau nu să suporte operația.</string>
|
||||
<string name="track_info_summary_original_file">Aplicația va citi doar fișierul original furnizat de server. Aplicația va cere în mod explicit serverului fișierul netranscodat cu bitrate-ul sursei originale.</string>
|
||||
<string name="track_info_summary_server_prioritized">Calitatea fișierului care urmează a fi redat este lăsată la alegerea serverului. Aplicația nu va forța alegerea codecului și bitrate-ului pentru nicio posibilă transcodare.</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">Aplicația va cere serverului să modifice bitrate-ul fișierului. Utilizatorul a solicitat un bitrate de %1$s, în timp ce codecul fișierului sursă va rămâne același. Orice modificări ale bitrate-ului fișierului în formatul ales vor fi făcute de server, care poate sau nu să suporte operația.</string>
|
||||
<string name="track_info_summary_transcoding_codec">Aplicația va cere serverului să transcodeze fișierul. Codecul solicitat de utilizator este %1$s, în timp ce bitrate-ul va fi același cu fișierul sursă. Potențiala transcodare a fișierului în formatul ales depinde de server, deoarece poate sau nu să suporte operația.</string>
|
||||
<string name="track_info_title">Titlu</string>
|
||||
<string name="track_info_track_number">Număr piesă</string>
|
||||
<string name="track_info_transcoded_content_type">Tip conținut transcodat</string>
|
||||
<string name="track_info_transcoded_suffix">Sufix transcodat</string>
|
||||
<string name="track_info_year">Anul</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">Mulțumiri speciale merită unDraw fără ale cărui ilustrații nu am putut face această aplicație mai frumoasă.</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="widget_label">Widget Tempus</string>
|
||||
<string name="widget_not_playing">Nu se redă</string>
|
||||
<string name="widget_placeholder_subtitle">Deschideți Tempus</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_content_desc_album_art">Ilustrație album</string>
|
||||
<string name="widget_content_desc_play_pause">Redare sau pauză</string>
|
||||
<string name="widget_content_desc_next">Piesa următoare</string>
|
||||
<string name="widget_content_desc_prev">Piesa anterioară</string>
|
||||
<string name="widget_content_desc_shuffle">Activaţi amestecare</string>
|
||||
<string name="widget_content_desc_repeat">Schimbați modul repetare</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d album de sincronizat</item>
|
||||
<item quantity="other">%d albume de sincronizat</item>
|
||||
</plurals>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">%d artist de sincronizat</item>
|
||||
<item quantity="other">%d artiști de sincronizat</item>
|
||||
</plurals>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">Se descarcă %d cântec</item>
|
||||
<item quantity="other">Se descarcă %d cântece</item>
|
||||
</plurals>
|
||||
<string name="equalizer_fragment_title">Egalizator</string>
|
||||
<string name="equalizer_reset">Resetaţi</string>
|
||||
<string name="equalizer_enable">Activaţi</string>
|
||||
<string name="equalizer_not_supported">Nu este acceptat pe acest dispozitiv</string>
|
||||
<string name="settings_app_equalizer">Egalizator</string>
|
||||
<string name="settings_app_equalizer_summary">Deschideți egalizatorul încorporat</string>
|
||||
|
||||
<string name="settings_album_detail">Arătați detalii album</string>
|
||||
<string name="settings_album_detail_summary">Dacă este activat, afișează detalii album cum ar fi gen, număr de cântece etc. pe pagina albumului</string>
|
||||
<string name="settings_artist_sort_by_album_count">Sortaţi artiști după numărul de albume</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Dacă este activat, sortează artiștii după numărul de albume. Sortează după nume dacă este dezactivat.</string>
|
||||
|
||||
<string name="folder_play_collecting">Se colectează cântece din folder…</string>
|
||||
<string name="folder_play_playing">Se redau %d cântece</string>
|
||||
<string name="folder_play_no_songs">Nu au fost găsite cântece în folder</string>
|
||||
|
||||
<string name="search_sort_title">Sortaţi căutările recente cronologic</string>
|
||||
<string name="search_sort_summary">Dacă este activat, sortează căutările cronologic. Sortează după nume dacă este dezactivat.</string>
|
||||
</resources>
|
||||
@@ -270,7 +270,13 @@
|
||||
<string name="server_unreachable_dialog_title">Сервер недоступен</string>
|
||||
<string name="settings_about_summary">Tempus — это легкий музыкальный клиент с открытым исходным кодом для Subsonic, разработанный и созданный специально для Android.</string>
|
||||
<string name="settings_about_title">О нас</string>
|
||||
<string name="settings_album_detail">Показать детали альбома</string>
|
||||
<string name="settings_album_detail_summary">Если включено, отображать информацию об альбоме, например жанр, количество песен и т. д., на странице альбома.</string>
|
||||
<string name="settings_allow_playlist_duplicates">Разрешить добавление дубликатов в плейлист</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">Если включено, дубликаты не будут проверяться при добавлении в плейлист..</string>
|
||||
<string name="settings_always_on_display">Всегда на дисплее</string>
|
||||
<string name="settings_artist_sort_by_album_count">Сортировать исполнителей по количеству альбомов</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">Если включено, сортировать исполнителей по количеству альбомов. Если отключено, сортировать по имени.</string>
|
||||
<string name="settings_audio_transcode_download_format">Формат перекодирования</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">Если этот параметр включен, Tempus не будет принудительно загружать трек с настройками перекодирования, указанными ниже.</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">Установите приоритет настроек сервера, используемых для потоковой передачи при загрузке</string>
|
||||
@@ -319,8 +325,12 @@
|
||||
<string name="settings_queue_syncing_countdown">Таймер синхронизации</string>
|
||||
<string name="settings_queue_syncing_summary">Если этот параметр включен, пользователь будет иметь возможность сохранять свою очередь воспроизведения и загружать состояние при открытии приложения.</string>
|
||||
<string name="settings_queue_syncing_title">Синхронизировать очередь воспроизведения для этого пользователя</string>
|
||||
<string name="settings_show_mini_shuffle_button">Показать кнопку Shuffle</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">Если включено, показывать кнопку перемешивания, убрать сердечко в мини-плеере</string>
|
||||
<string name="settings_radio">Показать радио</string>
|
||||
<string name="settings_radio_summary">Если включено, показывать раздел радио. Перезапустите приложение, чтобы оно вступило в силу.</string>
|
||||
<string name="settings_auto_download_lyrics">Автоматическая загрузка текстов песен</string>
|
||||
<string name="settings_auto_download_lyrics_summary">Автоматически сохранять тексты песен, когда они доступны, чтобы их можно было просматривать в автономном режиме.</string>
|
||||
<string name="settings_replay_gain">Установите режим усиления воспроизведения</string>
|
||||
<string name="settings_rounded_corner">Закругленные углы</string>
|
||||
<string name="settings_rounded_corner_size">Размер углов</string>
|
||||
@@ -346,6 +356,9 @@
|
||||
<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_artists_for_offline_use_title">Синхронизировать избранных исполнителей для использования офлайн</string>
|
||||
<string name="settings_support_summary">Присоединяйтесь к обсуждениям в сообществе и оказывайте поддержку</string>
|
||||
<string name="settings_support_title">Поддержка пользователей</string>
|
||||
<string name="settings_theme">Тема</string>
|
||||
<string name="settings_title_data">Данные</string>
|
||||
<string name="settings_title_general">Общий</string>
|
||||
|
||||
@@ -32,6 +32,21 @@
|
||||
<item>300</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="streaming_cache_size_titles">
|
||||
<item>禁用</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>原始</item>
|
||||
<item>32 kbps</item>
|
||||
@@ -224,4 +239,19 @@
|
||||
<item>4</item>
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>不筛选评分</item>
|
||||
<item>1 星及以上</item>
|
||||
<item>2 星及以上</item>
|
||||
<item>3 星及以上</item>
|
||||
<item>4 星及以上</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>
|
||||
@@ -1,13 +1,13 @@
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_conclusion">如果遇到问题,请访问 https://dontkillmyapp.com。 省电优化选项可能会影响应用的性能,网站上提供了如何禁用这些选项的详细说明。</string>
|
||||
<string name="activity_battery_optimizations_summary">请禁用针对媒体锁屏播放的电池优化。</string>
|
||||
<string name="activity_battery_optimizations_summary">请禁用针对锁屏播放的电池优化。</string>
|
||||
<string name="activity_battery_optimizations_title">电池优化</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>
|
||||
<string name="album_bottom_sheet_go_to_artist">查看该艺术家</string>
|
||||
<string name="album_bottom_sheet_instant_mix">即时混合</string>
|
||||
<string name="album_bottom_sheet_instant_mix">即时混听</string>
|
||||
<string name="album_bottom_sheet_play_next">下一首播放</string>
|
||||
<string name="album_bottom_sheet_remove_all">移除所有</string>
|
||||
<string name="album_bottom_sheet_share">分享</string>
|
||||
@@ -17,25 +17,27 @@
|
||||
<string name="album_error_retrieving_artist">检索艺术家时出错</string>
|
||||
<string name="album_list_page_downloaded">已下载的专辑</string>
|
||||
<string name="album_list_page_most_played">最常播放的专辑</string>
|
||||
<string name="album_list_page_new_releases">新发行</string>
|
||||
<string name="album_list_page_new_releases">新发行的专辑</string>
|
||||
<string name="album_list_page_recently_added">最近添加的专辑</string>
|
||||
<string name="album_list_page_recently_played">最近播放的专辑</string>
|
||||
<string name="album_list_page_starred">收藏的专辑</string>
|
||||
<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_date_label">发行日期:%1$s</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">Tempus</string>
|
||||
<string name="artist_adapter_radio_station_starting">正在搜索...</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">即时混合</string>
|
||||
<string name="artist_bottom_sheet_instant_mix">即时混听</string>
|
||||
<string name="artist_bottom_sheet_shuffle">随机播放</string>
|
||||
<string name="artist_catalogue_title">艺术家</string>
|
||||
<string name="artist_catalogue_title_expanded">浏览艺术家</string>
|
||||
<string name="artist_error_retrieving_radio">检索艺术家的电台时出错</string>
|
||||
<string name="artist_error_retrieving_tracks">检索艺术家曲目时出错</string>
|
||||
<string name="artist_error_retrieving_tracks">检索艺术家歌曲时出错</string>
|
||||
<string name="artist_list_page_downloaded">已下载的艺术家</string>
|
||||
<string name="artist_list_page_starred">收藏的艺人</string>
|
||||
<string name="artist_list_page_starred">收藏的艺术家</string>
|
||||
<string name="artist_list_page_title">艺术家</string>
|
||||
<string name="artist_page_radio_button">电台</string>
|
||||
<string name="artist_page_shuffle_button">随机播放</string>
|
||||
@@ -43,33 +45,63 @@
|
||||
<string name="artist_page_title_album_more_like_this_button">更多相似</string>
|
||||
<string name="artist_page_title_album_section">专辑</string>
|
||||
<string name="artist_page_title_biography_more_button">更多</string>
|
||||
<string name="artist_page_title_biography_section">个人简介</string>
|
||||
<string name="artist_page_title_biography_section">艺术家简介</string>
|
||||
<string name="artist_page_title_most_streamed_song_section">最常播放的歌曲</string>
|
||||
<string name="artist_page_title_most_streamed_song_see_all_button">查看全部</string>
|
||||
<string name="asset_link_chip_text">%1$s • %2$s</string>
|
||||
<string name="asset_link_clipboard_label">Tempus 资源链接</string>
|
||||
<string name="asset_link_copied_toast">已将 %1$s 复制到剪贴板</string>
|
||||
<string name="asset_link_debug_toast">资源链接:%1$s</string>
|
||||
<string name="asset_link_error_album">无法打开该专辑</string>
|
||||
<string name="asset_link_error_artist">无法打开该艺术家页</string>
|
||||
<string name="asset_link_error_playlist">无法打开该播放列表</string>
|
||||
<string name="asset_link_error_song">无法打开该歌曲</string>
|
||||
<string name="asset_link_error_unsupported">不支持的资源链接</string>
|
||||
<string name="asset_link_label_album">专辑 UID</string>
|
||||
<string name="asset_link_label_artist">艺术家 UID</string>
|
||||
<string name="asset_link_label_genre">流派 UID</string>
|
||||
<string name="asset_link_label_playlist">播放列表 UID</string>
|
||||
<string name="asset_link_label_song">歌曲 UID</string>
|
||||
<string name="asset_link_label_unknown">资源 UID</string>
|
||||
<string name="asset_link_label_year">年份 UID</string>
|
||||
<string name="battery_optimization_negative_button">忽略</string>
|
||||
<string name="battery_optimization_neutral_button">不要再问</string>
|
||||
<string name="battery_optimization_neutral_button">不再询问</string>
|
||||
<string name="battery_optimization_positive_button">禁用</string>
|
||||
<string name="cast_expanded_controller_loading">加载中...</string>
|
||||
<string name="connection_alert_dialog_negative_button">取消</string>
|
||||
<string name="connection_alert_dialog_neutral_button">启用流量节省</string>
|
||||
<string name="connection_alert_dialog_positive_button">确定</string>
|
||||
<string name="connection_alert_dialog_summary">已限制通过 Wi-Fi 以外的连接访问 Subsonic 服务器。 要阻止此警告对话框再次出现,请在应用程序设置中禁用连接检查。</string>
|
||||
<string name="connection_alert_dialog_title">Wi-Fi网络未连接</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="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>
|
||||
<string name="download_directory_dialog_title">下载曲目</string>
|
||||
<string name="download_info_empty_subtitle">下载歌曲后,您可以在这里找到它。</string>
|
||||
<string name="download_directory_dialog_summary">该文件夹中的所有歌曲将被下载。子文件夹中的歌曲将不会被下载。</string>
|
||||
<string name="download_directory_dialog_title">下载歌曲</string>
|
||||
<string name="download_directory_set">设置歌曲下载位置</string>
|
||||
<string name="download_info_empty_subtitle">下载歌曲后,您可以在这里找到它</string>
|
||||
<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_refresh_button_content_description">刷新下载项</string>
|
||||
<string name="download_refresh_no_changes">没有遗漏的下载项。</string>
|
||||
<string name="download_refresh_no_directory">设置下载目录以刷新下载内容。</string>
|
||||
<plurals name="download_refresh_removed">
|
||||
<item quantity="one">已移除 %d 个缺失的下载项。</item>
|
||||
<item quantity="other">已移除 %d 个缺失的下载项。</item>
|
||||
</plurals>
|
||||
<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_directory_dialog_neutral_button">目录</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>
|
||||
@@ -78,31 +110,75 @@
|
||||
<string name="downloaded_bottom_sheet_remove">移除</string>
|
||||
<string name="downloaded_bottom_sheet_remove_all">移除所有</string>
|
||||
<string name="downloaded_bottom_sheet_shuffle">随机播放</string>
|
||||
<string name="empty_string"></string>
|
||||
<string name="empty_string" />
|
||||
<string name="equalizer_enable">启用</string>
|
||||
<string name="equalizer_fragment_title">均衡器</string>
|
||||
<string name="equalizer_not_supported">此设备不支持</string>
|
||||
<string name="equalizer_reset">重置</string>
|
||||
<string name="error_required">必需</string>
|
||||
<string name="error_server_prefix">必须是 http 或 https 前缀</string>
|
||||
<string name="exo_controls_heart_off_description">取消收藏</string>
|
||||
<string name="exo_controls_heart_on_description">收藏</string>
|
||||
<string name="exo_download_notification_channel_name">下载</string>
|
||||
<string name="filter_artist">筛选艺术家</string>
|
||||
<string name="filter_info_selection">选择两个或多个过滤器</string>
|
||||
<string name="filter_title">筛选</string>
|
||||
<string name="filter_title_expanded">筛选流派</string>
|
||||
<string name="folder_play_collecting">正在读取文件夹中的歌曲...</string>
|
||||
<string name="folder_play_no_songs">文件夹内未发现歌曲</string>
|
||||
<string name="folder_play_playing">正在播放 %d 首歌曲</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">流派目录</string>
|
||||
<string name="genre_catalogue_title_expanded">浏览流派</string>
|
||||
<string name="github_update_dialog_negative_button">稍后提醒</string>
|
||||
<string name="github_update_dialog_neutral_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_option_reorganize">定制首页</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_subtitle">请重启应用以应用更改。</string>
|
||||
<string name="home_rearrangement_dialog_title">主页排序</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_made_for_you">从您喜欢的歌曲开始混听</string>
|
||||
<string name="home_subtitle_new_internet_radio_station">添加新的电台</string>
|
||||
<string name="home_subtitle_new_podcast_channel">添加新的播客频道</string>
|
||||
<plurals name="home_sync_starred_albums_count">
|
||||
<item quantity="one">%d 个待同步专辑</item>
|
||||
<item quantity="other">%d 个待同步专辑</item>
|
||||
</plurals>
|
||||
<string name="home_sync_starred_albums_subtitle">标记为收藏的专辑可离线使用</string>
|
||||
<string name="home_sync_starred_albums_title">同步收藏的专辑</string>
|
||||
<string name="home_sync_starred_artists_subtitle">你收藏的艺术家有未下载的歌曲</string>
|
||||
<string name="home_sync_starred_artists_title">同步收藏的艺术家</string>
|
||||
<plurals name="home_sync_starred_artists_count">
|
||||
<item quantity="one">%d 个待同步艺术家</item>
|
||||
<item quantity="other">%d 个待同步艺术家</item>
|
||||
</plurals>
|
||||
<string name="home_sync_starred_cancel">取消</string>
|
||||
<string name="home_sync_starred_download">下载</string>
|
||||
<string name="home_sync_starred_subtitle">下载这些曲目可能需要大量移动数据</string>
|
||||
<string name="home_sync_starred_title">似乎有一些已收藏的曲目需要同步</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d 首待同步歌曲</item>
|
||||
<item quantity="other">%d 首待同步歌曲</item>
|
||||
</plurals>
|
||||
<string name="home_sync_starred_subtitle">下载这些歌曲可能需要大量移动数据流量</string>
|
||||
<string name="home_sync_starred_title">似乎有一些收藏的歌曲需要同步</string>
|
||||
<string name="home_title_best_of">最佳</string>
|
||||
<string name="home_title_discovery">发现</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">全部随机播放</string>
|
||||
<string name="home_title_flashback">闪回</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">随机播放全部</string>
|
||||
<string name="home_title_flashback">重温旧曲</string>
|
||||
<string name="home_title_internet_radio_station">网络广播电台</string>
|
||||
<string name="home_title_last_month">上月</string>
|
||||
<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">为您定制</string>
|
||||
<string name="home_title_most_played">最常播放</string>
|
||||
<string name="home_title_most_played_see_all_button">查看全部</string>
|
||||
@@ -119,7 +195,7 @@
|
||||
<string name="home_title_starred_albums_see_all_button">查看全部</string>
|
||||
<string name="home_title_starred_artists">★ 收藏的艺术家</string>
|
||||
<string name="home_title_starred_artists_see_all_button">查看全部</string>
|
||||
<string name="home_title_starred_tracks">★ 收藏的曲目</string>
|
||||
<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>
|
||||
@@ -146,31 +222,53 @@
|
||||
<string name="menu_group_by_album">专辑</string>
|
||||
<string name="menu_group_by_artist">艺术家</string>
|
||||
<string name="menu_group_by_genre">流派</string>
|
||||
<string name="menu_group_by_track">曲目</string>
|
||||
<string name="menu_group_by_track">歌曲</string>
|
||||
<string name="menu_group_by_year">年份</string>
|
||||
<string name="menu_home_label">首页</string>
|
||||
<string name="menu_last_month_name">上月</string>
|
||||
<string name="menu_last_week_name">上周</string>
|
||||
<string name="menu_last_year_name">去年</string>
|
||||
<string name="menu_library_label">曲库</string>
|
||||
<string name="menu_pin_button">添加到主页</string>
|
||||
<string name="menu_rate_album">专辑评分</string>
|
||||
<string name="menu_search_button">搜索</string>
|
||||
<string name="menu_settings_button">设置</string>
|
||||
<string name="menu_sort_album_count">专辑数量</string>
|
||||
<string name="menu_sort_artist">艺术家</string>
|
||||
<string name="menu_sort_name">姓名</string>
|
||||
<string name="menu_sort_least_recently_starred">最早收藏</string>
|
||||
<string name="menu_sort_most_played">最多播放</string>
|
||||
<string name="menu_sort_most_recently_starred">最近收藏</string>
|
||||
<string name="menu_sort_name">名称</string>
|
||||
<string name="menu_sort_random">随机</string>
|
||||
<string name="menu_sort_recently_added">最近添加</string>
|
||||
<string name="menu_sort_recently_played">最近播放</string>
|
||||
<string name="menu_sort_year">年份</string>
|
||||
<string name="menu_unpin_button">从主页移除</string>
|
||||
<string name="player_lyrics_download_content_description">下载离线歌词</string>
|
||||
<string name="player_lyrics_download_failure">暂无歌词可供下载。</string>
|
||||
<string name="player_lyrics_download_success">离线歌词已保存。</string>
|
||||
<string name="player_lyrics_downloaded_content_description">已下载的离线歌词</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_queue_clean_all_button">清空队列</string>
|
||||
<string name="player_queue_load_queue">加载队列</string>
|
||||
<string name="player_queue_save_queue_success">保存队列</string>
|
||||
<string name="player_queue_save_to_playlist">保存队列到播放列表</string>
|
||||
<string name="player_server_priority">服务器优先级</string>
|
||||
<string name="player_transcoding">正在转码</string>
|
||||
<string name="player_transcoding_requested">已请求转码</string>
|
||||
<string name="player_unknown_format">未知格式</string>
|
||||
<string name="playlist_catalogue_title">播放列表目录</string>
|
||||
<string name="playlist_catalogue_title_expanded">浏览播放列表</string>
|
||||
<string name="playlist_chooser_dialog_empty">尚未创建播放列表</string>
|
||||
<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_counted_tracks">%1$d 首曲目 • %2$s</string>
|
||||
<string name="playlist_duration">持续时间 • %1$s</string>
|
||||
<string name="playlist_chooser_dialog_toast_add_success">将歌曲添加到播放列表</string>
|
||||
<string name="playlist_chooser_dialog_toast_all_skipped">所有歌曲已存在,无需重复添加</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>
|
||||
@@ -189,6 +287,7 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">浏览频道</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS 网址</string>
|
||||
<string name="podcast_channel_editor_dialog_title">播客频道</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">此服务器不支持播客。</string>
|
||||
<string name="podcast_channel_page_title_description_section">描述</string>
|
||||
<string name="podcast_channel_page_title_episode_section">剧集</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">没有可用的剧集</string>
|
||||
@@ -197,6 +296,8 @@
|
||||
<string name="podcast_info_empty_subtitle">添加频道后,您将在此处找到它</string>
|
||||
<string name="podcast_info_empty_title">未找到播客!</string>
|
||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">服务器不支持网络电台管理。</string>
|
||||
<string name="radio_editor_dialog_added">已添加电台</string>
|
||||
<string name="radio_editor_dialog_hint_homepage_url">电台主页 URL</string>
|
||||
<string name="radio_editor_dialog_hint_name">电台名称</string>
|
||||
<string name="radio_editor_dialog_hint_stream_url">广播流 URL</string>
|
||||
@@ -204,6 +305,7 @@
|
||||
<string name="radio_editor_dialog_neutral_button">删除</string>
|
||||
<string name="radio_editor_dialog_positive_button">保存</string>
|
||||
<string name="radio_editor_dialog_title">网络广播电台</string>
|
||||
<string name="radio_editor_dialog_updated">已更新电台</string>
|
||||
<string name="radio_station_info_empty_button">单击以隐藏该部分\n重启应用后生效</string>
|
||||
<string name="radio_station_info_empty_subtitle">添加广播电台后,您可以在此处找到它</string>
|
||||
<string name="radio_station_info_empty_title">没有找到电台!</string>
|
||||
@@ -212,10 +314,14 @@
|
||||
<string name="rating_dialog_title">评分</string>
|
||||
<string name="search_hint">搜索标题、艺术家或专辑</string>
|
||||
<string name="search_info_minimum_characters">输入至少三个字符</string>
|
||||
<string name="search_sort_summary">启用后将按时间排序搜索,关闭则按名称排序。</string>
|
||||
<string name="search_sort_title">按时间排序最近搜索</string>
|
||||
<string name="search_title_album">专辑</string>
|
||||
<string name="search_title_artist">艺术家</string>
|
||||
<string name="search_title_song">歌曲</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">长按删除</string>
|
||||
<string name="server_signup_dialog_action_low_security">低安全性</string>
|
||||
<string name="server_signup_dialog_hint_local_address">本地 URL</string>
|
||||
<string name="server_signup_dialog_hint_name">服务器名称</string>
|
||||
<string name="server_signup_dialog_hint_password">密码</string>
|
||||
<string name="server_signup_dialog_hint_url">服务器地址</string>
|
||||
@@ -231,84 +337,118 @@
|
||||
<string name="server_unreachable_dialog_title">服务器无法访问</string>
|
||||
<string name="settings_about_summary">Tempus 是 Subsonic 的开源轻量级音乐客户端,专为 Android 设计和构建。</string>
|
||||
<string name="settings_about_title">关于</string>
|
||||
<string name="settings_album_detail">显示专辑详情</string>
|
||||
<string name="settings_album_detail_summary">启用后将在专辑页显示流派、歌曲数量等信息</string>
|
||||
<string name="settings_allow_playlist_duplicates">允许添加重复歌曲到播放列表</string>
|
||||
<string name="settings_allow_playlist_duplicates_summary">启用后则添加到播放列表时将不再检查重复内容。</string>
|
||||
<string name="settings_always_on_display">保持屏幕常亮</string>
|
||||
<string name="settings_app_equalizer">均衡器</string>
|
||||
<string name="settings_app_equalizer_summary">打开内置均衡器</string>
|
||||
<string name="settings_artist_sort_by_album_count">按专辑数量排序艺术家</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">启用后按专辑数量排序;关闭则按名称排序。</string>
|
||||
<string name="settings_audio_quality">显示音频质量</string>
|
||||
<string name="settings_audio_quality_summary">显示歌曲的码率和音频格式。</string>
|
||||
<string name="settings_audio_transcode_download_format">转码格式</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">如果启用,Tempus 将不会强制使用下面的转码设置下载曲目。</string>
|
||||
<string name="settings_audio_transcode_download_priority_summary">启用后 Tempus 将不会强制使用下面的转码设置下载歌曲。</string>
|
||||
<string name="settings_audio_transcode_download_priority_title">优先考虑服务器上用于流式传输的设置</string>
|
||||
<string name="settings_audio_transcode_download_summary">如果启用,Tempus 将下载转码后的曲目。</string>
|
||||
<string name="settings_audio_transcode_download_title">下载转码后的曲目</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">如果启用,将发送请求到服务器以查询曲目的估计持续时间。</string>
|
||||
<string name="settings_audio_transcode_download_summary">启用后 Tempus 将下载转码后的歌曲。</string>
|
||||
<string name="settings_audio_transcode_download_title">下载转码后的歌曲</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_summary">启用后将发送请求到服务器以查询歌曲的估计持续时间。</string>
|
||||
<string name="settings_audio_transcode_estimate_content_length_title">估计内容长度</string>
|
||||
<string name="settings_audio_transcode_format_download">用于下载的转码格式</string>
|
||||
<string name="settings_audio_transcode_format_mobile">移动数据下的转码格式</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Wi-Fi 下的转码格式</string>
|
||||
<string name="settings_audio_transcode_priority_summary">如果启用,Tempus 将不会强制使用下面的转码设置流式传输曲目。</string>
|
||||
<string name="settings_audio_transcode_priority_summary">启用后 Tempus 将不会强制使用下面的转码设置流式传输歌曲。</string>
|
||||
<string name="settings_audio_transcode_priority_title">优先考虑服务器转码设置</string>
|
||||
<string name="settings_audio_transcode_priority_toast">曲目转码设置优先级设置为服务器</string>
|
||||
<string name="settings_audio_transcode_priority_toast">歌曲转码设置优先级设置为服务器</string>
|
||||
<string name="settings_auto_download_lyrics">自动下载歌词</string>
|
||||
<string name="settings_auto_download_lyrics_summary">自动保存可用歌词,以便离线时查看。</string>
|
||||
<string name="settings_buffering_strategy">缓存策略</string>
|
||||
<string name="settings_buffering_strategy_summary">为了使更改生效,您必须手动重新启动应用程序。</string>
|
||||
<string name="settings_continuous_play_summary">允许在播放列表结束后,播放相似的曲目。</string>
|
||||
<string name="settings_choose_download_folder">选择一个音乐下载目录</string>
|
||||
<string name="settings_clear_download_folder">清空下载文件夹</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_folder_cleared">下载文件夹已清除。</string>
|
||||
<string name="settings_download_folder_set">已设置下载文件夹</string>
|
||||
<string name="settings_download_storage_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/tempus</string>
|
||||
<string name="settings_github_summary">关注开发进展</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_github_update">更新</string>
|
||||
<string name="settings_github_update_summary">GitHub 版本默认会自动检查 APK 更新。您可以关闭此开关以禁用自动检查。</string>
|
||||
<string name="settings_github_update_title">请访问 Github 以检查更新</string>
|
||||
<string name="settings_image_size">设置图像分辨率</string>
|
||||
<string name="settings_item_rating">显示评分</string>
|
||||
<string name="settings_item_rating_summary">启用后则显示项目的评分和收藏状态。</string>
|
||||
<string name="settings_language">语言</string>
|
||||
<string name="settings_logout_title">注销登录</string>
|
||||
<string name="settings_max_bitrate_download">用于下载的比特率</string>
|
||||
<string name="settings_max_bitrate_mobile">移动数据下的比特率</string>
|
||||
<string name="settings_max_bitrate_wifi">Wi-Fi 下的比特率</string>
|
||||
<string name="settings_max_bitrate_download">用于下载的码率</string>
|
||||
<string name="settings_max_bitrate_mobile">移动数据下的码率</string>
|
||||
<string name="settings_max_bitrate_wifi">Wi-Fi 下的码率</string>
|
||||
<string name="settings_media_cache">媒体文件缓存大小</string>
|
||||
<string name="settings_music_directory">显示音乐目录</string>
|
||||
<string name="settings_music_directory_summary">如果启用,则显示音乐目录部分。 请注意,要使文件夹导航正常工作,服务器必须支持此功能。</string>
|
||||
<string name="settings_music_directory_summary">启用后则显示音乐目录部分。 请注意,要使文件夹导航正常工作,服务器必须支持此功能。</string>
|
||||
<string name="settings_podcast">显示播客</string>
|
||||
<string name="settings_podcast_summary">如果启用,则显示播客部分。</string>
|
||||
<string name="settings_audio_quality">显示音频质量</string>
|
||||
<string name="settings_audio_quality_summary">显示曲目的比特率和音频格式。</string>
|
||||
<string name="settings_item_rating">显示评分</string>
|
||||
<string name="settings_item_rating_summary">如果启用,则显示项目的评分和收藏状态。</string>
|
||||
<string name="settings_podcast_summary">启用后则显示播客部分。</string>
|
||||
<string name="settings_queue_syncing_countdown">同步定时器</string>
|
||||
<string name="settings_queue_syncing_summary">如果启用,将允许当前用户保存其播放队列,并能够在打开应用程序时加载保存状态。</string>
|
||||
<string name="settings_queue_syncing_summary">启用后将允许当前用户保存其播放队列,并能够在打开应用程序时加载保存状态。</string>
|
||||
<string name="settings_queue_syncing_title">同步当前用户的播放队列</string>
|
||||
<string name="settings_radio">显示广播</string>
|
||||
<string name="settings_radio_summary">如果启用,则显示电台部分。</string>
|
||||
<string name="settings_radio">显示电台</string>
|
||||
<string name="settings_radio_summary">启用后,则显示电台部分。</string>
|
||||
<string name="settings_replay_gain">设置播放增益模式</string>
|
||||
<string name="settings_rounded_corner">圆角</string>
|
||||
<string name="settings_rounded_corner_size">圆角大小</string>
|
||||
<string name="settings_rounded_corner_size_summary">设置圆角的大小。</string>
|
||||
<string name="settings_rounded_corner_summary">如果启用,则为所有渲染的封面设置圆角。 更改将在应用重新启动后生效。</string>
|
||||
<string name="settings_rounded_corner_summary">启用后则为所有渲染的封面设置圆角。更改将在应用重新启动后生效。</string>
|
||||
<string name="settings_scan_result">正在扫描:已发现 %1$d 首歌曲</string>
|
||||
<string name="settings_scan_title">扫描曲库</string>
|
||||
<string name="settings_scrobble_title">启用音乐记录</string>
|
||||
<string name="settings_set_download_folder">设置下载文件夹</string>
|
||||
<string name="settings_share_title">启用音乐共享</string>
|
||||
<string name="settings_show_mini_shuffle_button">显示随机按钮</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">启用后,在迷你播放器中显示随机播放按钮,并移除收藏按钮。</string>
|
||||
<string name="settings_song_rating">显示歌曲评分</string>
|
||||
<string name="settings_song_rating_summary">启用后歌曲详情页将显示五星评分。\n\n*需重启应用后生效</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">播放增益(Replay gain)允许您通过调整音轨的音量,以获得始终如一的聆听体验。 仅当曲目标签包含必要的元数据时,此设置才有效。</string>
|
||||
<string name="settings_summary_replay_gain">播放增益(Replay gain)允许您通过调整音轨的音量,以获得始终如一的聆听体验。 仅当歌曲标签包含必要的元数据时,此设置才有效。</string>
|
||||
<string name="settings_summary_scrobble">音乐记录(Scrobbling)允许您的设备将您收听的歌曲的相关信息发送到音乐服务器。 这些信息有助于基于您的音乐偏好生成个性化推荐。</string>
|
||||
<string name="settings_summary_share">允许用户通过链接共享音乐。 该功能需要服务器端支持并启用,并且仅限于单个曲目、专辑和队列。</string>
|
||||
<string name="settings_summary_syncing">返回当前用户的播放队列状态。 这包括播放队列中的曲目、正在播放的曲目以及曲目播放进度。需要服务器支持此功能。</string>
|
||||
<string name="settings_summary_share">允许用户通过链接共享音乐。 该功能需要服务器端支持并启用,并且仅限于单首歌曲、专辑和队列。</string>
|
||||
<string name="settings_summary_skip_min_star_rating">收听电台,即时混合和随机播放时,低于特定评分的歌曲将会被忽略。</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">下载转码后的媒体。 如果启用,将不会下载原始数据,而是使用以下设置。\n如果“用于下载的转码格式”设置为“下载原始”,则文件的比特率不会更改。</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">当文件即时转码时,客户端通常不会显示曲目长度。 可以向支持该功能的服务器发送请求,估计正在播放的曲目的持续时间,但可能响应变慢。</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_summary_syncing">返回当前用户的播放队列状态。 这包括播放队列中的歌曲、正在播放的歌曲以及歌曲播放进度。需要服务器支持此功能。</string>
|
||||
<string name="settings_summary_transcoding">转码模式优先级设置。 如果设置为“播放原始”,文件的码率将不会更改。</string>
|
||||
<string name="settings_summary_transcoding_download">下载转码后的媒体。 启用后将不会下载原始数据,而是使用以下设置。\n如果“用于下载的转码格式”设置为“下载原始”,则文件的码率不会更改。</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">当文件即时转码时,客户端通常不会显示歌曲长度。 可以向支持该功能的服务器发送请求,估计正在播放的歌曲的持续时间,但可能响应变慢。</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_support_summary">加入社区讨论并获取帮助</string>
|
||||
<string name="settings_support_title">用户支持</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_artists_for_offline_use_summary">启用后将自动下载收藏的艺术家以供离线使用。</string>
|
||||
<string name="settings_sync_starred_artists_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_system_equalizer_summary">调整音频设置</string>
|
||||
<string name="settings_system_equalizer_title">系统均衡器</string>
|
||||
<string name="settings_system_language">系统语言</string>
|
||||
<string name="settings_theme">主题</string>
|
||||
<string name="settings_title_data">数据</string>
|
||||
<string name="settings_title_general">通用</string>
|
||||
<string name="settings_title_playlist">播放列表</string>
|
||||
<string name="settings_title_rating">评分</string>
|
||||
<string name="settings_title_replay_gain">播放增益</string>
|
||||
<string name="settings_title_scrobble">音乐记录</string>
|
||||
<string name="settings_title_skip_min_star_rating">根据评分忽略歌曲</string>
|
||||
<string name="settings_title_share">分享</string>
|
||||
<string name="settings_title_skip_min_star_rating">根据评分忽略歌曲</string>
|
||||
<string name="settings_title_skip_min_star_rating_dialog">根据歌曲评分筛选:</string>
|
||||
<string name="settings_title_syncing">同步</string>
|
||||
<string name="settings_title_transcoding">转码</string>
|
||||
<string name="settings_title_transcoding_download">转码下载</string>
|
||||
@@ -321,6 +461,7 @@
|
||||
<string name="share_bottom_sheet_copy_link">复制链接</string>
|
||||
<string name="share_bottom_sheet_delete">删除分享</string>
|
||||
<string name="share_bottom_sheet_update">更新分享</string>
|
||||
<string name="share_no_expiration">永不过期</string>
|
||||
<string name="share_subtitle_item">到期日期:%1$s</string>
|
||||
<string name="share_unsupported_error">不支持分享或未启用</string>
|
||||
<string name="share_update_dialog_hint_description">描述</string>
|
||||
@@ -341,63 +482,69 @@
|
||||
<string name="song_bottom_sheet_remove">移除</string>
|
||||
<string name="song_bottom_sheet_share">分享</string>
|
||||
<string name="song_list_page_downloaded">已下载</string>
|
||||
<string name="song_list_page_most_played">最常播放的曲目</string>
|
||||
<string name="song_list_page_recently_added">最近添加的曲目</string>
|
||||
<string name="song_list_page_recently_played">最近播放的曲目</string>
|
||||
<string name="song_list_page_starred">已收藏的曲目</string>
|
||||
<string name="song_list_page_top">%1$s 的热门曲目</string>
|
||||
<string name="song_list_page_most_played">最常播放的歌曲</string>
|
||||
<string name="song_list_page_recently_added">最近添加的歌曲</string>
|
||||
<string name="song_list_page_recently_played">最近播放的歌曲</string>
|
||||
<string name="song_list_page_starred">已收藏的歌曲</string>
|
||||
<string name="song_list_page_top">%1$s 的热门歌曲</string>
|
||||
<string name="song_list_page_year">年份 %1$d</string>
|
||||
<string name="song_subtitle_formatter">%1$s • %2$s %3$s</string>
|
||||
<plurals name="songs_download_started">
|
||||
<item quantity="one">正在下载 %d 首歌曲</item>
|
||||
<item quantity="other">正在下载 %d 首歌曲</item>
|
||||
</plurals>
|
||||
<string name="starred_album_sync_dialog_summary">下载收藏的专辑可能会消耗大量移动数据流量。</string>
|
||||
<string name="starred_album_sync_dialog_title">同步收藏的专辑</string>
|
||||
<string name="starred_artist_sync_dialog_summary">下载收藏的艺术家可能会消耗大量移动数据流量。</string>
|
||||
<string name="starred_artist_sync_dialog_title">同步收藏的艺术家</string>
|
||||
<string name="starred_sync_dialog_negative_button">取消</string>
|
||||
<string name="starred_sync_dialog_neutral_button">继续</string>
|
||||
<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_sync_dialog_summary">下载收藏的歌曲可能会消耗大量移动数据流量。</string>
|
||||
<string name="starred_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="support_url">https://ko-fi.com/eddyizm</string>
|
||||
<string name="track_info_album">专辑</string>
|
||||
<string name="track_info_artist">艺术家</string>
|
||||
<string name="track_info_bitrate">比特率</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">确定</string>
|
||||
<string name="track_info_dialog_title">曲目信息</string>
|
||||
<string name="track_info_dialog_title">歌曲信息</string>
|
||||
<string name="track_info_disc_number">碟片编号</string>
|
||||
<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>
|
||||
<string name="track_info_summary_full_transcode">本应用将请求服务器对文件进行转码并修改其比特率。 用户请求的编解码器是%1$s,比特率为%2$s。 对所选格式的文件的编码和比特率的任何潜在更改都将由服务器处理,服务器可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_summary_original_file">本应用只会读取服务器提供的原始文件。 本应用将明确向服务器请求具有原始源比特率的未转码文件。</string>
|
||||
<string name="track_info_summary_server_prioritized">要播放的文件质量取决于服务器设置。 本应用不会强制选择任何用于潜在转码的编码和比特率。</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">本应用将请求服务器修改文件的比特率。 用户请求的比特率为%1$s,而源文件的编码将保持不变。 对所选格式的文件比特率的任何更改都将由服务器完成,服务器可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_summary_transcoding_codec">本应用将请求服务器对文件进行转码。 用户请求的编解码器是%1$s,而比特率将与源文件相同。 将文件转码为所选格式的可能性取决于服务器,因为它可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_summary_downloaded_file">该文件已使用 Subsonic API 下载。 文件的编码和码率与源文件一致。</string>
|
||||
<string name="track_info_summary_full_transcode">本应用将请求服务器对文件进行转码并修改其码率。 用户请求的编解码器是%1$s,码率为%2$s。 对所选格式的文件的编码和码率的任何潜在更改都将由服务器处理,服务器可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_summary_original_file">本应用只会读取服务器提供的原始文件。 本应用将明确向服务器请求具有原始源码率的未转码文件。</string>
|
||||
<string name="track_info_summary_server_prioritized">要播放的文件质量取决于服务器设置。 本应用不会强制选择任何用于潜在转码的编码和码率。</string>
|
||||
<string name="track_info_summary_transcoding_bitrate">本应用将请求服务器修改文件的码率。 用户请求的码率为%1$s,而源文件的编码将保持不变。 对所选格式的文件码率的任何更改都将由服务器完成,服务器可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_summary_transcoding_codec">本应用将请求服务器对文件进行转码。 用户请求的编解码器是%1$s,而码率将与源文件相同。 将文件转码为所选格式的可能性取决于服务器,因为它可能支持也可能不支持该操作。</string>
|
||||
<string name="track_info_title">标题</string>
|
||||
<string name="track_info_track_number">曲目编号</string>
|
||||
<string name="track_info_track_number">歌曲编号</string>
|
||||
<string name="track_info_transcoded_content_type">转码内容类型</string>
|
||||
<string name="track_info_transcoded_suffix">转码后缀</string>
|
||||
<string name="track_info_year">年份</string>
|
||||
<string name="streaming_cache_storage_external_dialog_positive_button">外部</string>
|
||||
<string name="streaming_cache_storage_internal_dialog_negative_button">内部</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">特别感谢 unDraw,没有它提供的插图,我们的应用不可能会如此精美。</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="album_page_release_date_label">发布于 %1$s</string>
|
||||
<string name="disc_titlefull">第 %1$s 张光盘 - %2$s</string>
|
||||
<string name="disc_titleless">第 %1$s 张光盘</string>
|
||||
<string name="download_shuffle_all_subtitle">随机播放</string>
|
||||
<string name="github_update_dialog_negative_button">稍后提醒</string>
|
||||
<string name="github_update_dialog_positive_button">现在下载</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_title_last_month">上个月</string>
|
||||
<string name="home_title_last_year">去年</string>
|
||||
<string name="menu_last_week_name">上周</string>
|
||||
<string name="menu_last_month_name">上个月</string>
|
||||
<string name="menu_pin_button">添加到主屏幕</string>
|
||||
<string name="menu_unpin_button">从主屏幕移除</string>
|
||||
<string name="playlist_editor_dialog_action_delete_toast">长按删除</string>
|
||||
<string name="server_signup_dialog_action_delete_toast">长按删除</string>
|
||||
<string name="server_signup_dialog_hint_local_address">本地 URL</string>
|
||||
<string name="widget_content_desc_album_art">专辑封面</string>
|
||||
<string name="widget_content_desc_next">下一首</string>
|
||||
<string name="widget_content_desc_play_pause">播放或暂停</string>
|
||||
<string name="widget_content_desc_prev">上一首</string>
|
||||
<string name="widget_content_desc_repeat">更改循环模式</string>
|
||||
<string name="widget_content_desc_shuffle">切换随机播放</string>
|
||||
<string name="widget_label">Tempus 小组件</string>
|
||||
<string name="widget_not_playing">未播放</string>
|
||||
<string name="widget_placeholder_subtitle">打开 Tempus</string>
|
||||
<string name="widget_time_duration_placeholder">0:00</string>
|
||||
<string name="widget_time_elapsed_placeholder">0:00</string>
|
||||
</resources>
|
||||
|
||||
@@ -240,6 +240,16 @@
|
||||
<item>8</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="playlist_sort_option_titles">
|
||||
<item>Name</item>
|
||||
<item>Random</item>
|
||||
</string-array>
|
||||
<string-array name="playlist_sort_option_values">
|
||||
<item>ORDER_BY_NAME</item>
|
||||
<item>ORDER_BY_RANDOM</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="skip_min_star_rating_titles">
|
||||
<item>0 star minimum</item>
|
||||
<item>1 star minimum</item>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<string name="artist_list_page_downloaded">Downloaded artists</string>
|
||||
<string name="artist_list_page_starred">Starred artists</string>
|
||||
<string name="artist_list_page_title">Artists</string>
|
||||
<string name="artist_no_artist_info_toast">No additional artist info</string>
|
||||
<string name="artist_page_radio_button">Radio</string>
|
||||
<string name="artist_page_shuffle_button">Shuffle</string>
|
||||
<string name="artist_page_switch_layout_button">Switch layout</string>
|
||||
@@ -51,6 +52,8 @@
|
||||
<string name="battery_optimization_negative_button">Ignore</string>
|
||||
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||
<string name="battery_optimization_positive_button">Disable</string>
|
||||
<string name="bottom_sheet_generating_instant_mix">Generating instant mix...</string>
|
||||
<string name="bottom_sheet_problem_generating_instant_mix">Could not retrieve tracks from subsonic server.</string>
|
||||
<string name="connection_alert_dialog_negative_button">Cancel</string>
|
||||
<string name="connection_alert_dialog_neutral_button">Enable data saver</string>
|
||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||
@@ -61,7 +64,7 @@
|
||||
<string name="delete_download_storage_dialog_positive_button">Continue</string>
|
||||
<string name="delete_download_storage_dialog_summary">Please be aware that continuing with this action will result in the permanent deletion of all saved items downloaded from all servers.</string>
|
||||
<string name="delete_download_storage_dialog_title">Delete saved items</string>
|
||||
<string name="description_empty_title">No description available</string>
|
||||
<string name="description_empty_title">No lyrics available</string>
|
||||
<string name="disc_titlefull">Disc %1$s - %2$s</string>
|
||||
<string name="disc_titleless">Disc %1$s</string>
|
||||
<string name="download_directory_dialog_negative_button">Cancel</string>
|
||||
@@ -133,6 +136,10 @@
|
||||
<string name="home_sync_starred_albums_subtitle">Albums marked with a star will be available offline</string>
|
||||
<string name="home_sync_starred_artists_title">Starred Artists Sync</string>
|
||||
<string name="home_sync_starred_artists_subtitle">You have starred artists with music not downloaded</string>
|
||||
<plurals name="home_sync_starred_songs_count">
|
||||
<item quantity="one">%d song needs sync</item>
|
||||
<item quantity="other">%d songs need sync</item>
|
||||
</plurals>
|
||||
<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>
|
||||
@@ -212,6 +219,8 @@
|
||||
<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_queue_save_to_playlist">Save Queue to Playlist</string>
|
||||
<string name="player_queue_load_queue">Load Queue</string>
|
||||
<string name="player_lyrics_download_content_description">Download lyrics for offline playback</string>
|
||||
<string name="player_lyrics_downloaded_content_description">Lyrics downloaded for offline playback</string>
|
||||
<string name="player_lyrics_download_success">Lyrics saved for offline playback.</string>
|
||||
@@ -250,6 +259,7 @@
|
||||
<string name="podcast_channel_catalogue_title_expanded">Browse Channels</string>
|
||||
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS Url</string>
|
||||
<string name="podcast_channel_editor_dialog_title">Podcast Channel</string>
|
||||
<string name="podcast_channel_not_supported_snackbar">Podcasts are not supported by this server.</string>
|
||||
<string name="podcast_channel_page_title_description_section">Description</string>
|
||||
<string name="podcast_channel_page_title_episode_section">Episodes</string>
|
||||
<string name="podcast_channel_page_title_no_episode_available">No episodes available</string>
|
||||
@@ -264,10 +274,13 @@
|
||||
<string name="radio_editor_dialog_negative_button">Cancel</string>
|
||||
<string name="radio_editor_dialog_neutral_button">Delete</string>
|
||||
<string name="radio_editor_dialog_positive_button">Save</string>
|
||||
<string name="radio_editor_dialog_added">Radio station added</string>
|
||||
<string name="radio_editor_dialog_updated">Radio station updated</string>
|
||||
<string name="radio_editor_dialog_title">Internet Radio Station</string>
|
||||
<string name="radio_station_info_empty_button">Click to hide the section\nThe effects will be visible on restart</string>
|
||||
<string name="radio_station_info_empty_subtitle">Once you add a radio station, you\'ll find it here</string>
|
||||
<string name="radio_station_info_empty_title">No stations found!</string>
|
||||
<string name="radio_dialog_not_supported_snackbar">Internet radio management are not supported by this server.</string>
|
||||
<string name="rating_dialog_negative_button">Cancel</string>
|
||||
<string name="rating_dialog_positive_button">Save</string>
|
||||
<string name="rating_dialog_title">Rate</string>
|
||||
@@ -331,6 +344,9 @@
|
||||
<string name="settings_github_summary">Follow the development</string>
|
||||
<string name="settings_github_title">Github</string>
|
||||
<string name="settings_support_discussion_link">https://github.com/eddyizm/tempus/discussions</string>
|
||||
<string name="settings_github_update">Updates</string>
|
||||
<string name="settings_github_update_title">Check github for release updates</string>
|
||||
<string name="settings_github_update_summary">If using the github version, by default app will check for new apk release. Toggle to disable automatic github checks</string>
|
||||
<string name="settings_support_summary">Join community discussions and support</string>
|
||||
<string name="settings_support_title">User support</string>
|
||||
<string name="settings_scan_result">Scanning: counting %1$d tracks</string>
|
||||
@@ -345,6 +361,7 @@
|
||||
<string name="settings_music_directory_summary">If enabled, show the music directory section. Please note that for folder navigation to work properly, the server must support this feature.</string>
|
||||
<string name="settings_podcast">Show podcast</string>
|
||||
<string name="settings_podcast_summary">If enabled, show the podcast section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_playlist_sort">Playlist sorting</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>
|
||||
@@ -379,7 +396,7 @@
|
||||
<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_download">Download transcoded media. If enabled, the download endpoint will not be used, but the following settings. \n\n If \"Transcode format for downloads\" 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_artists_for_offline_use_summary">If enabled, starred artists will be downloaded for offline use.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sync starred artists for offline use</string>
|
||||
@@ -530,4 +547,11 @@
|
||||
<string name="settings_album_detail_summary">If enabled, show the album details like genre, song count etc. on the album page</string>
|
||||
<string name="settings_artist_sort_by_album_count">Sort artists by album count</string>
|
||||
<string name="settings_artist_sort_by_album_count_summary">If enabled, sort the artists by album count. Sort by name if disabled.</string>
|
||||
|
||||
<string name="folder_play_collecting">Collecting songs from folder…</string>
|
||||
<string name="folder_play_playing">Playing %d songs</string>
|
||||
<string name="folder_play_no_songs">No songs found in folder</string>
|
||||
|
||||
<string name="search_sort_title">Sort recent searches chronologically</string>
|
||||
<string name="search_sort_summary">If enabled, sort searches chronologically. Sort by name if disabled.</string>
|
||||
</resources>
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
<item name="android:statusBarColor">?attr/colorSurface</item>
|
||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
||||
<item name="android:scrollbars">none</item>
|
||||
|
||||
<item name="floatingActionButtonStyle">@style/FloatingActionButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
@@ -47,6 +49,21 @@
|
||||
<item name="android:background">@color/dividerColor</item>
|
||||
</style>
|
||||
|
||||
<style name="FloatingActionButtonStyle" parent="Widget.MaterialComponents.FloatingActionButton">
|
||||
<item name="backgroundTint">?attr/colorSecondary</item>
|
||||
<item name="tint">?attr/colorOnPrimary</item>
|
||||
<item name="shapeAppearanceOverlay">@style/FabShapeStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="FabShapeStyle" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerSize">50%</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
<item name="cornerFamilyTopLeft">rounded</item>
|
||||
<item name="cornerFamilyTopRight">rounded</item>
|
||||
<item name="cornerFamilyBottomLeft">rounded</item>
|
||||
<item name="cornerFamilyBottomRight">rounded</item>
|
||||
</style>
|
||||
|
||||
<style name="NoConnectionTextView">
|
||||
<item name="background">?attr/colorErrorContainer</item>
|
||||
<item name="android:textColor">?attr/colorOnErrorContainer</item>
|
||||
|
||||
@@ -122,6 +122,12 @@
|
||||
android:summary="@string/settings_artist_sort_by_album_count_summary"
|
||||
android:key="artist_sort_by_album_count" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/search_sort_title"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/search_sort_summary"
|
||||
android:key="sort_search_chronologically" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_playlist">
|
||||
@@ -130,6 +136,14 @@
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_allow_playlist_duplicates_summary"
|
||||
android:key="allow_playlist_duplicates" />
|
||||
<ListPreference
|
||||
app:defaultValue="ORDER_BY_NAME"
|
||||
app:dialogTitle="@string/settings_playlist_sort"
|
||||
app:entries="@array/playlist_sort_option_titles"
|
||||
app:entryValues="@array/playlist_sort_option_values"
|
||||
app:key="home_sort_playlists"
|
||||
app:title="@string/settings_playlist_sort"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
@@ -400,6 +414,18 @@
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="settings_github_update_category_key"
|
||||
app:title="@string/settings_github_update">
|
||||
<Preference
|
||||
app:selectable="false"
|
||||
app:summary="@string/settings_github_update_summary" />
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_github_update_title"
|
||||
android:defaultValue="true"
|
||||
android:key="github_update_check" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_about_title">
|
||||
<Preference
|
||||
app:selectable="false"
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
<locale android:name="es-ES"/> <!-- Spanish (Spain) -->
|
||||
<locale android:name="pl-PL"/> <!-- Polish -->
|
||||
<locale android:name="tr-TR"/> <!-- Turkish -->
|
||||
<locale android:name="ca"/> <!-- Catalan -->
|
||||
<locale android:name="ro"/> <!-- Romanian -->
|
||||
</locale-config>
|
||||
|
||||
@@ -1,191 +1,18 @@
|
||||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory
|
||||
import com.cappielloantonio.tempo.util.MappingUtil
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.cappielloantonio.tempo.widget.WidgetUpdateManager
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
|
||||
@UnstableApi
|
||||
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
private lateinit var automotiveRepository: AutomotiveRepository
|
||||
private lateinit var player: ExoPlayer
|
||||
class MediaService : BaseMediaService(), SessionAvailabilityListener {
|
||||
private val automotiveRepository = AutomotiveRepository()
|
||||
private lateinit var castPlayer: CastPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
|
||||
private lateinit var networkCallback: CustomNetworkCallback
|
||||
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"
|
||||
const val ACTION_EQUALIZER_UPDATED = "com.cappielloantonio.tempo.service.EQUALIZER_UPDATED"
|
||||
}
|
||||
private val widgetUpdateHandler = Handler(Looper.getMainLooper())
|
||||
private var widgetUpdateScheduled = false
|
||||
private val widgetUpdateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (!player.isPlaying) {
|
||||
widgetUpdateScheduled = false
|
||||
return
|
||||
}
|
||||
updateWidget()
|
||||
widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMediaItems() {
|
||||
Log.d("MediaService", "update items");
|
||||
val n = player.mediaItemCount
|
||||
val k = player.currentMediaItemIndex
|
||||
val current = player.currentPosition
|
||||
val items = (0 .. n-1).map{i -> MappingUtil.mapMediaItem(player.getMediaItemAt(i))}
|
||||
player.clearMediaItems()
|
||||
player.setMediaItems(items, k, current)
|
||||
}
|
||||
|
||||
inner class CustomNetworkCallback : ConnectivityManager.NetworkCallback() {
|
||||
var wasWifi = false
|
||||
|
||||
init {
|
||||
val manager = getSystemService(ConnectivityManager::class.java)
|
||||
val network = manager.activeNetwork
|
||||
val capabilities = manager.getNetworkCapabilities(network)
|
||||
if (capabilities != null)
|
||||
wasWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
}
|
||||
|
||||
override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
|
||||
val isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
if (isWifi != wasWifi) {
|
||||
wasWifi = isWifi
|
||||
widgetUpdateHandler.post(Runnable {
|
||||
updateMediaItems()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
initializeRepository()
|
||||
initializePlayer()
|
||||
initializeMediaLibrarySession()
|
||||
restorePlayerFromQueue()
|
||||
initializePlayerListener()
|
||||
initializeCastPlayer()
|
||||
initializeEqualizerManager()
|
||||
initializeNetworkListener()
|
||||
|
||||
setPlayer(
|
||||
null,
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||
)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
val player = mediaLibrarySession.player
|
||||
|
||||
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releaseNetworkCallback()
|
||||
equalizerManager.release()
|
||||
stopWidgetUpdates()
|
||||
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(DynamicMediaSourceFactory(this))
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.setLoadControl(initializeLoadControl())
|
||||
.build()
|
||||
|
||||
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
|
||||
player.repeatMode = Preferences.getRepeatMode()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun initializeCastPlayer() {
|
||||
@@ -193,264 +20,41 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
) {
|
||||
CastContext.getSharedInstance(this, ContextCompat.getMainExecutor(this))
|
||||
.addOnSuccessListener { castContext ->
|
||||
castPlayer = CastPlayer(castContext)
|
||||
castPlayer.setSessionAvailabilityListener(this@MediaService)
|
||||
|
||||
if (castPlayer.isCastSessionAvailable && this::mediaLibrarySession.isInitialized) {
|
||||
setPlayer(player, castPlayer)
|
||||
}
|
||||
}
|
||||
.addOnSuccessListener { castContext ->
|
||||
castPlayer = CastPlayer(castContext)
|
||||
castPlayer.setSessionAvailabilityListener(this@MediaService)
|
||||
initializePlayerListener(castPlayer)
|
||||
if (castPlayer.isCastSessionAvailable)
|
||||
setPlayer(mediaLibrarySession.player, castPlayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
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()
|
||||
}
|
||||
|
||||
private fun initializeNetworkListener() {
|
||||
networkCallback = CustomNetworkCallback()
|
||||
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(networkCallback)
|
||||
updateMediaItems()
|
||||
}
|
||||
|
||||
private fun restorePlayerFromQueue() {
|
||||
if (player.mediaItemCount > 0) return
|
||||
|
||||
val queueRepository = QueueRepository()
|
||||
val storedQueue = queueRepository.media
|
||||
if (storedQueue.isNullOrEmpty()) return
|
||||
|
||||
val mediaItems = MappingUtil.mapMediaItems(storedQueue)
|
||||
if (mediaItems.isEmpty()) return
|
||||
|
||||
val lastIndex = try {
|
||||
queueRepository.lastPlayedMediaIndex
|
||||
} catch (_: Exception) {
|
||||
0
|
||||
}.coerceIn(0, mediaItems.size - 1)
|
||||
|
||||
val lastPosition = try {
|
||||
queueRepository.lastPlayedMediaTimestamp
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}.let { if (it < 0L) 0L else it }
|
||||
|
||||
player.setMediaItems(mediaItems, lastIndex, lastPosition)
|
||||
player.prepare()
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
private fun createLibrarySessionCallback(): MediaLibrarySessionCallback {
|
||||
override fun getMediaLibrarySessionCallback(): MediaLibrarySession.Callback {
|
||||
return MediaLibrarySessionCallback(this, automotiveRepository)
|
||||
}
|
||||
|
||||
private fun initializePlayerListener() {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
if (mediaItem == null) return
|
||||
override fun playerInitHook() {
|
||||
super.playerInitHook()
|
||||
initializeCastPlayer()
|
||||
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable)
|
||||
setPlayer(null, castPlayer)
|
||||
}
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
} else {
|
||||
MediaManager.scrobble(player.currentMediaItem, false)
|
||||
}
|
||||
if (isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
} else {
|
||||
stopWidgetUpdates()
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||
) {
|
||||
MediaManager.scrobble(player.currentMediaItem, true)
|
||||
MediaManager.saveChronology(player.currentMediaItem)
|
||||
}
|
||||
updateWidget()
|
||||
}
|
||||
|
||||
override fun onPositionDiscontinuity(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.scrobble(oldPosition.mediaItem, true)
|
||||
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||
}
|
||||
|
||||
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
}
|
||||
})
|
||||
if (player.isPlaying) {
|
||||
scheduleWidgetUpdates()
|
||||
override fun releasePlayers() {
|
||||
if (this::castPlayer.isInitialized) {
|
||||
castPlayer.setSessionAvailabilityListener(null)
|
||||
castPlayer.release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWidget() {
|
||||
val mi = player.currentMediaItem
|
||||
val title = mi?.mediaMetadata?.title?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("title")
|
||||
val artist = mi?.mediaMetadata?.artist?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("artist")
|
||||
val album = mi?.mediaMetadata?.albumTitle?.toString()
|
||||
?: mi?.mediaMetadata?.extras?.getString("album")
|
||||
val extras = mi?.mediaMetadata?.extras
|
||||
val coverId = extras?.getString("coverArtId")
|
||||
val songLink = extras?.getString("assetLinkSong")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_SONG, extras?.getString("id"))
|
||||
val albumLink = extras?.getString("assetLinkAlbum")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ALBUM, extras?.getString("albumId"))
|
||||
val artistLink = extras?.getString("assetLinkArtist")
|
||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
||||
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||
WidgetUpdateManager.updateFromState(
|
||||
this,
|
||||
title ?: "",
|
||||
artist ?: "",
|
||||
album ?: "",
|
||||
coverId,
|
||||
player.isPlaying,
|
||||
player.shuffleModeEnabled,
|
||||
player.repeatMode,
|
||||
position,
|
||||
duration,
|
||||
songLink,
|
||||
albumLink,
|
||||
artistLink
|
||||
)
|
||||
}
|
||||
|
||||
private fun scheduleWidgetUpdates() {
|
||||
if (widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.postDelayed(widgetUpdateRunnable, WIDGET_UPDATE_INTERVAL_MS)
|
||||
widgetUpdateScheduled = true
|
||||
}
|
||||
|
||||
private fun stopWidgetUpdates() {
|
||||
if (!widgetUpdateScheduled) return
|
||||
widgetUpdateHandler.removeCallbacks(widgetUpdateRunnable)
|
||||
widgetUpdateScheduled = false
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
|
||||
if (this::castPlayer.isInitialized) castPlayer.release()
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
automotiveRepository.deleteMetadata()
|
||||
super.releasePlayers()
|
||||
}
|
||||
|
||||
private fun releaseNetworkCallback() {
|
||||
getSystemService(ConnectivityManager::class.java).unregisterNetworkCallback(networkCallback)
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
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()
|
||||
setPlayer(exoplayer, castPlayer)
|
||||
}
|
||||
|
||||
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()
|
||||
setPlayer(castPlayer, exoplayer)
|
||||
}
|
||||
}
|
||||
|
||||
private const val WIDGET_UPDATE_INTERVAL_MS = 1000L
|
||||
}
|
||||
4
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/10.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
* fix: updates starred syncing downloads to user defined directory
|
||||
* fix: handle empty albums and null mappings
|
||||
* feat: integrate sort recent searches chronologically
|
||||
* feat: add heart to artist/album pages, fixed artist cover art failing
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user