mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-08 21:08:43 +02:00
Compare commits
383
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8b5b63d5 | ||
|
|
4485d0833e | ||
|
|
a163a6af88 | ||
|
|
38bb5af04b | ||
|
|
11804d1cb8 | ||
|
|
22529fc1d2 | ||
|
|
62b0c5fd62 | ||
|
|
1ed28a4fd4 | ||
|
|
5044aea1d7 | ||
|
|
47fda0ccca | ||
|
|
742490775a | ||
|
|
95bbed2b21 | ||
|
|
2b446e227d | ||
|
|
ea6417dbb1 | ||
|
|
d902417959 | ||
|
|
358197db03 | ||
|
|
102fd1f1a1 | ||
|
|
fe9168c6cf | ||
|
|
f7663fc17f | ||
|
|
e370ec36ab | ||
|
|
8f181c74d4 | ||
|
|
e2fc5bcbb1 | ||
|
|
67af09c4bd | ||
|
|
b81abf4da5 | ||
|
|
2c288bb34d | ||
|
|
df762e8910 | ||
|
|
253aa19701 | ||
|
|
ea608d875b | ||
|
|
d4340a8b95 | ||
|
|
33b3aafb14 | ||
|
|
6cedc2fe35 | ||
|
|
1bb03015d9 | ||
|
|
c5bef72c63 | ||
|
|
8b7c00b337 | ||
|
|
6199d4fab4 | ||
|
|
2063d8647a | ||
|
|
c0a9f85f77 | ||
|
|
b1651c5b01 | ||
|
|
1a8af2caee | ||
|
|
a593070b9f | ||
|
|
1d700b0663 | ||
|
|
2233885142 | ||
|
|
faf87d2942 | ||
|
|
706673aed0 | ||
|
|
eaea688e33 | ||
|
|
256be3521d | ||
|
|
cec1e8593d | ||
|
|
e18f9be810 | ||
|
|
69dca2db43 | ||
|
|
c03321b55e | ||
|
|
f8b4287229 | ||
|
|
ce2fa0c116 | ||
|
|
ebe9a230fd | ||
|
|
a05ceea4bc | ||
|
|
67c2ded1f0 | ||
|
|
66d09c886e | ||
|
|
bbff7513f1 | ||
|
|
4195279c5d | ||
|
|
464d967a0a | ||
|
|
4dd7bf8916 | ||
|
|
4b134eaef9 | ||
|
|
ece62c8b18 | ||
|
|
b430c185b1 | ||
|
|
5c13ccf9e7 | ||
|
|
cb0e2ba305 | ||
|
|
011fbb38cb | ||
|
|
0a69450bc0 | ||
|
|
b4a97f4347 | ||
|
|
96f7aa95ee | ||
|
|
e92f2177e8 | ||
|
|
ab7bda402e | ||
|
|
d78c168e16 | ||
|
|
2b04b4f008 | ||
|
|
a6cc0d742c | ||
|
|
e7e9a2755c | ||
|
|
56ea4ce2f5 | ||
|
|
05528f6dfd | ||
|
|
49cd36c33e | ||
|
|
898d96ce3a | ||
|
|
b257836b76 | ||
|
|
8fab4e56ef | ||
|
|
8e34c230d7 | ||
|
|
b32f2e2973 | ||
|
|
1047d1a598 | ||
|
|
1e5115b743 | ||
|
|
e1e12de91b | ||
|
|
58950afb99 | ||
|
|
89b64b165f | ||
|
|
90ab927bdc | ||
|
|
50ebf4eacb | ||
|
|
064ba080ab | ||
|
|
4ee0345217 | ||
|
|
d45cbbba98 | ||
|
|
612a7a7063 | ||
|
|
62e3ca4068 | ||
|
|
34dcdc89c3 | ||
|
|
bcc202ff3b | ||
|
|
d4a56b3823 | ||
|
|
626239038b | ||
|
|
f7da431e61 | ||
|
|
6ff59c423b | ||
|
|
076d4977e3 | ||
|
|
2a737a9d72 | ||
|
|
8c77a19eb6 | ||
|
|
3918696477 | ||
|
|
8a6a2b8577 | ||
|
|
584023380c | ||
|
|
1f4dd2bdb7 | ||
|
|
f7d5a0732b | ||
|
|
2a6cc882a5 | ||
|
|
f385339da3 | ||
|
|
c37566a999 | ||
|
|
f90a4b1374 | ||
|
|
b649680d3e | ||
|
|
b84c2abe14 | ||
|
|
58aff762c0 | ||
|
|
e5eef69c91 | ||
|
|
54f91b7a88 | ||
|
|
17676173ef | ||
|
|
31f4aa944a | ||
|
|
b30bd495b7 | ||
|
|
06f893436e | ||
|
|
20a2b3ed90 | ||
|
|
b1e3bc46c8 | ||
|
|
8bc5932889 | ||
|
|
3cd597d71e | ||
|
|
48d5ad462e | ||
|
|
40cab87640 | ||
|
|
f405947ad8 | ||
|
|
fd550c15a5 | ||
|
|
d18537cf04 | ||
|
|
f01f0229a8 | ||
|
|
b369eb2d40 | ||
|
|
f08c65c5bf | ||
|
|
1043f4ecd7 | ||
|
|
4f17a32373 | ||
|
|
2203aae6e6 | ||
|
|
7b60c7b6c5 | ||
|
|
be98e06efc | ||
|
|
45239bca34 | ||
|
|
84509ec1a8 | ||
|
|
2493ce7ed7 | ||
|
|
2b1e53b371 | ||
|
|
d643a1769f | ||
|
|
a63b7a188c | ||
|
|
f9c0162b8a | ||
|
|
cda3cb3512 | ||
|
|
766f6938a7 | ||
|
|
8e1ccb517b | ||
|
|
540aab6f83 | ||
|
|
d1969d7897 | ||
|
|
b69e8fd649 | ||
|
|
2e95cf908d | ||
|
|
b589bae2b0 | ||
|
|
e7e4891d82 | ||
|
|
79df676a13 | ||
|
|
03a46decba | ||
|
|
d80482f443 | ||
|
|
735cce7614 | ||
|
|
8ea35a0b36 | ||
|
|
ad90bd3d05 | ||
|
|
fa3856e845 | ||
|
|
752d4ddc8c | ||
|
|
fd2d7a05d1 | ||
|
|
ccf4675867 | ||
|
|
bc043c73ca | ||
|
|
291ab247bc | ||
|
|
d901f470a1 | ||
|
|
0ebb01e39d | ||
|
|
20ac99bf8d | ||
|
|
90e86e1575 | ||
|
|
28652031e7 | ||
|
|
cd8be40cc8 | ||
|
|
52fc937f96 | ||
|
|
68e4722387 | ||
|
|
5f5b38b2f9 | ||
|
|
65f8d3ce81 | ||
|
|
ac571fd63d | ||
|
|
ea2d6e9f31 | ||
|
|
80b999d648 | ||
|
|
54720039e4 | ||
|
|
8c6717c201 | ||
|
|
6610754e25 | ||
|
|
d38622d8a2 | ||
|
|
cd2750a489 | ||
|
|
30140629ae | ||
|
|
0ce29337ef | ||
|
|
6715f29ebb | ||
|
|
b4a8dc9e26 | ||
|
|
c349a0bf00 | ||
|
|
0dc6c142be | ||
|
|
e46c1d9120 | ||
|
|
27ad554ea1 | ||
|
|
5660772b46 | ||
|
|
dab80642ab | ||
|
|
da46191208 | ||
|
|
6484c4de79 | ||
|
|
6716b6042a | ||
|
|
0686859fa7 | ||
|
|
4180862c3a | ||
|
|
7aa20d3211 | ||
|
|
b694b55710 | ||
|
|
024b613063 | ||
|
|
8768d11a20 | ||
|
|
5898303ba4 | ||
|
|
e79d09941c | ||
|
|
c52a60fe77 | ||
|
|
e64f9aa427 | ||
|
|
ee343f78ef | ||
|
|
1f20d5821f | ||
|
|
b0c512e946 | ||
|
|
0d13b7abd1 | ||
|
|
43fd8285c9 | ||
|
|
2f7e8d618a | ||
|
|
a0b910deeb | ||
|
|
e9dc45613c | ||
|
|
b5e90ec3d2 | ||
|
|
3d9f42c1e1 | ||
|
|
e62f430f37 | ||
|
|
9c53564c46 | ||
|
|
f7add6f6cd | ||
|
|
7786e6a827 | ||
|
|
b213e2aabf | ||
|
|
ebca0fafca | ||
|
|
ac8a2ca985 | ||
|
|
6380d7cc02 | ||
|
|
9056ba6b8d | ||
|
|
23801363c4 | ||
|
|
1cdcd7327f | ||
|
|
50ec50003e | ||
|
|
5ff4db1a3f | ||
|
|
394b44c343 | ||
|
|
eb2aa6f773 | ||
|
|
81130e04e5 | ||
|
|
c0ec0758f4 | ||
|
|
e21f7c545e | ||
|
|
c16dd2fcf6 | ||
|
|
90974e6d73 | ||
|
|
8e4f75d85b | ||
|
|
792ee2adb8 | ||
|
|
2c57489c07 | ||
|
|
e3321770fd | ||
|
|
848673f028 | ||
|
|
e6d3003ecf | ||
|
|
90551b7ce7 | ||
|
|
aabac5da13 | ||
|
|
4fad77f9c1 | ||
|
|
0fb6d746eb | ||
|
|
7fc712d19b | ||
|
|
d6018cfe10 | ||
|
|
5b8f231a98 | ||
|
|
cb503f84e1 | ||
|
|
a34f9b80ac | ||
|
|
3e6696b516 | ||
|
|
9cd8bd439a | ||
|
|
ca5b99e23d | ||
|
|
c44fd74f5b | ||
|
|
f82c4834c3 | ||
|
|
dda95bd69c | ||
|
|
a623cb57a7 | ||
|
|
e798d930fb | ||
|
|
f91b0c9e2f | ||
|
|
b6824cf242 | ||
|
|
41477fdac1 | ||
|
|
d4e8afc2f1 | ||
|
|
d42cbfe3e4 | ||
|
|
c7c3ef52e6 | ||
|
|
92344325b6 | ||
|
|
a8172fb3f9 | ||
|
|
a73ec783d5 | ||
|
|
5c9fc4c9e4 | ||
|
|
494ff38ef3 | ||
|
|
b7e6cbf1ab | ||
|
|
b33623b74f | ||
|
|
4f939ce6fc | ||
|
|
4f45f360df | ||
|
|
4ad54f34fe | ||
|
|
574dda5286 | ||
|
|
d788623509 | ||
|
|
f047f6a23b | ||
|
|
48b30f6b03 | ||
|
|
67d32164d9 | ||
|
|
6672a21049 | ||
|
|
b833019a14 | ||
|
|
00eeeb2a5a | ||
|
|
06a0a68143 | ||
|
|
1a7ae468bd | ||
|
|
e8f74d7358 | ||
|
|
984f01da23 | ||
|
|
d6e75410a5 | ||
|
|
7627e1b58c | ||
|
|
5fb31b18ae | ||
|
|
20903e8217 | ||
|
|
8fe348d4eb | ||
|
|
24cda8a179 | ||
|
|
fc954a968b | ||
|
|
aae53a54cd | ||
|
|
cfedcffc98 | ||
|
|
0e74c74a6c | ||
|
|
fb86c45e78 | ||
|
|
124300b215 | ||
|
|
6746c0627b | ||
|
|
0f2cd3b132 | ||
|
|
e94d6fdc1e | ||
|
|
e301b3071c | ||
|
|
3d3dad5e4a | ||
|
|
f8a9033738 | ||
|
|
edb25f5bcd | ||
|
|
bcb239efd1 | ||
|
|
d208211c6f | ||
|
|
0b20b72241 | ||
|
|
5c29459af2 | ||
|
|
e54fd38784 | ||
|
|
94774f13df | ||
|
|
43ec35fe67 | ||
|
|
b2645dc444 | ||
|
|
c79624a82c | ||
|
|
8201f3c9a6 | ||
|
|
e0ac6a1c38 | ||
|
|
400da5566d | ||
|
|
9b67037774 | ||
|
|
48596542c7 | ||
|
|
9715d65d88 | ||
|
|
2e14b31a69 | ||
|
|
1c8ce6c3db | ||
|
|
dc74a6213d | ||
|
|
e9a66bb7a7 | ||
|
|
3c6d4b5f19 | ||
|
|
f04eebe0d8 | ||
|
|
63b5227ccc | ||
|
|
934d9b1df4 | ||
|
|
8994cb3a6e | ||
|
|
4a32de482b | ||
|
|
95f47793d2 | ||
|
|
f27048618d | ||
|
|
c90ad419e8 | ||
|
|
2cdc3ecb9d | ||
|
|
9f89fcd968 | ||
|
|
7c2d51f811 | ||
|
|
41fe7a29cd | ||
|
|
54f3eec303 | ||
|
|
cd91013f69 | ||
|
|
0af16ef05f | ||
|
|
5ba830d56e | ||
|
|
81919e963a | ||
|
|
8a1d4bb958 | ||
|
|
eabac0e709 | ||
|
|
d90783d7c8 | ||
|
|
faf8211e5c | ||
|
|
65d90947c5 | ||
|
|
752e918d26 | ||
|
|
ad08cc69f4 | ||
|
|
da3882fd5e | ||
|
|
c04c7a55d9 | ||
|
|
3318846b43 | ||
|
|
e0860feeb9 | ||
|
|
c0253a69b9 | ||
|
|
9242c7e6e0 | ||
|
|
0bfc579ae4 | ||
|
|
5fad06756b | ||
|
|
5be6afa6c1 | ||
|
|
5ba3b8be7a | ||
|
|
0eb62f541d | ||
|
|
6baff881c7 | ||
|
|
8c23c1e795 | ||
|
|
9706c66a27 | ||
|
|
f4f7252537 | ||
|
|
430d200151 | ||
|
|
7be82d03f3 | ||
|
|
15aaef742d | ||
|
|
e443ba09da | ||
|
|
8235d1f1f7 | ||
|
|
9a39dcceee | ||
|
|
a707b7fc86 | ||
|
|
b6fea85ef4 | ||
|
|
7e3b3f55ae | ||
|
|
d2d3bbe8a1 | ||
|
|
41a65c0f9d | ||
|
|
165ddd58a6 | ||
|
|
09dc04c0de | ||
|
|
57d5dbc0d0 | ||
|
|
11af75e127 | ||
|
|
f8d82a3020 |
+1
-6
@@ -1,13 +1,8 @@
|
|||||||
# editorconfig.org
|
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
labels: bug
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Steps to reproduce
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
### Expected behaviour
|
|
||||||
- Tell us what should happen
|
|
||||||
|
|
||||||
### Actual behaviour
|
|
||||||
- Tell us what happens
|
|
||||||
|
|
||||||
### Environment data
|
|
||||||
|
|
||||||
Audiobookshelf Version:
|
|
||||||
|
|
||||||
- [ ] Android App?
|
|
||||||
- [ ] iOS App?
|
|
||||||
|
|
||||||
#### Android Issue
|
|
||||||
Android version:
|
|
||||||
|
|
||||||
Device model:
|
|
||||||
|
|
||||||
Stock or customized system:
|
|
||||||
|
|
||||||
#### iOS Issue
|
|
||||||
iOS Version:
|
|
||||||
|
|
||||||
iPhone model:
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
name: 🐞 ABS App Bug Report
|
||||||
|
description: File a bug/issue and help us improve the Audiobookshelf mobile apps.
|
||||||
|
title: '[Bug]: '
|
||||||
|
labels: ['bug', 'triage']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: '## App Bug Description'
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: 'Thank you for filing a bug report! 🐛'
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: 'Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug.'
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What was the Problem?
|
||||||
|
description: Please help us understand the problem.
|
||||||
|
placeholder: Describe the problem you saw or experienced in detail! Include a screenshot or video, if applicable.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce the Issue
|
||||||
|
description: Please help us understand how we can reliably reproduce the issue.
|
||||||
|
placeholder: '1. Go to the library page of a Podcast library and...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-was-expected
|
||||||
|
attributes:
|
||||||
|
label: What was Expected?
|
||||||
|
description: Please help us understand what should have happened.
|
||||||
|
placeholder: Please help us visualize by including a doodle or screenshot.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: '## Mobile Environment'
|
||||||
|
- type: input
|
||||||
|
id: phone-model
|
||||||
|
attributes:
|
||||||
|
label: Phone Model
|
||||||
|
description: What kind of phone are you using?
|
||||||
|
placeholder: e.g. Pixel 6, iPhone 14, Samusung Galaxy s23, etc
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: phone-os
|
||||||
|
attributes:
|
||||||
|
label: Phone OS
|
||||||
|
description: What operating system? If Android, is it stock or a custom system?
|
||||||
|
placeholder: e.g. Android 14, iOS 17.4, etc
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: app-version
|
||||||
|
attributes:
|
||||||
|
label: Audiobookshelf App Version
|
||||||
|
description: Please ensure your app is up to date. *If you are using a 3rd-party app, please reach out to them directly.*
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Android App - 0.9.76
|
||||||
|
- iOS App - 0.9.76
|
||||||
|
- Android App - 0.9.75
|
||||||
|
- iOS App - 0.9.75
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: install-source
|
||||||
|
attributes:
|
||||||
|
label: Installation Source
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Google Play Store
|
||||||
|
- Testflight
|
||||||
|
- SideStore
|
||||||
|
- Other (List in "Additional Notes")
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional-notes
|
||||||
|
attributes:
|
||||||
|
label: Additional Notes
|
||||||
|
description: Anything else you want to add?
|
||||||
|
placeholder: 'e.g. I have tried X, Y, and Z.'
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
labels: enhancement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Is your feature request related to a problem? Please describe.
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
### Describe the solution you'd like
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
### Describe alternatives you've considered
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
### Additional context
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: 🚀 App Feature Request
|
||||||
|
description: Request a feature/enhancement
|
||||||
|
title: '[Enhancement]: '
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: '## App Feature Request Description'
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: 'Please first search in both issues & discussions for your enhancement and make sure your app is up to date.'
|
||||||
|
- type: textarea
|
||||||
|
id: describe
|
||||||
|
attributes:
|
||||||
|
label: Describe the Feature/Enhancement
|
||||||
|
description: Please help us understand what you want.
|
||||||
|
placeholder: What is your vision?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: the-why
|
||||||
|
attributes:
|
||||||
|
label: Why would this be helpful?
|
||||||
|
description: Please help us understand why this would enhance your experience.
|
||||||
|
placeholder: Explain the "why" or "use case".
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: image
|
||||||
|
attributes:
|
||||||
|
label: Future Implementation (Screenshot)
|
||||||
|
description: Please help us visualize by including a doodle or screenshot.
|
||||||
|
placeholder: How could this look or where could it go?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: '## App Current Implementation'
|
||||||
|
- type: dropdown
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Audiobookshelf App Version
|
||||||
|
description: Please ensure your app is up to date. *If you are using a 3rd-party app, please reach out to them directly.*
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Android App - 0.9.76
|
||||||
|
- iOS App - 0.9.76
|
||||||
|
- Android App - 0.9.75
|
||||||
|
- iOS App - 0.9.75
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: current-image
|
||||||
|
attributes:
|
||||||
|
label: Current Implementation (Screenshot)
|
||||||
|
description: What screen were you looking at when you thought of this enhancement?
|
||||||
|
placeholder: If an image is not applicable, please explain why.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: Close fixed issues on release.
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Close issues marked as fixed upon a release.
|
||||||
|
uses: gcampbell-msft/fixed-pending-release@7fa1b75a0c04bcd4b375110522878e5f6100cff5
|
||||||
|
with:
|
||||||
|
label: 'awaiting release'
|
||||||
|
removeLabel: true
|
||||||
|
applyToAll: true
|
||||||
|
message: Fixed in [${releaseTag}](${releaseUrl}).
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
name: Verify all i18n files are alphabetized
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- strings/** # Should only check if any strings changed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_translations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Check out the repository
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Set up node to run the javascript
|
||||||
|
- name: Set up node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
# The only argument is the `directory`, which is where the i18n files are
|
||||||
|
# stored.
|
||||||
|
- name: Run Update JSON Files action
|
||||||
|
uses: audiobookshelf/audiobookshelf-i18n-updater@v1.3.0
|
||||||
|
with:
|
||||||
|
directory: 'strings/' # Adjust the directory path as needed
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 400,
|
||||||
|
"proseWrap": "never",
|
||||||
|
"trailingComma": "none",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.html"],
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false,
|
||||||
|
"wrapAttributes": false,
|
||||||
|
"sortAttributes": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"octref.vetur"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"vetur.format.defaultFormatterOptions": {
|
||||||
|
"prettier": {
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 400,
|
||||||
|
"proseWrap": "never",
|
||||||
|
"trailingComma": "none"
|
||||||
|
},
|
||||||
|
"prettyhtml": {
|
||||||
|
"printWidth": 400,
|
||||||
|
"singleQuote": false,
|
||||||
|
"wrapAttributes": false,
|
||||||
|
"sortAttributes": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.detectIndentation": true,
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"javascript.format.semicolons": "remove",
|
||||||
|
"[javascript][json][jsonc]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "octref.vetur"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,13 +28,13 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs = ['-Xjvm-default=all']
|
freeCompilerArgs = ['-Xjvm-default=all']
|
||||||
}
|
}
|
||||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.audiobookshelf.app"
|
applicationId "com.audiobookshelf.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 103
|
versionCode 108
|
||||||
versionName "0.9.72-beta"
|
versionName "0.9.77-beta"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
manifestPlaceholders = [
|
manifestPlaceholders = [
|
||||||
"appAuthRedirectScheme": "com.audiobookshelf.app"
|
"appAuthRedirectScheme": "com.audiobookshelf.app"
|
||||||
@@ -64,8 +64,6 @@ repositories {
|
|||||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||||
}
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
// TODO: Temporarily using SNAPSHOT version of Simple Storage that resolves https://github.com/anggrayudi/SimpleStorage/issues/133
|
|
||||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.configureEach {
|
configurations.configureEach {
|
||||||
@@ -120,7 +118,7 @@ dependencies {
|
|||||||
implementation 'io.github.pilgr:paperdb:2.7.2'
|
implementation 'io.github.pilgr:paperdb:2.7.2'
|
||||||
|
|
||||||
// Simple Storage
|
// Simple Storage
|
||||||
implementation "com.anggrayudi:storage:1.5.5-SNAPSHOT"
|
implementation "com.anggrayudi:storage:1.5.6"
|
||||||
|
|
||||||
// OK HTTP
|
// OK HTTP
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ android {
|
|||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':byteowls-capacitor-filesharer')
|
implementation project(':byteowls-capacitor-filesharer')
|
||||||
|
implementation project(':capacitor-community-volume-buttons')
|
||||||
implementation project(':capacitor-app')
|
implementation project(':capacitor-app')
|
||||||
implementation project(':capacitor-browser')
|
implementation project(':capacitor-browser')
|
||||||
implementation project(':capacitor-clipboard')
|
implementation project(':capacitor-clipboard')
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
"pkg": "@byteowls/capacitor-filesharer",
|
"pkg": "@byteowls/capacitor-filesharer",
|
||||||
"classpath": "com.byteowls.capacitor.filesharer.FileSharerPlugin"
|
"classpath": "com.byteowls.capacitor.filesharer.FileSharerPlugin"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"pkg": "@capacitor-community/volume-buttons",
|
||||||
|
"classpath": "com.ryltsov.alex.plugins.volume.buttons.VolumeButtonsPlugin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"pkg": "@capacitor/app",
|
"pkg": "@capacitor/app",
|
||||||
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
|
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ class MainActivity : BridgeActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPlayerNotificationServiceInitialized():Boolean {
|
||||||
|
return ::foregroundService.isInitialized
|
||||||
|
}
|
||||||
|
|
||||||
fun stopMyService() {
|
fun stopMyService() {
|
||||||
if (mBounded) {
|
if (mBounded) {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class Podcast(
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode):PodcastEpisode {
|
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode):PodcastEpisode {
|
||||||
val localEpisodeId = "local_ep_" + episode.id
|
val localEpisodeId = "local_ep_" + episode.id
|
||||||
val newEpisode = PodcastEpisode(localEpisodeId,(episodes?.size ?: 0) + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,null,null,audioTrack,episode.chapters,audioTrack.duration,0, episode.id, localEpisodeId)
|
val newEpisode = PodcastEpisode(localEpisodeId,(episodes?.size ?: 0) + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,null,null,audioTrack,episode.chapters,audioTrack.duration,episode.size, episode.id, localEpisodeId)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ enum class ShakeSensitivitySetting {
|
|||||||
VERY_LOW, LOW, MEDIUM, HIGH, VERY_HIGH
|
VERY_LOW, LOW, MEDIUM, HIGH, VERY_HIGH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DownloadUsingCellularSetting {
|
||||||
|
ASK, ALWAYS, NEVER
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class StreamingUsingCellularSetting {
|
||||||
|
ASK, ALWAYS, NEVER
|
||||||
|
}
|
||||||
|
|
||||||
data class ServerConnectionConfig(
|
data class ServerConnectionConfig(
|
||||||
var id:String,
|
var id:String,
|
||||||
var index:Int,
|
var index:Int,
|
||||||
@@ -107,6 +115,7 @@ data class PlayItemRequestPayload(
|
|||||||
data class DeviceSettings(
|
data class DeviceSettings(
|
||||||
var disableAutoRewind:Boolean,
|
var disableAutoRewind:Boolean,
|
||||||
var enableAltView:Boolean,
|
var enableAltView:Boolean,
|
||||||
|
var allowSeekingOnMediaControls:Boolean,
|
||||||
var jumpBackwardsTime:Int,
|
var jumpBackwardsTime:Int,
|
||||||
var jumpForwardTime:Int,
|
var jumpForwardTime:Int,
|
||||||
var enableMp3IndexSeeking:Boolean,
|
var enableMp3IndexSeeking:Boolean,
|
||||||
@@ -122,7 +131,9 @@ data class DeviceSettings(
|
|||||||
var sleepTimerLength: Long, // Time in milliseconds
|
var sleepTimerLength: Long, // Time in milliseconds
|
||||||
var disableSleepTimerFadeOut: Boolean,
|
var disableSleepTimerFadeOut: Boolean,
|
||||||
var disableSleepTimerResetFeedback: Boolean,
|
var disableSleepTimerResetFeedback: Boolean,
|
||||||
var languageCode: String
|
var languageCode: String,
|
||||||
|
var downloadUsingCellular: DownloadUsingCellularSetting,
|
||||||
|
var streamingUsingCellular: StreamingUsingCellularSetting
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// Static method to get default device settings
|
// Static method to get default device settings
|
||||||
@@ -130,6 +141,7 @@ data class DeviceSettings(
|
|||||||
return DeviceSettings(
|
return DeviceSettings(
|
||||||
disableAutoRewind = false,
|
disableAutoRewind = false,
|
||||||
enableAltView = true,
|
enableAltView = true,
|
||||||
|
allowSeekingOnMediaControls = false,
|
||||||
jumpBackwardsTime = 10,
|
jumpBackwardsTime = 10,
|
||||||
jumpForwardTime = 10,
|
jumpForwardTime = 10,
|
||||||
enableMp3IndexSeeking = false,
|
enableMp3IndexSeeking = false,
|
||||||
@@ -145,7 +157,9 @@ data class DeviceSettings(
|
|||||||
autoSleepTimerAutoRewindTime = 300000L, // 5 minutes
|
autoSleepTimerAutoRewindTime = 300000L, // 5 minutes
|
||||||
disableSleepTimerFadeOut = false,
|
disableSleepTimerFadeOut = false,
|
||||||
disableSleepTimerResetFeedback = false,
|
disableSleepTimerResetFeedback = false,
|
||||||
languageCode = "en-us"
|
languageCode = "en-us",
|
||||||
|
downloadUsingCellular = DownloadUsingCellularSetting.ALWAYS,
|
||||||
|
streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.audiobookshelf.app.device.DeviceManager
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.audiobookshelf.app.player.PLAYMETHOD_LOCAL
|
import com.audiobookshelf.app.player.PLAYMETHOD_LOCAL
|
||||||
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
@@ -78,6 +79,27 @@ class LocalLibraryItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun hasTracks(episode:PodcastEpisode?): Boolean {
|
||||||
|
var audioTracks = media.getAudioTracks() as MutableList<AudioTrack>
|
||||||
|
if (episode != null) { // Get podcast episode audio track
|
||||||
|
episode.audioTrack?.let { at -> mutableListOf(at) }?.let { tracks -> audioTracks = tracks }
|
||||||
|
}
|
||||||
|
if (audioTracks.size == 0) return false
|
||||||
|
audioTracks.forEach {
|
||||||
|
// Check that metadata is not null
|
||||||
|
if (it.metadata === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Check that file exists
|
||||||
|
val file = File(it.metadata!!.path)
|
||||||
|
if (!file.exists()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getPlaybackSession(episode:PodcastEpisode?, deviceInfo:DeviceInfo):PlaybackSession {
|
fun getPlaybackSession(episode:PodcastEpisode?, deviceInfo:DeviceInfo):PlaybackSession {
|
||||||
val localEpisodeId = episode?.id
|
val localEpisodeId = episode?.id
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ object DeviceManager {
|
|||||||
if (deviceData.deviceSettings?.languageCode == null) {
|
if (deviceData.deviceSettings?.languageCode == null) {
|
||||||
deviceData.deviceSettings?.languageCode = "en-us"
|
deviceData.deviceSettings?.languageCode = "en-us"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deviceData.deviceSettings?.downloadUsingCellular == null) {
|
||||||
|
deviceData.deviceSettings?.downloadUsingCellular = DownloadUsingCellularSetting.ALWAYS
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceData.deviceSettings?.streamingUsingCellular == null) {
|
||||||
|
deviceData.deviceSettings?.streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBase64Id(id:String):String {
|
fun getBase64Id(id:String):String {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class FolderScanner(var ctx: Context) {
|
|||||||
)
|
)
|
||||||
localLibraryItem.localFiles.add(localFile)
|
localLibraryItem.localFiles.add(localFile)
|
||||||
|
|
||||||
|
val trackFileMetadata = FileMetadata(file.name, file.extension, file.absolutePath, file.getBasePath(ctx), file.length())
|
||||||
// Create new audio track
|
// Create new audio track
|
||||||
val track = AudioTrack(
|
val track = AudioTrack(
|
||||||
audioTrackFromServer.index,
|
audioTrackFromServer.index,
|
||||||
@@ -78,7 +79,7 @@ class FolderScanner(var ctx: Context) {
|
|||||||
localFile.filename ?: "",
|
localFile.filename ?: "",
|
||||||
localFile.contentUrl,
|
localFile.contentUrl,
|
||||||
localFile.mimeType ?: "",
|
localFile.mimeType ?: "",
|
||||||
null,
|
trackFileMetadata,
|
||||||
true,
|
true,
|
||||||
localFileId,
|
localFileId,
|
||||||
null,
|
null,
|
||||||
@@ -274,7 +275,8 @@ class FolderScanner(var ctx: Context) {
|
|||||||
localLibraryItem.localFiles.add(localFile)
|
localLibraryItem.localFiles.add(localFile)
|
||||||
|
|
||||||
// Create new audio track
|
// Create new audio track
|
||||||
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioTrackFromServer.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, null, audioTrackFromServer.index)
|
val trackFileMetadata = FileMetadata(docFile.name ?: "", docFile.extension ?: "", docFile.getAbsolutePath(ctx), docFile.getBasePath(ctx), docFile.length())
|
||||||
|
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioTrackFromServer.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", trackFileMetadata, true, localFileId, null, audioTrackFromServer.index)
|
||||||
audioTracks.add(track)
|
audioTracks.add(track)
|
||||||
|
|
||||||
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
|
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
|
||||||
|
|||||||
@@ -247,14 +247,16 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking {
|
private fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking {
|
||||||
Log.d(tag, "checkSetValidServerConnectionConfig | $serverConfigIdUsed")
|
Log.d(tag, "checkSetValidServerConnectionConfig | serverConfigIdUsed=$serverConfigIdUsed | lastServerConnectionConfigId=${DeviceManager.deviceData.lastServerConnectionConfigId}")
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
if (!DeviceManager.checkConnectivity(ctx)) {
|
if (!DeviceManager.checkConnectivity(ctx)) {
|
||||||
serverUserMediaProgress = mutableListOf()
|
serverUserMediaProgress = mutableListOf()
|
||||||
|
Log.d(tag, "checkSetValidServerConnectionConfig: No connectivity")
|
||||||
cb(false)
|
cb(false)
|
||||||
} else if (DeviceManager.serverConnectionConfigId == "") { // If in offline mode server connection config is unset
|
} else if (DeviceManager.deviceData.lastServerConnectionConfigId.isNullOrBlank()) { // If in offline mode last server connection config is unset
|
||||||
serverUserMediaProgress = mutableListOf()
|
serverUserMediaProgress = mutableListOf()
|
||||||
|
Log.d(tag, "checkSetValidServerConnectionConfig: No last server connection config")
|
||||||
cb(false)
|
cb(false)
|
||||||
} else {
|
} else {
|
||||||
var hasValidConn = false
|
var hasValidConn = false
|
||||||
@@ -334,6 +336,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
checkSetValidServerConnectionConfig { isConnected ->
|
checkSetValidServerConnectionConfig { isConnected ->
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
serverConfigIdUsed = DeviceManager.serverConnectionConfigId
|
serverConfigIdUsed = DeviceManager.serverConnectionConfigId
|
||||||
|
Log.d(tag, "loadAndroidAutoItems: Connected to server config id=$serverConfigIdUsed")
|
||||||
|
|
||||||
loadLibraries { libraries ->
|
loadLibraries { libraries ->
|
||||||
if (libraries.isEmpty()) {
|
if (libraries.isEmpty()) {
|
||||||
@@ -360,6 +363,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Not connected to server
|
} else { // Not connected to server
|
||||||
|
Log.d(tag, "loadAndroidAutoItems: Not connected to server")
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -15,8 +15,6 @@ import com.google.android.exoplayer2.Player
|
|||||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px
|
|
||||||
|
|
||||||
class AbMediaDescriptionAdapter constructor(private val controller: MediaControllerCompat, private val playerNotificationService: PlayerNotificationService) : PlayerNotificationManager.MediaDescriptionAdapter {
|
class AbMediaDescriptionAdapter constructor(private val controller: MediaControllerCompat, private val playerNotificationService: PlayerNotificationService) : PlayerNotificationManager.MediaDescriptionAdapter {
|
||||||
private val tag = "MediaDescriptionAdapter"
|
private val tag = "MediaDescriptionAdapter"
|
||||||
|
|
||||||
@@ -77,7 +75,7 @@ class AbMediaDescriptionAdapter constructor(private val controller: MediaControl
|
|||||||
.load(uri)
|
.load(uri)
|
||||||
.placeholder(R.drawable.icon)
|
.placeholder(R.drawable.icon)
|
||||||
.error(R.drawable.icon)
|
.error(R.drawable.icon)
|
||||||
.submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE)
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -85,7 +83,7 @@ class AbMediaDescriptionAdapter constructor(private val controller: MediaControl
|
|||||||
Glide.with(playerNotificationService)
|
Glide.with(playerNotificationService)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon))
|
.load(Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.icon))
|
||||||
.submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE)
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-15
@@ -34,6 +34,7 @@ import com.audiobookshelf.app.managers.SleepTimerManager
|
|||||||
import com.audiobookshelf.app.media.MediaManager
|
import com.audiobookshelf.app.media.MediaManager
|
||||||
import com.audiobookshelf.app.media.MediaProgressSyncer
|
import com.audiobookshelf.app.media.MediaProgressSyncer
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
|
import com.audiobookshelf.app.BuildConfig
|
||||||
import com.google.android.exoplayer2.*
|
import com.google.android.exoplayer2.*
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||||
@@ -319,25 +320,16 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
||||||
.setExtras(extra)
|
.setExtras(extra)
|
||||||
.setTitle(currentPlaybackSession!!.displayTitle)
|
.setTitle(currentPlaybackSession!!.displayTitle)
|
||||||
.setIconUri(coverUri)
|
|
||||||
|
|
||||||
bitmap?.let {
|
bitmap?.let {
|
||||||
mediaDescriptionBuilder.setIconBitmap(it)
|
mediaDescriptionBuilder.setIconBitmap(it)
|
||||||
}
|
} ?: mediaDescriptionBuilder.setIconUri(coverUri)
|
||||||
|
|
||||||
return mediaDescriptionBuilder.build()
|
return mediaDescriptionBuilder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaSessionConnector.setEnabledPlaybackActions(
|
setMediaSessionConnectorPlaybackActions()
|
||||||
PlaybackStateCompat.ACTION_PLAY_PAUSE
|
|
||||||
or PlaybackStateCompat.ACTION_PLAY
|
|
||||||
or PlaybackStateCompat.ACTION_PAUSE
|
|
||||||
or PlaybackStateCompat.ACTION_SEEK_TO
|
|
||||||
or PlaybackStateCompat.ACTION_FAST_FORWARD
|
|
||||||
or PlaybackStateCompat.ACTION_REWIND
|
|
||||||
or PlaybackStateCompat.ACTION_STOP
|
|
||||||
)
|
|
||||||
mediaSessionConnector.setQueueNavigator(queueNavigator)
|
mediaSessionConnector.setQueueNavigator(queueNavigator)
|
||||||
mediaSessionConnector.setPlaybackPreparer(MediaSessionPlaybackPreparer(this))
|
mediaSessionConnector.setPlaybackPreparer(MediaSessionPlaybackPreparer(this))
|
||||||
|
|
||||||
@@ -512,6 +504,20 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray())
|
mediaSessionConnector.setCustomActionProviders(*customActionProviders.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setMediaSessionConnectorPlaybackActions() {
|
||||||
|
var playbackActions = PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
||||||
|
PlaybackStateCompat.ACTION_PLAY or
|
||||||
|
PlaybackStateCompat.ACTION_PAUSE or
|
||||||
|
PlaybackStateCompat.ACTION_FAST_FORWARD or
|
||||||
|
PlaybackStateCompat.ACTION_REWIND or
|
||||||
|
PlaybackStateCompat.ACTION_STOP
|
||||||
|
|
||||||
|
if (deviceSettings.allowSeekingOnMediaControls) {
|
||||||
|
playbackActions = playbackActions or PlaybackStateCompat.ACTION_SEEK_TO
|
||||||
|
}
|
||||||
|
mediaSessionConnector.setEnabledPlaybackActions(playbackActions)
|
||||||
|
}
|
||||||
|
|
||||||
fun handlePlayerPlaybackError(errorMessage:String) {
|
fun handlePlayerPlaybackError(errorMessage:String) {
|
||||||
// On error and was attempting to direct play - fallback to transcode
|
// On error and was attempting to direct play - fallback to transcode
|
||||||
currentPlaybackSession?.let { playbackSession ->
|
currentPlaybackSession?.let { playbackSession ->
|
||||||
@@ -881,16 +887,18 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
|
|
||||||
fun closePlayback(calledOnError:Boolean? = false) {
|
fun closePlayback(calledOnError:Boolean? = false) {
|
||||||
Log.d(tag, "closePlayback")
|
Log.d(tag, "closePlayback")
|
||||||
|
val config = DeviceManager.serverConnectionConfig
|
||||||
|
|
||||||
val isLocal = mediaProgressSyncer.currentIsLocal
|
val isLocal = mediaProgressSyncer.currentIsLocal
|
||||||
val currentSessionId = mediaProgressSyncer.currentSessionId
|
val currentSessionId = mediaProgressSyncer.currentSessionId
|
||||||
if (mediaProgressSyncer.listeningTimerRunning) {
|
if (mediaProgressSyncer.listeningTimerRunning) {
|
||||||
Log.i(tag, "About to close playback so stopping media progress syncer first")
|
Log.i(tag, "About to close playback so stopping media progress syncer first")
|
||||||
|
|
||||||
mediaProgressSyncer.stop(calledOnError == false) { // If closing on error then do not sync progress (causes exception)
|
mediaProgressSyncer.stop(calledOnError == false) { // If closing on error then do not sync progress (causes exception)
|
||||||
Log.d(tag, "Media Progress syncer stopped")
|
Log.d(tag, "Media Progress syncer stopped")
|
||||||
|
|
||||||
// If not local session then close on server
|
// If not local session then close on server
|
||||||
if (!isLocal && currentSessionId != "") {
|
if (!isLocal && currentSessionId != "") {
|
||||||
apiHandler.closePlaybackSession(currentSessionId) {
|
apiHandler.closePlaybackSession(currentSessionId, config) {
|
||||||
Log.d(tag, "Closed playback session $currentSessionId")
|
Log.d(tag, "Closed playback session $currentSessionId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -898,7 +906,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
} else {
|
} else {
|
||||||
// If not local session then close on server
|
// If not local session then close on server
|
||||||
if (!isLocal && currentSessionId != "") {
|
if (!isLocal && currentSessionId != "") {
|
||||||
apiHandler.closePlaybackSession(currentSessionId) {
|
apiHandler.closePlaybackSession(currentSessionId, config) {
|
||||||
Log.d(tag, "Closed playback session $currentSessionId")
|
Log.d(tag, "Closed playback session $currentSessionId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -941,7 +949,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
appVersion: 0.9.46-beta
|
appVersion: 0.9.46-beta
|
||||||
*/
|
*/
|
||||||
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
||||||
return DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, Build.VERSION.RELEASE)
|
return DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, BuildConfig.VERSION_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default()
|
private val deviceSettings get() = DeviceManager.deviceData.deviceSettings ?: DeviceSettings.default()
|
||||||
|
|||||||
@@ -198,6 +198,9 @@ class AbsAudioPlayer : Plugin() {
|
|||||||
return call.resolve(JSObject("{\"error\":\"Podcast episode not found\"}"))
|
return call.resolve(JSObject("{\"error\":\"Podcast episode not found\"}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!it.hasTracks(episode)) {
|
||||||
|
return call.resolve(JSObject("{\"error\":\"No audio files found on device. Download book again to fix.\"}"))
|
||||||
|
}
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
|
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.audiobookshelf.app.plugins
|
package com.audiobookshelf.app.plugins
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
@@ -493,9 +495,16 @@ class AbsDatabase : Plugin() {
|
|||||||
fun updateDeviceSettings(call:PluginCall) { // Returns device data
|
fun updateDeviceSettings(call:PluginCall) { // Returns device data
|
||||||
Log.d(tag, "updateDeviceSettings ${call.data}")
|
Log.d(tag, "updateDeviceSettings ${call.data}")
|
||||||
val newDeviceSettings = jacksonMapper.readValue<DeviceSettings>(call.data.toString())
|
val newDeviceSettings = jacksonMapper.readValue<DeviceSettings>(call.data.toString())
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
DeviceManager.deviceData.deviceSettings = newDeviceSettings
|
DeviceManager.deviceData.deviceSettings = newDeviceSettings
|
||||||
DeviceManager.dbManager.saveDeviceData(DeviceManager.deviceData)
|
DeviceManager.dbManager.saveDeviceData(DeviceManager.deviceData)
|
||||||
|
|
||||||
|
// Updates playback actions for media notification (handles media control seek locking setting)
|
||||||
|
if (mainActivity.isPlayerNotificationServiceInitialized()) {
|
||||||
|
mainActivity.foregroundService.setMediaSessionConnectorPlaybackActions()
|
||||||
|
}
|
||||||
|
|
||||||
call.resolve(JSObject(jacksonMapper.writeValueAsString(DeviceManager.deviceData)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(DeviceManager.deviceData)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,11 @@ class AbsFileSystem : Plugin() {
|
|||||||
if (localLibraryItem?.folderId?.startsWith("internal-") == true) {
|
if (localLibraryItem?.folderId?.startsWith("internal-") == true) {
|
||||||
Log.d(tag, "Deleting internal library item at absolutePath $absolutePath")
|
Log.d(tag, "Deleting internal library item at absolutePath $absolutePath")
|
||||||
val file = File(absolutePath)
|
val file = File(absolutePath)
|
||||||
success = file.deleteRecursively()
|
success = if (file.exists()) {
|
||||||
|
file.deleteRecursively()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var subfolderPathToDelete = ""
|
var subfolderPathToDelete = ""
|
||||||
localLibraryItem?.folderId?.let { folderId ->
|
localLibraryItem?.folderId?.let { folderId ->
|
||||||
@@ -218,7 +222,12 @@ class AbsFileSystem : Plugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl))
|
val docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl))
|
||||||
success = docfile?.delete() == true
|
if (docfile?.exists() == true) {
|
||||||
|
success = docfile.delete() == true
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Folder $contentUrl doesn't exist")
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
|
||||||
if (subfolderPathToDelete != "") {
|
if (subfolderPathToDelete != "") {
|
||||||
Log.d(tag, "Deleting empty subfolder at $subfolderPathToDelete")
|
Log.d(tag, "Deleting empty subfolder at $subfolderPathToDelete")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.audiobookshelf.app.media.MediaEventManager
|
|||||||
import com.audiobookshelf.app.media.MediaProgressSyncData
|
import com.audiobookshelf.app.media.MediaProgressSyncData
|
||||||
import com.audiobookshelf.app.media.SyncResult
|
import com.audiobookshelf.app.media.SyncResult
|
||||||
import com.audiobookshelf.app.models.User
|
import com.audiobookshelf.app.models.User
|
||||||
|
import com.audiobookshelf.app.BuildConfig
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
@@ -43,10 +44,17 @@ class ApiHandler(var ctx:Context) {
|
|||||||
val address = config?.address ?: DeviceManager.serverAddress
|
val address = config?.address ?: DeviceManager.serverAddress
|
||||||
val token = config?.token ?: DeviceManager.token
|
val token = config?.token ?: DeviceManager.token
|
||||||
|
|
||||||
val request = Request.Builder()
|
try {
|
||||||
.url("${address}$endpoint").addHeader("Authorization", "Bearer $token")
|
val request = Request.Builder()
|
||||||
.build()
|
.url("${address}$endpoint").addHeader("Authorization", "Bearer $token")
|
||||||
makeRequest(request, httpClient, cb)
|
.build()
|
||||||
|
makeRequest(request, httpClient, cb)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
val jsobj = JSObject()
|
||||||
|
jsobj.put("error", "Request failed: ${e.message}")
|
||||||
|
cb(jsobj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postRequest(endpoint:String, payload: JSObject?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
|
private fun postRequest(endpoint:String, payload: JSObject?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
|
||||||
@@ -56,23 +64,38 @@ class ApiHandler(var ctx:Context) {
|
|||||||
val requestBody = payload?.toString()?.toRequestBody(mediaType) ?: EMPTY_REQUEST
|
val requestBody = payload?.toString()?.toRequestBody(mediaType) ?: EMPTY_REQUEST
|
||||||
val requestUrl = "${address}$endpoint"
|
val requestUrl = "${address}$endpoint"
|
||||||
Log.d(tag, "postRequest to $requestUrl")
|
Log.d(tag, "postRequest to $requestUrl")
|
||||||
val request = Request.Builder().post(requestBody)
|
try {
|
||||||
.url(requestUrl).addHeader("Authorization", "Bearer ${token}")
|
val request = Request.Builder().post(requestBody)
|
||||||
.build()
|
.url(requestUrl).addHeader("Authorization", "Bearer ${token}")
|
||||||
makeRequest(request, null, cb)
|
.build()
|
||||||
|
makeRequest(request, null, cb)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
val jsobj = JSObject()
|
||||||
|
jsobj.put("error", "Request failed: ${e.message}")
|
||||||
|
cb(jsobj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
private fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
||||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
val requestBody = payload.toString().toRequestBody(mediaType)
|
val requestBody = payload.toString().toRequestBody(mediaType)
|
||||||
val request = Request.Builder().patch(requestBody)
|
try {
|
||||||
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
val request = Request.Builder().patch(requestBody)
|
||||||
.build()
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
makeRequest(request, null, cb)
|
.build()
|
||||||
|
makeRequest(request, null, cb)
|
||||||
|
} catch(e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
val jsobj = JSObject()
|
||||||
|
jsobj.put("error", "Request failed: ${e.message}")
|
||||||
|
cb(jsobj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
|
private fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
|
||||||
val client = httpClient ?: defaultClient
|
val client = httpClient ?: defaultClient
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.d(tag, "FAILURE TO CONNECT")
|
Log.d(tag, "FAILURE TO CONNECT")
|
||||||
@@ -296,9 +319,9 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closePlaybackSession(playbackSessionId:String, cb: (Boolean) -> Unit) {
|
fun closePlaybackSession(playbackSessionId:String, config:ServerConnectionConfig?, cb: (Boolean) -> Unit) {
|
||||||
Log.d(tag, "closePlaybackSession: playbackSessionId=$playbackSessionId")
|
Log.d(tag, "closePlaybackSession: playbackSessionId=$playbackSessionId")
|
||||||
postRequest("/api/session/$playbackSessionId/close", null, null) {
|
postRequest("/api/session/$playbackSessionId/close", null, config) {
|
||||||
cb(true)
|
cb(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +350,7 @@ class ApiHandler(var ctx:Context) {
|
|||||||
fun sendSyncLocalSessions(playbackSessions:List<PlaybackSession>, cb: (Boolean, String?) -> Unit) {
|
fun sendSyncLocalSessions(playbackSessions:List<PlaybackSession>, cb: (Boolean, String?) -> Unit) {
|
||||||
@SuppressLint("HardwareIds")
|
@SuppressLint("HardwareIds")
|
||||||
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
val deviceId = Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
|
||||||
val deviceInfo = DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, Build.VERSION.RELEASE)
|
val deviceInfo = DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions, deviceInfo)))
|
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions, deviceInfo)))
|
||||||
Log.d(tag, "Sending ${playbackSessions.size} saved local playback sessions to server")
|
Log.d(tag, "Sending ${playbackSessions.size} saved local playback sessions to server")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.20'
|
ext.kotlin_version = '2.0.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/
|
|||||||
include ':byteowls-capacitor-filesharer'
|
include ':byteowls-capacitor-filesharer'
|
||||||
project(':byteowls-capacitor-filesharer').projectDir = new File('../node_modules/@byteowls/capacitor-filesharer/android')
|
project(':byteowls-capacitor-filesharer').projectDir = new File('../node_modules/@byteowls/capacitor-filesharer/android')
|
||||||
|
|
||||||
|
include ':capacitor-community-volume-buttons'
|
||||||
|
project(':capacitor-community-volume-buttons').projectDir = new File('../node_modules/@capacitor-community/volume-buttons/android')
|
||||||
|
|
||||||
include ':capacitor-app'
|
include ':capacitor-app'
|
||||||
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
ext {
|
ext {
|
||||||
minSdkVersion = 24
|
minSdkVersion = 24
|
||||||
compileSdkVersion = 34
|
compileSdkVersion = 34
|
||||||
targetSdkVersion = 33
|
targetSdkVersion = 34
|
||||||
androidxActivityVersion = '1.7.0'
|
androidxActivityVersion = '1.7.0'
|
||||||
androidxAppCompatVersion = '1.6.1'
|
androidxAppCompatVersion = '1.7.0'
|
||||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
androidxCoreVersion = '1.10.0'
|
androidxCoreVersion = '1.10.0'
|
||||||
androidPlayCore = '1.9.0'
|
androidPlayCore = '1.9.0'
|
||||||
@@ -13,8 +13,8 @@ ext {
|
|||||||
androidxEspressoCoreVersion = '3.5.1'
|
androidxEspressoCoreVersion = '3.5.1'
|
||||||
cordovaAndroidVersion = '10.1.1'
|
cordovaAndroidVersion = '10.1.1'
|
||||||
androidx_car_version = '1.0.0-alpha7'
|
androidx_car_version = '1.0.0-alpha7'
|
||||||
androidx_core_ktx_version = '1.12.0'
|
androidx_core_ktx_version = '1.13.1'
|
||||||
androidx_media_version = '1.6.0'
|
androidx_media_version = '1.7.0'
|
||||||
androidx_preference_version = '1.1.1'
|
androidx_preference_version = '1.1.1'
|
||||||
androidx_test_runner_version = '1.3.0'
|
androidx_test_runner_version = '1.3.0'
|
||||||
arch_lifecycle_version = '2.2.0'
|
arch_lifecycle_version = '2.2.0'
|
||||||
@@ -27,7 +27,7 @@ ext {
|
|||||||
gradle_version = '3.1.4'
|
gradle_version = '3.1.4'
|
||||||
gson_version = '2.8.5'
|
gson_version = '2.8.5'
|
||||||
junit_version = '4.13'
|
junit_version = '4.13'
|
||||||
kotlin_version = '1.8.10'
|
kotlin_version = '2.0.0'
|
||||||
kotlin_coroutines_version = '1.6.4'
|
kotlin_coroutines_version = '1.6.4'
|
||||||
multidex_version = '1.0.3'
|
multidex_version = '1.0.3'
|
||||||
play_services_auth_version = '18.1.0'
|
play_services_auth_version = '18.1.0'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<a v-if="showBack" @click="back" class="rounded-full h-10 w-10 flex items-center justify-center mr-2 cursor-pointer">
|
<a v-if="showBack" @click="back" class="rounded-full h-10 w-10 flex items-center justify-center mr-2 cursor-pointer">
|
||||||
<span class="material-icons text-3xl text-fg">arrow_back</span>
|
<span class="material-icons text-3xl text-fg">arrow_back</span>
|
||||||
</a>
|
</a>
|
||||||
<div v-if="user && currentLibrary && networkConnected">
|
<div v-if="user && currentLibrary && socketConnected">
|
||||||
<div class="pl-1.5 pr-2.5 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
<div class="pl-1.5 pr-2.5 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
||||||
<ui-library-icon :icon="currentLibraryIcon" :size="4" font-size="base" />
|
<ui-library-icon :icon="currentLibraryIcon" :size="4" font-size="base" />
|
||||||
<p class="text-sm leading-4 ml-2 mt-0.5 max-w-24 truncate">{{ currentLibraryName }}</p>
|
<p class="text-sm leading-4 ml-2 mt-0.5 max-w-24 truncate">{{ currentLibraryName }}</p>
|
||||||
@@ -54,8 +54,8 @@ export default {
|
|||||||
this.$store.commit('setCastAvailable', val)
|
this.$store.commit('setCastAvailable', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networkConnected() {
|
socketConnected() {
|
||||||
return this.$store.state.networkConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
currentLibrary() {
|
currentLibrary() {
|
||||||
return this.$store.getters['libraries/getCurrentLibrary']
|
return this.$store.getters['libraries/getCurrentLibrary']
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<div id="playerContent" class="playerContainer w-full z-20 absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" :style="{ backgroundColor: showFullscreen ? '' : coverRgb }" @click="clickContainer">
|
<div id="playerContent" class="playerContainer w-full z-20 absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" :style="{ backgroundColor: showFullscreen ? '' : coverRgb }" @click="clickContainer">
|
||||||
<div v-if="showFullscreen" class="absolute bottom-4 left-0 right-0 w-full pb-4 pt-2 mx-auto px-6" style="max-width: 414px">
|
<div v-if="showFullscreen" class="absolute bottom-4 left-0 right-0 w-full pb-4 pt-2 mx-auto px-6" style="max-width: 414px">
|
||||||
<div class="flex items-center justify-between pointer-events-auto">
|
<div class="flex items-center justify-between pointer-events-auto">
|
||||||
<span v-if="!isPodcast && serverLibraryItemId && networkConnected" class="material-icons text-3xl text-fg-muted cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
<span v-if="!isPodcast && serverLibraryItemId && socketConnected" class="material-icons text-3xl text-fg-muted cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
||||||
<!-- hidden for podcasts but still using this as a placeholder -->
|
<!-- hidden for podcasts but still using this as a placeholder -->
|
||||||
<span v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
<span v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
||||||
|
|
||||||
@@ -163,7 +163,8 @@ export default {
|
|||||||
showMoreMenuDialog: false,
|
showMoreMenuDialog: false,
|
||||||
coverRgb: 'rgb(55, 56, 56)',
|
coverRgb: 'rgb(55, 56, 56)',
|
||||||
coverBgIsLight: false,
|
coverBgIsLight: false,
|
||||||
titleMarquee: null
|
titleMarquee: null,
|
||||||
|
isRefreshingUI: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -374,8 +375,8 @@ export default {
|
|||||||
return secondsRemaining + 's'
|
return secondsRemaining + 's'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networkConnected() {
|
socketConnected() {
|
||||||
return this.$store.state.networkConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
mediaId() {
|
mediaId() {
|
||||||
if (this.isPodcast || !this.playbackSession) return null
|
if (this.isPodcast || !this.playbackSession) return null
|
||||||
@@ -652,7 +653,7 @@ export default {
|
|||||||
this.closePlayback()
|
this.closePlayback()
|
||||||
},
|
},
|
||||||
touchstart(e) {
|
touchstart(e) {
|
||||||
if (!e.changedTouches) return
|
if (!e.changedTouches || this.$store.state.globals.isModalOpen) return
|
||||||
const touchPosY = e.changedTouches[0].pageY
|
const touchPosY = e.changedTouches[0].pageY
|
||||||
// when minimized only listen to touchstart on the player
|
// when minimized only listen to touchstart on the player
|
||||||
if (!this.showFullscreen && touchPosY < window.innerHeight - 120) return
|
if (!this.showFullscreen && touchPosY < window.innerHeight - 120) return
|
||||||
@@ -880,15 +881,32 @@ export default {
|
|||||||
this.onProgressSyncSuccess = AbsAudioPlayer.addListener('onProgressSyncSuccess', this.showProgressSyncSuccess)
|
this.onProgressSyncSuccess = AbsAudioPlayer.addListener('onProgressSyncSuccess', this.showProgressSyncSuccess)
|
||||||
this.onPlaybackSpeedChangedListener = AbsAudioPlayer.addListener('onPlaybackSpeedChanged', this.onPlaybackSpeedChanged)
|
this.onPlaybackSpeedChangedListener = AbsAudioPlayer.addListener('onPlaybackSpeedChanged', this.onPlaybackSpeedChanged)
|
||||||
},
|
},
|
||||||
screenOrientationChange() {
|
async screenOrientationChange() {
|
||||||
setTimeout(() => {
|
if (this.isRefreshingUI) return
|
||||||
this.updateScreenSize()
|
this.isRefreshingUI = true
|
||||||
if (this.$refs.track) {
|
const windowWidth = window.innerWidth
|
||||||
this.trackWidth = this.$refs.track.clientWidth
|
this.refreshUI()
|
||||||
this.updateTrack()
|
|
||||||
this.updateReadyTrack()
|
// Window width does not always change right away. Wait up to 250ms for a change.
|
||||||
|
// iPhone 10 on iOS 16 took between 100 - 200ms to update when going from portrait to landscape
|
||||||
|
// but landscape to portrait was immediate
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||||
|
if (window.innerWidth !== windowWidth) {
|
||||||
|
this.refreshUI()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}, 50)
|
}
|
||||||
|
|
||||||
|
this.isRefreshingUI = false
|
||||||
|
},
|
||||||
|
refreshUI() {
|
||||||
|
this.updateScreenSize()
|
||||||
|
if (this.$refs.track) {
|
||||||
|
this.trackWidth = this.$refs.track.clientWidth
|
||||||
|
this.updateTrack()
|
||||||
|
this.updateReadyTrack()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateScreenSize() {
|
updateScreenSize() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { AbsAudioPlayer } from '@/plugins/capacitor'
|
import { AbsAudioPlayer } from '@/plugins/capacitor'
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
|
import CellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -39,6 +40,7 @@ export default {
|
|||||||
serverEpisodeId: null
|
serverEpisodeId: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [CellularPermissionHelpers],
|
||||||
computed: {
|
computed: {
|
||||||
bookmarks() {
|
bookmarks() {
|
||||||
if (!this.serverLibraryItemId) return []
|
if (!this.serverLibraryItemId) return []
|
||||||
@@ -193,12 +195,21 @@ export default {
|
|||||||
const startTime = payload.startTime
|
const startTime = payload.startTime
|
||||||
const startWhenReady = !payload.paused
|
const startWhenReady = !payload.paused
|
||||||
|
|
||||||
|
const isLocal = libraryItemId.startsWith('local')
|
||||||
|
if (!isLocal) {
|
||||||
|
const hasPermission = await this.checkCellularPermission('streaming')
|
||||||
|
if (!hasPermission) {
|
||||||
|
this.$store.commit('setPlayerDoneStartingPlayback')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When playing local library item and can also play this item from the server
|
// When playing local library item and can also play this item from the server
|
||||||
// then store the server library item id so it can be used if a cast is made
|
// then store the server library item id so it can be used if a cast is made
|
||||||
const serverLibraryItemId = payload.serverLibraryItemId || null
|
const serverLibraryItemId = payload.serverLibraryItemId || null
|
||||||
const serverEpisodeId = payload.serverEpisodeId || null
|
const serverEpisodeId = payload.serverEpisodeId || null
|
||||||
|
|
||||||
if (libraryItemId.startsWith('local') && this.$store.state.isCasting) {
|
if (isLocal && this.$store.state.isCasting) {
|
||||||
const { value } = await Dialog.confirm({
|
const { value } = await Dialog.confirm({
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
message: `Cannot cast downloaded media items. Confirm to close cast and play on your device.`
|
message: `Cannot cast downloaded media items. Confirm to close cast and play on your device.`
|
||||||
|
|||||||
@@ -79,7 +79,10 @@ export default {
|
|||||||
return this.$store.state.serverSettings || {}
|
return this.$store.state.serverSettings || {}
|
||||||
},
|
},
|
||||||
username() {
|
username() {
|
||||||
return this.user ? this.user.username : ''
|
return this.user?.username || ''
|
||||||
|
},
|
||||||
|
userIsAdminOrUp() {
|
||||||
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
navItems() {
|
navItems() {
|
||||||
var items = [
|
var items = [
|
||||||
@@ -132,6 +135,12 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (this.serverConnectionConfig) {
|
if (this.serverConnectionConfig) {
|
||||||
|
items.push({
|
||||||
|
icon: 'language',
|
||||||
|
text: this.$strings.ButtonGoToWebClient,
|
||||||
|
action: 'openWebClient'
|
||||||
|
})
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
icon: 'login',
|
icon: 'login',
|
||||||
text: this.$strings.ButtonSwitchServerUser,
|
text: this.$strings.ButtonSwitchServerUser,
|
||||||
@@ -151,6 +160,10 @@ export default {
|
|||||||
if (action === 'logout') {
|
if (action === 'logout') {
|
||||||
await this.logout()
|
await this.logout()
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
|
} else if (action === 'openWebClient') {
|
||||||
|
this.show = false
|
||||||
|
let path = `/library/${this.$store.state.libraries.currentLibraryId}`
|
||||||
|
await this.$store.dispatch('user/openWebClient', path)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clickBackground() {
|
clickBackground() {
|
||||||
@@ -158,6 +171,10 @@ export default {
|
|||||||
},
|
},
|
||||||
async logout() {
|
async logout() {
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
|
if (this.$store.getters['getIsPlayerOpen']) {
|
||||||
|
this.$eventBus.$emit('close-stream')
|
||||||
|
}
|
||||||
|
|
||||||
await this.$nativeHttp.post('/logout').catch((error) => {
|
await this.$nativeHttp.post('/logout').catch((error) => {
|
||||||
console.error('Failed to logout', error)
|
console.error('Failed to logout', error)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default {
|
|||||||
return this.page
|
return this.page
|
||||||
},
|
},
|
||||||
hasFilter() {
|
hasFilter() {
|
||||||
if (this.page === 'series' || this.page === 'collections') return false
|
if (this.page === 'series' || this.page === 'collections' || this.page === 'playlists') return false
|
||||||
return this.filterBy !== 'all'
|
return this.filterBy !== 'all'
|
||||||
},
|
},
|
||||||
orderBy() {
|
orderBy() {
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
|
|
||||||
<!-- Alternative bookshelf title/author/sort -->
|
<!-- Alternative bookshelf title/author/sort -->
|
||||||
<div v-if="isAltViewEnabled" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
<div v-if="isAltViewEnabled" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
<div :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }" class="flex items-center">
|
||||||
{{ displayTitle }}
|
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||||
</p>
|
{{ displayTitle }}
|
||||||
|
</p>
|
||||||
|
<widgets-explicit-indicator v-if="isExplicit" />
|
||||||
|
</div>
|
||||||
<p class="truncate text-fg-muted" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
<p class="truncate text-fg-muted" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
||||||
<p v-if="displaySortLine" class="truncate text-fg-muted" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
<p v-if="displaySortLine" class="truncate text-fg-muted" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,7 +313,7 @@ export default {
|
|||||||
return !!this.userProgress?.isFinished
|
return !!this.userProgress?.isFinished
|
||||||
},
|
},
|
||||||
showError() {
|
showError() {
|
||||||
return this.numMissingParts || this.isMissing || this.isInvalid
|
return this.isMissing || this.isInvalid
|
||||||
},
|
},
|
||||||
localLibraryItemId() {
|
localLibraryItemId() {
|
||||||
if (this.isLocal) return this.libraryItemId
|
if (this.isLocal) return this.libraryItemId
|
||||||
@@ -341,9 +344,8 @@ export default {
|
|||||||
isInvalid() {
|
isInvalid() {
|
||||||
return this._libraryItem.isInvalid
|
return this._libraryItem.isInvalid
|
||||||
},
|
},
|
||||||
numMissingParts() {
|
isExplicit() {
|
||||||
if (this.isPodcast) return 0
|
return !!this.mediaMetadata.explicit
|
||||||
return this.media.numMissingParts
|
|
||||||
},
|
},
|
||||||
overlayWrapperClasslist() {
|
overlayWrapperClasslist() {
|
||||||
var classes = []
|
var classes = []
|
||||||
@@ -366,9 +368,6 @@ export default {
|
|||||||
userCanDownload() {
|
userCanDownload() {
|
||||||
return this.store.getters['user/getUserCanDownload']
|
return this.store.getters['user/getUserCanDownload']
|
||||||
},
|
},
|
||||||
userIsRoot() {
|
|
||||||
return this.store.getters['user/getIsRoot']
|
|
||||||
},
|
|
||||||
titleFontSize() {
|
titleFontSize() {
|
||||||
return 0.75 * this.sizeMultiplier
|
return 0.75 * this.sizeMultiplier
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export default {
|
|||||||
return !!this.userProgress?.isFinished
|
return !!this.userProgress?.isFinished
|
||||||
},
|
},
|
||||||
showError() {
|
showError() {
|
||||||
return this.numMissingParts || this.isMissing || this.isInvalid
|
return this.isMissing || this.isInvalid
|
||||||
},
|
},
|
||||||
isStreaming() {
|
isStreaming() {
|
||||||
return this.store.getters['getlibraryItemIdStreaming'] === this.libraryItemId
|
return this.store.getters['getlibraryItemIdStreaming'] === this.libraryItemId
|
||||||
@@ -235,10 +235,6 @@ export default {
|
|||||||
isInvalid() {
|
isInvalid() {
|
||||||
return this._libraryItem.isInvalid
|
return this._libraryItem.isInvalid
|
||||||
},
|
},
|
||||||
numMissingParts() {
|
|
||||||
if (this.isPodcast) return 0
|
|
||||||
return this.media.numMissingParts
|
|
||||||
},
|
|
||||||
store() {
|
store() {
|
||||||
return this.$store || this.$nuxt.$store
|
return this.$store || this.$nuxt.$store
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<ui-text-input v-model="password" type="password" :disabled="processing" :placeholder="$strings.LabelPassword" class="w-full mb-2 text-lg" />
|
<ui-text-input v-model="password" type="password" :disabled="processing" :placeholder="$strings.LabelPassword" class="w-full mb-2 text-lg" />
|
||||||
|
|
||||||
<div class="flex items-center pt-2">
|
<div class="flex items-center pt-2">
|
||||||
<ui-icon-btn v-if="serverConfig.id" small bg-color="error" icon="delete" @click="removeServerConfigClick" />
|
<ui-icon-btn v-if="serverConfig.id" small bg-color="error" icon="delete" type="button" @click="removeServerConfigClick" />
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn :disabled="processing || !networkConnected" type="submit" class="mt-1 h-10">{{ networkConnected ? $strings.ButtonSubmit : $strings.MessageNoNetworkConnection }}</ui-btn>
|
<ui-btn :disabled="processing || !networkConnected" type="submit" class="mt-1 h-10">{{ networkConnected ? $strings.ButtonSubmit : $strings.MessageNoNetworkConnection }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="material-icons px-2" @click="showSortModal = true">sort</span>
|
<span class="material-icons px-2" @click="showSortModal = true">sort</span>
|
||||||
</template>
|
</template>
|
||||||
|
<span v-if="seriesBookPage" class="material-icons px-2" @click="downloadSeries">download</span>
|
||||||
<span v-if="(page == 'library' && isBookLibrary) || seriesBookPage" class="material-icons px-2" @click="showMoreMenuDialog = true">more_vert</span>
|
<span v-if="(page == 'library' && isBookLibrary) || seriesBookPage" class="material-icons px-2" @click="showMoreMenuDialog = true">more_vert</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,14 +65,14 @@ export default {
|
|||||||
return this.$route.query || {}
|
return this.$route.query || {}
|
||||||
},
|
},
|
||||||
entityTitle() {
|
entityTitle() {
|
||||||
if (this.isPodcast) return this.$strings.LabelPodcasts
|
if (this.page === 'library') {
|
||||||
if (this.page === 'library') return this.$strings.LabelBooks
|
return this.isPodcast ? this.$strings.LabelPodcasts : this.$strings.LabelBooks
|
||||||
else if (this.page === 'series') {
|
} else if (this.page === 'playlists') {
|
||||||
|
return this.$strings.ButtonPlaylists
|
||||||
|
} else if (this.page === 'series') {
|
||||||
return this.$strings.LabelSeries
|
return this.$strings.LabelSeries
|
||||||
} else if (this.page === 'collections') {
|
} else if (this.page === 'collections') {
|
||||||
return this.$strings.ButtonCollections
|
return this.$strings.ButtonCollections
|
||||||
} else if (this.page === 'playlists') {
|
|
||||||
return this.$strings.ButtonPlaylists
|
|
||||||
} else if (this.page === 'authors') {
|
} else if (this.page === 'authors') {
|
||||||
return this.$strings.LabelAuthors
|
return this.$strings.LabelAuthors
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,10 @@ export default {
|
|||||||
async changeView() {
|
async changeView() {
|
||||||
this.bookshelfListView = !this.bookshelfListView
|
this.bookshelfListView = !this.bookshelfListView
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
|
},
|
||||||
|
downloadSeries() {
|
||||||
|
console.log('Download Series click')
|
||||||
|
this.$eventBus.$emit('download-series-click')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export default {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
sublistItems() {
|
sublistItems() {
|
||||||
return (this[this.sublist] || []).map((item) => {
|
const sublistItems = (this[this.sublist] || []).map((item) => {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
return {
|
return {
|
||||||
text: item,
|
text: item,
|
||||||
@@ -223,6 +223,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (this.sublist === 'series') {
|
||||||
|
sublistItems.unshift({
|
||||||
|
text: this.$strings.MessageNoSeries,
|
||||||
|
value: this.$encode('no-series')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sublistItems
|
||||||
},
|
},
|
||||||
filterData() {
|
filterData() {
|
||||||
return this.$store.state.libraries.filterData || {}
|
return this.$store.state.libraries.filterData || {}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => null
|
default: () => null
|
||||||
},
|
},
|
||||||
|
playlist: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
},
|
||||||
hideRssFeedOption: Boolean
|
hideRssFeedOption: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -92,6 +96,15 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If on playlist page show remove from playlist option
|
||||||
|
if (this.playlist) {
|
||||||
|
items.push({
|
||||||
|
text: this.$strings.LabelRemoveFromPlaylist,
|
||||||
|
value: 'removeFromPlaylist',
|
||||||
|
icon: 'playlist_remove'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (this.showRSSFeedOption) {
|
if (this.showRSSFeedOption) {
|
||||||
items.push({
|
items.push({
|
||||||
text: this.rssFeed ? this.$strings.HeaderRSSFeed : this.$strings.HeaderOpenRSSFeed,
|
text: this.rssFeed ? this.$strings.HeaderRSSFeed : this.$strings.HeaderOpenRSSFeed,
|
||||||
@@ -122,6 +135,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isConnectedToServer) {
|
||||||
|
items.push({
|
||||||
|
text: this.$strings.ButtonGoToWebClient,
|
||||||
|
value: 'openWebClient',
|
||||||
|
icon: 'language'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.episode) {
|
if (!this.episode) {
|
||||||
items.push({
|
items.push({
|
||||||
text: this.$strings.LabelMoreInfo,
|
text: this.$strings.LabelMoreInfo,
|
||||||
@@ -255,6 +276,8 @@ export default {
|
|||||||
} else if (action === 'playlist') {
|
} else if (action === 'playlist') {
|
||||||
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode: this.episode }])
|
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode: this.episode }])
|
||||||
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
|
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
|
||||||
|
} else if (action === 'removeFromPlaylist') {
|
||||||
|
this.removeFromPlaylistClick()
|
||||||
} else if (action === 'markFinished') {
|
} else if (action === 'markFinished') {
|
||||||
if (this.episode) this.toggleEpisodeFinished()
|
if (this.episode) this.toggleEpisodeFinished()
|
||||||
else this.toggleFinished()
|
else this.toggleFinished()
|
||||||
@@ -270,6 +293,8 @@ export default {
|
|||||||
this.clickRSSFeed()
|
this.clickRSSFeed()
|
||||||
} else if (action === 'sendEbook') {
|
} else if (action === 'sendEbook') {
|
||||||
this.showSendEbookDevicesModal = true
|
this.showSendEbookDevicesModal = true
|
||||||
|
} else if (action === 'openWebClient') {
|
||||||
|
this.$store.dispatch('user/openWebClient', `/item/${this.serverLibraryItemId}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async toggleFinished() {
|
async toggleFinished() {
|
||||||
@@ -456,6 +481,29 @@ export default {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.$emit('update:processing', false)
|
this.$emit('update:processing', false)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
removeFromPlaylistClick() {
|
||||||
|
if (!this.playlist) {
|
||||||
|
this.$toast.error('Invalid: No Playlist')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('update:processing', true)
|
||||||
|
let url = `/api/playlists/${this.playlist.id}/item/${this.serverLibraryItemId}`
|
||||||
|
if (this.serverEpisodeId) url += `/${this.serverEpisodeId}`
|
||||||
|
this.$nativeHttp
|
||||||
|
.delete(url)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Item removed from playlist')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const errorMsg = error.response?.data || 'Unknown error'
|
||||||
|
console.error('Failed to remove item from playlist', error)
|
||||||
|
this.$toast.error('Failed to remove from playlist: ' + errorMsg)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.$emit('update:processing', false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ export default {
|
|||||||
{
|
{
|
||||||
text: this.$strings.LabelFileModified,
|
text: this.$strings.LabelFileModified,
|
||||||
value: 'mtimeMs'
|
value: 'mtimeMs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelRandomly,
|
||||||
|
value: 'random'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
podcastItems: [
|
podcastItems: [
|
||||||
@@ -89,6 +93,10 @@ export default {
|
|||||||
{
|
{
|
||||||
text: this.$strings.LabelFileModified,
|
text: this.$strings.LabelFileModified,
|
||||||
value: 'mtimeMs'
|
value: 'mtimeMs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelRandomly,
|
||||||
|
value: 'random'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
episodeItems: [
|
episodeItems: [
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
rates() {
|
rates() {
|
||||||
return [0.5, 1, 1.2, 1.5, 2, 3]
|
return [0.5, 1, 1.2, 1.5, 1.7, 2, 3]
|
||||||
},
|
},
|
||||||
canIncrement() {
|
canIncrement() {
|
||||||
return this.playbackRate + 0.1 <= this.MAX_SPEED
|
return this.playbackRate + 0.1 <= this.MAX_SPEED
|
||||||
|
|||||||
@@ -6,13 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div
|
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||||
class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center"
|
|
||||||
@click="
|
|
||||||
show = false
|
|
||||||
manualTimerModal = false
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-border" style="max-height: 75%" @click.stop>
|
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-border" style="max-height: 75%" @click.stop>
|
||||||
<div v-if="manualTimerModal" class="p-4">
|
<div v-if="manualTimerModal" class="p-4">
|
||||||
<div class="flex mb-4" @click="manualTimerModal = false">
|
<div class="flex mb-4" @click="manualTimerModal = false">
|
||||||
@@ -71,7 +65,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
manualTimerModal: null,
|
manualTimerModal: false,
|
||||||
manualTimeoutMin: 1
|
manualTimeoutMin: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -81,6 +75,9 @@ export default {
|
|||||||
return this.value
|
return this.value
|
||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
|
if (!val) {
|
||||||
|
this.manualTimerModal = false
|
||||||
|
}
|
||||||
this.$emit('input', val)
|
this.$emit('input', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
parseFilenames(filenames) {
|
parseFilenames(filenames) {
|
||||||
const acceptableImages = ['.jpeg', '.jpg', '.png']
|
const acceptableImages = ['.jpeg', '.jpg', '.png', '.webp']
|
||||||
var imageFiles = filenames.filter((f) => {
|
var imageFiles = filenames.filter((f) => {
|
||||||
return acceptableImages.includes((Path.extname(f) || '').toLowerCase())
|
return acceptableImages.includes((Path.extname(f) || '').toLowerCase())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,16 +34,18 @@ export default {
|
|||||||
currentLocationNum: 0,
|
currentLocationNum: 0,
|
||||||
currentLocationCfi: null,
|
currentLocationCfi: null,
|
||||||
inittingDisplay: true,
|
inittingDisplay: true,
|
||||||
|
isRefreshingUI: false,
|
||||||
ereaderSettings: {
|
ereaderSettings: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
fontScale: 100,
|
fontScale: 100,
|
||||||
lineSpacing: 115
|
lineSpacing: 115,
|
||||||
|
textStroke: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isPlayerOpen() {
|
isPlayerOpen() {
|
||||||
this.updateHeight()
|
this.refreshUI()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -112,7 +114,8 @@ export default {
|
|||||||
'*': {
|
'*': {
|
||||||
color: `${fontColor}!important`,
|
color: `${fontColor}!important`,
|
||||||
'background-color': `${backgroundColor}!important`,
|
'background-color': `${backgroundColor}!important`,
|
||||||
'line-height': this.ereaderSettings.lineSpacing + '%!important'
|
'line-height': this.ereaderSettings.lineSpacing + '%!important',
|
||||||
|
'-webkit-text-stroke': this.ereaderSettings.textStroke/100 + 'px ' + fontColor + '!important'
|
||||||
},
|
},
|
||||||
a: {
|
a: {
|
||||||
color: `${fontColor}!important`
|
color: `${fontColor}!important`
|
||||||
@@ -135,11 +138,6 @@ export default {
|
|||||||
goToChapter(href) {
|
goToChapter(href) {
|
||||||
return this.rendition?.display(href)
|
return this.rendition?.display(href)
|
||||||
},
|
},
|
||||||
updateHeight() {
|
|
||||||
if (this.rendition && this.rendition.resize) {
|
|
||||||
this.rendition.resize(window.innerWidth, window.innerHeight - this.readerHeightOffset)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prev() {
|
prev() {
|
||||||
if (this.rendition) {
|
if (this.rendition) {
|
||||||
this.rendition.prev()
|
this.rendition.prev()
|
||||||
@@ -382,14 +380,54 @@ export default {
|
|||||||
this.rendition.getContents().forEach((c) => {
|
this.rendition.getContents().forEach((c) => {
|
||||||
c.addStylesheetRules(this.themeRules)
|
c.addStylesheetRules(this.themeRules)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
async screenOrientationChange() {
|
||||||
|
if (this.isRefreshingUI) return
|
||||||
|
this.isRefreshingUI = true
|
||||||
|
const windowWidth = window.innerWidth
|
||||||
|
this.refreshUI()
|
||||||
|
|
||||||
|
// Window width does not always change right away. Wait up to 250ms for a change.
|
||||||
|
// iPhone 10 on iOS 16 took between 100 - 200ms to update when going from portrait to landscape
|
||||||
|
// but landscape to portrait was immediate
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||||
|
if (window.innerWidth !== windowWidth) {
|
||||||
|
this.refreshUI()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRefreshingUI = false
|
||||||
|
},
|
||||||
|
refreshUI() {
|
||||||
|
if (this.rendition?.resize) {
|
||||||
|
this.rendition.resize(window.innerWidth, window.innerHeight - this.readerHeightOffset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
|
||||||
this.book?.destroy()
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initEpub()
|
this.initEpub()
|
||||||
}
|
|
||||||
|
if (screen.orientation) {
|
||||||
|
// Not available on ios
|
||||||
|
screen.orientation.addEventListener('change', this.screenOrientationChange)
|
||||||
|
} else {
|
||||||
|
document.addEventListener('orientationchange', this.screenOrientationChange)
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', this.screenOrientationChange)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.book?.destroy()
|
||||||
|
|
||||||
|
if (screen.orientation) {
|
||||||
|
// Not available on ios
|
||||||
|
screen.orientation.removeEventListener('change', this.screenOrientationChange)
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('orientationchange', this.screenOrientationChange)
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', this.screenOrientationChange)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="h-full flex items-center justify-center">
|
<div class="h-full flex items-center justify-center">
|
||||||
<div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="w-full h-full overflow-auto">
|
<div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="w-full h-full overflow-auto">
|
||||||
<div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div>
|
<div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div>
|
||||||
<pdf ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md bg-white" :src="pdfDocInitParams" :page="page" :rotate="rotate" @progress="loadedRatio = $event" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event" @loaded="loadedEvt"></pdf>
|
<pdf v-if="pdfDocInitParams" ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md bg-white" :src="pdfDocInitParams" :page="page" :rotate="rotate" @progress="loadedRatio = $event" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event" @loaded="loadedEvt"></pdf>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -48,7 +48,8 @@ export default {
|
|||||||
page: 1,
|
page: 1,
|
||||||
numPages: 0,
|
numPages: 0,
|
||||||
windowWidth: 0,
|
windowWidth: 0,
|
||||||
windowHeight: 0
|
windowHeight: 0,
|
||||||
|
pdfDocInitParams: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -106,14 +107,6 @@ export default {
|
|||||||
if (!this.userItemProgress?.ebookLocation || isNaN(this.userItemProgress.ebookLocation)) return 0
|
if (!this.userItemProgress?.ebookLocation || isNaN(this.userItemProgress.ebookLocation)) return 0
|
||||||
return Number(this.userItemProgress.ebookLocation)
|
return Number(this.userItemProgress.ebookLocation)
|
||||||
},
|
},
|
||||||
pdfDocInitParams() {
|
|
||||||
return {
|
|
||||||
url: this.url,
|
|
||||||
httpHeaders: {
|
|
||||||
Authorization: `Bearer ${this.userToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isPlayerOpen() {
|
isPlayerOpen() {
|
||||||
return this.$store.getters['getIsPlayerOpen']
|
return this.$store.getters['getIsPlayerOpen']
|
||||||
}
|
}
|
||||||
@@ -157,6 +150,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
numPagesLoaded(e) {
|
numPagesLoaded(e) {
|
||||||
|
if (!e) return
|
||||||
|
|
||||||
this.numPages = e
|
this.numPages = e
|
||||||
},
|
},
|
||||||
prev() {
|
prev() {
|
||||||
@@ -175,6 +170,14 @@ export default {
|
|||||||
screenOrientationChange() {
|
screenOrientationChange() {
|
||||||
this.windowWidth = window.innerWidth
|
this.windowWidth = window.innerWidth
|
||||||
this.windowHeight = window.innerHeight
|
this.windowHeight = window.innerHeight
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.pdfDocInitParams = {
|
||||||
|
url: this.url,
|
||||||
|
httpHeaders: {
|
||||||
|
Authorization: `Bearer ${this.userToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -188,6 +191,8 @@ export default {
|
|||||||
document.addEventListener('orientationchange', this.screenOrientationChange)
|
document.addEventListener('orientationchange', this.screenOrientationChange)
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', this.screenOrientationChange)
|
window.addEventListener('resize', this.screenOrientationChange)
|
||||||
|
|
||||||
|
this.init()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (screen.orientation) {
|
if (screen.orientation) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<!-- ereader settings modal -->
|
<!-- ereader settings modal -->
|
||||||
<modals-fullscreen-modal v-model="showSettingsModal" :theme="ereaderTheme" half-screen>
|
<modals-fullscreen-modal v-model="showSettingsModal" :theme="ereaderTheme" half-screen>
|
||||||
<div style="box-shadow: 0px -8px 8px #11111155">
|
<div style="box-shadow: 0px -8px 8px #11111155">
|
||||||
<div class="flex items-end justify-between h-20 px-4 pb-2 mb-6">
|
<div class="flex items-end justify-between h-14 px-4 pb-2 mb-6">
|
||||||
<h1 class="text-lg">{{ $strings.HeaderEreaderSettings }}</h1>
|
<h1 class="text-lg">{{ $strings.HeaderEreaderSettings }}</h1>
|
||||||
<button class="flex" @click="showSettingsModal = false">
|
<button class="flex" @click="showSettingsModal = false">
|
||||||
<span class="material-icons">close</span>
|
<span class="material-icons">close</span>
|
||||||
@@ -82,12 +82,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<ui-range-input v-model="ereaderSettings.lineSpacing" :min="100" :max="300" :step="5" input-width="180px" @input="settingsUpdated" />
|
<ui-range-input v-model="ereaderSettings.lineSpacing" :min="100" :max="300" :step="5" input-width="180px" @input="settingsUpdated" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-32">
|
||||||
|
<p class="text-base">{{ $strings.LabelFontBoldness }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-range-input v-model="ereaderSettings.textStroke" :min="0" :max="300" :step="5" input-width="180px" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<p class="text-base">{{ $strings.LabelLayout }}:</p>
|
<p class="text-base">{{ $strings.LabelLayout }}:</p>
|
||||||
</div>
|
</div>
|
||||||
<ui-toggle-btns v-model="ereaderSettings.spread" :items="spreadItems" @input="settingsUpdated" />
|
<ui-toggle-btns v-model="ereaderSettings.spread" :items="spreadItems" @input="settingsUpdated" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-32">
|
||||||
|
<p class="text-base">{{ $strings.LabelNavigateWithVolume }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-toggle-btns v-model="ereaderSettings.navigateWithVolume" :items="navigateWithVolumeItems" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,6 +109,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Capacitor } from '@capacitor/core'
|
import { Capacitor } from '@capacitor/core'
|
||||||
|
import { VolumeButtons } from '@capacitor-community/volume-buttons'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -112,11 +125,14 @@ export default {
|
|||||||
showSettingsModal: false,
|
showSettingsModal: false,
|
||||||
comicHasMetadata: false,
|
comicHasMetadata: false,
|
||||||
chapters: [],
|
chapters: [],
|
||||||
|
isInittingWatchVolume: false,
|
||||||
ereaderSettings: {
|
ereaderSettings: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
fontScale: 100,
|
fontScale: 100,
|
||||||
lineSpacing: 115,
|
lineSpacing: 115,
|
||||||
spread: 'auto'
|
spread: 'auto',
|
||||||
|
textStroke: 0,
|
||||||
|
navigateWithVolume: 'enabled'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -127,13 +143,17 @@ export default {
|
|||||||
this.comicHasMetadata = false
|
this.comicHasMetadata = false
|
||||||
this.registerListeners()
|
this.registerListeners()
|
||||||
this.hideToolbar()
|
this.hideToolbar()
|
||||||
|
|
||||||
console.log('showReader for ebookFile', JSON.stringify(this.ebookFile))
|
|
||||||
} else {
|
} else {
|
||||||
this.unregisterListeners()
|
this.unregisterListeners()
|
||||||
this.$showHideStatusBar(true)
|
this.$showHideStatusBar(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isPlayerOpen(newVal, oldVal) {
|
||||||
|
// Closed player
|
||||||
|
if (!newVal && oldVal) {
|
||||||
|
this.initWatchVolume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -173,6 +193,22 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
navigateWithVolumeItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelNavigateWithVolumeEnabled,
|
||||||
|
value: 'enabled'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelNavigateWithVolumeMirrored,
|
||||||
|
value: 'mirrored'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelNavigateWithVolumeDisabled,
|
||||||
|
value: 'none'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
themeItems() {
|
themeItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -257,6 +293,8 @@ export default {
|
|||||||
settingsUpdated() {
|
settingsUpdated() {
|
||||||
this.$refs.readerComponent?.updateSettings?.(this.ereaderSettings)
|
this.$refs.readerComponent?.updateSettings?.(this.ereaderSettings)
|
||||||
localStorage.setItem('ereaderSettings', JSON.stringify(this.ereaderSettings))
|
localStorage.setItem('ereaderSettings', JSON.stringify(this.ereaderSettings))
|
||||||
|
|
||||||
|
this.initWatchVolume()
|
||||||
},
|
},
|
||||||
goToChapter(href) {
|
goToChapter(href) {
|
||||||
this.showTOCModal = false
|
this.showTOCModal = false
|
||||||
@@ -369,27 +407,67 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const settings = localStorage.getItem('ereaderSettings')
|
const settings = localStorage.getItem('ereaderSettings')
|
||||||
if (settings) {
|
if (settings) {
|
||||||
const ereaderSettings = JSON.parse(settings)
|
const _ereaderSettings = JSON.parse(settings)
|
||||||
if (!ereaderSettings.spread) {
|
for (const key in this.ereaderSettings) {
|
||||||
// Added in 0.9.71
|
if (_ereaderSettings[key] !== undefined) {
|
||||||
ereaderSettings.spread = 'auto'
|
this.ereaderSettings[key] = _ereaderSettings[key]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.ereaderSettings = ereaderSettings
|
|
||||||
this.settingsUpdated()
|
this.settingsUpdated()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load ereader settings', error)
|
console.error('Failed to load ereader settings', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async initWatchVolume() {
|
||||||
|
if (this.isInittingWatchVolume || !this.isEpub) return
|
||||||
|
this.isInittingWatchVolume = true
|
||||||
|
const isWatching = await VolumeButtons.isWatching()
|
||||||
|
|
||||||
|
if (this.ereaderSettings.navigateWithVolume !== 'none' && !this.isPlayerOpen) {
|
||||||
|
if (!isWatching.value) {
|
||||||
|
const options = {
|
||||||
|
disableSystemVolumeHandler: true,
|
||||||
|
suppressVolumeIndicator: true
|
||||||
|
}
|
||||||
|
await VolumeButtons.watchVolume(options, this.volumePressed)
|
||||||
|
}
|
||||||
|
} else if (isWatching.value) {
|
||||||
|
await VolumeButtons.clearWatch().catch((error) => {
|
||||||
|
console.error('Failed to clear volume watch', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInittingWatchVolume = false
|
||||||
|
},
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
this.$eventBus.$on('close-ebook', this.closeEvt)
|
this.$eventBus.$on('close-ebook', this.closeEvt)
|
||||||
document.body.addEventListener('touchstart', this.touchstart)
|
document.body.addEventListener('touchstart', this.touchstart)
|
||||||
document.body.addEventListener('touchend', this.touchend)
|
document.body.addEventListener('touchend', this.touchend)
|
||||||
|
this.initWatchVolume()
|
||||||
},
|
},
|
||||||
unregisterListeners() {
|
unregisterListeners() {
|
||||||
this.$eventBus.$on('close-ebook', this.closeEvt)
|
this.$eventBus.$on('close-ebook', this.closeEvt)
|
||||||
document.body.removeEventListener('touchstart', this.touchstart)
|
document.body.removeEventListener('touchstart', this.touchstart)
|
||||||
document.body.removeEventListener('touchend', this.touchend)
|
document.body.removeEventListener('touchend', this.touchend)
|
||||||
|
VolumeButtons.clearWatch().catch((error) => {
|
||||||
|
console.error('Failed to clear volume watch', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
volumePressed(e) {
|
||||||
|
if (this.ereaderSettings.navigateWithVolume == 'enabled') {
|
||||||
|
if (e.direction == 'up') {
|
||||||
|
this.prev()
|
||||||
|
} else {
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
} else if (this.ereaderSettings.navigateWithVolume == 'mirrored') {
|
||||||
|
if (e.direction == 'down') {
|
||||||
|
this.prev()
|
||||||
|
} else {
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
||||||
|
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@@ -73,6 +74,7 @@ export default {
|
|||||||
},
|
},
|
||||||
isLocal: Boolean
|
isLocal: Boolean
|
||||||
},
|
},
|
||||||
|
mixins: [cellularPermissionHelpers],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isProcessingReadUpdate: false,
|
isProcessingReadUpdate: false,
|
||||||
@@ -180,6 +182,9 @@ export default {
|
|||||||
async downloadClick() {
|
async downloadClick() {
|
||||||
if (this.downloadItem || this.startingDownload) return
|
if (this.downloadItem || this.startingDownload) return
|
||||||
|
|
||||||
|
const hasPermission = await this.checkCellularPermission('download')
|
||||||
|
if (!hasPermission) return
|
||||||
|
|
||||||
this.startingDownload = true
|
this.startingDownload = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.startingDownload = false
|
this.startingDownload = false
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ export default {
|
|||||||
isAdminOrUp() {
|
isAdminOrUp() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
networkConnected() {
|
socketConnected() {
|
||||||
return this.$store.state.networkConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
libraryItemId() {
|
libraryItemId() {
|
||||||
return this.libraryItem?.id || null
|
return this.libraryItem?.id || null
|
||||||
@@ -233,7 +233,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async searchEpisodes() {
|
async searchEpisodes() {
|
||||||
if (!this.networkConnected) {
|
if (!this.socketConnected) {
|
||||||
return this.$toast.error(this.$strings.MessageNoNetworkConnection)
|
return this.$toast.error(this.$strings.MessageNoNetworkConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,11 @@
|
|||||||
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId)" :width="40" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" class="md:hidden" />
|
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId)" :width="40" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" class="md:hidden" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-2">
|
<div class="flex-grow px-2">
|
||||||
<div class="-mt-0.5 mb-0.5" @click.stop>
|
<div class="flex items-center">
|
||||||
<nuxt-link :to="`/item/${libraryItemId}`" class="text-sm text-fg underline">{{ podcast.metadata.title }}</nuxt-link>
|
<div class="-mt-0.5 mb-0.5" @click.stop>
|
||||||
|
<nuxt-link :to="`/item/${libraryItemId}`" class="text-sm text-fg underline">{{ podcast.metadata.title }}</nuxt-link>
|
||||||
|
</div>
|
||||||
|
<widgets-explicit-indicator v-if="podcast.metadata.explicit" />
|
||||||
</div>
|
</div>
|
||||||
<p v-if="publishedAt" class="text-xs text-fg-muted">{{ $dateDistanceFromNow(publishedAt) }}</p>
|
<p v-if="publishedAt" class="text-xs text-fg-muted">{{ $dateDistanceFromNow(publishedAt) }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,6 +61,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
||||||
|
import CellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@@ -80,6 +84,7 @@ export default {
|
|||||||
processing: false
|
processing: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [CellularPermissionHelpers],
|
||||||
computed: {
|
computed: {
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
@@ -184,6 +189,10 @@ export default {
|
|||||||
},
|
},
|
||||||
async downloadClick() {
|
async downloadClick() {
|
||||||
if (this.downloadItem || this.pendingDownload) return
|
if (this.downloadItem || this.pendingDownload) return
|
||||||
|
|
||||||
|
const hasPermission = await this.checkCellularPermission('download')
|
||||||
|
if (!hasPermission) return
|
||||||
|
|
||||||
this.pendingDownload = true
|
this.pendingDownload = true
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
if (this.isIos) {
|
if (this.isIos) {
|
||||||
@@ -259,7 +268,6 @@ export default {
|
|||||||
|
|
||||||
if (this.localEpisode && this.localLibraryItemId) {
|
if (this.localEpisode && this.localLibraryItemId) {
|
||||||
console.log('Play local episode', this.localEpisode.id, this.localLibraryItemId)
|
console.log('Play local episode', this.localEpisode.id, this.localLibraryItemId)
|
||||||
|
|
||||||
this.$eventBus.$emit('play-item', {
|
this.$eventBus.$emit('play-item', {
|
||||||
libraryItemId: this.localLibraryItemId,
|
libraryItemId: this.localLibraryItemId,
|
||||||
episodeId: this.localEpisode.id,
|
episodeId: this.localEpisode.id,
|
||||||
@@ -282,7 +290,11 @@ export default {
|
|||||||
const isFinished = !this.userIsFinished
|
const isFinished = !this.userIsFinished
|
||||||
const localLibraryItemId = this.isLocal ? this.libraryItemId : this.localLibraryItemId
|
const localLibraryItemId = this.isLocal ? this.libraryItemId : this.localLibraryItemId
|
||||||
const localEpisodeId = this.isLocal ? this.episode.id : this.localEpisode.id
|
const localEpisodeId = this.isLocal ? this.episode.id : this.localEpisode.id
|
||||||
const payload = await this.$db.updateLocalMediaProgressFinished({ localLibraryItemId, localEpisodeId, isFinished })
|
const payload = await this.$db.updateLocalMediaProgressFinished({
|
||||||
|
localLibraryItemId,
|
||||||
|
localEpisodeId,
|
||||||
|
isFinished
|
||||||
|
})
|
||||||
console.log('toggleFinished payload', JSON.stringify(payload))
|
console.log('toggleFinished payload', JSON.stringify(payload))
|
||||||
if (payload?.error) {
|
if (payload?.error) {
|
||||||
this.$toast.error(payload?.error || 'Unknown error')
|
this.$toast.error(payload?.error || 'Unknown error')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" @mousedown.prevent :disabled="disabled || loading" :class="className" @click="clickBtn">
|
<button class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :disabled="disabled || loading" :class="className" :type="type" @mousedown.prevent @click="clickBtn">
|
||||||
<div v-if="loading" class="text-fg absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
|
<div v-if="loading" class="text-fg absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
|
||||||
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
|
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
icon: String,
|
icon: String,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'button'
|
||||||
|
},
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
bgColor: {
|
bgColor: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
|
||||||
|
<path
|
||||||
|
fill="white"
|
||||||
|
d="M 89.00,40.12
|
||||||
|
C 89.00,40.12 127.00,40.12 127.00,40.12
|
||||||
|
127.00,40.12 198.00,40.12 198.00,40.12
|
||||||
|
198.00,40.12 416.00,40.12 416.00,40.12
|
||||||
|
446.58,40.05 472.95,66.42 473.00,97.00
|
||||||
|
473.00,97.00 473.00,303.00 473.00,303.00
|
||||||
|
473.00,303.00 473.00,418.00 473.00,418.00
|
||||||
|
472.65,447.55 445.06,472.95 416.00,473.00
|
||||||
|
416.00,473.00 210.00,473.00 210.00,473.00
|
||||||
|
210.00,473.00 95.00,473.00 95.00,473.00
|
||||||
|
65.45,472.65 40.05,445.06 40.00,416.00
|
||||||
|
40.00,416.00 40.00,136.00 40.00,136.00
|
||||||
|
40.00,136.00 40.00,109.00 40.00,109.00
|
||||||
|
40.00,109.00 40.00,96.00 40.00,96.00
|
||||||
|
40.07,81.58 46.89,67.14 57.01,57.01
|
||||||
|
61.17,52.86 64.86,50.13 70.00,47.31
|
||||||
|
77.25,43.33 81.02,42.18 89.00,40.12 Z
|
||||||
|
M 337.00,121.00
|
||||||
|
C 337.00,121.00 175.00,121.00 175.00,121.00
|
||||||
|
175.00,121.00 175.00,392.00 175.00,392.00
|
||||||
|
175.00,392.00 337.00,392.00 337.00,392.00
|
||||||
|
337.00,392.00 337.00,349.00 337.00,349.00
|
||||||
|
337.00,349.00 226.00,349.00 226.00,349.00
|
||||||
|
226.00,349.00 226.00,274.00 226.00,274.00
|
||||||
|
226.00,274.00 332.00,274.00 332.00,274.00
|
||||||
|
332.00,274.00 332.00,232.00 332.00,232.00
|
||||||
|
332.00,232.00 226.00,232.00 226.00,232.00
|
||||||
|
226.00,232.00 226.00,164.00 226.00,164.00
|
||||||
|
226.00,164.00 337.00,164.00 337.00,164.00
|
||||||
|
337.00,164.00 337.00,121.00 337.00,121.00 Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -65,6 +65,9 @@
|
|||||||
E9E8814D28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */; };
|
E9E8814D28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */; };
|
||||||
E9E985F828B02D9400957F23 /* PlayerProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E985F728B02D9400957F23 /* PlayerProgress.swift */; };
|
E9E985F828B02D9400957F23 /* PlayerProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E985F728B02D9400957F23 /* PlayerProgress.swift */; };
|
||||||
E9FA07E328C82848005520B0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FA07E228C82848005520B0 /* Logger.swift */; };
|
E9FA07E328C82848005520B0 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FA07E228C82848005520B0 /* Logger.swift */; };
|
||||||
|
EACB38122BCCA1330060DA4A /* AudioPlayerRateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACB38112BCCA1330060DA4A /* AudioPlayerRateManager.swift */; };
|
||||||
|
EACB38142BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACB38132BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift */; };
|
||||||
|
EACB38162BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACB38152BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -142,6 +145,9 @@
|
|||||||
E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeUtilsTests.swift; sourceTree = "<group>"; };
|
E9E8814C28DA6B9000D750C1 /* PlayerTimeUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeUtilsTests.swift; sourceTree = "<group>"; };
|
||||||
E9E985F728B02D9400957F23 /* PlayerProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerProgress.swift; sourceTree = "<group>"; };
|
E9E985F728B02D9400957F23 /* PlayerProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerProgress.swift; sourceTree = "<group>"; };
|
||||||
E9FA07E228C82848005520B0 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
E9FA07E228C82848005520B0 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
|
||||||
|
EACB38112BCCA1330060DA4A /* AudioPlayerRateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioPlayerRateManager.swift; sourceTree = "<group>"; };
|
||||||
|
EACB38132BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyAudioPlayerRateManager.swift; sourceTree = "<group>"; };
|
||||||
|
EACB38152BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultedAudioPlayerRateManager.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -183,6 +189,9 @@
|
|||||||
3ABF6190280432610070250E /* player */ = {
|
3ABF6190280432610070250E /* player */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EACB38152BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift */,
|
||||||
|
EACB38132BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift */,
|
||||||
|
EACB38112BCCA1330060DA4A /* AudioPlayerRateManager.swift */,
|
||||||
E9E8814828DA641B00D750C1 /* util */,
|
E9E8814828DA641B00D750C1 /* util */,
|
||||||
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */,
|
3A200C1427D64D7E00CBF02E /* AudioPlayer.swift */,
|
||||||
E9DFCBFA28C28F4A00B36356 /* AudioPlayerSleepTimer.swift */,
|
E9DFCBFA28C28F4A00B36356 /* AudioPlayerSleepTimer.swift */,
|
||||||
@@ -521,6 +530,7 @@
|
|||||||
E9D5506228AC1CC900C746DD /* PlayerState.swift in Sources */,
|
E9D5506228AC1CC900C746DD /* PlayerState.swift in Sources */,
|
||||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
||||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||||
|
EACB38122BCCA1330060DA4A /* AudioPlayerRateManager.swift in Sources */,
|
||||||
E9FA07E328C82848005520B0 /* Logger.swift in Sources */,
|
E9FA07E328C82848005520B0 /* Logger.swift in Sources */,
|
||||||
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
|
3A90295F280968E700E1D427 /* PlaybackReport.swift in Sources */,
|
||||||
E9D5505A28AC1C4500C746DD /* Folder.swift in Sources */,
|
E9D5505A28AC1C4500C746DD /* Folder.swift in Sources */,
|
||||||
@@ -540,6 +550,7 @@
|
|||||||
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */,
|
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */,
|
||||||
E9D5507128AC1EC700C746DD /* DownloadItemPart.swift in Sources */,
|
E9D5507128AC1EC700C746DD /* DownloadItemPart.swift in Sources */,
|
||||||
4D66B956282EE951008272D4 /* AbsFileSystem.m in Sources */,
|
4D66B956282EE951008272D4 /* AbsFileSystem.m in Sources */,
|
||||||
|
EACB38142BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift in Sources */,
|
||||||
3AFCB5E827EA240D00ECCC05 /* NowPlayingInfo.swift in Sources */,
|
3AFCB5E827EA240D00ECCC05 /* NowPlayingInfo.swift in Sources */,
|
||||||
3AB34053280829BF0039308B /* Extensions.swift in Sources */,
|
3AB34053280829BF0039308B /* Extensions.swift in Sources */,
|
||||||
E9D5505828AC1C1A00C746DD /* Library.swift in Sources */,
|
E9D5505828AC1C1A00C746DD /* Library.swift in Sources */,
|
||||||
@@ -548,6 +559,7 @@
|
|||||||
4DABC04F2B0139CA000F6264 /* User.swift in Sources */,
|
4DABC04F2B0139CA000F6264 /* User.swift in Sources */,
|
||||||
4D66B952282EE822008272D4 /* AbsDownloader.m in Sources */,
|
4D66B952282EE822008272D4 /* AbsDownloader.m in Sources */,
|
||||||
E9D5506828AC1DC300C746DD /* LocalPodcastEpisode.swift in Sources */,
|
E9D5506828AC1DC300C746DD /* LocalPodcastEpisode.swift in Sources */,
|
||||||
|
EACB38162BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift in Sources */,
|
||||||
E9D5505228AC1B5D00C746DD /* Chapter.swift in Sources */,
|
E9D5505228AC1B5D00C746DD /* Chapter.swift in Sources */,
|
||||||
E9D5506028AC1CA900C746DD /* PlaybackMetadata.swift in Sources */,
|
E9D5506028AC1CA900C746DD /* PlaybackMetadata.swift in Sources */,
|
||||||
E9D5504828AC1A7A00C746DD /* MediaType.swift in Sources */,
|
E9D5504828AC1A7A00C746DD /* MediaType.swift in Sources */,
|
||||||
@@ -728,12 +740,12 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 28;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.71;
|
MARKETING_VERSION = 0.9.77;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -752,12 +764,12 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 28;
|
CURRENT_PROJECT_VERSION = 34;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.71;
|
MARKETING_VERSION = 0.9.77;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
|
|
||||||
let configuration = Realm.Configuration(
|
let configuration = Realm.Configuration(
|
||||||
schemaVersion: 16,
|
schemaVersion: 18,
|
||||||
migrationBlock: { [weak self] migration, oldSchemaVersion in
|
migrationBlock: { [weak self] migration, oldSchemaVersion in
|
||||||
if (oldSchemaVersion < 1) {
|
if (oldSchemaVersion < 1) {
|
||||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)")
|
self?.logger.log("Realm schema version was \(oldSchemaVersion)")
|
||||||
@@ -54,6 +54,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
newObject?["chapterTrack"] = false
|
newObject?["chapterTrack"] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldSchemaVersion < 17) {
|
||||||
|
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding downloadUsingCellular and streamingUsingCellular settings")
|
||||||
|
migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in
|
||||||
|
newObject?["downloadUsingCellular"] = "ALWAYS"
|
||||||
|
newObject?["streamingUsingCellular"] = "ALWAYS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,12 +8,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Capacitor
|
import Capacitor
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
import Network
|
||||||
|
|
||||||
@objc(AbsAudioPlayer)
|
@objc(AbsAudioPlayer)
|
||||||
public class AbsAudioPlayer: CAPPlugin {
|
public class AbsAudioPlayer: CAPPlugin {
|
||||||
private let logger = AppLogger(category: "AbsAudioPlayer")
|
private let logger = AppLogger(category: "AbsAudioPlayer")
|
||||||
|
|
||||||
private var initialPlayWhenReady = false
|
private var initialPlayWhenReady = false
|
||||||
|
private var monitor: NWPathMonitor?
|
||||||
|
private let queue = DispatchQueue.global(qos: .background)
|
||||||
|
|
||||||
override public func load() {
|
override public func load() {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
@@ -28,6 +31,11 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
self.bridge?.webView?.allowsBackForwardNavigationGestures = true;
|
self.bridge?.webView?.allowsBackForwardNavigationGestures = true;
|
||||||
self.bridge?.webView?.scrollView.alwaysBounceVertical = false;
|
self.bridge?.webView?.scrollView.alwaysBounceVertical = false;
|
||||||
|
|
||||||
|
setupNetworkMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
monitor?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func onReady(_ call: CAPPluginCall) {
|
@objc func onReady(_ call: CAPPluginCall) {
|
||||||
@@ -275,6 +283,25 @@ public class AbsAudioPlayer: CAPPlugin {
|
|||||||
@objc func sendPlaybackSession(session: [String: Any]) {
|
@objc func sendPlaybackSession(session: [String: Any]) {
|
||||||
self.notifyListeners("onPlaybackSession", data: session)
|
self.notifyListeners("onPlaybackSession", data: session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupNetworkMonitor() {
|
||||||
|
monitor = NWPathMonitor()
|
||||||
|
monitor?.pathUpdateHandler = { [weak self] path in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let isUnmetered = !path.isExpensive && !path.isConstrained
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.notifyNetworkMeteredChanged(isUnmetered: isUnmetered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
monitor?.start(queue: queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notifyNetworkMeteredChanged(isUnmetered: Bool) {
|
||||||
|
let data: [String: Any] = ["value": isUnmetered]
|
||||||
|
self.notifyListeners("onNetworkMeteredChanged", data: data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlayerError: String, Error {
|
enum PlayerError: String, Error {
|
||||||
|
|||||||
@@ -238,23 +238,31 @@ public class AbsDatabase: CAPPlugin {
|
|||||||
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
||||||
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
let disableAutoRewind = call.getBool("disableAutoRewind") ?? false
|
||||||
let enableAltView = call.getBool("enableAltView") ?? false
|
let enableAltView = call.getBool("enableAltView") ?? false
|
||||||
|
let allowSeekingOnMediaControls = call.getBool("allowSeekingOnMediaControls") ?? false
|
||||||
let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10
|
let jumpBackwardsTime = call.getInt("jumpBackwardsTime") ?? 10
|
||||||
let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10
|
let jumpForwardTime = call.getInt("jumpForwardTime") ?? 10
|
||||||
let lockOrientation = call.getString("lockOrientation") ?? "NONE"
|
let lockOrientation = call.getString("lockOrientation") ?? "NONE"
|
||||||
let hapticFeedback = call.getString("hapticFeedback") ?? "LIGHT"
|
let hapticFeedback = call.getString("hapticFeedback") ?? "LIGHT"
|
||||||
let languageCode = call.getString("languageCode") ?? "en-us"
|
let languageCode = call.getString("languageCode") ?? "en-us"
|
||||||
|
let downloadUsingCellular = call.getString("downloadUsingCellular") ?? "ALWAYS"
|
||||||
|
let streamingUsingCellular = call.getString("streamingUsingCellular") ?? "ALWAYS"
|
||||||
let settings = DeviceSettings()
|
let settings = DeviceSettings()
|
||||||
settings.disableAutoRewind = disableAutoRewind
|
settings.disableAutoRewind = disableAutoRewind
|
||||||
settings.enableAltView = enableAltView
|
settings.enableAltView = enableAltView
|
||||||
|
settings.allowSeekingOnMediaControls = allowSeekingOnMediaControls
|
||||||
settings.jumpBackwardsTime = jumpBackwardsTime
|
settings.jumpBackwardsTime = jumpBackwardsTime
|
||||||
settings.jumpForwardTime = jumpForwardTime
|
settings.jumpForwardTime = jumpForwardTime
|
||||||
settings.lockOrientation = lockOrientation
|
settings.lockOrientation = lockOrientation
|
||||||
settings.hapticFeedback = hapticFeedback
|
settings.hapticFeedback = hapticFeedback
|
||||||
settings.languageCode = languageCode
|
settings.languageCode = languageCode
|
||||||
|
settings.downloadUsingCellular = downloadUsingCellular
|
||||||
|
settings.streamingUsingCellular = streamingUsingCellular
|
||||||
|
|
||||||
Database.shared.setDeviceSettings(deviceSettings: settings)
|
Database.shared.setDeviceSettings(deviceSettings: settings)
|
||||||
|
|
||||||
// call.resolve([ "value": [] ])
|
// Updates the media notification controls (for allowSeekingOnMediaControls setting)
|
||||||
|
PlayerHandler.updateRemoteTransportControls()
|
||||||
|
|
||||||
getDeviceData(call)
|
getDeviceData(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ def capacitor_pods
|
|||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'ByteowlsCapacitorFilesharer', :path => '../../node_modules/@byteowls/capacitor-filesharer'
|
pod 'ByteowlsCapacitorFilesharer', :path => '../../node_modules/@byteowls/capacitor-filesharer'
|
||||||
|
pod 'CapacitorCommunityVolumeButtons', :path => '../../node_modules/@capacitor-community/volume-buttons'
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
pod 'CapacitorBrowser', :path => '../../node_modules/@capacitor/browser'
|
pod 'CapacitorBrowser', :path => '../../node_modules/@capacitor/browser'
|
||||||
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
||||||
|
|||||||
+22
-16
@@ -1,8 +1,8 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Alamofire (5.6.4)
|
- Alamofire (5.8.1)
|
||||||
- ByteowlsCapacitorFilesharer (5.0.0):
|
- ByteowlsCapacitorFilesharer (5.0.0):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- Capacitor (5.4.0):
|
- Capacitor (5.6.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- CapacitorApp (5.0.6):
|
- CapacitorApp (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
@@ -10,7 +10,9 @@ PODS:
|
|||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorClipboard (5.0.6):
|
- CapacitorClipboard (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorCordova (5.4.0)
|
- CapacitorCommunityVolumeButtons (1.0.2):
|
||||||
|
- Capacitor
|
||||||
|
- CapacitorCordova (5.6.0)
|
||||||
- CapacitorDialog (5.0.6):
|
- CapacitorDialog (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorHaptics (5.0.6):
|
- CapacitorHaptics (5.0.6):
|
||||||
@@ -21,13 +23,13 @@ PODS:
|
|||||||
- Capacitor
|
- Capacitor
|
||||||
- CapacitorStatusBar (5.0.6):
|
- CapacitorStatusBar (5.0.6):
|
||||||
- Capacitor
|
- Capacitor
|
||||||
- CordovaPlugins (5.4.0):
|
- CordovaPlugins (5.6.0):
|
||||||
- CapacitorCordova
|
- CapacitorCordova
|
||||||
- Realm (10.36.0):
|
- Realm (10.47.0):
|
||||||
- Realm/Headers (= 10.36.0)
|
- Realm/Headers (= 10.47.0)
|
||||||
- Realm/Headers (10.36.0)
|
- Realm/Headers (10.47.0)
|
||||||
- RealmSwift (10.36.0):
|
- RealmSwift (10.47.0):
|
||||||
- Realm (= 10.36.0)
|
- Realm (= 10.47.0)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Alamofire (~> 5.5)
|
- Alamofire (~> 5.5)
|
||||||
@@ -36,6 +38,7 @@ DEPENDENCIES:
|
|||||||
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
- "CapacitorApp (from `../../node_modules/@capacitor/app`)"
|
||||||
- "CapacitorBrowser (from `../../node_modules/@capacitor/browser`)"
|
- "CapacitorBrowser (from `../../node_modules/@capacitor/browser`)"
|
||||||
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
|
- "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)"
|
||||||
|
- "CapacitorCommunityVolumeButtons (from `../../node_modules/@capacitor-community/volume-buttons`)"
|
||||||
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
- "CapacitorCordova (from `../../node_modules/@capacitor/ios`)"
|
||||||
- "CapacitorDialog (from `../../node_modules/@capacitor/dialog`)"
|
- "CapacitorDialog (from `../../node_modules/@capacitor/dialog`)"
|
||||||
- "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)"
|
- "CapacitorHaptics (from `../../node_modules/@capacitor/haptics`)"
|
||||||
@@ -62,6 +65,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../node_modules/@capacitor/browser"
|
:path: "../../node_modules/@capacitor/browser"
|
||||||
CapacitorClipboard:
|
CapacitorClipboard:
|
||||||
:path: "../../node_modules/@capacitor/clipboard"
|
:path: "../../node_modules/@capacitor/clipboard"
|
||||||
|
CapacitorCommunityVolumeButtons:
|
||||||
|
:path: "../../node_modules/@capacitor-community/volume-buttons"
|
||||||
CapacitorCordova:
|
CapacitorCordova:
|
||||||
:path: "../../node_modules/@capacitor/ios"
|
:path: "../../node_modules/@capacitor/ios"
|
||||||
CapacitorDialog:
|
CapacitorDialog:
|
||||||
@@ -78,22 +83,23 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../capacitor-cordova-ios-plugins"
|
:path: "../capacitor-cordova-ios-plugins"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 4e95d97098eacb88856099c4fc79b526a299e48c
|
Alamofire: 3ca42e259043ee0dc5c0cdd76c4bc568b8e42af7
|
||||||
ByteowlsCapacitorFilesharer: f6a773825632d65d5404a34764c4a3fd857bb176
|
ByteowlsCapacitorFilesharer: f6a773825632d65d5404a34764c4a3fd857bb176
|
||||||
Capacitor: a5cd803e02b471591c81165f400ace01f40b11d3
|
Capacitor: ebfc16cdb8116d04c101686b080342872da42d43
|
||||||
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a
|
CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a
|
||||||
CapacitorBrowser: 7a0fb6a1011abfaaf2dfedfd8248f942a8eda3d6
|
CapacitorBrowser: 7a0fb6a1011abfaaf2dfedfd8248f942a8eda3d6
|
||||||
CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4
|
CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4
|
||||||
CapacitorCordova: 66ce22f9976de30fd816f746e9e92e07d6befafd
|
CapacitorCommunityVolumeButtons: 132d27a8e482898e45480b7edc882f3d26a00cb6
|
||||||
|
CapacitorCordova: 931b48fcdbc9bc985fc2f16cec9f77c794a27729
|
||||||
CapacitorDialog: 0f3c15dfe9414b83bc64aef4078f1b92bcfead26
|
CapacitorDialog: 0f3c15dfe9414b83bc64aef4078f1b92bcfead26
|
||||||
CapacitorHaptics: 1fffc1217c7e64a472d7845be50fb0c2f7d4204c
|
CapacitorHaptics: 1fffc1217c7e64a472d7845be50fb0c2f7d4204c
|
||||||
CapacitorNetwork: d80b3e79bef6ec37640ee2806c19771f07ff2d0c
|
CapacitorNetwork: d80b3e79bef6ec37640ee2806c19771f07ff2d0c
|
||||||
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
|
CapacitorPreferences: f03954bcb0ff09c792909e46bff88e3183c16b10
|
||||||
CapacitorStatusBar: 565c0a1ebd79bb40d797606a8992b4a105885309
|
CapacitorStatusBar: 565c0a1ebd79bb40d797606a8992b4a105885309
|
||||||
CordovaPlugins: a5db67e5ac1061b9869a0efd754f2c2f776aeccc
|
CordovaPlugins: dae7dac59b3cce58f5a7ce3894709d8f0f824656
|
||||||
Realm: 3fd136cb4c83a927482a7f1612496d37beed3cf5
|
Realm: e43fb540ae947497e3ea8a662443256920602060
|
||||||
RealmSwift: 513d4dcbf5bfc4d573454088b592685fc48dd716
|
RealmSwift: 8b06ed06b5d16749ae0c4d91c0cba414a9e28189
|
||||||
|
|
||||||
PODFILE CHECKSUM: 02e6ffe2f51a453ce222ee9af0e55e9448d8514c
|
PODFILE CHECKSUM: d0bbe98fb520752929ffc76e7c901d54b2eed7f0
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.12.1
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ import RealmSwift
|
|||||||
class DeviceSettings: Object {
|
class DeviceSettings: Object {
|
||||||
@Persisted var disableAutoRewind: Bool = false
|
@Persisted var disableAutoRewind: Bool = false
|
||||||
@Persisted var enableAltView: Bool = true
|
@Persisted var enableAltView: Bool = true
|
||||||
|
@Persisted var allowSeekingOnMediaControls: Bool = false
|
||||||
@Persisted var jumpBackwardsTime: Int = 10
|
@Persisted var jumpBackwardsTime: Int = 10
|
||||||
@Persisted var jumpForwardTime: Int = 10
|
@Persisted var jumpForwardTime: Int = 10
|
||||||
@Persisted var lockOrientation: String = "NONE"
|
@Persisted var lockOrientation: String = "NONE"
|
||||||
@Persisted var hapticFeedback: String = "LIGHT"
|
@Persisted var hapticFeedback: String = "LIGHT"
|
||||||
@Persisted var languageCode: String = "en-us"
|
@Persisted var languageCode: String = "en-us"
|
||||||
|
@Persisted var downloadUsingCellular: String = "ALWAYS"
|
||||||
|
@Persisted var streamingUsingCellular: String = "ALWAYS"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultDeviceSettings() -> DeviceSettings {
|
func getDefaultDeviceSettings() -> DeviceSettings {
|
||||||
@@ -26,10 +29,13 @@ func deviceSettingsToJSON(settings: DeviceSettings) -> Dictionary<String, Any> {
|
|||||||
return [
|
return [
|
||||||
"disableAutoRewind": settings.disableAutoRewind,
|
"disableAutoRewind": settings.disableAutoRewind,
|
||||||
"enableAltView": settings.enableAltView,
|
"enableAltView": settings.enableAltView,
|
||||||
|
"allowSeekingOnMediaControls": settings.allowSeekingOnMediaControls,
|
||||||
"jumpBackwardsTime": settings.jumpBackwardsTime,
|
"jumpBackwardsTime": settings.jumpBackwardsTime,
|
||||||
"jumpForwardTime": settings.jumpForwardTime,
|
"jumpForwardTime": settings.jumpForwardTime,
|
||||||
"lockOrientation": settings.lockOrientation,
|
"lockOrientation": settings.lockOrientation,
|
||||||
"hapticFeedback": settings.hapticFeedback,
|
"hapticFeedback": settings.hapticFeedback,
|
||||||
"languageCode": settings.languageCode
|
"languageCode": settings.languageCode,
|
||||||
|
"downloadUsingCellular": settings.downloadUsingCellular,
|
||||||
|
"streamingUsingCellular": settings.streamingUsingCellular
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ class AudioPlayer: NSObject {
|
|||||||
internal let logger = AppLogger(category: "AudioPlayer")
|
internal let logger = AppLogger(category: "AudioPlayer")
|
||||||
|
|
||||||
private var status: PlayerStatus
|
private var status: PlayerStatus
|
||||||
internal var rate: Float
|
internal var rateManager: AudioPlayerRateManager
|
||||||
|
|
||||||
private var tmpRate: Float = 1.0
|
|
||||||
|
|
||||||
private var playerContext = 0
|
private var playerContext = 0
|
||||||
private var playerItemContext = 0
|
private var playerItemContext = 0
|
||||||
@@ -64,11 +62,18 @@ class AudioPlayer: NSObject {
|
|||||||
self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
self.audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
self.status = .uninitialized
|
self.status = .uninitialized
|
||||||
self.rate = 0.0
|
|
||||||
self.tmpRate = playbackRate
|
if #available(iOS 16.0, *) {
|
||||||
|
self.rateManager = DefaultedAudioPlayerRateManager(audioPlayer: self.audioPlayer, defaultRate: playbackRate)
|
||||||
|
} else {
|
||||||
|
self.rateManager = LegacyAudioPlayerRateManager(audioPlayer: self.audioPlayer, defaultRate: playbackRate)
|
||||||
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.rateManager.rateChangedCompletion = self.updateNowPlaying
|
||||||
|
self.rateManager.defaultRateChangedCompletion = self.setupTimeObservers
|
||||||
|
|
||||||
initAudioSession()
|
initAudioSession()
|
||||||
setupRemoteTransportControls()
|
setupRemoteTransportControls()
|
||||||
|
|
||||||
@@ -81,7 +86,6 @@ class AudioPlayer: NSObject {
|
|||||||
|
|
||||||
// Listen to player events
|
// Listen to player events
|
||||||
self.setupAudioSessionNotifications()
|
self.setupAudioSessionNotifications()
|
||||||
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &playerContext)
|
|
||||||
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
|
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), options: .new, context: &playerContext)
|
||||||
|
|
||||||
for track in playbackSession.audioTracks {
|
for track in playbackSession.audioTracks {
|
||||||
@@ -150,7 +154,6 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove observers
|
// Remove observers
|
||||||
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &playerContext)
|
|
||||||
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
|
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.currentItem), context: &playerContext)
|
||||||
self.removeTimeObservers()
|
self.removeTimeObservers()
|
||||||
|
|
||||||
@@ -332,7 +335,7 @@ class AudioPlayer: NSObject {
|
|||||||
self.markAudioSessionAs(active: true)
|
self.markAudioSessionAs(active: true)
|
||||||
DispatchQueue.runOnMainQueue {
|
DispatchQueue.runOnMainQueue {
|
||||||
self.audioPlayer.play()
|
self.audioPlayer.play()
|
||||||
self.audioPlayer.rate = self.tmpRate
|
self.rateManager.handlePlayEvent()
|
||||||
}
|
}
|
||||||
self.status = .playing
|
self.status = .playing
|
||||||
|
|
||||||
@@ -424,25 +427,8 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setPlaybackRate(_ rate: Float, observed: Bool = false) {
|
public func setPlaybackRate(_ rate: Float) {
|
||||||
let playbackSpeedChanged = rate > 0.0 && rate != self.tmpRate && !(observed && rate == 1)
|
self.rateManager.setPlaybackRate(rate)
|
||||||
|
|
||||||
if self.audioPlayer.rate != rate {
|
|
||||||
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
|
|
||||||
DispatchQueue.runOnMainQueue {
|
|
||||||
self.audioPlayer.rate = rate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rate = rate
|
|
||||||
self.updateNowPlaying()
|
|
||||||
|
|
||||||
if playbackSpeedChanged {
|
|
||||||
self.tmpRate = rate
|
|
||||||
|
|
||||||
// Setup the time observer again at the new rate
|
|
||||||
self.setupTimeObservers()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setChapterTrack() {
|
public func setChapterTrack() {
|
||||||
@@ -588,7 +574,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Now playing
|
// MARK: - Now playing
|
||||||
private func setupRemoteTransportControls() {
|
func setupRemoteTransportControls() {
|
||||||
DispatchQueue.runOnMainQueue {
|
DispatchQueue.runOnMainQueue {
|
||||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||||
}
|
}
|
||||||
@@ -598,6 +584,7 @@ class AudioPlayer: NSObject {
|
|||||||
let jumpBackwardsTime = deviceSettings.jumpBackwardsTime
|
let jumpBackwardsTime = deviceSettings.jumpBackwardsTime
|
||||||
|
|
||||||
commandCenter.playCommand.isEnabled = true
|
commandCenter.playCommand.isEnabled = true
|
||||||
|
commandCenter.playCommand.removeTarget(nil)
|
||||||
commandCenter.playCommand.addTarget { [weak self] event in
|
commandCenter.playCommand.addTarget { [weak self] event in
|
||||||
guard let strongSelf = self else { return .commandFailed }
|
guard let strongSelf = self else { return .commandFailed }
|
||||||
if strongSelf.isPlaying() {
|
if strongSelf.isPlaying() {
|
||||||
@@ -609,6 +596,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.pauseCommand.isEnabled = true
|
commandCenter.pauseCommand.isEnabled = true
|
||||||
|
commandCenter.pauseCommand.removeTarget(nil)
|
||||||
commandCenter.pauseCommand.addTarget { [weak self] event in
|
commandCenter.pauseCommand.addTarget { [weak self] event in
|
||||||
guard let strongSelf = self else { return .commandFailed }
|
guard let strongSelf = self else { return .commandFailed }
|
||||||
if strongSelf.isPlaying() {
|
if strongSelf.isPlaying() {
|
||||||
@@ -620,6 +608,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.togglePlayPauseCommand.isEnabled = true
|
commandCenter.togglePlayPauseCommand.isEnabled = true
|
||||||
|
commandCenter.togglePlayPauseCommand.removeTarget(nil)
|
||||||
commandCenter.togglePlayPauseCommand.addTarget { [weak self] event in
|
commandCenter.togglePlayPauseCommand.addTarget { [weak self] event in
|
||||||
guard let strongSelf = self else { return .commandFailed }
|
guard let strongSelf = self else { return .commandFailed }
|
||||||
if strongSelf.isPlaying() {
|
if strongSelf.isPlaying() {
|
||||||
@@ -631,6 +620,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.skipForwardCommand.isEnabled = true
|
commandCenter.skipForwardCommand.isEnabled = true
|
||||||
|
commandCenter.skipForwardCommand.removeTarget(nil)
|
||||||
commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: jumpForwardTime)]
|
commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: jumpForwardTime)]
|
||||||
commandCenter.skipForwardCommand.addTarget { [weak self] event in
|
commandCenter.skipForwardCommand.addTarget { [weak self] event in
|
||||||
guard let command = event.command as? MPSkipIntervalCommand else {
|
guard let command = event.command as? MPSkipIntervalCommand else {
|
||||||
@@ -643,6 +633,7 @@ class AudioPlayer: NSObject {
|
|||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
commandCenter.skipBackwardCommand.isEnabled = true
|
commandCenter.skipBackwardCommand.isEnabled = true
|
||||||
|
commandCenter.skipBackwardCommand.removeTarget(nil)
|
||||||
commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: jumpBackwardsTime)]
|
commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: jumpBackwardsTime)]
|
||||||
commandCenter.skipBackwardCommand.addTarget { [weak self] event in
|
commandCenter.skipBackwardCommand.addTarget { [weak self] event in
|
||||||
guard let command = event.command as? MPSkipIntervalCommand else {
|
guard let command = event.command as? MPSkipIntervalCommand else {
|
||||||
@@ -656,6 +647,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.nextTrackCommand.isEnabled = true
|
commandCenter.nextTrackCommand.isEnabled = true
|
||||||
|
commandCenter.nextTrackCommand.removeTarget(nil)
|
||||||
commandCenter.nextTrackCommand.addTarget { [weak self] _ in
|
commandCenter.nextTrackCommand.addTarget { [weak self] _ in
|
||||||
guard let currentTime = self?.getCurrentTime() else {
|
guard let currentTime = self?.getCurrentTime() else {
|
||||||
return .commandFailed
|
return .commandFailed
|
||||||
@@ -664,6 +656,7 @@ class AudioPlayer: NSObject {
|
|||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
commandCenter.previousTrackCommand.isEnabled = true
|
commandCenter.previousTrackCommand.isEnabled = true
|
||||||
|
commandCenter.previousTrackCommand.removeTarget(nil)
|
||||||
commandCenter.previousTrackCommand.addTarget { [weak self] _ in
|
commandCenter.previousTrackCommand.addTarget { [weak self] _ in
|
||||||
guard let currentTime = self?.getCurrentTime() else {
|
guard let currentTime = self?.getCurrentTime() else {
|
||||||
return .commandFailed
|
return .commandFailed
|
||||||
@@ -672,7 +665,8 @@ class AudioPlayer: NSObject {
|
|||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
commandCenter.changePlaybackPositionCommand.isEnabled = deviceSettings.allowSeekingOnMediaControls
|
||||||
|
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
|
||||||
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in
|
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in
|
||||||
guard let event = event as? MPChangePlaybackPositionCommandEvent else {
|
guard let event = event as? MPChangePlaybackPositionCommandEvent else {
|
||||||
return .noSuchContent
|
return .noSuchContent
|
||||||
@@ -690,6 +684,7 @@ class AudioPlayer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commandCenter.changePlaybackRateCommand.isEnabled = true
|
commandCenter.changePlaybackRateCommand.isEnabled = true
|
||||||
|
commandCenter.changePlaybackRateCommand.removeTarget(nil)
|
||||||
commandCenter.changePlaybackRateCommand.supportedPlaybackRates = [0.5, 0.75, 1.0, 1.25, 1.5, 2]
|
commandCenter.changePlaybackRateCommand.supportedPlaybackRates = [0.5, 0.75, 1.0, 1.25, 1.5, 2]
|
||||||
commandCenter.changePlaybackRateCommand.addTarget { [weak self] event in
|
commandCenter.changePlaybackRateCommand.addTarget { [weak self] event in
|
||||||
guard let event = event as? MPChangePlaybackRateCommandEvent else {
|
guard let event = event as? MPChangePlaybackRateCommandEvent else {
|
||||||
@@ -706,23 +701,21 @@ class AudioPlayer: NSObject {
|
|||||||
NowPlayingInfo.shared.update(
|
NowPlayingInfo.shared.update(
|
||||||
duration: currentChapter.getRelativeChapterEndTime(),
|
duration: currentChapter.getRelativeChapterEndTime(),
|
||||||
currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime),
|
currentTime: currentChapter.getRelativeChapterCurrentTime(sessionCurrentTime: session.currentTime),
|
||||||
rate: rate,
|
rate: self.rateManager.rate,
|
||||||
|
defaultRate: self.rateManager.defaultRate,
|
||||||
chapterName: currentChapter.title,
|
chapterName: currentChapter.title,
|
||||||
chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1,
|
chapterNumber: (session.chapters.firstIndex(of: currentChapter) ?? 0) + 1,
|
||||||
chapterCount: session.chapters.count
|
chapterCount: session.chapters.count
|
||||||
)
|
)
|
||||||
} else if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
|
} else if let duration = self.getDuration(), let currentTime = self.getCurrentTime() {
|
||||||
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: rate)
|
NowPlayingInfo.shared.update(duration: duration, currentTime: currentTime, rate: self.rateManager.rate, defaultRate: self.rateManager.defaultRate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Observer
|
// MARK: - Observer
|
||||||
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
if context == &playerContext {
|
if context == &playerContext {
|
||||||
if keyPath == #keyPath(AVPlayer.rate) {
|
if keyPath == #keyPath(AVPlayer.currentItem) {
|
||||||
logger.log("playerContext observer player rate")
|
|
||||||
self.setPlaybackRate(change?[.newKey] as? Float ?? 1.0, observed: true)
|
|
||||||
} else if keyPath == #keyPath(AVPlayer.currentItem) {
|
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
logger.log("WARNING: Item ended")
|
logger.log("WARNING: Item ended")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// AudioPlayerRateManager.swift
|
||||||
|
// Audiobookshelf
|
||||||
|
//
|
||||||
|
// Created by Marke Hallowell on 4/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
protocol AudioPlayerRateManager {
|
||||||
|
var rate: Float { get }
|
||||||
|
var defaultRate: Float { get }
|
||||||
|
var rateChangedCompletion: () -> Void { get set }
|
||||||
|
var defaultRateChangedCompletion: () -> Void { get set }
|
||||||
|
|
||||||
|
init(audioPlayer: AVPlayer, defaultRate: Float)
|
||||||
|
|
||||||
|
func setPlaybackRate(_ rate: Float)
|
||||||
|
|
||||||
|
// Callback for play events (e.g. LegacyAudioPlayerRateManager uses this set rate immediately after playback resumes)
|
||||||
|
func handlePlayEvent() -> Void
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ extension AudioPlayer {
|
|||||||
// Return the player time until sleep
|
// Return the player time until sleep
|
||||||
var sleepTimeRemaining: Double? = nil
|
var sleepTimeRemaining: Double? = nil
|
||||||
if let chapterStopAt = self.sleepTimeChapterStopAt {
|
if let chapterStopAt = self.sleepTimeChapterStopAt {
|
||||||
sleepTimeRemaining = (chapterStopAt - currentTime) / Double(self.rate > 0 ? self.rate : 1.0)
|
sleepTimeRemaining = (chapterStopAt - currentTime) / Double(self.rateManager.rate > 0 ? self.rateManager.rate : 1.0)
|
||||||
} else if self.isCountdownSleepTimerSet() {
|
} else if self.isCountdownSleepTimerSet() {
|
||||||
sleepTimeRemaining = self.sleepTimeRemaining
|
sleepTimeRemaining = self.sleepTimeRemaining
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
//
|
||||||
|
// DefaultedAudioPlayerRateManager.swift
|
||||||
|
// Audiobookshelf
|
||||||
|
//
|
||||||
|
// Created by Marke Hallowell on 4/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
@available(iOS 16.0, *)
|
||||||
|
class DefaultedAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||||
|
internal let logger = AppLogger(category: "DefaultedAudioPlayerRateManager")
|
||||||
|
|
||||||
|
internal var audioPlayer: AVPlayer
|
||||||
|
|
||||||
|
// MARK: - AudioPlayerRateManager
|
||||||
|
public private(set) var defaultRate: Float
|
||||||
|
public private(set) var rate: Float
|
||||||
|
public var rateChangedCompletion: () -> Void
|
||||||
|
public var defaultRateChangedCompletion: () -> Void
|
||||||
|
|
||||||
|
required init(audioPlayer: AVPlayer, defaultRate: Float) {
|
||||||
|
self.audioPlayer = audioPlayer
|
||||||
|
self.rateChangedCompletion = {}
|
||||||
|
self.defaultRateChangedCompletion = {}
|
||||||
|
self.rate = self.audioPlayer.rate
|
||||||
|
self.defaultRate = defaultRate
|
||||||
|
self.audioPlayer.defaultRate = defaultRate
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(handleObservedRateChange), name: AVPlayer.rateDidChangeNotification, object: self.audioPlayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setPlaybackRate(_ rate: Float) {
|
||||||
|
self.handlePlaybackRateChange(rate, observed: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op (player automatically resumes at last-known defaultRate)
|
||||||
|
public func handlePlayEvent() { }
|
||||||
|
|
||||||
|
// MARK: - Destructor
|
||||||
|
public func destroy() {
|
||||||
|
NotificationCenter.default.removeObserver(self, name: AVPlayer.rateDidChangeNotification, object: self.audioPlayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Internal
|
||||||
|
internal func handlePlaybackRateChange(_ rate: Float, observed: Bool = false) {
|
||||||
|
let playbackSpeedChanged = rate > 0.0 && rate != self.defaultRate
|
||||||
|
|
||||||
|
if playbackSpeedChanged {
|
||||||
|
self.defaultRate = rate
|
||||||
|
self.audioPlayer.defaultRate = rate
|
||||||
|
self.defaultRateChangedCompletion()
|
||||||
|
|
||||||
|
// Check to see if we also need to make a temporary rate change to player
|
||||||
|
if self.audioPlayer.rate > 0.0 {
|
||||||
|
if self.audioPlayer.rate != rate {
|
||||||
|
self.rate = rate
|
||||||
|
self.audioPlayer.rate = rate
|
||||||
|
self.rateChangedCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.rate = rate
|
||||||
|
self.rateChangedCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - iOS rate change notification handler
|
||||||
|
@objc internal func handleObservedRateChange(notification: Notification) {
|
||||||
|
// TODO: Consider handling cases individually (e.g. overall session impact?)
|
||||||
|
/*
|
||||||
|
guard let reason = notification.userInfo?[AVPlayer.rateDidChangeReasonKey] as? AVPlayer.RateDidChangeReason else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch reason {
|
||||||
|
case .appBackgrounded:
|
||||||
|
// App transitioned to the background.
|
||||||
|
case .audioSessionInterrupted:
|
||||||
|
// The system interrupts the app's audio session.
|
||||||
|
case .setRateCalled:
|
||||||
|
// The app set the player's rate.
|
||||||
|
case .setRateFailed:
|
||||||
|
// An attempt to change the player's rate failed.
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
self.handlePlaybackRateChange(self.audioPlayer.rate, observed: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// LegacyAudioPlayerRateManager.swift
|
||||||
|
// Audiobookshelf
|
||||||
|
//
|
||||||
|
// Created by Marke Hallowell on 4/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
class LegacyAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||||
|
internal let logger = AppLogger(category: "AudioPlayer")
|
||||||
|
|
||||||
|
internal var audioPlayer: AVPlayer
|
||||||
|
|
||||||
|
internal var managerContext = 0
|
||||||
|
|
||||||
|
// MARK: - AudioPlayerRateManager
|
||||||
|
public private(set) var defaultRate: Float
|
||||||
|
public private(set) var rate: Float
|
||||||
|
public var rateChangedCompletion: () -> Void
|
||||||
|
public var defaultRateChangedCompletion: () -> Void
|
||||||
|
|
||||||
|
required init(audioPlayer: AVPlayer, defaultRate: Float) {
|
||||||
|
self.rate = 0.0
|
||||||
|
self.defaultRate = defaultRate
|
||||||
|
self.audioPlayer = audioPlayer
|
||||||
|
self.rateChangedCompletion = {}
|
||||||
|
self.defaultRateChangedCompletion = {}
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.audioPlayer.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: &managerContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setPlaybackRate(_ rate: Float) {
|
||||||
|
self.handlePlaybackRateChange(rate, observed: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func handlePlayEvent() {
|
||||||
|
DispatchQueue.runOnMainQueue {
|
||||||
|
self.audioPlayer.rate = self.defaultRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Destructor
|
||||||
|
public func destroy() {
|
||||||
|
// Remove Observer
|
||||||
|
self.audioPlayer.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate), context: &managerContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Internal
|
||||||
|
internal func handlePlaybackRateChange(_ rate: Float, observed: Bool = false) {
|
||||||
|
let playbackSpeedChanged = rate > 0.0 && rate != self.defaultRate && !(observed && rate == 1)
|
||||||
|
|
||||||
|
if self.audioPlayer.rate != rate {
|
||||||
|
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
|
||||||
|
DispatchQueue.runOnMainQueue {
|
||||||
|
self.audioPlayer.rate = rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rate = rate
|
||||||
|
self.rateChangedCompletion()
|
||||||
|
|
||||||
|
if playbackSpeedChanged {
|
||||||
|
self.defaultRate = rate
|
||||||
|
self.defaultRateChangedCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Observer
|
||||||
|
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if context == &managerContext {
|
||||||
|
if keyPath == #keyPath(AVPlayer.rate) {
|
||||||
|
logger.log("playerContext observer player rate")
|
||||||
|
self.handlePlaybackRateChange(change?[.newKey] as? Float ?? 1.0, observed: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ class PlayerHandler {
|
|||||||
public static var paused: Bool {
|
public static var paused: Bool {
|
||||||
get {
|
get {
|
||||||
guard let player = player else { return true }
|
guard let player = player else { return true }
|
||||||
return player.rate == 0.0
|
return player.rateManager.rate == 0.0
|
||||||
}
|
}
|
||||||
set(paused) {
|
set(paused) {
|
||||||
if paused {
|
if paused {
|
||||||
@@ -135,6 +135,10 @@ class PlayerHandler {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func updateRemoteTransportControls() {
|
||||||
|
self.player?.setupRemoteTransportControls()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helper logic
|
// MARK: - Helper logic
|
||||||
|
|
||||||
private static func cleanupOldSessions(currentSessionId: String?) {
|
private static func cleanupOldSessions(currentSessionId: String?) {
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ class NowPlayingInfo {
|
|||||||
self.setMetadata(artwork: artwork, metadata: metadata)
|
self.setMetadata(artwork: artwork, metadata: metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public func update(duration: Double, currentTime: Double, rate: Float, chapterName: String? = nil, chapterNumber: Int? = nil, chapterCount: Int? = nil) {
|
public func update(duration: Double, currentTime: Double, rate: Float, defaultRate: Float, chapterName: String? = nil, chapterNumber: Int? = nil, chapterCount: Int? = nil) {
|
||||||
// Update on the main to prevent access collisions
|
// Update on the main to prevent access collisions
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
if let self = self {
|
if let self = self {
|
||||||
self.nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
self.nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
||||||
self.nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentTime
|
||||||
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = rate
|
||||||
self.nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = 1.0
|
self.nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = defaultRate
|
||||||
|
|
||||||
|
|
||||||
if let chapterName = chapterName, let chapterNumber = chapterNumber, let chapterCount = chapterCount {
|
if let chapterName = chapterName, let chapterNumber = chapterNumber, let chapterCount = chapterCount {
|
||||||
|
|||||||
+1
-6
@@ -177,10 +177,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userLoggedOut() {
|
|
||||||
// Only cancels stream if streamining not playing downloaded
|
|
||||||
this.$eventBus.$emit('close-stream')
|
|
||||||
},
|
|
||||||
socketConnectionFailed(err) {
|
socketConnectionFailed(err) {
|
||||||
this.$toast.error('Socket connection error: ' + err.message)
|
this.$toast.error('Socket connection error: ' + err.message)
|
||||||
},
|
},
|
||||||
@@ -212,8 +208,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
userUpdated(user) {
|
userUpdated(user) {
|
||||||
// console.log('[default] userUpdated:', JSON.stringify(user))
|
if (this.user?.id == user.id) {
|
||||||
if (this.user && this.user.id == user.id) {
|
|
||||||
this.$store.commit('user/setUser', user)
|
this.$store.commit('user/setUser', user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Dialog } from '@capacitor/dialog';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
async checkCellularPermission(actionType) {
|
||||||
|
if (this.$store.state.networkConnectionType !== 'cellular') return true
|
||||||
|
|
||||||
|
let permission;
|
||||||
|
if (actionType === 'download') {
|
||||||
|
permission = this.$store.getters['getCanDownloadUsingCellular']
|
||||||
|
if (permission === 'NEVER') {
|
||||||
|
this.$toast.error(this.$strings.ToastDownloadNotAllowedOnCellular)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (actionType === 'streaming') {
|
||||||
|
permission = this.$store.getters['getCanStreamingUsingCellular']
|
||||||
|
if (permission === 'NEVER') {
|
||||||
|
this.$toast.error(this.$strings.ToastStreamingNotAllowedOnCellular)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission === 'ASK') {
|
||||||
|
const confirmed = await this.confirmAction(actionType)
|
||||||
|
return confirmed
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
async confirmAction(actionType) {
|
||||||
|
const message = actionType === 'download' ?
|
||||||
|
this.$strings.MessageConfirmDownloadUsingCellular :
|
||||||
|
this.$strings.MessageConfirmStreamingUsingCellular
|
||||||
|
|
||||||
|
const { value } = await Dialog.confirm({
|
||||||
|
title: 'Confirm',
|
||||||
|
message
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+15
-2
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.72-beta",
|
"version": "0.9.77-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.72-beta",
|
"version": "0.9.77-beta",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@byteowls/capacitor-filesharer": "^5.0.0",
|
"@byteowls/capacitor-filesharer": "^5.0.0",
|
||||||
|
"@capacitor-community/volume-buttons": "^1.0.2",
|
||||||
"@capacitor/android": "^5.0.0",
|
"@capacitor/android": "^5.0.0",
|
||||||
"@capacitor/app": "^5.0.6",
|
"@capacitor/app": "^5.0.6",
|
||||||
"@capacitor/browser": "^5.1.0",
|
"@capacitor/browser": "^5.1.0",
|
||||||
@@ -1979,6 +1980,18 @@
|
|||||||
"@capacitor/core": ">=5"
|
"@capacitor/core": ">=5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor-community/volume-buttons": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor-community/volume-buttons/-/volume-buttons-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-weEwVCgMEdkYF9uWOeZmA3kXltXSbiGlLPTmygF6NMcL1VUg1vBsiCIaikeI5XRne5V4q8UeEda83EfW7eb46g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0",
|
||||||
|
"npm": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor/android": {
|
"node_modules/@capacitor/android": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor/android/-/android-5.6.0.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.72-beta",
|
"version": "0.9.77-beta",
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@byteowls/capacitor-filesharer": "^5.0.0",
|
"@byteowls/capacitor-filesharer": "^5.0.0",
|
||||||
|
"@capacitor-community/volume-buttons": "^1.0.2",
|
||||||
"@capacitor/android": "^5.0.0",
|
"@capacitor/android": "^5.0.0",
|
||||||
"@capacitor/app": "^5.0.6",
|
"@capacitor/app": "^5.0.6",
|
||||||
"@capacitor/browser": "^5.1.0",
|
"@capacitor/browser": "^5.1.0",
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<template v-if="!showSelectedFeed">
|
<template v-if="!showSelectedFeed">
|
||||||
<div class="w-full mx-auto h-20 flex items-center px-2">
|
<div class="w-full mx-auto h-20 flex items-center px-2">
|
||||||
<form class="w-full" @submit.prevent="submit">
|
<form class="w-full" @submit.prevent="submit">
|
||||||
<ui-text-input v-model="searchInput" :disabled="processing || !networkConnected" placeholder="Enter search term or RSS feed URL" text-size="sm" />
|
<ui-text-input v-model="searchInput" :disabled="processing || !socketConnected" placeholder="Enter search term or RSS feed URL" text-size="sm" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!networkConnected" class="w-full text-center py-6">
|
<div v-if="!socketConnected" class="w-full text-center py-6">
|
||||||
<p class="text-lg text-error">{{ $strings.MessageNoNetworkConnection }}</p>
|
<p class="text-lg text-error">{{ $strings.MessageNoNetworkConnection }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full mx-auto pb-2 overflow-y-auto overflow-x-hidden h-[calc(100%-85px)]">
|
<div v-else class="w-full mx-auto pb-2 overflow-y-auto overflow-x-hidden h-[calc(100%-85px)]">
|
||||||
@@ -65,8 +65,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
networkConnected() {
|
socketConnected() {
|
||||||
return this.$store.state.networkConnected
|
return this.$store.state.socketConnected
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<h1 class="text-xl mb-2 font-semibold">{{ $strings.HeaderLatestEpisodes }}</h1>
|
<h1 class="text-xl mb-2 font-semibold">{{ $strings.HeaderLatestEpisodes }}</h1>
|
||||||
|
|
||||||
<template v-for="episode in recentEpisodes">
|
<template v-for="episode in recentEpisodes">
|
||||||
<tables-podcast-latest-episode-row :episode="episode" :local-episode="localEpisodeMap[episode.id]" :library-item-id="episode.libraryItemId" :local-library-item-id="null" :is-local="isLocal" :key="episode.id" @addToPlaylist="addEpisodeToPlaylist" />
|
<tables-podcast-latest-episode-row :episode="episode" :local-episode="localEpisodeMap[episode.id]" :library-item-id="episode.libraryItemId" :local-library-item-id="localEpisodeMap[episode.id]?.localLibraryItemId" :key="episode.id" @addToPlaylist="addEpisodeToPlaylist" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +17,6 @@ export default {
|
|||||||
totalEpisodes: 0,
|
totalEpisodes: 0,
|
||||||
currentPage: 0,
|
currentPage: 0,
|
||||||
localLibraryItems: [],
|
localLibraryItems: [],
|
||||||
isLocal: false,
|
|
||||||
loadedLibraryId: null
|
loadedLibraryId: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -30,7 +29,10 @@ export default {
|
|||||||
const episodes = []
|
const episodes = []
|
||||||
this.localLibraryItems.forEach((li) => {
|
this.localLibraryItems.forEach((li) => {
|
||||||
if (li.media.episodes?.length) {
|
if (li.media.episodes?.length) {
|
||||||
episodes.push(...li.media.episodes)
|
li.media.episodes.map((ep) => {
|
||||||
|
ep.localLibraryItemId = li.id
|
||||||
|
episodes.push(ep)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return episodes
|
return episodes
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<bookshelf-lazy-bookshelf page="series-books" :series-id="seriesId" />
|
<bookshelf-lazy-bookshelf page="series-books" :series-id="seriesId" v-on:downloadSeriesClick="downloadSeriesClick" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Dialog } from '@capacitor/dialog'
|
||||||
|
import { AbsDownloader } from '@/plugins/capacitor'
|
||||||
|
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ params, app, store, redirect }) {
|
async asyncData({ params, app, store, redirect }) {
|
||||||
var series = await app.$nativeHttp.get(`/api/series/${params.id}`).catch((error) => {
|
var series = await app.$nativeHttp.get(`/api/series/${params.id}`).catch((error) => {
|
||||||
@@ -19,10 +23,162 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {
|
||||||
|
startingDownload: false,
|
||||||
|
mediaType: 'book',
|
||||||
|
booksPerFetch: 20,
|
||||||
|
books: 0,
|
||||||
|
missingFiles: 0,
|
||||||
|
missingFilesSize: 0,
|
||||||
|
libraryIds: []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {},
|
mixins: [cellularPermissionHelpers],
|
||||||
methods: {},
|
computed: {
|
||||||
mounted() {}
|
isIos() {
|
||||||
|
return this.$platform === 'ios'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async downloadSeriesClick() {
|
||||||
|
console.log('Download Series clicked')
|
||||||
|
if (this.startingDownload) return
|
||||||
|
|
||||||
|
const hasPermission = await this.checkCellularPermission('download')
|
||||||
|
if (!hasPermission) return
|
||||||
|
|
||||||
|
this.startingDownload = true
|
||||||
|
setTimeout(() => {
|
||||||
|
this.startingDownload = false
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
this.download()
|
||||||
|
},
|
||||||
|
buildSearchParams() {
|
||||||
|
let searchParams = new URLSearchParams()
|
||||||
|
searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
|
||||||
|
return searchParams.toString()
|
||||||
|
},
|
||||||
|
async fetchSeriesEntities(page) {
|
||||||
|
const startIndex = page * this.booksPerFetch
|
||||||
|
|
||||||
|
this.currentSFQueryString = this.buildSearchParams()
|
||||||
|
|
||||||
|
const entityPath = `items`
|
||||||
|
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
||||||
|
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete`
|
||||||
|
|
||||||
|
const payload = await this.$nativeHttp.get(`/api/libraries/${this.series.libraryId}/${entityPath}${fullQueryString}`).catch((error) => {
|
||||||
|
console.error('failed to fetch books', error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (payload && payload.results) {
|
||||||
|
console.log('Received payload', payload)
|
||||||
|
this.books = payload.total
|
||||||
|
|
||||||
|
for (let i = 0; i < payload.results.length; i++) {
|
||||||
|
if (!(await this.$db.getLocalLibraryItem(`local_${payload.results[i].id}`))) {
|
||||||
|
this.missingFiles += payload.results[i].numFiles
|
||||||
|
this.missingFilesSize += payload.results[i].size
|
||||||
|
this.libraryIds.push(payload.results[i].id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let totalPages = Math.ceil(this.books / this.booksPerFetch)
|
||||||
|
if (totalPages > page + 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
async download(selectedLocalFolder = null) {
|
||||||
|
// Get the local folder to download to
|
||||||
|
let localFolder = selectedLocalFolder
|
||||||
|
if (!this.isIos && !localFolder) {
|
||||||
|
const localFolders = (await this.$db.getLocalFolders()) || []
|
||||||
|
console.log('Local folders loaded', localFolders.length)
|
||||||
|
const foldersWithMediaType = localFolders.filter((lf) => {
|
||||||
|
console.log('Checking local folder', lf.mediaType)
|
||||||
|
return lf.mediaType == this.mediaType
|
||||||
|
})
|
||||||
|
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
|
||||||
|
const internalStorageFolder = foldersWithMediaType.find((f) => f.id === `internal-${this.mediaType}`)
|
||||||
|
if (!foldersWithMediaType.length) {
|
||||||
|
localFolder = {
|
||||||
|
id: `internal-${this.mediaType}`,
|
||||||
|
name: this.$strings.LabelInternalAppStorage,
|
||||||
|
mediaType: this.mediaType
|
||||||
|
}
|
||||||
|
} else if (foldersWithMediaType.length === 1 && internalStorageFolder) {
|
||||||
|
localFolder = internalStorageFolder
|
||||||
|
} else {
|
||||||
|
this.$store.commit('globals/showSelectLocalFolderModal', {
|
||||||
|
mediaType: this.mediaType,
|
||||||
|
callback: (folder) => {
|
||||||
|
this.download(folder)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch series data from server
|
||||||
|
let page = 0
|
||||||
|
let fetchFinished = false
|
||||||
|
this.missingFiles = 0
|
||||||
|
this.missingFilesSize = 0
|
||||||
|
while (fetchFinished === false) {
|
||||||
|
fetchFinished = await this.fetchSeriesEntities(page)
|
||||||
|
page += 1
|
||||||
|
}
|
||||||
|
if (fetchFinished !== true) {
|
||||||
|
console.error('failed to fetch series books data')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (this.missingFiles == 0) {
|
||||||
|
alert(this.$getString('MessageSeriesAlreadyDownloaded'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format message for dialog
|
||||||
|
let startDownloadMessage = this.$getString('MessageSeriesDownloadConfirmIos', [this.libraryIds.length, this.missingFiles, this.$bytesPretty(this.missingFilesSize)])
|
||||||
|
if (!this.isIos) {
|
||||||
|
startDownloadMessage = this.$getString('MessageSeriesDownloadConfirm', [this.libraryIds.length, this.missingFiles, this.$bytesPretty(this.missingFilesSize), localFolder.name])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show confirmation dialog and start downloading if user chooses so
|
||||||
|
const { value } = await Dialog.confirm({
|
||||||
|
title: 'Confirm',
|
||||||
|
message: startDownloadMessage
|
||||||
|
})
|
||||||
|
if (value) {
|
||||||
|
for (let i = 0; i < this.libraryIds.length; i++) {
|
||||||
|
this.startDownload(localFolder, this.libraryIds[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.libraryIds = []
|
||||||
|
},
|
||||||
|
async startDownload(localFolder = null, libraryItemId) {
|
||||||
|
const payload = {
|
||||||
|
libraryItemId: libraryItemId
|
||||||
|
}
|
||||||
|
if (localFolder) {
|
||||||
|
console.log('Starting download to local folder', localFolder.name)
|
||||||
|
payload.localFolderId = localFolder.id
|
||||||
|
}
|
||||||
|
var downloadRes = await AbsDownloader.downloadLibraryItem(payload)
|
||||||
|
if (downloadRes && downloadRes.error) {
|
||||||
|
var errorMsg = downloadRes.error || 'Unknown error'
|
||||||
|
console.error('Download error', errorMsg)
|
||||||
|
this.$toast.error(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$eventBus.$on('download-series-click', this.downloadSeriesClick)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$eventBus.$off('download-series-click', this.downloadSeriesClick)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
+1
-5
@@ -34,11 +34,7 @@ export default {
|
|||||||
deviceData: null
|
deviceData: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {},
|
||||||
networkConnected() {
|
|
||||||
return this.$store.state.networkConnected
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
async init() {
|
async init() {
|
||||||
this.deviceData = await this.$db.getDeviceData()
|
this.deviceData = await this.$db.getDeviceData()
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="localLibraryItems.length" class="mt-4 text-sm text-fg-muted">
|
||||||
|
{{ $strings.LabelTotalSize }}: {{ $bytesPretty(localLibraryItems.reduce((acc, item) => acc + item.size, 0)) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</ui-btn>
|
</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-fg mt-1.5 mb-0.5 default-style" v-html="description" />
|
<p class="text-sm text-fg mt-1.5 mb-0.5 default-style description-container" v-html="transformedDescription"></p>
|
||||||
|
|
||||||
<!-- loading overlay -->
|
<!-- loading overlay -->
|
||||||
<div v-if="processing" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-30 flex items-center justify-center">
|
<div v-if="processing" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-30 flex items-center justify-center">
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
import { Capacitor } from '@capacitor/core'
|
import { Capacitor } from '@capacitor/core'
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
||||||
|
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, params, redirect, app }) {
|
async asyncData({ store, params, redirect, app }) {
|
||||||
@@ -115,7 +116,11 @@ export default {
|
|||||||
startingDownload: false
|
startingDownload: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [cellularPermissionHelpers],
|
||||||
computed: {
|
computed: {
|
||||||
|
transformedDescription() {
|
||||||
|
return this.parseDescription(this.description)
|
||||||
|
},
|
||||||
bookCoverAspectRatio() {
|
bookCoverAspectRatio() {
|
||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
},
|
},
|
||||||
@@ -310,6 +315,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
parseDescription(description) {
|
||||||
|
const timeMarkerLinkRegex = /<a href="#([^"]*?\b\d{1,2}:\d{1,2}(?::\d{1,2})?)">(.*?)<\/a>/g
|
||||||
|
const timeMarkerRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b/g
|
||||||
|
|
||||||
|
function convertToSeconds(time) {
|
||||||
|
const timeParts = time.split(':').map(Number)
|
||||||
|
return timeParts.reduce((acc, part, index) => acc * 60 + part, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return description.replace(timeMarkerLinkRegex, (match, href, displayTime) => {
|
||||||
|
const time = displayTime.match(timeMarkerRegex)[0]
|
||||||
|
const seekTimeInSeconds = convertToSeconds(time)
|
||||||
|
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${displayTime}</span>`
|
||||||
|
}).replace(timeMarkerRegex, (match) => {
|
||||||
|
const seekTimeInSeconds = convertToSeconds(match)
|
||||||
|
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${match}</span>`
|
||||||
|
})
|
||||||
|
},
|
||||||
async deleteLocalEpisode() {
|
async deleteLocalEpisode() {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
|
|
||||||
@@ -351,28 +374,56 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.$store.commit('setPlayerIsStartingPlayback', this.episode.id)
|
this.$store.commit('setPlayerIsStartingPlayback', this.episode.id)
|
||||||
|
|
||||||
if (this.localEpisodeId && this.localLibraryItemId && !this.isLocal) {
|
const playbackData = this.generatePlaybackData()
|
||||||
console.log('Play local episode', this.localEpisodeId, this.localLibraryItemId)
|
this.emitPlayItemEvent(playbackData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async clickPlaybackTime(event) {
|
||||||
|
const startTime = event.target.getAttribute('data-time')
|
||||||
|
if (this.playerIsStartingPlayback) return
|
||||||
|
|
||||||
this.$eventBus.$emit('play-item', {
|
await this.$hapticsImpact()
|
||||||
libraryItemId: this.localLibraryItemId,
|
|
||||||
episodeId: this.localEpisodeId,
|
this.$store.commit('setPlayerIsStartingPlayback', this.episode.id)
|
||||||
serverLibraryItemId: this.serverLibraryItemId,
|
|
||||||
serverEpisodeId: this.serverEpisodeId
|
const playbackData = this.generatePlaybackData(startTime)
|
||||||
})
|
this.emitPlayItemEvent(playbackData)
|
||||||
} else {
|
},
|
||||||
this.$eventBus.$emit('play-item', {
|
generatePlaybackData(startTime) {
|
||||||
libraryItemId: this.libraryItemId,
|
const playbackData = {
|
||||||
episodeId: this.episode.id,
|
libraryItemId: this.libraryItemId,
|
||||||
serverLibraryItemId: this.serverLibraryItemId,
|
episodeId: this.episode.id,
|
||||||
serverEpisodeId: this.serverEpisodeId
|
serverLibraryItemId: this.serverLibraryItemId,
|
||||||
})
|
serverEpisodeId: this.serverEpisodeId,
|
||||||
}
|
startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.localEpisodeId && this.localLibraryItemId && !this.isLocal) {
|
||||||
|
playbackData.libraryItemId = this.localLibraryItemId
|
||||||
|
playbackData.episodeId = this.localEpisodeId
|
||||||
|
}
|
||||||
|
|
||||||
|
return playbackData
|
||||||
|
},
|
||||||
|
emitPlayItemEvent(playbackData) {
|
||||||
|
this.$eventBus.$emit('play-item', playbackData)
|
||||||
|
},
|
||||||
|
bindTimeMarkerEvents() {
|
||||||
|
const container = document.querySelector('.description-container')
|
||||||
|
if (container) {
|
||||||
|
container.addEventListener('click', (event) => {
|
||||||
|
if (event.target.classList.contains('time-marker')) {
|
||||||
|
this.clickPlaybackTime(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async downloadClick() {
|
async downloadClick() {
|
||||||
if (this.downloadItem || this.startingDownload) return
|
if (this.downloadItem || this.startingDownload) return
|
||||||
|
|
||||||
|
const hasPermission = await this.checkCellularPermission('download')
|
||||||
|
if (!hasPermission) return
|
||||||
|
|
||||||
this.startingDownload = true
|
this.startingDownload = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.startingDownload = false
|
this.startingDownload = false
|
||||||
@@ -557,9 +608,13 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$eventBus.$on('new-local-library-item', this.newLocalLibraryItem)
|
this.$eventBus.$on('new-local-library-item', this.newLocalLibraryItem)
|
||||||
|
this.bindTimeMarkerEvents()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$eventBus.$off('new-local-library-item', this.newLocalLibraryItem)
|
this.$eventBus.$off('new-local-library-item', this.newLocalLibraryItem)
|
||||||
|
document.querySelectorAll('.time-marker').forEach(marker => {
|
||||||
|
marker.removeEventListener('click', this.clickPlaybackTime)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -28,7 +28,10 @@
|
|||||||
<div class="relative z-10 px-3 py-4">
|
<div class="relative z-10 px-3 py-4">
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<div class="text-center mb-2">
|
<div class="text-center mb-2">
|
||||||
<h1 class="text-xl font-semibold">{{ title }}</h1>
|
<div class="flex items-center justify-center">
|
||||||
|
<h1 class="text-xl font-semibold">{{ title }}</h1>
|
||||||
|
<widgets-explicit-indicator v-if="isExplicit" />
|
||||||
|
</div>
|
||||||
<p v-if="subtitle" class="text-fg text-base">{{ subtitle }}</p>
|
<p v-if="subtitle" class="text-fg text-base">{{ subtitle }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -168,6 +171,7 @@
|
|||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
import { AbsFileSystem, AbsDownloader } from '@/plugins/capacitor'
|
||||||
import { FastAverageColor } from 'fast-average-color'
|
import { FastAverageColor } from 'fast-average-color'
|
||||||
|
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, params, redirect, app, query }) {
|
async asyncData({ store, params, redirect, app, query }) {
|
||||||
@@ -184,7 +188,7 @@ export default {
|
|||||||
if (libraryItem?.libraryItemId?.startsWith('li_')) {
|
if (libraryItem?.libraryItemId?.startsWith('li_')) {
|
||||||
// Detect old library item id
|
// Detect old library item id
|
||||||
console.error('Local library item has old server library item id', libraryItem.libraryItemId)
|
console.error('Local library item has old server library item id', libraryItem.libraryItemId)
|
||||||
} else if (query.noredirect !== '1' && libraryItem?.libraryItemId && libraryItem?.serverAddress === store.getters['user/getServerAddress'] && store.state.networkConnected) {
|
} else if (query.noredirect !== '1' && libraryItem?.libraryItemId && libraryItem?.serverAddress === store.getters['user/getServerAddress'] && store.state.socketConnected) {
|
||||||
const queryParams = new URLSearchParams()
|
const queryParams = new URLSearchParams()
|
||||||
queryParams.set('localLibraryItemId', libraryItemId)
|
queryParams.set('localLibraryItemId', libraryItemId)
|
||||||
if (libraryItem.mediaType === 'podcast') {
|
if (libraryItem.mediaType === 'podcast') {
|
||||||
@@ -218,6 +222,7 @@ export default {
|
|||||||
startingDownload: false
|
startingDownload: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [cellularPermissionHelpers],
|
||||||
computed: {
|
computed: {
|
||||||
isIos() {
|
isIos() {
|
||||||
return this.$platform === 'ios'
|
return this.$platform === 'ios'
|
||||||
@@ -436,6 +441,9 @@ export default {
|
|||||||
isInvalid() {
|
isInvalid() {
|
||||||
return this.libraryItem.isInvalid
|
return this.libraryItem.isInvalid
|
||||||
},
|
},
|
||||||
|
isExplicit() {
|
||||||
|
return !!this.mediaMetadata.explicit
|
||||||
|
},
|
||||||
showPlay() {
|
showPlay() {
|
||||||
return !this.isMissing && !this.isInvalid && (this.numTracks || this.episodes.length)
|
return !this.isMissing && !this.isInvalid && (this.numTracks || this.episodes.length)
|
||||||
},
|
},
|
||||||
@@ -601,9 +609,11 @@ export default {
|
|||||||
this.download(localFolder)
|
this.download(localFolder)
|
||||||
},
|
},
|
||||||
async downloadClick() {
|
async downloadClick() {
|
||||||
if (this.downloadItem || this.startingDownload) {
|
if (this.downloadItem || this.startingDownload) return
|
||||||
return
|
|
||||||
}
|
const hasPermission = await this.checkCellularPermission('download')
|
||||||
|
if (!hasPermission) return
|
||||||
|
|
||||||
this.startingDownload = true
|
this.startingDownload = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.startingDownload = false
|
this.startingDownload = false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="w-full h-full py-6 px-2">
|
<div class="w-full h-full py-6 px-2">
|
||||||
<div v-if="localLibraryItem" class="w-full h-full">
|
<div v-if="localLibraryItem" class="w-full h-full">
|
||||||
<div class="px-2 flex items-center mb-2">
|
<div class="px-2 flex items-center mb-2">
|
||||||
<p class="text-base font-semibold">{{ mediaMetadata.title }}</p>
|
<p class="text-base font-semibold truncate">{{ mediaMetadata.title }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
<button v-if="audioTracks.length && !isPodcast" class="shadow-sm text-success flex items-center justify-center rounded-full mx-2" @click.stop="play">
|
<button v-if="audioTracks.length && !isPodcast" class="shadow-sm text-success flex items-center justify-center rounded-full mx-2" @click.stop="play">
|
||||||
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
<div class="w-full max-w-full media-item-container overflow-y-auto overflow-x-hidden relative pb-4" :class="{ 'media-order-changed': orderChanged }">
|
<div class="w-full max-w-full media-item-container overflow-y-auto overflow-x-hidden relative pb-4" :class="{ 'media-order-changed': orderChanged }">
|
||||||
<div v-if="!isPodcast && audioTracksCopy.length" class="w-full py-2">
|
<div v-if="!isPodcast && audioTracksCopy.length" class="w-full py-2">
|
||||||
<p class="text-base mb-2">Audio Tracks ({{ audioTracks.length }})</p>
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<p class="text-base">Audio Tracks ({{ audioTracks.length }})</p>
|
||||||
|
<p class="text-xs text-fg-muted px-2">{{ $strings.LabelTotalSize }}: {{ $bytesPretty(totalAudioSize) }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<draggable v-model="audioTracksCopy" v-bind="dragOptions" handle=".drag-handle" draggable=".item" tag="div" @start="drag = true" @end="drag = false" @update="draggableUpdate" :disabled="isIos">
|
<draggable v-model="audioTracksCopy" v-bind="dragOptions" handle=".drag-handle" draggable=".item" tag="div" @start="drag = true" @end="drag = false" @update="draggableUpdate" :disabled="isIos">
|
||||||
<transition-group type="transition" :name="!drag ? 'dragtrack' : null">
|
<transition-group type="transition" :name="!drag ? 'dragtrack' : null">
|
||||||
@@ -46,7 +49,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isPodcast" class="w-full py-2">
|
<div v-if="isPodcast" class="w-full py-2">
|
||||||
<p class="text-base mb-2">Episodes ({{ episodes.length }})</p>
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<p class="text-base">Episodes ({{ episodes.length }})</p>
|
||||||
|
<p class="text-xs text-fg-muted px-2">{{ $strings.LabelTotalSize }}: {{ $bytesPretty(totalEpisodesSize) }}</p>
|
||||||
|
</div>
|
||||||
<template v-for="episode in episodes">
|
<template v-for="episode in episodes">
|
||||||
<div :key="episode.id" class="flex items-center my-1">
|
<div :key="episode.id" class="flex items-center my-1">
|
||||||
<div class="w-10 h-12 flex items-center justify-center" style="min-width: 48px">
|
<div class="w-10 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||||
@@ -83,22 +89,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="otherFiles.length" class="text-lg py-2">Other Files</p>
|
<div v-if="otherFiles.length">
|
||||||
<template v-for="file in otherFiles">
|
<div class="flex justify-between items-center py-2">
|
||||||
<div :key="file.id" class="flex items-center my-1">
|
<p class="text-lg">Other Files</p>
|
||||||
<div class="w-12 h-12 flex items-center justify-center">
|
<p class="text-xs text-fg-muted px-2">{{ $strings.LabelTotalSize }}: {{ $bytesPretty(totalOtherFilesSize) }}</p>
|
||||||
<img v-if="(file.mimeType || '').startsWith('image')" :src="getCapImageSrc(file.contentUrl)" class="w-full h-full object-contain" />
|
|
||||||
<span v-else class="material-icons">music_note</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow px-2">
|
|
||||||
<p class="text-sm">{{ file.filename }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-24 text-center text-fg-muted" style="min-width: 96px">
|
|
||||||
<p class="text-xs">{{ file.mimeType }}</p>
|
|
||||||
<p class="text-sm">{{ $bytesPretty(file.size) }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<template v-for="file in otherFiles">
|
||||||
|
<div :key="file.id" class="flex items-center my-1">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center">
|
||||||
|
<img v-if="(file.mimeType || '').startsWith('image')" :src="getCapImageSrc(file.contentUrl)" class="w-full h-full object-contain" />
|
||||||
|
<span v-else class="material-icons">music_note</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow px-2">
|
||||||
|
<p class="text-sm">{{ file.filename }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-24 text-center text-fg-muted" style="min-width: 96px">
|
||||||
|
<p class="text-xs">{{ file.mimeType }}</p>
|
||||||
|
<p class="text-sm">{{ $bytesPretty(file.size) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-sm text-fg-muted">
|
||||||
|
{{ $strings.LabelTotalSize }}: {{ $bytesPretty(totalLibraryItemSize) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="px-2 w-full h-full">
|
<div v-else class="px-2 w-full h-full">
|
||||||
@@ -218,17 +233,27 @@ export default {
|
|||||||
},
|
},
|
||||||
dialogItems() {
|
dialogItems() {
|
||||||
if (this.selectedAudioTrack || this.selectedEpisode) {
|
if (this.selectedAudioTrack || this.selectedEpisode) {
|
||||||
return [
|
const items = [
|
||||||
{
|
{
|
||||||
text: this.$strings.ButtonDeleteLocalFile,
|
text: this.$strings.ButtonDeleteLocalFile,
|
||||||
value: 'track-delete'
|
value: 'track-delete',
|
||||||
|
icon: 'delete'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
if (this.isPodcast && this.selectedEpisode) {
|
||||||
|
items.unshift({
|
||||||
|
text: this.$strings.ButtonPlayEpisode,
|
||||||
|
value: 'play-episode',
|
||||||
|
icon: 'play_arrow'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: this.$strings.ButtonDeleteLocalItem,
|
text: this.$strings.ButtonDeleteLocalItem,
|
||||||
value: 'delete'
|
value: 'delete',
|
||||||
|
icon: 'delete'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -236,6 +261,18 @@ export default {
|
|||||||
playerIsStartingPlayback() {
|
playerIsStartingPlayback() {
|
||||||
// Play has been pressed and waiting for native play response
|
// Play has been pressed and waiting for native play response
|
||||||
return this.$store.state.playerIsStartingPlayback
|
return this.$store.state.playerIsStartingPlayback
|
||||||
|
},
|
||||||
|
totalAudioSize() {
|
||||||
|
return this.audioTracks.reduce((acc, item) => item.metadata ? acc + item.metadata.size : acc, 0)
|
||||||
|
},
|
||||||
|
totalEpisodesSize() {
|
||||||
|
return this.episodes.reduce((acc, item) => acc + item.size, 0)
|
||||||
|
},
|
||||||
|
totalOtherFilesSize() {
|
||||||
|
return this.otherFiles.reduce((acc, item) => acc + item.size, 0)
|
||||||
|
},
|
||||||
|
totalLibraryItemSize() {
|
||||||
|
return this.localFiles.reduce((acc, item) => acc + item.size, 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -291,6 +328,19 @@ export default {
|
|||||||
getCapImageSrc(contentUrl) {
|
getCapImageSrc(contentUrl) {
|
||||||
return Capacitor.convertFileSrc(contentUrl)
|
return Capacitor.convertFileSrc(contentUrl)
|
||||||
},
|
},
|
||||||
|
async playEpisode() {
|
||||||
|
if (!this.selectedEpisode) return
|
||||||
|
if (this.playerIsStartingPlayback) return
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
this.$store.commit('setPlayerIsStartingPlayback', this.selectedEpisode.serverEpisodeId)
|
||||||
|
|
||||||
|
this.$eventBus.$emit('play-item', {
|
||||||
|
libraryItemId: this.localLibraryItemId,
|
||||||
|
episodeId: this.selectedEpisode.id,
|
||||||
|
serverLibraryItemId: this.libraryItemId,
|
||||||
|
serverEpisodeId: this.selectedEpisode.serverEpisodeId
|
||||||
|
})
|
||||||
|
},
|
||||||
async dialogAction(action) {
|
async dialogAction(action) {
|
||||||
console.log('Dialog action', action)
|
console.log('Dialog action', action)
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
@@ -300,8 +350,10 @@ export default {
|
|||||||
} else if (action == 'track-delete') {
|
} else if (action == 'track-delete') {
|
||||||
if (this.isPodcast) this.deleteEpisode()
|
if (this.isPodcast) this.deleteEpisode()
|
||||||
else this.deleteTrack()
|
else this.deleteTrack()
|
||||||
|
} else if (action == 'play-episode') {
|
||||||
|
this.playEpisode()
|
||||||
}
|
}
|
||||||
this.showDialog = false
|
this.showDialog = false;
|
||||||
},
|
},
|
||||||
getLocalFileForTrack(localFileId) {
|
getLocalFileForTrack(localFileId) {
|
||||||
return this.localFiles.find((lf) => lf.id == localFileId)
|
return this.localFiles.find((lf) => lf.id == localFileId)
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export default {
|
|||||||
let lastKey = null
|
let lastKey = null
|
||||||
let numSaves = 0
|
let numSaves = 0
|
||||||
let numSyncs = 0
|
let numSyncs = 0
|
||||||
|
let lastSaveName = null
|
||||||
|
|
||||||
this.mediaEvents.forEach((evt) => {
|
this.mediaEvents.forEach((evt) => {
|
||||||
const date = this.$formatDate(evt.timestamp, 'MMM dd, yyyy')
|
const date = this.$formatDate(evt.timestamp, 'MMM dd, yyyy')
|
||||||
@@ -90,7 +91,8 @@ export default {
|
|||||||
|
|
||||||
// Collapse saves
|
// Collapse saves
|
||||||
if (evt.name === 'Save') {
|
if (evt.name === 'Save') {
|
||||||
if (numSaves > 0 && !keyUpdated) {
|
let saveName = evt.name + "-" + evt.serverSyncAttempted + "-" + evt.serverSyncSuccess
|
||||||
|
if (lastSaveName === saveName && numSaves > 0 && !keyUpdated) {
|
||||||
include = false
|
include = false
|
||||||
const totalInGroup = groups[key].length
|
const totalInGroup = groups[key].length
|
||||||
groups[key][totalInGroup - 1].num = numSaves
|
groups[key][totalInGroup - 1].num = numSaves
|
||||||
@@ -98,6 +100,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
numSaves = 1
|
numSaves = 1
|
||||||
}
|
}
|
||||||
|
lastSaveName = saveName
|
||||||
} else {
|
} else {
|
||||||
numSaves = 0
|
numSaves = 0
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modals-item-more-menu-modal v-model="showMoreMenu" :library-item="selectedLibraryItem" :episode="selectedEpisode" hide-rss-feed-option :processing.sync="processing" />
|
<modals-item-more-menu-modal v-model="showMoreMenu" :library-item="selectedLibraryItem" :episode="selectedEpisode" :playlist="playlist" hide-rss-feed-option :processing.sync="processing" />
|
||||||
<div v-show="processing" class="fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black/50 z-50">
|
<div v-show="processing" class="fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black/50 z-50">
|
||||||
<ui-loading-indicator />
|
<ui-loading-indicator />
|
||||||
</div>
|
</div>
|
||||||
@@ -140,8 +140,24 @@ export default {
|
|||||||
this.$eventBus.$emit('play-item', { libraryItemId: nextItem.libraryItemId, episodeId: nextItem.episodeId })
|
this.$eventBus.$emit('play-item', { libraryItemId: nextItem.libraryItemId, episodeId: nextItem.episodeId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
playlistUpdated(playlist) {
|
||||||
|
if (this.playlist.id !== playlist.id) return
|
||||||
|
this.playlist = playlist
|
||||||
|
},
|
||||||
|
playlistRemoved(playlist) {
|
||||||
|
if (this.playlist.id === playlist.id) {
|
||||||
|
this.$router.replace('/bookshelf/playlists')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {
|
||||||
|
this.$socket.$on('playlist_updated', this.playlistUpdated)
|
||||||
|
this.$socket.$on('playlist_removed', this.playlistRemoved)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$socket.$off('playlist_updated', this.playlistUpdated)
|
||||||
|
this.$socket.$off('playlist_removed', this.playlistRemoved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
+85
-1
@@ -61,6 +61,12 @@
|
|||||||
<p class="pl-4">{{ $strings.LabelEnableMp3IndexSeeking }}</p>
|
<p class="pl-4">{{ $strings.LabelEnableMp3IndexSeeking }}</p>
|
||||||
<span class="material-icons-outlined ml-2" @click.stop="showConfirmMp3IndexSeeking">info</span>
|
<span class="material-icons-outlined ml-2" @click.stop="showConfirmMp3IndexSeeking">info</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center py-3">
|
||||||
|
<div class="w-10 flex justify-center" @click="toggleAllowSeekingOnMediaControls">
|
||||||
|
<ui-toggle-switch v-model="settings.allowSeekingOnMediaControls" @input="saveSettings" />
|
||||||
|
</div>
|
||||||
|
<p class="pl-4">{{ $strings.LabelAllowSeekingOnMediaControls }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sleep timer settings -->
|
<!-- Sleep timer settings -->
|
||||||
<template v-if="!isiOS">
|
<template v-if="!isiOS">
|
||||||
@@ -129,6 +135,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Data settings -->
|
||||||
|
<p class="uppercase text-xs font-semibold text-fg-muted mb-2 mt-10">{{ $strings.HeaderDataSettings }}</p>
|
||||||
|
<div class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">{{ $strings.LabelDownloadUsingCellular }}</p>
|
||||||
|
<div @click.stop="showDownloadUsingCellularOptions">
|
||||||
|
<ui-text-input :value="downloadUsingCellularOption" readonly append-icon="expand_more" style="max-width: 200px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">{{ $strings.LabelStreamingUsingCellular }}</p>
|
||||||
|
<div @click.stop="showStreamingUsingCellularOptions">
|
||||||
|
<ui-text-input :value="streamingUsingCellularOption" readonly append-icon="expand_more" style="max-width: 200px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-show="loading" class="w-full h-full absolute top-0 left-0 flex items-center justify-center z-10">
|
<div v-show="loading" class="w-full h-full absolute top-0 left-0 flex items-center justify-center z-10">
|
||||||
<ui-loading-indicator />
|
<ui-loading-indicator />
|
||||||
</div>
|
</div>
|
||||||
@@ -154,6 +175,7 @@ export default {
|
|||||||
settings: {
|
settings: {
|
||||||
disableAutoRewind: false,
|
disableAutoRewind: false,
|
||||||
enableAltView: true,
|
enableAltView: true,
|
||||||
|
allowSeekingOnMediaControls: false,
|
||||||
jumpForwardTime: 10,
|
jumpForwardTime: 10,
|
||||||
jumpBackwardsTime: 10,
|
jumpBackwardsTime: 10,
|
||||||
enableMp3IndexSeeking: false,
|
enableMp3IndexSeeking: false,
|
||||||
@@ -169,7 +191,9 @@ export default {
|
|||||||
disableSleepTimerResetFeedback: false,
|
disableSleepTimerResetFeedback: false,
|
||||||
autoSleepTimerAutoRewind: false,
|
autoSleepTimerAutoRewind: false,
|
||||||
autoSleepTimerAutoRewindTime: 300000, // 5 minutes
|
autoSleepTimerAutoRewindTime: 300000, // 5 minutes
|
||||||
languageCode: 'en-us'
|
languageCode: 'en-us',
|
||||||
|
downloadUsingCellular: 'ALWAYS',
|
||||||
|
streamingUsingCellular: 'ALWAYS'
|
||||||
},
|
},
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
lockCurrentOrientation: false,
|
lockCurrentOrientation: false,
|
||||||
@@ -238,6 +262,34 @@ export default {
|
|||||||
text: this.$strings.LabelVeryHigh,
|
text: this.$strings.LabelVeryHigh,
|
||||||
value: 'VERY_HIGH'
|
value: 'VERY_HIGH'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
downloadUsingCellularItems: [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAskConfirmation,
|
||||||
|
value: 'ASK'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAlways,
|
||||||
|
value: 'ALWAYS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelNever,
|
||||||
|
value: 'NEVER'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
streamingUsingCellularItems: [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAskConfirmation,
|
||||||
|
value: 'ASK'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAlways,
|
||||||
|
value: 'ALWAYS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelNever,
|
||||||
|
value: 'NEVER'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -312,11 +364,21 @@ export default {
|
|||||||
const minutes = Number(this.settings.autoSleepTimerAutoRewindTime) / 1000 / 60
|
const minutes = Number(this.settings.autoSleepTimerAutoRewindTime) / 1000 / 60
|
||||||
return `${minutes} min`
|
return `${minutes} min`
|
||||||
},
|
},
|
||||||
|
downloadUsingCellularOption() {
|
||||||
|
const item = this.downloadUsingCellularItems.find((i) => i.value === this.settings.downloadUsingCellular)
|
||||||
|
return item?.text || 'Error'
|
||||||
|
},
|
||||||
|
streamingUsingCellularOption() {
|
||||||
|
const item = this.streamingUsingCellularItems.find((i) => i.value === this.settings.streamingUsingCellular)
|
||||||
|
return item?.text || 'Error'
|
||||||
|
},
|
||||||
moreMenuItems() {
|
moreMenuItems() {
|
||||||
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
|
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
|
||||||
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
|
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
|
||||||
else if (this.moreMenuSetting === 'language') return this.languageOptionItems
|
else if (this.moreMenuSetting === 'language') return this.languageOptionItems
|
||||||
else if (this.moreMenuSetting === 'theme') return this.themeOptionItems
|
else if (this.moreMenuSetting === 'theme') return this.themeOptionItems
|
||||||
|
else if (this.moreMenuSetting === 'downloadUsingCellular') return this.downloadUsingCellularItems
|
||||||
|
else if (this.moreMenuSetting === 'streamingUsingCellular') return this.streamingUsingCellularItems
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -351,6 +413,14 @@ export default {
|
|||||||
this.moreMenuSetting = 'theme'
|
this.moreMenuSetting = 'theme'
|
||||||
this.showMoreMenuDialog = true
|
this.showMoreMenuDialog = true
|
||||||
},
|
},
|
||||||
|
showDownloadUsingCellularOptions() {
|
||||||
|
this.moreMenuSetting = 'downloadUsingCellular'
|
||||||
|
this.showMoreMenuDialog = true
|
||||||
|
},
|
||||||
|
showStreamingUsingCellularOptions() {
|
||||||
|
this.moreMenuSetting = 'streamingUsingCellular'
|
||||||
|
this.showMoreMenuDialog = true
|
||||||
|
},
|
||||||
clickMenuAction(action) {
|
clickMenuAction(action) {
|
||||||
this.showMoreMenuDialog = false
|
this.showMoreMenuDialog = false
|
||||||
if (this.moreMenuSetting === 'shakeSensitivity') {
|
if (this.moreMenuSetting === 'shakeSensitivity') {
|
||||||
@@ -365,6 +435,12 @@ export default {
|
|||||||
} else if (this.moreMenuSetting === 'theme') {
|
} else if (this.moreMenuSetting === 'theme') {
|
||||||
this.theme = action
|
this.theme = action
|
||||||
this.saveTheme(action)
|
this.saveTheme(action)
|
||||||
|
} else if (this.moreMenuSetting === 'downloadUsingCellular') {
|
||||||
|
this.settings.downloadUsingCellular = action
|
||||||
|
this.saveSettings()
|
||||||
|
} else if (this.moreMenuSetting === 'streamingUsingCellular') {
|
||||||
|
this.settings.streamingUsingCellular = action
|
||||||
|
this.saveSettings()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveTheme(theme) {
|
saveTheme(theme) {
|
||||||
@@ -429,6 +505,10 @@ export default {
|
|||||||
this.settings.enableAltView = !this.settings.enableAltView
|
this.settings.enableAltView = !this.settings.enableAltView
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
toggleAllowSeekingOnMediaControls() {
|
||||||
|
this.settings.allowSeekingOnMediaControls = !this.settings.allowSeekingOnMediaControls
|
||||||
|
this.saveSettings()
|
||||||
|
},
|
||||||
getCurrentOrientation() {
|
getCurrentOrientation() {
|
||||||
const orientation = window.screen?.orientation || {}
|
const orientation = window.screen?.orientation || {}
|
||||||
const type = orientation.type || ''
|
const type = orientation.type || ''
|
||||||
@@ -471,6 +551,7 @@ export default {
|
|||||||
const deviceSettings = this.deviceData.deviceSettings || {}
|
const deviceSettings = this.deviceData.deviceSettings || {}
|
||||||
this.settings.disableAutoRewind = !!deviceSettings.disableAutoRewind
|
this.settings.disableAutoRewind = !!deviceSettings.disableAutoRewind
|
||||||
this.settings.enableAltView = !!deviceSettings.enableAltView
|
this.settings.enableAltView = !!deviceSettings.enableAltView
|
||||||
|
this.settings.allowSeekingOnMediaControls = !!deviceSettings.allowSeekingOnMediaControls
|
||||||
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
||||||
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
||||||
this.settings.enableMp3IndexSeeking = !!deviceSettings.enableMp3IndexSeeking
|
this.settings.enableMp3IndexSeeking = !!deviceSettings.enableMp3IndexSeeking
|
||||||
@@ -492,6 +573,9 @@ export default {
|
|||||||
this.settings.autoSleepTimerAutoRewindTime = !isNaN(deviceSettings.autoSleepTimerAutoRewindTime) ? deviceSettings.autoSleepTimerAutoRewindTime : 300000 // 5 minutes
|
this.settings.autoSleepTimerAutoRewindTime = !isNaN(deviceSettings.autoSleepTimerAutoRewindTime) ? deviceSettings.autoSleepTimerAutoRewindTime : 300000 // 5 minutes
|
||||||
|
|
||||||
this.settings.languageCode = deviceSettings.languageCode || 'en-us'
|
this.settings.languageCode = deviceSettings.languageCode || 'en-us'
|
||||||
|
|
||||||
|
this.settings.downloadUsingCellular = deviceSettings.downloadUsingCellular || 'ALWAYS'
|
||||||
|
this.settings.streamingUsingCellular = deviceSettings.streamingUsingCellular || 'ALWAYS'
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|||||||
+34
-29
@@ -1,38 +1,41 @@
|
|||||||
import Vue from "vue"
|
import Vue from 'vue'
|
||||||
import enUsStrings from '../strings/en-us.json'
|
import enUsStrings from '../strings/en-us.json'
|
||||||
|
|
||||||
const defaultCode = 'en-us'
|
const defaultCode = 'en-us'
|
||||||
let $localStore = null
|
let $localStore = null
|
||||||
|
|
||||||
const languageCodeMap = {
|
const languageCodeMap = {
|
||||||
'cs': { label: 'Čeština', dateFnsLocale: 'cs' },
|
cs: { label: 'Čeština', dateFnsLocale: 'cs' },
|
||||||
'da': { label: 'Dansk', dateFnsLocale: 'da' },
|
da: { label: 'Dansk', dateFnsLocale: 'da' },
|
||||||
'de': { label: 'Deutsch', dateFnsLocale: 'de' },
|
de: { label: 'Deutsch', dateFnsLocale: 'de' },
|
||||||
'en-us': { label: 'English', dateFnsLocale: 'enUS' },
|
'en-us': { label: 'English', dateFnsLocale: 'enUS' },
|
||||||
'es': { label: 'Español', dateFnsLocale: 'es' },
|
es: { label: 'Español', dateFnsLocale: 'es' },
|
||||||
'fr': { label: 'Français', dateFnsLocale: 'fr' },
|
fi: { label: 'Suomi', dateFnsLocale: 'fi' },
|
||||||
'hr': { label: 'Hrvatski', dateFnsLocale: 'hr' },
|
fr: { label: 'Français', dateFnsLocale: 'fr' },
|
||||||
'it': { label: 'Italiano', dateFnsLocale: 'it' },
|
hr: { label: 'Hrvatski', dateFnsLocale: 'hr' },
|
||||||
'lt': { label: 'Lietuvių', dateFnsLocale: 'lt' },
|
it: { label: 'Italiano', dateFnsLocale: 'it' },
|
||||||
'nl': { label: 'Nederlands', dateFnsLocale: 'nl' },
|
lt: { label: 'Lietuvių', dateFnsLocale: 'lt' },
|
||||||
'no': { label: 'Norsk', dateFnsLocale: 'no' },
|
hu: { label: 'Magyar', dateFnsLocale: 'hu' },
|
||||||
'pl': { label: 'Polski', dateFnsLocale: 'pl' },
|
nl: { label: 'Nederlands', dateFnsLocale: 'nl' },
|
||||||
'ru': { label: 'Русский', dateFnsLocale: 'ru' },
|
no: { label: 'Norsk', dateFnsLocale: 'no' },
|
||||||
'sv': { label: 'Svenska', dateFnsLocale: 'sv' },
|
pl: { label: 'Polski', dateFnsLocale: 'pl' },
|
||||||
'zh-cn': { label: '简体中文 (Simplified Chinese)', dateFnsLocale: 'zhCN' },
|
'pt-br': { label: 'Português (Brasil)', dateFnsLocale: 'ptBR' },
|
||||||
|
ru: { label: 'Русский', dateFnsLocale: 'ru' },
|
||||||
|
sv: { label: 'Svenska', dateFnsLocale: 'sv' },
|
||||||
|
uk: { label: 'Українська', dateFnsLocale: 'uk' },
|
||||||
|
'vi-vn': { label: 'Tiếng Việt', dateFnsLocale: 'vi' },
|
||||||
|
'zh-cn': { label: '简体中文 (Simplified Chinese)', dateFnsLocale: 'zhCN' }
|
||||||
}
|
}
|
||||||
|
|
||||||
function supplant(str, subs) {
|
function supplant(str, subs) {
|
||||||
// source: http://crockford.com/javascript/remedial.html
|
// source: http://crockford.com/javascript/remedial.html
|
||||||
return str.replace(/{([^{}]*)}/g,
|
return str.replace(/{([^{}]*)}/g, function (a, b) {
|
||||||
function (a, b) {
|
var r = subs[b]
|
||||||
var r = subs[b]
|
return typeof r === 'string' || typeof r === 'number' ? r : a
|
||||||
return typeof r === 'string' || typeof r === 'number' ? r : a
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(code => {
|
Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map((code) => {
|
||||||
return {
|
return {
|
||||||
text: languageCodeMap[code].label,
|
text: languageCodeMap[code].label,
|
||||||
value: code
|
value: code
|
||||||
@@ -62,12 +65,14 @@ var translations = {
|
|||||||
|
|
||||||
function loadTranslationStrings(code) {
|
function loadTranslationStrings(code) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
import(`../strings/${code}`).then((fileContents) => {
|
import(`../strings/${code}`)
|
||||||
resolve(fileContents.default)
|
.then((fileContents) => {
|
||||||
}).catch((error) => {
|
resolve(fileContents.default)
|
||||||
console.error('Failed to load i18n strings', code, error)
|
})
|
||||||
resolve(null)
|
.catch((error) => {
|
||||||
})
|
console.error('Failed to load i18n strings', code, error)
|
||||||
|
resolve(null)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ async function loadi18n(code) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const strings = translations[code] || await loadTranslationStrings(code)
|
const strings = translations[code] || (await loadTranslationStrings(code))
|
||||||
if (!strings) {
|
if (!strings) {
|
||||||
console.warn(`Invalid lang code ${code}`)
|
console.warn(`Invalid lang code ${code}`)
|
||||||
return false
|
return false
|
||||||
|
|||||||
+8
-5
@@ -48,11 +48,7 @@ class ServerSocket extends EventEmitter {
|
|||||||
this.socket.on('init', this.onInit.bind(this))
|
this.socket.on('init', this.onInit.bind(this))
|
||||||
this.socket.on('user_updated', this.onUserUpdated.bind(this))
|
this.socket.on('user_updated', this.onUserUpdated.bind(this))
|
||||||
this.socket.on('user_item_progress_updated', this.onUserItemProgressUpdated.bind(this))
|
this.socket.on('user_item_progress_updated', this.onUserItemProgressUpdated.bind(this))
|
||||||
|
this.socket.on('playlist_added', this.onPlaylistAdded.bind(this))
|
||||||
// Good for testing socket requests
|
|
||||||
// this.socket.onAny((evt, args) => {
|
|
||||||
// console.log(`[SOCKET] onAny: ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListeners() {
|
removeListeners() {
|
||||||
@@ -93,6 +89,13 @@ class ServerSocket extends EventEmitter {
|
|||||||
this.$store.commit('user/updateUserMediaProgress', payload.data)
|
this.$store.commit('user/updateUserMediaProgress', payload.data)
|
||||||
this.emit('user_media_progress_updated', payload)
|
this.emit('user_media_progress_updated', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPlaylistAdded() {
|
||||||
|
// Currently numUserPlaylists is only used for showing the playlist tab or not. Precise number is not necessary
|
||||||
|
if (!this.$store.state.libraries.numUserPlaylists) {
|
||||||
|
this.$store.commit('libraries/setNumUserPlaylists', 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ app, store }, inject) => {
|
export default ({ app, store }, inject) => {
|
||||||
|
|||||||
@@ -3,37 +3,40 @@
|
|||||||
Audiobookshelf is a self-hosted audiobook and podcast server.
|
Audiobookshelf is a self-hosted audiobook and podcast server.
|
||||||
|
|
||||||
### Android (beta)
|
### Android (beta)
|
||||||
|
|
||||||
Get the Android app on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
|
Get the Android app on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
|
||||||
|
|
||||||
### iOS (early beta)
|
### iOS (early beta)
|
||||||
**Beta is currently full. Apple has a hard limit of 10k beta testers. Updates will be posted in Discord/Matrix.**
|
|
||||||
|
|
||||||
Using Test Flight: https://testflight.apple.com/join/wiic7QIW ***(beta is full)***
|
**Beta is currently full. Apple has a hard limit of 10k beta testers. Updates will be posted in Discord.**
|
||||||
|
|
||||||
|
Using Test Flight: https://testflight.apple.com/join/wiic7QIW **_(beta is full)_**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[Go to the main project repo github.com/advplyr/audiobookshelf](https://github.com/advplyr/audiobookshelf) or the project site [audiobookshelf.org](https://audiobookshelf.org)
|
[Go to the main project repo github.com/advplyr/audiobookshelf](https://github.com/advplyr/audiobookshelf) or the project site [audiobookshelf.org](https://audiobookshelf.org)
|
||||||
|
|
||||||
Join us on [discord](https://discord.gg/pJsjuNCKRq) or [Matrix](https://matrix.to/#/#audiobookshelf:matrix.org)
|
Join us on [discord](https://discord.gg/pJsjuNCKRq)
|
||||||
|
|
||||||
**Requires an Audiobookshelf server to connect with**
|
**Requires an Audiobookshelf server to connect with**
|
||||||
|
|
||||||
<img alt="Screenshot" src="https://github.com/advplyr/audiobookshelf-app/raw/master/screenshots/DeviceDemoScreens.png" />
|
<img alt="Screenshot" src="https://github.com/advplyr/audiobookshelf-app/raw/master/screenshots/DeviceDemoScreens.png" />
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This application is built using [NuxtJS](https://nuxtjs.org/) and [Capacitor](https://capacitorjs.com/) in order to run on both iOS and Android on the same code base.
|
This application is built using [NuxtJS](https://nuxtjs.org/) and [Capacitor](https://capacitorjs.com/) in order to run on both iOS and Android on the same code base.
|
||||||
|
|
||||||
|
Information on helping with translations of the apps [here](https://www.audiobookshelf.org/faq#how-do-i-help-with-translations).
|
||||||
|
|
||||||
### Windows Environment Setup for Android
|
### Windows Environment Setup for Android
|
||||||
|
|
||||||
Required Software:
|
Required Software:
|
||||||
|
|
||||||
* [Git](https://git-scm.com/downloads)
|
- [Git](https://git-scm.com/downloads)
|
||||||
* [Node.js](https://nodejs.org/en/) (version 20)
|
- [Node.js](https://nodejs.org/en/) (version 20)
|
||||||
* Code editor of choice([VSCode](https://code.visualstudio.com/download), etc)
|
- Code editor of choice([VSCode](https://code.visualstudio.com/download), etc)
|
||||||
* [Android Studio](https://developer.android.com/studio)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
* [Android SDK](https://developer.android.com/studio)
|
- [Android SDK](https://developer.android.com/studio)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Install the required software with <a href=(https://docs.microsoft.com/en-us/windows/package-manager/winget/#production-recommended)>winget</a></summary>
|
<summary>Install the required software with <a href=(https://docs.microsoft.com/en-us/windows/package-manager/winget/#production-recommended)>winget</a></summary>
|
||||||
@@ -60,10 +63,10 @@ Your Windows environment should now be set up and ready to proceed!
|
|||||||
|
|
||||||
Required Software:
|
Required Software:
|
||||||
|
|
||||||
* [Android Studio](https://developer.android.com/studio)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
* [Node.js](https://nodejs.org/en/) (version 20)
|
- [Node.js](https://nodejs.org/en/) (version 20)
|
||||||
* [Cocoapods](https://guides.cocoapods.org/using/getting-started.html#installation)
|
- [Cocoapods](https://guides.cocoapods.org/using/getting-started.html#installation)
|
||||||
* [Android SDK](https://developer.android.com/studio)
|
- [Android SDK](https://developer.android.com/studio)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Install the required software with <a href=(https://brew.sh/)>homebrew</a></summary>
|
<summary>Install the required software with <a href=(https://brew.sh/)>homebrew</a></summary>
|
||||||
@@ -82,6 +85,7 @@ brew install android-studio node cocoapods
|
|||||||
Clone or fork the project from terminal or powershell and `cd` into the project directory.
|
Clone or fork the project from terminal or powershell and `cd` into the project directory.
|
||||||
|
|
||||||
Install the required node packages:
|
Install the required node packages:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
@@ -90,10 +94,12 @@ npm install
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Generate static web app:
|
Generate static web app:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm run generate
|
npm run generate
|
||||||
```
|
```
|
||||||
@@ -102,10 +108,12 @@ npm run generate
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Copy web app into native android/ios folders:
|
Copy web app into native android/ios folders:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npx cap sync
|
npx cap sync
|
||||||
```
|
```
|
||||||
@@ -114,6 +122,7 @@ npx cap sync
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -127,6 +136,7 @@ npx cap open android
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -136,15 +146,16 @@ Start coding!
|
|||||||
|
|
||||||
Required Software:
|
Required Software:
|
||||||
|
|
||||||
* [Xcode](https://developer.apple.com/xcode/)
|
- [Xcode](https://developer.apple.com/xcode/)
|
||||||
* [Node.js](https://nodejs.org/en/)
|
- [Node.js](https://nodejs.org/en/)
|
||||||
* [Cocoapods](https://guides.cocoapods.org/using/getting-started.html#installation)
|
- [Cocoapods](https://guides.cocoapods.org/using/getting-started.html#installation)
|
||||||
|
|
||||||
### Start working on the iOS app
|
### Start working on the iOS app
|
||||||
|
|
||||||
Clone or fork the project in the terminal and `cd` into the project directory.
|
Clone or fork the project in the terminal and `cd` into the project directory.
|
||||||
|
|
||||||
Install the required node packages:
|
Install the required node packages:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
@@ -153,10 +164,12 @@ npm install
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Generate static web app:
|
Generate static web app:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm run generate
|
npm run generate
|
||||||
```
|
```
|
||||||
@@ -165,10 +178,12 @@ npm run generate
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Copy web app into native android/ios folders:
|
Copy web app into native android/ios folders:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npx cap sync
|
npx cap sync
|
||||||
```
|
```
|
||||||
@@ -177,6 +192,7 @@ npx cap sync
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -190,6 +206,7 @@ npx cap open ios
|
|||||||
<summary>Expand for screenshot</summary>
|
<summary>Expand for screenshot</summary>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|||||||
+28
-11
@@ -31,16 +31,16 @@ export const state = () => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getCurrentPlaybackSessionId: state => {
|
getCurrentPlaybackSessionId: (state) => {
|
||||||
return state.currentPlaybackSession?.id || null
|
return state.currentPlaybackSession?.id || null
|
||||||
},
|
},
|
||||||
getIsPlayerOpen: state => {
|
getIsPlayerOpen: (state) => {
|
||||||
return !!state.currentPlaybackSession
|
return !!state.currentPlaybackSession
|
||||||
},
|
},
|
||||||
getIsCurrentSessionLocal: state => {
|
getIsCurrentSessionLocal: (state) => {
|
||||||
return state.currentPlaybackSession?.playMethod == PlayMethod.LOCAL
|
return state.currentPlaybackSession?.playMethod == PlayMethod.LOCAL
|
||||||
},
|
},
|
||||||
getIsMediaStreaming: state => (libraryItemId, episodeId) => {
|
getIsMediaStreaming: (state) => (libraryItemId, episodeId) => {
|
||||||
if (!state.currentPlaybackSession || !libraryItemId) return false
|
if (!state.currentPlaybackSession || !libraryItemId) return false
|
||||||
|
|
||||||
// Check using local library item id and local episode id
|
// Check using local library item id and local episode id
|
||||||
@@ -59,24 +59,32 @@ export const getters = {
|
|||||||
if (!episodeId) return true
|
if (!episodeId) return true
|
||||||
return state.currentPlaybackSession.episodeId === episodeId
|
return state.currentPlaybackSession.episodeId === episodeId
|
||||||
},
|
},
|
||||||
getServerSetting: state => key => {
|
getServerSetting: (state) => (key) => {
|
||||||
if (!state.serverSettings) return null
|
if (!state.serverSettings) return null
|
||||||
return state.serverSettings[key]
|
return state.serverSettings[key]
|
||||||
},
|
},
|
||||||
getJumpForwardTime: state => {
|
getJumpForwardTime: (state) => {
|
||||||
if (!state.deviceData?.deviceSettings) return 10
|
if (!state.deviceData?.deviceSettings) return 10
|
||||||
return state.deviceData.deviceSettings.jumpForwardTime || 10
|
return state.deviceData.deviceSettings.jumpForwardTime || 10
|
||||||
},
|
},
|
||||||
getJumpBackwardsTime: state => {
|
getJumpBackwardsTime: (state) => {
|
||||||
if (!state.deviceData?.deviceSettings) return 10
|
if (!state.deviceData?.deviceSettings) return 10
|
||||||
return state.deviceData.deviceSettings.jumpBackwardsTime || 10
|
return state.deviceData.deviceSettings.jumpBackwardsTime || 10
|
||||||
},
|
},
|
||||||
getAltViewEnabled: state => {
|
getAltViewEnabled: (state) => {
|
||||||
if (!state.deviceData?.deviceSettings) return true
|
if (!state.deviceData?.deviceSettings) return true
|
||||||
return state.deviceData.deviceSettings.enableAltView
|
return state.deviceData.deviceSettings.enableAltView
|
||||||
},
|
},
|
||||||
getOrientationLockSetting: state => {
|
getOrientationLockSetting: (state) => {
|
||||||
return state.deviceData?.deviceSettings?.lockOrientation
|
return state.deviceData?.deviceSettings?.lockOrientation
|
||||||
|
},
|
||||||
|
getCanDownloadUsingCellular: (state) => {
|
||||||
|
if (!state.deviceData?.deviceSettings?.downloadUsingCellular) return 'ALWAYS'
|
||||||
|
return state.deviceData.deviceSettings.downloadUsingCellular || 'ALWAYS'
|
||||||
|
},
|
||||||
|
getCanStreamingUsingCellular: (state) => {
|
||||||
|
if (!state.deviceData?.deviceSettings?.streamingUsingCellular) return 'ALWAYS'
|
||||||
|
return state.deviceData.deviceSettings.streamingUsingCellular || 'ALWAYS'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +124,7 @@ export const mutations = {
|
|||||||
setPlaybackSession(state, playbackSession) {
|
setPlaybackSession(state, playbackSession) {
|
||||||
state.currentPlaybackSession = playbackSession
|
state.currentPlaybackSession = playbackSession
|
||||||
|
|
||||||
state.isCasting = playbackSession?.mediaPlayer === "cast-player"
|
state.isCasting = playbackSession?.mediaPlayer === 'cast-player'
|
||||||
},
|
},
|
||||||
setMediaPlayer(state, mediaPlayer) {
|
setMediaPlayer(state, mediaPlayer) {
|
||||||
state.isCasting = mediaPlayer === 'cast-player'
|
state.isCasting = mediaPlayer === 'cast-player'
|
||||||
@@ -157,7 +165,16 @@ export const mutations = {
|
|||||||
state.isNetworkListenerInit = val
|
state.isNetworkListenerInit = val
|
||||||
},
|
},
|
||||||
setNetworkStatus(state, val) {
|
setNetworkStatus(state, val) {
|
||||||
state.networkConnected = val.connected
|
if (val.connectionType !== 'none') {
|
||||||
|
state.networkConnected = true
|
||||||
|
} else {
|
||||||
|
state.networkConnected = false
|
||||||
|
}
|
||||||
|
if (this.$platform === 'ios') {
|
||||||
|
// Capacitor Network plugin only shows ios device connected if internet access is available.
|
||||||
|
// This fix allows iOS users to use local servers without internet access.
|
||||||
|
state.networkConnected = true
|
||||||
|
}
|
||||||
state.networkConnectionType = val.connectionType
|
state.networkConnectionType = val.connectionType
|
||||||
},
|
},
|
||||||
setIsNetworkUnmetered(state, val) {
|
setIsNetworkUnmetered(state, val) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Browser } from '@capacitor/browser'
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
user: null,
|
user: null,
|
||||||
serverConnectionConfig: null,
|
serverConnectionConfig: null,
|
||||||
@@ -115,6 +117,21 @@ export const actions = {
|
|||||||
commit('setSettings', userSettings)
|
commit('setSettings', userSettings)
|
||||||
this.$eventBus.$emit('user-settings', state.settings)
|
this.$eventBus.$emit('user-settings', state.settings)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async openWebClient({ getters }, path = null) {
|
||||||
|
const serverAddress = getters.getServerAddress
|
||||||
|
if (!serverAddress) {
|
||||||
|
console.error('openWebClient: No server address')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let url = serverAddress.replace(/\/$/, '') // Remove trailing slash
|
||||||
|
if (path?.startsWith('/')) url += path
|
||||||
|
|
||||||
|
await Browser.open({ url })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening browser', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+314
@@ -0,0 +1,314 @@
|
|||||||
|
{
|
||||||
|
"ButtonAdd": "যোগ করুন",
|
||||||
|
"ButtonAddNewServer": "নতুন সার্ভার যোগ করুন",
|
||||||
|
"ButtonAuthors": "লেখকগণ",
|
||||||
|
"ButtonBack": "পেছনে যান",
|
||||||
|
"ButtonCancel": "বাতিল করুন",
|
||||||
|
"ButtonCancelTimer": "টাইমার বাতিল করুন",
|
||||||
|
"ButtonClearFilter": "ফিল্টার পরিষ্কার করুন",
|
||||||
|
"ButtonCloseFeed": "ফিড বন্ধ করুন",
|
||||||
|
"ButtonCollections": "সংগ্রহ",
|
||||||
|
"ButtonConnect": "সংযোগ করুন",
|
||||||
|
"ButtonConnectToServer": "সার্ভারের সাথে সংযোগ করুন",
|
||||||
|
"ButtonCreate": "তৈরি করুন",
|
||||||
|
"ButtonCreateBookmark": "বুকমার্ক তৈরি করুন",
|
||||||
|
"ButtonCreateNewPlaylist": "নতুন প্লেলিস্ট তৈরি করুন",
|
||||||
|
"ButtonDelete": "মুছুন",
|
||||||
|
"ButtonDeleteLocalEpisode": "স্থানীয় পর্ব মুছে ফেলুন",
|
||||||
|
"ButtonDeleteLocalFile": "স্থানীয় ফাইল মুছে ফেলুন",
|
||||||
|
"ButtonDeleteLocalItem": "স্থানীয় আইটেম মুছে ফেলুন",
|
||||||
|
"ButtonDisableAutoTimer": "স্বয়ংক্রিয় টাইমার নিষ্ক্রিয় করুন",
|
||||||
|
"ButtonDisconnect": "সংযোগ বিচ্ছিন্ন করুন",
|
||||||
|
"ButtonGoToWebClient": "ওয়েব ক্লায়েন্টে যান",
|
||||||
|
"ButtonHistory": "ইতিহাস",
|
||||||
|
"ButtonHome": "নীড়",
|
||||||
|
"ButtonIssues": "ইস্যু",
|
||||||
|
"ButtonLatest": "সর্বশেষ",
|
||||||
|
"ButtonLibrary": "লাইব্রেরি",
|
||||||
|
"ButtonLocalMedia": "স্থানীয় মিডিয়া",
|
||||||
|
"ButtonManageLocalFiles": "স্থানীয় ফাইলগুলো পরিচালনা করুন",
|
||||||
|
"ButtonNewFolder": "নতুন ফোল্ডার",
|
||||||
|
"ButtonNextEpisode": "পরবর্তী পর্ব",
|
||||||
|
"ButtonOpenFeed": "ফিড খুলুন",
|
||||||
|
"ButtonOverride": "ওভাররাইড",
|
||||||
|
"ButtonPause": "বিরতি",
|
||||||
|
"ButtonPlay": "বাজান",
|
||||||
|
"ButtonPlayEpisode": "পর্বটি চালান",
|
||||||
|
"ButtonPlaying": "বাজছে",
|
||||||
|
"ButtonPlaylists": "প্লেলিস্ট",
|
||||||
|
"ButtonRead": "পড়ুন",
|
||||||
|
"ButtonRemove": "মুছে ফেলুন",
|
||||||
|
"ButtonRemoveFromServer": "সার্ভার থেকে সরান",
|
||||||
|
"ButtonSave": "সংরক্ষণ করুন",
|
||||||
|
"ButtonSaveOrder": "অর্ডার সংরক্ষণ করুন",
|
||||||
|
"ButtonSearch": "অনুসন্ধান",
|
||||||
|
"ButtonSendEbookToDevice": "ডিভাইসে ইবুক পাঠান",
|
||||||
|
"ButtonSeries": "সিরিজ",
|
||||||
|
"ButtonSetTimer": "টাইমার সেট করুন",
|
||||||
|
"ButtonStream": "স্ট্রীম",
|
||||||
|
"ButtonSubmit": "জমা দিন",
|
||||||
|
"ButtonSwitchServerUser": "সার্ভার/ব্যবহারকারী পরিবর্তন করুন",
|
||||||
|
"ButtonUserStats": "ব্যবহারকারীর পরিসংখ্যান",
|
||||||
|
"ButtonYes": "হ্যাঁ",
|
||||||
|
"HeaderAccount": "অ্যাকাউন্ট",
|
||||||
|
"HeaderAdvanced": "অ্যাডভান্সড",
|
||||||
|
"HeaderAudioTracks": "অডিও ট্র্যাকসগুলো",
|
||||||
|
"HeaderChapters": "অধ্যায়",
|
||||||
|
"HeaderCollection": "সংগ্রহ",
|
||||||
|
"HeaderCollectionItems": "সংগ্রহ আইটেম",
|
||||||
|
"HeaderConnectionStatus": "সংযোগের অবস্থা",
|
||||||
|
"HeaderDataSettings": "ডেটা সেটিংস",
|
||||||
|
"HeaderDetails": "বিস্তারিত",
|
||||||
|
"HeaderDownloads": "ডাউনলোড",
|
||||||
|
"HeaderEbookFiles": "ই-বই ফাইল",
|
||||||
|
"HeaderEpisodes": "পর্ব",
|
||||||
|
"HeaderEreaderSettings": "ই-রিডার সেটিংস",
|
||||||
|
"HeaderLatestEpisodes": "সর্বশেষ পর্ব",
|
||||||
|
"HeaderLibraries": "লাইব্রেরি",
|
||||||
|
"HeaderLocalFolders": "স্থানীয় ফোল্ডার",
|
||||||
|
"HeaderLocalLibraryItems": "স্থানীয় লাইব্রেরি আইটেম",
|
||||||
|
"HeaderNewPlaylist": "নতুন প্লেলিস্ট",
|
||||||
|
"HeaderOpenRSSFeed": "আরএসএস ফিড খুলুন",
|
||||||
|
"HeaderPlaybackSettings": "প্লেব্যাক সেটিংস",
|
||||||
|
"HeaderPlaylist": "প্লেলিস্ট",
|
||||||
|
"HeaderPlaylistItems": "প্লেলিস্ট আইটেম",
|
||||||
|
"HeaderRSSFeed": "আরএসএস ফিডগুলো",
|
||||||
|
"HeaderRSSFeedGeneral": "আরএসএস বিবরণ",
|
||||||
|
"HeaderRSSFeedIsOpen": "আরএসএস ফিড খোলা আছে",
|
||||||
|
"HeaderSelectDownloadLocation": "ডাউনলোড অবস্থান নির্বাচন করুন",
|
||||||
|
"HeaderSettings": "সেটিংস",
|
||||||
|
"HeaderSleepTimer": "স্লিপ টাইমার",
|
||||||
|
"HeaderSleepTimerSettings": "স্লিপ টাইমার সেটিংস",
|
||||||
|
"HeaderStatsMinutesListeningChart": "মিনিট শ্রবণ (গত ৭ দিন)",
|
||||||
|
"HeaderStatsRecentSessions": "সাম্প্রতিক সেশন",
|
||||||
|
"HeaderTableOfContents": "বিষয়বস্তুর সারণী",
|
||||||
|
"HeaderUserInterfaceSettings": "ব্যবহারকারী ইন্টারফেস সেটিংস",
|
||||||
|
"HeaderYourStats": "আপনার পরিসংখ্যান",
|
||||||
|
"LabelAddToPlaylist": "প্লেলিস্টে যোগ করুন",
|
||||||
|
"LabelAdded": "যোগ করা হয়েছে",
|
||||||
|
"LabelAddedAt": "এতে যোগ করা হয়েছে",
|
||||||
|
"LabelAll": "সব",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "মিডিয়া বিজ্ঞপ্তি নিয়ন্ত্রণে অবস্থান খোঁজার অনুমতি দিন",
|
||||||
|
"LabelAlways": "সর্বদা",
|
||||||
|
"LabelAskConfirmation": "নিশ্চিতকরণের জন্য জিজ্ঞাসা করুন",
|
||||||
|
"LabelAuthor": "লেখক",
|
||||||
|
"LabelAuthorFirstLast": "লেখক (প্রথম শেষ)",
|
||||||
|
"LabelAuthorLastFirst": "লেখক (শেষ, প্রথম)",
|
||||||
|
"LabelAuthors": "লেখকগণ",
|
||||||
|
"LabelAutoDownloadEpisodes": "স্বয়ংক্রিয় ডাউনলোড পর্ব",
|
||||||
|
"LabelAutoRewindTime": "স্বয়ংক্রিয় রিওয়াইন্ড সময়",
|
||||||
|
"LabelAutoSleepTimer": "স্বয়ংক্রিয় ঘুমের টাইমার",
|
||||||
|
"LabelAutoSleepTimerAutoRewind": "অটো স্লিপ টাইমার অটো রিওয়াইন্ড",
|
||||||
|
"LabelAutoSleepTimerAutoRewindHelp": "অটো স্লিপ টাইমার শেষে আইটেমটি আবার চালানো হলে স্বয়ংক্রিয়ভাবে আপনার অবস্থান রিওয়াইন্ড করবে।",
|
||||||
|
"LabelAutoSleepTimerHelp": "নির্দিষ্ট শুরু এবং শেষ সময়ের মধ্যে মিডিয়া চালানোর সময় একটি স্লিপ টাইমার স্বয়ংক্রিয়ভাবে শুরু হবে।",
|
||||||
|
"LabelBooks": "বইগুলো",
|
||||||
|
"LabelChapterTrack": "অধ্যায় ট্র্যাক",
|
||||||
|
"LabelChapters": "অধ্যায়",
|
||||||
|
"LabelClosePlayer": "প্লেয়ার বন্ধ করুন",
|
||||||
|
"LabelCollapseSeries": "সিরিজ সঙ্কুচিত করুন",
|
||||||
|
"LabelComplete": "সম্পূর্ণ",
|
||||||
|
"LabelContinueBooks": "বই চালিয়ে যান",
|
||||||
|
"LabelContinueEpisodes": "পর্ব চালিয়ে যান",
|
||||||
|
"LabelContinueListening": "শোনা চালিয়ে যান",
|
||||||
|
"LabelContinueReading": "পড়া চালিয়ে যান",
|
||||||
|
"LabelContinueSeries": "সিরিজ চালিয়ে যান",
|
||||||
|
"LabelCustomTime": "কাস্টম সময়",
|
||||||
|
"LabelDescription": "বিবরণ",
|
||||||
|
"LabelDisableAudioFadeOut": "অডিও বিবর্ণ হওয়া অক্ষম করুন",
|
||||||
|
"LabelDisableAudioFadeOutHelp": "স্লিপ টাইমারে ১ মিনিটেরও কম সময় বাকি থাকলে অডিওর ভলিউম কমতে শুরু করবে। বিবর্ণ না হওয়ার জন্য এই সেটিংটি সক্ষম করুন।",
|
||||||
|
"LabelDisableAutoRewind": "অটো রিওয়াইন্ড অক্ষম করুন",
|
||||||
|
"LabelDisableShakeToReset": "ঝাঁকালে রিসেট হওয়া অক্ষম করুন",
|
||||||
|
"LabelDisableShakeToResetHelp": "টাইমার চলার সময় বা টাইমারের মেয়াদ শেষ হওয়ার ২ মিনিটের মধ্যে আপনার ডিভাইস ঝাঁকালে ঘুমের টাইমার রিসেট করবে। ঝাঁকালে রিসেট করা অক্ষম করতে এই সেটিংটি সক্ষম করুন।",
|
||||||
|
"LabelDisableVibrateOnReset": "রিসেট করার সময় কম্পন অক্ষম করুন",
|
||||||
|
"LabelDisableVibrateOnResetHelp": "স্লিপ টাইমার রিসেট হয়ে গেলে আপনার ডিভাইস কম্পিত হবে। স্লিপ টাইমার রিসেট করার সময় কম্পন না করতে এই সেটিংটি সক্ষম করুন।",
|
||||||
|
"LabelDiscover": "আবিষ্কার",
|
||||||
|
"LabelDownload": "ডাউনলোড করুন",
|
||||||
|
"LabelDownloadUsingCellular": "সেলুলার ব্যবহার করে ডাউনলোড করুন",
|
||||||
|
"LabelDownloaded": "ডাউনলোড করা হয়েছে",
|
||||||
|
"LabelDuration": "সময়কাল",
|
||||||
|
"LabelEbook": "ই-বই",
|
||||||
|
"LabelEbooks": "ই-বইগুলো",
|
||||||
|
"LabelEnable": "সক্ষম করুন",
|
||||||
|
"LabelEnableMp3IndexSeeking": "এমপি৩ সূচী খোঁজা সক্ষম করুন",
|
||||||
|
"LabelEnableMp3IndexSeekingHelp": "এই সেটিংটি শুধুমাত্র তখনই সক্রিয় করা উচিত যদি আপনার কাছে MP3 ফাইল থাকে যা সঠিকভাবে খুঁজছে না। ভেরিয়েবল বিটরেট (VBR) MP3 ফাইলের কারণে ভুল চাওয়া হয়। এই সেটিংটি সূচী খুঁজতে বাধ্য করবে, যেখানে ফাইলটি পড়ার সাথে-সাথে একটি টাইম-টু-বাইট ম্যাপিং তৈরি করা হয়। বড় MP3 ফাইলের সাথে কিছু ক্ষেত্রে ফাইলের শেষের দিকে খোঁজার সময় বিলম্ব হবে।",
|
||||||
|
"LabelEnd": "সমাপ্ত",
|
||||||
|
"LabelEndOfChapter": "অধ্যায়ের সমাপ্তি",
|
||||||
|
"LabelEndTime": "শেষ সময়",
|
||||||
|
"LabelEpisode": "পর্ব",
|
||||||
|
"LabelFeedURL": "ফিড ইউআরএল",
|
||||||
|
"LabelFile": "ফাইল",
|
||||||
|
"LabelFileBirthtime": "ফাইল জন্মের সময়",
|
||||||
|
"LabelFileModified": "ফাইল পরিবর্তিত",
|
||||||
|
"LabelFilename": "ফাইলের নাম",
|
||||||
|
"LabelFinished": "সমাপ্ত",
|
||||||
|
"LabelFolder": "ফোল্ডার",
|
||||||
|
"LabelFontBoldness": "হরফ বোল্ডনেস",
|
||||||
|
"LabelFontScale": "ফন্ট স্কেল",
|
||||||
|
"LabelGenre": "ঘরানা",
|
||||||
|
"LabelGenres": "ঘরানাগুলো",
|
||||||
|
"LabelHapticFeedback": "হ্যাপটিক প্রতিক্রিয়া",
|
||||||
|
"LabelHasEbook": "ই-বই আছে",
|
||||||
|
"LabelHasSupplementaryEbook": "পরিপূরক ই-বই আছে",
|
||||||
|
"LabelHeavy": "ভারী",
|
||||||
|
"LabelHigh": "উচু",
|
||||||
|
"LabelHost": "নিমন্ত্রণকর্তা",
|
||||||
|
"LabelInProgress": "প্রগতিতে আছে",
|
||||||
|
"LabelIncomplete": "অসম্পূর্ণ",
|
||||||
|
"LabelInternalAppStorage": "অভ্যন্তরীণ অ্যাপ স্টোরেজ",
|
||||||
|
"LabelJumpBackwardsTime": "পিছনের সময়ে ঝাঁপ",
|
||||||
|
"LabelJumpForwardsTime": "সামনের সময়ে ঝাঁপ",
|
||||||
|
"LabelLanguage": "ভাষা",
|
||||||
|
"LabelLayout": "লেআউট",
|
||||||
|
"LabelLayoutAuto": "স্বয়ংক্রিয়",
|
||||||
|
"LabelLayoutSinglePage": "একক পৃষ্ঠা",
|
||||||
|
"LabelLight": "আলো",
|
||||||
|
"LabelLineSpacing": "লাইন স্পেসিং",
|
||||||
|
"LabelListenAgain": "আবার শুনুন",
|
||||||
|
"LabelLocalBooks": "স্থানীয় বই",
|
||||||
|
"LabelLocalPodcasts": "স্থানীয় পডকাস্ট",
|
||||||
|
"LabelLockOrientation": "ওরিয়েন্টেশন লক করুন",
|
||||||
|
"LabelLockPlayer": "প্লেয়ার লক করুন",
|
||||||
|
"LabelLow": "নিচু",
|
||||||
|
"LabelMediaType": "মিডিয়ার ধরন",
|
||||||
|
"LabelMedium": "মাঝারি",
|
||||||
|
"LabelMore": "আরো",
|
||||||
|
"LabelMoreInfo": "আরো তথ্য",
|
||||||
|
"LabelName": "নাম",
|
||||||
|
"LabelNarrator": "কথক",
|
||||||
|
"LabelNarrators": "কথক",
|
||||||
|
"LabelNavigateWithVolume": "ভলিউম বাটন দিয়ে নেভিগেট করুন",
|
||||||
|
"LabelNavigateWithVolumeDisabled": "বন্ধ",
|
||||||
|
"LabelNavigateWithVolumeEnabled": "চালু",
|
||||||
|
"LabelNavigateWithVolumeMirrored": "মিরর করা হয়েছে",
|
||||||
|
"LabelNever": "কখনও না",
|
||||||
|
"LabelNewestAuthors": "নতুন লেখক",
|
||||||
|
"LabelNewestEpisodes": "নতুনতম পর্ব",
|
||||||
|
"LabelNo": "না",
|
||||||
|
"LabelNotFinished": "সমাপ্ত হয়নি",
|
||||||
|
"LabelNotStarted": "শুরু হয়নি",
|
||||||
|
"LabelOff": "বন্ধ",
|
||||||
|
"LabelPassword": "পাসওয়ার্ড",
|
||||||
|
"LabelPath": "পথ",
|
||||||
|
"LabelPlaybackDirect": "সরাসরি",
|
||||||
|
"LabelPlaybackLocal": "স্থানীয়",
|
||||||
|
"LabelPlaybackSpeed": "প্লেব্যাকের গতি",
|
||||||
|
"LabelPlaybackTranscode": "ট্রান্সকোড",
|
||||||
|
"LabelPodcast": "পডকাস্ট",
|
||||||
|
"LabelPodcasts": "পডকাস্টগুলো",
|
||||||
|
"LabelPreventIndexing": "আইটিউনস এবং গুগল পডকাস্ট ডিরেক্টরি দ্বারা আপনার ফিডকে ইন্ডেক্স করা থেকে বিরত রাখুন",
|
||||||
|
"LabelProgress": "প্রগতি",
|
||||||
|
"LabelPubDate": "প্রকাশের তারিখ",
|
||||||
|
"LabelPublishYear": "প্রকাশের বছর",
|
||||||
|
"LabelRSSFeedCustomOwnerEmail": "কাস্টম মালিকের ইমেইল",
|
||||||
|
"LabelRSSFeedCustomOwnerName": "কাস্টম মালিকের নাম",
|
||||||
|
"LabelRSSFeedPreventIndexing": "সূচীকরণ প্রতিরোধ করুন",
|
||||||
|
"LabelRSSFeedSlug": "আরএসএস ফিড স্লাগ",
|
||||||
|
"LabelRandomly": "এলোমেলোভাবে",
|
||||||
|
"LabelRead": "পড়ুন",
|
||||||
|
"LabelReadAgain": "আবার পড়ুন",
|
||||||
|
"LabelRecentSeries": "সাম্প্রতিক সিরিজ",
|
||||||
|
"LabelRecentlyAdded": "সম্প্রতি যোগ করা হয়েছে",
|
||||||
|
"LabelRemoveFromPlaylist": "প্লেলিস্ট থেকে সরান",
|
||||||
|
"LabelScaleElapsedTimeBySpeed": "অতিবাহিত সময় গতি দ্বারা স্কেল করুন",
|
||||||
|
"LabelSeason": "সেশন",
|
||||||
|
"LabelSelectADevice": "একটি ডিভাইস নির্বাচন করুন",
|
||||||
|
"LabelSeries": "সিরিজ",
|
||||||
|
"LabelServerAddress": "সার্ভার ঠিকানা",
|
||||||
|
"LabelSetEbookAsPrimary": "প্রাথমিক হিসাবে সেট করুন",
|
||||||
|
"LabelSetEbookAsSupplementary": "পরিপূরক হিসেবে সেট করুন",
|
||||||
|
"LabelShakeSensitivity": "ঝাঁকুনির সংবেদনশীলতা",
|
||||||
|
"LabelShowAll": "সব দেখান",
|
||||||
|
"LabelSize": "আকার",
|
||||||
|
"LabelSleepTimer": "স্লিপ টাইমার",
|
||||||
|
"LabelStart": "শুরু",
|
||||||
|
"LabelStartTime": "শুরুর সময়",
|
||||||
|
"LabelStatsBestDay": "সেরা দিন",
|
||||||
|
"LabelStatsDailyAverage": "দৈনিক গড়",
|
||||||
|
"LabelStatsDays": "দিন",
|
||||||
|
"LabelStatsDaysListened": "যেদিন শোনা হয়েছে",
|
||||||
|
"LabelStatsInARow": "এক সারিতে",
|
||||||
|
"LabelStatsItemsFinished": "আইটেম সমাপ্ত",
|
||||||
|
"LabelStatsMinutes": "মিনিট",
|
||||||
|
"LabelStatsMinutesListening": "মিনিট শুনছেন",
|
||||||
|
"LabelStatsWeekListening": "সপ্তাহ শোনা",
|
||||||
|
"LabelStreamingUsingCellular": "সেলুলার ব্যবহার করে স্ট্রিমিং",
|
||||||
|
"LabelTag": "ট্যাগ",
|
||||||
|
"LabelTags": "ট্যাগগুলো",
|
||||||
|
"LabelTheme": "থিম",
|
||||||
|
"LabelThemeDark": "অন্ধকার",
|
||||||
|
"LabelThemeLight": "আলো",
|
||||||
|
"LabelTimeRemaining": "{0}টি অবশিষ্ট",
|
||||||
|
"LabelTitle": "শিরোনাম",
|
||||||
|
"LabelTotalSize": "মোট আকার",
|
||||||
|
"LabelTotalTrack": "মোট ট্র্যাক",
|
||||||
|
"LabelTracks": "ট্র্যাকস",
|
||||||
|
"LabelType": "টাইপ",
|
||||||
|
"LabelUnlockPlayer": "প্লেয়ার আনলক করুন",
|
||||||
|
"LabelUseBookshelfView": "বুকশেলফ ভিউ ব্যবহার করুন",
|
||||||
|
"LabelUser": "ব্যবহারকারী",
|
||||||
|
"LabelUsername": "ব্যবহারকারীর নাম",
|
||||||
|
"LabelVeryHigh": "অত্যন্ত উচ্চ",
|
||||||
|
"LabelVeryLow": "খুবই কম",
|
||||||
|
"LabelYourBookmarks": "আপনার বুকমার্কস",
|
||||||
|
"LabelYourProgress": "আপনার অগ্রগতি",
|
||||||
|
"MessageAndroid10Downloads": "অ্যান্ড্রয়েড ১০ এবং এর নীচের সংস্করণগুলি ডাউনলোডের জন্য অভ্যন্তরীণ অ্যাপ স্টোরেজ ব্যবহার করবে।",
|
||||||
|
"MessageAttemptingServerConnection": "সার্ভার সংযোগের চেষ্টা করা হচ্ছে..।",
|
||||||
|
"MessageAudiobookshelfServerNotConnected": "অডিওবুকশেল্ফ সার্ভার সংযুক্ত করা যায়নি",
|
||||||
|
"MessageAudiobookshelfServerRequired": "<strong>গুরুত্বপূর্ণ!</strong> এই অ্যাপটি এমন একটি অডিওবুকশেল্ফ সার্ভারের সাথে কাজ করার জন্য ডিজাইন করা হয়েছে যা আপনি বা আপনার পরিচিত কেউ হোস্ট করছেন৷ এই অ্যাপ্লিকেশনটি নিজে থেকে কোন বিষয়বস্তু প্রদান করে না।",
|
||||||
|
"MessageBookshelfEmpty": "বুকশেলফ খালি",
|
||||||
|
"MessageConfirmDeleteLocalEpisode": "আপনার ডিভাইস থেকে স্থানীয় পর্ব \"{0}\" সরাবেন? সার্ভারে ফাইলটি প্রভাবিত হবে না।",
|
||||||
|
"MessageConfirmDeleteLocalFiles": "আপনার ডিভাইস থেকে এই আইটেমটির স্থানীয় ফাইলগুলি সরাবেন? সার্ভারে থাকা ফাইল এবং আপনার অগ্রগতি প্রভাবিত হবে না।",
|
||||||
|
"MessageConfirmDiscardProgress": "আপনি কি নিশ্চিত আপনার অগ্রগতি পুনরায় সেট করতে চান?",
|
||||||
|
"MessageConfirmDownloadUsingCellular": "আপনি সেলুলার ডেটা ব্যবহার করে ডাউনলোড করতে চলেছেন৷ এর মধ্যে ক্যারিয়ার ডেটা চার্জ অন্তর্ভুক্ত থাকতে পারে। আপনি কি চালিয়ে যেতে চান?",
|
||||||
|
"MessageConfirmMarkAsFinished": "আপনি কি এই আইটেমটিকে সমাপ্ত হিসাবে চিহ্নিত করার বিষয়ে নিশ্চিত?",
|
||||||
|
"MessageConfirmRemoveBookmark": "আপনি কি বুকমার্ক সরানোর বিষয়ে নিশ্চিত?",
|
||||||
|
"MessageConfirmStreamingUsingCellular": "আপনি সেলুলার ডেটা ব্যবহার করে স্ট্রিম করতে চলেছেন৷ এর মধ্যে ক্যারিয়ার ডেটা চার্জ অন্তর্ভুক্ত থাকতে পারে। আপনি কি চালিয়ে যেতে চান?",
|
||||||
|
"MessageDiscardProgress": "অগ্রগতি বাতিল করুন",
|
||||||
|
"MessageDownloadCompleteProcessing": "ডাউনলোড সম্পূর্ণ। প্রক্রিয়া করা হচ্ছে..।",
|
||||||
|
"MessageDownloading": "ডাউনলোড হচ্ছে..।",
|
||||||
|
"MessageDownloadingEpisode": "ডাউনলোডিং পর্ব",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} পর্ব(গুলি) ডাউনলোডের জন্য সারিবদ্ধ",
|
||||||
|
"MessageFeedURLWillBe": "ফিড URL হবে {0}",
|
||||||
|
"MessageFetching": "আনয় হচ্ছে.।",
|
||||||
|
"MessageFollowTheProjectOnGithub": "গিটহাবে প্রকল্পটিকে অনুসরণ করুন",
|
||||||
|
"MessageItemDownloadCompleteFailedToCreate": "আইটেম ডাউনলোড সম্পূর্ণ হয়েছে কিন্তু লাইব্রেরি আইটেম তৈরি করতে ব্যর্থ হয়েছে",
|
||||||
|
"MessageLoading": "লোড হচ্ছে.।",
|
||||||
|
"MessageLoadingServerData": "সার্ভার ডেটা লোড হচ্ছে..।",
|
||||||
|
"MessageMarkAsFinished": "সমাপ্ত হিসাবে চিহ্নিত করুন",
|
||||||
|
"MessageMediaLinkedToADifferentServer": "মিডিয়া একটি ভিন্ন ঠিকানায় একটি অডিওবুকশেল্ফ সার্ভারের সাথে লিঙ্ক করা হয়েছে ({0})৷ এই সার্ভার ঠিকানার সাথে সংযুক্ত হলে অগ্রগতি সিঙ্ক করা হবে।",
|
||||||
|
"MessageMediaLinkedToADifferentUser": "মিডিয়া এই সার্ভারের সাথে লিঙ্ক করা হয়েছে কিন্তু অন্য ব্যবহারকারী দ্বারা ডাউনলোড করা হয়েছে৷ অগ্রগতি শুধুমাত্র সেই ব্যবহারকারীর সাথে সিঙ্ক করা হবে যে এটি ডাউনলোড করেছে।",
|
||||||
|
"MessageMediaLinkedToServer": "সার্ভারের সাথে লিঙ্ক করা হয়েছে {0}",
|
||||||
|
"MessageMediaLinkedToThisServer": "ডাউনলোড করা মিডিয়া এই সার্ভারের সাথে লিঙ্ক করা হয়েছে",
|
||||||
|
"MessageMediaNotLinkedToServer": "মিডিয়া কোন অডিওবুকশেল্ফ সার্ভারের সাথে লিঙ্ক করা নেই৷ কোন অগ্রগতি সিঙ্ক করা হবে না।",
|
||||||
|
"MessageNoBookmarks": "কোন বুকমার্ক নেই",
|
||||||
|
"MessageNoChapters": "কোনও অধ্যায় নেই",
|
||||||
|
"MessageNoItems": "কোন আইটেম নেই",
|
||||||
|
"MessageNoItemsFound": "কোন আইটেম পাওয়া যায়নি",
|
||||||
|
"MessageNoListeningSessions": "কোনও শোনার সেশন নেই",
|
||||||
|
"MessageNoMediaFolders": "কোনো মিডিয়া ফোল্ডার নেই",
|
||||||
|
"MessageNoNetworkConnection": "নেটওয়ার্ক সংযোগ নেই",
|
||||||
|
"MessageNoPodcastsFound": "কোন পডকাস্ট পাওয়া যায়নি",
|
||||||
|
"MessageNoSeries": "কোনো সিরিজ নেই",
|
||||||
|
"MessageNoUpdatesWereNecessary": "কোন আপডেটের প্রয়োজন ছিল না",
|
||||||
|
"MessageNoUserPlaylists": "আপনার কোনো প্লেলিস্ট নেই",
|
||||||
|
"MessageReportBugsAndContribute": "বাগ রিপোর্ট করুন, বৈশিষ্ট্যের অনুরোধ করুন এবং এতে অবদান রাখুন",
|
||||||
|
"MessageSocketConnectedOverMeteredCellular": "মিটারযুক্ত সেলুলারের দ্বারা সকেট সংযোগ করা হয়েছে",
|
||||||
|
"MessageSocketConnectedOverMeteredWifi": "মিটারযুক্ত ওয়াইফাইয়ের মাধ্যমে সকেট সংযুক্ত",
|
||||||
|
"MessageSocketConnectedOverUnmeteredCellular": "মিটারবিহীন সেলুলারের দ্বারা সকেট সংযোগ করা হয়েছে",
|
||||||
|
"MessageSocketConnectedOverUnmeteredWifi": "মিটারবিহীন ওয়াইফাইয়ের মাধ্যমে সকেট সংযুক্ত",
|
||||||
|
"MessageSocketNotConnected": "সকেট সংযুক্ত নয়",
|
||||||
|
"NoteRSSFeedPodcastAppsHttps": "সতর্কতা: বেশিরভাগ পডকাস্ট অ্যাপের জন্য প্রয়োজন হবে RSS ফিড URL যেটি HTTPS ব্যবহার করছে",
|
||||||
|
"NoteRSSFeedPodcastAppsPubDate": "সতর্কতা: আপনার 1 বা তার বেশি পর্বের একটি পাব তারিখ নেই। কিছু পডকাস্ট অ্যাপের এটি প্রয়োজন।",
|
||||||
|
"ToastBookmarkCreateFailed": "বুকমার্ক তৈরি করতে ব্যর্থ",
|
||||||
|
"ToastBookmarkRemoveFailed": "বুকমার্ক সরাতে ব্যর্থ",
|
||||||
|
"ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastDownloadNotAllowedOnCellular": "সেলুলার ডেটাতে ডাউনলোড করার অনুমতি নেই",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "সমাপ্ত হিসাবে চিহ্নিত করতে ব্যর্থ",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "সমাপ্ত হয়নি হিসাবে চিহ্নিত করতে ব্যর্থ",
|
||||||
|
"ToastPlaylistCreateFailed": "প্লেলিস্ট তৈরি করতে ব্যর্থ",
|
||||||
|
"ToastPodcastCreateFailed": "পডকাস্ট তৈরি করতে ব্যর্থ",
|
||||||
|
"ToastPodcastCreateSuccess": "পডকাস্ট সফলভাবে তৈরি করা হয়েছে",
|
||||||
|
"ToastRSSFeedCloseFailed": "RSS ফিড বন্ধ করতে ব্যর্থ",
|
||||||
|
"ToastRSSFeedCloseSuccess": "RSS ফিড বন্ধ",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "সেলুলার ডেটাতে স্ট্রিমিং অনুমোদিত নয়"
|
||||||
|
}
|
||||||
+181
-159
@@ -1,52 +1,54 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Přidat",
|
"ButtonAdd": "Přidat",
|
||||||
"ButtonAddNewServer": "Add New Server",
|
"ButtonAddNewServer": "Přidat nový server",
|
||||||
"ButtonAuthors": "Autoři",
|
"ButtonAuthors": "Autoři",
|
||||||
"ButtonBack": "Back",
|
"ButtonBack": "Zpět",
|
||||||
"ButtonCancel": "Zrušit",
|
"ButtonCancel": "Zrušit",
|
||||||
"ButtonCancelTimer": "Cancel Timer",
|
"ButtonCancelTimer": "Zrušit časovač",
|
||||||
"ButtonClearFilter": "Vymazat filtr",
|
"ButtonClearFilter": "Vymazat filtr",
|
||||||
"ButtonCloseFeed": "Zavřít kanál",
|
"ButtonCloseFeed": "Zavřít kanál",
|
||||||
"ButtonCollections": "Kolekce",
|
"ButtonCollections": "Kolekce",
|
||||||
"ButtonConnect": "Connect",
|
"ButtonConnect": "Připojit",
|
||||||
"ButtonConnectToServer": "Connect to Server",
|
"ButtonConnectToServer": "Připojit k serveru",
|
||||||
"ButtonCreate": "Vytvořit",
|
"ButtonCreate": "Vytvořit",
|
||||||
"ButtonCreateBookmark": "Create Bookmark",
|
"ButtonCreateBookmark": "Vytvořit záložku",
|
||||||
"ButtonCreateNewPlaylist": "Create New Playlist",
|
"ButtonCreateNewPlaylist": "Vytvořit nový seznam skladeb",
|
||||||
"ButtonDelete": "Smazat",
|
"ButtonDelete": "Smazat",
|
||||||
"ButtonDeleteLocalEpisode": "Delete local episode",
|
"ButtonDeleteLocalEpisode": "Smazat místní epizodu",
|
||||||
"ButtonDeleteLocalFile": "Delete local file",
|
"ButtonDeleteLocalFile": "Smazat místní soubor",
|
||||||
"ButtonDeleteLocalItem": "Delete local item",
|
"ButtonDeleteLocalItem": "Smazat místní položku",
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
"ButtonDisableAutoTimer": "Zakázat automatický časovač",
|
||||||
"ButtonDisconnect": "Disconnect",
|
"ButtonDisconnect": "Odpojit",
|
||||||
"ButtonHistory": "History",
|
"ButtonGoToWebClient": "Otevřít webového klienta",
|
||||||
|
"ButtonHistory": "Historie",
|
||||||
"ButtonHome": "Domů",
|
"ButtonHome": "Domů",
|
||||||
"ButtonIssues": "Problémy",
|
"ButtonIssues": "Problémy",
|
||||||
"ButtonLatest": "Nejnovější",
|
"ButtonLatest": "Nejnovější",
|
||||||
"ButtonLibrary": "Knihovna",
|
"ButtonLibrary": "Knihovna",
|
||||||
"ButtonLocalMedia": "Local Media",
|
"ButtonLocalMedia": "Místní média",
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
"ButtonManageLocalFiles": "Spravovat místní soubory",
|
||||||
"ButtonNewFolder": "New Folder",
|
"ButtonNewFolder": "Nová složka",
|
||||||
"ButtonNextEpisode": "Next Episode",
|
"ButtonNextEpisode": "Další epizoda",
|
||||||
"ButtonOpenFeed": "Otevřít kanál",
|
"ButtonOpenFeed": "Otevřít kanál",
|
||||||
"ButtonOverride": "Override",
|
"ButtonOverride": "Přepsat",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pozastavit",
|
||||||
"ButtonPlay": "Přehrát",
|
"ButtonPlay": "Přehrát",
|
||||||
|
"ButtonPlayEpisode": "Přehrát epizodu",
|
||||||
"ButtonPlaying": "Hraje",
|
"ButtonPlaying": "Hraje",
|
||||||
"ButtonPlaylists": "Seznamy skladeb",
|
"ButtonPlaylists": "Seznamy skladeb",
|
||||||
"ButtonRead": "Číst",
|
"ButtonRead": "Číst",
|
||||||
"ButtonRemove": "Odstranit",
|
"ButtonRemove": "Odstranit",
|
||||||
"ButtonRemoveFromServer": "Remove from Server",
|
"ButtonRemoveFromServer": "Odstranit ze serveru",
|
||||||
"ButtonSave": "Uložit",
|
"ButtonSave": "Uložit",
|
||||||
"ButtonSaveOrder": "Save Order",
|
"ButtonSaveOrder": "Uložit pořadí",
|
||||||
"ButtonSearch": "Hledat",
|
"ButtonSearch": "Hledat",
|
||||||
"ButtonSendEbookToDevice": "Send Ebook to Device",
|
"ButtonSendEbookToDevice": "Odeslat e-knihu na zařízení",
|
||||||
"ButtonSeries": "Série",
|
"ButtonSeries": "Série",
|
||||||
"ButtonSetTimer": "Set Timer",
|
"ButtonSetTimer": "Nastavit časovač",
|
||||||
"ButtonStream": "Stream",
|
"ButtonStream": "Streamovat",
|
||||||
"ButtonSubmit": "Odeslat",
|
"ButtonSubmit": "Odeslat",
|
||||||
"ButtonSwitchServerUser": "Switch Server/User",
|
"ButtonSwitchServerUser": "Přepnout server/uživatele",
|
||||||
"ButtonUserStats": "User Stats",
|
"ButtonUserStats": "Statistiky uživatele",
|
||||||
"ButtonYes": "Ano",
|
"ButtonYes": "Ano",
|
||||||
"HeaderAccount": "Účet",
|
"HeaderAccount": "Účet",
|
||||||
"HeaderAdvanced": "Pokročilé",
|
"HeaderAdvanced": "Pokročilé",
|
||||||
@@ -54,239 +56,259 @@
|
|||||||
"HeaderChapters": "Kapitoly",
|
"HeaderChapters": "Kapitoly",
|
||||||
"HeaderCollection": "Kolekce",
|
"HeaderCollection": "Kolekce",
|
||||||
"HeaderCollectionItems": "Položky kolekce",
|
"HeaderCollectionItems": "Položky kolekce",
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
"HeaderConnectionStatus": "Stav připojení",
|
||||||
|
"HeaderDataSettings": "Nastavení dat",
|
||||||
"HeaderDetails": "Podrobnosti",
|
"HeaderDetails": "Podrobnosti",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Stahování",
|
||||||
"HeaderEbookFiles": "Soubory elektronických knih",
|
"HeaderEbookFiles": "Soubory e-knih",
|
||||||
"HeaderEpisodes": "Epizody",
|
"HeaderEpisodes": "Epizody",
|
||||||
"HeaderEreaderSettings": "Nastavení čtečky elektronických knih",
|
"HeaderEreaderSettings": "Nastavení čtečky e-knih",
|
||||||
"HeaderLatestEpisodes": "Poslední epizody",
|
"HeaderLatestEpisodes": "Nejnovější epizody",
|
||||||
"HeaderLibraries": "Knihovny",
|
"HeaderLibraries": "Knihovny",
|
||||||
"HeaderLocalFolders": "Local Folders",
|
"HeaderLocalFolders": "Místní složky",
|
||||||
"HeaderLocalLibraryItems": "Local Library Items",
|
"HeaderLocalLibraryItems": "Místní položky knihovny",
|
||||||
"HeaderNewPlaylist": "New Playlist",
|
"HeaderNewPlaylist": "Nový seznam skladeb",
|
||||||
"HeaderOpenRSSFeed": "Otevřít RSS kanál",
|
"HeaderOpenRSSFeed": "Otevřít RSS kanál",
|
||||||
"HeaderPlaybackSettings": "Playback Settings",
|
"HeaderPlaybackSettings": "Nastavení přehrávání",
|
||||||
"HeaderPlaylist": "Seznam skladeb",
|
"HeaderPlaylist": "Seznam skladeb",
|
||||||
"HeaderPlaylistItems": "Položky seznamu přehrávání",
|
"HeaderPlaylistItems": "Položky seznamu přehrávání",
|
||||||
"HeaderRSSFeed": "RSS Feed",
|
"HeaderRSSFeed": "RSS kanál",
|
||||||
"HeaderRSSFeedGeneral": "Podrobnosti o RSS",
|
"HeaderRSSFeedGeneral": "Detaily RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Informační kanál RSS je otevřený",
|
"HeaderRSSFeedIsOpen": "Kanál RSS je otevřen",
|
||||||
"HeaderSelectDownloadLocation": "Select Download Location",
|
"HeaderSelectDownloadLocation": "Vyberte umístění pro stažení",
|
||||||
"HeaderSettings": "Nastavení",
|
"HeaderSettings": "Nastavení",
|
||||||
"HeaderSleepTimer": "Časovač vypnutí",
|
"HeaderSleepTimer": "Časovač spánku",
|
||||||
"HeaderSleepTimerSettings": "Sleep Timer Settings",
|
"HeaderSleepTimerSettings": "Nastavení časovače spánku",
|
||||||
"HeaderStatsMinutesListeningChart": "Počet minut poslechu (posledních 7 dní)",
|
"HeaderStatsMinutesListeningChart": "Poslech za posledních 7 dní (min)",
|
||||||
"HeaderStatsRecentSessions": "Poslední relace",
|
"HeaderStatsRecentSessions": "Poslední sezení",
|
||||||
"HeaderTableOfContents": "Obsah",
|
"HeaderTableOfContents": "Obsah",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "Nastavení uživatelského rozhraní",
|
||||||
"HeaderYourStats": "Vaše statistiky",
|
"HeaderYourStats": "Vaše statistiky",
|
||||||
|
"LabelAddToPlaylist": "Přidat do seznamu skladeb",
|
||||||
"LabelAdded": "Přidáno",
|
"LabelAdded": "Přidáno",
|
||||||
"LabelAddedAt": "Přidáno v",
|
"LabelAddedAt": "Přidáno v",
|
||||||
"LabelAddToPlaylist": "Přidat do seznamu přehrávání",
|
|
||||||
"LabelAll": "Vše",
|
"LabelAll": "Vše",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Povolit vyhledávání polohy v ovládacích prvcích oznámení médií",
|
||||||
|
"LabelAlways": "Vždy",
|
||||||
|
"LabelAskConfirmation": "Požádat o potvrzení",
|
||||||
"LabelAuthor": "Autor",
|
"LabelAuthor": "Autor",
|
||||||
"LabelAuthorFirstLast": "Autor (jméno a příjmení)",
|
"LabelAuthorFirstLast": "Autor (jméno a příjmení)",
|
||||||
"LabelAuthorLastFirst": "Autor (příjmení a jméno)",
|
"LabelAuthorLastFirst": "Autor (příjmení a jméno)",
|
||||||
"LabelAuthors": "Autoři",
|
"LabelAuthors": "Autoři",
|
||||||
"LabelAutoDownloadEpisodes": "Automaticky stahovat epizody",
|
"LabelAutoDownloadEpisodes": "Automaticky stahovat epizody",
|
||||||
"LabelAutoRewindTime": "Auto rewind time",
|
"LabelAutoRewindTime": "Přetočit zpět o",
|
||||||
"LabelAutoSleepTimer": "Auto sleep timer",
|
"LabelAutoSleepTimer": "Automatický časovač spánku",
|
||||||
"LabelAutoSleepTimerAutoRewind": "Auto sleep timer auto rewind",
|
"LabelAutoSleepTimerAutoRewind": "Přetočit zpět po uspání",
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
"LabelAutoSleepTimerAutoRewindHelp": "Když dojde k uspání automatickým časovačem spánku, pozice přehrávání je posunuta zpět o vybraný čas.",
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
"LabelAutoSleepTimerHelp": "Během přehrávání média v časovém rozmezí \"Od\" a \"Do\" se automaticky spustí časovač spánku.",
|
||||||
"LabelBooks": "Knihy",
|
"LabelBooks": "Knihy",
|
||||||
|
"LabelChapterTrack": "Stopa kapitoly",
|
||||||
"LabelChapters": "Kapitoly",
|
"LabelChapters": "Kapitoly",
|
||||||
"LabelChapterTrack": "Chapter Track",
|
|
||||||
"LabelClosePlayer": "Zavřít přehrávač",
|
"LabelClosePlayer": "Zavřít přehrávač",
|
||||||
"LabelCollapseSeries": "Sbalit sérii",
|
"LabelCollapseSeries": "Sbalit sérii",
|
||||||
"LabelComplete": "Dokončeno",
|
"LabelComplete": "Dokončeno",
|
||||||
"LabelContinueBooks": "Continue Books",
|
"LabelContinueBooks": "Pokračovat v knihách",
|
||||||
"LabelContinueEpisodes": "Continue Episodes",
|
"LabelContinueEpisodes": "Pokračovat v epizodách",
|
||||||
"LabelContinueListening": "Continue Listening",
|
"LabelContinueListening": "Pokračovat v poslechu",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Pokračovat ve čtení",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Pokračovat v sériích",
|
||||||
"LabelCustomTime": "Custom time",
|
"LabelCustomTime": "Vlastní čas",
|
||||||
"LabelDescription": "Popis",
|
"LabelDescription": "Popis",
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
"LabelDisableAudioFadeOut": "Vypnout postupné snižování zvuku",
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
"LabelDisableAudioFadeOutHelp": "Hlasitost zvuku začne klesat, pokud zbývá méně než 1 minuta do vypršení časovače spánku. Tuto možnost povolte, pokud nechcete postupné snižování hlasitosti.",
|
||||||
"LabelDisableAutoRewind": "Disable auto rewind",
|
"LabelDisableAutoRewind": "Zakázat automatické přetočení",
|
||||||
"LabelDisableShakeToReset": "Disable shake to reset",
|
"LabelDisableShakeToReset": "Zakázat resetování otřesem",
|
||||||
"LabelDisableShakeToResetHelp": "Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.",
|
"LabelDisableShakeToResetHelp": "Když je časovač spánku spuštěn nebo zbýva 2 minuty do jeho vypršení, zatřesení zařízením se resetuje časovač spánku. Povolte toto nastavení, pokud chcete resetování otřesem zakázat.",
|
||||||
"LabelDisableVibrateOnReset": "Disable vibrate on reset",
|
"LabelDisableVibrateOnReset": "Zakázat vibraci při resetování",
|
||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
"LabelDisableVibrateOnResetHelp": "Když je časovač spánku resetován, zařízení zavibruje. Tuto možnost povolte, pokud nechcete, aby zařízení vibrace provádělo při resetování časovače spánku.",
|
||||||
"LabelDiscover": "Discover",
|
"LabelDiscover": "Objevit",
|
||||||
"LabelDownload": "Stáhnout",
|
"LabelDownload": "Stáhnout",
|
||||||
"LabelDownloaded": "Downloaded",
|
"LabelDownloadUsingCellular": "Stáhnout přes mobilní data",
|
||||||
"LabelDuration": "Doba trvání",
|
"LabelDownloaded": "Staženo",
|
||||||
"LabelEbook": "Elektronická kniha",
|
"LabelDuration": "Délka trvání",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbook": "E-kniha",
|
||||||
|
"LabelEbooks": "E-knihy",
|
||||||
"LabelEnable": "Povolit",
|
"LabelEnable": "Povolit",
|
||||||
"LabelEnableMp3IndexSeeking": "Enable mp3 index seeking",
|
"LabelEnableMp3IndexSeeking": "Povolit indexové vyhledávání mp3",
|
||||||
"LabelEnableMp3IndexSeekingHelp": "This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.",
|
"LabelEnableMp3IndexSeekingHelp": "Toto nastavení by mělo být povoleno pouze v případě, že soubory MP3 nejsou správně vyhledávány. Nepřesné vyhledávání je s největší pravděpodobností způsobeno soubory MP3 s proměnlivým datovým tokem (VBR). Toto nastavení vynutí indexové vyhledávání, při kterém se při čtení souboru vytváří mapování času na bajty. V některých případech u velkých souborů MP3 dochází ke zpoždění při vyhledávání ke konci souboru.",
|
||||||
"LabelEnd": "Konec",
|
"LabelEnd": "Konec",
|
||||||
"LabelEndOfChapter": "End of Chapter",
|
"LabelEndOfChapter": "Konec kapitoly",
|
||||||
"LabelEndTime": "End time",
|
"LabelEndTime": "Do",
|
||||||
"LabelEpisode": "Epizoda",
|
"LabelEpisode": "Epizoda",
|
||||||
"LabelFeedURL": "URL zdroje",
|
"LabelFeedURL": "URL kanálu",
|
||||||
"LabelFile": "Soubor",
|
"LabelFile": "Soubor",
|
||||||
"LabelFileBirthtime": "Čas vzniku souboru",
|
"LabelFileBirthtime": "Čas vytvoření souboru",
|
||||||
"LabelFileModified": "Soubor změněn",
|
"LabelFileModified": "Soubor upraven",
|
||||||
"LabelFilename": "Název souboru",
|
"LabelFilename": "Název souboru",
|
||||||
"LabelFinished": "Dokončeno",
|
"LabelFinished": "Dokončeno",
|
||||||
"LabelFolder": "Složka",
|
"LabelFolder": "Složka",
|
||||||
|
"LabelFontBoldness": "Výraznost písma",
|
||||||
"LabelFontScale": "Měřítko písma",
|
"LabelFontScale": "Měřítko písma",
|
||||||
"LabelGenre": "Žánr",
|
"LabelGenre": "Žánr",
|
||||||
"LabelGenres": "Žánry",
|
"LabelGenres": "Žánry",
|
||||||
"LabelHapticFeedback": "Haptic feedback",
|
"LabelHapticFeedback": "Haptická odezva",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Má e-knihu",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Má doplňkovou e-knihu",
|
||||||
"LabelHeavy": "Heavy",
|
"LabelHeavy": "Těžké",
|
||||||
"LabelHigh": "High",
|
"LabelHigh": "Vysoké",
|
||||||
"LabelHost": "Hostitel",
|
"LabelHost": "Hostitel",
|
||||||
"LabelIncomplete": "Neúplné",
|
|
||||||
"LabelInProgress": "Probíhá",
|
"LabelInProgress": "Probíhá",
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
"LabelIncomplete": "Neúplné",
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
"LabelInternalAppStorage": "Interní úložiště aplikace",
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
"LabelJumpBackwardsTime": "Délka skoku zpět v čase",
|
||||||
|
"LabelJumpForwardsTime": "Délka skoku vpřed v čase",
|
||||||
"LabelLanguage": "Jazyk",
|
"LabelLanguage": "Jazyk",
|
||||||
"LabelLayout": "Layout",
|
"LabelLayout": "Rozvržení",
|
||||||
"LabelLayoutAuto": "Auto",
|
"LabelLayoutAuto": "Automatické",
|
||||||
"LabelLayoutSinglePage": "Single page",
|
"LabelLayoutSinglePage": "Jedna stránka",
|
||||||
"LabelLight": "Light",
|
"LabelLight": "Světlé",
|
||||||
"LabelLineSpacing": "Řádkování",
|
"LabelLineSpacing": "Řádkování",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Poslouchat znovu",
|
||||||
"LabelLocalBooks": "Local Books",
|
"LabelLocalBooks": "Místní knihy",
|
||||||
"LabelLocalPodcasts": "Local Podcasts",
|
"LabelLocalPodcasts": "Místní podcasty",
|
||||||
"LabelLockOrientation": "Lock orientation",
|
"LabelLockOrientation": "Zamknout orientaci",
|
||||||
"LabelLockPlayer": "Lock Player",
|
"LabelLockPlayer": "Zamknout přehrávač",
|
||||||
"LabelLow": "Low",
|
"LabelLow": "Nízké",
|
||||||
"LabelMediaType": "Typ média",
|
"LabelMediaType": "Typ média",
|
||||||
"LabelMedium": "Medium",
|
"LabelMedium": "Střední",
|
||||||
"LabelMore": "Více",
|
"LabelMore": "Více",
|
||||||
"LabelMoreInfo": "Více informací",
|
"LabelMoreInfo": "Více informací",
|
||||||
"LabelName": "Jméno",
|
"LabelName": "Jméno",
|
||||||
"LabelNarrator": "Interpret",
|
"LabelNarrator": "Vypravěč",
|
||||||
"LabelNarrators": "Interpreti",
|
"LabelNarrators": "Vypravěči",
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNavigateWithVolume": "Navigace pomocí tlačítek hlasitosti",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
"LabelNavigateWithVolumeDisabled": "Vypnuto",
|
||||||
"LabelNo": "No",
|
"LabelNavigateWithVolumeEnabled": "Zapnuto",
|
||||||
|
"LabelNavigateWithVolumeMirrored": "Zrcadleno",
|
||||||
|
"LabelNever": "Nikdy",
|
||||||
|
"LabelNewestAuthors": "Nejnovější autoři",
|
||||||
|
"LabelNewestEpisodes": "Nejnovější epizody",
|
||||||
|
"LabelNo": "Ne",
|
||||||
"LabelNotFinished": "Nedokončeno",
|
"LabelNotFinished": "Nedokončeno",
|
||||||
"LabelNotStarted": "Nezahájeno",
|
"LabelNotStarted": "Nezahájeno",
|
||||||
"LabelOff": "Off",
|
"LabelOff": "Vypnout",
|
||||||
"LabelPassword": "Heslo",
|
"LabelPassword": "Heslo",
|
||||||
"LabelPath": "Cesta",
|
"LabelPath": "Cesta",
|
||||||
"LabelPlaybackDirect": "Direct",
|
"LabelPlaybackDirect": "Přímé",
|
||||||
"LabelPlaybackLocal": "Local",
|
"LabelPlaybackLocal": "Místní",
|
||||||
"LabelPlaybackSpeed": "Playback Speed",
|
"LabelPlaybackSpeed": "Rychlost přehrávání",
|
||||||
"LabelPlaybackTranscode": "Transcode",
|
"LabelPlaybackTranscode": "Překódovává se",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasty",
|
"LabelPodcasts": "Podcasty",
|
||||||
"LabelPreventIndexing": "Zabránit indexování vašeho kanálu v adresářích podcastů iTunes a Google",
|
"LabelPreventIndexing": "Zabránit indexování vašeho kanálu v adresářích podcastů iTunes a Google",
|
||||||
"LabelProgress": "Průběh",
|
"LabelProgress": "Průběh",
|
||||||
"LabelPubDate": "Datum vydání",
|
"LabelPubDate": "Datum vydání",
|
||||||
"LabelPublishYear": "Rok vydání",
|
"LabelPublishYear": "Rok vydání",
|
||||||
"LabelRead": "Číst",
|
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Vlastní e-mail vlastníka",
|
"LabelRSSFeedCustomOwnerEmail": "Vlastní e-mail vlastníka",
|
||||||
"LabelRSSFeedCustomOwnerName": "Vlastní jméno vlastníka",
|
"LabelRSSFeedCustomOwnerName": "Vlastní jméno vlastníka",
|
||||||
"LabelRSSFeedPreventIndexing": "Zabránit indexování",
|
"LabelRSSFeedPreventIndexing": "Zabránit indexování",
|
||||||
"LabelRSSFeedSlug": "RSS kanál Slug",
|
"LabelRSSFeedSlug": "Klíčové slovo kanálu RSS",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
"LabelRandomly": "Náhodně",
|
||||||
|
"LabelRead": "Číst",
|
||||||
|
"LabelReadAgain": "Číst znovu",
|
||||||
|
"LabelRecentSeries": "Nedávné série",
|
||||||
|
"LabelRecentlyAdded": "Nedávno přidáno",
|
||||||
|
"LabelRemoveFromPlaylist": "Odebrat z Playlistu",
|
||||||
|
"LabelScaleElapsedTimeBySpeed": "Škálovat uplynulý čas podle rychlosti",
|
||||||
"LabelSeason": "Sezóna",
|
"LabelSeason": "Sezóna",
|
||||||
"LabelSelectADevice": "Select a device",
|
"LabelSelectADevice": "Vyberte zařízení",
|
||||||
"LabelSeries": "Série",
|
"LabelSeries": "Série",
|
||||||
"LabelServerAddress": "Server address",
|
"LabelServerAddress": "Adresa serveru",
|
||||||
"LabelSetEbookAsPrimary": "Nastavit jako primární",
|
"LabelSetEbookAsPrimary": "Nastavit jako primární",
|
||||||
"LabelSetEbookAsSupplementary": "Nastavit jako doplňkové",
|
"LabelSetEbookAsSupplementary": "Nastavit jako doplňkový",
|
||||||
"LabelShakeSensitivity": "Shake sensitivity",
|
"LabelShakeSensitivity": "Citlivost na otřes",
|
||||||
"LabelShowAll": "Zobrazit vše",
|
"LabelShowAll": "Zobrazit vše",
|
||||||
"LabelSize": "Velikost",
|
"LabelSize": "Velikost",
|
||||||
"LabelSleepTimer": "Časovač vypnutí",
|
"LabelSleepTimer": "Spánek po",
|
||||||
"LabelStart": "Spustit",
|
"LabelStart": "Spustit",
|
||||||
"LabelStartTime": "Čas Spuštění",
|
"LabelStartTime": "Od",
|
||||||
"LabelStatsBestDay": "Nejlepší den",
|
"LabelStatsBestDay": "Nejlepší den",
|
||||||
"LabelStatsDailyAverage": "Denní průměr",
|
"LabelStatsDailyAverage": "Denní průměr",
|
||||||
"LabelStatsDays": "Dny",
|
"LabelStatsDays": "Dny",
|
||||||
"LabelStatsDaysListened": "Dny poslechu",
|
"LabelStatsDaysListened": "Dny poslechu",
|
||||||
"LabelStatsInARow": "v řadě",
|
"LabelStatsInARow": "v řadě",
|
||||||
"LabelStatsItemsFinished": "Dokončené Položky",
|
"LabelStatsItemsFinished": "Dokončené položky",
|
||||||
"LabelStatsMinutes": "minut",
|
"LabelStatsMinutes": "minut",
|
||||||
"LabelStatsMinutesListening": "Minuty poslechu",
|
"LabelStatsMinutesListening": "Minuty poslechu",
|
||||||
"LabelStatsWeekListening": "Týdenní poslech",
|
"LabelStatsWeekListening": "Za týden",
|
||||||
"LabelTag": "Značka",
|
"LabelStreamingUsingCellular": "Přehrávání přes mobilní data",
|
||||||
"LabelTags": "Značky",
|
"LabelTag": "Štítek",
|
||||||
|
"LabelTags": "Štítky",
|
||||||
"LabelTheme": "Téma",
|
"LabelTheme": "Téma",
|
||||||
"LabelThemeDark": "Tmavé",
|
"LabelThemeDark": "Tmavé",
|
||||||
"LabelThemeLight": "Světlé",
|
"LabelThemeLight": "Světlé",
|
||||||
"LabelTimeRemaining": "{0} zbývá",
|
"LabelTimeRemaining": "{0} zbývá",
|
||||||
"LabelTitle": "Název",
|
"LabelTitle": "Název",
|
||||||
"LabelTotalTrack": "Total Track",
|
"LabelTotalSize": "Celková velikost",
|
||||||
|
"LabelTotalTrack": "Celková stopa",
|
||||||
"LabelTracks": "Stopy",
|
"LabelTracks": "Stopy",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnlockPlayer": "Unlock Player",
|
"LabelUnlockPlayer": "Odemknout přehrávač",
|
||||||
"LabelUseBookshelfView": "Use bookshelf view",
|
"LabelUseBookshelfView": "Použít vzhled knihovny",
|
||||||
"LabelUser": "Uživatel",
|
"LabelUser": "Uživatel",
|
||||||
"LabelUsername": "Uživatelské jméno",
|
"LabelUsername": "Uživatelské jméno",
|
||||||
"LabelVeryHigh": "Very High",
|
"LabelVeryHigh": "Velmi vysoké",
|
||||||
"LabelVeryLow": "Very Low",
|
"LabelVeryLow": "Velmi nízké",
|
||||||
"LabelYourBookmarks": "Vaše záložky",
|
"LabelYourBookmarks": "Vaše záložky",
|
||||||
"LabelYourProgress": "Váš pokrok",
|
"LabelYourProgress": "Váš pokrok",
|
||||||
"MessageAndroid10Downloads": "Android 10 and below will use internal app storage for downloads.",
|
"MessageAndroid10Downloads": "Android 10 a starší budou používat interní úložiště aplikace pro stahování.",
|
||||||
"MessageAttemptingServerConnection": "Attempting server connection...",
|
"MessageAttemptingServerConnection": "Pokouším se o připojit k serveru...",
|
||||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf server not connected",
|
"MessageAudiobookshelfServerNotConnected": "Server Audiobookshelf není připojen",
|
||||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> This app is designed to work with an Audiobookshelf server that you or someone you know is hosting. This app does not provide any content.",
|
"MessageAudiobookshelfServerRequired": "<strong>Důležité!</strong> Tato aplikace je navržena, aby pracovala se serverem Audiobookshelf, který hostuje buď vy, nebo někdo, koho znáte. Tato aplikace neposkytuje žádný obsah.",
|
||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
"MessageBookshelfEmpty": "Knihovna je prázdná",
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
"MessageConfirmDeleteLocalEpisode": "Odebrat místní epizodu „{0}“ ze zařízení? Soubor na serveru zůstane nezměněný.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
"MessageConfirmDeleteLocalFiles": "Odebrat místní soubory této položky ze zařízení? Soubory na serveru a váš pokrok nebudou ovlivněny.",
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
"MessageConfirmDiscardProgress": "Opravdu chcete zahodit svůj pokrok?",
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
"MessageConfirmDownloadUsingCellular": "Chystáte se stahovat přes mobilní data. Toto může zahrnovat poplatky za mobilní data. Chcete pokračovat?",
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
"MessageConfirmMarkAsFinished": "Opravdu chcete tuto položku označit jako dokončenou?",
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
"MessageConfirmRemoveBookmark": "Opravdu chcete odebrat záložku?",
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
"MessageConfirmStreamingUsingCellular": "Chystáte se přehrávat přes mobilní data. Toto může zahrnovat poplatky za mobilní data. Chcete pokračovat?",
|
||||||
"MessageDownloading": "Downloading...",
|
"MessageDiscardProgress": "Zahodit pokrok",
|
||||||
|
"MessageDownloadCompleteProcessing": "Stahování dokončeno. Zpracovává se...",
|
||||||
|
"MessageDownloading": "Stahuje se...",
|
||||||
"MessageDownloadingEpisode": "Stahuji epizodu",
|
"MessageDownloadingEpisode": "Stahuji epizodu",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} epizody zařazené do fronty ke stažení",
|
"MessageEpisodesQueuedForDownload": "{0} Epizody zařazené do fronty ke stažení",
|
||||||
"MessageFeedURLWillBe": "URL zdroje bude {0}",
|
"MessageFeedURLWillBe": "URL zdroje bude {0}",
|
||||||
"MessageFetching": "Stahování...",
|
"MessageFetching": "Načítání...",
|
||||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
"MessageFollowTheProjectOnGithub": "Sledujte projekt na GitHubu",
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Item download complete but failed to create library item",
|
"MessageItemDownloadCompleteFailedToCreate": "Stahování položky bylo dokončeno, ale nepodařilo se vytvořit položku v knihovně",
|
||||||
"MessageLoading": "Načítá se...",
|
"MessageLoading": "Načítá se...",
|
||||||
"MessageLoadingServerData": "Loading server data...",
|
"MessageLoadingServerData": "Načítání dat ze serveru...",
|
||||||
"MessageMarkAsFinished": "Označit jako dokončené",
|
"MessageMarkAsFinished": "Označit jako dokončené",
|
||||||
"MessageMediaLinkedToADifferentServer": "Media is linked to an Audiobookshelf server on a different address ({0}). Progress will be synced when connected to this server address.",
|
"MessageMediaLinkedToADifferentServer": "Média jsou propojena se serverem Audiobookshelf na jiné adrese ({0}). Pokrok bude synchronizován, když budete připojeni k této adrese serveru.",
|
||||||
"MessageMediaLinkedToADifferentUser": "Media is linked to this server but was downloaded by a different user. Progress will only be synced to the user that downloaded it.",
|
"MessageMediaLinkedToADifferentUser": "Média jsou propojena se serverem, ale byla stažena jiným uživatelem. Pokrok bude synchronizován pouze s uživatelem, který je stáhl.",
|
||||||
"MessageMediaLinkedToServer": "Linked to server {0}",
|
"MessageMediaLinkedToServer": "Propojeno se serverem {0}",
|
||||||
"MessageMediaLinkedToThisServer": "Downloaded media is linked to this server",
|
"MessageMediaLinkedToThisServer": "Stažená média jsou propojena s tímto serverem",
|
||||||
"MessageMediaNotLinkedToServer": "Media is not linked to an Audiobookshelf server. No progress will be synced.",
|
"MessageMediaNotLinkedToServer": "Média nejsou propojena se serverem Audiobookshelf. Pokrok nebude synchronizován.",
|
||||||
"MessageNoBookmarks": "Žádné záložky",
|
"MessageNoBookmarks": "Žádné záložky",
|
||||||
"MessageNoChapters": "Žádné kapitoly",
|
"MessageNoChapters": "Žádné kapitoly",
|
||||||
"MessageNoItems": "Žádné položky",
|
"MessageNoItems": "Žádné položky",
|
||||||
"MessageNoItemsFound": "Nebyly nalezeny žádné položky",
|
"MessageNoItemsFound": "Nebyly nalezeny žádné položky",
|
||||||
"MessageNoListeningSessions": "Žádné poslechové relace",
|
"MessageNoListeningSessions": "Žádné poslechové relace",
|
||||||
"MessageNoMediaFolders": "No Media Folders",
|
"MessageNoMediaFolders": "Žádné složky médií",
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
"MessageNoNetworkConnection": "Žádné připojení k síti",
|
||||||
"MessageNoPodcastsFound": "Nebyly nalezeny žádné podcasty",
|
"MessageNoPodcastsFound": "Nebyly nalezeny žádné podcasty",
|
||||||
|
"MessageNoSeries": "Žádné série",
|
||||||
"MessageNoUpdatesWereNecessary": "Nebyly nutné žádné aktualizace",
|
"MessageNoUpdatesWereNecessary": "Nebyly nutné žádné aktualizace",
|
||||||
"MessageNoUserPlaylists": "Nemáte žádné seznamy skladeb",
|
"MessageNoUserPlaylists": "Nemáte žádné seznamy skladeb",
|
||||||
"MessageReportBugsAndContribute": "Hlásit chyby, žádat o funkce a přispívat",
|
"MessageReportBugsAndContribute": "Nahlašte chyby, vyžádejte si funkce a přispěte na",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
"MessageSocketConnectedOverMeteredCellular": "Socket připojen přes měřenou mobilní síť",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
"MessageSocketConnectedOverMeteredWifi": "Socket připojen přes měřenou Wi-Fi",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
"MessageSocketConnectedOverUnmeteredCellular": "Socket připojen přes neměřenou mobilní síť",
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered wifi",
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket připojen přes neměřenou Wi-Fi",
|
||||||
"MessageSocketNotConnected": "Socket not connected",
|
"MessageSocketNotConnected": "Socket není připojen",
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Upozornění: Většina aplikací pro podcasty bude vyžadovat, aby adresa URL kanálu RSS používala protokol HTTPS",
|
"NoteRSSFeedPodcastAppsHttps": "Upozornění: Většina aplikací pro podcasty bude vyžadovat, aby adresa URL kanálu RSS používala protokol HTTPS",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Upozornění: 1 nebo více epizod nemá datum vydání. Některé podcastové aplikace to vyžadují.",
|
"NoteRSSFeedPodcastAppsPubDate": "Upozornění: 1 nebo více epizod nemá datum vydání. Některé podcastové aplikace to vyžadují.",
|
||||||
"ToastBookmarkCreateFailed": "Vytvoření záložky se nezdařilo",
|
"ToastBookmarkCreateFailed": "Vytvoření záložky se nezdařilo",
|
||||||
"ToastBookmarkRemoveFailed": "Nepodařilo se odstranit záložku",
|
"ToastBookmarkRemoveFailed": "Nepodařilo se odstranit záložku",
|
||||||
"ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
|
"ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
|
||||||
|
"ToastDownloadNotAllowedOnCellular": "Stahování přes mobilní data není povoleno",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Nepodařilo se označit jako dokončené",
|
"ToastItemMarkedAsFinishedFailed": "Nepodařilo se označit jako dokončené",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Nepodařilo se označit jako nedokončené",
|
"ToastItemMarkedAsNotFinishedFailed": "Nepodařilo se označit jako nedokončené",
|
||||||
"ToastPlaylistCreateFailed": "Vytvoření seznamu přehrávání se nezdařilo",
|
"ToastPlaylistCreateFailed": "Vytvoření seznamu přehrávání se nezdařilo",
|
||||||
"ToastPodcastCreateFailed": "Vytvoření podcastu se nezdařilo",
|
"ToastPodcastCreateFailed": "Vytvoření podcastu se nezdařilo",
|
||||||
"ToastPodcastCreateSuccess": "Podcast byl úspěšně vytvořen",
|
"ToastPodcastCreateSuccess": "Podcast byl úspěšně vytvořen",
|
||||||
"ToastRSSFeedCloseFailed": "Nepodařilo se zavřít RSS kanál",
|
"ToastRSSFeedCloseFailed": "Nepodařilo se zavřít RSS kanál",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS kanál uzavřen"
|
"ToastRSSFeedCloseSuccess": "RSS kanál uzavřen",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "Přehrávání přes mobilní data není povoleno"
|
||||||
}
|
}
|
||||||
+90
-132
@@ -1,52 +1,54 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Tilføj",
|
"ButtonAdd": "Tilføj",
|
||||||
"ButtonAddNewServer": "Add New Server",
|
"ButtonAddNewServer": "Tilføj ny server",
|
||||||
"ButtonAuthors": "Forfattere",
|
"ButtonAuthors": "Forfattere",
|
||||||
"ButtonBack": "Back",
|
"ButtonBack": "Tilbage",
|
||||||
"ButtonCancel": "Annuller",
|
"ButtonCancel": "Annuller",
|
||||||
"ButtonCancelTimer": "Cancel Timer",
|
"ButtonCancelTimer": "Stop timer",
|
||||||
"ButtonClearFilter": "Ryd filter",
|
"ButtonClearFilter": "Ryd filter",
|
||||||
"ButtonCloseFeed": "Luk feed",
|
"ButtonCloseFeed": "Luk feed",
|
||||||
"ButtonCollections": "Samlinger",
|
"ButtonCollections": "Samlinger",
|
||||||
"ButtonConnect": "Connect",
|
"ButtonConnect": "Forbind",
|
||||||
"ButtonConnectToServer": "Connect to Server",
|
"ButtonConnectToServer": "Forbind til Server",
|
||||||
"ButtonCreate": "Opret",
|
"ButtonCreate": "Opret",
|
||||||
"ButtonCreateBookmark": "Create Bookmark",
|
"ButtonCreateBookmark": "Opret Bogmærker",
|
||||||
"ButtonCreateNewPlaylist": "Create New Playlist",
|
"ButtonCreateNewPlaylist": "Opret Ny Playlist",
|
||||||
"ButtonDelete": "Slet",
|
"ButtonDelete": "Slet",
|
||||||
"ButtonDeleteLocalEpisode": "Delete local episode",
|
"ButtonDeleteLocalEpisode": "Slet lokalt afsnit",
|
||||||
"ButtonDeleteLocalFile": "Delete local file",
|
"ButtonDeleteLocalFile": "Slet lokal fil",
|
||||||
"ButtonDeleteLocalItem": "Delete local item",
|
"ButtonDeleteLocalItem": "Slet lokalt element",
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
"ButtonDisableAutoTimer": "Afbryd Auto Timer",
|
||||||
"ButtonDisconnect": "Disconnect",
|
"ButtonDisconnect": "Luk forbindelse",
|
||||||
"ButtonHistory": "History",
|
"ButtonGoToWebClient": "Gå til webklient",
|
||||||
|
"ButtonHistory": "Historik",
|
||||||
"ButtonHome": "Hjem",
|
"ButtonHome": "Hjem",
|
||||||
"ButtonIssues": "Problemer",
|
"ButtonIssues": "Problemer",
|
||||||
"ButtonLatest": "Seneste",
|
"ButtonLatest": "Seneste",
|
||||||
"ButtonLibrary": "Bibliotek",
|
"ButtonLibrary": "Bibliotek",
|
||||||
"ButtonLocalMedia": "Local Media",
|
"ButtonLocalMedia": "Lokalt medie",
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
"ButtonManageLocalFiles": "Administrér Lokale Filer",
|
||||||
"ButtonNewFolder": "New Folder",
|
"ButtonNewFolder": "Ny Folder",
|
||||||
"ButtonNextEpisode": "Next Episode",
|
"ButtonNextEpisode": "Næste Afsnit",
|
||||||
"ButtonOpenFeed": "Åbn feed",
|
"ButtonOpenFeed": "Åbn feed",
|
||||||
"ButtonOverride": "Override",
|
"ButtonOverride": "Override",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pause",
|
||||||
"ButtonPlay": "Afspil",
|
"ButtonPlay": "Afspil",
|
||||||
|
"ButtonPlayEpisode": "Afspil Afsnit",
|
||||||
"ButtonPlaying": "Afspiller",
|
"ButtonPlaying": "Afspiller",
|
||||||
"ButtonPlaylists": "Afspilningslister",
|
"ButtonPlaylists": "Afspilningslister",
|
||||||
"ButtonRead": "Læs",
|
"ButtonRead": "Læs",
|
||||||
"ButtonRemove": "Fjern",
|
"ButtonRemove": "Fjern",
|
||||||
"ButtonRemoveFromServer": "Remove from Server",
|
"ButtonRemoveFromServer": "Fjern fra Server",
|
||||||
"ButtonSave": "Gem",
|
"ButtonSave": "Gem",
|
||||||
"ButtonSaveOrder": "Save Order",
|
"ButtonSaveOrder": "Gem Rækkefølge",
|
||||||
"ButtonSearch": "Søg",
|
"ButtonSearch": "Søg",
|
||||||
"ButtonSendEbookToDevice": "Send Ebook to Device",
|
"ButtonSendEbookToDevice": "Send E-bog til Enhed",
|
||||||
"ButtonSeries": "Serie",
|
"ButtonSeries": "Serier",
|
||||||
"ButtonSetTimer": "Set Timer",
|
"ButtonSetTimer": "Indstil timer",
|
||||||
"ButtonStream": "Stream",
|
"ButtonStream": "Stream",
|
||||||
"ButtonSubmit": "Send",
|
"ButtonSubmit": "Send",
|
||||||
"ButtonSwitchServerUser": "Switch Server/User",
|
"ButtonSwitchServerUser": "Skift Server/Bruger",
|
||||||
"ButtonUserStats": "User Stats",
|
"ButtonUserStats": "Bruger statitstik",
|
||||||
"ButtonYes": "Ja",
|
"ButtonYes": "Ja",
|
||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
"HeaderAdvanced": "Avanceret",
|
"HeaderAdvanced": "Avanceret",
|
||||||
@@ -54,7 +56,8 @@
|
|||||||
"HeaderChapters": "Kapitler",
|
"HeaderChapters": "Kapitler",
|
||||||
"HeaderCollection": "Samling",
|
"HeaderCollection": "Samling",
|
||||||
"HeaderCollectionItems": "Samlingselementer",
|
"HeaderCollectionItems": "Samlingselementer",
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
"HeaderConnectionStatus": "Forbindelsesstatus",
|
||||||
|
"HeaderDataSettings": "Data indstillinger",
|
||||||
"HeaderDetails": "Detaljer",
|
"HeaderDetails": "Detaljer",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Downloads",
|
||||||
"HeaderEbookFiles": "E-bogsfiler",
|
"HeaderEbookFiles": "E-bogsfiler",
|
||||||
@@ -62,150 +65,132 @@
|
|||||||
"HeaderEreaderSettings": "E-læser Indstillinger",
|
"HeaderEreaderSettings": "E-læser Indstillinger",
|
||||||
"HeaderLatestEpisodes": "Seneste episoder",
|
"HeaderLatestEpisodes": "Seneste episoder",
|
||||||
"HeaderLibraries": "Biblioteker",
|
"HeaderLibraries": "Biblioteker",
|
||||||
"HeaderLocalFolders": "Local Folders",
|
"HeaderLocalFolders": "Lokale mapper",
|
||||||
"HeaderLocalLibraryItems": "Local Library Items",
|
"HeaderLocalLibraryItems": "Lokale biblioteks elementer",
|
||||||
"HeaderNewPlaylist": "New Playlist",
|
"HeaderNewPlaylist": "Ny afspilningsliste",
|
||||||
"HeaderOpenRSSFeed": "Åbn RSS Feed",
|
"HeaderOpenRSSFeed": "Åbn RSS Feed",
|
||||||
"HeaderPlaybackSettings": "Playback Settings",
|
"HeaderPlaybackSettings": "Afspilnings indstillinger",
|
||||||
"HeaderPlaylist": "Afspilningsliste",
|
"HeaderPlaylist": "Afspilningsliste",
|
||||||
"HeaderPlaylistItems": "Afspilningsliste Elementer",
|
"HeaderPlaylistItems": "Afspilningsliste Elementer",
|
||||||
"HeaderRSSFeed": "RSS Feed",
|
"HeaderRSSFeed": "RSS Feed",
|
||||||
"HeaderRSSFeedGeneral": "RSS Detaljer",
|
"HeaderRSSFeedGeneral": "RSS Detaljer",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed er Åben",
|
"HeaderRSSFeedIsOpen": "RSS Feed er Åben",
|
||||||
"HeaderSelectDownloadLocation": "Select Download Location",
|
"HeaderSelectDownloadLocation": "Vælg Download Placering",
|
||||||
"HeaderSettings": "Indstillinger",
|
"HeaderSettings": "Indstillinger",
|
||||||
"HeaderSleepTimer": "Søvntimer",
|
"HeaderSleepTimer": "Søvntimer",
|
||||||
"HeaderSleepTimerSettings": "Sleep Timer Settings",
|
"HeaderSleepTimerSettings": "Sleep Timer instillinger",
|
||||||
"HeaderStatsMinutesListeningChart": "Minutter Lyttet (sidste 7 dage)",
|
"HeaderStatsMinutesListeningChart": "Minutter Lyttet (sidste 7 dage)",
|
||||||
"HeaderStatsRecentSessions": "Seneste Sessions",
|
"HeaderStatsRecentSessions": "Seneste Sessions",
|
||||||
"HeaderTableOfContents": "Indholdsfortegnelse",
|
"HeaderTableOfContents": "Indholdsfortegnelse",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "Indstillinger for brugergrænseflade",
|
||||||
"HeaderYourStats": "Dine Statistikker",
|
"HeaderYourStats": "Dine Statistikker",
|
||||||
"LabelAdded": "Tilføjet",
|
|
||||||
"LabelAddedAt": "Tilføjet Kl.",
|
|
||||||
"LabelAddToPlaylist": "Tilføj til Afspilningsliste",
|
"LabelAddToPlaylist": "Tilføj til Afspilningsliste",
|
||||||
|
"LabelAdded": "Tilføjet",
|
||||||
|
"LabelAddedAt": "Tilføjet Kl",
|
||||||
"LabelAll": "Alle",
|
"LabelAll": "Alle",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Tillad positionssøgning på mediemeddelelseskontroller",
|
||||||
|
"LabelAlways": "Altid",
|
||||||
|
"LabelAskConfirmation": "Bed om bekræftelse",
|
||||||
"LabelAuthor": "Forfatter",
|
"LabelAuthor": "Forfatter",
|
||||||
"LabelAuthorFirstLast": "Forfatter (Fornavn Efternavn)",
|
"LabelAuthorFirstLast": "Forfatter (Fornavn Efternavn)",
|
||||||
"LabelAuthorLastFirst": "Forfatter (Efternavn, Fornavn)",
|
"LabelAuthorLastFirst": "Forfatter (Efternavn, Fornavn)",
|
||||||
"LabelAuthors": "Forfattere",
|
"LabelAuthors": "Forfattere",
|
||||||
"LabelAutoDownloadEpisodes": "Auto Download Episoder",
|
"LabelAutoDownloadEpisodes": "Auto Download Episoder",
|
||||||
"LabelAutoRewindTime": "Auto rewind time",
|
"LabelAutoRewindTime": "Automatisk tilbagespolingstid",
|
||||||
"LabelAutoSleepTimer": "Auto sleep timer",
|
"LabelAutoSleepTimer": "Auto sleep timer",
|
||||||
"LabelAutoSleepTimerAutoRewind": "Auto sleep timer auto rewind",
|
"LabelAutoSleepTimerAutoRewind": "Automatisk sleep timer automatisk tilbagespoling",
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
"LabelAutoSleepTimerAutoRewindHelp": "Når den automatiske sleep-timer er færdig, vil genafspilning af elementet automatisk spole din position tilbage.",
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
"LabelAutoSleepTimerHelp": "Når der afspilles medie mellem de angivne start- og sluttidspunkter, starter en sleep-timer automatisk.",
|
||||||
"LabelBooks": "Bøger",
|
"LabelBooks": "Bøger",
|
||||||
|
"LabelChapterTrack": "Kapitel spor",
|
||||||
"LabelChapters": "Kapitler",
|
"LabelChapters": "Kapitler",
|
||||||
"LabelChapterTrack": "Chapter Track",
|
|
||||||
"LabelClosePlayer": "Luk afspiller",
|
"LabelClosePlayer": "Luk afspiller",
|
||||||
"LabelCollapseSeries": "Fold Serie Sammen",
|
"LabelCollapseSeries": "Fold Serier Sammen",
|
||||||
"LabelComplete": "Fuldfør",
|
"LabelComplete": "Fuldfør",
|
||||||
"LabelContinueBooks": "Continue Books",
|
"LabelContinueBooks": "Fortsæt Bøger",
|
||||||
"LabelContinueEpisodes": "Continue Episodes",
|
"LabelContinueEpisodes": "Fortsæt Episoder",
|
||||||
"LabelContinueListening": "Continue Listening",
|
"LabelContinueListening": "Fortsæt med at lytte",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Fortsæt med at læse",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Fortsæt Serien",
|
||||||
"LabelCustomTime": "Custom time",
|
"LabelCustomTime": "Brugerdefineret tid",
|
||||||
"LabelDescription": "Beskrivelse",
|
"LabelDescription": "Beskrivelse",
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
"LabelDisableAudioFadeOut": "Deaktiver lydudtoning",
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
"LabelDisableAudioFadeOutHelp": "Lydstyrken begynder at falde, når der er mindre end 1 minut tilbage på sleep-timeren. Aktiver denne indstilling for ikke at fade ud.",
|
||||||
"LabelDisableAutoRewind": "Disable auto rewind",
|
"LabelDisableAutoRewind": "Deaktiver automatisk tilbagespoling",
|
||||||
"LabelDisableShakeToReset": "Disable shake to reset",
|
"LabelDisableShakeToReset": "Deaktiver ryst for at nulstille",
|
||||||
"LabelDisableShakeToResetHelp": "Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.",
|
"LabelDisableShakeToResetHelp": "Hvis du ryster din enhed, mens timeren kører ELLER inden for 2 minutter før, at timeren er udløbet, nulstilles sleeptimeren. Aktiver denne indstilling for at deaktivere \"rystelse for at nulstille\".",
|
||||||
"LabelDisableVibrateOnReset": "Disable vibrate on reset",
|
"LabelDisableVibrateOnReset": "Deaktiver vibrering ved nulstilling",
|
||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
"LabelDisableVibrateOnResetHelp": "Når sleep-timeren nulstilles, vil din enhed vibrere. Aktiver denne indstilling for ikke at vibrere, når sleep-timeren nulstilles.",
|
||||||
"LabelDiscover": "Discover",
|
"LabelDiscover": "Opdag",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
"LabelDownloaded": "Downloaded",
|
"LabelDownloadUsingCellular": "Download med mobildata",
|
||||||
|
"LabelDownloaded": "Downloadet",
|
||||||
"LabelDuration": "Varighed",
|
"LabelDuration": "Varighed",
|
||||||
"LabelEbook": "E-bog",
|
"LabelEbook": "E-bog",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "E-bøger",
|
||||||
"LabelEnable": "Aktivér",
|
"LabelEnable": "Aktivér",
|
||||||
"LabelEnableMp3IndexSeeking": "Enable mp3 index seeking",
|
"LabelEnableMp3IndexSeeking": "Aktiver mp3-indekssøgning",
|
||||||
"LabelEnableMp3IndexSeekingHelp": "This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.",
|
"LabelEnableMp3IndexSeekingHelp": "Denne indstilling bør kun aktiveres, hvis du har mp3-filer, der ikke søger korrekt. Upræcis søgning skyldes højst sandsynligt Variable birate (VBR) MP3-filer. Denne indstilling vil fremtvinge indekssøgning, hvor en tid-til-byte mapping er bygget, mens filen læses. I nogle tilfælde med store MP3-filer vil der være en forsinkelse ved søgning mod slutningen af filen.",
|
||||||
"LabelEnd": "Slut",
|
"LabelEnd": "Slut",
|
||||||
"LabelEndOfChapter": "End of Chapter",
|
"LabelEndOfChapter": "Slutningen af kapitel",
|
||||||
"LabelEndTime": "End time",
|
"LabelEndTime": "Sluttidspunkt",
|
||||||
"LabelEpisode": "Episode",
|
"LabelEpisode": "Episode",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFile": "Fil",
|
"LabelFile": "Fil",
|
||||||
"LabelFileBirthtime": "Fødselstidspunkt for fil",
|
"LabelFileBirthtime": "Oprettelsestidspunkt for fil",
|
||||||
"LabelFileModified": "Fil ændret",
|
"LabelFileModified": "Fil ændret",
|
||||||
"LabelFilename": "Filnavn",
|
"LabelFilename": "Filnavn",
|
||||||
"LabelFinished": "Færdig",
|
"LabelFinished": "Færdig",
|
||||||
"LabelFolder": "Mappe",
|
"LabelFolder": "Mappe",
|
||||||
|
"LabelFontBoldness": "Skrift tykkelse",
|
||||||
"LabelFontScale": "Skriftstørrelse",
|
"LabelFontScale": "Skriftstørrelse",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genrer",
|
"LabelGenres": "Genrer",
|
||||||
"LabelHapticFeedback": "Haptic feedback",
|
"LabelHapticFeedback": "Haptisk feedback",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Har e-bog",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Har supplerende e-bog",
|
||||||
"LabelHeavy": "Heavy",
|
"LabelHeavy": "Tung",
|
||||||
"LabelHigh": "High",
|
"LabelHigh": "Høj",
|
||||||
"LabelHost": "Vært",
|
"LabelHost": "Vært",
|
||||||
"LabelIncomplete": "Ufuldstændig",
|
|
||||||
"LabelInProgress": "I gang",
|
"LabelInProgress": "I gang",
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
"LabelIncomplete": "Ufuldstændig",
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
"LabelJumpBackwardsTime": "Spring tilbage tid",
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
"LabelJumpForwardsTime": "Spring frem tid",
|
||||||
"LabelLanguage": "Sprog",
|
"LabelLanguage": "Sprog",
|
||||||
"LabelLayout": "Layout",
|
"LabelLayout": "Layout",
|
||||||
"LabelLayoutAuto": "Auto",
|
|
||||||
"LabelLayoutSinglePage": "Single page",
|
|
||||||
"LabelLight": "Light",
|
|
||||||
"LabelLineSpacing": "Linjeafstand",
|
"LabelLineSpacing": "Linjeafstand",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelLockPlayer": "Lås afspiller",
|
||||||
"LabelLocalBooks": "Local Books",
|
|
||||||
"LabelLocalPodcasts": "Local Podcasts",
|
|
||||||
"LabelLockOrientation": "Lock orientation",
|
|
||||||
"LabelLockPlayer": "Lock Player",
|
|
||||||
"LabelLow": "Low",
|
|
||||||
"LabelMediaType": "Medietype",
|
"LabelMediaType": "Medietype",
|
||||||
"LabelMedium": "Medium",
|
|
||||||
"LabelMore": "Mere",
|
"LabelMore": "Mere",
|
||||||
"LabelMoreInfo": "Mere info",
|
"LabelMoreInfo": "Mere info",
|
||||||
"LabelName": "Navn",
|
"LabelName": "Navn",
|
||||||
"LabelNarrator": "Fortæller",
|
"LabelNarrator": "Fortæller",
|
||||||
"LabelNarrators": "Fortællere",
|
"LabelNarrators": "Fortællere",
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNever": "Aldrig",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
|
||||||
"LabelNo": "No",
|
|
||||||
"LabelNotFinished": "Ikke færdig",
|
"LabelNotFinished": "Ikke færdig",
|
||||||
"LabelNotStarted": "Ikke påbegyndt",
|
"LabelNotStarted": "Ikke påbegyndt",
|
||||||
"LabelOff": "Off",
|
|
||||||
"LabelPassword": "Kodeord",
|
"LabelPassword": "Kodeord",
|
||||||
"LabelPath": "Sti",
|
"LabelPath": "Sti",
|
||||||
"LabelPlaybackDirect": "Direct",
|
"LabelPlaybackSpeed": "Afspilningshastighed",
|
||||||
"LabelPlaybackLocal": "Local",
|
|
||||||
"LabelPlaybackSpeed": "Playback Speed",
|
|
||||||
"LabelPlaybackTranscode": "Transcode",
|
"LabelPlaybackTranscode": "Transcode",
|
||||||
"LabelPodcast": "Podcast",
|
|
||||||
"LabelPodcasts": "Podcasts",
|
|
||||||
"LabelPreventIndexing": "Forhindrer, at dit feed bliver indekseret af iTunes og Google podcastkataloger",
|
"LabelPreventIndexing": "Forhindrer, at dit feed bliver indekseret af iTunes og Google podcastkataloger",
|
||||||
"LabelProgress": "Fremskridt",
|
"LabelProgress": "Fremskridt",
|
||||||
"LabelPubDate": "Udgivelsesdato",
|
"LabelPubDate": "Udgivelsesdato",
|
||||||
"LabelPublishYear": "Udgivelsesår",
|
"LabelPublishYear": "Udgivelsesår",
|
||||||
"LabelRead": "Læst",
|
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
|
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
|
||||||
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
|
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
|
||||||
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
|
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
|
||||||
"LabelRSSFeedSlug": "RSS-feed-slug",
|
"LabelRSSFeedSlug": "RSS-feed-slug",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
"LabelRandomly": "Tilfældigt",
|
||||||
|
"LabelRead": "Læst",
|
||||||
|
"LabelRecentSeries": "Seneste serier",
|
||||||
"LabelSeason": "Sæson",
|
"LabelSeason": "Sæson",
|
||||||
"LabelSelectADevice": "Select a device",
|
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
"LabelServerAddress": "Server address",
|
|
||||||
"LabelSetEbookAsPrimary": "Indstil som primær",
|
"LabelSetEbookAsPrimary": "Indstil som primær",
|
||||||
"LabelSetEbookAsSupplementary": "Indstil som supplerende",
|
"LabelSetEbookAsSupplementary": "Indstil som supplerende",
|
||||||
"LabelShakeSensitivity": "Shake sensitivity",
|
|
||||||
"LabelShowAll": "Vis alle",
|
"LabelShowAll": "Vis alle",
|
||||||
"LabelSize": "Størrelse",
|
"LabelSize": "Størrelse",
|
||||||
"LabelSleepTimer": "Søvntimer",
|
"LabelSleepTimer": "Søvntimer",
|
||||||
"LabelStart": "Start",
|
|
||||||
"LabelStartTime": "Starttid",
|
"LabelStartTime": "Starttid",
|
||||||
"LabelStatsBestDay": "Bedste dag",
|
"LabelStatsBestDay": "Bedste dag",
|
||||||
"LabelStatsDailyAverage": "Daglig gennemsnit",
|
"LabelStatsDailyAverage": "Daglig gennemsnit",
|
||||||
@@ -223,60 +208,33 @@
|
|||||||
"LabelThemeLight": "Lys",
|
"LabelThemeLight": "Lys",
|
||||||
"LabelTimeRemaining": "{0} tilbage",
|
"LabelTimeRemaining": "{0} tilbage",
|
||||||
"LabelTitle": "Titel",
|
"LabelTitle": "Titel",
|
||||||
"LabelTotalTrack": "Total Track",
|
|
||||||
"LabelTracks": "Spor",
|
"LabelTracks": "Spor",
|
||||||
"LabelType": "Type",
|
|
||||||
"LabelUnlockPlayer": "Unlock Player",
|
|
||||||
"LabelUseBookshelfView": "Use bookshelf view",
|
|
||||||
"LabelUser": "Bruger",
|
"LabelUser": "Bruger",
|
||||||
"LabelUsername": "Brugernavn",
|
"LabelUsername": "Brugernavn",
|
||||||
"LabelVeryHigh": "Very High",
|
|
||||||
"LabelVeryLow": "Very Low",
|
|
||||||
"LabelYourBookmarks": "Dine bogmærker",
|
"LabelYourBookmarks": "Dine bogmærker",
|
||||||
"LabelYourProgress": "Din fremgang",
|
"LabelYourProgress": "Din fremgang",
|
||||||
"MessageAndroid10Downloads": "Android 10 and below will use internal app storage for downloads.",
|
"MessageDiscardProgress": "Kassér fremgang",
|
||||||
"MessageAttemptingServerConnection": "Attempting server connection...",
|
"MessageDownloadCompleteProcessing": "Download fuldført. Behandler...",
|
||||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf server not connected",
|
"MessageDownloading": "Downloader...",
|
||||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> This app is designed to work with an Audiobookshelf server that you or someone you know is hosting. This app does not provide any content.",
|
|
||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
|
||||||
"MessageDownloading": "Downloading...",
|
|
||||||
"MessageDownloadingEpisode": "Downloader episode",
|
"MessageDownloadingEpisode": "Downloader episode",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} episoder er sat i kø til download",
|
"MessageEpisodesQueuedForDownload": "{0} episoder er sat i kø til download",
|
||||||
"MessageFeedURLWillBe": "Feed-URL vil være {0}",
|
"MessageFeedURLWillBe": "Feed-URL vil være {0}",
|
||||||
"MessageFetching": "Henter...",
|
"MessageFetching": "Henter...",
|
||||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
"MessageFollowTheProjectOnGithub": "Follow the project on GitHub",
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Item download complete but failed to create library item",
|
|
||||||
"MessageLoading": "Indlæser...",
|
"MessageLoading": "Indlæser...",
|
||||||
"MessageLoadingServerData": "Loading server data...",
|
|
||||||
"MessageMarkAsFinished": "Markér som afsluttet",
|
"MessageMarkAsFinished": "Markér som afsluttet",
|
||||||
"MessageMediaLinkedToADifferentServer": "Media is linked to an Audiobookshelf server on a different address ({0}). Progress will be synced when connected to this server address.",
|
|
||||||
"MessageMediaLinkedToADifferentUser": "Media is linked to this server but was downloaded by a different user. Progress will only be synced to the user that downloaded it.",
|
|
||||||
"MessageMediaLinkedToServer": "Linked to server {0}",
|
|
||||||
"MessageMediaLinkedToThisServer": "Downloaded media is linked to this server",
|
|
||||||
"MessageMediaNotLinkedToServer": "Media is not linked to an Audiobookshelf server. No progress will be synced.",
|
|
||||||
"MessageNoBookmarks": "Ingen bogmærker",
|
"MessageNoBookmarks": "Ingen bogmærker",
|
||||||
"MessageNoChapters": "Ingen kapitler",
|
"MessageNoChapters": "Ingen kapitler",
|
||||||
"MessageNoItems": "Ingen elementer",
|
"MessageNoItems": "Ingen elementer",
|
||||||
"MessageNoItemsFound": "Ingen elementer fundet",
|
"MessageNoItemsFound": "Ingen elementer fundet",
|
||||||
"MessageNoListeningSessions": "Ingen lyttesessioner",
|
"MessageNoListeningSessions": "Ingen lyttesessioner",
|
||||||
"MessageNoMediaFolders": "No Media Folders",
|
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
|
||||||
"MessageNoPodcastsFound": "Ingen podcasts fundet",
|
"MessageNoPodcastsFound": "Ingen podcasts fundet",
|
||||||
|
"MessageNoSeries": "Ingen serier",
|
||||||
"MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige",
|
"MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige",
|
||||||
"MessageNoUserPlaylists": "Du har ingen afspilningslister",
|
"MessageNoUserPlaylists": "Du har ingen afspilningslister",
|
||||||
"MessageReportBugsAndContribute": "Rapporter fejl, anmod om funktioner og bidrag på",
|
"MessageReportBugsAndContribute": "Rapporter fejl, anmod om funktioner og bidrag på",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered Wi-Fi",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered Wi-Fi",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered wifi",
|
|
||||||
"MessageSocketNotConnected": "Socket not connected",
|
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Advarsel: De fleste podcast-apps kræver, at RSS-feedets URL bruger HTTPS",
|
"NoteRSSFeedPodcastAppsHttps": "Advarsel: De fleste podcast-apps kræver, at RSS-feedets URL bruger HTTPS",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Advarsel: En eller flere af dine episoder har ikke en Pub Date. Nogle podcast-apps kræver dette.",
|
"NoteRSSFeedPodcastAppsPubDate": "Advarsel: En eller flere af dine episoder har ikke en Pub Date. Nogle podcast-apps kræver dette.",
|
||||||
"ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke",
|
"ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke",
|
||||||
|
|||||||
+72
-47
@@ -19,6 +19,7 @@
|
|||||||
"ButtonDeleteLocalItem": "Lösche lokales Element",
|
"ButtonDeleteLocalItem": "Lösche lokales Element",
|
||||||
"ButtonDisableAutoTimer": "Deaktiviere automatischen Timer",
|
"ButtonDisableAutoTimer": "Deaktiviere automatischen Timer",
|
||||||
"ButtonDisconnect": "Trennen",
|
"ButtonDisconnect": "Trennen",
|
||||||
|
"ButtonGoToWebClient": "Gehe zum Web Client",
|
||||||
"ButtonHistory": "Historie",
|
"ButtonHistory": "Historie",
|
||||||
"ButtonHome": "Startseite",
|
"ButtonHome": "Startseite",
|
||||||
"ButtonIssues": "Probleme",
|
"ButtonIssues": "Probleme",
|
||||||
@@ -30,20 +31,21 @@
|
|||||||
"ButtonNextEpisode": "Nächste Episode",
|
"ButtonNextEpisode": "Nächste Episode",
|
||||||
"ButtonOpenFeed": "Feed öffnen",
|
"ButtonOpenFeed": "Feed öffnen",
|
||||||
"ButtonOverride": "Überschreiben",
|
"ButtonOverride": "Überschreiben",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pausieren",
|
||||||
"ButtonPlay": "Abspielen",
|
"ButtonPlay": "Abspielen",
|
||||||
|
"ButtonPlayEpisode": "Episode abspielen",
|
||||||
"ButtonPlaying": "Spielt",
|
"ButtonPlaying": "Spielt",
|
||||||
"ButtonPlaylists": "Wiedergabelisten",
|
"ButtonPlaylists": "Wiedergabelisten",
|
||||||
"ButtonRead": "Lesen",
|
"ButtonRead": "Lesen",
|
||||||
"ButtonRemove": "Löschen",
|
"ButtonRemove": "Entfernen",
|
||||||
"ButtonRemoveFromServer": "Vom Server entfernen",
|
"ButtonRemoveFromServer": "Vom Server entfernen",
|
||||||
"ButtonSave": "Speichern",
|
"ButtonSave": "Speichern",
|
||||||
"ButtonSaveOrder": "Reihenfolge speichern",
|
"ButtonSaveOrder": "Reihenfolge speichern",
|
||||||
"ButtonSearch": "Suchen",
|
"ButtonSearch": "Suchen",
|
||||||
"ButtonSendEbookToDevice": "Sende E-Book an Gerät",
|
"ButtonSendEbookToDevice": "E-Buch an Gerät senden",
|
||||||
"ButtonSeries": "Serien",
|
"ButtonSeries": "Serien",
|
||||||
"ButtonSetTimer": "Setze Timer",
|
"ButtonSetTimer": "Setze Timer",
|
||||||
"ButtonStream": "Stream",
|
"ButtonStream": "Streamen",
|
||||||
"ButtonSubmit": "Ok",
|
"ButtonSubmit": "Ok",
|
||||||
"ButtonSwitchServerUser": "Wechsle Server/Benutzer",
|
"ButtonSwitchServerUser": "Wechsle Server/Benutzer",
|
||||||
"ButtonUserStats": "Nutzerstatistiken",
|
"ButtonUserStats": "Nutzerstatistiken",
|
||||||
@@ -52,21 +54,22 @@
|
|||||||
"HeaderAdvanced": "Erweitert",
|
"HeaderAdvanced": "Erweitert",
|
||||||
"HeaderAudioTracks": "Audiodateien",
|
"HeaderAudioTracks": "Audiodateien",
|
||||||
"HeaderChapters": "Kapitel",
|
"HeaderChapters": "Kapitel",
|
||||||
"HeaderCollection": "Sammlungen",
|
"HeaderCollection": "Sammlung",
|
||||||
"HeaderCollectionItems": "Sammlungseinträge",
|
"HeaderCollectionItems": "Sammlungseinträge",
|
||||||
"HeaderConnectionStatus": "Verbindungsstatus",
|
"HeaderConnectionStatus": "Verbindungsstatus",
|
||||||
|
"HeaderDataSettings": "Daten-Einstellungen",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Downloads",
|
||||||
"HeaderEbookFiles": "E-Book Dateien",
|
"HeaderEbookFiles": "E-Buch-Dateien",
|
||||||
"HeaderEpisodes": "Episoden",
|
"HeaderEpisodes": "Episoden",
|
||||||
"HeaderEreaderSettings": "E-Reader Einstellungen",
|
"HeaderEreaderSettings": "Einstellungen zum Lesen",
|
||||||
"HeaderLatestEpisodes": "Neueste Episoden",
|
"HeaderLatestEpisodes": "Neueste Episoden",
|
||||||
"HeaderLibraries": "Bibliotheken",
|
"HeaderLibraries": "Bibliotheken",
|
||||||
"HeaderLocalFolders": "Lokale Ordner",
|
"HeaderLocalFolders": "Lokale Ordner",
|
||||||
"HeaderLocalLibraryItems": "Lokale Bibliothek Elemente",
|
"HeaderLocalLibraryItems": "Elemente der lokalen Bibliothek",
|
||||||
"HeaderNewPlaylist": "Neue Wiedergabeliste",
|
"HeaderNewPlaylist": "Neue Wiedergabeliste",
|
||||||
"HeaderOpenRSSFeed": "RSS-Feed öffnen",
|
"HeaderOpenRSSFeed": "RSS-Feed öffnen",
|
||||||
"HeaderPlaybackSettings": "Wiedergabe Einstellungen",
|
"HeaderPlaybackSettings": "Wiedergabe-Einstellungen",
|
||||||
"HeaderPlaylist": "Wiedergabeliste",
|
"HeaderPlaylist": "Wiedergabeliste",
|
||||||
"HeaderPlaylistItems": "Einträge in der Wiedergabeliste",
|
"HeaderPlaylistItems": "Einträge in der Wiedergabeliste",
|
||||||
"HeaderRSSFeed": "RSS-Feed",
|
"HeaderRSSFeed": "RSS-Feed",
|
||||||
@@ -74,32 +77,35 @@
|
|||||||
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
||||||
"HeaderSelectDownloadLocation": "Wähle Download-Speicherort",
|
"HeaderSelectDownloadLocation": "Wähle Download-Speicherort",
|
||||||
"HeaderSettings": "Einstellungen",
|
"HeaderSettings": "Einstellungen",
|
||||||
"HeaderSleepTimer": "Sleep-Timer",
|
"HeaderSleepTimer": "Schlummerfunktion",
|
||||||
"HeaderSleepTimerSettings": "Sleep-Timer Einstellungen",
|
"HeaderSleepTimerSettings": "Schlummerfunktion Einstellungen",
|
||||||
"HeaderStatsMinutesListeningChart": "Hörminuten (letzte 7 Tage)",
|
"HeaderStatsMinutesListeningChart": "Hörminuten (letzte 7 Tage)",
|
||||||
"HeaderStatsRecentSessions": "Neueste Ereignisse",
|
"HeaderStatsRecentSessions": "Neueste Ereignisse",
|
||||||
"HeaderTableOfContents": "Inhaltsverzeichnis",
|
"HeaderTableOfContents": "Inhaltsverzeichnis",
|
||||||
"HeaderUserInterfaceSettings": "Einstellungen der Benutzeroberfläche",
|
"HeaderUserInterfaceSettings": "Einstellungen der Benutzeroberfläche",
|
||||||
"HeaderYourStats": "Eigene Statistiken",
|
"HeaderYourStats": "Eigene Statistiken",
|
||||||
|
"LabelAddToPlaylist": "Zur Wiedergabeliste hinzufügen",
|
||||||
"LabelAdded": "Hinzugefügt",
|
"LabelAdded": "Hinzugefügt",
|
||||||
"LabelAddedAt": "Hinzugefügt am",
|
"LabelAddedAt": "Hinzugefügt am",
|
||||||
"LabelAddToPlaylist": "Zur Wiedergabeliste hinzufügen",
|
|
||||||
"LabelAll": "Alle",
|
"LabelAll": "Alle",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Erlaube Vor- und Zurückspulen auf dem Medienkontrollelement bei den Benachrichtigungen",
|
||||||
|
"LabelAlways": "Immer",
|
||||||
|
"LabelAskConfirmation": "Bestätigung anfordern",
|
||||||
"LabelAuthor": "Autor",
|
"LabelAuthor": "Autor",
|
||||||
"LabelAuthorFirstLast": "Autor (Vorname Nachname)",
|
"LabelAuthorFirstLast": "Autor (Vorname Nachname)",
|
||||||
"LabelAuthorLastFirst": "Autor (Nachname, Vorname)",
|
"LabelAuthorLastFirst": "Autor (Nachname, Vorname)",
|
||||||
"LabelAuthors": "Autoren",
|
"LabelAuthors": "Autoren",
|
||||||
"LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen",
|
"LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen",
|
||||||
"LabelAutoRewindTime": "Automatische Rückspulzeit",
|
"LabelAutoRewindTime": "Automatische Rückspulzeit",
|
||||||
"LabelAutoSleepTimer": "Automatischer Sleep-Timer",
|
"LabelAutoSleepTimer": "Automatische Schlummerfunktion",
|
||||||
"LabelAutoSleepTimerAutoRewind": "Automatischer Sleep-Timer automatische Rückspulzeit",
|
"LabelAutoSleepTimerAutoRewind": "Automatische Schlummerfunktion automatische Rückspulzeit",
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "Wenn der Sleep-Timer abgelaufen ist, wird bei der erneuten Wiedergabe des Titels die Position automatisch zurückgespult.",
|
"LabelAutoSleepTimerAutoRewindHelp": "Wenn die Schlummerfunktion abgelaufen ist, wird bei der erneuten Wiedergabe des Titels die Position automatisch zurückgespult.",
|
||||||
"LabelAutoSleepTimerHelp": "Bei der Wiedergabe von Medien zwischen der angegebenen Start- und Endzeit wird automatisch ein Sleep-Timer gestartet.",
|
"LabelAutoSleepTimerHelp": "Bei der Wiedergabe von Medien zwischen der angegebenen Start- und Endzeit wird automatisch eine Schlummerfunktion gestartet.",
|
||||||
"LabelBooks": "Bücher",
|
"LabelBooks": "Bücher",
|
||||||
|
"LabelChapterTrack": "Kapitelspur",
|
||||||
"LabelChapters": "Kapitel",
|
"LabelChapters": "Kapitel",
|
||||||
"LabelChapterTrack": "Kapitel Spur",
|
|
||||||
"LabelClosePlayer": "Player schließen",
|
"LabelClosePlayer": "Player schließen",
|
||||||
"LabelCollapseSeries": "Serien zusammenfassen",
|
"LabelCollapseSeries": "Serien einklappen",
|
||||||
"LabelComplete": "Vollständig",
|
"LabelComplete": "Vollständig",
|
||||||
"LabelContinueBooks": "Bücher fortfahren",
|
"LabelContinueBooks": "Bücher fortfahren",
|
||||||
"LabelContinueEpisodes": "Episoden fortfahren",
|
"LabelContinueEpisodes": "Episoden fortfahren",
|
||||||
@@ -117,42 +123,44 @@
|
|||||||
"LabelDisableVibrateOnResetHelp": "Wenn der Sleep-Timer zurückgesetzt wird, vibriert dein Gerät. Aktiviere diese Einstellung, um nicht zu vibrieren, wenn der Sleep-Timer zurückgesetzt wird.",
|
"LabelDisableVibrateOnResetHelp": "Wenn der Sleep-Timer zurückgesetzt wird, vibriert dein Gerät. Aktiviere diese Einstellung, um nicht zu vibrieren, wenn der Sleep-Timer zurückgesetzt wird.",
|
||||||
"LabelDiscover": "Entdecken",
|
"LabelDiscover": "Entdecken",
|
||||||
"LabelDownload": "Herunterladen",
|
"LabelDownload": "Herunterladen",
|
||||||
|
"LabelDownloadUsingCellular": "Über mobile Daten herunterladen",
|
||||||
"LabelDownloaded": "Heruntergeladen",
|
"LabelDownloaded": "Heruntergeladen",
|
||||||
"LabelDuration": "Laufzeit",
|
"LabelDuration": "Laufzeit",
|
||||||
"LabelEbook": "E-Book",
|
"LabelEbook": "E-Buch",
|
||||||
"LabelEbooks": "E-Books",
|
"LabelEbooks": "E-Bücher",
|
||||||
"LabelEnable": "Aktivieren",
|
"LabelEnable": "Aktivieren",
|
||||||
"LabelEnableMp3IndexSeeking": "MP3-Indexsuche",
|
"LabelEnableMp3IndexSeeking": "MP3-Indexsuche",
|
||||||
"LabelEnableMp3IndexSeekingHelp": "Diese Einstellung sollte nur aktiviert werden, wenn du MP3-Dateien hast, bei denen das Navigieren (Seeking) nicht korrekt funktioniert. Ungenaues Navigieren ist höchstwahrscheinlich auf MP3-Dateien mit variabler Bitrate (VBR) zurückzuführen. Diese Einstellung erzwingt das Index-Navigieren (Index Seeking), bei dem eine Zeit-zu-Byte-Zuordnung erstellt wird, während die Datei gelesen wird. In einigen Fällen kann es bei großen MP3-Dateien zu einer Verzögerung kommen, wenn gegen Ende der Datei navigiert wird.",
|
"LabelEnableMp3IndexSeekingHelp": "Diese Einstellung sollte nur aktiviert werden, wenn du MP3-Dateien hast, bei denen das Navigieren (Seeking) nicht korrekt funktioniert. Ungenaues Navigieren ist höchstwahrscheinlich auf MP3-Dateien mit variabler Bitrate (VBR) zurückzuführen. Diese Einstellung erzwingt das Index-Navigieren (Index Seeking), bei dem eine Zeit-zu-Byte-Zuordnung erstellt wird, während die Datei gelesen wird. In einigen Fällen kann es bei großen MP3-Dateien zu einer Verzögerung kommen, wenn gegen Ende der Datei navigiert wird.",
|
||||||
"LabelEnd": "Ende",
|
"LabelEnd": "Ende",
|
||||||
"LabelEndOfChapter": "Kapitelende",
|
"LabelEndOfChapter": "Ende des Kapitels",
|
||||||
"LabelEndTime": "Endzeit",
|
"LabelEndTime": "Endzeit",
|
||||||
"LabelEpisode": "Episode",
|
"LabelEpisode": "Episode",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed-URL",
|
||||||
"LabelFile": "Datei",
|
"LabelFile": "Datei",
|
||||||
"LabelFileBirthtime": "Datei erstellt",
|
"LabelFileBirthtime": "Datei erstellt",
|
||||||
"LabelFileModified": "Datei geändert",
|
"LabelFileModified": "Datei geändert",
|
||||||
"LabelFilename": "Dateiname",
|
"LabelFilename": "Dateiname",
|
||||||
"LabelFinished": "Beendet",
|
"LabelFinished": "Beendet",
|
||||||
"LabelFolder": "Ordner",
|
"LabelFolder": "Ordner",
|
||||||
|
"LabelFontBoldness": "Schriftstärke",
|
||||||
"LabelFontScale": "Schriftgröße",
|
"LabelFontScale": "Schriftgröße",
|
||||||
"LabelGenre": "Kategorie",
|
"LabelGenre": "Kategorie",
|
||||||
"LabelGenres": "Kategorien",
|
"LabelGenres": "Kategorien",
|
||||||
"LabelHapticFeedback": "Haptische Rückmeldung",
|
"LabelHapticFeedback": "Haptische Rückmeldung",
|
||||||
"LabelHasEbook": "E-Book verfügbar",
|
"LabelHasEbook": "E-Buch verfügbar",
|
||||||
"LabelHasSupplementaryEbook": "Ergänzendes E-Book verfügbar",
|
"LabelHasSupplementaryEbook": "Ergänzendes E-Buch verfügbar",
|
||||||
"LabelHeavy": "Stark",
|
"LabelHeavy": "Stark",
|
||||||
"LabelHigh": "Hoch",
|
"LabelHigh": "Hoch",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Anbieter",
|
||||||
"LabelIncomplete": "Unvollständig",
|
|
||||||
"LabelInProgress": "In Bearbeitung",
|
"LabelInProgress": "In Bearbeitung",
|
||||||
|
"LabelIncomplete": "Unvollständig",
|
||||||
"LabelInternalAppStorage": "Interner App Speicher",
|
"LabelInternalAppStorage": "Interner App Speicher",
|
||||||
"LabelJumpBackwardsTime": "Rückspulzeit",
|
"LabelJumpBackwardsTime": "Rückspulzeit",
|
||||||
"LabelJumpForwardsTime": "Vorwärtsspulzeit",
|
"LabelJumpForwardsTime": "Vorwärtsspulzeit",
|
||||||
"LabelLanguage": "Sprache",
|
"LabelLanguage": "Sprache",
|
||||||
"LabelLayout": "Layout",
|
"LabelLayout": "Ansicht",
|
||||||
"LabelLayoutAuto": "Auto",
|
"LabelLayoutAuto": "Auto",
|
||||||
"LabelLayoutSinglePage": "Einzelne Seite",
|
"LabelLayoutSinglePage": "Eine Seite",
|
||||||
"LabelLight": "Leicht",
|
"LabelLight": "Leicht",
|
||||||
"LabelLineSpacing": "Zeilenabstand",
|
"LabelLineSpacing": "Zeilenabstand",
|
||||||
"LabelListenAgain": "Erneut anhören",
|
"LabelListenAgain": "Erneut anhören",
|
||||||
@@ -168,6 +176,11 @@
|
|||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
"LabelNarrator": "Erzähler",
|
"LabelNarrator": "Erzähler",
|
||||||
"LabelNarrators": "Erzähler",
|
"LabelNarrators": "Erzähler",
|
||||||
|
"LabelNavigateWithVolume": "Mit Lautstärke blättern",
|
||||||
|
"LabelNavigateWithVolumeDisabled": "Aus",
|
||||||
|
"LabelNavigateWithVolumeEnabled": "An",
|
||||||
|
"LabelNavigateWithVolumeMirrored": "Umgekehrt",
|
||||||
|
"LabelNever": "Nie",
|
||||||
"LabelNewestAuthors": "Neueste Autoren",
|
"LabelNewestAuthors": "Neueste Autoren",
|
||||||
"LabelNewestEpisodes": "Neueste Episoden",
|
"LabelNewestEpisodes": "Neueste Episoden",
|
||||||
"LabelNo": "Nein",
|
"LabelNo": "Nein",
|
||||||
@@ -186,25 +199,27 @@
|
|||||||
"LabelProgress": "Fortschritt",
|
"LabelProgress": "Fortschritt",
|
||||||
"LabelPubDate": "Veröffentlichungsdatum",
|
"LabelPubDate": "Veröffentlichungsdatum",
|
||||||
"LabelPublishYear": "Jahr",
|
"LabelPublishYear": "Jahr",
|
||||||
"LabelRead": "Lesen",
|
|
||||||
"LabelReadAgain": "Erneut lesen",
|
|
||||||
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
|
||||||
"LabelRecentSeries": "Aktuelle Serien",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
||||||
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
||||||
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
||||||
"LabelRSSFeedSlug": "RSS Feed Schlagwort",
|
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
||||||
|
"LabelRandomly": "Zufällig",
|
||||||
|
"LabelRead": "Lesen",
|
||||||
|
"LabelReadAgain": "Noch einmal Lesen",
|
||||||
|
"LabelRecentSeries": "Aktuelle Serien",
|
||||||
|
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
||||||
|
"LabelRemoveFromPlaylist": "Von Wiedergabeliste entfernen",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Vergangene Zeit anhand der Geschwindigkeit skalieren",
|
"LabelScaleElapsedTimeBySpeed": "Vergangene Zeit anhand der Geschwindigkeit skalieren",
|
||||||
"LabelSeason": "Staffel",
|
"LabelSeason": "Staffel",
|
||||||
"LabelSelectADevice": "Wähle ein Gerät",
|
"LabelSelectADevice": "Wähle ein Gerät",
|
||||||
"LabelSeries": "Serien",
|
"LabelSeries": "Serien",
|
||||||
"LabelServerAddress": "Server Adresse",
|
"LabelServerAddress": "Serveradresse",
|
||||||
"LabelSetEbookAsPrimary": "Als Hauptbuch setzen",
|
"LabelSetEbookAsPrimary": "Als Hauptbuch setzen",
|
||||||
"LabelSetEbookAsSupplementary": "Als Ergänzung setzen",
|
"LabelSetEbookAsSupplementary": "Als Ergänzung setzen",
|
||||||
"LabelShakeSensitivity": "Schüttel-Empfindlichkeit",
|
"LabelShakeSensitivity": "Schüttel-Empfindlichkeit",
|
||||||
"LabelShowAll": "Alles anzeigen",
|
"LabelShowAll": "Alles anzeigen",
|
||||||
"LabelSize": "Größe",
|
"LabelSize": "Größe",
|
||||||
"LabelSleepTimer": "Sleep-Timer",
|
"LabelSleepTimer": "Schlummerfunktion",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStartTime": "Startzeit",
|
"LabelStartTime": "Startzeit",
|
||||||
"LabelStatsBestDay": "Bester Tag",
|
"LabelStatsBestDay": "Bester Tag",
|
||||||
@@ -215,14 +230,16 @@
|
|||||||
"LabelStatsItemsFinished": "Gehörte Medien",
|
"LabelStatsItemsFinished": "Gehörte Medien",
|
||||||
"LabelStatsMinutes": "Minuten",
|
"LabelStatsMinutes": "Minuten",
|
||||||
"LabelStatsMinutesListening": "Gehörte Minuten",
|
"LabelStatsMinutesListening": "Gehörte Minuten",
|
||||||
"LabelStatsWeekListening": "Gehörte Wochen",
|
"LabelStatsWeekListening": "Wochenhördauer",
|
||||||
|
"LabelStreamingUsingCellular": "Über mobile Daten streamen",
|
||||||
"LabelTag": "Schlagwort",
|
"LabelTag": "Schlagwort",
|
||||||
"LabelTags": "Schlagwörter",
|
"LabelTags": "Schlagwörter",
|
||||||
"LabelTheme": "Theme",
|
"LabelTheme": "Farbschema",
|
||||||
"LabelThemeDark": "Dunkel",
|
"LabelThemeDark": "Dunkel",
|
||||||
"LabelThemeLight": "Hell",
|
"LabelThemeLight": "Hell",
|
||||||
"LabelTimeRemaining": "{0} verbleibend",
|
"LabelTimeRemaining": "{0} verbleibend",
|
||||||
"LabelTitle": "Titel",
|
"LabelTitle": "Titel",
|
||||||
|
"LabelTotalSize": "Gesamtegröße",
|
||||||
"LabelTotalTrack": "Gesamtanzahl Titel",
|
"LabelTotalTrack": "Gesamtanzahl Titel",
|
||||||
"LabelTracks": "Titel",
|
"LabelTracks": "Titel",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
@@ -242,18 +259,20 @@
|
|||||||
"MessageConfirmDeleteLocalEpisode": "Soll die lokale Episode \"{0}\" von deinem Gerät entfernt werden? Die Datei auf dem Server bleibt davon unberührt.",
|
"MessageConfirmDeleteLocalEpisode": "Soll die lokale Episode \"{0}\" von deinem Gerät entfernt werden? Die Datei auf dem Server bleibt davon unberührt.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Sollen lokale Dateien dieses Elements von deinem Gerät entfernt werden? Die Dateien auf dem Server und Ihr Fortschritt bleiben davon unberührt.",
|
"MessageConfirmDeleteLocalFiles": "Sollen lokale Dateien dieses Elements von deinem Gerät entfernt werden? Die Dateien auf dem Server und Ihr Fortschritt bleiben davon unberührt.",
|
||||||
"MessageConfirmDiscardProgress": "Bist du sicher, dass du deinen Fortschritt zurücksetzen willst?",
|
"MessageConfirmDiscardProgress": "Bist du sicher, dass du deinen Fortschritt zurücksetzen willst?",
|
||||||
|
"MessageConfirmDownloadUsingCellular": "Du bist dabei, über mobile Daten herunterzuladen. Dies kann zu Gebühren deines Mobilfunkanbieters führen. Möchtest du fortfahren?",
|
||||||
"MessageConfirmMarkAsFinished": "Bist du sicher, dass du diesen Artikel als beendet markieren willst?",
|
"MessageConfirmMarkAsFinished": "Bist du sicher, dass du diesen Artikel als beendet markieren willst?",
|
||||||
"MessageConfirmRemoveBookmark": "Bist du sicher, dass du das Lesezeichen entfernen willst?",
|
"MessageConfirmRemoveBookmark": "Bist du sicher, dass du das Lesezeichen entfernen willst?",
|
||||||
|
"MessageConfirmStreamingUsingCellular": "Du bist dabei, über mobile Daten zu streamen. Dies kann zu Gebühren deines Mobilfunkanbieters führen. Möchtest du fortfahren?",
|
||||||
"MessageDiscardProgress": "Fortschritt verwerfen",
|
"MessageDiscardProgress": "Fortschritt verwerfen",
|
||||||
"MessageDownloadCompleteProcessing": "Download abgeschlossen. Verarbeite...",
|
"MessageDownloadCompleteProcessing": "Download abgeschlossen. Wird verarbeitet …",
|
||||||
"MessageDownloading": "Herunterladen...",
|
"MessageDownloading": "Wird heruntergeladen …",
|
||||||
"MessageDownloadingEpisode": "Episode herunterladen",
|
"MessageDownloadingEpisode": "Episode herunterladen",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen",
|
"MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen",
|
||||||
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
||||||
"MessageFetching": "Abrufen...",
|
"MessageFetching": "Wird abgerufen …",
|
||||||
"MessageFollowTheProjectOnGithub": "Folge dem Projekt auf Github",
|
"MessageFollowTheProjectOnGithub": "Folge dem Projekt auf GitHub",
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Element-Download abgeschlossen, aber Bibliothekselement kann nicht erstellt werden",
|
"MessageItemDownloadCompleteFailedToCreate": "Element-Download abgeschlossen, aber Bibliothekselement kann nicht erstellt werden",
|
||||||
"MessageLoading": "Laden...",
|
"MessageLoading": "Wird geladen …",
|
||||||
"MessageLoadingServerData": "Lade Server Daten...",
|
"MessageLoadingServerData": "Lade Server Daten...",
|
||||||
"MessageMarkAsFinished": "Als beendet markieren",
|
"MessageMarkAsFinished": "Als beendet markieren",
|
||||||
"MessageMediaLinkedToADifferentServer": "Die Medien sind mit einem Audiobookshelf-Server mit einer anderen Adresse ({0}) verknüpft. Der Fortschritt wird synchronisiert, wenn eine Verbindung zu dieser Serveradresse besteht.",
|
"MessageMediaLinkedToADifferentServer": "Die Medien sind mit einem Audiobookshelf-Server mit einer anderen Adresse ({0}) verknüpft. Der Fortschritt wird synchronisiert, wenn eine Verbindung zu dieser Serveradresse besteht.",
|
||||||
@@ -269,24 +288,30 @@
|
|||||||
"MessageNoMediaFolders": "Keine Medien Ordner",
|
"MessageNoMediaFolders": "Keine Medien Ordner",
|
||||||
"MessageNoNetworkConnection": "Keine Netzwerkverbindung",
|
"MessageNoNetworkConnection": "Keine Netzwerkverbindung",
|
||||||
"MessageNoPodcastsFound": "Keine Podcasts gefunden",
|
"MessageNoPodcastsFound": "Keine Podcasts gefunden",
|
||||||
|
"MessageNoSeries": "Keine Serie",
|
||||||
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",
|
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",
|
||||||
"MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden",
|
"MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden",
|
||||||
"MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und mitwirken",
|
"MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und mitwirken",
|
||||||
|
"MessageSeriesAlreadyDownloaded": "Du hast bereits alle Bücher in dieser Serie heruntergeladen.",
|
||||||
|
"MessageSeriesDownloadConfirm": "Lade {0} Bücher mit {1} Daten, zusammen {2}, in den Ordner {3} herunter?",
|
||||||
|
"MessageSeriesDownloadConfirmIos": "Lade {0} Bücher mit {1} Daten, zusammen {2}, herunter?",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket verbunden über getaktetes Mobilfunknetz",
|
"MessageSocketConnectedOverMeteredCellular": "Socket verbunden über getaktetes Mobilfunknetz",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket verbunden über getaktetes WLAN",
|
"MessageSocketConnectedOverMeteredWifi": "Socket verbunden über getaktetes WLAN",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket verbunden über ungetaktetes Mobilfunknetz",
|
"MessageSocketConnectedOverUnmeteredCellular": "Socket verbunden über ungetaktetes Mobilfunknetz",
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket verbunden über ungetaktetes Mobilfunknetz",
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket verbunden über ungetaktetes Mobilfunknetz",
|
||||||
"MessageSocketNotConnected": "Socket nicht verbunden",
|
"MessageSocketNotConnected": "Socket nicht verbunden",
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Warnung: Die meisten Podcast-Apps verlangen, dass die URL des RSS-Feeds HTTPS verwendet.",
|
"NoteRSSFeedPodcastAppsHttps": "Warnung: Die meisten Podcast-Apps verlangen, dass die URL des RSS-Feeds HTTPS verwendet",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Warnung: 1 oder mehrere deiner Episoden haben kein Veröffentlichungsdatum. Einige Podcast-Apps verlangen dies.",
|
"NoteRSSFeedPodcastAppsPubDate": "Warnung: 1 oder mehrere deiner Episoden haben kein Veröffentlichungsdatum. Einige Podcast-Apps verlangen dies.",
|
||||||
"ToastBookmarkCreateFailed": "Lesezeichen konnte nicht erstellt werden",
|
"ToastBookmarkCreateFailed": "Lesezeichen konnte nicht erstellt werden",
|
||||||
"ToastBookmarkRemoveFailed": "Lesezeichen konnte nicht gelöscht werden",
|
"ToastBookmarkRemoveFailed": "Lesezeichen konnte nicht entfernt werden",
|
||||||
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Fehler bei der Markierung des Mediums als \"Beendet\"",
|
"ToastDownloadNotAllowedOnCellular": "Das Herunterladen über mobile Daten ist nicht erlaubt",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Fehler bei der Markierung des Mediums als \"Nicht Beendet\"",
|
"ToastItemMarkedAsFinishedFailed": "Fehler bei der Markierung des Artikels als \"Beendet\"",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "Fehler bei der Markierung des Artikels als \"Nicht Beendet\"",
|
||||||
"ToastPlaylistCreateFailed": "Erstellen der Wiedergabeliste fehlgeschlagen",
|
"ToastPlaylistCreateFailed": "Erstellen der Wiedergabeliste fehlgeschlagen",
|
||||||
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
||||||
"ToastPodcastCreateSuccess": "Podcast erstellt",
|
"ToastPodcastCreateSuccess": "Podcast erstellt",
|
||||||
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen"
|
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "Das Streamen über mobile Daten ist nicht erlaubt"
|
||||||
}
|
}
|
||||||
+33
-8
@@ -19,6 +19,7 @@
|
|||||||
"ButtonDeleteLocalItem": "Delete local item",
|
"ButtonDeleteLocalItem": "Delete local item",
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
||||||
"ButtonDisconnect": "Disconnect",
|
"ButtonDisconnect": "Disconnect",
|
||||||
|
"ButtonGoToWebClient": "Go to Web Client",
|
||||||
"ButtonHistory": "History",
|
"ButtonHistory": "History",
|
||||||
"ButtonHome": "Home",
|
"ButtonHome": "Home",
|
||||||
"ButtonIssues": "Issues",
|
"ButtonIssues": "Issues",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"ButtonOverride": "Override",
|
"ButtonOverride": "Override",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pause",
|
||||||
"ButtonPlay": "Play",
|
"ButtonPlay": "Play",
|
||||||
|
"ButtonPlayEpisode": "Play Episode",
|
||||||
"ButtonPlaying": "Playing",
|
"ButtonPlaying": "Playing",
|
||||||
"ButtonPlaylists": "Playlists",
|
"ButtonPlaylists": "Playlists",
|
||||||
"ButtonRead": "Read",
|
"ButtonRead": "Read",
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
"HeaderCollection": "Collection",
|
"HeaderCollection": "Collection",
|
||||||
"HeaderCollectionItems": "Collection Items",
|
"HeaderCollectionItems": "Collection Items",
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
"HeaderConnectionStatus": "Connection Status",
|
||||||
|
"HeaderDataSettings": "Data Settings",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Downloads",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "Ebook Files",
|
||||||
@@ -81,10 +84,13 @@
|
|||||||
"HeaderTableOfContents": "Table of Contents",
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "User Interface Settings",
|
||||||
"HeaderYourStats": "Your Stats",
|
"HeaderYourStats": "Your Stats",
|
||||||
|
"LabelAddToPlaylist": "Add to Playlist",
|
||||||
"LabelAdded": "Added",
|
"LabelAdded": "Added",
|
||||||
"LabelAddedAt": "Added At",
|
"LabelAddedAt": "Added At",
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
|
||||||
"LabelAll": "All",
|
"LabelAll": "All",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Allow position seeking on media notification controls",
|
||||||
|
"LabelAlways": "Always",
|
||||||
|
"LabelAskConfirmation": "Ask for confirmation",
|
||||||
"LabelAuthor": "Author",
|
"LabelAuthor": "Author",
|
||||||
"LabelAuthorFirstLast": "Author (First Last)",
|
"LabelAuthorFirstLast": "Author (First Last)",
|
||||||
"LabelAuthorLastFirst": "Author (Last, First)",
|
"LabelAuthorLastFirst": "Author (Last, First)",
|
||||||
@@ -96,8 +102,8 @@
|
|||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
||||||
"LabelBooks": "Books",
|
"LabelBooks": "Books",
|
||||||
"LabelChapters": "Chapters",
|
|
||||||
"LabelChapterTrack": "Chapter Track",
|
"LabelChapterTrack": "Chapter Track",
|
||||||
|
"LabelChapters": "Chapters",
|
||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
"LabelComplete": "Complete",
|
"LabelComplete": "Complete",
|
||||||
@@ -117,6 +123,7 @@
|
|||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
||||||
"LabelDiscover": "Discover",
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadUsingCellular": "Download using Cellular",
|
||||||
"LabelDownloaded": "Downloaded",
|
"LabelDownloaded": "Downloaded",
|
||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -135,6 +142,7 @@
|
|||||||
"LabelFilename": "Filename",
|
"LabelFilename": "Filename",
|
||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
|
"LabelFontBoldness": "Font Boldness",
|
||||||
"LabelFontScale": "Font scale",
|
"LabelFontScale": "Font scale",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
@@ -144,8 +152,8 @@
|
|||||||
"LabelHeavy": "Heavy",
|
"LabelHeavy": "Heavy",
|
||||||
"LabelHigh": "High",
|
"LabelHigh": "High",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelIncomplete": "Incomplete",
|
|
||||||
"LabelInProgress": "In Progress",
|
"LabelInProgress": "In Progress",
|
||||||
|
"LabelIncomplete": "Incomplete",
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
"LabelInternalAppStorage": "Internal App Storage",
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
"LabelJumpBackwardsTime": "Jump backwards time",
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
"LabelJumpForwardsTime": "Jump forwards time",
|
||||||
@@ -168,6 +176,11 @@
|
|||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
"LabelNarrator": "Narrator",
|
"LabelNarrator": "Narrator",
|
||||||
"LabelNarrators": "Narrators",
|
"LabelNarrators": "Narrators",
|
||||||
|
"LabelNavigateWithVolume": "Navigate with volume keys",
|
||||||
|
"LabelNavigateWithVolumeDisabled": "Off",
|
||||||
|
"LabelNavigateWithVolumeEnabled": "On",
|
||||||
|
"LabelNavigateWithVolumeMirrored": "Mirrored",
|
||||||
|
"LabelNever": "Never",
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNewestAuthors": "Newest Authors",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
"LabelNewestEpisodes": "Newest Episodes",
|
||||||
"LabelNo": "No",
|
"LabelNo": "No",
|
||||||
@@ -186,14 +199,16 @@
|
|||||||
"LabelProgress": "Progress",
|
"LabelProgress": "Progress",
|
||||||
"LabelPubDate": "Pub Date",
|
"LabelPubDate": "Pub Date",
|
||||||
"LabelPublishYear": "Publish Year",
|
"LabelPublishYear": "Publish Year",
|
||||||
"LabelRead": "Read",
|
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
|
"LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
|
||||||
"LabelRSSFeedCustomOwnerName": "Custom owner Name",
|
"LabelRSSFeedCustomOwnerName": "Custom owner Name",
|
||||||
"LabelRSSFeedPreventIndexing": "Prevent Indexing",
|
"LabelRSSFeedPreventIndexing": "Prevent Indexing",
|
||||||
"LabelRSSFeedSlug": "RSS Feed Slug",
|
"LabelRSSFeedSlug": "RSS Feed Slug",
|
||||||
|
"LabelRandomly": "Randomly",
|
||||||
|
"LabelRead": "Read",
|
||||||
|
"LabelReadAgain": "Read Again",
|
||||||
|
"LabelRecentSeries": "Recent Series",
|
||||||
|
"LabelRecentlyAdded": "Recently Added",
|
||||||
|
"LabelRemoveFromPlaylist": "Remove from Playlist",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
"LabelSelectADevice": "Select a device",
|
"LabelSelectADevice": "Select a device",
|
||||||
@@ -216,6 +231,7 @@
|
|||||||
"LabelStatsMinutes": "minutes",
|
"LabelStatsMinutes": "minutes",
|
||||||
"LabelStatsMinutesListening": "Minutes Listening",
|
"LabelStatsMinutesListening": "Minutes Listening",
|
||||||
"LabelStatsWeekListening": "Week Listening",
|
"LabelStatsWeekListening": "Week Listening",
|
||||||
|
"LabelStreamingUsingCellular": "Streaming using Cellular",
|
||||||
"LabelTag": "Tag",
|
"LabelTag": "Tag",
|
||||||
"LabelTags": "Tags",
|
"LabelTags": "Tags",
|
||||||
"LabelTheme": "Theme",
|
"LabelTheme": "Theme",
|
||||||
@@ -223,6 +239,7 @@
|
|||||||
"LabelThemeLight": "Light",
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeRemaining": "{0} remaining",
|
"LabelTimeRemaining": "{0} remaining",
|
||||||
"LabelTitle": "Title",
|
"LabelTitle": "Title",
|
||||||
|
"LabelTotalSize": "Total Size",
|
||||||
"LabelTotalTrack": "Total Track",
|
"LabelTotalTrack": "Total Track",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
@@ -242,8 +259,10 @@
|
|||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
||||||
|
"MessageConfirmDownloadUsingCellular": "You are about to download using cellular data. This may include carrier data charges. Do you wish to continue?",
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
||||||
|
"MessageConfirmStreamingUsingCellular": "You are about to stream using cellular data. This may include carrier data charges. Do you wish to continue?",
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
"MessageDiscardProgress": "Discard Progress",
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
||||||
"MessageDownloading": "Downloading...",
|
"MessageDownloading": "Downloading...",
|
||||||
@@ -269,9 +288,13 @@
|
|||||||
"MessageNoMediaFolders": "No Media Folders",
|
"MessageNoMediaFolders": "No Media Folders",
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
"MessageNoNetworkConnection": "No network connection",
|
||||||
"MessageNoPodcastsFound": "No podcasts found",
|
"MessageNoPodcastsFound": "No podcasts found",
|
||||||
|
"MessageNoSeries": "No series",
|
||||||
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
"MessageNoUserPlaylists": "You have no playlists",
|
||||||
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
||||||
|
"MessageSeriesAlreadyDownloaded": "You have already downloaded all books in this series.",
|
||||||
|
"MessageSeriesDownloadConfirm": "Download missing {0} book(s) with {1} file(s), totaling {2} to folder {3}?",
|
||||||
|
"MessageSeriesDownloadConfirmIos": "Download missing {0} book(s) with {1} file(s), totaling {2}?",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
||||||
@@ -282,11 +305,13 @@
|
|||||||
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
||||||
"ToastBookmarkRemoveFailed": "Failed to remove bookmark",
|
"ToastBookmarkRemoveFailed": "Failed to remove bookmark",
|
||||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||||
|
"ToastDownloadNotAllowedOnCellular": "Downloading is not allowed on cellular data",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
|
"ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
|
"ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
|
||||||
"ToastPlaylistCreateFailed": "Failed to create playlist",
|
"ToastPlaylistCreateFailed": "Failed to create playlist",
|
||||||
"ToastPodcastCreateFailed": "Failed to create podcast",
|
"ToastPodcastCreateFailed": "Failed to create podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast created successfully",
|
"ToastPodcastCreateSuccess": "Podcast created successfully",
|
||||||
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed closed"
|
"ToastRSSFeedCloseSuccess": "RSS feed closed",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "Streaming is not allowed on cellular data"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+204
-179
@@ -1,210 +1,225 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Agregar",
|
"ButtonAdd": "Agregar",
|
||||||
"ButtonAddNewServer": "Add New Server",
|
"ButtonAddNewServer": "Agregar nuevo servidor",
|
||||||
"ButtonAuthors": "Autores",
|
"ButtonAuthors": "Autores",
|
||||||
"ButtonBack": "Back",
|
"ButtonBack": "Atrás",
|
||||||
"ButtonCancel": "Cancelar",
|
"ButtonCancel": "Cancelar",
|
||||||
"ButtonCancelTimer": "Cancel Timer",
|
"ButtonCancelTimer": "Cancelar el temporizador",
|
||||||
"ButtonClearFilter": "Quitar Filtros",
|
"ButtonClearFilter": "Quitar filtros",
|
||||||
"ButtonCloseFeed": "Cerrar Fuente",
|
"ButtonCloseFeed": "Cerrar fuente",
|
||||||
"ButtonCollections": "Colecciones",
|
"ButtonCollections": "Colecciones",
|
||||||
"ButtonConnect": "Connect",
|
"ButtonConnect": "Conectar",
|
||||||
"ButtonConnectToServer": "Connect to Server",
|
"ButtonConnectToServer": "Conectar al servidor",
|
||||||
"ButtonCreate": "Crear",
|
"ButtonCreate": "Crear",
|
||||||
"ButtonCreateBookmark": "Create Bookmark",
|
"ButtonCreateBookmark": "Crear marcador",
|
||||||
"ButtonCreateNewPlaylist": "Create New Playlist",
|
"ButtonCreateNewPlaylist": "Crear nueva lista de reproducción",
|
||||||
"ButtonDelete": "Eliminar",
|
"ButtonDelete": "Eliminar",
|
||||||
"ButtonDeleteLocalEpisode": "Delete local episode",
|
"ButtonDeleteLocalEpisode": "Borrar episodio local",
|
||||||
"ButtonDeleteLocalFile": "Delete local file",
|
"ButtonDeleteLocalFile": "Borrar archivo local",
|
||||||
"ButtonDeleteLocalItem": "Delete local item",
|
"ButtonDeleteLocalItem": "Borrar elemento local",
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
"ButtonDisableAutoTimer": "Desactivar temporizador automático",
|
||||||
"ButtonDisconnect": "Disconnect",
|
"ButtonDisconnect": "Desconectar",
|
||||||
"ButtonHistory": "History",
|
"ButtonGoToWebClient": "Ir al cliente web",
|
||||||
|
"ButtonHistory": "Historial",
|
||||||
"ButtonHome": "Inicio",
|
"ButtonHome": "Inicio",
|
||||||
"ButtonIssues": "Problemas",
|
"ButtonIssues": "Problemas",
|
||||||
"ButtonLatest": "Últimos",
|
"ButtonLatest": "Últimos",
|
||||||
"ButtonLibrary": "Biblioteca",
|
"ButtonLibrary": "Biblioteca",
|
||||||
"ButtonLocalMedia": "Local Media",
|
"ButtonLocalMedia": "Medios locales",
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
"ButtonManageLocalFiles": "Gestionar archivos locales",
|
||||||
"ButtonNewFolder": "New Folder",
|
"ButtonNewFolder": "Nueva carpeta",
|
||||||
"ButtonNextEpisode": "Next Episode",
|
"ButtonNextEpisode": "Próximo episodio",
|
||||||
"ButtonOpenFeed": "Abrir Fuente",
|
"ButtonOpenFeed": "Abrir fuente",
|
||||||
"ButtonOverride": "Override",
|
"ButtonOverride": "Sustituir",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pausar",
|
||||||
"ButtonPlay": "Reproducir",
|
"ButtonPlay": "Reproducir",
|
||||||
|
"ButtonPlayEpisode": "Reproducir episodio",
|
||||||
"ButtonPlaying": "Reproduciendo",
|
"ButtonPlaying": "Reproduciendo",
|
||||||
"ButtonPlaylists": "Listas de Reproducción",
|
"ButtonPlaylists": "Listas de reproducción",
|
||||||
"ButtonRead": "Leer",
|
"ButtonRead": "Leer",
|
||||||
"ButtonRemove": "Remover",
|
"ButtonRemove": "Eliminar",
|
||||||
"ButtonRemoveFromServer": "Remove from Server",
|
"ButtonRemoveFromServer": "Eliminar del Servidor",
|
||||||
"ButtonSave": "Guardar",
|
"ButtonSave": "Guardar",
|
||||||
"ButtonSaveOrder": "Save Order",
|
"ButtonSaveOrder": "Guardar pedido",
|
||||||
"ButtonSearch": "Buscar",
|
"ButtonSearch": "Buscar",
|
||||||
"ButtonSendEbookToDevice": "Send Ebook to Device",
|
"ButtonSendEbookToDevice": "Enviar libro al dispositivo",
|
||||||
"ButtonSeries": "Series",
|
"ButtonSeries": "Series",
|
||||||
"ButtonSetTimer": "Set Timer",
|
"ButtonSetTimer": "Ajustar el temporizador",
|
||||||
"ButtonStream": "Stream",
|
"ButtonStream": "En directo",
|
||||||
"ButtonSubmit": "Enviar",
|
"ButtonSubmit": "Enviar",
|
||||||
"ButtonSwitchServerUser": "Switch Server/User",
|
"ButtonSwitchServerUser": "Cambiar servidor/usuario",
|
||||||
"ButtonUserStats": "User Stats",
|
"ButtonUserStats": "Estadísticas de usuario",
|
||||||
"ButtonYes": "Aceptar",
|
"ButtonYes": "Sí",
|
||||||
"HeaderAccount": "Cuenta",
|
"HeaderAccount": "Cuenta",
|
||||||
"HeaderAdvanced": "Avanzado",
|
"HeaderAdvanced": "Avanzado",
|
||||||
"HeaderAudioTracks": "Pistas de Audio",
|
"HeaderAudioTracks": "Pistas de audio",
|
||||||
"HeaderChapters": "Capítulos",
|
"HeaderChapters": "Capítulos",
|
||||||
"HeaderCollection": "Colección",
|
"HeaderCollection": "Colección",
|
||||||
"HeaderCollectionItems": "Elementos en la Colección",
|
"HeaderCollectionItems": "Elementos en la colección",
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
"HeaderConnectionStatus": "Estado de la conexión",
|
||||||
|
"HeaderDataSettings": "Ajustes de los datos",
|
||||||
"HeaderDetails": "Detalles",
|
"HeaderDetails": "Detalles",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Descargas",
|
||||||
"HeaderEbookFiles": "Archivos de Ebook",
|
"HeaderEbookFiles": "Archivos de libros digitales",
|
||||||
"HeaderEpisodes": "Episodios",
|
"HeaderEpisodes": "Episodios",
|
||||||
"HeaderEreaderSettings": "Opciones de Ereader",
|
"HeaderEreaderSettings": "Ajustes del lector",
|
||||||
"HeaderLatestEpisodes": "Últimos Episodios",
|
"HeaderLatestEpisodes": "Últimos episodios",
|
||||||
"HeaderLibraries": "Bibliotecas",
|
"HeaderLibraries": "Bibliotecas",
|
||||||
"HeaderLocalFolders": "Local Folders",
|
"HeaderLocalFolders": "Carpetas locales",
|
||||||
"HeaderLocalLibraryItems": "Local Library Items",
|
"HeaderLocalLibraryItems": "Elementos de la biblioteca local",
|
||||||
"HeaderNewPlaylist": "New Playlist",
|
"HeaderNewPlaylist": "Nueva lista de reproducción",
|
||||||
"HeaderOpenRSSFeed": "Abrir fuente RSS",
|
"HeaderOpenRSSFeed": "Abrir fuente RSS",
|
||||||
"HeaderPlaybackSettings": "Playback Settings",
|
"HeaderPlaybackSettings": "Ajustes de reproducción",
|
||||||
"HeaderPlaylist": "Lista de Reproducción",
|
"HeaderPlaylist": "Lista de reproducción",
|
||||||
"HeaderPlaylistItems": "Elementos de Lista de Reproducción",
|
"HeaderPlaylistItems": "Elementos de lista de reproducción",
|
||||||
"HeaderRSSFeed": "RSS Feed",
|
"HeaderRSSFeed": "Fuente RSS",
|
||||||
"HeaderRSSFeedGeneral": "Detalles RSS",
|
"HeaderRSSFeedGeneral": "Detalles RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Fuente RSS esta abierta",
|
"HeaderRSSFeedIsOpen": "Fuente RSS está abierta",
|
||||||
"HeaderSelectDownloadLocation": "Select Download Location",
|
"HeaderSelectDownloadLocation": "Seleccionar ubicación de descarga",
|
||||||
"HeaderSettings": "Configuraciones",
|
"HeaderSettings": "Configuraciones",
|
||||||
"HeaderSleepTimer": "Temporizador para Dormir",
|
"HeaderSleepTimer": "Temporizador de apagado",
|
||||||
"HeaderSleepTimerSettings": "Sleep Timer Settings",
|
"HeaderSleepTimerSettings": "Ajustes del temporizador para dormir",
|
||||||
"HeaderStatsMinutesListeningChart": "Minutos Escuchando (Últimos 7 días)",
|
"HeaderStatsMinutesListeningChart": "Minutos escuchando (Últimos 7 días)",
|
||||||
"HeaderStatsRecentSessions": "Sesiones Recientes",
|
"HeaderStatsRecentSessions": "Sesiones recientes",
|
||||||
"HeaderTableOfContents": "Tabla de Contenidos",
|
"HeaderTableOfContents": "Tabla de contenidos",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "Ajustes de la interfaz de usuario",
|
||||||
"HeaderYourStats": "Tus Estadísticas",
|
"HeaderYourStats": "Tus estadísticas",
|
||||||
|
"LabelAddToPlaylist": "Añadido a la lista de reproducción",
|
||||||
"LabelAdded": "Añadido",
|
"LabelAdded": "Añadido",
|
||||||
"LabelAddedAt": "Añadido",
|
"LabelAddedAt": "Añadido",
|
||||||
"LabelAddToPlaylist": "Añadido a la Lista de Reproducción",
|
|
||||||
"LabelAll": "Todos",
|
"LabelAll": "Todos",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Permitir la búsqueda de posición en los controles de notificación de medios",
|
||||||
|
"LabelAlways": "Siempre",
|
||||||
|
"LabelAskConfirmation": "Pedir confirmación",
|
||||||
"LabelAuthor": "Autor",
|
"LabelAuthor": "Autor",
|
||||||
"LabelAuthorFirstLast": "Autor (Nombre Apellido)",
|
"LabelAuthorFirstLast": "Autor (Nombre Apellido)",
|
||||||
"LabelAuthorLastFirst": "Autor (Apellido, Nombre)",
|
"LabelAuthorLastFirst": "Autor (Apellido, Nombre)",
|
||||||
"LabelAuthors": "Autores",
|
"LabelAuthors": "Autores",
|
||||||
"LabelAutoDownloadEpisodes": "Descargar Episodios Automáticamente",
|
"LabelAutoDownloadEpisodes": "Descargar episodios automáticamente",
|
||||||
"LabelAutoRewindTime": "Auto rewind time",
|
"LabelAutoRewindTime": "Tiempo de rebobinado automático",
|
||||||
"LabelAutoSleepTimer": "Auto sleep timer",
|
"LabelAutoSleepTimer": "Temporizador de apagado automático",
|
||||||
"LabelAutoSleepTimerAutoRewind": "Auto sleep timer auto rewind",
|
"LabelAutoSleepTimerAutoRewind": "Temporizador de apagado automático con rebobinado automático",
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
"LabelAutoSleepTimerAutoRewindHelp": "Cuando el temporizador de auto apagado finaliza, reproducir el elemento nuevamente rebobinará automáticamente tu posición.",
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
"LabelAutoSleepTimerHelp": "Cuando se reproduce contenido multimedia entre las horas de inicio y finalización especificadas, se activará automáticamente un temporizador de apagado.",
|
||||||
"LabelBooks": "Libros",
|
"LabelBooks": "Libros",
|
||||||
|
"LabelChapterTrack": "Seguimiento de Capítulo",
|
||||||
"LabelChapters": "Capítulos",
|
"LabelChapters": "Capítulos",
|
||||||
"LabelChapterTrack": "Chapter Track",
|
"LabelClosePlayer": "Cerrar reproductor",
|
||||||
"LabelClosePlayer": "Cerrar Reproductor",
|
"LabelCollapseSeries": "Colapsar serie",
|
||||||
"LabelCollapseSeries": "Colapsar Serie",
|
|
||||||
"LabelComplete": "Completo",
|
"LabelComplete": "Completo",
|
||||||
"LabelContinueBooks": "Continue Books",
|
"LabelContinueBooks": "Continuar libros",
|
||||||
"LabelContinueEpisodes": "Continue Episodes",
|
"LabelContinueEpisodes": "Continuar episodios",
|
||||||
"LabelContinueListening": "Continue Listening",
|
"LabelContinueListening": "Seguir escuchando",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continuar leyendo",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Continuar series",
|
||||||
"LabelCustomTime": "Custom time",
|
"LabelCustomTime": "Tiempo personalizado",
|
||||||
"LabelDescription": "Descripción",
|
"LabelDescription": "Descripción",
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
"LabelDisableAudioFadeOut": "Desactivar el fundido de audio",
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
"LabelDisableAudioFadeOutHelp": "El volumen de audio comenzará a disminuir cuando quede menos de 1 minuto en el temporizador de apagado. Habilite este ajuste para que no se desvanezca.",
|
||||||
"LabelDisableAutoRewind": "Disable auto rewind",
|
"LabelDisableAutoRewind": "Desactivar rebobinado automático",
|
||||||
"LabelDisableShakeToReset": "Disable shake to reset",
|
"LabelDisableShakeToReset": "Desactivar agitar para reiniciar",
|
||||||
"LabelDisableShakeToResetHelp": "Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.",
|
"LabelDisableShakeToResetHelp": "Si agitas el dispositivo mientras el temporizador está en marcha o en los 2 minutos siguientes a la expiración del temporizador, éste se reiniciará. Habilita esta configuración para desactivar el restablecimiento al agitar.",
|
||||||
"LabelDisableVibrateOnReset": "Disable vibrate on reset",
|
"LabelDisableVibrateOnReset": "Desactivar vibración al reiniciar",
|
||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
"LabelDisableVibrateOnResetHelp": "Cuando el temporizador de apagado se reinicia, el dispositivo vibra. Activa esta opción para que no vibre cuando se reinicie el temporizador.",
|
||||||
"LabelDiscover": "Discover",
|
"LabelDiscover": "Descubrir",
|
||||||
"LabelDownload": "Descargar",
|
"LabelDownload": "Descargar",
|
||||||
"LabelDownloaded": "Downloaded",
|
"LabelDownloadUsingCellular": "Descargar usando el móvil",
|
||||||
|
"LabelDownloaded": "Descargado",
|
||||||
"LabelDuration": "Duración",
|
"LabelDuration": "Duración",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Libro electrónico",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "Libros electrónicos",
|
||||||
"LabelEnable": "Habilitar",
|
"LabelEnable": "Habilitar",
|
||||||
"LabelEnableMp3IndexSeeking": "Enable mp3 index seeking",
|
"LabelEnableMp3IndexSeeking": "Activar la búsqueda de índices mp3",
|
||||||
"LabelEnableMp3IndexSeekingHelp": "This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.",
|
"LabelEnableMp3IndexSeekingHelp": "Esta configuración solo debe habilitarse si tienes archivos mp3 que no se están buscando correctamente. La búsqueda inexacta probablemente se deba a archivos MP3 de tasa de bits variable (VBR). Esta configuración forzará la búsqueda de índice, en la que se construye un mapeo de tiempo a bytes mientras se lee el archivo. En algunos casos, con archivos MP3 grandes, puede haber un retraso al buscar hacia el final del archivo.",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
"LabelEndOfChapter": "End of Chapter",
|
"LabelEndOfChapter": "Fin del capítulo",
|
||||||
"LabelEndTime": "End time",
|
"LabelEndTime": "Hora de finalización",
|
||||||
"LabelEpisode": "Episodio",
|
"LabelEpisode": "Episodio",
|
||||||
"LabelFeedURL": "Fuente de URL",
|
"LabelFeedURL": "Fuente de URL",
|
||||||
"LabelFile": "Archivo",
|
"LabelFile": "Archivo",
|
||||||
"LabelFileBirthtime": "Archivo Creado en",
|
"LabelFileBirthtime": "Archivo creado en",
|
||||||
"LabelFileModified": "Archivo modificado",
|
"LabelFileModified": "Archivo modificado",
|
||||||
"LabelFilename": "Nombre del Archivo",
|
"LabelFilename": "Nombre del archivo",
|
||||||
"LabelFinished": "Terminado",
|
"LabelFinished": "Terminado",
|
||||||
"LabelFolder": "Carpeta",
|
"LabelFolder": "Carpeta",
|
||||||
"LabelFontScale": "Tamaño de Fuente",
|
"LabelFontBoldness": "Nivel de negrilla en fuente",
|
||||||
"LabelGenre": "Genero",
|
"LabelFontScale": "Tamaño de fuente",
|
||||||
|
"LabelGenre": "Género",
|
||||||
"LabelGenres": "Géneros",
|
"LabelGenres": "Géneros",
|
||||||
"LabelHapticFeedback": "Haptic feedback",
|
"LabelHapticFeedback": "Respuesta táctil",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Tiene un libro",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Tiene un libro complementario",
|
||||||
"LabelHeavy": "Heavy",
|
"LabelHeavy": "Pesado",
|
||||||
"LabelHigh": "High",
|
"LabelHigh": "Alto",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
|
"LabelInProgress": "En proceso",
|
||||||
"LabelIncomplete": "Incompleto",
|
"LabelIncomplete": "Incompleto",
|
||||||
"LabelInProgress": "En Proceso",
|
"LabelInternalAppStorage": "Almacenamiento interno de aplicaciones",
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
"LabelJumpBackwardsTime": "Saltar atrás en el tiempo",
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
"LabelJumpForwardsTime": "Salto adelante en el tiempo",
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
"LabelLanguage": "Idioma",
|
||||||
"LabelLanguage": "Lenguaje",
|
"LabelLayout": "Diseño",
|
||||||
"LabelLayout": "Layout",
|
"LabelLayoutAuto": "Automático",
|
||||||
"LabelLayoutAuto": "Auto",
|
"LabelLayoutSinglePage": "Página única",
|
||||||
"LabelLayoutSinglePage": "Single page",
|
"LabelLight": "Claro",
|
||||||
"LabelLight": "Light",
|
|
||||||
"LabelLineSpacing": "Interlineado",
|
"LabelLineSpacing": "Interlineado",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Volver a escuchar",
|
||||||
"LabelLocalBooks": "Local Books",
|
"LabelLocalBooks": "Libros Locales",
|
||||||
"LabelLocalPodcasts": "Local Podcasts",
|
"LabelLocalPodcasts": "Podcasts Locales",
|
||||||
"LabelLockOrientation": "Lock orientation",
|
"LabelLockOrientation": "Bloquear orientación",
|
||||||
"LabelLockPlayer": "Lock Player",
|
"LabelLockPlayer": "Bloquear el reproductor",
|
||||||
"LabelLow": "Low",
|
"LabelLow": "Bajo",
|
||||||
"LabelMediaType": "Tipo de Multimedia",
|
"LabelMediaType": "Tipo de multimedia",
|
||||||
"LabelMedium": "Medium",
|
"LabelMedium": "Medio",
|
||||||
"LabelMore": "Más",
|
"LabelMore": "Más",
|
||||||
"LabelMoreInfo": "Más Información",
|
"LabelMoreInfo": "Más información",
|
||||||
"LabelName": "Nombre",
|
"LabelName": "Nombre",
|
||||||
"LabelNarrator": "Narrador",
|
"LabelNarrator": "Narrador",
|
||||||
"LabelNarrators": "Narradores",
|
"LabelNarrators": "Narradores",
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNavigateWithVolume": "Navegar con las teclas de volumen",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
"LabelNavigateWithVolumeDisabled": "Apagar",
|
||||||
|
"LabelNavigateWithVolumeEnabled": "Encender",
|
||||||
|
"LabelNavigateWithVolumeMirrored": "Espejo",
|
||||||
|
"LabelNever": "Nunca",
|
||||||
|
"LabelNewestAuthors": "Autores más recientes",
|
||||||
|
"LabelNewestEpisodes": "Episodios más recientes",
|
||||||
"LabelNo": "No",
|
"LabelNo": "No",
|
||||||
"LabelNotFinished": "No Terminado",
|
"LabelNotFinished": "No terminado",
|
||||||
"LabelNotStarted": "Sin Iniciar",
|
"LabelNotStarted": "Sin iniciar",
|
||||||
"LabelOff": "Off",
|
"LabelOff": "Apagado",
|
||||||
"LabelPassword": "Contraseña",
|
"LabelPassword": "Contraseña",
|
||||||
"LabelPath": "Ruta de Carpeta",
|
"LabelPath": "Ruta de carpeta",
|
||||||
"LabelPlaybackDirect": "Direct",
|
"LabelPlaybackDirect": "Directo",
|
||||||
"LabelPlaybackLocal": "Local",
|
"LabelPlaybackLocal": "Local",
|
||||||
"LabelPlaybackSpeed": "Playback Speed",
|
"LabelPlaybackSpeed": "Velocidad de reproducción",
|
||||||
"LabelPlaybackTranscode": "Transcode",
|
"LabelPlaybackTranscode": "Transcodificar",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPreventIndexing": "Evite que su fuente sea indexada por los directorios de podcasts de iTunes y Google",
|
"LabelPreventIndexing": "Evite que su fuente sea indexada por los directorios de podcasts de iTunes y Google",
|
||||||
"LabelProgress": "Progreso",
|
"LabelProgress": "Progreso",
|
||||||
"LabelPubDate": "Fecha de Publicación",
|
"LabelPubDate": "Fecha de publicación",
|
||||||
"LabelPublishYear": "Año de Publicación",
|
"LabelPublishYear": "Año de publicación",
|
||||||
"LabelRead": "Leído",
|
"LabelRSSFeedCustomOwnerEmail": "Correo electrónico de dueño personalizado",
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Email de dueño personalizado",
|
|
||||||
"LabelRSSFeedCustomOwnerName": "Nombre de dueño personalizado",
|
"LabelRSSFeedCustomOwnerName": "Nombre de dueño personalizado",
|
||||||
"LabelRSSFeedPreventIndexing": "Prevenir Indexado",
|
"LabelRSSFeedPreventIndexing": "Prevenir indexado",
|
||||||
"LabelRSSFeedSlug": "Fuente RSS Slug",
|
"LabelRSSFeedSlug": "Fuente RSS Slug",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
"LabelRandomly": "Aleatorio",
|
||||||
|
"LabelRead": "Leído",
|
||||||
|
"LabelReadAgain": "Volver a leer",
|
||||||
|
"LabelRecentSeries": "Series Recientes",
|
||||||
|
"LabelRecentlyAdded": "Añadido Recientemente",
|
||||||
|
"LabelRemoveFromPlaylist": "Eliminar de la Lista de Reproducción",
|
||||||
|
"LabelScaleElapsedTimeBySpeed": "Escala el tiempo transcurrido según la velocidad",
|
||||||
"LabelSeason": "Temporada",
|
"LabelSeason": "Temporada",
|
||||||
"LabelSelectADevice": "Select a device",
|
"LabelSelectADevice": "Selecciona un dispositivo",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
"LabelServerAddress": "Server address",
|
"LabelServerAddress": "Dirección del servidor",
|
||||||
"LabelSetEbookAsPrimary": "Establecer como primario",
|
"LabelSetEbookAsPrimary": "Establecer como primario",
|
||||||
"LabelSetEbookAsSupplementary": "Establecer como suplementario",
|
"LabelSetEbookAsSupplementary": "Establecer como suplementario",
|
||||||
"LabelShakeSensitivity": "Shake sensitivity",
|
"LabelShakeSensitivity": "Sensibilidad a la sacudida",
|
||||||
"LabelShowAll": "Mostrar Todos",
|
"LabelShowAll": "Mostrar Todos",
|
||||||
"LabelSize": "Tamaño",
|
"LabelSize": "Tamaño",
|
||||||
"LabelSleepTimer": "Temporizador para Dormir",
|
"LabelSleepTimer": "Temporizador de apagado",
|
||||||
"LabelStart": "Iniciar",
|
"LabelStart": "Iniciar",
|
||||||
"LabelStartTime": "Tiempo de Inicio",
|
"LabelStartTime": "Tiempo de Inicio",
|
||||||
"LabelStatsBestDay": "Mejor Día",
|
"LabelStatsBestDay": "Mejor Día",
|
||||||
@@ -216,6 +231,7 @@
|
|||||||
"LabelStatsMinutes": "minutos",
|
"LabelStatsMinutes": "minutos",
|
||||||
"LabelStatsMinutesListening": "Minutos Escuchando",
|
"LabelStatsMinutesListening": "Minutos Escuchando",
|
||||||
"LabelStatsWeekListening": "Tiempo escuchando en la Semana",
|
"LabelStatsWeekListening": "Tiempo escuchando en la Semana",
|
||||||
|
"LabelStreamingUsingCellular": "Streaming por móvil",
|
||||||
"LabelTag": "Etiqueta",
|
"LabelTag": "Etiqueta",
|
||||||
"LabelTags": "Etiquetas",
|
"LabelTags": "Etiquetas",
|
||||||
"LabelTheme": "Tema",
|
"LabelTheme": "Tema",
|
||||||
@@ -223,70 +239,79 @@
|
|||||||
"LabelThemeLight": "Claro",
|
"LabelThemeLight": "Claro",
|
||||||
"LabelTimeRemaining": "{0} restante",
|
"LabelTimeRemaining": "{0} restante",
|
||||||
"LabelTitle": "Título",
|
"LabelTitle": "Título",
|
||||||
"LabelTotalTrack": "Total Track",
|
"LabelTotalSize": "Tamaño Total",
|
||||||
|
"LabelTotalTrack": "Pista Total",
|
||||||
"LabelTracks": "Pistas",
|
"LabelTracks": "Pistas",
|
||||||
"LabelType": "Tipo",
|
"LabelType": "Tipo",
|
||||||
"LabelUnlockPlayer": "Unlock Player",
|
"LabelUnlockPlayer": "Desbloquear Reproductor",
|
||||||
"LabelUseBookshelfView": "Use bookshelf view",
|
"LabelUseBookshelfView": "Usar la Vista de Estantería",
|
||||||
"LabelUser": "Usuario",
|
"LabelUser": "Usuario",
|
||||||
"LabelUsername": "Nombre de Usuario",
|
"LabelUsername": "Nombre de Usuario",
|
||||||
"LabelVeryHigh": "Very High",
|
"LabelVeryHigh": "Muy Alto",
|
||||||
"LabelVeryLow": "Very Low",
|
"LabelVeryLow": "Muy Bajo",
|
||||||
"LabelYourBookmarks": "Tus Marcadores",
|
"LabelYourBookmarks": "Tus Marcadores",
|
||||||
"LabelYourProgress": "Tu Progreso",
|
"LabelYourProgress": "Tu Progreso",
|
||||||
"MessageAndroid10Downloads": "Android 10 and below will use internal app storage for downloads.",
|
"MessageAndroid10Downloads": "Android 10 e inferiores utilizarán el almacenamiento interno de aplicaciones para las descargas.",
|
||||||
"MessageAttemptingServerConnection": "Attempting server connection...",
|
"MessageAttemptingServerConnection": "Intentando conectar con el servidor...",
|
||||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf server not connected",
|
"MessageAudiobookshelfServerNotConnected": "Servidor de Audiobookshelf no conectado",
|
||||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> This app is designed to work with an Audiobookshelf server that you or someone you know is hosting. This app does not provide any content.",
|
"MessageAudiobookshelfServerRequired": "<strong>¡Importante!</strong> Esta aplicación está diseñada para trabajar con un servidor Audiobookshelf que usted o alguien que usted conoce es el anfitrión. Esta aplicación no proporciona ningún contenido.",
|
||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
"MessageBookshelfEmpty": "Estantería vacía",
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
"MessageConfirmDeleteLocalEpisode": "¿Eliminar episodio local \"{0}\" de su dispositivo? El archivo en el servidor no se verá afectado.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
"MessageConfirmDeleteLocalFiles": "¿Eliminar los archivos locales de este elemento de tu dispositivo? Los archivos del servidor y tu progreso no se verán afectados.",
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
"MessageConfirmDiscardProgress": "¿Estás seguro de que quieres reiniciar tu progreso?",
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
"MessageConfirmDownloadUsingCellular": "Estas a punto de realizar una descarga utilizando datos móviles. Esto puede incluir cargos de datos del operador. ¿Deseas continuar?",
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
"MessageConfirmMarkAsFinished": "¿Está seguro de que desea marcar este artículo como terminado?",
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
"MessageConfirmRemoveBookmark": "¿Estás seguro de que quieres eliminar el marcador?",
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
"MessageConfirmStreamingUsingCellular": "Estás a punto de hacer streaming utilizando datos móviles. Esto puede incluir cargos de datos del operador. ¿Deseas continuar?",
|
||||||
"MessageDownloading": "Downloading...",
|
"MessageDiscardProgress": "Descartar Progreso",
|
||||||
|
"MessageDownloadCompleteProcessing": "Descarga Completada. Procesando...",
|
||||||
|
"MessageDownloading": "Descargando...",
|
||||||
"MessageDownloadingEpisode": "Descargando Capitulo",
|
"MessageDownloadingEpisode": "Descargando Capitulo",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
|
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
|
||||||
"MessageFeedURLWillBe": "URL de la fuente será {0}",
|
"MessageFeedURLWillBe": "URL de la fuente será {0}",
|
||||||
"MessageFetching": "Buscando...",
|
"MessageFetching": "Buscando...",
|
||||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
"MessageFollowTheProjectOnGithub": "Sigue el proyecto en GitHub",
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Item download complete but failed to create library item",
|
"MessageItemDownloadCompleteFailedToCreate": "Se ha completado la descarga del elemento, pero no se ha podido crear el elemento de la biblioteca",
|
||||||
"MessageLoading": "Cargando...",
|
"MessageLoading": "Cargando...",
|
||||||
"MessageLoadingServerData": "Loading server data...",
|
"MessageLoadingServerData": "Cargando datos del servidor...",
|
||||||
"MessageMarkAsFinished": "Marcar como Terminado",
|
"MessageMarkAsFinished": "Marcar como terminado",
|
||||||
"MessageMediaLinkedToADifferentServer": "Media is linked to an Audiobookshelf server on a different address ({0}). Progress will be synced when connected to this server address.",
|
"MessageMediaLinkedToADifferentServer": "El contenido está vinculado a un servidor de Audiobookshelf en una dirección diferente ({0}). El progreso se sincronizará cuando se conecte a esta dirección de servidor.",
|
||||||
"MessageMediaLinkedToADifferentUser": "Media is linked to this server but was downloaded by a different user. Progress will only be synced to the user that downloaded it.",
|
"MessageMediaLinkedToADifferentUser": "El contenido está vinculado a este servidor pero fue descargado por otro usuario. El progreso solo se sincronizará con el usuario que lo descargó.",
|
||||||
"MessageMediaLinkedToServer": "Linked to server {0}",
|
"MessageMediaLinkedToServer": "Vinculado al servidor {0}",
|
||||||
"MessageMediaLinkedToThisServer": "Downloaded media is linked to this server",
|
"MessageMediaLinkedToThisServer": "El contenido descargado está vinculado a este servidor",
|
||||||
"MessageMediaNotLinkedToServer": "Media is not linked to an Audiobookshelf server. No progress will be synced.",
|
"MessageMediaNotLinkedToServer": "El contenido multimedia no está vinculado a un servidor de Audiobookshelf. No se sincronizará ningún progreso.",
|
||||||
"MessageNoBookmarks": "Sin Marcadores",
|
"MessageNoBookmarks": "Sin marcadores",
|
||||||
"MessageNoChapters": "Sin Capítulos",
|
"MessageNoChapters": "Sin capítulos",
|
||||||
"MessageNoItems": "Sin Elementos",
|
"MessageNoItems": "Sin elementos",
|
||||||
"MessageNoItemsFound": "Ningún Elemento Encontrado",
|
"MessageNoItemsFound": "Ningún elemento encontrado",
|
||||||
"MessageNoListeningSessions": "Ninguna Session Escuchada",
|
"MessageNoListeningSessions": "Ninguna sesión escuchada",
|
||||||
"MessageNoMediaFolders": "No Media Folders",
|
"MessageNoMediaFolders": "Sin carpetas multimedia",
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
"MessageNoNetworkConnection": "Sin conexión de red",
|
||||||
"MessageNoPodcastsFound": "Ningún podcast encontrado",
|
"MessageNoPodcastsFound": "Ningún podcast encontrado",
|
||||||
|
"MessageNoSeries": "Ninguna serie",
|
||||||
"MessageNoUpdatesWereNecessary": "No fue necesario actualizar",
|
"MessageNoUpdatesWereNecessary": "No fue necesario actualizar",
|
||||||
"MessageNoUserPlaylists": "No tienes lista de reproducciones",
|
"MessageNoUserPlaylists": "No tienes ninguna lista de reproducción",
|
||||||
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
|
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
"MessageSeriesAlreadyDownloaded": "Ya has descargado todos los libros de esta serie.",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
"MessageSeriesDownloadConfirm": "¿Descargar {0} libro(s) faltante(s) con {1} archivo(s), totalizando {2} a la carpeta {3}?",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
"MessageSeriesDownloadConfirmIos": "¿Descargar {0} libro(s) faltantes con {1} archivo(s), con un total de {2}?",
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered wifi",
|
"MessageSocketConnectedOverMeteredCellular": "Conexión de socket a través de red celular de tarifa por consumo",
|
||||||
"MessageSocketNotConnected": "Socket not connected",
|
"MessageSocketConnectedOverMeteredWifi": "Socket conectado a través de una red Wi-Fi con tarificación por consumo",
|
||||||
|
"MessageSocketConnectedOverUnmeteredCellular": "Socket conectado a través de red celular sin tarificación por consumo",
|
||||||
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket conectado a través de una red Wi-Fi sin tarificación por consumo",
|
||||||
|
"MessageSocketNotConnected": "Socket no conectado",
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Advertencia: La mayoría de las aplicaciones de podcast requieren que la URL de la fuente RSS use HTTPS",
|
"NoteRSSFeedPodcastAppsHttps": "Advertencia: La mayoría de las aplicaciones de podcast requieren que la URL de la fuente RSS use HTTPS",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Advertencia: 1 o más de sus episodios no tienen fecha de publicación. Algunas aplicaciones de podcast lo requieren.",
|
"NoteRSSFeedPodcastAppsPubDate": "Advertencia: 1 o más de sus episodios no tienen fecha de publicación. Algunas aplicaciones de podcast lo requieren.",
|
||||||
"ToastBookmarkCreateFailed": "Error al crear marcador",
|
"ToastBookmarkCreateFailed": "Error al crear marcador",
|
||||||
"ToastBookmarkRemoveFailed": "Error al eliminar marcador",
|
"ToastBookmarkRemoveFailed": "Error al eliminar marcador",
|
||||||
"ToastBookmarkUpdateFailed": "Error al actualizar el marcador",
|
"ToastBookmarkUpdateFailed": "Error al actualizar el marcador",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Error al marcar como Terminado",
|
"ToastDownloadNotAllowedOnCellular": "Las descargas no están permitidas con datos móviles",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Error al marcar como No Terminado",
|
"ToastItemMarkedAsFinishedFailed": "Error al marcar como terminado",
|
||||||
"ToastPlaylistCreateFailed": "Error al crear la lista de reproducción.",
|
"ToastItemMarkedAsNotFinishedFailed": "No se ha podido marcar como no finalizado",
|
||||||
|
"ToastPlaylistCreateFailed": "Error al crear la lista de reproducción",
|
||||||
"ToastPodcastCreateFailed": "Error al crear podcast",
|
"ToastPodcastCreateFailed": "Error al crear podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast creado",
|
"ToastPodcastCreateSuccess": "Podcast creado",
|
||||||
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada"
|
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "El streaming no está permitido con datos móviles"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
{
|
||||||
|
"ButtonAdd": "Lisää",
|
||||||
|
"ButtonAddNewServer": "Lisää uusi palvelin",
|
||||||
|
"ButtonAuthors": "Tekijät",
|
||||||
|
"ButtonBack": "Takaisin",
|
||||||
|
"ButtonCancel": "Peruuta",
|
||||||
|
"ButtonCancelTimer": "Peruuta ajastin",
|
||||||
|
"ButtonClearFilter": "Poista suodatus",
|
||||||
|
"ButtonCloseFeed": "Sulje syöte",
|
||||||
|
"ButtonCollections": "Kokoelmat",
|
||||||
|
"ButtonConnect": "Yhdistä",
|
||||||
|
"ButtonConnectToServer": "Yhdistä palvelimelle",
|
||||||
|
"ButtonCreate": "Luo",
|
||||||
|
"ButtonCreateBookmark": "Luo kirjanmerkki",
|
||||||
|
"ButtonCreateNewPlaylist": "Luo uusi soittolista",
|
||||||
|
"ButtonDelete": "Poista",
|
||||||
|
"ButtonDeleteLocalEpisode": "Poista paikallinen jakso",
|
||||||
|
"ButtonDeleteLocalFile": "Poista paikallinen tiedosto",
|
||||||
|
"ButtonDeleteLocalItem": "Poista paikallinen sisältö",
|
||||||
|
"ButtonDisableAutoTimer": "Sammuta automaattiajastin",
|
||||||
|
"ButtonDisconnect": "Katkaise yhteys",
|
||||||
|
"ButtonGoToWebClient": "Siirry Web-käyttöliittymään",
|
||||||
|
"ButtonHistory": "Historia",
|
||||||
|
"ButtonHome": "Koti",
|
||||||
|
"ButtonIssues": "Ongelmat",
|
||||||
|
"ButtonLatest": "Viimeisimmät",
|
||||||
|
"ButtonLibrary": "Kirjasto",
|
||||||
|
"ButtonLocalMedia": "Paikallinen media",
|
||||||
|
"ButtonManageLocalFiles": "Hallitse paikallsiia tiedostoja",
|
||||||
|
"ButtonNewFolder": "Uusi kansio",
|
||||||
|
"ButtonNextEpisode": "Seuraava jakso",
|
||||||
|
"ButtonOpenFeed": "Avaa syöte",
|
||||||
|
"ButtonOverride": "Ohita",
|
||||||
|
"ButtonPause": "Pysäytä",
|
||||||
|
"ButtonPlay": "Toista",
|
||||||
|
"ButtonPlayEpisode": "Toista jakso",
|
||||||
|
"ButtonPlaying": "Toistetaan",
|
||||||
|
"ButtonPlaylists": "Soittolistat",
|
||||||
|
"ButtonRead": "Lue",
|
||||||
|
"ButtonRemove": "Poista",
|
||||||
|
"ButtonRemoveFromServer": "Poista palvelimelta",
|
||||||
|
"ButtonSave": "Tallenna",
|
||||||
|
"ButtonSaveOrder": "Tallenna järjestys",
|
||||||
|
"ButtonSearch": "Etsi",
|
||||||
|
"ButtonSendEbookToDevice": "Lähetä kirja lukulaitteeseen",
|
||||||
|
"ButtonSeries": "Sarjat",
|
||||||
|
"ButtonSetTimer": "Aseta ajastin",
|
||||||
|
"ButtonStream": "Suoratoisto",
|
||||||
|
"ButtonSubmit": "Lähetä",
|
||||||
|
"ButtonSwitchServerUser": "Vaihda palvelinta/käyttäjää",
|
||||||
|
"ButtonUserStats": "Käyttäjän tilastot",
|
||||||
|
"ButtonYes": "Kyllä",
|
||||||
|
"HeaderAccount": "Tili",
|
||||||
|
"HeaderAdvanced": "Edistynyt",
|
||||||
|
"HeaderAudioTracks": "Ääniraidat",
|
||||||
|
"HeaderChapters": "Luvut",
|
||||||
|
"HeaderCollection": "Kokoelma",
|
||||||
|
"HeaderCollectionItems": "Kokoelman osat",
|
||||||
|
"HeaderConnectionStatus": "Kokoelman tila",
|
||||||
|
"HeaderDataSettings": "Tietojen asetukset",
|
||||||
|
"HeaderDetails": "Yksityiskohdat",
|
||||||
|
"HeaderDownloads": "Lataukset",
|
||||||
|
"HeaderEbookFiles": "E-kirjatiedostot",
|
||||||
|
"HeaderEpisodes": "Jaksot",
|
||||||
|
"HeaderEreaderSettings": "E-lukijan asetukset",
|
||||||
|
"HeaderLatestEpisodes": "Viimeisimmät jaksot",
|
||||||
|
"HeaderLibraries": "Kirjastot",
|
||||||
|
"HeaderLocalFolders": "Paikalliset kansiot",
|
||||||
|
"HeaderLocalLibraryItems": "Paikallisen kirjaston niteet",
|
||||||
|
"HeaderNewPlaylist": "Uusi soittolista",
|
||||||
|
"HeaderOpenRSSFeed": "Avaa RSS-syöte",
|
||||||
|
"HeaderPlaybackSettings": "Toistoasetukset",
|
||||||
|
"HeaderPlaylist": "Soittolista",
|
||||||
|
"HeaderPlaylistItems": "Soittolistan kohteet",
|
||||||
|
"HeaderRSSFeed": "RSS syöte",
|
||||||
|
"HeaderRSSFeedGeneral": "RSS yksityiskohdat",
|
||||||
|
"HeaderRSSFeedIsOpen": "RSS syöte on avoinna",
|
||||||
|
"HeaderSelectDownloadLocation": "Valitse tallennussijainti",
|
||||||
|
"HeaderSettings": "Asetukset",
|
||||||
|
"HeaderSleepTimer": "Uniajastin",
|
||||||
|
"HeaderSleepTimerSettings": "Uniajastimen asetukset",
|
||||||
|
"HeaderStatsMinutesListeningChart": "Kuunteluminuutit (viim. 7 pv)",
|
||||||
|
"HeaderStatsRecentSessions": "Viimeaikaiset istunnot",
|
||||||
|
"HeaderTableOfContents": "Sisällysluettelo",
|
||||||
|
"HeaderUserInterfaceSettings": "Käyttöliittymän asetukset",
|
||||||
|
"HeaderYourStats": "Tilastosi",
|
||||||
|
"LabelAddToPlaylist": "Lisää soittolistaan",
|
||||||
|
"LabelAdded": "Lisätty",
|
||||||
|
"LabelAddedAt": "Lisätty listalle",
|
||||||
|
"LabelAll": "Kaikki",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Salli kelaaminen ilmoituksen säätimillä",
|
||||||
|
"LabelAlways": "Aina",
|
||||||
|
"LabelAskConfirmation": "Kysy varmistusta",
|
||||||
|
"LabelAuthor": "Tekijä",
|
||||||
|
"LabelAuthorFirstLast": "Tekijä (Etunimi Sukunimi)",
|
||||||
|
"LabelAuthorLastFirst": "Tekijä (Sukunimi, Etunimi)",
|
||||||
|
"LabelAuthors": "Tekijät",
|
||||||
|
"LabelAutoDownloadEpisodes": "Lataa jaksot automaattisesti",
|
||||||
|
"LabelAutoRewindTime": "Automaattisen takaisinkelauksen kesto",
|
||||||
|
"LabelAutoSleepTimer": "Automaattinen uniajastin",
|
||||||
|
"LabelAutoSleepTimerAutoRewind": "Automaattisen uniajastimen automaattinen takaisinkelaus",
|
||||||
|
"LabelAutoSleepTimerAutoRewindHelp": "Kun automaattinen uniajastin päättyy, ensi kerralla toistetaan osa kuunenltua uudelleen.",
|
||||||
|
"LabelAutoSleepTimerHelp": "Toiston alkaessa tällä aikävälillä unikytkin kytketään automaattisesti käyttöön.",
|
||||||
|
"LabelBooks": "Kirjat",
|
||||||
|
"LabelChapterTrack": "Luvun raita",
|
||||||
|
"LabelChapters": "Luvut",
|
||||||
|
"LabelClosePlayer": "Sulje soitin",
|
||||||
|
"LabelCollapseSeries": "Pienennä sarja",
|
||||||
|
"LabelComplete": "Valmis",
|
||||||
|
"LabelContinueBooks": "Jatka kirjoja",
|
||||||
|
"LabelContinueEpisodes": "Jatka jaksoja",
|
||||||
|
"LabelContinueListening": "Jatka kuuntelua",
|
||||||
|
"LabelContinueReading": "Jatka lukemista",
|
||||||
|
"LabelContinueSeries": "Jatka sarjoja",
|
||||||
|
"LabelCustomTime": "Mukautettu aika",
|
||||||
|
"LabelDescription": "Kuvaus",
|
||||||
|
"LabelDisableAudioFadeOut": "Poista äänen häivytys käytöstä",
|
||||||
|
"LabelDisableAudioFadeOutHelp": "Äänenvoimakkuus alkaa laskea kun uniajastimessa on jäljellä alle minuutti. Laita tämä asetus pois päältä estääksesi sen.",
|
||||||
|
"LabelDisableAutoRewind": "Poista käytöstä automaattinen takaisinkelaus",
|
||||||
|
"LabelDisableShakeToReset": "Poista uniajastimnen nollaus ravistamalla käytöstä",
|
||||||
|
"LabelDisableShakeToResetHelp": "Ravistamalla laitettasi uniajastimen kuluessa tai enintään kaksi minuuttia sen loppumisen jälkeen uudistaa ajastimen.",
|
||||||
|
"LabelDisableVibrateOnReset": "Poista värinä käytöstä nollattaessa",
|
||||||
|
"LabelDisableVibrateOnResetHelp": "Kun uniajastin nollautuu, laitteesi tärisee. Kytke tämä asetus päälle poistaaksesi tärinä kun uniajastin nollautuu.",
|
||||||
|
"LabelDiscover": "Löydä",
|
||||||
|
"LabelDownload": "Lataa",
|
||||||
|
"LabelDownloadUsingCellular": "Lataa käyttäen mobiilidataa",
|
||||||
|
"LabelDownloaded": "Ladatut",
|
||||||
|
"LabelDuration": "Kesto",
|
||||||
|
"LabelEbook": "E-kirja",
|
||||||
|
"LabelEbooks": "E-kirjat",
|
||||||
|
"LabelEnable": "Ota käyttöön",
|
||||||
|
"LabelEnd": "Loppu",
|
||||||
|
"LabelEndOfChapter": "Luvun loppu",
|
||||||
|
"LabelEpisode": "Jakso",
|
||||||
|
"LabelFeedURL": "Syötteen URL",
|
||||||
|
"LabelFile": "Tiedosto",
|
||||||
|
"LabelFileBirthtime": "Tiedoston syntymäaika",
|
||||||
|
"LabelFileModified": "Muutettu tiedosto",
|
||||||
|
"LabelFilename": "Tiedostonimi",
|
||||||
|
"LabelFinished": "Valmis",
|
||||||
|
"LabelFolder": "Kansio",
|
||||||
|
"LabelGenre": "Lajityyppi",
|
||||||
|
"LabelGenres": "Lajityypit",
|
||||||
|
"LabelHapticFeedback": "Tuntopalaute",
|
||||||
|
"LabelHigh": "Korkea",
|
||||||
|
"LabelHost": "Isäntä",
|
||||||
|
"LabelInProgress": "Kesken",
|
||||||
|
"LabelIncomplete": "Keskeneräinen",
|
||||||
|
"LabelJumpBackwardsTime": "Hyppää taaksepäin ajassa",
|
||||||
|
"LabelJumpForwardsTime": "Hyppää eteenpäin ajassa",
|
||||||
|
"LabelLanguage": "Kieli",
|
||||||
|
"LabelLight": "Vaalea",
|
||||||
|
"LabelLineSpacing": "Riviväli",
|
||||||
|
"LabelListenAgain": "Kuuntele uudelleen",
|
||||||
|
"LabelLocalBooks": "Paikalliset kirjat",
|
||||||
|
"LabelLocalPodcasts": "Paikalliset podcastit",
|
||||||
|
"LabelLockPlayer": "Lukitse soitin",
|
||||||
|
"LabelLow": "Matala",
|
||||||
|
"LabelMediaType": "Mediatyyppi",
|
||||||
|
"LabelMedium": "Keskitaso",
|
||||||
|
"LabelMore": "Lisää",
|
||||||
|
"LabelMoreInfo": "Lisätietoja",
|
||||||
|
"LabelName": "Nimi",
|
||||||
|
"LabelNarrator": "Lukija",
|
||||||
|
"LabelNarrators": "Lukijat",
|
||||||
|
"LabelNavigateWithVolume": "Navigoi äänenvoimakkuus-painikkeilla",
|
||||||
|
"LabelNavigateWithVolumeDisabled": "Pois päältä",
|
||||||
|
"LabelNavigateWithVolumeEnabled": "Päällä",
|
||||||
|
"LabelNever": "Ei koskaan",
|
||||||
|
"LabelNewestAuthors": "Uusimmat kirjailijat",
|
||||||
|
"LabelNewestEpisodes": "Uusimmat jaksot",
|
||||||
|
"LabelNo": "Ei",
|
||||||
|
"LabelNotStarted": "Ei aloitettu",
|
||||||
|
"LabelOff": "Pois päältä",
|
||||||
|
"LabelPassword": "Salasana",
|
||||||
|
"LabelPath": "Polku",
|
||||||
|
"LabelPlaybackLocal": "Paikallinen",
|
||||||
|
"LabelPlaybackSpeed": "Toistonopeus",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
|
"LabelPodcasts": "Podcastit",
|
||||||
|
"LabelPublishYear": "Julkaisuvuosi",
|
||||||
|
"LabelRSSFeedPreventIndexing": "Estä indeksointi",
|
||||||
|
"LabelRandomly": "Satunnaisesti",
|
||||||
|
"LabelRead": "Lue",
|
||||||
|
"LabelReadAgain": "Lue uudelleen",
|
||||||
|
"LabelRecentSeries": "Viimeisimmät sarjat",
|
||||||
|
"LabelRecentlyAdded": "Viimeeksi lisätyt",
|
||||||
|
"LabelRemoveFromPlaylist": "Poista soittolistalta",
|
||||||
|
"LabelScaleElapsedTimeBySpeed": "Skaalaa kulunut aika nopeuden mukaan",
|
||||||
|
"LabelSeason": "Kausi",
|
||||||
|
"LabelSelectADevice": "Valitse laite",
|
||||||
|
"LabelSeries": "Sarja",
|
||||||
|
"LabelServerAddress": "Palvelimen osoite",
|
||||||
|
"LabelSetEbookAsPrimary": "Aseta ensisijaiseksi",
|
||||||
|
"LabelSetEbookAsSupplementary": "Aseta täydentäväksi",
|
||||||
|
"LabelShakeSensitivity": "Ravistusherkkyys",
|
||||||
|
"LabelShowAll": "Näytä kaikki",
|
||||||
|
"LabelSize": "Koko",
|
||||||
|
"LabelSleepTimer": "Uniajastin",
|
||||||
|
"LabelStart": "Aloita",
|
||||||
|
"LabelStartTime": "Aloitusaika",
|
||||||
|
"LabelStatsBestDay": "Paras päivä",
|
||||||
|
"LabelStatsDailyAverage": "Päivittäinen keskiarvo",
|
||||||
|
"LabelStatsDays": "Päivää",
|
||||||
|
"LabelStatsDaysListened": "Päivää kuunneltu",
|
||||||
|
"LabelStatsInARow": "peräjälkeen",
|
||||||
|
"LabelStatsMinutes": "minuuttia",
|
||||||
|
"LabelStatsMinutesListening": "Minuuttia kuunneltu",
|
||||||
|
"LabelStatsWeekListening": "Viikon aikana kuunneltu",
|
||||||
|
"LabelTag": "Tägi",
|
||||||
|
"LabelTags": "Tägit",
|
||||||
|
"LabelTheme": "Teema",
|
||||||
|
"LabelThemeDark": "Tumma",
|
||||||
|
"LabelThemeLight": "Kirkas",
|
||||||
|
"LabelTimeRemaining": "{0} jäljellä",
|
||||||
|
"LabelTitle": "Nimi",
|
||||||
|
"LabelTotalSize": "Kokonaiskoko",
|
||||||
|
"LabelTracks": "Raidat",
|
||||||
|
"LabelType": "Tyyppi",
|
||||||
|
"LabelUseBookshelfView": "Käytä kirjahyllynäkymää",
|
||||||
|
"LabelUser": "Käyttäjä",
|
||||||
|
"LabelUsername": "Käyttäjätunnus",
|
||||||
|
"LabelVeryHigh": "Erittäin korkea",
|
||||||
|
"LabelVeryLow": "Erittäin matala",
|
||||||
|
"LabelYourBookmarks": "Kirjanmerkkisi",
|
||||||
|
"LabelYourProgress": "Edistymisesi",
|
||||||
|
"MessageAndroid10Downloads": "Android 10 ja sitä vanhemmat käyttävät sovelluksen sisäistä tallennustilaa latauksiin.",
|
||||||
|
"MessageAttemptingServerConnection": "Yritetään palvelinyhteyttä...",
|
||||||
|
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf palvelinta ei ole yhdistetty",
|
||||||
|
"MessageAudiobookshelfServerRequired": "<strong>Tärkeää!</strong> Tämä sovellus on suunniteltu toimimaan sinun tai tuttavasi Audiobookshelf-palvelimen kanssa. Tämä sovellus ei tarjoa sisältöä.",
|
||||||
|
"MessageBookshelfEmpty": "Kirjahylly on tyhjä",
|
||||||
|
"MessageConfirmDeleteLocalEpisode": "Poista jakso \"{0}\" laitteeltasi? Tämä ei vaikuta palvelimella olevaan tiedostoon.",
|
||||||
|
"MessageConfirmDeleteLocalFiles": "Poista tämän kohteen tiedostot laitteestasi? Tämä ei vaikuta palvelimella oleviin tiedostoihin tai edistymiseesi.",
|
||||||
|
"MessageConfirmDiscardProgress": "Haluatko varmasti nollata edistymisesi?",
|
||||||
|
"MessageConfirmDownloadUsingCellular": "Olet lataamassa mobiilidatan avulla. Tämä voi aiheuttaa operaattorin datamaksuja. Haluatko jatkaa?",
|
||||||
|
"MessageConfirmMarkAsFinished": "Haluatko varmasti merkitä tämän kohteen valmiiksi?",
|
||||||
|
"MessageConfirmRemoveBookmark": "Haluatko varmasti poistaa kirjanmerkin?",
|
||||||
|
"MessageConfirmStreamingUsingCellular": "Olet suoratoistamassa matkapuhelindatan avulla. Tämä voi aiheuttaa operaattorin datamaksuja. Haluatko jatkaa?",
|
||||||
|
"MessageDiscardProgress": "Hylkää edistyminen",
|
||||||
|
"MessageDownloadCompleteProcessing": "Lataus valmis. Käsitellään...",
|
||||||
|
"MessageDownloading": "Ladataan...",
|
||||||
|
"MessageDownloadingEpisode": "Ladataan jaksoa",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} jaksoa on latausjonossa",
|
||||||
|
"MessageFeedURLWillBe": "Syötteen URL tulee olemaan {0}",
|
||||||
|
"MessageFetching": "Haetaan...",
|
||||||
|
"MessageFollowTheProjectOnGithub": "Seuraa projektia Githubissa",
|
||||||
|
"MessageItemDownloadCompleteFailedToCreate": "Kohteen lataus on valmis, mutta kirjastokohteen luominen epäonnistui",
|
||||||
|
"MessageLoading": "Ladataan...",
|
||||||
|
"MessageLoadingServerData": "Ladataan palvelintietoja...",
|
||||||
|
"MessageMarkAsFinished": "Merkitse valmiiksi",
|
||||||
|
"MessageMediaLinkedToADifferentServer": "Media on linkitetty Audiobookshelf-palvelimeen eri osoitteessa ({0}). Edistyminen synkronoidaan, kun olet yhdistänyt tähän palvelinosoitteeseen.",
|
||||||
|
"MessageMediaLinkedToADifferentUser": "Media on linkitetty tähän palvelimeen, mutta sen latasi eri käyttäjä. Edistyminen synkronoidaan vain käyttäjälle, joka latasi sen.",
|
||||||
|
"MessageMediaLinkedToServer": "Linkitetty palvelimeen {0}",
|
||||||
|
"MessageMediaLinkedToThisServer": "Ladattu media on linkitetty tähän palvelimeen",
|
||||||
|
"MessageMediaNotLinkedToServer": "Mediaa ei ole linkitetty Audiobookshelf-palvelimeen. Edistystä ei synkronoida.",
|
||||||
|
"MessageNoBookmarks": "Ei kirjanmerkkejä",
|
||||||
|
"MessageNoChapters": "Ei kappaleita",
|
||||||
|
"MessageNoItems": "Ei kohteita",
|
||||||
|
"MessageNoItemsFound": "Kohteita ei löytynyt",
|
||||||
|
"MessageNoListeningSessions": "Ei kuunteluistuntoja",
|
||||||
|
"MessageNoMediaFolders": "Ei mediakansioita",
|
||||||
|
"MessageNoNetworkConnection": "Ei verkkoyhteyttä",
|
||||||
|
"MessageNoPodcastsFound": "Podcasteja ei löytynyt",
|
||||||
|
"MessageNoSeries": "Ei sarjoja",
|
||||||
|
"MessageNoUpdatesWereNecessary": "Päivityksiä ei tarvittu",
|
||||||
|
"MessageNoUserPlaylists": "Sinulla ei ole soittolistoja",
|
||||||
|
"MessageReportBugsAndContribute": "Ilmoita virheistä, toivo ominaisuuksia ja osallistu",
|
||||||
|
"ToastBookmarkCreateFailed": "Kirjanmerkin luominen epäonnistui",
|
||||||
|
"ToastBookmarkRemoveFailed": "Kirjanmerkin poistaminen epäonnistui",
|
||||||
|
"ToastBookmarkUpdateFailed": "Kirjanmerkin päivittäminen epäonnistui",
|
||||||
|
"ToastDownloadNotAllowedOnCellular": "Lataaminen ei ole sallittua mobiilidatalla",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "Valmiiksi merkitseminen epäonnistui",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "Valmiiksi merkitsemisen poisto epäonnistui",
|
||||||
|
"ToastPlaylistCreateFailed": "Soittolistan luominen epäonnistui",
|
||||||
|
"ToastPodcastCreateFailed": "Podcastin luominen epäonnistui",
|
||||||
|
"ToastPodcastCreateSuccess": "Podcastin luominen onnistui",
|
||||||
|
"ToastRSSFeedCloseFailed": "RSS syötteen sulkeminen epäonnistui",
|
||||||
|
"ToastRSSFeedCloseSuccess": "RSS syöte suljettu",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "Suoratoisto ei ole sallittu mobiilidatalla"
|
||||||
|
}
|
||||||
+183
-161
@@ -1,132 +1,139 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Ajouter",
|
"ButtonAdd": "Ajouter",
|
||||||
"ButtonAddNewServer": "Add New Server",
|
"ButtonAddNewServer": "Ajouter un serveur",
|
||||||
"ButtonAuthors": "Auteurs",
|
"ButtonAuthors": "Auteurs",
|
||||||
"ButtonBack": "Back",
|
"ButtonBack": "Retour",
|
||||||
"ButtonCancel": "Annuler",
|
"ButtonCancel": "Annuler",
|
||||||
"ButtonCancelTimer": "Cancel Timer",
|
"ButtonCancelTimer": "Annuler la minuterie",
|
||||||
"ButtonClearFilter": "Effacer le filtre",
|
"ButtonClearFilter": "Effacer le filtre",
|
||||||
"ButtonCloseFeed": "Fermer le flux",
|
"ButtonCloseFeed": "Fermer le flux",
|
||||||
"ButtonCollections": "Collections",
|
"ButtonCollections": "Collections",
|
||||||
"ButtonConnect": "Connect",
|
"ButtonConnect": "Connecter",
|
||||||
"ButtonConnectToServer": "Connect to Server",
|
"ButtonConnectToServer": "Se connecter au serveur",
|
||||||
"ButtonCreate": "Créer",
|
"ButtonCreate": "Créer",
|
||||||
"ButtonCreateBookmark": "Create Bookmark",
|
"ButtonCreateBookmark": "Créer un marque-page",
|
||||||
"ButtonCreateNewPlaylist": "Create New Playlist",
|
"ButtonCreateNewPlaylist": "Créer une nouvelle liste de lecture",
|
||||||
"ButtonDelete": "Effacer",
|
"ButtonDelete": "Supprimer",
|
||||||
"ButtonDeleteLocalEpisode": "Delete local episode",
|
"ButtonDeleteLocalEpisode": "Supprimer l’épisode de l’appareil",
|
||||||
"ButtonDeleteLocalFile": "Delete local file",
|
"ButtonDeleteLocalFile": "Supprimer le fichier de l’appareil",
|
||||||
"ButtonDeleteLocalItem": "Delete local item",
|
"ButtonDeleteLocalItem": "Supprimer l’élément de l’appareil",
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
"ButtonDisableAutoTimer": "Désactiver la minuterie automatique",
|
||||||
"ButtonDisconnect": "Disconnect",
|
"ButtonDisconnect": "Déconnecter",
|
||||||
"ButtonHistory": "History",
|
"ButtonGoToWebClient": "Ouvrir dans le navigateur",
|
||||||
|
"ButtonHistory": "Historique",
|
||||||
"ButtonHome": "Accueil",
|
"ButtonHome": "Accueil",
|
||||||
"ButtonIssues": "Parutions",
|
"ButtonIssues": "Problèmes",
|
||||||
"ButtonLatest": "Dernière version",
|
"ButtonLatest": "Dernière version",
|
||||||
"ButtonLibrary": "Bibliothèque",
|
"ButtonLibrary": "Bibliothèque",
|
||||||
"ButtonLocalMedia": "Local Media",
|
"ButtonLocalMedia": "Médias locaux",
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
"ButtonManageLocalFiles": "Gérer les fichiers locaux",
|
||||||
"ButtonNewFolder": "New Folder",
|
"ButtonNewFolder": "Nouveau dossier",
|
||||||
"ButtonNextEpisode": "Next Episode",
|
"ButtonNextEpisode": "Prochain épisode",
|
||||||
"ButtonOpenFeed": "Ouvrir le flux",
|
"ButtonOpenFeed": "Ouvrir le flux",
|
||||||
"ButtonOverride": "Override",
|
"ButtonOverride": "Remplacer",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "Pause",
|
||||||
"ButtonPlay": "Écouter",
|
"ButtonPlay": "Lire",
|
||||||
|
"ButtonPlayEpisode": "Lire l’épisode",
|
||||||
"ButtonPlaying": "En lecture",
|
"ButtonPlaying": "En lecture",
|
||||||
"ButtonPlaylists": "Listes de lecture",
|
"ButtonPlaylists": "Listes de lecture",
|
||||||
"ButtonRead": "Lire",
|
"ButtonRead": "Lire",
|
||||||
"ButtonRemove": "Supprimer",
|
"ButtonRemove": "Supprimer",
|
||||||
"ButtonRemoveFromServer": "Remove from Server",
|
"ButtonRemoveFromServer": "Retirer du serveur",
|
||||||
"ButtonSave": "Sauvegarder",
|
"ButtonSave": "Sauvegarder",
|
||||||
"ButtonSaveOrder": "Save Order",
|
"ButtonSaveOrder": "Sauvegarder l’ordre",
|
||||||
"ButtonSearch": "Chercher",
|
"ButtonSearch": "Chercher",
|
||||||
"ButtonSendEbookToDevice": "Send Ebook to Device",
|
"ButtonSendEbookToDevice": "Envoyer le livre à l’appareil",
|
||||||
"ButtonSeries": "Séries",
|
"ButtonSeries": "Séries",
|
||||||
"ButtonSetTimer": "Set Timer",
|
"ButtonSetTimer": "Régler la minuterie",
|
||||||
"ButtonStream": "Stream",
|
"ButtonStream": "Flux",
|
||||||
"ButtonSubmit": "Soumettre",
|
"ButtonSubmit": "Soumettre",
|
||||||
"ButtonSwitchServerUser": "Switch Server/User",
|
"ButtonSwitchServerUser": "Changer de serveur / utilisateur",
|
||||||
"ButtonUserStats": "User Stats",
|
"ButtonUserStats": "Statistiques utilisateur",
|
||||||
"ButtonYes": "Oui",
|
"ButtonYes": "Oui",
|
||||||
"HeaderAccount": "Compte",
|
"HeaderAccount": "Compte",
|
||||||
"HeaderAdvanced": "Avancé",
|
"HeaderAdvanced": "Avancé",
|
||||||
"HeaderAudioTracks": "Pistes audio",
|
"HeaderAudioTracks": "Pistes audio",
|
||||||
"HeaderChapters": "Chapitres",
|
"HeaderChapters": "Chapitres",
|
||||||
"HeaderCollection": "Collection",
|
"HeaderCollection": "Collection",
|
||||||
"HeaderCollectionItems": "Entrées de la Collection",
|
"HeaderCollectionItems": "Entrées de la collection",
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
"HeaderConnectionStatus": "Status de connexion",
|
||||||
|
"HeaderDataSettings": "Paramètres de données",
|
||||||
"HeaderDetails": "Détails",
|
"HeaderDetails": "Détails",
|
||||||
"HeaderDownloads": "Downloads",
|
"HeaderDownloads": "Téléchargements",
|
||||||
"HeaderEbookFiles": "Fichier des livres numériques",
|
"HeaderEbookFiles": "Fichiers des livres numériques",
|
||||||
"HeaderEpisodes": "Épisodes",
|
"HeaderEpisodes": "Épisodes",
|
||||||
"HeaderEreaderSettings": "Options Ereader",
|
"HeaderEreaderSettings": "Paramètres de la liseuse",
|
||||||
"HeaderLatestEpisodes": "Dernier épisodes",
|
"HeaderLatestEpisodes": "Dernier épisodes",
|
||||||
"HeaderLibraries": "Bibliothèque",
|
"HeaderLibraries": "Bibliothèque",
|
||||||
"HeaderLocalFolders": "Local Folders",
|
"HeaderLocalFolders": "Dossiers locaux",
|
||||||
"HeaderLocalLibraryItems": "Local Library Items",
|
"HeaderLocalLibraryItems": "Éléments de la bibliothèque locale",
|
||||||
"HeaderNewPlaylist": "New Playlist",
|
"HeaderNewPlaylist": "Nouvelle liste de lecture",
|
||||||
"HeaderOpenRSSFeed": "Ouvrir Flux RSS",
|
"HeaderOpenRSSFeed": "Ouvrir le flux RSS",
|
||||||
"HeaderPlaybackSettings": "Playback Settings",
|
"HeaderPlaybackSettings": "Paramètres de lecture",
|
||||||
"HeaderPlaylist": "Liste de lecture",
|
"HeaderPlaylist": "Liste de lecture",
|
||||||
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
||||||
"HeaderRSSFeed": "RSS Feed",
|
"HeaderRSSFeed": "Flux RSS",
|
||||||
"HeaderRSSFeedGeneral": "Détails de flux RSS",
|
"HeaderRSSFeedGeneral": "Détails du flux RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Le Flux RSS est actif",
|
"HeaderRSSFeedIsOpen": "Le flux RSS est actif",
|
||||||
"HeaderSelectDownloadLocation": "Select Download Location",
|
"HeaderSelectDownloadLocation": "Sélectionner le dossier de téléchargement",
|
||||||
"HeaderSettings": "Paramètres",
|
"HeaderSettings": "Paramètres",
|
||||||
"HeaderSleepTimer": "Minuterie",
|
"HeaderSleepTimer": "Minuterie",
|
||||||
"HeaderSleepTimerSettings": "Sleep Timer Settings",
|
"HeaderSleepTimerSettings": "Paramètres du minuteur nocturne",
|
||||||
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
||||||
"HeaderStatsRecentSessions": "Sessions récentes",
|
"HeaderStatsRecentSessions": "Sessions récentes",
|
||||||
"HeaderTableOfContents": "Table des matières",
|
"HeaderTableOfContents": "Table des matières",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "Paramètres de l'interface",
|
||||||
"HeaderYourStats": "Vos statistiques",
|
"HeaderYourStats": "Vos statistiques",
|
||||||
|
"LabelAddToPlaylist": "Ajouter à la liste de lecture",
|
||||||
"LabelAdded": "Ajouté",
|
"LabelAdded": "Ajouté",
|
||||||
"LabelAddedAt": "Date d’ajout",
|
"LabelAddedAt": "Date d’ajout",
|
||||||
"LabelAddToPlaylist": "Ajouter à la liste de lecture",
|
|
||||||
"LabelAll": "Tout",
|
"LabelAll": "Tout",
|
||||||
|
"LabelAllowSeekingOnMediaControls": "Autoriser la recherche de position depuis la notification du lecteur multimédia",
|
||||||
|
"LabelAlways": "Toujours",
|
||||||
|
"LabelAskConfirmation": "Demander une confirmation",
|
||||||
"LabelAuthor": "Auteur",
|
"LabelAuthor": "Auteur",
|
||||||
"LabelAuthorFirstLast": "Auteur (Prénom Nom)",
|
"LabelAuthorFirstLast": "Auteur (Prénom Nom)",
|
||||||
"LabelAuthorLastFirst": "Auteur (Nom, Prénom)",
|
"LabelAuthorLastFirst": "Auteur (Nom, Prénom)",
|
||||||
"LabelAuthors": "Auteurs",
|
"LabelAuthors": "Auteurs",
|
||||||
"LabelAutoDownloadEpisodes": "Téléchargement automatique d’épisode",
|
"LabelAutoDownloadEpisodes": "Téléchargement automatique des épisodes",
|
||||||
"LabelAutoRewindTime": "Auto rewind time",
|
"LabelAutoRewindTime": "Saut arrière automatique",
|
||||||
"LabelAutoSleepTimer": "Auto sleep timer",
|
"LabelAutoSleepTimer": "Minuteur nocturne automatique",
|
||||||
"LabelAutoSleepTimerAutoRewind": "Auto sleep timer auto rewind",
|
"LabelAutoSleepTimerAutoRewind": "Saut arrière automatique du minuteur nocturne",
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
"LabelAutoSleepTimerAutoRewindHelp": "Lorsque le minuteur nocturne se termine, relire l'élément fera un saut en arrière.",
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
"LabelAutoSleepTimerHelp": "Lorsqu'un éléments est lu entre l'heure de début et de fin, un minuteur nocturne se lance automatiquement.",
|
||||||
"LabelBooks": "Livres",
|
"LabelBooks": "Livres",
|
||||||
|
"LabelChapterTrack": "Piste des chapitres",
|
||||||
"LabelChapters": "Chapitres",
|
"LabelChapters": "Chapitres",
|
||||||
"LabelChapterTrack": "Chapter Track",
|
|
||||||
"LabelClosePlayer": "Fermer le lecteur",
|
"LabelClosePlayer": "Fermer le lecteur",
|
||||||
"LabelCollapseSeries": "Réduire les séries",
|
"LabelCollapseSeries": "Réduire les séries",
|
||||||
"LabelComplete": "Complet",
|
"LabelComplete": "Complet",
|
||||||
"LabelContinueBooks": "Continue Books",
|
"LabelContinueBooks": "Continuer les Livres",
|
||||||
"LabelContinueEpisodes": "Continue Episodes",
|
"LabelContinueEpisodes": "Continuer les épisodes",
|
||||||
"LabelContinueListening": "Continue Listening",
|
"LabelContinueListening": "Continuer la lecture",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continuer la lecture",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Continuer les séries",
|
||||||
"LabelCustomTime": "Custom time",
|
"LabelCustomTime": "Temps personalisé",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
"LabelDisableAudioFadeOut": "Désactiver la diminution automatique du volume",
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
"LabelDisableAudioFadeOutHelp": "Le volume audio diminuera lorsqu’il restera moins d’une minute avant la mise en veille. Activez ce paramètre pour que le volume ne diminue pas.",
|
||||||
"LabelDisableAutoRewind": "Disable auto rewind",
|
"LabelDisableAutoRewind": "Désactiver le saut arrière automatique",
|
||||||
"LabelDisableShakeToReset": "Disable shake to reset",
|
"LabelDisableShakeToReset": "Désactiver secouer pour redémarrer",
|
||||||
"LabelDisableShakeToResetHelp": "Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.",
|
"LabelDisableShakeToResetHelp": "Si vous secouez votre appareil pendant que la minuterie est en cours OU dans les 2 minutes qui suivent son expiration, alors la minuterie de sommeil sera réinitialisée. Activez ce paramètre pour désactiver la fonction « Secouer pour réinitialiser ».",
|
||||||
"LabelDisableVibrateOnReset": "Disable vibrate on reset",
|
"LabelDisableVibrateOnReset": "Désactiver la vibration au redémarrage",
|
||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
"LabelDisableVibrateOnResetHelp": "Lorsque le minuteur est redémarré, l'appareil vibre. Sélectionner pour désactiver les vibrations.",
|
||||||
"LabelDiscover": "Discover",
|
"LabelDiscover": "Découvrir",
|
||||||
"LabelDownload": "Téléchargement",
|
"LabelDownload": "Téléchargement",
|
||||||
"LabelDownloaded": "Downloaded",
|
"LabelDownloadUsingCellular": "Télécharger via les données mobiles",
|
||||||
|
"LabelDownloaded": "Téléchargé",
|
||||||
"LabelDuration": "Durée",
|
"LabelDuration": "Durée",
|
||||||
"LabelEbook": "Livre numérique",
|
"LabelEbook": "Livre numérique",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "Livres numériques",
|
||||||
"LabelEnable": "Activer",
|
"LabelEnable": "Activer",
|
||||||
"LabelEnableMp3IndexSeeking": "Enable mp3 index seeking",
|
"LabelEnableMp3IndexSeeking": "Activer la recherche par index pour mp3",
|
||||||
"LabelEnableMp3IndexSeekingHelp": "This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.",
|
"LabelEnableMp3IndexSeekingHelp": "Ce paramètres ne devrai être activer que si vous avez des problèmes avec des fichiers mp3 que vous ne pouvez pas chercher. La mauvaise indexation est probablement du au Bitrate Variable (VBR) du fichier MP3. Ce paramètre va forcer l'indexation. Une correspondance Temps-Octet est construit durant la lecture. Dans certains cas, avec de gros fichier MP3, il y aura du délais lors de la recherche vers la fin du fichier.",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
"LabelEndOfChapter": "End of Chapter",
|
"LabelEndOfChapter": "Fin du chapitre",
|
||||||
"LabelEndTime": "End time",
|
"LabelEndTime": "Heure de Fin",
|
||||||
"LabelEpisode": "Épisode",
|
"LabelEpisode": "Épisode",
|
||||||
"LabelFeedURL": "URL du flux",
|
"LabelFeedURL": "URL du flux",
|
||||||
"LabelFile": "Fichier",
|
"LabelFile": "Fichier",
|
||||||
@@ -135,87 +142,96 @@
|
|||||||
"LabelFilename": "Nom de fichier",
|
"LabelFilename": "Nom de fichier",
|
||||||
"LabelFinished": "Fini(e)",
|
"LabelFinished": "Fini(e)",
|
||||||
"LabelFolder": "Dossier",
|
"LabelFolder": "Dossier",
|
||||||
|
"LabelFontBoldness": "Graisse de la police",
|
||||||
"LabelFontScale": "Taille de la police de caractère",
|
"LabelFontScale": "Taille de la police de caractère",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHapticFeedback": "Haptic feedback",
|
"LabelHapticFeedback": "Retour haptique",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "A un livre numérique",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "A un livre numérique supplémentaire",
|
||||||
"LabelHeavy": "Heavy",
|
"LabelHeavy": "Puissant",
|
||||||
"LabelHigh": "High",
|
"LabelHigh": "Importante",
|
||||||
"LabelHost": "Hôte",
|
"LabelHost": "Hôte",
|
||||||
"LabelIncomplete": "Incomplet",
|
|
||||||
"LabelInProgress": "En cours",
|
"LabelInProgress": "En cours",
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
"LabelIncomplete": "Incomplet",
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
"LabelInternalAppStorage": "Stockage Interne de l'application",
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
"LabelJumpBackwardsTime": "Durée du saut arrière",
|
||||||
|
"LabelJumpForwardsTime": "Durée du saut avant",
|
||||||
"LabelLanguage": "Langue",
|
"LabelLanguage": "Langue",
|
||||||
"LabelLayout": "Layout",
|
"LabelLayout": "Mise en page",
|
||||||
"LabelLayoutAuto": "Auto",
|
"LabelLayoutAuto": "Automatique",
|
||||||
"LabelLayoutSinglePage": "Single page",
|
"LabelLayoutSinglePage": "Page seule",
|
||||||
"LabelLight": "Light",
|
"LabelLight": "Léger",
|
||||||
"LabelLineSpacing": "Interligne",
|
"LabelLineSpacing": "Interligne",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Écouter à nouveau",
|
||||||
"LabelLocalBooks": "Local Books",
|
"LabelLocalBooks": "Livres locaux",
|
||||||
"LabelLocalPodcasts": "Local Podcasts",
|
"LabelLocalPodcasts": "Podcasts locaux",
|
||||||
"LabelLockOrientation": "Lock orientation",
|
"LabelLockOrientation": "Verrouiller l'orientation",
|
||||||
"LabelLockPlayer": "Lock Player",
|
"LabelLockPlayer": "Verrouiller le lecteur",
|
||||||
"LabelLow": "Low",
|
"LabelLow": "Basse",
|
||||||
"LabelMediaType": "Type de média",
|
"LabelMediaType": "Type de média",
|
||||||
"LabelMedium": "Medium",
|
"LabelMedium": "Moyenne",
|
||||||
"LabelMore": "Plus",
|
"LabelMore": "Plus",
|
||||||
"LabelMoreInfo": "Plus d’info",
|
"LabelMoreInfo": "Plus d’infos",
|
||||||
"LabelName": "Nom",
|
"LabelName": "Nom",
|
||||||
"LabelNarrator": "Narrateur",
|
"LabelNarrator": "Narrateur",
|
||||||
"LabelNarrators": "Narrateurs",
|
"LabelNarrators": "Narrateurs",
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNavigateWithVolume": "Naviguer avec les touches de volume",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
"LabelNavigateWithVolumeDisabled": "Désactivé",
|
||||||
"LabelNo": "No",
|
"LabelNavigateWithVolumeEnabled": "Activé",
|
||||||
"LabelNotFinished": "Non terminé(e)",
|
"LabelNavigateWithVolumeMirrored": "En miroir",
|
||||||
"LabelNotStarted": "Non Démarré(e)",
|
"LabelNever": "Jamais",
|
||||||
"LabelOff": "Off",
|
"LabelNewestAuthors": "Auteurs récents",
|
||||||
|
"LabelNewestEpisodes": "Épisodes récents",
|
||||||
|
"LabelNo": "Non",
|
||||||
|
"LabelNotFinished": "Non terminé",
|
||||||
|
"LabelNotStarted": "Pas commencé",
|
||||||
|
"LabelOff": "Éteint",
|
||||||
"LabelPassword": "Mot de passe",
|
"LabelPassword": "Mot de passe",
|
||||||
"LabelPath": "Chemin",
|
"LabelPath": "Chemin",
|
||||||
"LabelPlaybackDirect": "Direct",
|
"LabelPlaybackDirect": "Direct",
|
||||||
"LabelPlaybackLocal": "Local",
|
"LabelPlaybackLocal": "Local",
|
||||||
"LabelPlaybackSpeed": "Playback Speed",
|
"LabelPlaybackSpeed": "Vitesse de lecture",
|
||||||
"LabelPlaybackTranscode": "Transcode",
|
"LabelPlaybackTranscode": "Transcoder",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de données iTunes et Google podcast",
|
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de données iTunes et Google podcast",
|
||||||
"LabelProgress": "Progression",
|
"LabelProgress": "Progression",
|
||||||
"LabelPubDate": "Date de publication",
|
"LabelPubDate": "Date de publication",
|
||||||
"LabelPublishYear": "Année d’édition",
|
"LabelPublishYear": "Année de publication",
|
||||||
"LabelRead": "Lire",
|
"LabelRSSFeedCustomOwnerEmail": "Courriel personnalisée du propriétaire",
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Courriel du propriétaire personnalisé",
|
|
||||||
"LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé",
|
"LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé",
|
||||||
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
||||||
"LabelRSSFeedSlug": "Identificateur d’adresse du Flux RSS ",
|
"LabelRSSFeedSlug": "Identifiant d’URL du flux RSS",
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
"LabelRandomly": "Au hasard",
|
||||||
|
"LabelRead": "Lire",
|
||||||
|
"LabelReadAgain": "Lire à nouveau",
|
||||||
|
"LabelRecentSeries": "Séries récentes",
|
||||||
|
"LabelRecentlyAdded": "Ajouts récents",
|
||||||
|
"LabelRemoveFromPlaylist": "Supprimer de la Liste de Lecture",
|
||||||
|
"LabelScaleElapsedTimeBySpeed": "Traduire le temps restant en fonction de la vitesse de lecture",
|
||||||
"LabelSeason": "Saison",
|
"LabelSeason": "Saison",
|
||||||
"LabelSelectADevice": "Select a device",
|
"LabelSelectADevice": "Sélectionner un Appareil",
|
||||||
"LabelSeries": "Séries",
|
"LabelSeries": "Séries",
|
||||||
"LabelServerAddress": "Server address",
|
"LabelServerAddress": "Adresse du serveur",
|
||||||
"LabelSetEbookAsPrimary": "Définir comme principale",
|
"LabelSetEbookAsPrimary": "Définir comme principale",
|
||||||
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
|
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
|
||||||
"LabelShakeSensitivity": "Shake sensitivity",
|
"LabelShakeSensitivity": "Sensibilité à la secousse",
|
||||||
"LabelShowAll": "Afficher Tout",
|
"LabelShowAll": "Tout afficher",
|
||||||
"LabelSize": "Taille",
|
"LabelSize": "Taille",
|
||||||
"LabelSleepTimer": "Minuterie",
|
"LabelSleepTimer": "Minuterie",
|
||||||
"LabelStart": "Démarrer",
|
"LabelStart": "Démarrer",
|
||||||
"LabelStartTime": "Heure de Démarrage",
|
"LabelStartTime": "Heure de démarrage",
|
||||||
"LabelStatsBestDay": "Meilleur Jour",
|
"LabelStatsBestDay": "Meilleur jour",
|
||||||
"LabelStatsDailyAverage": "Moyenne Journalière",
|
"LabelStatsDailyAverage": "Moyenne Journalière",
|
||||||
"LabelStatsDays": "Jours",
|
"LabelStatsDays": "Jours",
|
||||||
"LabelStatsDaysListened": "Jours d’écoute",
|
"LabelStatsDaysListened": "Jours d’écoute",
|
||||||
"LabelStatsInARow": "d’affilé(s)",
|
"LabelStatsInARow": "d’affilé(s)",
|
||||||
"LabelStatsItemsFinished": "Articles terminés",
|
"LabelStatsItemsFinished": "Éléments terminés",
|
||||||
"LabelStatsMinutes": "minutes",
|
"LabelStatsMinutes": "minutes",
|
||||||
"LabelStatsMinutesListening": "Minutes d’écoute",
|
"LabelStatsMinutesListening": "Minutes d’écoute",
|
||||||
"LabelStatsWeekListening": "Écoute de la semaine",
|
"LabelStatsWeekListening": "Écoute de la semaine",
|
||||||
|
"LabelStreamingUsingCellular": "Diffuser via les données mobiles",
|
||||||
"LabelTag": "Étiquette",
|
"LabelTag": "Étiquette",
|
||||||
"LabelTags": "Étiquettes",
|
"LabelTags": "Étiquettes",
|
||||||
"LabelTheme": "Thème",
|
"LabelTheme": "Thème",
|
||||||
@@ -223,70 +239,76 @@
|
|||||||
"LabelThemeLight": "Clair",
|
"LabelThemeLight": "Clair",
|
||||||
"LabelTimeRemaining": "{0} restantes",
|
"LabelTimeRemaining": "{0} restantes",
|
||||||
"LabelTitle": "Titre",
|
"LabelTitle": "Titre",
|
||||||
"LabelTotalTrack": "Total Track",
|
"LabelTotalSize": "Taille totale",
|
||||||
|
"LabelTotalTrack": "Pistes totale",
|
||||||
"LabelTracks": "Pistes",
|
"LabelTracks": "Pistes",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnlockPlayer": "Unlock Player",
|
"LabelUnlockPlayer": "Déverrouiller le lecteur",
|
||||||
"LabelUseBookshelfView": "Use bookshelf view",
|
"LabelUseBookshelfView": "Utiliser l'interface bibliothèque",
|
||||||
"LabelUser": "Utilisateur",
|
"LabelUser": "Utilisateur",
|
||||||
"LabelUsername": "Nom d’utilisateur",
|
"LabelUsername": "Nom d’utilisateur",
|
||||||
"LabelVeryHigh": "Very High",
|
"LabelVeryHigh": "Très importante",
|
||||||
"LabelVeryLow": "Very Low",
|
"LabelVeryLow": "Très peu importante",
|
||||||
"LabelYourBookmarks": "Vos signets",
|
"LabelYourBookmarks": "Vos favoris",
|
||||||
"LabelYourProgress": "Votre progression",
|
"LabelYourProgress": "Votre progression",
|
||||||
"MessageAndroid10Downloads": "Android 10 and below will use internal app storage for downloads.",
|
"MessageAndroid10Downloads": "Android 10 et en dessous utiliseront le stockage interne de l'application pour les téléchargements.",
|
||||||
"MessageAttemptingServerConnection": "Attempting server connection...",
|
"MessageAttemptingServerConnection": "Tentative de connexion...",
|
||||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf server not connected",
|
"MessageAudiobookshelfServerNotConnected": "Serveur Audiobookshelf non connecté",
|
||||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> This app is designed to work with an Audiobookshelf server that you or someone you know is hosting. This app does not provide any content.",
|
"MessageAudiobookshelfServerRequired": "<strong>Important !</strong> Cette application est faite pour fonctionner avec un serveur Audiobookshelf. L'application ne fournit aucun contenu.",
|
||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
"MessageBookshelfEmpty": "Bibliothèque vide",
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
"MessageConfirmDeleteLocalEpisode": "Retirer l’épisode local « {0} » de votre appareil ? Le fichier sur le serveur ne sera pas affecté.",
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
"MessageConfirmDeleteLocalFiles": "Supprimer les fichiers locaux de cet élément de votre appareil ? Les fichiers sur le serveur ainsi que votre progression ne serons pas affectés.",
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
"MessageConfirmDiscardProgress": "Êtes-vous sûr·e de vouloir supprimer votre progression ?",
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
"MessageConfirmDownloadUsingCellular": "Vous êtes sur le point d’effectuer un téléchargement en utilisant des données mobiles.Il se peut que des frais de données soient facturés par votre opérateur. Souhaitez-vous continuer ?",
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
"MessageConfirmMarkAsFinished": "Êtes-vous sûr·e de vouloir marquer cette élement comme terminé ?",
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
"MessageConfirmRemoveBookmark": "Êtes-vous sûr·e de vouloir supprimer le marque-page ?",
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
"MessageConfirmStreamingUsingCellular": "Vous êtes sur le point de diffuser du contenu en utilisant des données mobiles. Il se peut que des frais de données soient facturés par votre opérateur. Souhaitez-vous continuer ?",
|
||||||
"MessageDownloading": "Downloading...",
|
"MessageDiscardProgress": "Supprimer la progression",
|
||||||
|
"MessageDownloadCompleteProcessing": "Téléchargement terminé. Analyse…",
|
||||||
|
"MessageDownloading": "Téléchargement…",
|
||||||
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||||
"MessageFeedURLWillBe": "l’URL du flux sera {0}",
|
"MessageFeedURLWillBe": "l’URL du flux sera {0}",
|
||||||
"MessageFetching": "Récupération…",
|
"MessageFetching": "Récupération…",
|
||||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
"MessageFollowTheProjectOnGithub": "Suivre le projet sur GitHub",
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Item download complete but failed to create library item",
|
"MessageItemDownloadCompleteFailedToCreate": "Le téléchargement est terminé mais l’ajout de l’élément dans la bibliothèque a échoué",
|
||||||
"MessageLoading": "Chargement…",
|
"MessageLoading": "Chargement…",
|
||||||
"MessageLoadingServerData": "Loading server data...",
|
"MessageLoadingServerData": "Chargement des données du serveur...",
|
||||||
"MessageMarkAsFinished": "Marquer comme terminé",
|
"MessageMarkAsFinished": "Marquer comme terminé",
|
||||||
"MessageMediaLinkedToADifferentServer": "Media is linked to an Audiobookshelf server on a different address ({0}). Progress will be synced when connected to this server address.",
|
"MessageMediaLinkedToADifferentServer": "L'élément est lié à un serveur Audiobookshelf sur une autre adresse ({0}). La progression sera synchroniser lors de la connexion au serveur à cette adresse.",
|
||||||
"MessageMediaLinkedToADifferentUser": "Media is linked to this server but was downloaded by a different user. Progress will only be synced to the user that downloaded it.",
|
"MessageMediaLinkedToADifferentUser": "L'élément est lié a ce serveur, mais a ete téléchargé par un autre utilisateur. La progression ne sera synchronisé que pour l'utilisateur qui l'a téléchargé..",
|
||||||
"MessageMediaLinkedToServer": "Linked to server {0}",
|
"MessageMediaLinkedToServer": "Lié au serveur {0}",
|
||||||
"MessageMediaLinkedToThisServer": "Downloaded media is linked to this server",
|
"MessageMediaLinkedToThisServer": "L'élément téléchargé est lié à a ce serveur",
|
||||||
"MessageMediaNotLinkedToServer": "Media is not linked to an Audiobookshelf server. No progress will be synced.",
|
"MessageMediaNotLinkedToServer": "L'élément n'est lié à aucun serveur. La progression ne sera pas synchronisé.",
|
||||||
"MessageNoBookmarks": "Aucun signet",
|
"MessageNoBookmarks": "Aucun favoris",
|
||||||
"MessageNoChapters": "Aucun chapitre",
|
"MessageNoChapters": "Aucun chapitre",
|
||||||
"MessageNoItems": "Aucun article",
|
"MessageNoItems": "Aucun élément",
|
||||||
"MessageNoItemsFound": "Aucun article trouvé",
|
"MessageNoItemsFound": "Aucun élément trouvé",
|
||||||
"MessageNoListeningSessions": "Aucune session d’écoute en cours",
|
"MessageNoListeningSessions": "Aucune session d’écoute en cours",
|
||||||
"MessageNoMediaFolders": "No Media Folders",
|
"MessageNoMediaFolders": "Aucun dossier multimédia",
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
"MessageNoNetworkConnection": "Pas de connexion réseau",
|
||||||
"MessageNoPodcastsFound": "Aucun podcast trouvé",
|
"MessageNoPodcastsFound": "Aucun podcast trouvé",
|
||||||
|
"MessageNoSeries": "Aucune série",
|
||||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
||||||
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
||||||
"MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur",
|
"MessageReportBugsAndContribute": "Signalez des anomalies, demandez des fonctionnalités et contribuez sur",
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
"MessageSocketConnectedOverMeteredCellular": "Socket connecté via données cellulaire limitée",
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
"MessageSocketConnectedOverMeteredWifi": "Socket connecté via Wi-Fi limitée",
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
"MessageSocketConnectedOverUnmeteredCellular": "Socket connecté via données cellulaire",
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered wifi",
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket connecté via Wi-Fi",
|
||||||
"MessageSocketNotConnected": "Socket not connected",
|
"MessageSocketNotConnected": "Socket non connecté",
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Attention : la majorité des application de podcast nécessite une adresse de flux en HTTPS.",
|
"NoteRSSFeedPodcastAppsHttps": "Attention : la majorité des application de podcast nécessite une adresse de flux HTTPS",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Attention : un ou plusieurs de vos épisodes ne possèdent pas de date de publication. Certaines applications de podcast le requièrent.",
|
"NoteRSSFeedPodcastAppsPubDate": "Attention : un ou plusieurs de vos épisodes ne possèdent pas de date de publication. Certaines applications de podcast le requièrent.",
|
||||||
"ToastBookmarkCreateFailed": "Échec de la création de signet",
|
"ToastBookmarkCreateFailed": "Échec de la création de marque-page",
|
||||||
"ToastBookmarkRemoveFailed": "Échec de la suppression de signet",
|
"ToastBookmarkRemoveFailed": "Échec de la suppression de marque-page",
|
||||||
"ToastBookmarkUpdateFailed": "Échec de la mise à jour de signet",
|
"ToastBookmarkUpdateFailed": "Échec de la mise à jour de marsue-page",
|
||||||
|
"ToastDownloadNotAllowedOnCellular": "Le téléchargement sur les données mobile n’est pas autorisé",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Échec de l’annotation terminée",
|
"ToastItemMarkedAsFinishedFailed": "Échec de l’annotation terminée",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Échec de l’annotation non-terminée",
|
"ToastItemMarkedAsNotFinishedFailed": "Échec de l’annotation non-terminée",
|
||||||
"ToastPlaylistCreateFailed": "Échec de la création de la liste de lecture",
|
"ToastPlaylistCreateFailed": "Échec de la création de la liste de lecture",
|
||||||
"ToastPodcastCreateFailed": "Échec de la création du Podcast",
|
"ToastPodcastCreateFailed": "Échec de la création du Podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast créé",
|
"ToastPodcastCreateSuccess": "Podcast créé avec succès",
|
||||||
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Flux RSS fermé"
|
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
||||||
|
"ToastStreamingNotAllowedOnCellular": "La diffusion sur les données mobile n’est pas autorisée"
|
||||||
}
|
}
|
||||||
+4
-265
@@ -1,292 +1,31 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "ઉમેરો",
|
"ButtonAdd": "ઉમેરો",
|
||||||
"ButtonAddNewServer": "Add New Server",
|
|
||||||
"ButtonAuthors": "લેખકો",
|
"ButtonAuthors": "લેખકો",
|
||||||
"ButtonBack": "Back",
|
|
||||||
"ButtonCancel": "રદ કરો",
|
"ButtonCancel": "રદ કરો",
|
||||||
"ButtonCancelTimer": "Cancel Timer",
|
"ButtonClearFilter": "ફિલ્ટર જતુ કરો",
|
||||||
"ButtonClearFilter": "ફિલ્ટર જતુ કરો ",
|
|
||||||
"ButtonCloseFeed": "ફીડ બંધ કરો",
|
"ButtonCloseFeed": "ફીડ બંધ કરો",
|
||||||
"ButtonCollections": "સંગ્રહ",
|
"ButtonCollections": "સંગ્રહ",
|
||||||
"ButtonConnect": "Connect",
|
|
||||||
"ButtonConnectToServer": "Connect to Server",
|
|
||||||
"ButtonCreate": "બનાવો",
|
"ButtonCreate": "બનાવો",
|
||||||
"ButtonCreateBookmark": "Create Bookmark",
|
|
||||||
"ButtonCreateNewPlaylist": "Create New Playlist",
|
|
||||||
"ButtonDelete": "કાઢી નાખો",
|
"ButtonDelete": "કાઢી નાખો",
|
||||||
"ButtonDeleteLocalEpisode": "Delete local episode",
|
|
||||||
"ButtonDeleteLocalFile": "Delete local file",
|
|
||||||
"ButtonDeleteLocalItem": "Delete local item",
|
|
||||||
"ButtonDisableAutoTimer": "Disable Auto Timer",
|
|
||||||
"ButtonDisconnect": "Disconnect",
|
|
||||||
"ButtonHistory": "History",
|
|
||||||
"ButtonHome": "ઘર",
|
"ButtonHome": "ઘર",
|
||||||
"ButtonIssues": "સમસ્યાઓ",
|
"ButtonIssues": "સમસ્યાઓ",
|
||||||
"ButtonLatest": "નવીનતમ",
|
"ButtonLatest": "નવીનતમ",
|
||||||
"ButtonLibrary": "પુસ્તકાલય",
|
"ButtonLibrary": "પુસ્તકાલય",
|
||||||
"ButtonLocalMedia": "Local Media",
|
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
|
||||||
"ButtonNewFolder": "New Folder",
|
|
||||||
"ButtonNextEpisode": "Next Episode",
|
|
||||||
"ButtonOpenFeed": "ફીડ ખોલો",
|
"ButtonOpenFeed": "ફીડ ખોલો",
|
||||||
"ButtonOverride": "Override",
|
|
||||||
"ButtonPause": "Pause",
|
|
||||||
"ButtonPlay": "ચલાવો",
|
"ButtonPlay": "ચલાવો",
|
||||||
"ButtonPlaying": "ચલાવી રહ્યું છે",
|
"ButtonPlaying": "ચલાવી રહ્યું છે",
|
||||||
"ButtonPlaylists": "પ્લેલિસ્ટ",
|
"ButtonPlaylists": "પ્લેલિસ્ટ",
|
||||||
"ButtonRead": "વાંચો",
|
"ButtonRead": "વાંચો",
|
||||||
"ButtonRemove": "કાઢી નાખો",
|
"ButtonRemove": "કાઢી નાખો",
|
||||||
"ButtonRemoveFromServer": "Remove from Server",
|
|
||||||
"ButtonSave": "સાચવો",
|
"ButtonSave": "સાચવો",
|
||||||
"ButtonSaveOrder": "Save Order",
|
|
||||||
"ButtonSearch": "શોધો",
|
"ButtonSearch": "શોધો",
|
||||||
"ButtonSendEbookToDevice": "Send Ebook to Device",
|
|
||||||
"ButtonSeries": "સિરીઝ",
|
"ButtonSeries": "સિરીઝ",
|
||||||
"ButtonSetTimer": "Set Timer",
|
|
||||||
"ButtonStream": "Stream",
|
|
||||||
"ButtonSubmit": "સબમિટ કરો",
|
"ButtonSubmit": "સબમિટ કરો",
|
||||||
"ButtonSwitchServerUser": "Switch Server/User",
|
|
||||||
"ButtonUserStats": "User Stats",
|
|
||||||
"ButtonYes": "હા",
|
"ButtonYes": "હા",
|
||||||
"HeaderAccount": "એકાઉન્ટ",
|
"HeaderAccount": "એકાઉન્ટ",
|
||||||
"HeaderAdvanced": "અડ્વાન્સડ",
|
"HeaderAdvanced": "અડ્વાન્સડ",
|
||||||
"HeaderAudioTracks": "Audio Tracks",
|
|
||||||
"HeaderChapters": "Chapters",
|
|
||||||
"HeaderCollection": "Collection",
|
|
||||||
"HeaderCollectionItems": "Collection Items",
|
|
||||||
"HeaderConnectionStatus": "Connection Status",
|
|
||||||
"HeaderDetails": "Details",
|
|
||||||
"HeaderDownloads": "Downloads",
|
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
|
||||||
"HeaderEpisodes": "Episodes",
|
|
||||||
"HeaderEreaderSettings": "Ereader Settings",
|
|
||||||
"HeaderLatestEpisodes": "Latest episodes",
|
|
||||||
"HeaderLibraries": "Libraries",
|
|
||||||
"HeaderLocalFolders": "Local Folders",
|
|
||||||
"HeaderLocalLibraryItems": "Local Library Items",
|
|
||||||
"HeaderNewPlaylist": "New Playlist",
|
|
||||||
"HeaderOpenRSSFeed": "Open RSS Feed",
|
|
||||||
"HeaderPlaybackSettings": "Playback Settings",
|
|
||||||
"HeaderPlaylist": "Playlist",
|
|
||||||
"HeaderPlaylistItems": "Playlist Items",
|
|
||||||
"HeaderRSSFeed": "RSS Feed",
|
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
|
||||||
"HeaderSelectDownloadLocation": "Select Download Location",
|
|
||||||
"HeaderSettings": "Settings",
|
|
||||||
"HeaderSleepTimer": "Sleep Timer",
|
|
||||||
"HeaderSleepTimerSettings": "Sleep Timer Settings",
|
|
||||||
"HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)",
|
|
||||||
"HeaderStatsRecentSessions": "Recent Sessions",
|
|
||||||
"HeaderTableOfContents": "Table of Contents",
|
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
|
||||||
"HeaderYourStats": "Your Stats",
|
|
||||||
"LabelAdded": "Added",
|
|
||||||
"LabelAddedAt": "Added At",
|
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
|
||||||
"LabelAll": "All",
|
|
||||||
"LabelAuthor": "Author",
|
|
||||||
"LabelAuthorFirstLast": "Author (First Last)",
|
|
||||||
"LabelAuthorLastFirst": "Author (Last, First)",
|
|
||||||
"LabelAuthors": "Authors",
|
|
||||||
"LabelAutoDownloadEpisodes": "Auto Download Episodes",
|
|
||||||
"LabelAutoRewindTime": "Auto rewind time",
|
|
||||||
"LabelAutoSleepTimer": "Auto sleep timer",
|
|
||||||
"LabelAutoSleepTimerAutoRewind": "Auto sleep timer auto rewind",
|
|
||||||
"LabelAutoSleepTimerAutoRewindHelp": "When the auto sleep timer finishes, playing the item again will automatically rewind your position.",
|
|
||||||
"LabelAutoSleepTimerHelp": "When playing media between the specified start and end times a sleep timer will automatically start.",
|
|
||||||
"LabelBooks": "Books",
|
|
||||||
"LabelChapters": "Chapters",
|
|
||||||
"LabelChapterTrack": "Chapter Track",
|
|
||||||
"LabelClosePlayer": "Close player",
|
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
|
||||||
"LabelComplete": "Complete",
|
|
||||||
"LabelContinueBooks": "Continue Books",
|
|
||||||
"LabelContinueEpisodes": "Continue Episodes",
|
|
||||||
"LabelContinueListening": "Continue Listening",
|
|
||||||
"LabelContinueReading": "Continue Reading",
|
|
||||||
"LabelContinueSeries": "Continue Series",
|
|
||||||
"LabelCustomTime": "Custom time",
|
|
||||||
"LabelDescription": "Description",
|
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
|
||||||
"LabelDisableAutoRewind": "Disable auto rewind",
|
|
||||||
"LabelDisableShakeToReset": "Disable shake to reset",
|
|
||||||
"LabelDisableShakeToResetHelp": "Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.",
|
|
||||||
"LabelDisableVibrateOnReset": "Disable vibrate on reset",
|
|
||||||
"LabelDisableVibrateOnResetHelp": "When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.",
|
|
||||||
"LabelDiscover": "Discover",
|
|
||||||
"LabelDownload": "Download",
|
|
||||||
"LabelDownloaded": "Downloaded",
|
|
||||||
"LabelDuration": "Duration",
|
|
||||||
"LabelEbook": "Ebook",
|
|
||||||
"LabelEbooks": "Ebooks",
|
|
||||||
"LabelEnable": "Enable",
|
|
||||||
"LabelEnableMp3IndexSeeking": "Enable mp3 index seeking",
|
|
||||||
"LabelEnableMp3IndexSeekingHelp": "This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.",
|
|
||||||
"LabelEnd": "End",
|
|
||||||
"LabelEndOfChapter": "End of Chapter",
|
|
||||||
"LabelEndTime": "End time",
|
|
||||||
"LabelEpisode": "Episode",
|
|
||||||
"LabelFeedURL": "Feed URL",
|
|
||||||
"LabelFile": "File",
|
|
||||||
"LabelFileBirthtime": "File Birthtime",
|
|
||||||
"LabelFileModified": "File Modified",
|
|
||||||
"LabelFilename": "Filename",
|
|
||||||
"LabelFinished": "Finished",
|
|
||||||
"LabelFolder": "Folder",
|
|
||||||
"LabelFontScale": "Font scale",
|
|
||||||
"LabelGenre": "Genre",
|
|
||||||
"LabelGenres": "Genres",
|
|
||||||
"LabelHapticFeedback": "Haptic feedback",
|
|
||||||
"LabelHasEbook": "Has ebook",
|
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
|
||||||
"LabelHeavy": "Heavy",
|
|
||||||
"LabelHigh": "High",
|
|
||||||
"LabelHost": "Host",
|
|
||||||
"LabelIncomplete": "Incomplete",
|
|
||||||
"LabelInProgress": "In Progress",
|
|
||||||
"LabelInternalAppStorage": "Internal App Storage",
|
|
||||||
"LabelJumpBackwardsTime": "Jump backwards time",
|
|
||||||
"LabelJumpForwardsTime": "Jump forwards time",
|
|
||||||
"LabelLanguage": "Language",
|
|
||||||
"LabelLayout": "Layout",
|
|
||||||
"LabelLayoutAuto": "Auto",
|
|
||||||
"LabelLayoutSinglePage": "Single page",
|
|
||||||
"LabelLight": "Light",
|
|
||||||
"LabelLineSpacing": "Line spacing",
|
|
||||||
"LabelListenAgain": "Listen Again",
|
|
||||||
"LabelLocalBooks": "Local Books",
|
|
||||||
"LabelLocalPodcasts": "Local Podcasts",
|
|
||||||
"LabelLockOrientation": "Lock orientation",
|
|
||||||
"LabelLockPlayer": "Lock Player",
|
|
||||||
"LabelLow": "Low",
|
|
||||||
"LabelMediaType": "Media Type",
|
|
||||||
"LabelMedium": "Medium",
|
|
||||||
"LabelMore": "More",
|
|
||||||
"LabelMoreInfo": "More Info",
|
|
||||||
"LabelName": "Name",
|
|
||||||
"LabelNarrator": "Narrator",
|
|
||||||
"LabelNarrators": "Narrators",
|
|
||||||
"LabelNewestAuthors": "Newest Authors",
|
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
|
||||||
"LabelNo": "No",
|
|
||||||
"LabelNotFinished": "Not Finished",
|
|
||||||
"LabelNotStarted": "Not Started",
|
|
||||||
"LabelOff": "Off",
|
|
||||||
"LabelPassword": "Password",
|
|
||||||
"LabelPath": "Path",
|
|
||||||
"LabelPlaybackDirect": "Direct",
|
|
||||||
"LabelPlaybackLocal": "Local",
|
|
||||||
"LabelPlaybackSpeed": "Playback Speed",
|
|
||||||
"LabelPlaybackTranscode": "Transcode",
|
|
||||||
"LabelPodcast": "Podcast",
|
|
||||||
"LabelPodcasts": "Podcasts",
|
|
||||||
"LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories",
|
|
||||||
"LabelProgress": "Progress",
|
|
||||||
"LabelPubDate": "Pub Date",
|
|
||||||
"LabelPublishYear": "Publish Year",
|
|
||||||
"LabelRead": "Read",
|
|
||||||
"LabelReadAgain": "Read Again",
|
|
||||||
"LabelRecentlyAdded": "Recently Added",
|
|
||||||
"LabelRecentSeries": "Recent Series",
|
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
|
|
||||||
"LabelRSSFeedCustomOwnerName": "Custom owner Name",
|
|
||||||
"LabelRSSFeedPreventIndexing": "Prevent Indexing",
|
|
||||||
"LabelRSSFeedSlug": "RSS Feed Slug",
|
|
||||||
"LabelScaleElapsedTimeBySpeed": "Scale Elapsed Time by Speed",
|
|
||||||
"LabelSeason": "Season",
|
|
||||||
"LabelSelectADevice": "Select a device",
|
|
||||||
"LabelSeries": "Series",
|
|
||||||
"LabelServerAddress": "Server address",
|
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
|
||||||
"LabelShakeSensitivity": "Shake sensitivity",
|
|
||||||
"LabelShowAll": "Show All",
|
|
||||||
"LabelSize": "Size",
|
|
||||||
"LabelSleepTimer": "Sleep timer",
|
|
||||||
"LabelStart": "Start",
|
|
||||||
"LabelStartTime": "Start Time",
|
"LabelStartTime": "Start Time",
|
||||||
"LabelStatsBestDay": "Best Day",
|
"MessageFollowTheProjectOnGithub": "Follow the project on GitHub",
|
||||||
"LabelStatsDailyAverage": "Daily Average",
|
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered Wi-Fi",
|
||||||
"LabelStatsDays": "Days",
|
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered Wi-Fi"
|
||||||
"LabelStatsDaysListened": "Days Listened",
|
|
||||||
"LabelStatsInARow": "in a row",
|
|
||||||
"LabelStatsItemsFinished": "Items Finished",
|
|
||||||
"LabelStatsMinutes": "minutes",
|
|
||||||
"LabelStatsMinutesListening": "Minutes Listening",
|
|
||||||
"LabelStatsWeekListening": "Week Listening",
|
|
||||||
"LabelTag": "Tag",
|
|
||||||
"LabelTags": "Tags",
|
|
||||||
"LabelTheme": "Theme",
|
|
||||||
"LabelThemeDark": "Dark",
|
|
||||||
"LabelThemeLight": "Light",
|
|
||||||
"LabelTimeRemaining": "{0} remaining",
|
|
||||||
"LabelTitle": "Title",
|
|
||||||
"LabelTotalTrack": "Total Track",
|
|
||||||
"LabelTracks": "Tracks",
|
|
||||||
"LabelType": "Type",
|
|
||||||
"LabelUnlockPlayer": "Unlock Player",
|
|
||||||
"LabelUseBookshelfView": "Use bookshelf view",
|
|
||||||
"LabelUser": "User",
|
|
||||||
"LabelUsername": "Username",
|
|
||||||
"LabelVeryHigh": "Very High",
|
|
||||||
"LabelVeryLow": "Very Low",
|
|
||||||
"LabelYourBookmarks": "Your Bookmarks",
|
|
||||||
"LabelYourProgress": "Your Progress",
|
|
||||||
"MessageAndroid10Downloads": "Android 10 and below will use internal app storage for downloads.",
|
|
||||||
"MessageAttemptingServerConnection": "Attempting server connection...",
|
|
||||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf server not connected",
|
|
||||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> This app is designed to work with an Audiobookshelf server that you or someone you know is hosting. This app does not provide any content.",
|
|
||||||
"MessageBookshelfEmpty": "Bookshelf empty",
|
|
||||||
"MessageConfirmDeleteLocalEpisode": "Remove local episode \"{0}\" from your device? The file on the server will be unaffected.",
|
|
||||||
"MessageConfirmDeleteLocalFiles": "Remove local files of this item from your device? The files on the server and your progress will be unaffected.",
|
|
||||||
"MessageConfirmDiscardProgress": "Are you sure you want to reset your progress?",
|
|
||||||
"MessageConfirmMarkAsFinished": "Are you sure you want to mark this item as finished?",
|
|
||||||
"MessageConfirmRemoveBookmark": "Are you sure you want to remove bookmark?",
|
|
||||||
"MessageDiscardProgress": "Discard Progress",
|
|
||||||
"MessageDownloadCompleteProcessing": "Download complete. Processing...",
|
|
||||||
"MessageDownloading": "Downloading...",
|
|
||||||
"MessageDownloadingEpisode": "Downloading episode",
|
|
||||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
|
||||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
|
||||||
"MessageFetching": "Fetching...",
|
|
||||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
|
||||||
"MessageItemDownloadCompleteFailedToCreate": "Item download complete but failed to create library item",
|
|
||||||
"MessageLoading": "Loading...",
|
|
||||||
"MessageLoadingServerData": "Loading server data...",
|
|
||||||
"MessageMarkAsFinished": "Mark as Finished",
|
|
||||||
"MessageMediaLinkedToADifferentServer": "Media is linked to an Audiobookshelf server on a different address ({0}). Progress will be synced when connected to this server address.",
|
|
||||||
"MessageMediaLinkedToADifferentUser": "Media is linked to this server but was downloaded by a different user. Progress will only be synced to the user that downloaded it.",
|
|
||||||
"MessageMediaLinkedToServer": "Linked to server {0}",
|
|
||||||
"MessageMediaLinkedToThisServer": "Downloaded media is linked to this server",
|
|
||||||
"MessageMediaNotLinkedToServer": "Media is not linked to an Audiobookshelf server. No progress will be synced.",
|
|
||||||
"MessageNoBookmarks": "No Bookmarks",
|
|
||||||
"MessageNoChapters": "No Chapters",
|
|
||||||
"MessageNoItems": "No Items",
|
|
||||||
"MessageNoItemsFound": "No items found",
|
|
||||||
"MessageNoListeningSessions": "No Listening Sessions",
|
|
||||||
"MessageNoMediaFolders": "No Media Folders",
|
|
||||||
"MessageNoNetworkConnection": "No network connection",
|
|
||||||
"MessageNoPodcastsFound": "No podcasts found",
|
|
||||||
"MessageNoUpdatesWereNecessary": "No updates were necessary",
|
|
||||||
"MessageNoUserPlaylists": "You have no playlists",
|
|
||||||
"MessageReportBugsAndContribute": "Report bugs, request features, and contribute on",
|
|
||||||
"MessageSocketConnectedOverMeteredCellular": "Socket connected over metered cellular",
|
|
||||||
"MessageSocketConnectedOverMeteredWifi": "Socket connected over metered wifi",
|
|
||||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket connected over unmetered cellular",
|
|
||||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket connected over unmetered wifi",
|
|
||||||
"MessageSocketNotConnected": "Socket not connected",
|
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Warning: Most podcast apps will require the RSS feed URL is using HTTPS",
|
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Warning: 1 or more of your episodes do not have a Pub Date. Some podcast apps require this.",
|
|
||||||
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
|
||||||
"ToastBookmarkRemoveFailed": "Failed to remove bookmark",
|
|
||||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
|
||||||
"ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
|
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
|
|
||||||
"ToastPlaylistCreateFailed": "Failed to create playlist",
|
|
||||||
"ToastPodcastCreateFailed": "Failed to create podcast",
|
|
||||||
"ToastPodcastCreateSuccess": "Podcast created successfully",
|
|
||||||
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
|
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed closed"
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user