1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-04-23 03:03:18 +00:00

Compare commits

...

151 Commits
v0.27.3 ... dev

Author SHA1 Message Date
Stypox
276bf390b2
Merge pull request #12117 from malania02/dev
Show download date of downloaded videos
2025-04-11 20:17:27 +02:00
malania02
f39eda086f
Fix for overlapping 2025-04-09 23:40:14 +02:00
Stypox
756327da39
Merge pull request #12093 from mileskrell/mileskrell/support-per-app-language-preferences
Support per-app language preferences
2025-04-08 23:13:07 +02:00
Stypox
5840d3a437
Merge pull request #12150 from FineFindus/fix/potoken-index
[YouTube] Access first element if array size is one
2025-04-08 23:06:04 +02:00
FineFindus
e1dedd45ed
[YouTube] Access first element if array size is one
Fixes a regression, where if the challenge data array size was one, the second element
would be accessed, leading to a crash.
This was introduced when porting the challenge parsing from JS to
Kotlin.

Ref: 53b599b042
2025-04-02 22:14:01 +02:00
malania02
912f07a1dd
Missing lines added 2025-03-30 14:50:05 +02:00
Miles Krell
205466c56a Move call to setApplicationLocales 2025-03-27 19:14:41 -04:00
Miles Krell
7f10312d0a Move migration to NewPipeSettings 2025-03-23 17:39:21 -04:00
malania02
63be3220e7
Show download date 2025-03-22 16:19:26 +01:00
malania02
536b78f2e6
textview for download date added 2025-03-22 16:13:45 +01:00
malania02
6d6b73ef73
textview for download date added 2025-03-22 16:09:58 +01:00
Stypox
196c27792b
Merge pull request #12044 from TeamNewPipe/android-auto
Add support for Android Auto *(season 2)*
2025-03-21 11:21:58 +01:00
Stypox
b3789315ad
Merge pull request #12104 from TeamNewPipe/update-npe
Update NewPipe Extractor and add new proguard rules
2025-03-21 10:52:37 +01:00
Miles Krell
c7bf498c04 Don't show toast because of changing content language or country 2025-03-16 20:27:05 -04:00
Miles Krell
35abb99dac Only show toast on Android <13 2025-03-16 20:15:38 -04:00
Miles Krell
70416e73f3 Move app language setting migration to SettingMigrations 2025-03-16 19:24:04 -04:00
TobiGr
a0b76c3385 Update NewPipe Extractor and add new proguard rules
New rules are required since Rhino and Rhino Engine 1.8.0
2025-03-16 22:08:10 +01:00
Tobi
c232193a46
Merge pull request #12083 from har-123/bugfix/11894_fix_duplicate_menu_options
Fix duplicate menu options in ChannelFragment
2025-03-16 10:34:52 +01:00
Siddhesh Naik
f289bea6b3 Fix sonar warning 2025-03-16 12:44:05 +05:30
Harshita
48b200868a BF-11894 : Fix the menu disappearing on performing backGesture 2025-03-16 12:44:05 +05:30
Harshita
54bf7f0ced BF-11894 : Fix the Duplicate menu options in ChannelFragment 2025-03-16 12:44:05 +05:30
Miles Krell
980a35a708 Move migration to separate method 2025-03-15 23:00:31 -04:00
Miles Krell
da106e2361 Don't try to migrate "system" app language 2025-03-15 22:54:17 -04:00
Miles Krell
3532ac96b4 Migrate from pre-Android 13 app language pref 2025-03-15 22:13:01 -04:00
Miles Krell
87693a2ad1 Redirect to per-app language settings on Android 13+ 2025-03-15 21:56:02 -04:00
Hosted Weblate
d321e57620 Translated using Weblate (Czech)
Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Catalan)

Currently translated at 88.2% (653 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 90.4% (76 of 84 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Croatian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hebrew)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Greek)

Currently translated at 25.0% (21 of 84 strings)

Translated using Weblate (Greek)

Currently translated at 23.8% (20 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 99.5% (737 of 740 strings)

Co-authored-by: 439JBYL80IGQTF25UXNR0X1BG <439JBYL80IGQTF25UXNR0X1BG@users.noreply.hosted.weblate.org>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Antonin Del Fabbro <message@antonin.one>
Co-authored-by: Christian Eichert <c@zp1.net>
Co-authored-by: Drugi Sapog <dindrugi@users.noreply.hosted.weblate.org>
Co-authored-by: Eduardo Calixto <eduardogubertcalixto@gmail.com>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jan Layola <gilajan@protonmail.com>
Co-authored-by: Kevin Wang <wmk153024@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Petr Kadlec <mormegil@centrum.cz>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Sergio Marques <so.boston.android@gmail.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: XxVictoriaxX <evakonoob@gmail.com>
Co-authored-by: Yaron Shahrabani <sh.yaron@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: whistlingwoods <72640314+whistlingwoods@users.noreply.github.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/el/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pa/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/
Translation: NewPipe/Metadata
2025-03-15 17:43:36 +01:00
Tobi
fb4a65a14a
Merge pull request #12043 from TeamNewPipe/hide-view-logs
Disable logs about view animations by default
2025-03-15 17:17:59 +01:00
Stypox
3047704e1c
Merge pull request #12089 from mileskrell/mileskrell/fix-audio-track-labels
Disambiguate audio track labels
2025-03-15 12:45:20 +01:00
Stypox
3dcfdaf510
Merge pull request #12065 from tfga/YouTubeTemporaryPlaylist
Share as YouTube temporary playlist
2025-03-15 10:11:59 +01:00
Thiago F. G. Albuquerque
2ceb70236e sharePlaylist(): converting javadoc from Markdown back to "classic javadoc"
(request from @Stypox)
2025-03-14 21:56:42 -03:00
Thiago F. G. Albuquerque
be097f26c8 Deleting the "explanatory text" bellow the title
<string name="share_playlist_with_titles_message">Share playlist with details such as playlist name and video titles or as a simple list of video URLs</string>
    Share playlist with details such as playlist name and video titles or as a simple list of video URLs</string>

(Discussion: https://github.com/TeamNewPipe/NewPipe/pull/12065#discussion_r1994349485)
2025-03-13 19:10:26 -03:00
Thiago F. G. Albuquerque
098f60d593 Don't add the title when sharing as YouTube temp playlist 2025-03-13 18:16:09 -03:00
Thiago F. G. Albuquerque
eb0568044a R.string.share_playlist_as_youtube_temporary_playlist: pt-BR
+ Minor fixes to related translations
2025-03-12 19:09:31 -03:00
Thiago F. G. Albuquerque
f3b3d5c3e7 R.string.share_playlist_as_youtube_temporary_playlist 2025-03-12 19:08:09 -03:00
Miles Krell
b888dc72cf Support per-app language preferences 2025-03-11 23:29:23 -04:00
Thiago F. G. Albuquerque
599d86151a Making ktLint happy 2025-03-11 21:26:58 -03:00
tfga
587df093ea
YouTube video IDs are 11 characters long
Co-authored-by: Stypox <stypox@pm.me>
2025-03-11 20:35:41 -03:00
tfga
8830e87242
YouTube video IDs are 11 characters long
Co-authored-by: Stypox <stypox@pm.me>
2025-03-11 20:35:18 -03:00
Thiago F. G. Albuquerque
f96b8f7b2a Comment: maximum length of 50 items
(PR review from @Stypox)
2025-03-11 20:19:54 -03:00
Thiago F. G. Albuquerque
c28478ae53 getYouTubeId(): Changing implementation to use YoutubeStreamLinkHandler
(PR review from @Stypox)
2025-03-11 20:12:25 -03:00
Miles Krell
10110397fd Use display name instead of only the language 2025-03-10 22:01:09 -04:00
tfga
d81244e77c
YT temp playlist URL: http => https
Co-authored-by: Stypox <stypox@pm.me>
2025-03-10 19:11:20 -03:00
Stypox
ea20ca9e72
Merge pull request #12067 from Isira-Seneviratne/Fix-notification-grouping
Fix stream notification grouping
2025-02-28 11:51:11 +01:00
Isira Seneviratne
f0c89494dd Fix stream notification grouping 2025-02-27 09:15:40 +05:30
Thiago F. G. Albuquerque
0fd2d4fed6 [#11930] Removing Apache Commons Collections
It's no longer needed after the conversion to Kotlin.
2025-02-26 21:29:48 -03:00
Thiago F. G. Albuquerque
3c7b026d7d [#11930] Updating javadoc 2025-02-25 20:23:07 -03:00
Thiago F. G. Albuquerque
998d84de6c [#11930] Converting to Kotlin 2025-02-25 18:56:12 -03:00
Thiago F. G. Albuquerque
76a02d5858 [#11930] Extracting to a separate file 2025-02-24 20:16:40 -03:00
Thiago F. G. Albuquerque
24bb71a23f [#11930] Making it more efficient: Reverse iteration + limit(50) + reverse 2025-02-24 19:22:36 -03:00
Stypox
49b71942ad
Fix style and add comment about null player 2025-02-24 14:21:05 +01:00
Thompson3142
c9ec257a5e
Ugly fix for broken text colors in dark mode (#12035)
* Ugly fix for broken text colors in dark mode

* Add comment for clarification

* Added error prevention

* Update app/src/main/java/org/schabi/newpipe/MainActivity.java

---------

Co-authored-by: Stypox <stypox@pm.me>
2025-02-21 09:38:58 +00:00
Thiago F. G. Albuquerque
b1f995a78c [#11930] Playlist with more than 50 items 2025-02-20 16:26:03 -03:00
Thiago F. G. Albuquerque
acac50a1d1 [#11930] Non-Youtube URLs should be ignored 2025-02-19 16:29:34 -03:00
Thiago F. G. Albuquerque
c6b87cd316 [#11930] Making CheckStyle happy 2025-02-18 20:59:13 -03:00
Thiago F. G. Albuquerque
94d4c21cc7 [#11930] @Test export_justUrls() 2025-02-18 17:47:22 -03:00
Stypox
a7a7dc5363
Handle player and player service separately
This is, again, a consequence of the commit "Drop some assumptions on how PlayerService is started and reused".
This commit notified VideoDetailFragment of player starting and stopping independently of the player.
Read the comments in the code changes for more information.
2025-02-18 19:27:46 +01:00
Stypox
126f4b0e30
Fix crash when closing video detail fragment
This bug started appearing because the way to close the player is now unified in PlayerHolder.stopService(), which causes the player to reach back to the video detail fragment with a notification of the shutdown (i.e. onServiceStopped() is called). This is fixed by adding a nullability check on the binding.
2025-02-18 18:03:10 +01:00
Stypox
6558794d26
Try to bind to PlayerService when MainActivity starts
Fixes mini-player not appearing on app start if the player service is already playing something.

The PlayerService (and the player) may be started from an external intent that does not involve the MainActivity (e.g. RouterActivity or Android Auto's media browser interface).
This PR tries to bind to the PlayerService as soon as the MainActivity starts, but only does so in a passive way, i.e. if the service is not already running it is not started.
Once the connection between PlayerHolder and PlayerService is setup, the ACTION_PLAYER_STARTED broadcast is sent to MainActivity so that it can setup the bottom mini-player.
Another important thing this commit does is to check whether the player is open before actually adding the mini-player view, since the PlayerService could be bound even without a running player (e.g. Android Auto's media browser is being used). This is a consequence of commit "Drop some assumptions on how PlayerService is started and reused".
2025-02-18 17:49:38 +01:00
Stypox
1d12874937
Merge pull request #12046 from TobiGr/weblate
Update translations
2025-02-16 22:01:41 +01:00
Stypox
1d98518bfa
Fix loading remote playlists in media browser 2025-02-16 21:44:50 +01:00
Stypox
e5458bcb14
Properly handle item errors during media browser loading
Non-item errors, i.e. critical parsing errors of the page, are still handled properly.
2025-02-16 21:44:50 +01:00
Stypox
dc62d211f5
Properly stop PlayerService
This commit is a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". Since the assumptions on how the PlayerService is started and reused have changed, we also need to adapt the way it is stopped. This means allowing the service to remain alive even after the player is destroyed, in case the system is still accessing PlayerService e.g. through the media browser interface. The foreground service needs to be stopped and the notification removed in any case.
2025-02-16 21:44:49 +01:00
Stypox
ec6612dd71
Call exoPlayer.prepare() on PlaybackPreparer.onPrepare()
If a playbackPreparer is set, then instead of calling `player.prepare()`, the MediaSessionConnector will call `playbackPreparer.onPrepare(true)` instead, as seen below.
This commit makes it so that playbackPreparer.onPrepare(true) restores the original behavior of just calling player.prepare().

From MediaSessionConnector -> MediaSessionCompat.Callback implementation:
```java
    @Override
    public void onPlay() {
      if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
        if (player.getPlaybackState() == Player.STATE_IDLE) {
          if (playbackPreparer != null) {
            playbackPreparer.onPrepare(/* playWhenReady= */ true);
          } else {
            player.prepare();
          }
        } else if (player.getPlaybackState() == Player.STATE_ENDED) {
          seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET);
        }
        Assertions.checkNotNull(player).play();
      }
    }
```
2025-02-16 21:44:49 +01:00
Stypox
064e1d39c7
Use the media browser implementation in PlayerService
Now the media browser queries are replied to by MediaBrowserImpl

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:44:05 +01:00
Stypox
4c88a193bd
Add MediaBrowserImpl
This class implements the media browser service interface as a standalone class for clearer separation of concerns (otherwise everything would need to go in PlayerService, since PlayerService overrides MediaBrowserServiceCompat)

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
Co-authored-by: Profpatsch <mail@profpatsch.de>
2025-02-16 21:43:46 +01:00
Stypox
3fcac10e7f
Add MediaBrowserPlaybackPreparer
This class will receive the media URLs generated by [MediaBrowserImpl] and will start playback of the corresponding streams or playlists.

Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
Co-authored-by: Profpatsch <mail@profpatsch.de>
2025-02-16 21:43:35 +01:00
Stypox
6cedd117fe
Add StreamHistoryEntry.toStreamInfoItem()
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:55 +01:00
Stypox
5eabcb52b5
Add getThumbnailUrl() to PlaylistLocalItem interface
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:48 +01:00
Stypox
690b40d0c4
Allow creating PlayQueue from ListInfo and index 2025-02-16 21:40:47 +01:00
Stypox
9bb2c0b484
Add getPlaylist(id) to RemotePlaylistManager
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:36 +01:00
Stypox
1e08cc8c8f
Add MediaBrowserCommon with info item's and pages' IDs
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:40:29 +01:00
Stypox
7d17468266
Instantiate media session and connector in PlayerService
This changes significantly how the MediaSessionCompat and MediaSessionConnector objects are used:
- now they are tied to the service and not to the player, and so they might be reused with multiple players (which should be allowed)
- now they can exist even if there is no player (which is fundamental to be able to answer media browser queries)
2025-02-16 21:40:13 +01:00
Stypox
5819546ea9
Have PlayerService implement MediaBrowserServiceCompat
Co-authored-by: Haggai Eran <haggai.eran@gmail.com>
2025-02-16 21:36:59 +01:00
Stypox
cfb6e114d6
Disable logs about view animations by default 2025-02-16 10:31:42 +01:00
Stypox
b764ad33c4
Drop some assumptions on how PlayerService is started and reused
Read the comments in the lines changed to understand more
2025-02-15 17:48:19 +01:00
Hosted Weblate
430b4eb916
Translated using Weblate (Persian)
Currently translated at 92.7% (686 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 83.3% (70 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 16.6% (14 of 84 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Mainfränkisch)

Currently translated at 1.0% (8 of 740 strings)

Translated using Weblate (Bavarian)

Currently translated at 3.9% (29 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (84 of 84 strings)

Added translation using Weblate (Mainfränkisch)

Translated using Weblate (Thai)

Currently translated at 36.6% (271 of 740 strings)

Translated using Weblate (Armenian)

Currently translated at 28.2% (209 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 85.7% (72 of 84 strings)

Translated using Weblate (Thai)

Currently translated at 34.3% (254 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.3% (84 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Nepali)

Currently translated at 1.1% (1 of 84 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (French)

Currently translated at 100.0% (84 of 84 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.0% (82 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Bruno Fragoso <darth_signa@hotmail.com>
Co-authored-by: Davit Mayilyan <davit.mayilyan@protonmail.ch>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Garfield2150 <knd.garfield@gmail.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Goudarz Jafari <goudarz.jafari@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kchenik Poudel <Kakapoudel7@gmail.com>
Co-authored-by: Kuko <kuko7@protonmail.ch>
Co-authored-by: Paul Sibila <p.aul@mail.de>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: freddyLovesUs <compteperso@outlook.com>
Co-authored-by: રાજ ભાતેલીઆ <rajbhatelia@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ka/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ne/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translation: NewPipe/Metadata
2025-02-15 13:08:00 +01:00
Thiago F. G. Albuquerque
2339f51ad4 [#11930] Share as YouTube temporary playlist
Initial commit.
2025-02-14 21:14:42 -03:00
Stypox
c6e1721884
Add translated changelogs for v0.27.6 (1003)
Copied from 1002.txt
2025-02-05 11:30:37 +01:00
Stypox
94684fe380
Merge branch 'weblate-dev' into dev 2025-02-05 11:29:14 +01:00
Hosted Weblate
398a2f55ce
Merge branch 'origin/dev' into Weblate. 2025-02-05 11:28:09 +01:00
Stypox
1f7b3b5b06
Add changelog for v0.27.6 (1003) 2025-02-05 11:25:58 +01:00
Stypox
909ed616c4
Hotfix release v0.27.6 (1003) 2025-02-05 11:14:17 +01:00
Stypox
dd223af28d
Merge pull request #11955 from Stypox/po-token
[YouTube] Add support for poTokens
2025-02-05 10:52:16 +01:00
Stypox
dbee8d8128
Update NewPipeExtractor to v0.24.5
Using commit 9f83b385a since JitPack is buggy...
2025-02-05 10:24:34 +01:00
Stypox
b62a09b5b3
Use WebSettingsCompat.setSafeBrowsingEnabled 2025-02-04 21:50:10 +01:00
Stypox
87317c6faf
Reorder functions in PoTokenWebView 2025-02-04 21:38:01 +01:00
Stypox
53b599b042
Make JavaScript code compatible with older WebViews 2025-02-04 21:38:01 +01:00
Stypox
21df24abfd
Detect when WebView is broken and return null poToken
Some old Android devices have a broken WebView implementation, that can't execute the poToken code. This is now detected and the getWebClientPoToken return null instead of throwing an error in such a case, to allow the extractor to try to extract the video data even without a poToken.
2025-02-04 11:22:50 +01:00
Hosted Weblate
ca4592a935
Translated using Weblate (Russian)
Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 13.2% (11 of 83 strings)

Translated using Weblate (Latin)

Currently translated at 8.6% (64 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Turkish)

Currently translated at 48.1% (40 of 83 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 96.3% (80 of 83 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Vietnamese)

Currently translated at 78.3% (65 of 83 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (German)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (83 of 83 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (ryu (generated) (ryu))

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (N’Ko)

Currently translated at 89.4% (662 of 740 strings)

Translated using Weblate (Norwegian Nynorsk)

Currently translated at 19.8% (147 of 740 strings)

Translated using Weblate (Georgian)

Currently translated at 89.1% (660 of 740 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.0% (733 of 740 strings)

Translated using Weblate (Kurdish (Northern))

Currently translated at 65.4% (484 of 740 strings)

Translated using Weblate (Somali)

Currently translated at 75.1% (556 of 740 strings)

Translated using Weblate (Uzbek (Latin script))

Currently translated at 62.0% (459 of 740 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 7.4% (55 of 740 strings)

Translated using Weblate (Odia)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Santali)

Currently translated at 14.5% (108 of 740 strings)

Translated using Weblate (Bengali)

Currently translated at 76.7% (568 of 740 strings)

Translated using Weblate (Sardinian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Bengali (India))

Currently translated at 40.1% (297 of 740 strings)

Translated using Weblate (Kurdish (Central))

Currently translated at 84.0% (622 of 740 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 97.7% (723 of 740 strings)

Translated using Weblate (Malayalam)

Currently translated at 76.4% (566 of 740 strings)

Translated using Weblate (Interlingua)

Currently translated at 32.2% (239 of 740 strings)

Translated using Weblate (Filipino)

Currently translated at 31.3% (232 of 740 strings)

Translated using Weblate (Thai)

Currently translated at 30.0% (222 of 740 strings)

Translated using Weblate (Nepali)

Currently translated at 59.0% (437 of 740 strings)

Translated using Weblate (Danish)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Galician)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Malay)

Currently translated at 57.9% (429 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.0% (696 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Punjabi)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Albanian)

Currently translated at 79.8% (591 of 740 strings)

Translated using Weblate (Dutch (Belgium))

Currently translated at 75.1% (556 of 740 strings)

Translated using Weblate (Urdu)

Currently translated at 68.2% (505 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Catalan)

Currently translated at 87.0% (644 of 740 strings)

Translated using Weblate (Kurdish)

Currently translated at 63.7% (472 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Telugu)

Currently translated at 58.1% (430 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Finnish)

Currently translated at 97.9% (725 of 740 strings)

Translated using Weblate (Croatian)

Currently translated at 98.9% (732 of 740 strings)

Translated using Weblate (Vietnamese)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Lithuanian)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Hebrew)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 54.3% (402 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Asturian)

Currently translated at 63.3% (469 of 740 strings)

Translated using Weblate (Persian)

Currently translated at 92.4% (684 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.4% (529 of 740 strings)

Translated using Weblate (Slovak)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Romanian)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script, Hong Kong))

Currently translated at 99.3% (735 of 740 strings)

Translated using Weblate (Basque)

Currently translated at 99.8% (739 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (Korean)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Japanese)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Dutch)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 99.7% (738 of 740 strings)

Translated using Weblate (German)

Currently translated at 99.5% (737 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Macedonian)

Currently translated at 6.0% (5 of 82 strings)

Translated using Weblate (Macedonian)

Currently translated at 80.6% (597 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (French)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Esperanto)

Currently translated at 71.7% (531 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Kabyle)

Currently translated at 28.7% (213 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 12.1% (10 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 11.0% (82 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 10.9% (9 of 82 strings)

Translated using Weblate (Danish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 98.7% (81 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (French)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Undetermined)

Currently translated at 2.4% (2 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Chinese (Traditional Han script, Hong Kong))

Currently translated at 28.0% (23 of 82 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Gujarati)

Currently translated at 9.4% (70 of 740 strings)

Translated using Weblate (Estonian)

Currently translated at 9.7% (8 of 82 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Hungarian)

Currently translated at 74.3% (61 of 82 strings)

Translated using Weblate (Russian)

Currently translated at 98.7% (81 of 82 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 64.6% (53 of 82 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Russian)

Currently translated at 97.5% (80 of 82 strings)

Translated using Weblate (German)

Currently translated at 100.0% (82 of 82 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Alex25820 <alexs25820@gmail.com>
Co-authored-by: Andrey F <firsan777@mail.ru>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Anthony Romero <dagazcii@gmail.com>
Co-authored-by: ButterflyOfFire <boffire@users.noreply.hosted.weblate.org>
Co-authored-by: Bảo Nam (Namm) <namb20994@gmail.com>
Co-authored-by: C. Rüdinger <Mail-an-CR@web.de>
Co-authored-by: Ding User <dengus@users.noreply.hosted.weblate.org>
Co-authored-by: Emin Tufan Çetin <etcetin@gmail.com>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: GeoCup <geokapaniaris@gmail.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Sorocean <sorocean.igor@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Jaidyn Ann <jadedctrl@posteo.net>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Massimo Pissarello <mapi68@gmail.com>
Co-authored-by: MatthieuPh <matthieu.philippe@protonmail.com>
Co-authored-by: Mickaël Binos <mickaelbinos@outlook.com>
Co-authored-by: Miguel <mp0187595@tutamail.com>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Nicolas SALMIERI <1salmieri.nicolas@gmail.com>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
Co-authored-by: Philip Goto <philip.goto@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Random <random-r@users.noreply.hosted.weblate.org>
Co-authored-by: Rex_sa <rex.sa@pm.me>
Co-authored-by: Rijolo <rijolo4790@gholar.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Szia Tomi <sziatomi01@gmail.com>
Co-authored-by: TobiGr <TobiGr@users.noreply.github.com>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: VfBFan <drop0815@posteo.de>
Co-authored-by: VisionR1 <25982450+VisionR1@users.noreply.github.com>
Co-authored-by: Vtrytobe <vtrytobe@gmail.com>
Co-authored-by: cat <catsnote@proton.me>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gfbdrgng <hnaofegnp@hldrive.com>
Co-authored-by: hajayad577 <hajayad577@numerobo.com>
Co-authored-by: jpkaster 77 <jpkaster81@gmail.com>
Co-authored-by: polarwood <wreckfitzgerald@proton.me>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: yummysheepouo <jerry88182821@gmail.com>
Co-authored-by: zmni <zmni@outlook.com>
Co-authored-by: Валентин Барсуков <valikbars04@gmail.com>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: મેબીરાજ <rajbhatelia@gmail.com>
Co-authored-by: રાજ ભાતેલીઆ <rajbhatelia@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/et/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/mk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ta/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/und/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/vi/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/
Translation: NewPipe/Metadata
2025-02-04 10:59:39 +01:00
Stypox
3fc487310b
Use Runnable instead of () -> Unit if converted to Runnable anyway 2025-02-04 10:23:45 +01:00
Stypox
056809cb0d
Use "this" instead of "globalThis" as global scope
globalThis was introduced only on newer versions of JS
2025-02-04 10:22:10 +01:00
AudricV
a60bb3e7af
[YouTube] Change BotGuard endpoint to youtube.com's one
This prevents non-abilities to fetch BotGuard challenge and send its
result with the jnn-pa.googleapis.com domain (domain block like done
on Pi-hole lists or DNS servers).

That's what the official website uses to send the challenge execution
result, however it uses InnerTube to fetch the challenge. Embeds
still use the jnn-pa.googleapis.com domain.

Also rename the makeJnnPaGoogleapisRequest method appropriately.
2025-02-03 13:05:39 +01:00
AudricV
ecd3f6c2ee
[YouTube] Clarify BotGuard API key's origin and disable related Sonar warning 2025-02-01 15:40:16 +01:00
AudricV
70ff47b810
[YouTube] Get visitorData from the service to get valid responses 2025-02-01 15:39:07 +01:00
AudricV
b8e050f6c4
Adapt YoutubeHttpDataSource to extractor changes and improve requests
Always use POST requests and the same body that official HTML5 clients
use for a while.
2025-01-31 22:50:10 +01:00
AudricV
46d0bc1004
Update NewPipeExtractor 2025-01-31 22:28:08 +01:00
Stypox
e7fe84f2c7
Make sure downloadAndRunBotguard() is called after <script> loaded 2025-01-31 21:47:46 +01:00
Stypox
2b183a0576
Wrap logs in BuildConfig.DEBUG 2025-01-31 21:47:46 +01:00
Stypox
f856bd9306
Recreate poToken generator if current is broken
This will be tried only once, and afterwards an error will be thrown
2025-01-31 21:47:45 +01:00
Stypox
0066b322e1
Unify running on main thread 2025-01-31 21:47:45 +01:00
Stypox
3bdae81c0a
Fix checkstyle 2025-01-31 21:47:45 +01:00
Stypox
6010c4ea7f
Connect poToken generation to extractor 2025-01-31 21:47:45 +01:00
Stypox
690b3410e9
Interfaces for poTokens + WebView implementation 2025-01-31 21:47:44 +01:00
Profpatsch
ba86ce137b
Merge pull request #11969 from neosis91/dev
DownloaderImpl: Auto-close resources and simplify header setting
2025-01-31 15:56:39 +01:00
Bertrand Jaunet
410c01547c DownloaderImpl: Auto-close resources and simplify header setting
The headers should be overwritten in the same way, based on how
`.header` is the same as `.removeHeader().addHeader()`.

We weren’t closing the request resources after using them, potentially
leaking file handles. This will add autoclosing for both the request
and the body objects.
2025-01-31 12:36:27 +01:00
Stypox
47263f5254
Merge pull request #11959 from Stypox/fix-loading-stream-twice
Fix loading StreamInfo twice on first VideoDetailFragment opening
2025-01-27 14:56:51 +01:00
Stypox
01bf855015
Fix naming in VideoDetailFragment: video->stream, videoUrl->url 2025-01-27 14:52:35 +01:00
Profpatsch
ebf3008729
Merge pull request #11870 from TeamNewPipe/sidebar_donations
Add link to donation page on app drawer
2025-01-27 13:59:29 +01:00
Christian Schabesberger
33ecfb757e Sidebar: Add donation link to app drawer
This creates a donation link that leads to our donation page on the
NewPipe website.
2025-01-27 13:43:34 +01:00
Stypox
ffe26d882b
Fix loading StreamInfo twice on first VideoDetailFragment opening 2025-01-26 12:39:07 +01:00
Stypox
83f8141fe7
Merge pull request #11806 from Thompson3142/fix_subtitle_size
Fix caption sizes not being changed
2025-01-25 18:10:56 +01:00
Profpatsch
9253640fae
Merge pull request #11887 from Nikunj-Aggarwal/bg-iso-timestamp
Convert error report timestamps to ISO format
2025-01-23 19:51:18 +01:00
Stypox
8b5aa5cd9b
Merge branch 'master' into dev 2025-01-22 11:10:22 +01:00
Stypox
58393ad4ef
Release v0.27.5 (1002) 2025-01-21 23:34:42 +01:00
Stypox
977f7e28b5
Add changelogs for hotfix release v0.27.5 (1002) 2025-01-21 23:34:12 +01:00
Stypox
99e77249de
Update NewPipeExtractor to v0.24.4 2025-01-21 23:19:49 +01:00
Profpatsch
a955408053
Merge pull request #11928 from LeMeuble/bug-checksum-deleted-file
Fix the issue of getting the checksum of a removed file
2025-01-21 17:40:50 +01:00
Thompson3142
86203d6800 MainPlayer/PopupPlayer: Use system settings for subtitle size
This will use the exact subtitle sizes the user requested, both for
the main and the popup player. They will always be the same fraction
of the video, even if the popup player is resized.
2025-01-21 17:23:08 +01:00
Profpatsch
edd19641ac ErrorActivity: add Timestamp and Package/Service to markdown export
These were displayed in the UI, but not added into the markdown export
string.
2025-01-21 16:25:54 +01:00
Nikunj-Aggarwal
65749cbac0 ErrorActivity: Use a proper zoned ISO timestamp
Will have a timezone offset and be parsable as valid ISO8601
timestamp.

Also change the label in the UI to just say “Timestamp”
2025-01-21 16:24:07 +01:00
LeMeuble
658ddfc921 Fix issue of checksum for removed file 2025-01-16 10:46:25 +01:00
Stypox
f7d0fd545d
Merge pull request #11879 from tom93/pr/fix-image-minimizer-multiple-images
Fix image-minimizer on lines containing multiple images
2025-01-04 09:34:07 +01:00
Tom Levy
27e6be792f Fix image-minimizer on lines containing multiple images 2025-01-04 08:15:44 +00:00
Stypox
3fc0147f47
Merge pull request #11784 from Rishi2003Das/typo_change
Correct a Typo in Contributing.md
2024-12-17 10:42:34 +01:00
Rishi Das
c6b05c6094
Update CONTRIBUTING.md 2024-12-08 02:00:41 +05:30
Rishi Das
240a2fe36b
Update CONTRIBUTING.md 2024-12-08 02:00:04 +05:30
Rishi Das
de46e3abb3
Update CONTRIBUTING.md 2024-12-08 01:59:14 +05:30
Stypox
70748fa0bc
Use JDK 21 in build-release-apk.yml
See https://github.com/TeamNewPipe/NewPipe/issues/11754
2024-12-02 13:49:30 +01:00
Stypox
3847b32c11
Release v0.27.4 (1001) 2024-11-30 15:11:23 +01:00
Stypox
9054575f6c
Add changelog for v0.27.4 (1001) 2024-11-30 15:10:38 +01:00
Stypox
0dca92dd59
Merge branch 'master' into dev 2024-11-30 14:55:31 +01:00
Hosted Weblate
b19cd00dba
Translated using Weblate (Malay)
Currently translated at 9.8% (8 of 81 strings)

Translated using Weblate (Malay)

Currently translated at 57.9% (429 of 740 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 96.2% (78 of 81 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 64.1% (52 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 50.6% (41 of 81 strings)

Translated using Weblate (Icelandic)

Currently translated at 99.4% (736 of 740 strings)

Translated using Weblate (Arabic (Libya))

Currently translated at 4.9% (4 of 81 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Hungarian)

Currently translated at 32.0% (26 of 81 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (81 of 81 strings)

Translated using Weblate (Turkish)

Currently translated at 46.9% (38 of 81 strings)

Translated using Weblate (Estonian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Azerbaijani)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Basque)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (German)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Japanese)

Currently translated at 12.3% (10 of 81 strings)

Translated using Weblate (Punjabi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (740 of 740 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (740 of 740 strings)

Co-authored-by: Agnieszka C <aga_04@o2.pl>
Co-authored-by: Aliberk Sandıkçı <git@aliberksandikci.com.tr>
Co-authored-by: Dampuzakura <dampuzakura@users.noreply.hosted.weblate.org>
Co-authored-by: Eder Etxebarria Rojo <eder@betxepare.eus>
Co-authored-by: Femini <nizamismidov4@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: H Tamás <hovanszki@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Igor Rückert <igorruckert@yahoo.com.br>
Co-authored-by: Jeff Huang <s8321414@gmail.com>
Co-authored-by: LeoL <leonardo.lapa.04@protonmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Milan <mobrcian@hotmail.com>
Co-authored-by: NEXI <nexiphotographer@gmail.com>
Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com>
Co-authored-by: Retrial <giwrgosmant@gmail.com>
Co-authored-by: Shafiq Jamzuri <shafiq.joe@yandex.com>
Co-authored-by: ShareASmile <ShareASmile@users.noreply.hosted.weblate.org>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: VfBFan <VfBFan@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: trunars <trunars@gmail.com>
Co-authored-by: Максим Горпиніч <mgorpinic2005@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar_LY/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ms/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_PT/
Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/
Translation: NewPipe/Metadata
2024-11-30 14:55:05 +01:00
Stypox
88d8d90bbd
Merge pull request #11765 from Stypox/release-workflow
Add build-release-apk workflow
2024-11-30 14:53:16 +01:00
Stypox
c569f08a32
Add build-release-apk workflow 2024-11-30 13:39:18 +01:00
Stypox
246fc034c1
Add build-release-apk workflow 2024-11-30 13:29:38 +01:00
Stypox
52942ffd30
Merge pull request #11738 from cillyvms/a13-player-notifs
Always allow changing player notification preferences on Android 13+
2024-11-27 19:12:19 +01:00
Stypox
e4b0245530
Merge pull request #11734 from Thompson3142/fix_timestamp_popup_time
Fix player resuming from start when clicking on a timestamp
2024-11-27 18:38:49 +01:00
Tobi
c6b8bcf0f4
Merge pull request #11745 from Stypox/truncate-before-export
Fix downloading/exporting when overwriting file would not truncate
2024-11-27 17:37:53 +01:00
Stypox
e31a8ad7a2
Mock openAndTruncateStream instead of getStream in test 2024-11-27 16:37:25 +01:00
Stypox
b21981a9c7
Add comments to explain why openAndTruncateStream() 2024-11-27 16:34:50 +01:00
Thompson3142
f9711a3402 Removed call to setRecovery() entirely 2024-11-24 22:12:25 +01:00
Stypox
df941670a8
Fix downloading/exporting when overwriting file would not truncate 2024-11-24 18:36:54 +01:00
Stypox
57e66b17c6
Merge branch 'master' into dev 2024-11-24 17:43:45 +01:00
cillyvms
213f49f5c4
Allow changing player notification preferences regardless of system settings on Android 13 and above. 2024-11-22 14:21:46 +01:00
Thompson3142
16c79c8219 Fixed player resuming from start when clicking on a timestamp 2024-11-21 22:42:42 +01:00
ShareASmile
14081505cd
Update backup and restore explanation & improve hindi, punjabi and assamese READMEs (#11243)
* update backup and restore explanation in punjabi README

* Update backup and restore explanation in hindi README

* add_matrix_link to hindi and punjabi README

also translate Warning in hindi & punjabi language Readme's

* improve hindi and punjabi readme

add missing link #supported-services in hindi readme (that is #समर्थित-सेवाएँ}
improve translation of supported services in punjabi
Use Fdroid Hindi badge instead of english in hindi readme

* revert translate Warning in hindi & punjabi language Readme's

* update backup and restore explanation in assamese README

* fix assamese readme librapay donate button not showing and fix weird formating

* add matrix chat link to assamese readme & fix Newpipe logo not showing

* Update Matrix room URL to new link

oh! I missed this one earlier

* remove references to Bitcoin and Bountysource donation options in hindi readme

* more improvements in punjabi README

* fix CONTRIBUTING.md link in punjabi readme

* fix CONTRIBUTING.md link in assamese readme

* add missing paragraphs in hindi translation for hi readme

* revert localisation of app name NewPipe as it stands out

* address the review and place supported-services at correct place in hindi readme

do required changes for punjabi
do much needed improvements in assamese readme

* fix formatting issues in assamese readme

* fix link to releases in punjabi readme

* resolve conflicts
2024-11-20 10:42:29 +01:00
Tobi
ebd4880188
Merge pull request #10969 from yosrinajar/Read-Me-Translation
Readme translation to arabic
2024-11-19 14:38:52 +01:00
Profpatsch
ffcba175ff
Merge pull request #11330 from Isira-Seneviratne/Java-10-URL-NP
Apply URL encode/decode changes
2024-11-19 14:05:04 +01:00
Isira Seneviratne
c7848e5e86 Apply URL encode/decode changes 2024-11-19 13:17:10 +01:00
yosrinajar
6d686b93cb fixed all readme files 2024-11-19 12:17:25 +01:00
yosrinajar
2cc38f59d3 Readme translation to arabic 2024-11-19 12:16:30 +01:00
485 changed files with 7281 additions and 1462 deletions

View File

@ -6,7 +6,7 @@ NewPipe contribution guidelines
## Crash reporting ## Crash reporting
Report crashes through the **automated crash report system** of NewPipe. Report crashes through the **automated crash report system** of NewPipe.
This way all the data needed for debugging is included in your bugreport for GitHub. This way all the data needed for debugging is included in your bug report for GitHub.
You'll see *exactly* what is sent, be able to add **your comments**, and then send it. You'll see *exactly* what is sent, be able to add **your comments**, and then send it.
## Issue reporting/feature requests ## Issue reporting/feature requests

38
.github/workflows/build-release-apk.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: "Build unsigned release APK on master"
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: 'master'
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: "Build release APK"
run: ./gradlew assembleRelease --stacktrace
- name: "Rename APK"
run: |
VERSION_NAME="$(jq -r ".elements[0].versionName" "app/build/outputs/apk/release/output-metadata.json")"
echo "Version name: $VERSION_NAME" >> "$GITHUB_STEP_SUMMARY"
echo '```json' >> "$GITHUB_STEP_SUMMARY"
cat "app/build/outputs/apk/release/output-metadata.json" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
# assume there is only one APK in that folder
mv app/build/outputs/apk/release/*.apk "app/build/outputs/apk/release/NewPipe_v$VERSION_NAME.apk"
- name: "Upload APK"
uses: actions/upload-artifact@v4
with:
name: app
path: app/build/outputs/apk/release/*.apk

View File

@ -32,8 +32,8 @@ module.exports = async ({github, context}) => {
} }
// Regex for finding images (simple variant) ![ALT_TEXT](https://*.githubusercontent.com/<number>/<variousHexStringsAnd->.<fileExtension>) // Regex for finding images (simple variant) ![ALT_TEXT](https://*.githubusercontent.com/<number>/<variousHexStringsAnd->.<fileExtension>)
const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[(.*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm; const REGEX_USER_CONTENT_IMAGE_LOOKUP = /\!\[([^\]]*)\]\((https:\/\/[-a-z0-9]+\.githubusercontent\.com\/\d+\/[-0-9a-f]{32,512}\.(jpg|gif|png))\)/gm;
const REGEX_ASSETS_IMAGE_LOCKUP = /\!\[(.*)\]\((https:\/\/github\.com\/[-\w\d]+\/[-\w\d]+\/assets\/\d+\/[\-0-9a-f]{32,512})\)/gm; const REGEX_ASSETS_IMAGE_LOCKUP = /\!\[([^\]]*)\]\((https:\/\/github\.com\/[-\w\d]+\/[-\w\d]+\/assets\/\d+\/[\-0-9a-f]{32,512})\)/gm;
// Check if we found something // Check if we found something
let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody) let foundSimpleImages = REGEX_USER_CONTENT_IMAGE_LOOKUP.test(initialBody)

View File

@ -20,7 +20,7 @@
<p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p> <p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p>
<hr> <hr>
*Read this document in other languages: [Deutsch](doc/README.de.md), [English](README.md), [Español](doc/README.es.md), [Français](doc/README.fr.md), [हिन्दी](doc/README.hi.md), [Italiano](doc/README.it.md), [한국어](doc/README.ko.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [ਪੰਜਾਬੀ ](doc/README.pa.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Soomaali](doc/README.so.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md), [অসমীয়া](doc/README.asm.md), [Српски](doc/README.sr.md)* *Read this document in other languages: [Deutsch](doc/README.de.md), [English](README.md), [Español](doc/README.es.md), [Français](doc/README.fr.md), [हिन्दी](doc/README.hi.md), [Italiano](doc/README.it.md), [한국어](doc/README.ko.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [ਪੰਜਾਬੀ ](doc/README.pa.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Soomaali](doc/README.so.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md), [অসমীয়া](doc/README.asm.md), [Српски](doc/README.sr.md), [العربية](README.ar.md)*
> [!warning] > [!warning]
> <b>THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b> > <b>THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.</b>

View File

@ -23,9 +23,9 @@ android {
if (System.properties.containsKey('versionCodeOverride')) { if (System.properties.containsKey('versionCodeOverride')) {
versionCode System.getProperty('versionCodeOverride') as Integer versionCode System.getProperty('versionCodeOverride') as Integer
} else { } else {
versionCode 1000 versionCode 1003
} }
versionName "0.27.3" versionName "0.27.6"
if (System.properties.containsKey('versionNameSuffix')) { if (System.properties.containsKey('versionNameSuffix')) {
versionNameSuffix System.getProperty('versionNameSuffix') versionNameSuffix System.getProperty('versionNameSuffix')
} }
@ -97,6 +97,10 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
} }
androidResources {
generateLocaleConfig = true
}
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true buildConfig true
@ -208,7 +212,7 @@ dependencies {
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
// WORKAROUND: if you get errors with the NewPipeExtractor dependency, replace `v0.24.3` with // WORKAROUND: if you get errors with the NewPipeExtractor dependency, replace `v0.24.3` with
// the corresponding commit hash, since JitPack is sometimes buggy // the corresponding commit hash, since JitPack is sometimes buggy
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.3' implementation 'com.github.TeamNewPipe:NewPipeExtractor:0b99100db'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/ /** Checkstyle **/
@ -241,6 +245,7 @@ dependencies {
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
implementation 'com.google.android.material:material:1.11.0' implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.webkit:webkit:1.9.0"
/** Third-party libraries **/ /** Third-party libraries **/
// Instance state boilerplate elimination // Instance state boilerplate elimination

View File

@ -5,10 +5,17 @@
## Rules for NewPipeExtractor ## Rules for NewPipeExtractor
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } -keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
## Rules for Rhino and Rhino Engine
-keep class org.mozilla.javascript.* { *; }
-keep class org.mozilla.javascript.** { *; } -keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.javascript.engine.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter -keep class org.mozilla.classfile.ClassFileWriter
-dontwarn org.mozilla.javascript.JavaToJSONConverters -dontwarn org.mozilla.javascript.JavaToJSONConverters
-dontwarn org.mozilla.javascript.tools.** -dontwarn org.mozilla.javascript.tools.**
-keep class javax.script.** { *; }
-dontwarn javax.script.**
-keep class jdk.dynalink.** { *; }
-dontwarn jdk.dynalink.**
## Rules for ExoPlayer ## Rules for ExoPlayer
-keep class com.google.android.exoplayer2.** { *; } -keep class com.google.android.exoplayer2.** { *; }

View File

@ -64,6 +64,9 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service> </service>
<activity <activity
@ -424,5 +427,10 @@
<meta-data <meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive" android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" /> android:value="true" />
<!-- Android Auto -->
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@mipmap/ic_launcher" />
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en"><head><title></title><script>
/**
* Factory method to create and load a BotGuardClient instance.
* @param options - Configuration options for the BotGuardClient.
* @returns A promise that resolves to a loaded BotGuardClient instance.
*/
function loadBotGuard(challengeData) {
this.vm = this[challengeData.globalName];
this.program = challengeData.program;
this.vmFunctions = {};
this.syncSnapshotFunction = null;
if (!this.vm)
throw new Error('[BotGuardClient]: VM not found in the global object');
if (!this.vm.a)
throw new Error('[BotGuardClient]: Could not load program');
const vmFunctionsCallback = function (
asyncSnapshotFunction,
shutdownFunction,
passEventFunction,
checkCameraFunction
) {
this.vmFunctions = {
asyncSnapshotFunction: asyncSnapshotFunction,
shutdownFunction: shutdownFunction,
passEventFunction: passEventFunction,
checkCameraFunction: checkCameraFunction
};
};
this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/** no-op */ }, [ [], [] ])[0]
// an asynchronous function runs in the background and it will eventually call
// `vmFunctionsCallback`, however we need to manually tell JavaScript to pass
// control to the things running in the background by interrupting this async
// function in any way, e.g. with a delay of 1ms. The loop is most probably not
// needed but is there just because.
return new Promise(function (resolve, reject) {
i = 0
refreshIntervalId = setInterval(function () {
if (!!this.vmFunctions.asyncSnapshotFunction) {
resolve(this)
clearInterval(refreshIntervalId);
}
if (i >= 10000) {
reject("asyncSnapshotFunction is null even after 10 seconds")
clearInterval(refreshIntervalId);
}
i += 1;
}, 1);
})
}
/**
* Takes a snapshot asynchronously.
* @returns The snapshot result.
* @example
* ```ts
* const result = await botguard.snapshot({
* contentBinding: {
* c: "a=6&a2=10&b=SZWDwKVIuixOp7Y4euGTgwckbJA&c=1729143849&d=1&t=7200&c1a=1&c6a=1&c6b=1&hh=HrMb5mRWTyxGJphDr0nW2Oxonh0_wl2BDqWuLHyeKLo",
* e: "ENGAGEMENT_TYPE_VIDEO_LIKE",
* encryptedVideoId: "P-vC09ZJcnM"
* }
* });
*
* console.log(result);
* ```
*/
function snapshot(args) {
return new Promise(function (resolve, reject) {
if (!this.vmFunctions.asyncSnapshotFunction)
return reject(new Error('[BotGuardClient]: Async snapshot function not found'));
this.vmFunctions.asyncSnapshotFunction(function (response) { resolve(response) }, [
args.contentBinding,
args.signedTimestamp,
args.webPoSignalOutput,
args.skipPrivacyBuffer
]);
});
}
function runBotGuard(challengeData) {
const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
if (interpreterJavascript) {
new Function(interpreterJavascript)();
} else throw new Error('Could not load VM');
const webPoSignalOutput = [];
return loadBotGuard({
globalName: challengeData.globalName,
globalObj: this,
program: challengeData.program
}).then(function (botguard) {
return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput })
}).then(function (botguardResponse) {
return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse }
})
}
function obtainPoToken(webPoSignalOutput, integrityToken, identifier) {
const getMinter = webPoSignalOutput[0];
if (!getMinter)
throw new Error('PMD:Undefined');
const mintCallback = getMinter(integrityToken);
if (!(mintCallback instanceof Function))
throw new Error('APF:Failed');
const result = mintCallback(identifier);
if (!result)
throw new Error('YNJ:Undefined');
if (!(result instanceof Uint8Array))
throw new Error('ODM:Invalid');
return result;
}
</script></head><body></body></html>

View File

@ -17,6 +17,7 @@ import org.acra.config.CoreConfigurationBuilder;
import org.schabi.newpipe.error.ReCaptchaActivity; import org.schabi.newpipe.error.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.BridgeStateSaverInitializer; import org.schabi.newpipe.util.BridgeStateSaverInitializer;
@ -26,6 +27,7 @@ import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.image.ImageStrategy; import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PicassoHelper; import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.image.PreferredImageQuality; import org.schabi.newpipe.util.image.PreferredImageQuality;
import org.schabi.newpipe.util.potoken.PoTokenProviderImpl;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
@ -118,6 +120,8 @@ public class App extends Application {
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false)); && prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
configureRxJavaErrorHandler(); configureRxJavaErrorHandler();
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl.INSTANCE);
} }
@Override @Override

View File

@ -137,7 +137,8 @@ public final class DownloaderImpl extends Downloader {
} }
final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url) .method(httpMethod, requestBody)
.url(url)
.addHeader("User-Agent", USER_AGENT); .addHeader("User-Agent", USER_AGENT);
final String cookies = getCookies(url); final String cookies = getCookies(url);
@ -145,38 +146,33 @@ public final class DownloaderImpl extends Downloader {
requestBuilder.addHeader("Cookie", cookies); requestBuilder.addHeader("Cookie", cookies);
} }
for (final Map.Entry<String, List<String>> pair : headers.entrySet()) { headers.forEach((headerName, headerValueList) -> {
final String headerName = pair.getKey(); requestBuilder.removeHeader(headerName);
final List<String> headerValueList = pair.getValue(); headerValueList.forEach(headerValue ->
requestBuilder.addHeader(headerName, headerValue));
});
if (headerValueList.size() > 1) { try (
requestBuilder.removeHeader(headerName); okhttp3.Response response = client.newCall(requestBuilder.build()).execute()
for (final String headerValue : headerValueList) { ) {
requestBuilder.addHeader(headerName, headerValue); if (response.code() == 429) {
} throw new ReCaptchaException("reCaptcha Challenge requested", url);
} else if (headerValueList.size() == 1) {
requestBuilder.header(headerName, headerValueList.get(0));
} }
String responseBodyToReturn = null;
try (ResponseBody body = response.body()) {
if (body != null) {
responseBodyToReturn = body.string();
}
}
final String latestUrl = response.request().url().toString();
return new Response(
response.code(),
response.message(),
response.headers().toMultimap(),
responseBodyToReturn,
latestUrl);
} }
final okhttp3.Response response = client.newCall(requestBuilder.build()).execute();
if (response.code() == 429) {
response.close();
throw new ReCaptchaException("reCaptcha Challenge requested", url);
}
final ResponseBody body = response.body();
String responseBodyToReturn = null;
if (body != null) {
responseBodyToReturn = body.string();
}
final String latestUrl = response.request().url().toString();
return new Response(response.code(), response.message(), response.headers().toMultimap(),
responseBodyToReturn, latestUrl);
} }
} }

View File

@ -38,6 +38,7 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -92,6 +93,7 @@ import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.FocusOverlayView; import org.schabi.newpipe.views.FocusOverlayView;
import java.util.ArrayList; import java.util.ArrayList;
@ -120,7 +122,8 @@ public class MainActivity extends AppCompatActivity {
private static final int ITEM_ID_DOWNLOADS = -4; private static final int ITEM_ID_DOWNLOADS = -4;
private static final int ITEM_ID_HISTORY = -5; private static final int ITEM_ID_HISTORY = -5;
private static final int ITEM_ID_SETTINGS = 0; private static final int ITEM_ID_SETTINGS = 0;
private static final int ITEM_ID_ABOUT = 1; private static final int ITEM_ID_DONATION = 1;
private static final int ITEM_ID_ABOUT = 2;
private static final int ORDER = 0; private static final int ORDER = 0;
@ -138,6 +141,19 @@ public class MainActivity extends AppCompatActivity {
ThemeHelper.setDayNightMode(this); ThemeHelper.setDayNightMode(this);
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
// Fixes text color turning black in dark/black mode:
// https://github.com/TeamNewPipe/NewPipe/issues/12016
// For further reference see: https://issuetracker.google.com/issues/37124582
if (DeviceUtils.supportsWebView()) {
try {
new WebView(this);
} catch (final Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to create WebView", e);
}
}
}
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -174,6 +190,8 @@ public class MainActivity extends AppCompatActivity {
&& ReleaseVersionUtil.INSTANCE.isReleaseApk()) { && ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
UpdateSettingsFragment.askForConsentToUpdateChecks(this); UpdateSettingsFragment.askForConsentToUpdateChecks(this);
} }
Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
} }
@Override @Override
@ -262,6 +280,10 @@ public class MainActivity extends AppCompatActivity {
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) .add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
.setIcon(R.drawable.ic_settings); .setIcon(R.drawable.ic_settings);
drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_DONATION, ORDER,
R.string.donation_title)
.setIcon(R.drawable.volunteer_activism_ic);
drawerLayoutBinding.navigation.getMenu() drawerLayoutBinding.navigation.getMenu()
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) .add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
.setIcon(R.drawable.ic_info_outline); .setIcon(R.drawable.ic_info_outline);
@ -337,6 +359,9 @@ public class MainActivity extends AppCompatActivity {
case ITEM_ID_SETTINGS: case ITEM_ID_SETTINGS:
NavigationHelper.openSettings(this); NavigationHelper.openSettings(this);
break; break;
case ITEM_ID_DONATION:
ShareUtils.openUrlInBrowser(this, getString(R.string.donation_url));
break;
case ITEM_ID_ABOUT: case ITEM_ID_ABOUT:
NavigationHelper.openAbout(this); NavigationHelper.openAbout(this);
break; break;
@ -839,7 +864,8 @@ public class MainActivity extends AppCompatActivity {
@Override @Override
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(), if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED)) { VideoDetailFragment.ACTION_PLAYER_STARTED)
&& PlayerHolder.getInstance().isPlayerOpen()) {
openMiniPlayerIfMissing(); openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions // At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that. // are useless since the fragment will not be removed after that.
@ -851,6 +877,10 @@ public class MainActivity extends AppCompatActivity {
final IntentFilter intentFilter = new IntentFilter(); final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED); intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter); registerReceiver(broadcastReceiver, intentFilter);
// If the PlayerHolder is not bound yet, but the service is running, try to bind to it.
// Once the connection is established, the ACTION_PLAYER_STARTED will be sent.
PlayerHolder.getInstance().tryBindIfNeeded(this);
} }
} }
@ -924,4 +954,5 @@ public class MainActivity extends AppCompatActivity {
return sheetState == BottomSheetBehavior.STATE_HIDDEN return sheetState == BottomSheetBehavior.STATE_HIDDEN
|| sheetState == BottomSheetBehavior.STATE_COLLAPSED; || sheetState == BottomSheetBehavior.STATE_COLLAPSED;
} }
} }

View File

@ -3,6 +3,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Embedded import androidx.room.Embedded
import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime import java.time.OffsetDateTime
data class StreamHistoryEntry( data class StreamHistoryEntry(
@ -27,4 +29,17 @@ data class StreamHistoryEntry(
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId && return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate) accessDate.isEqual(other.accessDate)
} }
fun toStreamInfoItem(): StreamInfoItem =
StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType,
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
} }

View File

@ -1,5 +1,7 @@
package org.schabi.newpipe.database.playlist; package org.schabi.newpipe.database.playlist;
import androidx.annotation.Nullable;
import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.LocalItem;
public interface PlaylistLocalItem extends LocalItem { public interface PlaylistLocalItem extends LocalItem {
@ -10,4 +12,7 @@ public interface PlaylistLocalItem extends LocalItem {
long getUid(); long getUid();
void setDisplayIndex(long displayIndex); void setDisplayIndex(long displayIndex);
@Nullable
String getThumbnailUrl();
} }

View File

@ -9,6 +9,8 @@ import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_STREAM_ID;
import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_THUMBNAIL_URL;
import androidx.annotation.Nullable;
public class PlaylistMetadataEntry implements PlaylistLocalItem { public class PlaylistMetadataEntry implements PlaylistLocalItem {
public static final String PLAYLIST_STREAM_COUNT = "streamCount"; public static final String PLAYLIST_STREAM_COUNT = "streamCount";
@ -71,4 +73,10 @@ public class PlaylistMetadataEntry implements PlaylistLocalItem {
public void setDisplayIndex(final long displayIndex) { public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex; this.displayIndex = displayIndex;
} }
@Nullable
@Override
public String getThumbnailUrl() {
return thumbnailUrl;
}
} }

View File

@ -34,7 +34,7 @@ public interface PlaylistRemoteDAO extends BasicDAO<PlaylistRemoteEntity> {
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_ID + " = :playlistId") + REMOTE_PLAYLIST_ID + " = :playlistId")
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId); Flowable<PlaylistRemoteEntity> getPlaylist(long playlistId);
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE " @Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId") + REMOTE_PLAYLIST_URL + " = :url AND " + REMOTE_PLAYLIST_SERVICE_ID + " = :serviceId")

View File

@ -2,6 +2,7 @@ package org.schabi.newpipe.database.playlist.model;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo; import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Ignore; import androidx.room.Ignore;
@ -134,6 +135,8 @@ public class PlaylistRemoteEntity implements PlaylistLocalItem {
this.name = name; this.name = name;
} }
@Nullable
@Override
public String getThumbnailUrl() { public String getThumbnailUrl() {
return thumbnailUrl; return thumbnailUrl;
} }

View File

@ -26,7 +26,7 @@ import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.time.LocalDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -67,10 +67,6 @@ public class ErrorActivity extends AppCompatActivity {
public static final String ERROR_GITHUB_ISSUE_URL = public static final String ERROR_GITHUB_ISSUE_URL =
"https://github.com/TeamNewPipe/NewPipe/issues"; "https://github.com/TeamNewPipe/NewPipe/issues";
public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
private ErrorInfo errorInfo; private ErrorInfo errorInfo;
private String currentTimeStamp; private String currentTimeStamp;
@ -107,7 +103,9 @@ public class ErrorActivity extends AppCompatActivity {
// important add guru meditation // important add guru meditation
addGuruMeditation(); addGuruMeditation();
currentTimeStamp = CURRENT_TIMESTAMP_FORMATTER.format(LocalDateTime.now()); // print current time, as zoned ISO8601 timestamp
final ZonedDateTime now = ZonedDateTime.now();
currentTimeStamp = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
activityErrorBinding.errorReportEmailButton.setOnClickListener(v -> activityErrorBinding.errorReportEmailButton.setOnClickListener(v ->
openPrivacyPolicyDialog(this, "EMAIL")); openPrivacyPolicyDialog(this, "EMAIL"));
@ -250,6 +248,9 @@ public class ErrorActivity extends AppCompatActivity {
.append("\n* __Content Language:__ ").append(getContentLanguageString()) .append("\n* __Content Language:__ ").append(getContentLanguageString())
.append("\n* __App Language:__ ").append(getAppLanguage()) .append("\n* __App Language:__ ").append(getAppLanguage())
.append("\n* __Service:__ ").append(errorInfo.getServiceName()) .append("\n* __Service:__ ").append(errorInfo.getServiceName())
.append("\n* __Timestamp:__ ").append(currentTimeStamp)
.append("\n* __Package:__ ").append(getPackageName())
.append("\n* __Service:__ ").append(errorInfo.getServiceName())
.append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME) .append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
.append("\n* __OS:__ ").append(getOsString()).append("\n"); .append("\n* __OS:__ ").append(getOsString()).append("\n");

View File

@ -185,10 +185,8 @@ public class ReCaptchaActivity extends AppCompatActivity {
final int abuseEnd = url.indexOf("+path"); final int abuseEnd = url.indexOf("+path");
try { try {
String abuseCookie = url.substring(abuseStart + 13, abuseEnd); handleCookies(Utils.decodeUrlUtf8(url.substring(abuseStart + 13, abuseEnd)));
abuseCookie = Utils.decodeUrlUtf8(abuseCookie); } catch (final StringIndexOutOfBoundsException e) {
handleCookies(abuseCookie);
} catch (IllegalArgumentException | StringIndexOutOfBoundsException e) {
if (MainActivity.DEBUG) { if (MainActivity.DEBUG) {
Log.e(TAG, "handleCookiesFromUrl: invalid google abuse starting at " Log.e(TAG, "handleCookiesFromUrl: invalid google abuse starting at "
+ abuseStart + " and ending at " + abuseEnd + " for url " + url, e); + abuseStart + " and ending at " + abuseEnd + " for url " + url, e);

View File

@ -236,11 +236,14 @@ public final class VideoDetailFragment
// Service management // Service management
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onServiceConnected(final Player connectedPlayer, public void onServiceConnected(@NonNull final PlayerService connectedPlayerService) {
final PlayerService connectedPlayerService,
final boolean playAfterConnect) {
player = connectedPlayer;
playerService = connectedPlayerService; playerService = connectedPlayerService;
}
@Override
public void onPlayerConnected(@NonNull final Player connectedPlayer,
final boolean playAfterConnect) {
player = connectedPlayer;
// It will do nothing if the player is not in fullscreen mode // It will do nothing if the player is not in fullscreen mode
hideSystemUiIfNeeded(); hideSystemUiIfNeeded();
@ -272,22 +275,29 @@ public final class VideoDetailFragment
updateOverlayPlayQueueButtonVisibility(); updateOverlayPlayQueueButtonVisibility();
} }
@Override
public void onPlayerDisconnected() {
player = null;
// the binding could be null at this point, if the app is finishing
if (binding != null) {
restoreDefaultBrightness();
}
}
@Override @Override
public void onServiceDisconnected() { public void onServiceDisconnected() {
playerService = null; playerService = null;
player = null;
restoreDefaultBrightness();
} }
/*////////////////////////////////////////////////////////////////////////*/ /*////////////////////////////////////////////////////////////////////////*/
public static VideoDetailFragment getInstance(final int serviceId, public static VideoDetailFragment getInstance(final int serviceId,
@Nullable final String videoUrl, @Nullable final String url,
@NonNull final String name, @NonNull final String name,
@Nullable final PlayQueue queue) { @Nullable final PlayQueue queue) {
final VideoDetailFragment instance = new VideoDetailFragment(); final VideoDetailFragment instance = new VideoDetailFragment();
instance.setInitialData(serviceId, videoUrl, name, queue); instance.setInitialData(serviceId, url, name, queue);
return instance; return instance;
} }
@ -1736,7 +1746,7 @@ public final class VideoDetailFragment
playQueue = queue; playQueue = queue;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onQueueUpdate() called with: serviceId = [" Log.d(TAG, "onQueueUpdate() called with: serviceId = ["
+ serviceId + "], videoUrl = [" + url + "], name = [" + serviceId + "], url = [" + url + "], name = ["
+ title + "], playQueue = [" + playQueue + "]"); + title + "], playQueue = [" + playQueue + "]");
} }
@ -1848,13 +1858,16 @@ public final class VideoDetailFragment
@Override @Override
public void onServiceStopped() { public void onServiceStopped() {
setOverlayPlayPauseImage(false); // the binding could be null at this point, if the app is finishing
if (currentInfo != null) { if (binding != null) {
updateOverlayData(currentInfo.getName(), setOverlayPlayPauseImage(false);
currentInfo.getUploaderName(), if (currentInfo != null) {
currentInfo.getThumbnails()); updateOverlayData(currentInfo.getName(),
currentInfo.getUploaderName(),
currentInfo.getThumbnails());
}
updateOverlayPlayQueueButtonVisibility();
} }
updateOverlayPlayQueueButtonVisibility();
} }
@Override @Override

View File

@ -120,67 +120,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
// LifeCycle // LifeCycle
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
menuProvider = new MenuProvider() {
@Override
public void onCreateMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
}
@Override
public void onPrepareMenu(@NonNull final Menu menu) {
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
updateRssButton();
updateNotifyButton(channelSubscription);
}
@Override
public boolean onMenuItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_notify:
final boolean value = !item.isChecked();
item.setEnabled(false);
setNotify(value);
break;
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
if (currentInfo != null) {
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
}
break;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(),
currentInfo.getOriginalUrl());
}
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name,
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
}
break;
default:
return false;
}
return true;
}
};
activity.addMenuProvider(menuProvider);
}
@Override @Override
public void onAttach(@NonNull final Context context) { public void onAttach(@NonNull final Context context) {
super.onAttach(context); super.onAttach(context);
@ -195,6 +134,67 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
return binding.getRoot(); return binding.getRoot();
} }
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
menuProvider = new MenuProvider() {
@Override
public void onCreateMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.menu_channel, menu);
if (DEBUG) {
Log.d(TAG, "onCreateOptionsMenu() called with: "
+ "menu = [" + menu + "], inflater = [" + inflater + "]");
}
}
@Override
public void onPrepareMenu(@NonNull final Menu menu) {
menuRssButton = menu.findItem(R.id.menu_item_rss);
menuNotifyButton = menu.findItem(R.id.menu_item_notify);
updateRssButton();
updateNotifyButton(channelSubscription);
}
@Override
public boolean onMenuItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_notify:
final boolean value = !item.isChecked();
item.setEnabled(false);
setNotify(value);
break;
case R.id.action_settings:
NavigationHelper.openSettings(requireContext());
break;
case R.id.menu_item_rss:
if (currentInfo != null) {
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
}
break;
case R.id.menu_item_openInBrowser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(),
currentInfo.getOriginalUrl());
}
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name,
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
}
break;
default:
return false;
}
return true;
}
};
activity.addMenuProvider(menuProvider);
}
@Override // called from onViewCreated in BaseFragment.onViewCreated @Override // called from onViewCreated in BaseFragment.onViewCreated
protected void initViews(final View rootView, final Bundle savedInstanceState) { protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState); super.initViews(rootView, savedInstanceState);
@ -232,6 +232,14 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
binding.subChannelTitleView.setOnClickListener(openSubChannel); binding.subChannelTitleView.setOnClickListener(openSubChannel);
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (menuProvider != null) {
activity.removeMenuProvider(menuProvider);
}
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -240,7 +248,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
} }
disposables.clear(); disposables.clear();
binding = null; binding = null;
activity.removeMenuProvider(menuProvider);
menuProvider = null; menuProvider = null;
} }

View File

@ -7,3 +7,16 @@ import androidx.core.os.BundleCompat
inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? { inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
return BundleCompat.getParcelableArrayList(this, key, T::class.java) return BundleCompat.getParcelableArrayList(this, key, T::class.java)
} }
fun Bundle?.toDebugString(): String {
if (this == null) {
return "null"
}
val string = StringBuilder("Bundle{")
for (key in this.keySet()) {
@Suppress("DEPRECATION") // we want this[key] to return items of any type
string.append(" ").append(key).append(" => ").append(this[key]).append(";")
}
string.append(" }")
return string.toString()
}

View File

@ -17,8 +17,10 @@ import androidx.core.view.isGone
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import org.schabi.newpipe.MainActivity
// logs in this class are disabled by default since it's usually not useful,
// you can enable them by setting this flag to MainActivity.DEBUG
private const val DEBUG = false
private const val TAG = "ViewUtils" private const val TAG = "ViewUtils"
/** /**
@ -38,7 +40,7 @@ fun View.animate(
delay: Long = 0, delay: Long = 0,
execOnEnd: Runnable? = null execOnEnd: Runnable? = null
) { ) {
if (MainActivity.DEBUG) { if (DEBUG) {
val id = try { val id = try {
resources.getResourceEntryName(id) resources.getResourceEntryName(id)
} catch (e: Exception) { } catch (e: Exception) {
@ -51,7 +53,7 @@ fun View.animate(
Log.d(TAG, "animate(): $msg") Log.d(TAG, "animate(): $msg")
} }
if (isVisible && enterOrExit) { if (isVisible && enterOrExit) {
if (MainActivity.DEBUG) { if (DEBUG) {
Log.d(TAG, "animate(): view was already visible > view = [$this]") Log.d(TAG, "animate(): view was already visible > view = [$this]")
} }
animate().setListener(null).cancel() animate().setListener(null).cancel()
@ -60,7 +62,7 @@ fun View.animate(
execOnEnd?.run() execOnEnd?.run()
return return
} else if ((isGone || isInvisible) && !enterOrExit) { } else if ((isGone || isInvisible) && !enterOrExit) {
if (MainActivity.DEBUG) { if (DEBUG) {
Log.d(TAG, "animate(): view was already gone > view = [$this]") Log.d(TAG, "animate(): view was already gone > view = [$this]")
} }
animate().setListener(null).cancel() animate().setListener(null).cancel()
@ -89,7 +91,7 @@ fun View.animate(
* @param colorEnd the background color to end with * @param colorEnd the background color to end with
*/ */
fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) {
if (MainActivity.DEBUG) { if (DEBUG) {
Log.d( Log.d(
TAG, TAG,
"animateBackgroundColor() called with: view = [$this], duration = [$duration], " + "animateBackgroundColor() called with: view = [$this], duration = [$duration], " +
@ -109,7 +111,7 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo
} }
fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
if (MainActivity.DEBUG) { if (DEBUG) {
Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this") Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this")
} }
val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat()) val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat())
@ -127,7 +129,7 @@ fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator {
} }
fun View.animateRotation(duration: Long, targetRotation: Int) { fun View.animateRotation(duration: Long, targetRotation: Int) {
if (MainActivity.DEBUG) { if (DEBUG) {
Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this") Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this")
} }
animate().setListener(null).cancel() animate().setListener(null).cancel()

View File

@ -88,7 +88,7 @@ class NotificationHelper(val context: Context) {
// Show individual stream notifications, set channel icon only if there is actually // Show individual stream notifications, set channel icon only if there is actually
// one // one
showStreamNotifications(newStreams, data.serviceId, bitmap) showStreamNotifications(newStreams, data.serviceId, data.url, bitmap)
// Show summary notification // Show summary notification
manager.notify(data.pseudoId, summaryBuilder.build()) manager.notify(data.pseudoId, summaryBuilder.build())
@ -97,7 +97,7 @@ class NotificationHelper(val context: Context) {
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
// Show individual stream notifications // Show individual stream notifications
showStreamNotifications(newStreams, data.serviceId, null) showStreamNotifications(newStreams, data.serviceId, data.url, null)
// Show summary notification // Show summary notification
manager.notify(data.pseudoId, summaryBuilder.build()) manager.notify(data.pseudoId, summaryBuilder.build())
iconLoadingTargets.remove(this) // allow it to be garbage-collected iconLoadingTargets.remove(this) // allow it to be garbage-collected
@ -118,10 +118,11 @@ class NotificationHelper(val context: Context) {
private fun showStreamNotifications( private fun showStreamNotifications(
newStreams: List<StreamInfoItem>, newStreams: List<StreamInfoItem>,
serviceId: Int, serviceId: Int,
channelUrl: String,
channelIcon: Bitmap? channelIcon: Bitmap?
) { ) {
for (stream in newStreams) { for (stream in newStreams) {
val notification = createStreamNotification(stream, serviceId, channelIcon) val notification = createStreamNotification(stream, serviceId, channelUrl, channelIcon)
manager.notify(stream.url.hashCode(), notification) manager.notify(stream.url.hashCode(), notification)
} }
} }
@ -129,6 +130,7 @@ class NotificationHelper(val context: Context) {
private fun createStreamNotification( private fun createStreamNotification(
item: StreamInfoItem, item: StreamInfoItem,
serviceId: Int, serviceId: Int,
channelUrl: String,
channelIcon: Bitmap? channelIcon: Bitmap?
): Notification { ): Notification {
return NotificationCompat.Builder( return NotificationCompat.Builder(
@ -139,7 +141,7 @@ class NotificationHelper(val context: Context) {
.setLargeIcon(channelIcon) .setLargeIcon(channelIcon)
.setContentTitle(item.name) .setContentTitle(item.name)
.setContentText(item.uploaderName) .setContentText(item.uploaderName)
.setGroup(item.uploaderUrl) .setGroup(channelUrl)
.setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background))
.setColorized(true) .setColorized(true)
.setAutoCancel(true) .setAutoCancel(true)

View File

@ -0,0 +1,72 @@
package org.schabi.newpipe.local.playlist
import android.content.Context
import org.schabi.newpipe.R
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.extractor.exceptions.ParsingException
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
import org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS
import org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES
import org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST
fun export(
shareMode: PlayListShareMode,
playlist: List<PlaylistStreamEntry>,
context: Context
): String {
return when (shareMode) {
WITH_TITLES -> exportWithTitles(playlist, context)
JUST_URLS -> exportJustUrls(playlist)
YOUTUBE_TEMP_PLAYLIST -> exportAsYoutubeTempPlaylist(playlist)
}
}
fun exportWithTitles(
playlist: List<PlaylistStreamEntry>,
context: Context
): String {
return playlist.asSequence()
.map { it.streamEntity }
.map { entity ->
context.getString(
R.string.video_details_list_item,
entity.title,
entity.url
)
}
.joinToString(separator = "\n")
}
fun exportJustUrls(playlist: List<PlaylistStreamEntry>): String {
return playlist.asSequence()
.map { it.streamEntity.url }
.joinToString(separator = "\n")
}
fun exportAsYoutubeTempPlaylist(playlist: List<PlaylistStreamEntry>): String {
val videoIDs = playlist.asReversed().asSequence()
.map { it.streamEntity.url }
.mapNotNull(::getYouTubeId)
.take(50) // YouTube limitation: temp playlists can't have more than 50 items
.toList()
.asReversed()
.joinToString(separator = ",")
return "https://www.youtube.com/watch_videos?video_ids=$videoIDs"
}
val linkHandler: YoutubeStreamLinkHandlerFactory = YoutubeStreamLinkHandlerFactory.getInstance()
/**
* Gets the video id from a YouTube URL.
*
* @param url YouTube URL
* @return the video id
*/
fun getYouTubeId(url: String): String? {
return try { linkHandler.getId(url) } catch (e: ParsingException) { null }
}

View File

@ -2,8 +2,13 @@ package org.schabi.newpipe.local.playlist;
import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar; import static org.schabi.newpipe.error.ErrorUtil.showUiErrorSnackbar;
import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.local.playlist.ExportPlaylistKt.export;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.JUST_URLS;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.WITH_TITLES;
import static org.schabi.newpipe.local.playlist.PlayListShareMode.YOUTUBE_TEMP_PLAYLIST;
import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout; import static org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
@ -27,7 +32,6 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.viewbinding.ViewBinding; import androidx.viewbinding.ViewBinding;
import com.evernote.android.state.State; import com.evernote.android.state.State;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription; import org.reactivestreams.Subscription;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
@ -385,34 +389,41 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
/** /**
* Shares the playlist as a list of stream URLs if {@code shouldSharePlaylistDetails} is * Shares the playlist in one of 3 ways, depending on the value of {@code shareMode}:
* set to {@code false}. Shares the playlist name along with a list of video titles and URLs * <ul>
* if {@code shouldSharePlaylistDetails} is set to {@code true}. * <li>{@code JUST_URLS}: shares the URLs only.</li>
* <li>{@code WITH_TITLES}: each entry in the list is accompanied by its title.</li>
* <li>{@code YOUTUBE_TEMP_PLAYLIST}: shares as a YouTube temporary playlist.</li>
* </ul>
* *
* @param shouldSharePlaylistDetails Whether the playlist details should be included in the * @param shareMode The way the playlist should be shared.
* shared content.
*/ */
private void sharePlaylist(final boolean shouldSharePlaylistDetails) { private void sharePlaylist(final PlayListShareMode shareMode) {
final Context context = requireContext(); final Context context = requireContext();
disposables.add(playlistManager.getPlaylistStreams(playlistId) disposables.add(playlistManager.getPlaylistStreams(playlistId)
.flatMapSingle(playlist -> Single.just(playlist.stream() .flatMapSingle(playlist -> Single.just(export(
.map(PlaylistStreamEntry::getStreamEntity)
.map(streamEntity -> { shareMode,
if (shouldSharePlaylistDetails) { playlist,
return context.getString(R.string.video_details_list_item, context
streamEntity.getTitle(), streamEntity.getUrl()); )))
} else { .observeOn(AndroidSchedulers.mainThread())
return streamEntity.getUrl(); .subscribe(
} urlsText -> {
})
.collect(Collectors.joining("\n")))) final String content = shareMode == WITH_TITLES
.observeOn(AndroidSchedulers.mainThread()) ? context.getString(R.string.share_playlist_content_details,
.subscribe(urlsText -> ShareUtils.shareText( name,
context, name, shouldSharePlaylistDetails urlsText
? context.getString(R.string.share_playlist_content_details, )
name, urlsText) : urlsText), : urlsText;
throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)));
ShareUtils.shareText(context, name, content);
},
throwable -> showUiErrorSnackbar(this, "Sharing playlist", throwable)
)
);
} }
public void removeWatchedStreams(final boolean removePartiallyWatched) { public void removeWatchedStreams(final boolean removePartiallyWatched) {
@ -872,13 +883,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private void createShareConfirmationDialog() { private void createShareConfirmationDialog() {
new AlertDialog.Builder(requireContext()) new AlertDialog.Builder(requireContext())
.setTitle(R.string.share_playlist) .setTitle(R.string.share_playlist)
.setMessage(R.string.share_playlist_with_titles_message)
.setCancelable(true) .setCancelable(true)
.setPositiveButton(R.string.share_playlist_with_titles, (dialog, which) -> .setPositiveButton(R.string.share_playlist_with_titles, (dialog, which) ->
sharePlaylist(/* shouldSharePlaylistDetails= */ true) sharePlaylist(WITH_TITLES)
)
.setNeutralButton(R.string.share_playlist_as_youtube_temporary_playlist,
(dialog, which) -> sharePlaylist(YOUTUBE_TEMP_PLAYLIST)
) )
.setNegativeButton(R.string.share_playlist_with_list, (dialog, which) -> .setNegativeButton(R.string.share_playlist_with_list, (dialog, which) ->
sharePlaylist(/* shouldSharePlaylistDetails= */ false) sharePlaylist(JUST_URLS)
) )
.show(); .show();
} }

View File

@ -0,0 +1,8 @@
package org.schabi.newpipe.local.playlist;
public enum PlayListShareMode {
JUST_URLS,
WITH_TITLES,
YOUTUBE_TEMP_PLAYLIST
}

View File

@ -26,6 +26,10 @@ public class RemotePlaylistManager {
return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io()); return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io());
} }
public Flowable<PlaylistRemoteEntity> getPlaylist(final long playlistId) {
return playlistRemoteTable.getPlaylist(playlistId).subscribeOn(Schedulers.io());
}
public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) { public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) {
return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl()) return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl())
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());

View File

@ -76,7 +76,10 @@ public class SubscriptionsExportService extends BaseImportExportService {
try { try {
outFile = new StoredFileHelper(this, path, "application/json"); outFile = new StoredFileHelper(this, path, "application/json");
outputStream = new SharpOutputStream(outFile.getStream()); // truncate the file before writing to it, otherwise if the new content is smaller than
// the previous file size, the file will retain part of the previous content and be
// corrupted
outputStream = new SharpOutputStream(outFile.openAndTruncateStream());
} catch (final IOException e) { } catch (final IOException e) {
handleError(e); handleError(e);
return START_NOT_STICKY; return START_NOT_STICKY;

View File

@ -183,7 +183,10 @@ public final class PlayQueueActivity extends AppCompatActivity
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
private void bind() { private void bind() {
// Note: this code should not really exist, and PlayerHolder should be used instead, but
// it will be rewritten when NewPlayer will replace the current player.
final Intent bindIntent = new Intent(this, PlayerService.class); final Intent bindIntent = new Intent(this, PlayerService.class);
bindIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);
if (!success) { if (!success) {
unbindService(serviceConnection); unbindService(serviceConnection);
@ -221,7 +224,7 @@ public final class PlayQueueActivity extends AppCompatActivity
Log.d(TAG, "Player service is connected"); Log.d(TAG, "Player service is connected");
if (service instanceof PlayerService.LocalBinder) { if (service instanceof PlayerService.LocalBinder) {
player = ((PlayerService.LocalBinder) service).getPlayer(); player = ((PlayerService.LocalBinder) service).getService().getPlayer();
} }
if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) { if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) {

View File

@ -55,6 +55,7 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.AudioManager; import android.media.AudioManager;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -71,6 +72,7 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.PositionInfo; import com.google.android.exoplayer2.Player.PositionInfo;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.Tracks;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@ -269,7 +271,16 @@ public final class Player implements PlaybackListener, Listener {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
//region Constructor //region Constructor
public Player(@NonNull final PlayerService service) { /**
* @param service the service this player resides in
* @param mediaSession used to build the {@link MediaSessionPlayerUi}, lives in the service and
* could possibly be reused with multiple player instances
* @param sessionConnector used to build the {@link MediaSessionPlayerUi}, lives in the service
* and could possibly be reused with multiple player instances
*/
public Player(@NonNull final PlayerService service,
@NonNull final MediaSessionCompat mediaSession,
@NonNull final MediaSessionConnector sessionConnector) {
this.service = service; this.service = service;
context = service; context = service;
prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs = PreferenceManager.getDefaultSharedPreferences(context);
@ -302,7 +313,7 @@ public final class Player implements PlaybackListener, Listener {
// notification ui in the UIs list, since the notification depends on the media session in // notification ui in the UIs list, since the notification depends on the media session in
// PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved. // PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved.
UIs = new PlayerUiList( UIs = new PlayerUiList(
new MediaSessionPlayerUi(this), new MediaSessionPlayerUi(this, mediaSession, sessionConnector),
new NotificationPlayerUi(this) new NotificationPlayerUi(this)
); );
} }
@ -462,7 +473,6 @@ public final class Player implements PlaybackListener, Listener {
if (oldPlayerType != playerType && playQueue != null) { if (oldPlayerType != playerType && playQueue != null) {
// If playerType changes from one to another we should reload the player // If playerType changes from one to another we should reload the player
// (to disable/enable video stream or to set quality) // (to disable/enable video stream or to set quality)
setRecovery();
reloadPlayQueueManager(); reloadPlayQueueManager();
} }
@ -647,7 +657,7 @@ public final class Player implements PlaybackListener, Listener {
Log.d(TAG, "onPlaybackShutdown() called"); Log.d(TAG, "onPlaybackShutdown() called");
} }
// destroys the service, which in turn will destroy the player // destroys the service, which in turn will destroy the player
service.stopService(); service.destroyPlayerAndStopService();
} }
public void smoothStopForImmediateReusing() { public void smoothStopForImmediateReusing() {
@ -719,7 +729,7 @@ public final class Player implements PlaybackListener, Listener {
pause(); pause();
break; break;
case ACTION_CLOSE: case ACTION_CLOSE:
service.stopService(); service.destroyPlayerAndStopService();
break; break;
case ACTION_PLAY_PAUSE: case ACTION_PLAY_PAUSE:
playPause(); playPause();
@ -1376,6 +1386,19 @@ public final class Player implements PlaybackListener, Listener {
public void onCues(@NonNull final CueGroup cueGroup) { public void onCues(@NonNull final CueGroup cueGroup) {
UIs.call(playerUi -> playerUi.onCues(cueGroup.cues)); UIs.call(playerUi -> playerUi.onCues(cueGroup.cues));
} }
/**
* To be called when the {@code PlaybackPreparer} set in the {@link MediaSessionConnector}
* receives an {@code onPrepare()} call. This function allows restoring the default behavior
* that would happen if there was no playback preparer set, i.e. to just call
* {@code player.prepare()}. You can find the default behavior in `onPlay()` inside the
* {@link MediaSessionConnector} file.
*/
public void onPrepare() {
if (!exoPlayerIsNull()) {
simpleExoPlayer.prepare();
}
}
//endregion //endregion

View File

@ -21,75 +21,142 @@ package org.schabi.newpipe.player;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ServiceCompat;
import androidx.media.MediaBrowserServiceCompat;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import org.schabi.newpipe.ktx.BundleKt;
import org.schabi.newpipe.player.mediabrowser.MediaBrowserImpl;
import org.schabi.newpipe.player.mediabrowser.MediaBrowserPlaybackPreparer;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List;
import java.util.function.Consumer;
/** /**
* One service for all players. * One service for all players.
*/ */
public final class PlayerService extends Service { public final class PlayerService extends MediaBrowserServiceCompat {
private static final String TAG = PlayerService.class.getSimpleName(); private static final String TAG = PlayerService.class.getSimpleName();
private static final boolean DEBUG = Player.DEBUG; private static final boolean DEBUG = Player.DEBUG;
public static final String SHOULD_START_FOREGROUND_EXTRA = "should_start_foreground_extra";
public static final String BIND_PLAYER_HOLDER_ACTION = "bind_player_holder_action";
// These objects are used to cleanly separate the Service implementation (in this file) and the
// media browser and playback preparer implementations. At the moment the playback preparer is
// only used in conjunction with the media browser.
private MediaBrowserImpl mediaBrowserImpl;
private MediaBrowserPlaybackPreparer mediaBrowserPlaybackPreparer;
// these are instantiated in onCreate() as per
// https://developer.android.com/training/cars/media#browser_workflow
private MediaSessionCompat mediaSession;
private MediaSessionConnector sessionConnector;
@Nullable
private Player player; private Player player;
private final IBinder mBinder = new PlayerService.LocalBinder(this); private final IBinder mBinder = new PlayerService.LocalBinder(this);
/**
* The parameter taken by this {@link Consumer} can be null to indicate the player is being
* stopped.
*/
@Nullable
private Consumer<Player> onPlayerStartedOrStopped = null;
/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
//region Service lifecycle
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate();
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onCreate() called"); Log.d(TAG, "onCreate() called");
} }
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
player = new Player(this); mediaBrowserImpl = new MediaBrowserImpl(this, this::notifyChildrenChanged);
/*
Create the player notification and start immediately the service in foreground, // see https://developer.android.com/training/cars/media#browser_workflow
otherwise if nothing is played or initializing the player and its components (especially mediaSession = new MediaSessionCompat(this, "MediaSessionPlayerServ");
loading stream metadata) takes a lot of time, the app would crash on Android 8+ as the setSessionToken(mediaSession.getSessionToken());
service would never be put in the foreground while we said to the system we would do so sessionConnector = new MediaSessionConnector(mediaSession);
*/ sessionConnector.setMetadataDeduplicationEnabled(true);
player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground); mediaBrowserPlaybackPreparer = new MediaBrowserPlaybackPreparer(
this,
sessionConnector::setCustomErrorMessage,
() -> sessionConnector.setCustomErrorMessage(null),
(playWhenReady) -> {
if (player != null) {
player.onPrepare();
}
}
);
sessionConnector.setPlaybackPreparer(mediaBrowserPlaybackPreparer);
// Note: you might be tempted to create the player instance and call startForeground here,
// but be aware that the Android system might start the service just to perform media
// queries. In those cases creating a player instance is a waste of resources, and calling
// startForeground means creating a useless empty notification. In case it's really needed
// the player instance can be created here, but startForeground() should definitely not be
// called here unless the service is actually starting in the foreground, to avoid the
// useless notification.
} }
@Override @Override
public int onStartCommand(final Intent intent, final int flags, final int startId) { public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent Log.d(TAG, "onStartCommand() called with: intent = [" + intent
+ "], extras = [" + BundleKt.toDebugString(intent.getExtras())
+ "], flags = [" + flags + "], startId = [" + startId + "]"); + "], flags = [" + flags + "], startId = [" + startId + "]");
} }
/* // All internal NewPipe intents used to interact with the player, that are sent to the
Be sure that the player notification is set and the service is started in foreground, // PlayerService using startForegroundService(), will have SHOULD_START_FOREGROUND_EXTRA,
otherwise, the app may crash on Android 8+ as the service would never be put in the // to ensure startForeground() is called (otherwise Android will force-crash the app).
foreground while we said to the system we would do so if (intent.getBooleanExtra(SHOULD_START_FOREGROUND_EXTRA, false)) {
The service is always requested to be started in foreground, so always creating a final boolean playerWasNull = (player == null);
notification if there is no one already and starting the service in foreground should if (playerWasNull) {
not create any issues // make sure the player exists, in case the service was resumed
If the service is already started in foreground, requesting it to be started shouldn't player = new Player(this, mediaSession, sessionConnector);
do anything }
*/
if (player != null) { // Be sure that the player notification is set and the service is started in foreground,
// otherwise, the app may crash on Android 8+ as the service would never be put in the
// foreground while we said to the system we would do so. The service is always
// requested to be started in foreground, so always creating a notification if there is
// no one already and starting the service in foreground should not create any issues.
// If the service is already started in foreground, requesting it to be started
// shouldn't do anything.
player.UIs().get(NotificationPlayerUi.class) player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground); .ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
if (playerWasNull && onPlayerStartedOrStopped != null) {
// notify that a new player was created (but do it after creating the foreground
// notification just to make sure we don't incur, due to slowness, in
// "Context.startForegroundService() did not then call Service.startForeground()")
onPlayerStartedOrStopped.accept(player);
}
} }
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
@ -100,7 +167,7 @@ public final class PlayerService extends Service {
Stop the service in this case, which will be removed from the foreground and its Stop the service in this case, which will be removed from the foreground and its
notification cancelled in its destruction notification cancelled in its destruction
*/ */
stopSelf(); destroyPlayerAndStopService();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
@ -142,29 +209,84 @@ public final class PlayerService extends Service {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "destroy() called"); Log.d(TAG, "destroy() called");
} }
super.onDestroy();
cleanup(); cleanup();
mediaBrowserPlaybackPreparer.dispose();
mediaSession.release();
mediaBrowserImpl.dispose();
} }
private void cleanup() { private void cleanup() {
if (player != null) { if (player != null) {
if (onPlayerStartedOrStopped != null) {
// notify that the player is being destroyed
onPlayerStartedOrStopped.accept(null);
}
player.destroy(); player.destroy();
player = null; player = null;
} }
// Should already be handled by MediaSessionPlayerUi, but just to be sure.
mediaSession.setActive(false);
// Should already be handled by NotificationUtil.cancelNotificationAndStopForeground() in
// NotificationPlayerUi, but let's make sure that the foreground service is stopped.
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE);
} }
public void stopService() { /**
* Destroys the player and allows the player instance to be garbage collected. Sets the media
* session to inactive. Stops the foreground service and removes the player notification
* associated with it. Tries to stop the {@link PlayerService} completely, but this step will
* have no effect in case some service connection still uses the service (e.g. the Android Auto
* system accesses the media browser even when no player is running).
*/
public void destroyPlayerAndStopService() {
if (DEBUG) {
Log.d(TAG, "destroyPlayerAndStopService() called");
}
cleanup(); cleanup();
stopSelf();
// This only really stops the service if there are no other service connections (see docs):
// for example the (Android Auto) media browser binder will block stopService().
// This is why we also stopForeground() above, to make sure the notification is removed.
// If we were to call stopSelf(), then the service would be surely stopped (regardless of
// other service connections), but this would be a waste of resources since the service
// would be immediately restarted by those same connections to perform the queries.
stopService(new Intent(this, PlayerService.class));
} }
@Override @Override
protected void attachBaseContext(final Context base) { protected void attachBaseContext(final Context base) {
super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base)); super.attachBaseContext(AudioServiceLeakFix.preventLeakOf(base));
} }
//endregion
//region Bind
@Override @Override
public IBinder onBind(final Intent intent) { public IBinder onBind(final Intent intent) {
return mBinder; if (DEBUG) {
Log.d(TAG, "onBind() called with: intent = [" + intent
+ "], extras = [" + BundleKt.toDebugString(intent.getExtras()) + "]");
}
if (BIND_PLAYER_HOLDER_ACTION.equals(intent.getAction())) {
// Note that this binder might be reused multiple times while the service is alive, even
// after unbind() has been called: https://stackoverflow.com/a/8794930 .
return mBinder;
} else if (MediaBrowserServiceCompat.SERVICE_INTERFACE.equals(intent.getAction())) {
// MediaBrowserService also uses its own binder, so for actions related to the media
// browser service, pass the onBind to the superclass.
return super.onBind(intent);
} else {
// This is an unknown request, avoid returning any binder to not leak objects.
return null;
}
} }
public static class LocalBinder extends Binder { public static class LocalBinder extends Binder {
@ -177,9 +299,52 @@ public final class PlayerService extends Service {
public PlayerService getService() { public PlayerService getService() {
return playerService.get(); return playerService.get();
} }
}
public Player getPlayer() { /**
return playerService.get().player; * @return the current active player instance. May be null, since the player service can outlive
* the player e.g. to respond to Android Auto media browser queries.
*/
@Nullable
public Player getPlayer() {
return player;
}
/**
* Sets the listener that will be called when the player is started or stopped. If a
* {@code null} listener is passed, then the current listener will be unset. The parameter taken
* by the {@link Consumer} can be null to indicate that the player is stopping.
* @param listener the listener to set or unset
*/
public void setPlayerListener(@Nullable final Consumer<Player> listener) {
this.onPlayerStartedOrStopped = listener;
if (listener != null) {
// if there is no player, then `null` will be sent here, to ensure the state is synced
listener.accept(player);
} }
} }
//endregion
//region Media browser
@Override
public BrowserRoot onGetRoot(@NonNull final String clientPackageName,
final int clientUid,
@Nullable final Bundle rootHints) {
// TODO check if the accessing package has permission to view data
return mediaBrowserImpl.onGetRoot(clientPackageName, clientUid, rootHints);
}
@Override
public void onLoadChildren(@NonNull final String parentId,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
mediaBrowserImpl.onLoadChildren(parentId, result);
}
@Override
public void onSearch(@NonNull final String query,
final Bundle extras,
@NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
mediaBrowserImpl.onSearch(query, result);
}
//endregion
} }

View File

@ -14,10 +14,12 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getAndroidUserAgent; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getAndroidUserAgent;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getIosUserAgent; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getIosUserAgent;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTvHtml5UserAgent;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isAndroidStreamingUrl; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isAndroidStreamingUrl;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isIosStreamingUrl; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isIosStreamingUrl;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isTvHtml5StreamingUrl;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebStreamingUrl; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebStreamingUrl;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isTvHtml5SimplyEmbeddedPlayerStreamingUrl; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isWebEmbeddedPlayerStreamingUrl;
import static java.lang.Math.min; import static java.lang.Math.min;
import android.net.Uri; import android.net.Uri;
@ -270,6 +272,7 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
private static final String RN_PARAMETER = "&rn="; private static final String RN_PARAMETER = "&rn=";
private static final String YOUTUBE_BASE_URL = "https://www.youtube.com"; private static final String YOUTUBE_BASE_URL = "https://www.youtube.com";
private static final byte[] POST_BODY = new byte[] {0x78, 0};
private final boolean allowCrossProtocolRedirects; private final boolean allowCrossProtocolRedirects;
private final boolean rangeParameterEnabled; private final boolean rangeParameterEnabled;
@ -658,8 +661,11 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
} }
} }
final boolean isTvHtml5StreamingUrl = isTvHtml5StreamingUrl(requestUrl);
if (isWebStreamingUrl(requestUrl) if (isWebStreamingUrl(requestUrl)
|| isTvHtml5SimplyEmbeddedPlayerStreamingUrl(requestUrl)) { || isTvHtml5StreamingUrl
|| isWebEmbeddedPlayerStreamingUrl(requestUrl)) {
httpURLConnection.setRequestProperty(HttpHeaders.ORIGIN, YOUTUBE_BASE_URL); httpURLConnection.setRequestProperty(HttpHeaders.ORIGIN, YOUTUBE_BASE_URL);
httpURLConnection.setRequestProperty(HttpHeaders.REFERER, YOUTUBE_BASE_URL); httpURLConnection.setRequestProperty(HttpHeaders.REFERER, YOUTUBE_BASE_URL);
httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_DEST, "empty"); httpURLConnection.setRequestProperty(HttpHeaders.SEC_FETCH_DEST, "empty");
@ -679,6 +685,9 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
} else if (isIosStreamingUrl) { } else if (isIosStreamingUrl) {
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
getIosUserAgent(null)); getIosUserAgent(null));
} else if (isTvHtml5StreamingUrl) {
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT,
getTvHtml5UserAgent());
} else { } else {
// non-mobile user agent // non-mobile user agent
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, DownloaderImpl.USER_AGENT); httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, DownloaderImpl.USER_AGENT);
@ -687,22 +696,16 @@ public final class YoutubeHttpDataSource extends BaseDataSource implements HttpD
httpURLConnection.setRequestProperty(HttpHeaders.ACCEPT_ENCODING, httpURLConnection.setRequestProperty(HttpHeaders.ACCEPT_ENCODING,
allowGzip ? "gzip" : "identity"); allowGzip ? "gzip" : "identity");
httpURLConnection.setInstanceFollowRedirects(followRedirects); httpURLConnection.setInstanceFollowRedirects(followRedirects);
httpURLConnection.setDoOutput(httpBody != null); // Most clients use POST requests to fetch contents
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setDoOutput(true);
httpURLConnection.setFixedLengthStreamingMode(POST_BODY.length);
httpURLConnection.connect();
// Mobile clients uses POST requests to fetch contents final OutputStream os = httpURLConnection.getOutputStream();
httpURLConnection.setRequestMethod(isAndroidStreamingUrl || isIosStreamingUrl os.write(POST_BODY);
? "POST" os.close();
: DataSpec.getStringForHttpMethod(httpMethod));
if (httpBody != null) {
httpURLConnection.setFixedLengthStreamingMode(httpBody.length);
httpURLConnection.connect();
final OutputStream os = httpURLConnection.getOutputStream();
os.write(httpBody);
os.close();
} else {
httpURLConnection.connect();
}
return httpURLConnection; return httpURLConnection;
} }

View File

@ -1,11 +1,48 @@
package org.schabi.newpipe.player.event; package org.schabi.newpipe.player.event;
import androidx.annotation.NonNull;
import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.Player;
/**
* In addition to {@link PlayerServiceEventListener}, provides callbacks for service and player
* connections and disconnections. "Connected" here means that the service (resp. the
* player) is running and is bound to {@link org.schabi.newpipe.player.helper.PlayerHolder}.
* "Disconnected" means that either the service (resp. the player) was stopped completely, or that
* {@link org.schabi.newpipe.player.helper.PlayerHolder} is not bound.
*/
public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener { public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener {
void onServiceConnected(Player player, /**
PlayerService playerService, * The player service just connected to {@link org.schabi.newpipe.player.helper.PlayerHolder},
boolean playAfterConnect); * but the player may not be active at this moment, e.g. in case the service is running to
* respond to Android Auto media browser queries without playing anything.
* {@link #onPlayerConnected(Player, boolean)} will be called right after this function if there
* is a player.
*
* @param playerService the newly connected player service
*/
void onServiceConnected(@NonNull PlayerService playerService);
/**
* The player service is already connected and the player was just started.
*
* @param player the newly connected or started player
* @param playAfterConnect whether to open the video player in the video details fragment
*/
void onPlayerConnected(@NonNull Player player, boolean playAfterConnect);
/**
* The player got disconnected, for one of these reasons: the player is getting closed while
* leaving the service open for future media browser queries, the service is stopping
* completely, or {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding.
*/
void onPlayerDisconnected();
/**
* The service got disconnected from {@link org.schabi.newpipe.player.helper.PlayerHolder},
* either because {@link org.schabi.newpipe.player.helper.PlayerHolder} is unbinding or because
* the service is stopping completely.
*/
void onServiceDisconnected(); void onServiceDisconnected();
} }

View File

@ -22,6 +22,10 @@ import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener;
import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.Optional;
import java.util.function.Consumer;
public final class PlayerHolder { public final class PlayerHolder {
@ -44,7 +48,16 @@ public final class PlayerHolder {
private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection();
private boolean bound; private boolean bound;
@Nullable private PlayerService playerService; @Nullable private PlayerService playerService;
@Nullable private Player player;
private Optional<Player> getPlayer() {
return Optional.ofNullable(playerService)
.flatMap(s -> Optional.ofNullable(s.getPlayer()));
}
private Optional<PlayQueue> getPlayQueue() {
// player play queue might be null e.g. while player is starting
return getPlayer().flatMap(p -> Optional.ofNullable(p.getPlayQueue()));
}
/** /**
* Returns the current {@link PlayerType} of the {@link PlayerService} service, * Returns the current {@link PlayerType} of the {@link PlayerService} service,
@ -54,21 +67,15 @@ public final class PlayerHolder {
*/ */
@Nullable @Nullable
public PlayerType getType() { public PlayerType getType() {
if (player == null) { return getPlayer().map(Player::getPlayerType).orElse(null);
return null;
}
return player.getPlayerType();
} }
public boolean isPlaying() { public boolean isPlaying() {
if (player == null) { return getPlayer().map(Player::isPlaying).orElse(false);
return false;
}
return player.isPlaying();
} }
public boolean isPlayerOpen() { public boolean isPlayerOpen() {
return player != null; return getPlayer().isPresent();
} }
/** /**
@ -77,7 +84,7 @@ public final class PlayerHolder {
* @return true only if the player is open and its play queue is ready (i.e. it is not null) * @return true only if the player is open and its play queue is ready (i.e. it is not null)
*/ */
public boolean isPlayQueueReady() { public boolean isPlayQueueReady() {
return player != null && player.getPlayQueue() != null; return getPlayQueue().isPresent();
} }
public boolean isBound() { public boolean isBound() {
@ -85,18 +92,11 @@ public final class PlayerHolder {
} }
public int getQueueSize() { public int getQueueSize() {
if (player == null || player.getPlayQueue() == null) { return getPlayQueue().map(PlayQueue::size).orElse(0);
// player play queue might be null e.g. while player is starting
return 0;
}
return player.getPlayQueue().size();
} }
public int getQueuePosition() { public int getQueuePosition() {
if (player == null || player.getPlayQueue() == null) { return getPlayQueue().map(PlayQueue::getIndex).orElse(0);
return 0;
}
return player.getPlayQueue().getIndex();
} }
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) { public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
@ -107,9 +107,10 @@ public final class PlayerHolder {
} }
// Force reload data from service // Force reload data from service
if (player != null) { if (playerService != null) {
listener.onServiceConnected(player, playerService, false); listener.onServiceConnected(playerService);
startPlayerListener(); startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
} }
} }
@ -121,6 +122,9 @@ public final class PlayerHolder {
public void startService(final boolean playAfterConnect, public void startService(final boolean playAfterConnect,
final PlayerServiceExtendedEventListener newListener) { final PlayerServiceExtendedEventListener newListener) {
if (DEBUG) {
Log.d(TAG, "startService() called with playAfterConnect=" + playAfterConnect);
}
final Context context = getCommonContext(); final Context context = getCommonContext();
setListener(newListener); setListener(newListener);
if (bound) { if (bound) {
@ -130,14 +134,24 @@ public final class PlayerHolder {
// and NullPointerExceptions inside the service because the service will be // and NullPointerExceptions inside the service because the service will be
// bound twice. Prevent it with unbinding first // bound twice. Prevent it with unbinding first
unbind(context); unbind(context);
ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class)); final Intent intent = new Intent(context, PlayerService.class);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
ContextCompat.startForegroundService(context, intent);
serviceConnection.doPlayAfterConnect(playAfterConnect); serviceConnection.doPlayAfterConnect(playAfterConnect);
bind(context); bind(context);
} }
public void stopService() { public void stopService() {
if (DEBUG) {
Log.d(TAG, "stopService() called");
}
if (playerService != null) {
playerService.destroyPlayerAndStopService();
}
final Context context = getCommonContext(); final Context context = getCommonContext();
unbind(context); unbind(context);
// destroyPlayerAndStopService() already runs the next line of code, but run it again just
// to make sure to stop the service even if playerService is null by any chance.
context.stopService(new Intent(context, PlayerService.class)); context.stopService(new Intent(context, PlayerService.class));
} }
@ -167,11 +181,16 @@ public final class PlayerHolder {
final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service; final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service;
playerService = localBinder.getService(); playerService = localBinder.getService();
player = localBinder.getPlayer();
if (listener != null) { if (listener != null) {
listener.onServiceConnected(player, playerService, playAfterConnect); listener.onServiceConnected(playerService);
getPlayer().ifPresent(p -> listener.onPlayerConnected(p, playAfterConnect));
} }
startPlayerListener(); startPlayerListener();
// ^ will call listener.onPlayerConnected() down the line if there is an active player
// notify the main activity that binding the service has completed, so that it can
// open the bottom mini-player
NavigationHelper.sendPlayerStartedEvent(localBinder.getService());
} }
} }
@ -179,15 +198,28 @@ public final class PlayerHolder {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "bind() called"); Log.d(TAG, "bind() called");
} }
// BIND_AUTO_CREATE starts the service if it's not already running
final Intent serviceIntent = new Intent(context, PlayerService.class); bound = bind(context, Context.BIND_AUTO_CREATE);
bound = context.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
if (!bound) { if (!bound) {
context.unbindService(serviceConnection); context.unbindService(serviceConnection);
} }
} }
public void tryBindIfNeeded(final Context context) {
if (!bound) {
// flags=0 means the service will not be started if it does not already exist. In this
// case the return value is not useful, as a value of "true" does not really indicate
// that the service is going to be bound.
bind(context, 0);
}
}
private boolean bind(final Context context, final int flags) {
final Intent serviceIntent = new Intent(context, PlayerService.class);
serviceIntent.setAction(PlayerService.BIND_PLAYER_HOLDER_ACTION);
return context.bindService(serviceIntent, serviceConnection, flags);
}
private void unbind(final Context context) { private void unbind(final Context context) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "unbind() called"); Log.d(TAG, "unbind() called");
@ -198,25 +230,32 @@ public final class PlayerHolder {
bound = false; bound = false;
stopPlayerListener(); stopPlayerListener();
playerService = null; playerService = null;
player = null;
if (listener != null) { if (listener != null) {
listener.onPlayerDisconnected();
listener.onServiceDisconnected(); listener.onServiceDisconnected();
} }
} }
} }
private void startPlayerListener() { private void startPlayerListener() {
if (player != null) { if (playerService != null) {
player.setFragmentListener(internalListener); // setting the player listener will take care of calling relevant callbacks if the
// player in the service is (not) already active, also see playerStateListener below
playerService.setPlayerListener(playerStateListener);
} }
getPlayer().ifPresent(p -> p.setFragmentListener(internalListener));
} }
private void stopPlayerListener() { private void stopPlayerListener() {
if (player != null) { if (playerService != null) {
player.removeFragmentListener(internalListener); playerService.setPlayerListener(null);
} }
getPlayer().ifPresent(p -> p.removeFragmentListener(internalListener));
} }
/**
* This listener will be held by the players created by {@link PlayerService}.
*/
private final PlayerServiceEventListener internalListener = private final PlayerServiceEventListener internalListener =
new PlayerServiceEventListener() { new PlayerServiceEventListener() {
@Override @Override
@ -303,4 +342,23 @@ public final class PlayerHolder {
unbind(getCommonContext()); unbind(getCommonContext());
} }
}; };
/**
* This listener will be held by bound {@link PlayerService}s to notify of the player starting
* or stopping. This is necessary since the service outlives the player e.g. to answer Android
* Auto media browser queries.
*/
private final Consumer<Player> playerStateListener = (@Nullable final Player player) -> {
if (listener != null) {
if (player == null) {
// player.fragmentListener=null is already done by player.stopActivityBinding(),
// which is called by player.destroy(), which is in turn called by PlayerService
// before setting its player to null
listener.onPlayerDisconnected();
} else {
listener.onPlayerConnected(player, serviceConnection.playAfterConnect);
player.setFragmentListener(internalListener);
}
}
};
} }

View File

@ -0,0 +1,40 @@
package org.schabi.newpipe.player.mediabrowser
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
internal const val ID_AUTHORITY = BuildConfig.APPLICATION_ID
internal const val ID_ROOT = "//$ID_AUTHORITY"
internal const val ID_BOOKMARKS = "playlists"
internal const val ID_HISTORY = "history"
internal const val ID_INFO_ITEM = "item"
internal const val ID_LOCAL = "local"
internal const val ID_REMOTE = "remote"
internal const val ID_URL = "url"
internal const val ID_STREAM = "stream"
internal const val ID_PLAYLIST = "playlist"
internal const val ID_CHANNEL = "channel"
internal fun infoItemTypeToString(type: InfoType): String {
return when (type) {
InfoType.STREAM -> ID_STREAM
InfoType.PLAYLIST -> ID_PLAYLIST
InfoType.CHANNEL -> ID_CHANNEL
else -> throw IllegalStateException("Unexpected value: $type")
}
}
internal fun infoItemTypeFromString(type: String): InfoType {
return when (type) {
ID_STREAM -> InfoType.STREAM
ID_PLAYLIST -> InfoType.PLAYLIST
ID_CHANNEL -> InfoType.CHANNEL
else -> throw IllegalStateException("Unexpected value: $type")
}
}
internal fun parseError(mediaId: String): ContentNotAvailableException {
return ContentNotAvailableException("Failed to parse media ID $mediaId")
}

View File

@ -0,0 +1,399 @@
package org.schabi.newpipe.player.mediabrowser
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.media.MediaBrowserServiceCompat
import androidx.media.MediaBrowserServiceCompat.Result
import androidx.media.utils.MediaConstants
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.R
import org.schabi.newpipe.database.history.model.StreamHistoryEntry
import org.schabi.newpipe.database.playlist.PlaylistLocalItem
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity
import org.schabi.newpipe.extractor.InfoItem
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
import org.schabi.newpipe.extractor.search.SearchInfo
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.bookmark.MergedPlaylistManager
import org.schabi.newpipe.local.playlist.LocalPlaylistManager
import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.image.ImageStrategy
import java.util.function.Consumer
/**
* This class is used to cleanly separate the Service implementation (in
* [org.schabi.newpipe.player.PlayerService]) and the media browser implementation (in this file).
*
* @param notifyChildrenChanged takes the parent id of the children that changed
*/
class MediaBrowserImpl(
private val context: Context,
notifyChildrenChanged: Consumer<String>, // parentId
) {
private val database = NewPipeDatabase.getInstance(context)
private var disposables = CompositeDisposable()
init {
// this will listen to changes in the bookmarks until this MediaBrowserImpl is dispose()d
disposables.add(
getMergedPlaylists().subscribe { notifyChildrenChanged.accept(ID_BOOKMARKS) }
)
}
//region Cleanup
fun dispose() {
disposables.dispose()
}
//endregion
//region onGetRoot
fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot {
if (DEBUG) {
Log.d(TAG, "onGetRoot($clientPackageName, $clientUid, $rootHints)")
}
val extras = Bundle()
extras.putBoolean(
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true
)
return MediaBrowserServiceCompat.BrowserRoot(ID_ROOT, extras)
}
//endregion
//region onLoadChildren
fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
if (DEBUG) {
Log.d(TAG, "onLoadChildren($parentId)")
}
result.detach() // allows sendResult() to happen later
disposables.add(
onLoadChildren(parentId)
.subscribe(
{ result.sendResult(it) },
{ throwable ->
// null indicates an error, see the docs of MediaSessionCompat.onSearch()
result.sendResult(null)
Log.e(TAG, "onLoadChildren error for parentId=$parentId: $throwable")
}
)
)
}
private fun onLoadChildren(parentId: String): Single<List<MediaBrowserCompat.MediaItem>> {
try {
val parentIdUri = Uri.parse(parentId)
val path = ArrayList(parentIdUri.pathSegments)
if (path.isEmpty()) {
return Single.just(
listOf(
createRootMediaItem(
ID_BOOKMARKS,
context.resources.getString(R.string.tab_bookmarks_short),
R.drawable.ic_bookmark_white
),
createRootMediaItem(
ID_HISTORY,
context.resources.getString(R.string.action_history),
R.drawable.ic_history_white
)
)
)
}
when (/*val uriType = */path.removeAt(0)) {
ID_BOOKMARKS -> {
if (path.isEmpty()) {
return populateBookmarks()
}
if (path.size == 2) {
val localOrRemote = path[0]
val playlistId = path[1].toLong()
if (localOrRemote == ID_LOCAL) {
return populateLocalPlaylist(playlistId)
} else if (localOrRemote == ID_REMOTE) {
return populateRemotePlaylist(playlistId)
}
}
Log.w(TAG, "Unknown playlist URI: $parentId")
throw parseError(parentId)
}
ID_HISTORY -> return populateHistory()
else -> throw parseError(parentId)
}
} catch (e: ContentNotAvailableException) {
return Single.error(e)
}
}
private fun createRootMediaItem(
mediaId: String?,
folderName: String?,
@DrawableRes iconResId: Int
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(mediaId)
builder.setTitle(folderName)
val resources = context.resources
builder.setIconUri(
Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(resources.getResourcePackageName(iconResId))
.appendPath(resources.getResourceTypeName(iconResId))
.appendPath(resources.getResourceEntryName(iconResId))
.build()
)
val extras = Bundle()
extras.putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
context.getString(R.string.app_name)
)
builder.setExtras(extras)
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
}
private fun createPlaylistMediaItem(playlist: PlaylistLocalItem): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder
.setMediaId(createMediaIdForInfoItem(playlist is PlaylistRemoteEntity, playlist.uid))
.setTitle(playlist.orderingName)
.setIconUri(playlist.thumbnailUrl?.let { Uri.parse(it) })
val extras = Bundle()
extras.putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
context.resources.getString(R.string.tab_bookmarks),
)
builder.setExtras(extras)
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE,
)
}
private fun createInfoItemMediaItem(item: InfoItem): MediaBrowserCompat.MediaItem? {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForInfoItem(item))
.setTitle(item.name)
when (item.infoType) {
InfoType.STREAM -> builder.setSubtitle((item as StreamInfoItem).uploaderName)
InfoType.PLAYLIST -> builder.setSubtitle((item as PlaylistInfoItem).uploaderName)
InfoType.CHANNEL -> builder.setSubtitle((item as ChannelInfoItem).description)
else -> return null
}
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(Uri.parse(it))
}
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun buildMediaId(): Uri.Builder {
return Uri.Builder().authority(ID_AUTHORITY)
}
private fun buildPlaylistMediaId(playlistType: String?): Uri.Builder {
return buildMediaId()
.appendPath(ID_BOOKMARKS)
.appendPath(playlistType)
}
private fun buildLocalPlaylistItemMediaId(isRemote: Boolean, playlistId: Long): Uri.Builder {
return buildPlaylistMediaId(if (isRemote) ID_REMOTE else ID_LOCAL)
.appendPath(playlistId.toString())
}
private fun buildInfoItemMediaId(item: InfoItem): Uri.Builder {
return buildMediaId()
.appendPath(ID_INFO_ITEM)
.appendPath(infoItemTypeToString(item.infoType))
.appendPath(item.serviceId.toString())
.appendQueryParameter(ID_URL, item.url)
}
private fun createMediaIdForInfoItem(isRemote: Boolean, playlistId: Long): String {
return buildLocalPlaylistItemMediaId(isRemote, playlistId)
.build().toString()
}
private fun createLocalPlaylistStreamMediaItem(
playlistId: Long,
item: PlaylistStreamEntry,
index: Int,
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForPlaylistIndex(false, playlistId, index))
.setTitle(item.streamEntity.title)
.setSubtitle(item.streamEntity.uploader)
.setIconUri(Uri.parse(item.streamEntity.thumbnailUrl))
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun createRemotePlaylistStreamMediaItem(
playlistId: Long,
item: StreamInfoItem,
index: Int,
): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
builder.setMediaId(createMediaIdForPlaylistIndex(true, playlistId, index))
.setTitle(item.name)
.setSubtitle(item.uploaderName)
ImageStrategy.choosePreferredImage(item.thumbnails)?.let {
builder.setIconUri(Uri.parse(it))
}
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun createMediaIdForPlaylistIndex(
isRemote: Boolean,
playlistId: Long,
index: Int,
): String {
return buildLocalPlaylistItemMediaId(isRemote, playlistId)
.appendPath(index.toString())
.build().toString()
}
private fun createMediaIdForInfoItem(item: InfoItem): String {
return buildInfoItemMediaId(item).build().toString()
}
private fun populateHistory(): Single<List<MediaBrowserCompat.MediaItem>> {
val history = database.streamHistoryDAO().getHistory().firstOrError()
return history.map { items ->
items.map { this.createHistoryMediaItem(it) }
}
}
private fun createHistoryMediaItem(streamHistoryEntry: StreamHistoryEntry): MediaBrowserCompat.MediaItem {
val builder = MediaDescriptionCompat.Builder()
val mediaId = buildMediaId()
.appendPath(ID_HISTORY)
.appendPath(streamHistoryEntry.streamId.toString())
.build().toString()
builder.setMediaId(mediaId)
.setTitle(streamHistoryEntry.streamEntity.title)
.setSubtitle(streamHistoryEntry.streamEntity.uploader)
.setIconUri(Uri.parse(streamHistoryEntry.streamEntity.thumbnailUrl))
return MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
}
private fun getMergedPlaylists(): Flowable<MutableList<PlaylistLocalItem>> {
return MergedPlaylistManager.getMergedOrderedPlaylists(
LocalPlaylistManager(database),
RemotePlaylistManager(database)
)
}
private fun populateBookmarks(): Single<List<MediaBrowserCompat.MediaItem>> {
val playlists = getMergedPlaylists().firstOrError()
return playlists.map { playlist ->
playlist.map { this.createPlaylistMediaItem(it) }
}
}
private fun populateLocalPlaylist(playlistId: Long): Single<List<MediaBrowserCompat.MediaItem>> {
val playlist = LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError()
return playlist.map { items ->
items.mapIndexed { index, item ->
createLocalPlaylistStreamMediaItem(playlistId, item, index)
}
}
}
private fun populateRemotePlaylist(playlistId: Long): Single<List<MediaBrowserCompat.MediaItem>> {
return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError()
.flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) }
.map {
// ignore it.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
it.relatedItems.mapIndexed { index, item ->
createRemotePlaylistStreamMediaItem(playlistId, item, index)
}
}
}
//endregion
//region Search
fun onSearch(
query: String,
result: Result<List<MediaBrowserCompat.MediaItem>>
) {
if (DEBUG) {
Log.d(TAG, "onSearch($query)")
}
result.detach() // allows sendResult() to happen later
disposables.add(
searchMusicBySongTitle(query)
// ignore it.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
.map { it.relatedItems.mapNotNull(this::createInfoItemMediaItem) }
.subscribeOn(Schedulers.io())
.subscribe(
{ result.sendResult(it) },
{ throwable ->
// null indicates an error, see the docs of MediaSessionCompat.onSearch()
result.sendResult(null)
Log.e(TAG, "Search error for query=\"$query\": $throwable")
}
)
)
}
private fun searchMusicBySongTitle(query: String?): Single<SearchInfo> {
val serviceId = ServiceHelper.getSelectedServiceId(context)
return ExtractorHelper.searchFor(serviceId, query, listOf(), "")
}
//endregion
companion object {
private val TAG: String = MediaBrowserImpl::class.java.getSimpleName()
}
}

View File

@ -0,0 +1,259 @@
package org.schabi.newpipe.player.mediabrowser
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.os.ResultReceiver
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector.PlaybackPreparer
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler
import org.schabi.newpipe.local.playlist.LocalPlaylistManager
import org.schabi.newpipe.local.playlist.RemotePlaylistManager
import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue
import org.schabi.newpipe.player.playqueue.PlayQueue
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue
import org.schabi.newpipe.player.playqueue.SinglePlayQueue
import org.schabi.newpipe.util.ChannelTabHelper
import org.schabi.newpipe.util.ExtractorHelper
import org.schabi.newpipe.util.NavigationHelper
import java.util.function.BiConsumer
import java.util.function.Consumer
/**
* This class is used to cleanly separate the Service implementation (in
* [org.schabi.newpipe.player.PlayerService]) and the playback preparer implementation (in this
* file). We currently use the playback preparer only in conjunction with the media browser: the
* playback preparer will receive the media URLs generated by [MediaBrowserImpl] and will start
* playback of the corresponding streams or playlists.
*
* @param setMediaSessionError takes an error String and an error code from [PlaybackStateCompat],
* calls `sessionConnector.setCustomErrorMessage(errorString, errorCode)`
* @param clearMediaSessionError calls `sessionConnector.setCustomErrorMessage(null)`
* @param onPrepare takes playWhenReady, calls `player.prepare()`; this is needed because
* `MediaSessionConnector`'s `onPlay()` method calls this class' [onPrepare] instead of
* `player.prepare()` if the playback preparer is not null, but we want the original behavior
*/
class MediaBrowserPlaybackPreparer(
private val context: Context,
private val setMediaSessionError: BiConsumer<String, Int>, // error string, error code
private val clearMediaSessionError: Runnable,
private val onPrepare: Consumer<Boolean>,
) : PlaybackPreparer {
private val database = NewPipeDatabase.getInstance(context)
private var disposable: Disposable? = null
fun dispose() {
disposable?.dispose()
}
//region Overrides
override fun getSupportedPrepareActions(): Long {
return PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
}
override fun onPrepare(playWhenReady: Boolean) {
onPrepare.accept(playWhenReady)
}
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
if (MainActivity.DEBUG) {
Log.d(TAG, "onPrepareFromMediaId($mediaId, $playWhenReady, $extras)")
}
disposable?.dispose()
disposable = extractPlayQueueFromMediaId(mediaId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ playQueue ->
clearMediaSessionError.run()
NavigationHelper.playOnBackgroundPlayer(context, playQueue, playWhenReady)
},
{ throwable ->
Log.e(TAG, "Failed to start playback of media ID [$mediaId]", throwable)
onPrepareError()
}
)
}
override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) {
onUnsupportedError()
}
override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) {
onUnsupportedError()
}
override fun onCommand(
player: Player,
command: String,
extras: Bundle?,
cb: ResultReceiver?
): Boolean {
return false
}
//endregion
//region Errors
private fun onUnsupportedError() {
setMediaSessionError.accept(
context.getString(R.string.content_not_supported),
PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED
)
}
private fun onPrepareError() {
setMediaSessionError.accept(
context.getString(R.string.error_snackbar_message),
PlaybackStateCompat.ERROR_CODE_APP_ERROR
)
}
//endregion
//region Building play queues from playlists and history
private fun extractLocalPlayQueue(playlistId: Long, index: Int): Single<PlayQueue> {
return LocalPlaylistManager(database).getPlaylistStreams(playlistId).firstOrError()
.map { items -> SinglePlayQueue(items.map { it.toStreamInfoItem() }, index) }
}
private fun extractRemotePlayQueue(playlistId: Long, index: Int): Single<PlayQueue> {
return RemotePlaylistManager(database).getPlaylist(playlistId).firstOrError()
.flatMap { ExtractorHelper.getPlaylistInfo(it.serviceId, it.url, false) }
// ignore info.errors, i.e. ignore errors about specific items, since there would
// be no way to show the error properly in Android Auto anyway
.map { info -> PlaylistPlayQueue(info, index) }
}
private fun extractPlayQueueFromMediaId(mediaId: String): Single<PlayQueue> {
try {
val mediaIdUri = Uri.parse(mediaId)
val path = ArrayList(mediaIdUri.pathSegments)
if (path.isEmpty()) {
throw parseError(mediaId)
}
return when (/*val uriType = */path.removeAt(0)) {
ID_BOOKMARKS -> extractPlayQueueFromPlaylistMediaId(
mediaId,
path,
mediaIdUri.getQueryParameter(ID_URL)
)
ID_HISTORY -> extractPlayQueueFromHistoryMediaId(mediaId, path)
ID_INFO_ITEM -> extractPlayQueueFromInfoItemMediaId(
mediaId,
path,
mediaIdUri.getQueryParameter(ID_URL) ?: throw parseError(mediaId)
)
else -> throw parseError(mediaId)
}
} catch (e: ContentNotAvailableException) {
return Single.error(e)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromPlaylistMediaId(
mediaId: String,
path: MutableList<String>,
url: String?,
): Single<PlayQueue> {
if (path.isEmpty()) {
throw parseError(mediaId)
}
when (val playlistType = path.removeAt(0)) {
ID_LOCAL, ID_REMOTE -> {
if (path.size != 2) {
throw parseError(mediaId)
}
val playlistId = path[0].toLong()
val index = path[1].toInt()
return if (playlistType == ID_LOCAL)
extractLocalPlayQueue(playlistId, index)
else
extractRemotePlayQueue(playlistId, index)
}
ID_URL -> {
if (path.size != 1 || url == null) {
throw parseError(mediaId)
}
val serviceId = path[0].toInt()
return ExtractorHelper.getPlaylistInfo(serviceId, url, false)
.map { PlaylistPlayQueue(it) }
}
else -> throw parseError(mediaId)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromHistoryMediaId(
mediaId: String,
path: List<String>,
): Single<PlayQueue> {
if (path.size != 1) {
throw parseError(mediaId)
}
val streamId = path[0].toLong()
return database.streamHistoryDAO().getHistory()
.firstOrError()
.map { items ->
val infoItems = items
.filter { it.streamId == streamId }
.map { it.toStreamInfoItem() }
SinglePlayQueue(infoItems, 0)
}
}
@Throws(ContentNotAvailableException::class)
private fun extractPlayQueueFromInfoItemMediaId(
mediaId: String,
path: List<String>,
url: String,
): Single<PlayQueue> {
if (path.size != 2) {
throw parseError(mediaId)
}
val serviceId = path[1].toInt()
return when (/*val infoItemType = */infoItemTypeFromString(path[0])) {
InfoType.STREAM -> ExtractorHelper.getStreamInfo(serviceId, url, false)
.map { SinglePlayQueue(it) }
InfoType.PLAYLIST -> ExtractorHelper.getPlaylistInfo(serviceId, url, false)
.map { PlaylistPlayQueue(it) }
InfoType.CHANNEL -> ExtractorHelper.getChannelInfo(serviceId, url, false)
.map { info ->
val playableTab = info.tabs
.firstOrNull { ChannelTabHelper.isStreamsTab(it) }
?: throw ContentNotAvailableException("No streams tab found")
return@map ChannelTabPlayQueue(serviceId, ListLinkHandler(playableTab))
}
else -> throw parseError(mediaId)
}
}
//endregion
companion object {
private val TAG = MediaBrowserPlaybackPreparer::class.simpleName
}
}

View File

@ -38,10 +38,10 @@ public class MediaSessionPlayerUi extends PlayerUi
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "MediaSessUi"; private static final String TAG = "MediaSessUi";
@Nullable @NonNull
private MediaSessionCompat mediaSession; private final MediaSessionCompat mediaSession;
@Nullable @NonNull
private MediaSessionConnector sessionConnector; private final MediaSessionConnector sessionConnector;
private final String ignoreHardwareMediaButtonsKey; private final String ignoreHardwareMediaButtonsKey;
private boolean shouldIgnoreHardwareMediaButtons = false; private boolean shouldIgnoreHardwareMediaButtons = false;
@ -50,9 +50,13 @@ public class MediaSessionPlayerUi extends PlayerUi
private List<NotificationActionData> prevNotificationActions = List.of(); private List<NotificationActionData> prevNotificationActions = List.of();
public MediaSessionPlayerUi(@NonNull final Player player) { public MediaSessionPlayerUi(@NonNull final Player player,
@NonNull final MediaSessionCompat mediaSession,
@NonNull final MediaSessionConnector sessionConnector) {
super(player); super(player);
ignoreHardwareMediaButtonsKey = this.mediaSession = mediaSession;
this.sessionConnector = sessionConnector;
this.ignoreHardwareMediaButtonsKey =
context.getString(R.string.ignore_hardware_media_buttons_key); context.getString(R.string.ignore_hardware_media_buttons_key);
} }
@ -61,10 +65,8 @@ public class MediaSessionPlayerUi extends PlayerUi
super.initPlayer(); super.initPlayer();
destroyPlayer(); // release previously used resources destroyPlayer(); // release previously used resources
mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setActive(true); mediaSession.setActive(true);
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player));
sessionConnector.setPlayer(getForwardingPlayer()); sessionConnector.setPlayer(getForwardingPlayer());
@ -89,27 +91,18 @@ public class MediaSessionPlayerUi extends PlayerUi
public void destroyPlayer() { public void destroyPlayer() {
super.destroyPlayer(); super.destroyPlayer();
player.getPrefs().unregisterOnSharedPreferenceChangeListener(this); player.getPrefs().unregisterOnSharedPreferenceChangeListener(this);
if (sessionConnector != null) { sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setMediaButtonEventHandler(null); sessionConnector.setPlayer(null);
sessionConnector.setPlayer(null); sessionConnector.setQueueNavigator(null);
sessionConnector.setQueueNavigator(null); mediaSession.setActive(false);
sessionConnector = null;
}
if (mediaSession != null) {
mediaSession.setActive(false);
mediaSession.release();
mediaSession = null;
}
prevNotificationActions = List.of(); prevNotificationActions = List.of();
} }
@Override @Override
public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { public void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
super.onThumbnailLoaded(bitmap); super.onThumbnailLoaded(bitmap);
if (sessionConnector != null) { // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update
// the thumbnail is now loaded: invalidate the metadata to trigger a metadata update sessionConnector.invalidateMediaSessionMetadata();
sessionConnector.invalidateMediaSessionMetadata();
}
} }
@ -200,8 +193,8 @@ public class MediaSessionPlayerUi extends PlayerUi
return; return;
} }
if (sessionConnector == null) { if (!mediaSession.isActive()) {
// sessionConnector will be null after destroyPlayer is called // mediaSession will be inactive after destroyPlayer is called
return; return;
} }

View File

@ -28,13 +28,17 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo<? extends InfoItem>>
private transient Disposable fetchReactor; private transient Disposable fetchReactor;
protected AbstractInfoPlayQueue(final T info) { protected AbstractInfoPlayQueue(final T info) {
this(info, 0);
}
protected AbstractInfoPlayQueue(final T info, final int index) {
this(info.getServiceId(), info.getUrl(), info.getNextPage(), this(info.getServiceId(), info.getUrl(), info.getNextPage(),
info.getRelatedItems() info.getRelatedItems()
.stream() .stream()
.filter(StreamInfoItem.class::isInstance) .filter(StreamInfoItem.class::isInstance)
.map(StreamInfoItem.class::cast) .map(StreamInfoItem.class::cast)
.collect(Collectors.toList()), .collect(Collectors.toList()),
0); index);
} }
protected AbstractInfoPlayQueue(final int serviceId, protected AbstractInfoPlayQueue(final int serviceId,

View File

@ -16,6 +16,10 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo>
super(info); super(info);
} }
public PlaylistPlayQueue(final PlaylistInfo info, final int index) {
super(info, index);
}
public PlaylistPlayQueue(final int serviceId, public PlaylistPlayQueue(final int serviceId,
final String url, final String url,
final Page nextPage, final Page nextPage,

View File

@ -25,9 +25,7 @@ import android.graphics.Color;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -44,6 +42,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.video.VideoSize; import com.google.android.exoplayer2.video.VideoSize;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -522,11 +521,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
@Override @Override
protected void setupSubtitleView(final float captionScale) { protected void setupSubtitleView(final float captionScale) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); binding.subtitleView.setFractionalTextSize(
final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionScale);
final float captionRatioInverse = 20f + 4f * (1.0f - captionScale);
binding.subtitleView.setFixedTextSize(
TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse);
} }
//endregion //endregion

View File

@ -382,7 +382,7 @@ public final class PopupPlayerUi extends VideoPlayerUi {
private void end() { private void end() {
windowManager.removeView(closeOverlayBinding.getRoot()); windowManager.removeView(closeOverlayBinding.getRoot());
closeOverlayBinding = null; closeOverlayBinding = null;
player.getService().stopService(); player.getService().destroyPlayerAndStopService();
} }
}).start(); }).start();
} }
@ -424,9 +424,8 @@ public final class PopupPlayerUi extends VideoPlayerUi {
@Override @Override
protected void setupSubtitleView(final float captionScale) { protected void setupSubtitleView(final float captionScale) {
final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f;
binding.subtitleView.setFractionalTextSize( binding.subtitleView.setFractionalTextSize(
SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionScale);
} }
@Override @Override

View File

@ -1414,6 +1414,10 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
binding.subtitleView.setStyle(captionStyle); binding.subtitleView.setStyle(captionStyle);
} }
/**
*
* @param captionScale Value returned by {@link PlayerHelper#getCaptionScale}.
*/
protected abstract void setupSubtitleView(float captionScale); protected abstract void setupSubtitleView(float captionScale);
//endregion //endregion

View File

@ -1,10 +1,15 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.schabi.newpipe.DownloaderImpl; import org.schabi.newpipe.DownloaderImpl;
@ -17,12 +22,11 @@ import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.image.PreferredImageQuality; import org.schabi.newpipe.util.image.PreferredImageQuality;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
public class ContentSettingsFragment extends BasePreferenceFragment { public class ContentSettingsFragment extends BasePreferenceFragment {
private String youtubeRestrictedModeEnabledKey; private String youtubeRestrictedModeEnabledKey;
private Localization initialSelectedLocalization;
private ContentCountry initialSelectedContentCountry;
private String initialLanguage; private String initialLanguage;
@Override @Override
@ -31,12 +35,28 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
addPreferencesFromResourceRegistry(); addPreferencesFromResourceRegistry();
initialSelectedLocalization = org.schabi.newpipe.util.Localization
.getPreferredLocalization(requireContext());
initialSelectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en"); initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en");
if (Build.VERSION.SDK_INT >= 33) {
requirePreference(R.string.app_language_key).setVisible(false);
final Preference newAppLanguagePref =
requirePreference(R.string.app_language_android_13_and_up_key);
newAppLanguagePref.setSummaryProvider(preference -> {
final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
if (customLocale != null) {
return customLocale.getDisplayName();
}
return getString(R.string.systems_language);
});
newAppLanguagePref.setOnPreferenceClickListener(preference -> {
final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
.setData(Uri.fromParts("package", requireContext().getPackageName(), null));
startActivity(intent);
return true;
});
newAppLanguagePref.setVisible(true);
}
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key); final Preference imageQualityPreference = requirePreference(R.string.image_quality_key);
imageQualityPreference.setOnPreferenceChangeListener( imageQualityPreference.setOnPreferenceChangeListener(
(preference, newValue) -> { (preference, newValue) -> {
@ -72,19 +92,21 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
final Localization selectedLocalization = org.schabi.newpipe.util.Localization
.getPreferredLocalization(requireContext());
final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
final String selectedLanguage = final String selectedLanguage =
defaultPreferences.getString(getString(R.string.app_language_key), "en"); defaultPreferences.getString(getString(R.string.app_language_key), "en");
if (!selectedLocalization.equals(initialSelectedLocalization) if (!selectedLanguage.equals(initialLanguage)) {
|| !selectedContentCountry.equals(initialSelectedContentCountry) if (Build.VERSION.SDK_INT < 33) {
|| !selectedLanguage.equals(initialLanguage)) { Toast.makeText(
Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart, requireContext(),
Toast.LENGTH_LONG).show(); R.string.localization_changes_requires_app_restart,
Toast.LENGTH_LONG
).show();
}
final Localization selectedLocalization = org.schabi.newpipe.util.Localization
.getPreferredLocalization(requireContext());
final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
.getPreferredContentCountry(requireContext());
NewPipe.setupLocalization(selectedLocalization, selectedContentCountry); NewPipe.setupLocalization(selectedLocalization, selectedContentCountry);
} }
} }

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.settings; package org.schabi.newpipe.settings;
import static org.schabi.newpipe.extractor.utils.Utils.decodeUrlUtf8;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
import android.app.Activity; import android.app.Activity;
@ -30,7 +29,6 @@ import org.schabi.newpipe.util.FilePickerActivityHelper;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
public class DownloadSettingsFragment extends BasePreferenceFragment { public class DownloadSettingsFragment extends BasePreferenceFragment {
public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true;
@ -107,28 +105,15 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
private void showPathInSummary(final String prefKey, @StringRes final int defaultString, private void showPathInSummary(final String prefKey, @StringRes final int defaultString,
final Preference target) { final Preference target) {
String rawUri = defaultPreferences.getString(prefKey, null); final Uri uri = Uri.parse(defaultPreferences.getString(prefKey, ""));
if (rawUri == null || rawUri.isEmpty()) { if (uri.equals(Uri.EMPTY)) {
target.setSummary(getString(defaultString)); target.setSummary(getString(defaultString));
return; return;
} }
if (rawUri.charAt(0) == File.separatorChar) { final String summary = ContentResolver.SCHEME_FILE.equals(uri.getScheme())
target.setSummary(rawUri); ? uri.getPath() : uri.toString();
return; target.setSummary(summary);
}
if (rawUri.startsWith(ContentResolver.SCHEME_FILE)) {
target.setSummary(new File(URI.create(rawUri)).getPath());
return;
}
try {
rawUri = decodeUrlUtf8(rawUri);
} catch (final IllegalArgumentException e) {
// nothing to do
}
target.setSummary(rawUri);
} }
private boolean isFileUri(final String path) { private boolean isFileUri(final String path) {

View File

@ -3,8 +3,10 @@ package org.schabi.newpipe.settings
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.SwitchPreference
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
@ -21,15 +23,17 @@ import org.schabi.newpipe.local.subscription.SubscriptionManager
class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferenceChangeListener { class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferenceChangeListener {
private var streamsNotificationsPreference: SwitchPreference? = null
private var notificationWarningSnackbar: Snackbar? = null private var notificationWarningSnackbar: Snackbar? = null
private var loader: Disposable? = null private var loader: Disposable? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.notifications_settings) addPreferencesFromResource(R.xml.notifications_settings)
streamsNotificationsPreference =
findPreference(getString(R.string.enable_streams_notifications))
// main check is done in onResume, but also do it here to prevent flickering // main check is done in onResume, but also do it here to prevent flickering
preferenceScreen.isEnabled = updateEnabledState(NotificationHelper.areNotificationsEnabledOnDevice(requireContext()))
NotificationHelper.areNotificationsEnabledOnDevice(requireContext())
} }
override fun onStart() { override fun onStart() {
@ -68,7 +72,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen
// If they are disabled, show a snackbar informing the user about that // If they are disabled, show a snackbar informing the user about that
// while allowing them to open the device's app settings. // while allowing them to open the device's app settings.
val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext())
preferenceScreen.isEnabled = enabled // it is disabled by default, see the xml updateEnabledState(enabled)
if (!enabled) { if (!enabled) {
if (notificationWarningSnackbar == null) { if (notificationWarningSnackbar == null) {
notificationWarningSnackbar = Snackbar.make( notificationWarningSnackbar = Snackbar.make(
@ -109,6 +113,16 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen
super.onPause() super.onPause()
} }
private fun updateEnabledState(enabled: Boolean) {
// On Android 13 player notifications are exempt from notification settings
// so the preferences in app should always be available.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
streamsNotificationsPreference?.isEnabled = enabled
} else {
preferenceScreen.isEnabled = enabled
}
}
private fun updateSubscriptions(subscriptions: List<SubscriptionEntity>) { private fun updateSubscriptions(subscriptions: List<SubscriptionEntity>) {
val notified = subscriptions.count { it.notificationMode != NotificationMode.DISABLED } val notified = subscriptions.count { it.notificationMode != NotificationMode.DISABLED }
val preference = findPreference<Preference>(getString(R.string.streams_notifications_channels_key)) val preference = findPreference<Preference>(getString(R.string.streams_notifications_channels_key))

View File

@ -24,8 +24,9 @@ class ImportExportManager(private val fileLocator: BackupFileLocator) {
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) {
file.create() // truncate the file before writing to it, otherwise if the new content is smaller than the
ZipOutputStream(SharpOutputStream(file.stream).buffered()).use { outZip -> // previous file size, the file will retain part of the previous content and be corrupted
ZipOutputStream(SharpOutputStream(file.openAndTruncateStream()).buffered()).use { outZip ->
// add the database // add the database
ZipHelper.addFileToZip( ZipHelper.addFileToZip(
outZip, outZip,

View File

@ -189,6 +189,19 @@ public class StoredFileHelper implements Serializable {
} }
} }
public SharpStream openAndTruncateStream() throws IOException {
final SharpStream sharpStream = getStream();
try {
sharpStream.setLength(0);
} catch (final Throwable e) {
// we can't use try-with-resources here, since we only want to close the stream if an
// exception occurs, but leave it open if everything goes well
sharpStream.close();
throw e;
}
return sharpStream;
}
/** /**
* Indicates whether it's using the {@code java.io} API. * Indicates whether it's using the {@code java.io} API.
* *

View File

@ -17,6 +17,7 @@ import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowManager; import android.view.WindowManager;
import android.webkit.CookieManager;
import androidx.annotation.Dimension; import androidx.annotation.Dimension;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -335,4 +336,17 @@ public final class DeviceUtils {
&& !TX_50JXW834 && !TX_50JXW834
&& !HMB9213NW; && !HMB9213NW;
} }
/**
* @return whether the device has support for WebView, see
* <a href="https://stackoverflow.com/a/69626735">https://stackoverflow.com/a/69626735</a>
*/
public static boolean supportsWebView() {
try {
CookieManager.getInstance();
return true;
} catch (final Throwable ignored) {
return false;
}
}
} }

View File

@ -12,12 +12,15 @@ import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.math.MathUtils; import androidx.core.math.MathUtils;
import androidx.core.os.LocaleListCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.ocpsoft.prettytime.PrettyTime; import org.ocpsoft.prettytime.PrettyTime;
@ -39,6 +42,7 @@ import java.time.format.FormatStyle;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -63,6 +67,7 @@ import java.util.stream.Collectors;
*/ */
public final class Localization { public final class Localization {
private static final String TAG = Localization.class.toString();
public static final String DOT_SEPARATOR = ""; public static final String DOT_SEPARATOR = "";
private static PrettyTime prettyTime; private static PrettyTime prettyTime;
@ -101,6 +106,10 @@ public final class Localization {
} }
public static Locale getAppLocale(@NonNull final Context context) { public static Locale getAppLocale(@NonNull final Context context) {
if (Build.VERSION.SDK_INT >= 33) {
final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
return Objects.requireNonNullElseGet(customLocale, Locale::getDefault);
}
return getLocaleFromPrefs(context, R.string.app_language_key); return getLocaleFromPrefs(context, R.string.app_language_key);
} }
@ -308,7 +317,7 @@ public final class Localization {
* <ul> * <ul>
* <li>English (original)</li> * <li>English (original)</li>
* <li>English (descriptive)</li> * <li>English (descriptive)</li>
* <li>Spanish (dubbed)</li> * <li>Spanish (Spain) (dubbed)</li>
* </ul> * </ul>
* *
* @param context the context used to get the app language * @param context the context used to get the app language
@ -318,7 +327,7 @@ public final class Localization {
public static String audioTrackName(@NonNull final Context context, final AudioStream track) { public static String audioTrackName(@NonNull final Context context, final AudioStream track) {
final String name; final String name;
if (track.getAudioLocale() != null) { if (track.getAudioLocale() != null) {
name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context)); name = track.getAudioLocale().getDisplayName();
} else if (track.getAudioTrackName() != null) { } else if (track.getAudioTrackName() != null) {
name = track.getAudioTrackName(); name = track.getAudioTrackName();
} else { } else {
@ -427,4 +436,32 @@ public final class Localization {
final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE); final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE);
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
} }
public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) {
// Starting with pull request #12093, NewPipe on Android 13+ exclusively uses Android's
// public per-app language APIs to read and set the UI language for NewPipe.
// If running on Android 13+, the following code will migrate any existing custom
// app language in SharedPreferences to use the public per-app language APIs instead.
if (Build.VERSION.SDK_INT >= 33) {
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
final String appLanguageKey = context.getString(R.string.app_language_key);
final String appLanguageValue = sp.getString(appLanguageKey, null);
if (appLanguageValue != null) {
sp.edit().remove(appLanguageKey).apply();
final String appLanguageDefaultValue =
context.getString(R.string.default_localization_key);
if (!appLanguageValue.equals(appLanguageDefaultValue)) {
try {
AppCompatDelegate.setApplicationLocales(
LocaleListCompat.forLanguageTags(appLanguageValue)
);
} catch (final RuntimeException e) {
Log.e(TAG, "Failed to migrate previous custom app language "
+ "setting to public per-app language APIs"
);
}
}
}
}
}
} }

View File

@ -96,6 +96,7 @@ public final class NavigationHelper {
} }
intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent()); intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent());
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
return intent; return intent;
} }
@ -452,8 +453,12 @@ public final class NavigationHelper {
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) { if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
onVideoDetailFragmentReady.run((VideoDetailFragment) fragment); onVideoDetailFragmentReady.run((VideoDetailFragment) fragment);
} else { } else {
// Specify no url here, otherwise the VideoDetailFragment will start loading the
// stream automatically if it's the first time it is being opened, but then
// onVideoDetailFragmentReady will kick in and start another loading process.
// See VideoDetailFragment.wasCleared() and its usage in doInitialLoadLogic().
final VideoDetailFragment instance = VideoDetailFragment final VideoDetailFragment instance = VideoDetailFragment
.getInstance(serviceId, url, title, playQueue); .getInstance(serviceId, null, title, playQueue);
instance.setAutoPlay(autoPlay); instance.setAutoPlay(autoPlay);
defaultTransaction(fragmentManager) defaultTransaction(fragmentManager)

View File

@ -0,0 +1,113 @@
package org.schabi.newpipe.util.potoken
import com.grack.nanojson.JsonObject
import com.grack.nanojson.JsonParser
import com.grack.nanojson.JsonWriter
import okio.ByteString.Companion.decodeBase64
import okio.ByteString.Companion.toByteString
/**
* Parses the raw challenge data obtained from the Create endpoint and returns an object that can be
* embedded in a JavaScript snippet.
*/
fun parseChallengeData(rawChallengeData: String): String {
val scrambled = JsonParser.array().from(rawChallengeData)
val challengeData = if (scrambled.size > 1 && scrambled.isString(1)) {
val descrambled = descramble(scrambled.getString(1))
JsonParser.array().from(descrambled)
} else {
scrambled.getArray(0)
}
val messageId = challengeData.getString(0)
val interpreterHash = challengeData.getString(3)
val program = challengeData.getString(4)
val globalName = challengeData.getString(5)
val clientExperimentsStateBlob = challengeData.getString(7)
val privateDoNotAccessOrElseSafeScriptWrappedValue = challengeData.getArray(1, null)?.find { it is String }
val privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = challengeData.getArray(2, null)?.find { it is String }
return JsonWriter.string(
JsonObject.builder()
.value("messageId", messageId)
.`object`("interpreterJavascript")
.value("privateDoNotAccessOrElseSafeScriptWrappedValue", privateDoNotAccessOrElseSafeScriptWrappedValue)
.value("privateDoNotAccessOrElseTrustedResourceUrlWrappedValue", privateDoNotAccessOrElseTrustedResourceUrlWrappedValue)
.end()
.value("interpreterHash", interpreterHash)
.value("program", program)
.value("globalName", globalName)
.value("clientExperimentsStateBlob", clientExperimentsStateBlob)
.done()
)
}
/**
* Parses the raw integrity token data obtained from the GenerateIT endpoint to a JavaScript
* `Uint8Array` that can be embedded directly in JavaScript code, and an [Int] representing the
* duration of this token in seconds.
*/
fun parseIntegrityTokenData(rawIntegrityTokenData: String): Pair<String, Long> {
val integrityTokenData = JsonParser.array().from(rawIntegrityTokenData)
return base64ToU8(integrityTokenData.getString(0)) to integrityTokenData.getLong(1)
}
/**
* Converts a string (usually the identifier used as input to `obtainPoToken`) to a JavaScript
* `Uint8Array` that can be embedded directly in JavaScript code.
*/
fun stringToU8(identifier: String): String {
return newUint8Array(identifier.toByteArray())
}
/**
* Takes a poToken encoded as a sequence of bytes represented as integers separated by commas
* (e.g. "97,98,99" would be "abc"), which is the output of `Uint8Array::toString()` in JavaScript,
* and converts it to the specific base64 representation for poTokens.
*/
fun u8ToBase64(poToken: String): String {
return poToken.split(",")
.map { it.toUByte().toByte() }
.toByteArray()
.toByteString()
.base64()
.replace("+", "-")
.replace("/", "_")
}
/**
* Takes the scrambled challenge, decodes it from base64, adds 97 to each byte.
*/
private fun descramble(scrambledChallenge: String): String {
return base64ToByteString(scrambledChallenge)
.map { (it + 97).toByte() }
.toByteArray()
.decodeToString()
}
/**
* Decodes a base64 string encoded in the specific base64 representation used by YouTube, and
* returns a JavaScript `Uint8Array` that can be embedded directly in JavaScript code.
*/
private fun base64ToU8(base64: String): String {
return newUint8Array(base64ToByteString(base64))
}
private fun newUint8Array(contents: ByteArray): String {
return "new Uint8Array([" + contents.joinToString(separator = ",") { it.toUByte().toString() } + "])"
}
/**
* Decodes a base64 string encoded in the specific base64 representation used by YouTube.
*/
private fun base64ToByteString(base64: String): ByteArray {
val base64Mod = base64
.replace('-', '+')
.replace('_', '/')
.replace('.', '=')
return (base64Mod.decodeBase64() ?: throw PoTokenException("Cannot base64 decode"))
.toByteArray()
}

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.util.potoken
class PoTokenException(message: String) : Exception(message)
// to be thrown if the WebView provided by the system is broken
class BadWebViewException(message: String) : Exception(message)
fun buildExceptionForJsError(error: String): Exception {
return if (error.contains("SyntaxError"))
BadWebViewException(error)
else
PoTokenException(error)
}

View File

@ -0,0 +1,35 @@
package org.schabi.newpipe.util.potoken
import android.content.Context
import io.reactivex.rxjava3.core.Single
import java.io.Closeable
/**
* This interface was created to allow for multiple methods to generate poTokens in the future (e.g.
* via WebView and via a local DOM implementation)
*/
interface PoTokenGenerator : Closeable {
/**
* Generates a poToken for the provided identifier, using the `integrityToken` and
* `webPoSignalOutput` previously obtained in the initialization of [PoTokenWebView]. Can be
* called multiple times.
*/
fun generatePoToken(identifier: String): Single<String>
/**
* @return whether the `integrityToken` is expired, in which case all tokens generated by
* [generatePoToken] will be invalid
*/
fun isExpired(): Boolean
interface Factory {
/**
* Initializes a [PoTokenGenerator] by loading the BotGuard VM, running it, and obtaining
* an `integrityToken`. Can then be used multiple times to generate multiple poTokens with
* [generatePoToken].
*
* @param context used e.g. to load the HTML asset or to instantiate a WebView
*/
fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator>
}
}

View File

@ -0,0 +1,131 @@
package org.schabi.newpipe.util.potoken
import android.os.Handler
import android.os.Looper
import android.util.Log
import org.schabi.newpipe.App
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo
import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider
import org.schabi.newpipe.extractor.services.youtube.PoTokenResult
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
import org.schabi.newpipe.util.DeviceUtils
object PoTokenProviderImpl : PoTokenProvider {
val TAG = PoTokenProviderImpl::class.simpleName
private val webViewSupported by lazy { DeviceUtils.supportsWebView() }
private var webViewBadImpl = false // whether the system has a bad WebView implementation
private object WebPoTokenGenLock
private var webPoTokenVisitorData: String? = null
private var webPoTokenStreamingPot: String? = null
private var webPoTokenGenerator: PoTokenGenerator? = null
override fun getWebClientPoToken(videoId: String): PoTokenResult? {
if (!webViewSupported || webViewBadImpl) {
return null
}
try {
return getWebClientPoToken(videoId = videoId, forceRecreate = false)
} catch (e: RuntimeException) {
// RxJava's Single wraps exceptions into RuntimeErrors, so we need to unwrap them here
when (val cause = e.cause) {
is BadWebViewException -> {
Log.e(TAG, "Could not obtain poToken because WebView is broken", e)
webViewBadImpl = true
return null
}
null -> throw e
else -> throw cause // includes PoTokenException
}
}
}
/**
* @param forceRecreate whether to force the recreation of [webPoTokenGenerator], to be used in
* case the current [webPoTokenGenerator] threw an error last time
* [PoTokenGenerator.generatePoToken] was called
*/
private fun getWebClientPoToken(videoId: String, forceRecreate: Boolean): PoTokenResult {
// just a helper class since Kotlin does not have builtin support for 4-tuples
data class Quadruple<T1, T2, T3, T4>(val t1: T1, val t2: T2, val t3: T3, val t4: T4)
val (poTokenGenerator, visitorData, streamingPot, hasBeenRecreated) =
synchronized(WebPoTokenGenLock) {
val shouldRecreate = webPoTokenGenerator == null || forceRecreate ||
webPoTokenGenerator!!.isExpired()
if (shouldRecreate) {
val innertubeClientRequestInfo = InnertubeClientRequestInfo.ofWebClient()
innertubeClientRequestInfo.clientInfo.clientVersion =
YoutubeParsingHelper.getClientVersion()
webPoTokenVisitorData = YoutubeParsingHelper.getVisitorDataFromInnertube(
innertubeClientRequestInfo,
NewPipe.getPreferredLocalization(),
NewPipe.getPreferredContentCountry(),
YoutubeParsingHelper.getYouTubeHeaders(),
YoutubeParsingHelper.YOUTUBEI_V1_URL,
null,
false
)
// close the current webPoTokenGenerator on the main thread
webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } }
// create a new webPoTokenGenerator
webPoTokenGenerator = PoTokenWebView
.newPoTokenGenerator(App.getApp()).blockingGet()
// The streaming poToken needs to be generated exactly once before generating
// any other (player) tokens.
webPoTokenStreamingPot = webPoTokenGenerator!!
.generatePoToken(webPoTokenVisitorData!!).blockingGet()
}
return@synchronized Quadruple(
webPoTokenGenerator!!,
webPoTokenVisitorData!!,
webPoTokenStreamingPot!!,
shouldRecreate
)
}
val playerPot = try {
// Not using synchronized here, since poTokenGenerator would be able to generate
// multiple poTokens in parallel if needed. The only important thing is for exactly one
// visitorData/streaming poToken to be generated before anything else.
poTokenGenerator.generatePoToken(videoId).blockingGet()
} catch (throwable: Throwable) {
if (hasBeenRecreated) {
// the poTokenGenerator has just been recreated (and possibly this is already the
// second time we try), so there is likely nothing we can do
throw throwable
} else {
// retry, this time recreating the [webPoTokenGenerator] from scratch;
// this might happen for example if NewPipe goes in the background and the WebView
// content is lost
Log.e(TAG, "Failed to obtain poToken, retrying", throwable)
return getWebClientPoToken(videoId = videoId, forceRecreate = true)
}
}
if (BuildConfig.DEBUG) {
Log.d(
TAG,
"poToken for $videoId: playerPot=$playerPot, " +
"streamingPot=$streamingPot, visitor_data=$visitorData"
)
}
return PoTokenResult(visitorData, playerPot, streamingPot)
}
override fun getWebEmbedClientPoToken(videoId: String): PoTokenResult? = null
override fun getAndroidClientPoToken(videoId: String): PoTokenResult? = null
override fun getIosClientPoToken(videoId: String): PoTokenResult? = null
}

View File

@ -0,0 +1,395 @@
package org.schabi.newpipe.util.potoken
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.ConsoleMessage
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.annotation.MainThread
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.core.SingleEmitter
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.DownloaderImpl
import java.time.Instant
class PoTokenWebView private constructor(
context: Context,
// to be used exactly once only during initialization!
private val generatorEmitter: SingleEmitter<PoTokenGenerator>,
) : PoTokenGenerator {
private val webView = WebView(context)
private val disposables = CompositeDisposable() // used only during initialization
private val poTokenEmitters = mutableListOf<Pair<String, SingleEmitter<String>>>()
private lateinit var expirationInstant: Instant
//region Initialization
init {
val webViewSettings = webView.settings
//noinspection SetJavaScriptEnabled we want to use JavaScript!
webViewSettings.javaScriptEnabled = true
if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_ENABLE)) {
WebSettingsCompat.setSafeBrowsingEnabled(webViewSettings, false)
}
webViewSettings.userAgentString = USER_AGENT
webViewSettings.blockNetworkLoads = true // the WebView does not need internet access
// so that we can run async functions and get back the result
webView.addJavascriptInterface(this, JS_INTERFACE)
webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(m: ConsoleMessage): Boolean {
if (m.message().contains("Uncaught")) {
// There should not be any uncaught errors while executing the code, because
// everything that can fail is guarded by try-catch. Therefore, this likely
// indicates that there was a syntax error in the code, i.e. the WebView only
// supports a really old version of JS.
val fmt = "\"${m.message()}\", source: ${m.sourceId()} (${m.lineNumber()})"
val exception = BadWebViewException(fmt)
Log.e(TAG, "This WebView implementation is broken: $fmt")
onInitializationErrorCloseAndCancel(exception)
popAllPoTokenEmitters().forEach { (_, emitter) -> emitter.onError(exception) }
}
return super.onConsoleMessage(m)
}
}
}
/**
* Must be called right after instantiating [PoTokenWebView] to perform the actual
* initialization. This will asynchronously go through all the steps needed to load BotGuard,
* run it, and obtain an `integrityToken`.
*/
private fun loadHtmlAndObtainBotguard(context: Context) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "loadHtmlAndObtainBotguard() called")
}
disposables.add(
Single.fromCallable {
val html = context.assets.open("po_token.html").bufferedReader()
.use { it.readText() }
return@fromCallable html
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ html ->
webView.loadDataWithBaseURL(
"https://www.youtube.com",
html.replaceFirst(
"</script>",
// calls downloadAndRunBotguard() when the page has finished loading
"\n$JS_INTERFACE.downloadAndRunBotguard()</script>"
),
"text/html",
"utf-8",
null,
)
},
this::onInitializationErrorCloseAndCancel
)
)
}
/**
* Called during initialization by the JavaScript snippet appended to the HTML page content in
* [loadHtmlAndObtainBotguard] after the WebView content has been loaded.
*/
@JavascriptInterface
fun downloadAndRunBotguard() {
if (BuildConfig.DEBUG) {
Log.d(TAG, "downloadAndRunBotguard() called")
}
makeBotguardServiceRequest(
"https://www.youtube.com/api/jnn/v1/Create",
"[ \"$REQUEST_KEY\" ]",
) { responseBody ->
val parsedChallengeData = parseChallengeData(responseBody)
webView.evaluateJavascript(
"""try {
data = $parsedChallengeData
runBotGuard(data).then(function (result) {
this.webPoSignalOutput = result.webPoSignalOutput
$JS_INTERFACE.onRunBotguardResult(result.botguardResponse)
}, function (error) {
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
})
} catch (error) {
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
}""",
null
)
}
}
/**
* Called during initialization by the JavaScript snippets from either
* [downloadAndRunBotguard] or [onRunBotguardResult].
*/
@JavascriptInterface
fun onJsInitializationError(error: String) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "Initialization error from JavaScript: $error")
}
onInitializationErrorCloseAndCancel(buildExceptionForJsError(error))
}
/**
* Called during initialization by the JavaScript snippet from [downloadAndRunBotguard] after
* obtaining the BotGuard execution output [botguardResponse].
*/
@JavascriptInterface
fun onRunBotguardResult(botguardResponse: String) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "botguardResponse: $botguardResponse")
}
makeBotguardServiceRequest(
"https://www.youtube.com/api/jnn/v1/GenerateIT",
"[ \"$REQUEST_KEY\", \"$botguardResponse\" ]",
) { responseBody ->
if (BuildConfig.DEBUG) {
Log.d(TAG, "GenerateIT response: $responseBody")
}
val (integrityToken, expirationTimeInSeconds) = parseIntegrityTokenData(responseBody)
// leave 10 minutes of margin just to be sure
expirationInstant = Instant.now().plusSeconds(expirationTimeInSeconds - 600)
webView.evaluateJavascript(
"this.integrityToken = $integrityToken"
) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "initialization finished, expiration=${expirationTimeInSeconds}s")
}
generatorEmitter.onSuccess(this)
}
}
}
//endregion
//region Obtaining poTokens
override fun generatePoToken(identifier: String): Single<String> =
Single.create { emitter ->
if (BuildConfig.DEBUG) {
Log.d(TAG, "generatePoToken() called with identifier $identifier")
}
runOnMainThread(emitter) {
addPoTokenEmitter(identifier, emitter)
val u8Identifier = stringToU8(identifier)
webView.evaluateJavascript(
"""try {
identifier = "$identifier"
u8Identifier = $u8Identifier
poTokenU8 = obtainPoToken(webPoSignalOutput, integrityToken, u8Identifier)
poTokenU8String = ""
for (i = 0; i < poTokenU8.length; i++) {
if (i != 0) poTokenU8String += ","
poTokenU8String += poTokenU8[i]
}
$JS_INTERFACE.onObtainPoTokenResult(identifier, poTokenU8String)
} catch (error) {
$JS_INTERFACE.onObtainPoTokenError(identifier, error + "\n" + error.stack)
}""",
) {}
}
}
/**
* Called by the JavaScript snippet from [generatePoToken] when an error occurs in calling the
* JavaScript `obtainPoToken()` function.
*/
@JavascriptInterface
fun onObtainPoTokenError(identifier: String, error: String) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "obtainPoToken error from JavaScript: $error")
}
popPoTokenEmitter(identifier)?.onError(buildExceptionForJsError(error))
}
/**
* Called by the JavaScript snippet from [generatePoToken] with the original identifier and the
* result of the JavaScript `obtainPoToken()` function.
*/
@JavascriptInterface
fun onObtainPoTokenResult(identifier: String, poTokenU8: String) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Generated poToken (before decoding): identifier=$identifier poTokenU8=$poTokenU8")
}
val poToken = try {
u8ToBase64(poTokenU8)
} catch (t: Throwable) {
popPoTokenEmitter(identifier)?.onError(t)
return
}
if (BuildConfig.DEBUG) {
Log.d(TAG, "Generated poToken: identifier=$identifier poToken=$poToken")
}
popPoTokenEmitter(identifier)?.onSuccess(poToken)
}
override fun isExpired(): Boolean {
return Instant.now().isAfter(expirationInstant)
}
//endregion
//region Handling multiple emitters
/**
* Adds the ([identifier], [emitter]) pair to the [poTokenEmitters] list. This makes it so that
* multiple poToken requests can be generated invparallel, and the results will be notified to
* the right emitters.
*/
private fun addPoTokenEmitter(identifier: String, emitter: SingleEmitter<String>) {
synchronized(poTokenEmitters) {
poTokenEmitters.add(Pair(identifier, emitter))
}
}
/**
* Extracts and removes from the [poTokenEmitters] list a [SingleEmitter] based on its
* [identifier]. The emitter is supposed to be used immediately after to either signal a success
* or an error.
*/
private fun popPoTokenEmitter(identifier: String): SingleEmitter<String>? {
return synchronized(poTokenEmitters) {
poTokenEmitters.indexOfFirst { it.first == identifier }.takeIf { it >= 0 }?.let {
poTokenEmitters.removeAt(it).second
}
}
}
/**
* Clears [poTokenEmitters] and returns its previous contents. The emitters are supposed to be
* used immediately after to either signal a success or an error.
*/
private fun popAllPoTokenEmitters(): List<Pair<String, SingleEmitter<String>>> {
return synchronized(poTokenEmitters) {
val result = poTokenEmitters.toList()
poTokenEmitters.clear()
result
}
}
//endregion
//region Utils
/**
* Makes a POST request to [url] with the given [data] by setting the correct headers. Calls
* [onInitializationErrorCloseAndCancel] in case of any network errors and also if the response
* does not have HTTP code 200, therefore this is supposed to be used only during
* initialization. Calls [handleResponseBody] with the response body if the response is
* successful. The request is performed in the background and a disposable is added to
* [disposables].
*/
private fun makeBotguardServiceRequest(
url: String,
data: String,
handleResponseBody: (String) -> Unit,
) {
disposables.add(
Single.fromCallable {
return@fromCallable DownloaderImpl.getInstance().post(
url,
mapOf(
// replace the downloader user agent
"User-Agent" to listOf(USER_AGENT),
"Accept" to listOf("application/json"),
"Content-Type" to listOf("application/json+protobuf"),
"x-goog-api-key" to listOf(GOOGLE_API_KEY),
"x-user-agent" to listOf("grpc-web-javascript/0.1"),
),
data.toByteArray()
)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ response ->
val httpCode = response.responseCode()
if (httpCode != 200) {
onInitializationErrorCloseAndCancel(
PoTokenException("Invalid response code: $httpCode")
)
return@subscribe
}
val responseBody = response.responseBody()
handleResponseBody(responseBody)
},
this::onInitializationErrorCloseAndCancel
)
)
}
/**
* Handles any error happening during initialization, releasing resources and sending the error
* to [generatorEmitter].
*/
private fun onInitializationErrorCloseAndCancel(error: Throwable) {
runOnMainThread(generatorEmitter) {
close()
generatorEmitter.onError(error)
}
}
/**
* Releases all [webView] and [disposables] resources.
*/
@MainThread
override fun close() {
disposables.dispose()
webView.clearHistory()
// clears RAM cache and disk cache (globally for all WebViews)
webView.clearCache(true)
// ensures that the WebView isn't doing anything when destroying it
webView.loadUrl("about:blank")
webView.onPause()
webView.removeAllViews()
webView.destroy()
}
//endregion
companion object : PoTokenGenerator.Factory {
private val TAG = PoTokenWebView::class.simpleName
// Public API key used by BotGuard, which has been got by looking at BotGuard requests
private const val GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw" // NOSONAR
private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo"
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3"
private const val JS_INTERFACE = "PoTokenWebView"
override fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator> =
Single.create { emitter ->
runOnMainThread(emitter) {
val potWv = PoTokenWebView(context, emitter)
potWv.loadHtmlAndObtainBotguard(context)
emitter.setDisposable(potWv.disposables)
}
}
/**
* Runs [runnable] on the main thread using `Handler(Looper.getMainLooper()).post()`, and
* if the `post` fails emits an error on [emitterIfPostFails].
*/
private fun runOnMainThread(
emitterIfPostFails: SingleEmitter<out Any>,
runnable: Runnable,
) {
if (!Handler(Looper.getMainLooper()).post(runnable)) {
emitterIfPostFails.onError(PoTokenException("Could not run on main thread"))
}
}
}
}

View File

@ -71,6 +71,9 @@ import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Date;
import java.util.Locale;
import java.text.DateFormat;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Observable;
@ -208,11 +211,17 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause); h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause);
updateProgress(h); updateProgress(h);
mPendingDownloadsItems.add(h); mPendingDownloadsItems.add(h);
h.date.setText("");
} else { } else {
h.progress.setMarquee(false); h.progress.setMarquee(false);
h.status.setText("100%"); h.status.setText("100%");
h.progress.setProgress(1.0f); h.progress.setProgress(1.0f);
h.size.setText(Utility.formatBytes(item.mission.length)); h.size.setText(Utility.formatBytes(item.mission.length));
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault());
Date date = new Date(item.mission.timestamp);
h.date.setText(dateFormat.format(date));
} }
} }
@ -664,6 +673,13 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
return true; return true;
case R.id.md5: case R.id.md5:
case R.id.sha1: case R.id.sha1:
final StoredFileHelper storage = h.item.mission.storage;
if (!storage.existsAsFile()) {
Toast.makeText(mContext, R.string.missing_file, Toast.LENGTH_SHORT).show();
mDeleter.append(h.item.mission);
applyChanges();
return true;
}
final NotificationManager notificationManager final NotificationManager notificationManager
= ContextCompat.getSystemService(mContext, NotificationManager.class); = ContextCompat.getSystemService(mContext, NotificationManager.class);
final NotificationCompat.Builder progressNotificationBuilder final NotificationCompat.Builder progressNotificationBuilder
@ -678,7 +694,6 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
notificationManager.notify(HASH_NOTIFICATION_ID, progressNotificationBuilder notificationManager.notify(HASH_NOTIFICATION_ID, progressNotificationBuilder
.build()); .build());
final StoredFileHelper storage = h.item.mission.storage;
compositeDisposable.add( compositeDisposable.add(
Observable.fromCallable(() -> Utility.checksum(storage, id)) Observable.fromCallable(() -> Utility.checksum(storage, id))
.subscribeOn(Schedulers.computation()) .subscribeOn(Schedulers.computation())
@ -826,6 +841,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
ImageView icon; ImageView icon;
TextView name; TextView name;
TextView size; TextView size;
TextView date;
ProgressDrawable progress; ProgressDrawable progress;
PopupMenu popupMenu; PopupMenu popupMenu;
@ -856,6 +872,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
name = itemView.findViewById(R.id.item_name); name = itemView.findViewById(R.id.item_name);
icon = itemView.findViewById(R.id.item_icon); icon = itemView.findViewById(R.id.item_icon);
size = itemView.findViewById(R.id.item_size); size = itemView.findViewById(R.id.item_size);
date = itemView.findViewById(R.id.item_date);
name.setSelected(true); name.setSelected(true);

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M640,520 L474,358q-31,-30 -52.5,-66.5T400,212q0,-55 38.5,-93.5T532,80q32,0 60,13.5t48,36.5q20,-23 48,-36.5t60,-13.5q55,0 93.5,38.5T880,212q0,43 -21,79.5T807,358L640,520ZM640,408 L749,301q19,-19 35,-40.5t16,-48.5q0,-22 -15,-37t-37,-15q-14,0 -26.5,5.5T700,182l-60,72 -60,-72q-9,-11 -21.5,-16.5T532,160q-22,0 -37,15t-15,37q0,27 16,48.5t35,40.5l109,107ZM280,740l278,76 238,-74q-5,-9 -14.5,-15.5T760,720L558,720q-27,0 -43,-2t-33,-8l-93,-31 22,-78 81,27q17,5 40,8t68,4q0,-11 -6.5,-21T578,606l-234,-86h-64v220ZM40,880v-440h304q7,0 14,1.5t13,3.5l235,87q33,12 53.5,42t20.5,66h80q50,0 85,33t35,87v40L560,900l-280,-78v58L40,880ZM120,800h80v-280h-80v280ZM640,254Z"
android:fillColor="#FF000000"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/white"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
</vector>

View File

@ -82,6 +82,18 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="12sp" /> android:textSize="12sp" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/item_name"
android:layout_alignParentRight="true"
android:padding="6dp"
android:singleLine="true"
android:text=""
android:textColor="@color/white"
android:textSize="12sp" />
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -62,6 +62,18 @@
android:textSize="12sp" android:textSize="12sp"
android:textStyle="bold" /> android:textStyle="bold" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/item_name"
android:layout_toLeftOf="@id/item_more"
android:padding="6dp"
android:singleLine="true"
android:text=""
android:textColor="@color/white"
android:textSize="12sp" />
<ImageView <ImageView
android:id="@+id/item_more" android:id="@+id/item_more"
style="?attr/buttonBarButtonStyle" style="?attr/buttonBarButtonStyle"

View File

@ -0,0 +1 @@
unqualifiedResLocale=en-US

View File

@ -856,6 +856,5 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">شارِك قائمة التشغيل</string> <string name="share_playlist">شارِك قائمة التشغيل</string>
<string name="share_playlist_with_titles_message">شارِك قائمة التشغيل بتفاصيليها مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين تشعّبيّة للفيديوهات</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
</resources> </resources>

View File

@ -18,7 +18,7 @@
<string name="download_path_dialog_title">اختر مجلد التنزيل لملفات الفيديو</string> <string name="download_path_dialog_title">اختر مجلد التنزيل لملفات الفيديو</string>
<string name="download_path_summary">يتم تخزين ملفات الفيديو التي تم تنزيلها هنا</string> <string name="download_path_summary">يتم تخزين ملفات الفيديو التي تم تنزيلها هنا</string>
<string name="download_path_title">مجلد تحميل الفيديو</string> <string name="download_path_title">مجلد تحميل الفيديو</string>
<string name="install">ثبت</string> <string name="install">ثبيت</string>
<string name="kore_not_found">تطبيق Kore غير موجود. هل تريد تثبيته؟</string> <string name="kore_not_found">تطبيق Kore غير موجود. هل تريد تثبيته؟</string>
<string name="light_theme_title">فاتح</string> <string name="light_theme_title">فاتح</string>
<string name="network_error">خطأ في الشبكة</string> <string name="network_error">خطأ في الشبكة</string>
@ -83,7 +83,7 @@
<string name="resume_on_audio_focus_gain_title">استئناف التشغيل</string> <string name="resume_on_audio_focus_gain_title">استئناف التشغيل</string>
<string name="resume_on_audio_focus_gain_summary">متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية)</string> <string name="resume_on_audio_focus_gain_summary">متابعة التشغيل بعد المقاطعات (مثل المكالمات الهاتفية)</string>
<string name="show_hold_to_append_title">إظهار تلميح \"اضغط للفتح\"</string> <string name="show_hold_to_append_title">إظهار تلميح \"اضغط للفتح\"</string>
<string name="show_hold_to_append_summary">عرض تلميح عند الضغط على زر استخدام المشغل الخلفي أو النافذة المنبثقة في صفحة تفاصيل الفديو</string> <string name="show_hold_to_append_summary">إظهار التلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\"</string>
<string name="settings_category_player_title">المشغل</string> <string name="settings_category_player_title">المشغل</string>
<string name="settings_category_player_behavior_title">السلوك</string> <string name="settings_category_player_behavior_title">السلوك</string>
<string name="popup_playing_toast">تشغيل في وضع منبثق</string> <string name="popup_playing_toast">تشغيل في وضع منبثق</string>
@ -856,7 +856,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">مشاركة قائمة التشغيل</string> <string name="share_playlist">مشاركة قائمة التشغيل</string>
<string name="share_playlist_with_titles_message">شارك تفاصيل قائمة التشغيل مثل اسم قائمة التشغيل وعناوين الفيديو أو كقائمة بسيطة من عناوين URL للفيديو</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="zero">رد %s</item> <item quantity="zero">رد %s</item>
@ -881,4 +880,5 @@
\nهل تريد تمكين هذا؟</string> \nهل تريد تمكين هذا؟</string>
<string name="no">لا</string> <string name="no">لا</string>
<string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string> <string name="import_settings_vulnerable_format">تستخدم الإعدادات الموجودة في عملية التصدير التي يتم استيرادها تنسيقًا عرضة للاختراق تم إهماله منذ NewPipe 0.27.0. تأكد من أن التصدير الذي يتم استيراده من مصدر موثوق به، ويفضل استخدام عمليات التصدير التي تم الحصول عليها من NewPipe 0.27.0 أو الأحدث في المستقبل فقط. سيتم قريبًا إزالة دعم استيراد الإعدادات بهذا التنسيق الضعيف تمامًا، وبعد ذلك لن تتمكن الإصدارات القديمة من NewPipe من استيراد إعدادات التصدير من الإصدارات الجديدة بعد الآن.</string>
</resources> <string name="audio_track_type_secondary">الثانوي</string>
</resources>

View File

@ -213,7 +213,7 @@
<string name="downloads_storage_ask_title">Haraya endiriləcəyini soruş</string> <string name="downloads_storage_ask_title">Haraya endiriləcəyini soruş</string>
<string name="downloads_storage_ask_summary">Sizdən hər endirmənin harada saxlanılacağı soruşulacaq. <string name="downloads_storage_ask_summary">Sizdən hər endirmənin harada saxlanılacağı soruşulacaq.
\nXarici SD karta endirmək istəyirsinizsə, sistem qovluğu seçicisini (SAF) aktiv edin</string> \nXarici SD karta endirmək istəyirsinizsə, sistem qovluğu seçicisini (SAF) aktiv edin</string>
<string name="downloads_storage_use_saf_summary">\'Yaddaş Giriş Çərçivəsi \' xarici SD karta endirməyə imkan verir</string> <string name="downloads_storage_use_saf_summary">\'Yaddaş Giriş Quruluşu\' xarici SD karta endirməyə imkan verir</string>
<string name="systems_language">Sistem standartı</string> <string name="systems_language">Sistem standartı</string>
<string name="app_language_title">Tətbiq dili</string> <string name="app_language_title">Tətbiq dili</string>
<plurals name="days"> <plurals name="days">
@ -236,17 +236,7 @@
<string name="feed_update_threshold_title">Axın yeniləmə aralığı</string> <string name="feed_update_threshold_title">Axın yeniləmə aralığı</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Sürətli rejimi aktivləşdir</string> <string name="feed_use_dedicated_fetch_method_enable_button">Sürətli rejimi aktivləşdir</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Sürətli rejimi bağla</string> <string name="feed_use_dedicated_fetch_method_disable_button">Sürətli rejimi bağla</string>
<string name="feed_use_dedicated_fetch_method_help_text">Axının çox yavaş yükləndiyini düşünürsünüz?Elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdə dəyişə və ya aşağıdakı düyməni basa bilərsiniz). <string name="feed_use_dedicated_fetch_method_help_text">Axının çox yavaş yükləndiyini düşünürsünüz? \n Elədirsə, sürətli yükləməni işə salmağı sınayın (tənzimləmələrdə dəyişə və ya aşağıdakı düyməni basa bilərsiniz). \n \nNewPipe 2 axın yükləmə üsulu təklif edir: \n• Yavaş, lakin tam şəkildə bütün abunəlik kanalı gətirilir. \n• Ayrılmış xidmət uc nöqtəsi istifadə etmək, bu sürətlidir, amma tam deyil. \n \nİkisi arasında fərq budur ki, sürətlisində, adətən elementin müddəti və növü kimi bəzi məlumatlar çatışmır (canlı video ilə adisini ayırd edə bilmir) və daha az elementlər gətirir. \n \nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir. \n \nBeləliklə, seçim sizin nəyə üstünlük verməyinizdən asılıdır: sürət yoxsa dəqiq məlumat.</string>
\n
\nNewPipe 2 axın yükləmə üsulu təklif edir:
\n• Yavaş, lakin tam şəkildə bütün abunəlik kanalı gətirilir.
\n• Ayrılmış xidmət uc nöqtəsi istifadə etmək, bu sürətlidir, amma tam deyil.
\n
\nİkisi arasında fərq budur ki, sürətlisində, adətən elementin müddəti və növü kimi bəzi məlumatlar çatışmır (canlı video ilə adisini ayırd edə bilmir) və daha az elementlər gətirir.
\n
\nYouTube öz RSS axını ilə bu sürətli metodu təklif edən xidmətlərdən biridir.
\n
\nBeləliklə, seçim sizin nəyə üstünlük verməyinizdən asılıdır: sürət yoxsa dəqiq məlumat.</string>
<string name="player_stream_failure">Bu yayımı oynatmaq alınmadı</string> <string name="player_stream_failure">Bu yayımı oynatmaq alınmadı</string>
<string name="app_ui_crash">Tətbiq/UI çökdü</string> <string name="app_ui_crash">Tətbiq/UI çökdü</string>
<string name="could_not_setup_download_menu">Endirmə menyusunu qurmaq mümkün olmadı</string> <string name="could_not_setup_download_menu">Endirmə menyusunu qurmaq mümkün olmadı</string>
@ -456,7 +446,7 @@
<string name="error_http_no_content">Server məlumat göndərmir</string> <string name="error_http_no_content">Server məlumat göndərmir</string>
<string name="error_download_resource_gone">Bu endirməni bərpa etmək mümkün deyil</string> <string name="error_download_resource_gone">Bu endirməni bərpa etmək mümkün deyil</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Sizdən hər endirmənin harada saxlanılacağı soruşulacaq</string> <string name="downloads_storage_ask_summary_no_saf_notice">Sizdən hər endirmənin harada saxlanılacağı soruşulacaq</string>
<string name="downloads_storage_use_saf_summary_api_29">\"Yaddaş Giriş Çərçivəsi\"yalnız Android 10\'dan başlayaraq dəstəklənir</string> <string name="downloads_storage_use_saf_summary_api_29">\"Yaddaş Giriş Quruluşu\"yalnız Android 10\'dan başlayaraq dəstəklənir</string>
<string name="detail_sub_channel_thumbnail_view_description">Kanalın avatar miniatürü</string> <string name="detail_sub_channel_thumbnail_view_description">Kanalın avatar miniatürü</string>
<string name="select_night_theme_toast">Sevdiyiniz gecə temasını aşağıda seçə bilərsiniz</string> <string name="select_night_theme_toast">Sevdiyiniz gecə temasını aşağıda seçə bilərsiniz</string>
<string name="notification_colorize_summary">Android\'in bildiriş rəngini miniatürdəki əsas rəngə uyğun fərdiləşdirməsini təmin et (qeyd edək ki, bu, bütün cihazlarda mövcud deyil)</string> <string name="notification_colorize_summary">Android\'in bildiriş rəngini miniatürdəki əsas rəngə uyğun fərdiləşdirməsini təmin et (qeyd edək ki, bu, bütün cihazlarda mövcud deyil)</string>
@ -614,7 +604,7 @@
<string name="export_to">Bura ixrac et</string> <string name="export_to">Bura ixrac et</string>
<string name="import_file_title">Faylı idxal et</string> <string name="import_file_title">Faylı idxal et</string>
<string name="subscriptions_import_unsuccessful">Abunəlikləri idxal etmək mümkün olmadı</string> <string name="subscriptions_import_unsuccessful">Abunəlikləri idxal etmək mümkün olmadı</string>
<string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Xahiş edirik, diqqətlə oxuyun. Xəta məlumatın bizə göndərmək üçün qəbul etməlisiniz.</string> <string name="start_accept_privacy_policy">Avropa Ümumi Məlumat Mühafizəsi Qaydasına (GDPR) riayət etmək üçün diqqətinizi NewPipe məxfilik siyasətinə cəlb edirik. Xahiş edirik, diqqətlə oxuyun.\nXəta məlumatın bizə göndərmək üçün qəbul etməlisiniz.</string>
<string name="overwrite_unrelated_warning">Bu adda fayl artıq mövcuddur</string> <string name="overwrite_unrelated_warning">Bu adda fayl artıq mövcuddur</string>
<string name="download_already_pending">Bu adla gözlənilən bir endirmə var</string> <string name="download_already_pending">Bu adla gözlənilən bir endirmə var</string>
<string name="error_path_creation">Təyinat qovluğu yaradıla bilməz</string> <string name="error_path_creation">Təyinat qovluğu yaradıla bilməz</string>
@ -663,16 +653,7 @@
<string name="loading_stream_details">Yayım təfərrüatları yüklənir…</string> <string name="loading_stream_details">Yayım təfərrüatları yüklənir…</string>
<string name="disable_media_tunneling_title">Media girişin qeyri-aktiv et</string> <string name="disable_media_tunneling_title">Media girişin qeyri-aktiv et</string>
<string name="crash_the_app">Tətbiq çökdü</string> <string name="crash_the_app">Tətbiq çökdü</string>
<string name="import_youtube_instructions">YouTube abunəliklərin Google Takeout-dan <string name="import_youtube_instructions">YouTube abunəliklərin Google Takeout-dan idxal et: \n \n1. %1$s URL\'ə keçin: \n2. Soruşulduqda daxil ol \n3. \"Bütün Məlumatlar Daxildir\",sonra \"Hamısın Seçmə\", yalnız \"abunəlikləri\" seç və \"Oldu\" kliklə \n4. \"Növbəti addım\"üzərinə kliklə, sonra isə \"İxrac Yarat\" üzərinə kliklə \n5. Görünəndən sonra, \"Endirin\"düyməsin bas \n6. Aşağıda FAYLI İDXAL ET düyməsin kliklə və yüklənilmiş (.zip) faylın seç \n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylın çıxar(adətən\"YouTubeandYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda FAYLI İDXAL ET-ə kliklə və çıxarılan csv faylın seç</string>
\nidxal et:
\n
\n1. %1$s URL\'ə keçin:
\n2. Soruşulduqda daxil ol
\n3. \"Bütün Məlumatlar Daxildir\",sonra \"Hamısın Seçmə\", yalnız \"abunəlikləri\" seç və \"Oldu\" kliklə
\n4. \"Növbəti addım\"üzərinə kliklə, sonra isə \"İxrac Yarat\" üzərinə kliklə
\n5. Görünəndən sonra, \"Endirin\"düyməsin bas
\n6. Aşağıda FAYLI İDXAL ET düyməsin kliklə və yüklənilmiş (.zip) faylın seç
\n7. [Əgər .zip faylı idxalı uğursuz olsa] .csv faylın çıxar(adətən\"YouTubeandYouTubeMusic/subscriptions/subscriptions.csv\" altında),aşağıda FAYLI İDXAL ET-ə kliklə və çıxarılan csv faylın seç</string>
<string name="playback_speed_control">Oynatma Sürəti Nizamlamaları</string> <string name="playback_speed_control">Oynatma Sürəti Nizamlamaları</string>
<string name="unhook_checkbox">Ayır (pozuntuya səbəb ola bilər)</string> <string name="unhook_checkbox">Ayır (pozuntuya səbəb ola bilər)</string>
<string name="show_error">Xətanı göstər</string> <string name="show_error">Xətanı göstər</string>
@ -686,11 +667,9 @@
<string name="feed_load_error_fast_unknown">Sürətli axın rejimi bu barədə əlavə məlumat vermir.</string> <string name="feed_load_error_fast_unknown">Sürətli axın rejimi bu barədə əlavə məlumat vermir.</string>
<string name="new_seek_duration_toast">ExoPlayer məhdudiyyətlərinə görə axtarış müddəti %d saniyəyə təyin edildi</string> <string name="new_seek_duration_toast">ExoPlayer məhdudiyyətlərinə görə axtarış müddəti %d saniyəyə təyin edildi</string>
<string name="feed_use_dedicated_fetch_method_summary">Bəzi xidmətlərdə mövcuddur, adətən daha sürətli olur, lakin məhdud sayda elementləri və çox vaxt natamam məlumatı qaytara bilər (məsələn, müddət, element növü, canlı status yoxdur)</string> <string name="feed_use_dedicated_fetch_method_summary">Bəzi xidmətlərdə mövcuddur, adətən daha sürətli olur, lakin məhdud sayda elementləri və çox vaxt natamam məlumatı qaytara bilər (məsələn, müddət, element növü, canlı status yoxdur)</string>
<string name="no_appropriate_file_manager_message">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. <string name="no_appropriate_file_manager_message">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. Zəhmət olmasa, fayl menecerini quraşdır və ya endirmə tənzimləmələrində \'%s\'-i qeyri-aktiv etməyə çalış</string>
\nZəhmət olmasa, fayl menecerini quraşdır və ya endirmə tənzimləmələrində \'%s\'-i qeyri-aktiv etməyə çalış</string>
<string name="feed_load_error_account_info">\'%s\' üçün axın yükləmək mümkün olmadı.</string> <string name="feed_load_error_account_info">\'%s\' üçün axın yükləmək mümkün olmadı.</string>
<string name="no_appropriate_file_manager_message_android_10">Bu əməliyyat üçün uyğun fayl meneceri tapılmadı. <string name="no_appropriate_file_manager_message_android_10">Bu fəaliyyət üçün uyğun fayl meneceri tapılmadı.\nXahiş olunur, Yaddaş Giriş Quruluşuna uyğun fayl meneceri quraşdırın</string>
\nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın</string>
<string name="youtube_music_premium_content">Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string> <string name="youtube_music_premium_content">Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil.</string>
<string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və linklər kliklənməyə bilər.</string> <string name="description_select_note">İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə və linklər kliklənməyə bilər.</string>
<string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs</string> <string name="notification_scale_to_square_image_summary">Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 görünüş nisbətinə qədər kəs</string>
@ -748,9 +727,7 @@
<string name="settings_category_exoplayer_summary">Bəzi ExoPlayer tənzimləmələrin idarə et. Bu dəyişiklikləri təsirli etmək üçün oynadıcını yenidən başlatmaq tələb olunur</string> <string name="settings_category_exoplayer_summary">Bəzi ExoPlayer tənzimləmələrin idarə et. Bu dəyişiklikləri təsirli etmək üçün oynadıcını yenidən başlatmaq tələb olunur</string>
<string name="use_exoplayer_decoder_fallback_title">ExoPlayer-in çözücü xüsusiyyətin istifadə et</string> <string name="use_exoplayer_decoder_fallback_title">ExoPlayer-in çözücü xüsusiyyətin istifadə et</string>
<string name="use_exoplayer_decoder_fallback_summary">Əsas çözücüləri işlətmə uğursuz olarsa, çözücü işlətmək probleminiz varsa (daha aşağı prioritetli çözücülərə düşür), bu seçimi aktiv edin. Bu, əsas çözücülərdən istifadə ilə müqayisədə zəif oynatma performansı ilə nəticələnə bilər</string> <string name="use_exoplayer_decoder_fallback_summary">Əsas çözücüləri işlətmə uğursuz olarsa, çözücü işlətmək probleminiz varsa (daha aşağı prioritetli çözücülərə düşür), bu seçimi aktiv edin. Bu, əsas çözücülərdən istifadə ilə müqayisədə zəif oynatma performansı ilə nəticələnə bilər</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Bu həll yolu səthi kodlayıcıya birbaşa tənzimləmək əvəzinə, səth dəyişikliyi olarsa video kodlayıcıları buraxır və yenidən işlədir. Artıq ExoPlayer vasitəsilə bu problemli bəzi cihazlarda istifadə olunur, bu tənzimləmə təsiri yalnız Android 6 və daha yüksəkdə var. <string name="always_use_exoplayer_set_output_surface_workaround_summary">Bu həll yolu səthi kodlayıcıya birbaşa tənzimləmək əvəzinə, səth dəyişikliyi olarsa video kodlayıcıları buraxır və yenidən işlədir. Artıq ExoPlayer vasitəsilə bu problemli bəzi cihazlarda istifadə olunur, bu tənzimləmə təsiri yalnız Android 6 və daha yüksəkdə var.\n\nBu seçimi işlətmə cari video oynadıcı dəyişəndə və ya tam ekrana keçəndə oynatma xətaların düzəldə bilər</string>
\n
\nBu seçimi işlətmə cari video oynadıcı dəyişəndə və ya tam ekrana keçəndə oynatma xətaların düzəldə bilər</string>
<string name="audio_track_type_original">orijinal</string> <string name="audio_track_type_original">orijinal</string>
<string name="audio_track_type_dubbed">dublyaj edilib</string> <string name="audio_track_type_dubbed">dublyaj edilib</string>
<string name="audio_track_type_descriptive">təsviri</string> <string name="audio_track_type_descriptive">təsviri</string>
@ -766,7 +743,7 @@
<string name="feed_fetch_channel_tabs">Kanal səhifələrin əldə et</string> <string name="feed_fetch_channel_tabs">Kanal səhifələrin əldə et</string>
<string name="metadata_avatars">Avatarlar</string> <string name="metadata_avatars">Avatarlar</string>
<string name="metadata_subchannel_avatars">Alt kanal avatarları</string> <string name="metadata_subchannel_avatars">Alt kanal avatarları</string>
<string name="feed_fetch_channel_tabs_summary">Axın yenilənərkən əldə edilən səhifələr.Kanal sürətli rejim istifadə edərək yenilənirsə, bu seçimin heç bir təsiri yoxdur.</string> <string name="feed_fetch_channel_tabs_summary">Axın yenilənərkən əldə edilən səhifələr. Kanal sürətli rejim istifadə edərək yenilənirsə, bu seçimin heç bir təsiri yoxdur.</string>
<string name="metadata_uploader_avatars">Yükləyici avatarları</string> <string name="metadata_uploader_avatars">Yükləyici avatarları</string>
<string name="metadata_thumbnails">Miniatürlər</string> <string name="metadata_thumbnails">Miniatürlər</string>
<string name="notification_actions_summary_android13">Aşağıdakı hər bildiriş fəaliyyətin ona toxunub redaktə edin. İlk üç fəaliyyət (oynatma/fasilə, əvvəlki və növbəti) sistem tərəfindən tənzimlənib və dəyişdirilə bilməz.</string> <string name="notification_actions_summary_android13">Aşağıdakı hər bildiriş fəaliyyətin ona toxunub redaktə edin. İlk üç fəaliyyət (oynatma/fasilə, əvvəlki və növbəti) sistem tərəfindən tənzimlənib və dəyişdirilə bilməz.</string>
@ -775,7 +752,7 @@
<string name="auto_update_check_description">NewPipe arabir avtomatik şəkildə yeni versiyaları yoxlaya və onlar mövcud olduqda sizə bildirə bilər. <string name="auto_update_check_description">NewPipe arabir avtomatik şəkildə yeni versiyaları yoxlaya və onlar mövcud olduqda sizə bildirə bilər.
\nBunu aktivləşdirmək istəyirsiniz?</string> \nBunu aktivləşdirmək istəyirsiniz?</string>
<string name="yes">Bəli</string> <string name="yes">Bəli</string>
<string name="reset_settings_title">Sıfırlama tənzimləmələri</string> <string name="reset_settings_title">Tənzimləmələri sıfırla</string>
<string name="reset_settings_summary">Bütün tənzimləmələri standart dəyərlərinə sıfırla</string> <string name="reset_settings_summary">Bütün tənzimləmələri standart dəyərlərinə sıfırla</string>
<string name="error_insufficient_storage">Cihazda yetərlik boş yer yoxdur</string> <string name="error_insufficient_storage">Cihazda yetərlik boş yer yoxdur</string>
<string name="metadata_subscribers">Abunəliklər</string> <string name="metadata_subscribers">Abunəliklər</string>
@ -810,7 +787,6 @@
<string name="image_quality_high">Yüksək keyfiyyət</string> <string name="image_quality_high">Yüksək keyfiyyət</string>
<string name="question_mark">\?</string> <string name="question_mark">\?</string>
<string name="share_playlist">Oynatma siyahısın paylaş</string> <string name="share_playlist">Oynatma siyahısın paylaş</string>
<string name="share_playlist_with_titles_message">Pleylist adı və video başlıqları kimi təfsilatlar və ya video URL-lərin sadə siyahısı olaraq pleylist paylaş</string>
<string name="share_playlist_with_titles">Başlıqlarla paylaşın</string> <string name="share_playlist_with_titles">Başlıqlarla paylaşın</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
@ -826,4 +802,5 @@
<string name="image_quality_title">Şəkil keyfiyyəti</string> <string name="image_quality_title">Şəkil keyfiyyəti</string>
<string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string> <string name="image_quality_summary">Məlumat və yaddaş istifadəsini azaltmaq üçün şəkillərin keyfiyyətini və ya şəkillərin əsla yüklənib-yüklənilməməsini seçin. Dəyişikliklər həm yaddaşdaxili, həm də diskdə olan təsvir qalığın təmizləyir — %s</string>
<string name="share_playlist_with_list">URL siyahısını paylaşın</string> <string name="share_playlist_with_list">URL siyahısını paylaşın</string>
</resources> <string name="audio_track_type_secondary">ikinci dərəcəli</string>
</resources>

View File

@ -563,4 +563,4 @@
<string name="install">Instalar</string> <string name="install">Instalar</string>
<string name="no_player_found">Nun s\'atopó nengún reproductor de fluxos. ¿Instalar VLC\?</string> <string name="no_player_found">Nun s\'atopó nengún reproductor de fluxos. ¿Instalar VLC\?</string>
<string name="main_bg_subtitle">Toca «Buscar» pa entamar</string> <string name="main_bg_subtitle">Toca «Buscar» pa entamar</string>
</resources> </resources>

View File

@ -555,4 +555,4 @@
<string name="error_unable_to_load_comments">Fikrlarni yuklab bolmadi</string> <string name="error_unable_to_load_comments">Fikrlarni yuklab bolmadi</string>
<string name="import_settings">Sozlamalarni ham import qilmoqchimisiz\?</string> <string name="import_settings">Sozlamalarni ham import qilmoqchimisiz\?</string>
<string name="override_current_data">Bu sizning joriy sozlamangizni bekor qiladi.</string> <string name="override_current_data">Bu sizning joriy sozlamangizni bekor qiladi.</string>
</resources> </resources>

View File

@ -26,4 +26,6 @@
<string name="use_external_video_player_summary">Duad bei manchen Auflösungen d\'Tonspur weggad</string> <string name="use_external_video_player_summary">Duad bei manchen Auflösungen d\'Tonspur weggad</string>
<string name="open_in_popup_mode">Im Pop-up Modus aufmacha</string> <string name="open_in_popup_mode">Im Pop-up Modus aufmacha</string>
<string name="main_bg_subtitle">Drug auf\'d Lubn zum ofanga.</string> <string name="main_bg_subtitle">Drug auf\'d Lubn zum ofanga.</string>
</resources> <string name="ok">Bassd scho</string>
<string name="no">naa</string>
</resources>

File diff suppressed because it is too large Load Diff

View File

@ -57,10 +57,10 @@
<string name="resume_on_audio_focus_gain_title">Възобновяване</string> <string name="resume_on_audio_focus_gain_title">Възобновяване</string>
<string name="resume_on_audio_focus_gain_summary">Продължавай възпроизвеждането след прекъсване (например телефонно обаждане)</string> <string name="resume_on_audio_focus_gain_summary">Продължавай възпроизвеждането след прекъсване (например телефонно обаждане)</string>
<string name="download_dialog_title">Изтегли</string> <string name="download_dialog_title">Изтегли</string>
<string name="show_next_and_similar_title">Показвай „следващ“ и „подобни“</string> <string name="show_next_and_similar_title">Покажи „следващ“ и „подобни“</string>
<string name="show_hold_to_append_title">Показване на съвета „Задръжте за поставяне в опашка“</string> <string name="show_hold_to_append_title">Покажи съвет „Задръжте за поставяне в опашка“</string>
<string name="unsupported_url">Непознат URL</string> <string name="unsupported_url">Непознат URL</string>
<string name="content_language_title">Език на съдържанието по подразбиране</string> <string name="content_language_title">Език на съдържание по подразбиране</string>
<string name="settings_category_player_title">Плейър</string> <string name="settings_category_player_title">Плейър</string>
<string name="settings_category_player_behavior_title">Поведение</string> <string name="settings_category_player_behavior_title">Поведение</string>
<string name="settings_category_video_audio_title">Видео и аудио</string> <string name="settings_category_video_audio_title">Видео и аудио</string>
@ -69,7 +69,7 @@
<string name="background_player_playing_toast">Възпроизвеждане във фонов режим</string> <string name="background_player_playing_toast">Възпроизвеждане във фонов режим</string>
<string name="popup_playing_toast">Възпроизвеждане в подпрозорец</string> <string name="popup_playing_toast">Възпроизвеждане в подпрозорец</string>
<string name="content">Съдържание</string> <string name="content">Съдържание</string>
<string name="show_age_restricted_content_title">Показване на съдържание с възрастови ограничения</string> <string name="show_age_restricted_content_title">Покажи съдържание с възрастови ограничения</string>
<string name="duration_live">На живо</string> <string name="duration_live">На живо</string>
<string name="downloads">Изтегляния</string> <string name="downloads">Изтегляния</string>
<string name="downloads_title">Изтегляния</string> <string name="downloads_title">Изтегляния</string>
@ -152,7 +152,7 @@
<string name="use_inexact_seek_title">Използвай бързо, но неточно превъртане</string> <string name="use_inexact_seek_title">Използвай бързо, но неточно превъртане</string>
<string name="use_inexact_seek_summary">По-бързо превъртане с по-ниска прецизност. Превъртане с по 5, 15 или 25 секунди няма да работи с тази опция</string> <string name="use_inexact_seek_summary">По-бързо превъртане с по-ниска прецизност. Превъртане с по 5, 15 или 25 секунди няма да работи с тази опция</string>
<string name="thumbnail_cache_wipe_complete_notice">Кеш-паметта с изображения е изтрита</string> <string name="thumbnail_cache_wipe_complete_notice">Кеш-паметта с изображения е изтрита</string>
<string name="metadata_cache_wipe_title">Изтрий кешираните метаданни</string> <string name="metadata_cache_wipe_title">Изтрий кешираните мета данни</string>
<string name="metadata_cache_wipe_summary">Премахни всички метаданни за уебстраници от кеш-паметта</string> <string name="metadata_cache_wipe_summary">Премахни всички метаданни за уебстраници от кеш-паметта</string>
<string name="metadata_cache_wipe_complete_notice">Кеш-паметта с метаданни бе изтрита</string> <string name="metadata_cache_wipe_complete_notice">Кеш-паметта с метаданни бе изтрита</string>
<string name="auto_queue_title">Автоматично поставяне на следващия поток в опашка</string> <string name="auto_queue_title">Автоматично поставяне на следващия поток в опашка</string>
@ -165,8 +165,8 @@
<string name="switch_to_background">Мини във фонов режим</string> <string name="switch_to_background">Мини във фонов режим</string>
<string name="switch_to_popup">Мини към нов прозорец</string> <string name="switch_to_popup">Мини към нов прозорец</string>
<string name="switch_to_main">Мини в основен режим</string> <string name="switch_to_main">Мини в основен режим</string>
<string name="import_data_title">Импортиране на база данни</string> <string name="import_data_title">Внасяне на база данни</string>
<string name="export_data_title">Експортиране на база данни</string> <string name="export_data_title">Изнасяне на база данни</string>
<string name="import_data_summary">Замества текущата ви история, абонаменти, списъци за възпроизвеждане и (по избор) настройки</string> <string name="import_data_summary">Замества текущата ви история, абонаменти, списъци за възпроизвеждане и (по избор) настройки</string>
<string name="export_data_summary">Изнасяне на история, абонаменти, плейлисти и настройки</string> <string name="export_data_summary">Изнасяне на история, абонаменти, плейлисти и настройки</string>
<string name="clear_views_history_title">Изтрий историята с изгледани</string> <string name="clear_views_history_title">Изтрий историята с изгледани</string>
@ -192,7 +192,7 @@
<string name="copyright" formatted="true">© %1$s от %2$s под лиценза %3$s</string> <string name="copyright" formatted="true">© %1$s от %2$s под лиценза %3$s</string>
<string name="contribution_title">Съдействайте</string> <string name="contribution_title">Съдействайте</string>
<string name="contribution_encouragement">За всичко, което се сетите: превод, промени по дизайна, изчистване на кода или много сериозни промени по кода помощта е винаги добре дошла. Колкото повече развитие, толкова по-добре!</string> <string name="contribution_encouragement">За всичко, което се сетите: превод, промени по дизайна, изчистване на кода или много сериозни промени по кода помощта е винаги добре дошла. Колкото повече развитие, толкова по-добре!</string>
<string name="donation_title">Направете дарение</string> <string name="donation_title">Дарение</string>
<string name="donation_encouragement">NewPipe се разработва от доброволци, които отделят от своето време, за да предоставят най-доброто потребителско изживяване. Включете се в разработката като почерпите разработчиците с една чашка кафе, които да изпият, докато правят NewPipe още по-добро приложение.</string> <string name="donation_encouragement">NewPipe се разработва от доброволци, които отделят от своето време, за да предоставят най-доброто потребителско изживяване. Включете се в разработката като почерпите разработчиците с една чашка кафе, които да изпият, докато правят NewPipe още по-добро приложение.</string>
<string name="give_back">Дари</string> <string name="give_back">Дари</string>
<string name="website_title">Уебсайт</string> <string name="website_title">Уебсайт</string>
@ -203,7 +203,7 @@
<string name="read_privacy_policy">Прочетете нашата политика за поверителност</string> <string name="read_privacy_policy">Прочетете нашата политика за поверителност</string>
<string name="app_license_title">Лицензът на NewPipe</string> <string name="app_license_title">Лицензът на NewPipe</string>
<string name="no_player_found_toast">Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма).</string> <string name="no_player_found_toast">Липсва стрийм плейър (можете да изтеглите VLC, за да пуснете стрийма).</string>
<string name="show_hold_to_append_summary">Показване на съвет при натискане на фона или изскачащия бутон във видеоклипа Подробности:“</string> <string name="show_hold_to_append_summary">Покажи съвет при натискане на фона или изскачащия бутон във видеоклипа \"Подробности:“</string>
<string name="clear_views_history_summary">Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането</string> <string name="clear_views_history_summary">Изтрива историята на възпроизвежданите стриймове и позицията на възпроизвеждането</string>
<string name="video_streams_empty">Не са намерени видео стриймове</string> <string name="video_streams_empty">Не са намерени видео стриймове</string>
<string name="audio_streams_empty">Не са намерени аудио стриймове</string> <string name="audio_streams_empty">Не са намерени аудио стриймове</string>
@ -233,12 +233,12 @@
<string name="select_a_channel">Изберете канал</string> <string name="select_a_channel">Изберете канал</string>
<string name="no_channel_subscribed_yet">За момента нямате абонаменти</string> <string name="no_channel_subscribed_yet">За момента нямате абонаменти</string>
<string name="select_a_kiosk">Изберете павилион</string> <string name="select_a_kiosk">Изберете павилион</string>
<string name="export_complete_toast">Експортирането приключи</string> <string name="export_complete_toast">Изнасянето приключи</string>
<string name="import_complete_toast">Импортирането приключи</string> <string name="import_complete_toast">Внасянето приключи</string>
<string name="no_valid_zip_file">Невалиден ZIP файл</string> <string name="no_valid_zip_file">Невалиден ZIP файл</string>
<string name="could_not_import_all_files">Внимание: не всички файлове бяха импортирани успешно.</string> <string name="could_not_import_all_files">Внимание: не всички файлове бяха внесени успешно.</string>
<string name="override_current_data">Това ще замени текущата Ви инсталация.</string> <string name="override_current_data">Това ще замени текущата Ви инсталация.</string>
<string name="import_settings">Желаете ли също да импортирате настройките?</string> <string name="import_settings">Искате ли да внесете и настройки?</string>
<string name="trending">Набиращи популярност</string> <string name="trending">Набиращи популярност</string>
<string name="top_50">Топ 50</string> <string name="top_50">Топ 50</string>
<string name="new_and_hot">Ново и горещо</string> <string name="new_and_hot">Ново и горещо</string>
@ -278,30 +278,17 @@
<string name="caption_setting_description">Модифицирай текстовия мащаб на надписите и стила на техния фон. За промените се изисква рестарт на приложението</string> <string name="caption_setting_description">Модифицирай текстовия мащаб на надписите и стила на техния фон. За промените се изисква рестарт на приложението</string>
<string name="enable_leak_canary_summary">Следенето за пропускане на памет може да направи приложението нестабилно</string> <string name="enable_leak_canary_summary">Следенето за пропускане на памет може да направи приложението нестабилно</string>
<string name="enable_disposed_exceptions_title">Докладвай за извънредни грешки</string> <string name="enable_disposed_exceptions_title">Докладвай за извънредни грешки</string>
<string name="import_title">Импортирай</string> <string name="import_title">Внасяне</string>
<string name="import_from">Импортирай от</string> <string name="import_from">Внасяне от</string>
<string name="export_to">Експортирай в</string> <string name="export_to">Изнеси в</string>
<string name="import_ongoing">Импортиране…</string> <string name="import_ongoing">Внасяне…</string>
<string name="export_ongoing">Експортиране…</string> <string name="export_ongoing">Изнасяне…</string>
<string name="import_file_title">Файл с данни за импортиране</string> <string name="import_file_title">Файл с данни за внасяне</string>
<string name="previous_export">Предишно експортиране</string> <string name="previous_export">Предишно изнасяне</string>
<string name="subscriptions_import_unsuccessful">Неуспешно импортиране на абонатите</string> <string name="subscriptions_import_unsuccessful">Неуспешно внасяне на абонатите</string>
<string name="subscriptions_export_unsuccessful">Неуспешно експортиране на абонатите</string> <string name="subscriptions_export_unsuccessful">Неуспешно изнасяне на абонатите</string>
<string name="import_youtube_instructions">Импортиране на абонаменти в YouTube от Google Takeout: <string name="import_youtube_instructions">Внасяне на абонаменти в YouTube от Google Takeout: \n \n1. Отидете на този URL: %1$s \n2. Влезте, когато бъдете помолени \n3. Щракнете върху „Всички включени данни“, след това върху „Демаркиране на всички“, след това изберете само „абонаменти“ и щракнете върху „OK“ \n4. Кликнете върху „Следваща стъпка“ и след това върху „Създаване на износ“ \n5. Кликнете върху бутона \"Изтегляне\", след като се появи \n6. Кликнете върху ВНАСЯНЕ НА ФАЙЛ по-долу и изберете изтегления .zip файл \n7. [Ако внасянето на .zip е неуспешно] Разархивирайте .csv файла (обикновено под „YouTube и YouTube Music/subscriptions/subscriptions.csv“), щракнете върху ВНАСЯНЕ НА ФАЙЛ по-долу и изберете извлечения csv файл</string>
\n <string name="import_soundcloud_instructions">Внесете профил в SoundCloud, като въведете или URL адреса, или вашия ID: \n \n1. Включете „десктоп режим“ в браузър (сайтът е недостъпен за мобилни устройства) \n2. Посетете връзката: %1$s \n3. Влезте в профила си, ако се изисква \n4. Копирайте хипервръзката на профилната страница, към която сте насочени.</string>
\n1. Отидете на този URL: %1$s
\n2. Влезте, когато бъдете помолени
\n3. Щракнете върху „Всички включени данни“, след това върху „Демаркиране на всички“, след това изберете само „абонаменти“ и щракнете върху „OK“
\n4. Кликнете върху „Следваща стъпка“ и след това върху „Създаване на експорт“
\n5. Кликнете върху бутона \"Изтегляне\", след като се появи
\n6. Кликнете върху ИМПОРТИРАНЕ НА ФАЙЛ по-долу и изберете изтегления .zip файл
\n7. [Ако импортирането на .zip е неуспешно] Разархивирайте .csv файла (обикновено под „YouTube и YouTube Music/subscriptions/subscriptions.csv“), щракнете върху ИМПОРТИРАНЕ НА ФАЙЛ по-долу и изберете извлечения csv файл</string>
<string name="import_soundcloud_instructions">Импортирайте SoundCloud профил чрез въвеждане на хипервръзката към него или чрез вашия ID:
\n
\n1. Включете „десктоп режим“ в браузър (сайтът е недостъпен за мобилни устройства)
\n2. Посетете връзката: %1$s
\n3. Влезте в профила си, ако се изисква
\n4. Копирайте хипервръзката на профилната страница, към която сте насочени.</string>
<string name="import_soundcloud_instructions_hint">вашиятID, soundcloud.com/вашиятID</string> <string name="import_soundcloud_instructions_hint">вашиятID, soundcloud.com/вашиятID</string>
<string name="import_network_expensive_warning">Това действие може да изразходва голямо количество данни от вашия трафик. <string name="import_network_expensive_warning">Това действие може да изразходва голямо количество данни от вашия трафик.
\n \n
@ -332,8 +319,8 @@
<string name="unsubscribe">Отписване</string> <string name="unsubscribe">Отписване</string>
<string name="file_deleted">Файлът е изтрит</string> <string name="file_deleted">Файлът е изтрит</string>
<string name="events">Събития</string> <string name="events">Събития</string>
<string name="show_comments_title">Показвай коментари</string> <string name="show_comments_title">Покажи коментари</string>
<string name="show_comments_summary">Изключване за скриване на коментарите</string> <string name="show_comments_summary">Изключете за скриване на коментарите</string>
<string name="autoplay_title">Автоматично пускане</string> <string name="autoplay_title">Автоматично пускане</string>
<string name="tab_choose">Избор на раздел</string> <string name="tab_choose">Избор на раздел</string>
<string name="settings_category_updates_title">Промени</string> <string name="settings_category_updates_title">Промени</string>
@ -353,7 +340,7 @@
<string name="no_one_watching">Няма зрители</string> <string name="no_one_watching">Няма зрители</string>
<string name="clear_playback_states_summary">Изтрива всички позиции на възпроизвеждане</string> <string name="clear_playback_states_summary">Изтрива всички позиции на възпроизвеждане</string>
<string name="unsupported_url_dialog_message">URL адресът не можа да бъде разпознат. Да се отвори ли с друго приложение?</string> <string name="unsupported_url_dialog_message">URL адресът не можа да бъде разпознат. Да се отвори ли с друго приложение?</string>
<string name="show_description_title">Показване на описание</string> <string name="show_description_title">Покажи описание</string>
<string name="night_theme_title">Нощна тема</string> <string name="night_theme_title">Нощна тема</string>
<string name="notification_colorize_title">Оцветяване на известие</string> <string name="notification_colorize_title">Оцветяване на известие</string>
<string name="notification_action_nothing">Нищо</string> <string name="notification_action_nothing">Нищо</string>
@ -371,7 +358,7 @@
<string name="dont_show">Не показвай</string> <string name="dont_show">Не показвай</string>
<string name="local_search_suggestions">Местни предложения за търсене</string> <string name="local_search_suggestions">Местни предложения за търсене</string>
<string name="error_report_open_github_notice">Моля проверете дали има създадена дискусия за срив като този. Постъпилите дубликати са в ущърб на така ценното време за разработка.</string> <string name="error_report_open_github_notice">Моля проверете дали има създадена дискусия за срив като този. Постъпилите дубликати са в ущърб на така ценното време за разработка.</string>
<string name="show_meta_info_title">Показване на метаданни</string> <string name="show_meta_info_title">Покажи мета данни</string>
<string name="grid">Мрежа</string> <string name="grid">Мрежа</string>
<string name="list">Списък</string> <string name="list">Списък</string>
<string name="notification_action_3_title">Четвърто действие</string> <string name="notification_action_3_title">Четвърто действие</string>
@ -532,12 +519,12 @@
<string name="pause_downloads_on_mobile_desc">Полезно при превключване към мобилни данни, въпреки че някои изтегляния не поддържат възобновяване и ще започнат отначало</string> <string name="pause_downloads_on_mobile_desc">Полезно при превключване към мобилни данни, въпреки че някои изтегляния не поддържат възобновяване и ще започнат отначало</string>
<string name="crash_the_app">Срив на приложението</string> <string name="crash_the_app">Срив на приложението</string>
<string name="notification_colorize_summary">Цветът на известието да се избира според главния цвят в миниатюрата на видеото (може да не работи на всички устройства)</string> <string name="notification_colorize_summary">Цветът на известието да се избира според главния цвят в миниатюрата на видеото (може да не работи на всички устройства)</string>
<string name="youtube_restricted_mode_enabled_title">Използване на ограничения режим на YouTube</string> <string name="youtube_restricted_mode_enabled_title">Включване на \"Ограничен режим“ в YouTube</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube предлага „ограничен режим“, чрез който можете да филтрирате потенциално съдържание за възрастни</string> <string name="youtube_restricted_mode_enabled_summary">YouTube предлага „ограничен режим“, чрез който можете да филтрирате потенциално съдържание за възрастни</string>
<string name="restricted_video">Това видео е с възрастова граница. <string name="restricted_video">Това видео е с възрастова граница.
\n \n
\nВключете „%1$s“ в настройките ако искате да го пуснете.</string> \nВключете „%1$s“ в настройките ако искате да го пуснете.</string>
<string name="show_image_indicators_summary">Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен от мрежата, син от диска и червен от паметта)</string> <string name="show_image_indicators_summary">Покажи цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен от мрежата, син от диска и червен от паметта)</string>
<string name="auto_device_theme_title">Автоматична (тази на устройството)</string> <string name="auto_device_theme_title">Автоматична (тази на устройството)</string>
<string name="notification_scale_to_square_image_summary">Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания)</string> <string name="notification_scale_to_square_image_summary">Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания)</string>
<string name="select_a_playlist">Избете плейлист</string> <string name="select_a_playlist">Избете плейлист</string>
@ -568,10 +555,10 @@
<string name="progressive_load_interval_title">Размер на интервала на зареждане при възпроизвеждане</string> <string name="progressive_load_interval_title">Размер на интервала на зареждане при възпроизвеждане</string>
<string name="ignore_hardware_media_buttons_title">Игнорирайте събитията с хардуерни медийни бутони</string> <string name="ignore_hardware_media_buttons_title">Игнорирайте събитията с хардуерни медийни бутони</string>
<string name="left_gesture_control_summary">Изберете жест за лявата половина на екрана на плейъра</string> <string name="left_gesture_control_summary">Изберете жест за лявата половина на екрана на плейъра</string>
<string name="enable_playback_state_lists_summary">Показване на индикатори за позиция на изглед в списъци</string> <string name="enable_playback_state_lists_summary">Покажи индикатори за позиция на възпроизвеждане в списъци</string>
<string name="notification_actions_summary_android13">Редактирайте всяко действие за известяване по-долу, като щракнете върху него. Първите три действия (възпроизвеждане/пауза, предишно и следващо) се задават от системата и не могат да бъдат конфигурирани.</string> <string name="notification_actions_summary_android13">Редактирайте всяко действие за известяване по-долу, като щракнете върху него. Първите три действия (възпроизвеждане/пауза, предишно и следващо) се задават от системата и не могат да бъдат конфигурирани.</string>
<string name="right_gesture_control_summary">Жест за дясната страна на екрана на плейъра</string> <string name="right_gesture_control_summary">Изберете жест за дясната половина на екрана на плейъра</string>
<string name="right_gesture_control_title">Действие жеста справа</string> <string name="right_gesture_control_title">Действие с жест надясно</string>
<string name="start_main_player_fullscreen_title">Стартирайте основния плейър на цял екран</string> <string name="start_main_player_fullscreen_title">Стартирайте основния плейър на цял екран</string>
<string name="streams_notification_channel_description">Известия за нови видеоклипове в абонаментите</string> <string name="streams_notification_channel_description">Известия за нови видеоклипове в абонаментите</string>
<string name="enable_streams_notifications_summary">Известявайте за нови видеоклипове в абонаментите</string> <string name="enable_streams_notifications_summary">Известявайте за нови видеоклипове в абонаментите</string>
@ -596,12 +583,10 @@
<string name="remove_duplicates_title">Премахни повторения?</string> <string name="remove_duplicates_title">Премахни повторения?</string>
<string name="feed_groups_header_title">Група от канали</string> <string name="feed_groups_header_title">Група от канали</string>
<string name="feed_load_error_account_info">Неуспешно зареждане на емисия за \'%s\'.</string> <string name="feed_load_error_account_info">Неуспешно зареждане на емисия за \'%s\'.</string>
<string name="feed_load_error_terminated">Акаунтът на автора е бил отстранен. <string name="feed_load_error_terminated">Профилът на автора е бил отстранен. \nNewPipe няма да може да зареди тази емисия вече. \nИскате ли да махнете абонамента от този канал?</string>
\nNewPipe няма да може да зареди тази емисия вече.
\nИскате ли да махнете абонамента от този канал?</string>
<string name="feed_update_threshold_option_always_update">Винаги опреснявай</string> <string name="feed_update_threshold_option_always_update">Винаги опреснявай</string>
<string name="feed_load_error">Грешка при зареждане на емисия</string> <string name="feed_load_error">Грешка при зареждане на емисия</string>
<string name="feed_show_hide_streams">Показване/Скриване на потоци</string> <string name="feed_show_hide_streams">Покажи/Скрий потоци</string>
<string name="private_content">Съдържанието е лично, затова не може да бъде възпроизведено или изтеглено от NewPipe.</string> <string name="private_content">Съдържанието е лично, затова не може да бъде възпроизведено или изтеглено от NewPipe.</string>
<string name="select_night_theme_toast">Можете да изберете любимата си тъмна тема по-долу</string> <string name="select_night_theme_toast">Можете да изберете любимата си тъмна тема по-долу</string>
<string name="tablet_mode_title">Режим таблет</string> <string name="tablet_mode_title">Режим таблет</string>
@ -655,7 +640,7 @@
<string name="settings_category_backup_restore_title">Резервно копие и възстановяване</string> <string name="settings_category_backup_restore_title">Резервно копие и възстановяване</string>
<string name="loading_metadata_title">Зареждане на метаданни…</string> <string name="loading_metadata_title">Зареждане на метаданни…</string>
<string name="restore_defaults_confirmation">Искате ли да възстановите настройките по подразбиране?</string> <string name="restore_defaults_confirmation">Искате ли да възстановите настройките по подразбиране?</string>
<string name="show_age_restricted_content_summary">Показвай съдържание, което вероятно е неподходящо за деца, защото има възрастово ограничение (например 18+)</string> <string name="show_age_restricted_content_summary">Покажи съдържание, което вероятно е неподходящо за деца, защото има възрастово ограничение (например 18+)</string>
<string name="error_report_notification_toast">Възникна грешка, погледнете известието</string> <string name="error_report_notification_toast">Възникна грешка, погледнете известието</string>
<string name="no_dir_yet">Не е избрана директория за теглене. Изберете директорията за теглене по подразбиране сега</string> <string name="no_dir_yet">Не е избрана директория за теглене. Изберете директорията за теглене по подразбиране сега</string>
<string name="audio_track">Аудио поток</string> <string name="audio_track">Аудио поток</string>
@ -705,14 +690,14 @@
<string name="channel_tab_livestreams">На живо</string> <string name="channel_tab_livestreams">На живо</string>
<string name="error_progress_lost">Прогресът е загубен, защото файлът е изтрит</string> <string name="error_progress_lost">Прогресът е загубен, защото файлът е изтрит</string>
<string name="card">Карта</string> <string name="card">Карта</string>
<string name="delete_downloaded_files_confirm">Изтриване на всички изтеглени файлове от диска?</string> <string name="delete_downloaded_files_confirm">Изтрий всички изтеглени файлове от диска?</string>
<string name="enable_queue_limit_desc">Едно изтегляне ще се изпълнява едновременно</string> <string name="enable_queue_limit_desc">Едно изтегляне ще се изпълнява едновременно</string>
<string name="downloads_storage_ask_title">Попитайте къде да изтеглите</string> <string name="downloads_storage_ask_title">Подкана за папка за изтегляне</string>
<string name="systems_language">Система по подразбиране</string> <string name="systems_language">Система по подразбиране</string>
<string name="remove_duplicates">Премахване на дубликати</string> <string name="remove_duplicates">Премахване на дубликати</string>
<string name="remove_duplicates_message">Искате ли да премахнете всички дублиращи се потоци в този плейлист?</string> <string name="remove_duplicates_message">Искате ли да премахнете всички дублиращи се потоци в този плейлист?</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Показване само на негрупирани абонаменти</string> <string name="feed_group_show_only_ungrouped_subscriptions">Покажи само на негрупирани абонаменти</string>
<string name="feed_hide_streams_title">Покажете следните потоци</string> <string name="feed_hide_streams_title">Покажи следните потоци</string>
<string name="no_appropriate_file_manager_message">Не е намерен подходящ файлов мениджър за това действие. <string name="no_appropriate_file_manager_message">Не е намерен подходящ файлов мениджър за това действие.
\nМоля, инсталирайте файлов мениджър или опитайте да деактивирате „%s“ в настройките за изтегляне</string> \nМоля, инсталирайте файлов мениджър или опитайте да деактивирате „%s“ в настройките за изтегляне</string>
<string name="no_appropriate_file_manager_message_android_10">Не е намерен подходящ файлов мениджър за това действие. <string name="no_appropriate_file_manager_message_android_10">Не е намерен подходящ файлов мениджър за това действие.
@ -729,7 +714,6 @@
<string name="replay">Повторение</string> <string name="replay">Повторение</string>
<string name="rewind">Превъртане назад</string> <string name="rewind">Превъртане назад</string>
<string name="forward">Напред</string> <string name="forward">Напред</string>
<string name="share_playlist_with_titles_message">Споделете плейлист с подробности, като име на плейлист и заглавия на видеоклипове или като обикновен списък с URL адреси на видеоклипове</string>
<string name="share_playlist_with_list">Споделяне на списък с URL</string> <string name="share_playlist_with_list">Споделяне на списък с URL</string>
<string name="delete_playback_states_alert">Изтрии всички позиции на възпроизвеждане?</string> <string name="delete_playback_states_alert">Изтрии всички позиции на възпроизвеждане?</string>
<string name="watch_history_states_deleted">Позициите за възпроизвеждане са изтрити</string> <string name="watch_history_states_deleted">Позициите за възпроизвеждане са изтрити</string>
@ -757,7 +741,7 @@
<string name="show_crash_the_player_title">Покажи \"Сриване на плейъра\"</string> <string name="show_crash_the_player_title">Покажи \"Сриване на плейъра\"</string>
<string name="show_crash_the_player_summary">Показва опция за срив при използване на плейъра</string> <string name="show_crash_the_player_summary">Показва опция за срив при използване на плейъра</string>
<string name="check_new_streams">Стартирайте проверка за нови потоци</string> <string name="check_new_streams">Стартирайте проверка за нови потоци</string>
<string name="show_error_snackbar">Показване на бърза лента за грешка</string> <string name="show_error_snackbar">Покажи бърза лента за грешка</string>
<string name="local">Местен</string> <string name="local">Местен</string>
<string name="max_retry_msg">Максимален брой повторни опити</string> <string name="max_retry_msg">Максимален брой повторни опити</string>
<string name="enable_queue_limit">Ограничете опашката за изтегляне</string> <string name="enable_queue_limit">Ограничете опашката за изтегляне</string>
@ -765,8 +749,8 @@
<string name="unset_playlist_thumbnail">Премахване на постоянното миниизображение</string> <string name="unset_playlist_thumbnail">Премахване на постоянното миниизображение</string>
<string name="enable_streams_notifications_title">Известия за нови потоци</string> <string name="enable_streams_notifications_title">Известия за нови потоци</string>
<string name="any_network">Всяка мрежа</string> <string name="any_network">Всяка мрежа</string>
<string name="updates_setting_description">Показване на известие за актуализация на приложението, когато е налична нова версия</string> <string name="updates_setting_description">Покажи известие за актуализация на приложението, когато е налична нова версия</string>
<string name="account_terminated">Акаунтът е прекратен</string> <string name="account_terminated">Профилът е прекратен</string>
<string name="detail_pinned_comment_view_description">Фиксиран коментар</string> <string name="detail_pinned_comment_view_description">Фиксиран коментар</string>
<string name="streams_not_yet_supported_removed">Потоци, които все още не се поддържат от програмата за изтегляне, не се показват</string> <string name="streams_not_yet_supported_removed">Потоци, които все още не се поддържат от програмата за изтегляне, не се показват</string>
<string name="soundcloud_go_plus_content">Това е песен на SoundCloud Go+, поне във вашата страна, така че не може да бъде предавана поточно или изтеглена от NewPipe.</string> <string name="soundcloud_go_plus_content">Това е песен на SoundCloud Go+, поне във вашата страна, така че не може да бъде предавана поточно или изтеглена от NewPipe.</string>
@ -793,7 +777,7 @@
<string name="image_quality_summary">Изберете качеството на изображенията и дали изобщо да се зареждат изображения, за да намалите използването на данни и памет. Промените изчистват както кеша на изображенията в паметта, така и на диска — %s</string> <string name="image_quality_summary">Изберете качеството на изображенията и дали изобщо да се зареждат изображения, за да намалите използването на данни и памет. Промените изчистват както кеша на изображенията в паметта, така и на диска — %s</string>
<string name="share_playlist_with_titles">Споделяне със заглавия</string> <string name="share_playlist_with_titles">Споделяне със заглавия</string>
<string name="feed_fetch_channel_tabs">Извличане на раздели на канали</string> <string name="feed_fetch_channel_tabs">Извличане на раздели на канали</string>
<string name="import_settings_vulnerable_format">Настройките в внесения експорт използват уязвим формат, който е отпаднал от NewPipe 0.27.0. Уверете се, че внесеният експорт е от надежден източник, и предпочитайте в бъдеще да използвате само експорти, получени от NewPipe 0.27.0 или по-нова версия. Поддръжката за импортиране на настройки в този уязвим формат скоро ще бъде напълно премахната и тогава старите версии на NewPipe вече няма да могат да внасят настройки на експорти от нови версии.</string> <string name="import_settings_vulnerable_format">Настройките в изнасяния файл, който се внася, използват уязвим формат, който е отпаднал от NewPipe 0.27.0. Уверете се, че внесеният износ е от надежден източник, и предпочитайте в бъдеще да използвате само изнасяния, получени от NewPipe 0.27.0 или по-нова версия. Поддръжката за внасяне на настройки в този уязвим формат скоро ще бъде напълно премахната и тогава старите версии на NewPipe вече няма да могат да внасят настройки на изнесени от нови версии.</string>
<string name="hash_channel_name">Видео хеш известие</string> <string name="hash_channel_name">Видео хеш известие</string>
<string name="clear_cookie_summary">Изчистете бисквитките, които NewPipe съхранява, когато разрешите reCAPTCHA</string> <string name="clear_cookie_summary">Изчистете бисквитките, които NewPipe съхранява, когато разрешите reCAPTCHA</string>
<string name="streams_notifications_network_title">Необходима мрежова връзка</string> <string name="streams_notifications_network_title">Необходима мрежова връзка</string>
@ -810,7 +794,7 @@
<string name="error_report_notification_title">NewPipe откри грешка, докоснете, за да докладвате</string> <string name="error_report_notification_title">NewPipe откри грешка, докоснете, за да докладвате</string>
<string name="no_streams">Няма потоци</string> <string name="no_streams">Няма потоци</string>
<string name="disable_media_tunneling_title">Деактивиране на медийното тунелиране</string> <string name="disable_media_tunneling_title">Деактивиране на медийното тунелиране</string>
<string name="show_image_indicators_title">Показване на индикатори за изображения</string> <string name="show_image_indicators_title">Покажи индикатори за изображения</string>
<string name="missions_header_pending">В очакване</string> <string name="missions_header_pending">В очакване</string>
<string name="error_postprocessing_failed">Неуспешна последваща обработка</string> <string name="error_postprocessing_failed">Неуспешна последваща обработка</string>
<string name="pause_downloads_on_mobile">Прекъсване на мрежи с измерване</string> <string name="pause_downloads_on_mobile">Прекъсване на мрежи с измерване</string>
@ -824,5 +808,6 @@
<string name="metadata_subchannel_avatars">Аватари за подканали</string> <string name="metadata_subchannel_avatars">Аватари за подканали</string>
<string name="metadata_banners">Банери</string> <string name="metadata_banners">Банери</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">Винаги използвайте заобикаляне на настройката на повърхността на видеоизхода на ExoPlayer</string> <string name="always_use_exoplayer_set_output_surface_workaround_title">Винаги използвайте заобикаляне на настройката на повърхността на видеоизхода на ExoPlayer</string>
<string name="clear_playback_states_title">Изтриване на позиции за възпроизвеждане</string> <string name="clear_playback_states_title">Изтрий позиции за възпроизвеждане</string>
</resources> <string name="audio_track_type_secondary">вторичен</string>
</resources>

View File

@ -450,4 +450,4 @@
<string name="metadata_tags">ট্যাগসমূহ</string> <string name="metadata_tags">ট্যাগসমূহ</string>
<string name="notification_colorize_summary">অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়)</string> <string name="notification_colorize_summary">অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়)</string>
<string name="unknown_format">অজানা ধরন</string> <string name="unknown_format">অজানা ধরন</string>
</resources> </resources>

View File

@ -307,4 +307,4 @@
<string name="notification_scale_to_square_image_summary">বিজ্ঞপ্তিতে প্রদর্শিত ভিডিও থাম্বনেল 16:9 থেকে 1:1 অনুপাতের করুন (বিকৃতি দেখা যেতে পারে)</string> <string name="notification_scale_to_square_image_summary">বিজ্ঞপ্তিতে প্রদর্শিত ভিডিও থাম্বনেল 16:9 থেকে 1:1 অনুপাতের করুন (বিকৃতি দেখা যেতে পারে)</string>
<string name="notification_action_shuffle">অদলবদল</string> <string name="notification_action_shuffle">অদলবদল</string>
<string name="notification_action_nothing">কিছু না</string> <string name="notification_action_nothing">কিছু না</string>
</resources> </resources>

View File

@ -633,4 +633,4 @@
<string name="main_page_content_swipe_remove">ভুক্তি মুছতে ডানে-বামে সরাও</string> <string name="main_page_content_swipe_remove">ভুক্তি মুছতে ডানে-বামে সরাও</string>
<string name="loading_stream_details">সম্প্রচার বিষয়ক তথ্য প্রক্রিয়ারত…</string> <string name="loading_stream_details">সম্প্রচার বিষয়ক তথ্য প্রক্রিয়ারত…</string>
<string name="progressive_load_interval_title">প্লেব্যাক লোড বিরতির আকার</string> <string name="progressive_load_interval_title">প্লেব্যাক লোড বিরতির আকার</string>
</resources> </resources>

View File

@ -728,4 +728,11 @@
<string name="audio_track">Pista d\'àudio</string> <string name="audio_track">Pista d\'àudio</string>
<string name="no">No</string> <string name="no">No</string>
<string name="no_streams">Cap emissió</string> <string name="no_streams">Cap emissió</string>
</resources> <string name="enable_streams_notifications_summary">Notifica sobre les noves retransmissions de les subscripcions</string>
<string name="enable_streams_notifications_title">Noves notificacions de retransmissions</string>
<string name="duplicate_in_playlist">Les llistes de reproducció que estan en gris ja contenen aquest element.</string>
<string name="unset_playlist_thumbnail">Desestableix la miniatura permanent</string>
<string name="playlist_add_stream_success_duplicate">Duplicat afegit/s %d vegada/es</string>
<string name="disable_media_tunneling_automatic_info">El túnel multimèdia s\'ha desactivat de manera predeterminada al dispositiu perquè se sap que el vostre model de dispositiu no ho permet.</string>
<string name="semitone">Semiton</string>
</resources>

View File

@ -702,4 +702,4 @@
<string name="ignore_hardware_media_buttons_summary">بەسوودە، بۆ نموونە، ئەگەر هێدسێتێک بەکاربهێنیت لەگەڵ دوگمەی فیزیکی شکاو</string> <string name="ignore_hardware_media_buttons_summary">بەسوودە، بۆ نموونە، ئەگەر هێدسێتێک بەکاربهێنیت لەگەڵ دوگمەی فیزیکی شکاو</string>
<string name="progressive_load_interval_title">قەبارەی نێوان بارکردنی پەخشکردن</string> <string name="progressive_load_interval_title">قەبارەی نێوان بارکردنی پەخشکردن</string>
<string name="ignore_hardware_media_buttons_title">دوگمەی ڕووداوەکانی میدیای هاردوێر بەجێبهێڵە</string> <string name="ignore_hardware_media_buttons_title">دوگمەی ڕووداوەکانی میدیای هاردوێر بەجێبهێڵە</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="upload_date_text">Publikováno na %1$s</string> <string name="upload_date_text">Publikováno %1$s</string>
<string name="no_player_found">Nenalezen žádný přehrávač. Nainstalovat VLC?</string> <string name="no_player_found">Nenalezen žádný přehrávač. Nainstalovat VLC?</string>
<string name="install">Instalovat</string> <string name="install">Instalovat</string>
<string name="cancel">Zrušit</string> <string name="cancel">Zrušit</string>
@ -785,7 +785,7 @@
<string name="question_mark">\?</string> <string name="question_mark">\?</string>
<string name="metadata_subscribers">Odběratelé</string> <string name="metadata_subscribers">Odběratelé</string>
<string name="show_channel_tabs_summary">Které karty mají být zobrazeny na stránkách kanálů</string> <string name="show_channel_tabs_summary">Které karty mají být zobrazeny na stránkách kanálů</string>
<string name="share_playlist_with_list">Sdílet URL seznamu</string> <string name="share_playlist_with_list">Sdílet seznam adres</string>
<string name="share_playlist_with_titles">Sdílet s názvy</string> <string name="share_playlist_with_titles">Sdílet s názvy</string>
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
@ -806,7 +806,6 @@
<string name="channel_tab_albums">Alba</string> <string name="channel_tab_albums">Alba</string>
<string name="rewind">Přetočení zpět</string> <string name="rewind">Přetočení zpět</string>
<string name="replay">Znovu přehrát</string> <string name="replay">Znovu přehrát</string>
<string name="share_playlist_with_titles_message">Sdílejte playlist s podrobnostmi jako je jeho název a názvy videí, nebo jako jednoduchý seznam adres videí</string>
<string name="image_quality_medium">Střední kvalita</string> <string name="image_quality_medium">Střední kvalita</string>
<string name="metadata_banners">Bannery</string> <string name="metadata_banners">Bannery</string>
<string name="channel_tab_playlists">Playlisty</string> <string name="channel_tab_playlists">Playlisty</string>
@ -839,4 +838,5 @@
<string name="auto_update_check_description">NewPipe může čas od času automaticky kontrolovat nové verze a upozornit vás na jejich dostupnost. <string name="auto_update_check_description">NewPipe může čas od času automaticky kontrolovat nové verze a upozornit vás na jejich dostupnost.
\nChcete tuto funkci povolit?</string> \nChcete tuto funkci povolit?</string>
<string name="import_settings_vulnerable_format">Nastavení v importovaném exportu používají zranitelný formát. NewPipe používá nový formát od verze 0.27.0. Ujistěte se, že export importujete z důvěryhodného zdroje a v budoucnu upřednostňujte používání exportů získaných z NewPipe 0.27.0 nebo novějších. Podpora importu nastavení v tomto zranitelném formátu bude brzy kompletně odstraněna, kvůli čemuž staré verze NewPipe nebudou moci importovat nastavení z exportů z nových verzí.</string> <string name="import_settings_vulnerable_format">Nastavení v importovaném exportu používají zranitelný formát. NewPipe používá nový formát od verze 0.27.0. Ujistěte se, že export importujete z důvěryhodného zdroje a v budoucnu upřednostňujte používání exportů získaných z NewPipe 0.27.0 nebo novějších. Podpora importu nastavení v tomto zranitelném formátu bude brzy kompletně odstraněna, kvůli čemuž staré verze NewPipe nebudou moci importovat nastavení z exportů z nových verzí.</string>
</resources> <string name="audio_track_type_secondary">sekundární</string>
</resources>

View File

@ -772,7 +772,6 @@
<string name="image_quality_none">Indlæs ikke billeder</string> <string name="image_quality_none">Indlæs ikke billeder</string>
<string name="image_quality_low">Lav kvalitet</string> <string name="image_quality_low">Lav kvalitet</string>
<string name="share_playlist">Del Playliste</string> <string name="share_playlist">Del Playliste</string>
<string name="share_playlist_with_titles_message">Del playliste med detajler såsom playlistenavn og videotitler eller som en simpel liste over video-URL\'er</string>
<string name="share_playlist_with_titles">Del med Titler</string> <string name="share_playlist_with_titles">Del med Titler</string>
<string name="share_playlist_with_list">Del URL-liste</string> <string name="share_playlist_with_list">Del URL-liste</string>
<plurals name="replies"> <plurals name="replies">
@ -825,4 +824,5 @@
\nEr du sikker på, at du vil fortsætte?</string> \nEr du sikker på, at du vil fortsætte?</string>
<string name="import_settings_vulnerable_format">Indstillingerne i den eksport, der importeres, bruger et sårbart format, der er blevet forældet siden NewPipe 0.27.0. Sørg for, at den eksport, der importeres, er fra en pålidelig kilde, og brug helst kun eksport fra NewPipe 0.27.0 eller nyere i fremtiden. Understøttelse af import af indstillinger i dette sårbare format fjernes snart helt, og så vil gamle versioner af NewPipe ikke længere være i stand til at importere indstillinger fra eksport fra nye versioner.</string> <string name="import_settings_vulnerable_format">Indstillingerne i den eksport, der importeres, bruger et sårbart format, der er blevet forældet siden NewPipe 0.27.0. Sørg for, at den eksport, der importeres, er fra en pålidelig kilde, og brug helst kun eksport fra NewPipe 0.27.0 eller nyere i fremtiden. Understøttelse af import af indstillinger i dette sårbare format fjernes snart helt, og så vil gamle versioner af NewPipe ikke længere være i stand til at importere indstillinger fra eksport fra nye versioner.</string>
<string name="settings_category_backup_restore_title">Sikkerhedskopiering og gendannelse</string> <string name="settings_category_backup_restore_title">Sikkerhedskopiering og gendannelse</string>
</resources> <string name="audio_track_type_secondary">sekundær</string>
</resources>

View File

@ -63,7 +63,7 @@
<string name="error_snackbar_message">Entschuldigung, etwas ist schiefgelaufen.</string> <string name="error_snackbar_message">Entschuldigung, etwas ist schiefgelaufen.</string>
<string name="your_comment">Dein Kommentar (auf englisch):</string> <string name="your_comment">Dein Kommentar (auf englisch):</string>
<string name="duration_live">Live</string> <string name="duration_live">Live</string>
<string name="main_bg_subtitle">Tippe auf die Lupe, um zu beginnen.</string> <string name="main_bg_subtitle">Tippe auf die Lupe, um zu suchen.</string>
<string name="downloads">Downloads</string> <string name="downloads">Downloads</string>
<string name="downloads_title">Downloads</string> <string name="downloads_title">Downloads</string>
<string name="error_report_title">Fehlerbericht</string> <string name="error_report_title">Fehlerbericht</string>
@ -135,7 +135,7 @@
<string name="action_history">Verlauf</string> <string name="action_history">Verlauf</string>
<string name="subscription_change_failed">Abonnement konnte nicht geändert werden</string> <string name="subscription_change_failed">Abonnement konnte nicht geändert werden</string>
<string name="subscription_update_failed">Abonnement konnte nicht aktualisiert werden</string> <string name="subscription_update_failed">Abonnement konnte nicht aktualisiert werden</string>
<string name="resume_on_audio_focus_gain_summary">Nach Unterbrechungen (z. B. Telefonaten) Wiedergabe fortsetzen</string> <string name="resume_on_audio_focus_gain_summary">Nach Unterbrechungen (z. B. Telefonaten) Wiedergabe fortsetzen</string>
<string name="notification_channel_name">NewPipe-Benachrichtigung</string> <string name="notification_channel_name">NewPipe-Benachrichtigung</string>
<string name="notification_channel_description">Benachrichtigungen für den NewPipe-Player</string> <string name="notification_channel_description">Benachrichtigungen für den NewPipe-Player</string>
<string name="settings_category_player_behavior_title">Verhalten</string> <string name="settings_category_player_behavior_title">Verhalten</string>
@ -319,7 +319,7 @@
\nDu musst den Datenschutzrichtlinien zustimmen, um den Fehlerbericht an uns zu senden.</string> \nDu musst den Datenschutzrichtlinien zustimmen, um den Fehlerbericht an uns zu senden.</string>
<string name="limit_data_usage_none_description">Unbegrenzt</string> <string name="limit_data_usage_none_description">Unbegrenzt</string>
<string name="limit_mobile_data_usage_title">Auflösung bei Verwendung mobiler Daten begrenzen</string> <string name="limit_mobile_data_usage_title">Auflösung bei Verwendung mobiler Daten begrenzen</string>
<string name="minimize_on_exit_title">Beim App-wechsel minimieren</string> <string name="minimize_on_exit_title">Beim App-Wechsel minimieren</string>
<string name="minimize_on_exit_summary">Aktion beim Umschalten auf eine andere App vom Haupt-Videoplayer — %s</string> <string name="minimize_on_exit_summary">Aktion beim Umschalten auf eine andere App vom Haupt-Videoplayer — %s</string>
<string name="minimize_on_exit_none_description">Keine</string> <string name="minimize_on_exit_none_description">Keine</string>
<string name="minimize_on_exit_background_description">Für die Wiedergabe im Hintergrund minimieren</string> <string name="minimize_on_exit_background_description">Für die Wiedergabe im Hintergrund minimieren</string>
@ -409,7 +409,7 @@
<string name="error_progress_lost">Vorgang abgebrochen, da die Datei gelöscht wurde</string> <string name="error_progress_lost">Vorgang abgebrochen, da die Datei gelöscht wurde</string>
<string name="confirm_prompt">Möchtest du deinen Downloadverlauf oder alle heruntergeladenen Dateien löschen\?</string> <string name="confirm_prompt">Möchtest du deinen Downloadverlauf oder alle heruntergeladenen Dateien löschen\?</string>
<string name="enable_queue_limit">Downloadwarteschlange begrenzen</string> <string name="enable_queue_limit">Downloadwarteschlange begrenzen</string>
<string name="enable_queue_limit_desc">Ein Download wird zur gleichen Zeit ausgeführt</string> <string name="enable_queue_limit_desc">Ein Download wird gleichzeitig ausgeführt</string>
<string name="start_downloads">Downloads starten</string> <string name="start_downloads">Downloads starten</string>
<string name="pause_downloads">Downloads anhalten</string> <string name="pause_downloads">Downloads anhalten</string>
<string name="downloads_storage_ask_title">Downloadziel abfragen</string> <string name="downloads_storage_ask_title">Downloadziel abfragen</string>
@ -425,7 +425,7 @@
<string name="no_one_watching">Niemand schaut zu</string> <string name="no_one_watching">Niemand schaut zu</string>
<plurals name="watching"> <plurals name="watching">
<item quantity="one">%s Zuschauer</item> <item quantity="one">%s Zuschauer</item>
<item quantity="other">%s Zuschauende</item> <item quantity="other">%s Zuschauer</item>
</plurals> </plurals>
<string name="no_one_listening">Niemand hört zu</string> <string name="no_one_listening">Niemand hört zu</string>
<plurals name="listening"> <plurals name="listening">
@ -497,7 +497,7 @@
<string name="feed_oldest_subscription_update">Feed zuletzt aktualisiert: %s</string> <string name="feed_oldest_subscription_update">Feed zuletzt aktualisiert: %s</string>
<string name="feed_update_threshold_title">Grenzwert für Feed-Aktualisierung</string> <string name="feed_update_threshold_title">Grenzwert für Feed-Aktualisierung</string>
<string name="feed_use_dedicated_fetch_method_title">Aus fest zugeordnetem Feed abrufen wenn verfügbar</string> <string name="feed_use_dedicated_fetch_method_title">Aus fest zugeordnetem Feed abrufen wenn verfügbar</string>
<string name="feed_use_dedicated_fetch_method_summary">Steht in manchen Diensten zur Verfügung, ist meist viel schneller, liefert aber eventuell eine eingeschränkte Anzahl an Elementen und oft unvollständige Informationen (z. B. keine Videolänge, keinen Elementtyp, keinen Live-Status)</string> <string name="feed_use_dedicated_fetch_method_summary">Steht in manchen Diensten zur Verfügung, ist meist viel schneller, liefert aber eventuell eine eingeschränkte Anzahl an Elementen und oft unvollständige Informationen (z. B. keine Videolänge, keinen Elementtyp, keinen Live-Status)</string>
<string name="feed_use_dedicated_fetch_method_help_text">Glaubst du, dass das Laden von Feeds zu langsam ist? Wenn ja, versuche den Schnelllademodus einzuschalten (du kannst ihn in den Einstellungen oder über die Schaltfläche unten ändern). <string name="feed_use_dedicated_fetch_method_help_text">Glaubst du, dass das Laden von Feeds zu langsam ist? Wenn ja, versuche den Schnelllademodus einzuschalten (du kannst ihn in den Einstellungen oder über die Schaltfläche unten ändern).
\n \n
\nNewPipe bietet zwei Feed-Ladestrategien: \nNewPipe bietet zwei Feed-Ladestrategien:
@ -527,7 +527,7 @@
<string name="remove_watched_popup_title">Gesehene Videos entfernen\?</string> <string name="remove_watched_popup_title">Gesehene Videos entfernen\?</string>
<string name="show_original_time_ago_title">Originalzeit vor Elementen anzeigen</string> <string name="show_original_time_ago_title">Originalzeit vor Elementen anzeigen</string>
<string name="show_original_time_ago_summary">Originaltexte von Diensten werden in Stream-Elementen sichtbar sein</string> <string name="show_original_time_ago_summary">Originaltexte von Diensten werden in Stream-Elementen sichtbar sein</string>
<string name="youtube_restricted_mode_enabled_title">Aktivieren des „Eingeschränkten Modus“ von YouTube</string> <string name="youtube_restricted_mode_enabled_title">YouTubes „Eingeschränkten Modus“ aktivieren</string>
<string name="detail_sub_channel_thumbnail_view_description">Profilbild des Kanals</string> <string name="detail_sub_channel_thumbnail_view_description">Profilbild des Kanals</string>
<string name="channel_created_by">Erstellt von %s</string> <string name="channel_created_by">Erstellt von %s</string>
<string name="video_detail_by">Von %s</string> <string name="video_detail_by">Von %s</string>
@ -659,7 +659,7 @@
<string name="checking_updates_toast">Suche nach Aktualisierungen </string> <string name="checking_updates_toast">Suche nach Aktualisierungen </string>
<string name="manual_update_description">Manuelle Prüfung auf neue Versionen</string> <string name="manual_update_description">Manuelle Prüfung auf neue Versionen</string>
<string name="feed_new_items">Neue Feed-Elemente</string> <string name="feed_new_items">Neue Feed-Elemente</string>
<string name="show_crash_the_player_title">\"Player abstürzen lassen\" anzeigen</string> <string name="show_crash_the_player_title">„Player abstürzen lassen“ anzeigen</string>
<string name="crash_the_player">Player abstürzen lassen</string> <string name="crash_the_player">Player abstürzen lassen</string>
<string name="show_crash_the_player_summary">Zeigt eine Absturzoption an, wenn der Player verwendet wird</string> <string name="show_crash_the_player_summary">Zeigt eine Absturzoption an, wenn der Player verwendet wird</string>
<string name="error_report_channel_name">Fehlerbericht-Benachrichtigung</string> <string name="error_report_channel_name">Fehlerbericht-Benachrichtigung</string>
@ -719,7 +719,7 @@
<string name="card">Karte</string> <string name="card">Karte</string>
<string name="playlist_add_stream_success_duplicate">Duplikat %d mal hinzugefügt</string> <string name="playlist_add_stream_success_duplicate">Duplikat %d mal hinzugefügt</string>
<string name="duplicate_in_playlist">Die ausgegrauten Wiedergabelisten enthalten dieses Element bereits.</string> <string name="duplicate_in_playlist">Die ausgegrauten Wiedergabelisten enthalten dieses Element bereits.</string>
<string name="ignore_hardware_media_buttons_summary">Nützlich, wenn z. B. ein Headset mit defekten physischen Tasten verwendet wird</string> <string name="ignore_hardware_media_buttons_summary">Nützlich, wenn z. B. ein Headset mit defekten physischen Tasten verwendet wird</string>
<string name="ignore_hardware_media_buttons_title">Ereignisse der Hardware-Medientasten ignorieren</string> <string name="ignore_hardware_media_buttons_title">Ereignisse der Hardware-Medientasten ignorieren</string>
<string name="remove_duplicates">Duplikate entfernen</string> <string name="remove_duplicates">Duplikate entfernen</string>
<string name="remove_duplicates_title">Duplikate entfernen\?</string> <string name="remove_duplicates_title">Duplikate entfernen\?</string>
@ -804,7 +804,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Wiedergabeliste teilen</string> <string name="share_playlist">Wiedergabeliste teilen</string>
<string name="share_playlist_with_titles_message">Teile die Wiedergabeliste mit Details wie dem Namen der Wiedergabeliste und den Videotiteln oder als einfache Liste von Video-URLs</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="one">%s Antwort</item> <item quantity="one">%s Antwort</item>
@ -825,4 +824,5 @@
\n \n
\nMöchtest du wirklich fortfahren?</string> \nMöchtest du wirklich fortfahren?</string>
<string name="import_settings_vulnerable_format">Die Einstellungen in dem zu importierenden Export verwenden ein angreifbares Format, das seit NewPipe 0.27.0 veraltet ist. Stellen Sie sicher, dass der zu importierende Export aus einer vertrauenswürdigen Quelle stammt, und verwenden Sie in Zukunft nur noch Exporte, die aus NewPipe 0.27.0 oder neuer stammen. Die Unterstützung für den Import von Einstellungen in diesem angreifbaren Format wird bald vollständig entfernt werden, und dann werden alte Versionen von NewPipe nicht mehr in der Lage sein, Einstellungen von Exporten aus neuen Versionen zu importieren.</string> <string name="import_settings_vulnerable_format">Die Einstellungen in dem zu importierenden Export verwenden ein angreifbares Format, das seit NewPipe 0.27.0 veraltet ist. Stellen Sie sicher, dass der zu importierende Export aus einer vertrauenswürdigen Quelle stammt, und verwenden Sie in Zukunft nur noch Exporte, die aus NewPipe 0.27.0 oder neuer stammen. Die Unterstützung für den Import von Einstellungen in diesem angreifbaren Format wird bald vollständig entfernt werden, und dann werden alte Versionen von NewPipe nicht mehr in der Lage sein, Einstellungen von Exporten aus neuen Versionen zu importieren.</string>
</resources> <string name="audio_track_type_secondary">Sekundär</string>
</resources>

View File

@ -9,7 +9,7 @@
<string name="download">Λήψη</string> <string name="download">Λήψη</string>
<string name="search">Αναζήτηση</string> <string name="search">Αναζήτηση</string>
<string name="settings">Ρυθμίσεις</string> <string name="settings">Ρυθμίσεις</string>
<string name="did_you_mean">Μήπως εννοείτε «%1$s»;</string> <string name="did_you_mean">Μήπως εννοείτε \"%1$s\";</string>
<string name="share_dialog_title">Κοινοποίηση με</string> <string name="share_dialog_title">Κοινοποίηση με</string>
<string name="use_external_video_player_title">Χρήση εξωτερικής εφαρμογής αναπαραγωγής βίντεο</string> <string name="use_external_video_player_title">Χρήση εξωτερικής εφαρμογής αναπαραγωγής βίντεο</string>
<string name="use_external_audio_player_title">Χρήση εξωτερικής συσκευής αναπαραγωγής ήχου</string> <string name="use_external_audio_player_title">Χρήση εξωτερικής συσκευής αναπαραγωγής ήχου</string>
@ -103,7 +103,7 @@
<string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string> <string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string>
<string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string> <string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string>
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string> <string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του «Πιέστε παρατεταμένα για προσθήκη στην ουρά»</string>
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο</string> <string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στις \"Λεπτομέρειες:\\ στο βίντεο</string>
<string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string> <string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string>
<string name="settings_category_player_title">Αναπαραγωγός</string> <string name="settings_category_player_title">Αναπαραγωγός</string>
<string name="settings_category_player_behavior_title">Συμπεριφορά</string> <string name="settings_category_player_behavior_title">Συμπεριφορά</string>
@ -472,7 +472,7 @@
<string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας. <string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας.
\n \n
\nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string> \nΕνεργοποιήστε το «%1$s» στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
<string name="youtube_restricted_mode_enabled_title">Λειτουργία περιορισμένης πρόσβασης του YouTube</string> <string name="youtube_restricted_mode_enabled_title">Ενεργοποίηση \"Περιορισμένη Λειτουργία\\ του YouTube</string>
<string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string> <string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string>
<string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string> <string name="auto_queue_toggle">Αυτόματη προσθήκη στην ουρά</string>
<string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string> <string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string>
@ -656,7 +656,7 @@
<string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string> <string name="manual_update_description">Χειροκίνητος έλεγχος για νέα έκδοση</string>
<string name="check_for_updates">Έλεγχος αναβάθμισης</string> <string name="check_for_updates">Έλεγχος αναβάθμισης</string>
<string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string> <string name="feed_new_items">Νέα αντικείμενα τροφοδοσίας</string>
<string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευσης αναπαραγωγέα»</string> <string name="show_crash_the_player_title">Εμφάνιση «Κατάρρευση αναπαραγωγέα\\</string>
<string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string> <string name="show_crash_the_player_summary">Εμφανίζει μια επιλογή κατάρρευσης κατά τη χρήση του αναπαραγωγέα</string>
<string name="crash_the_player">Κατάρρευση αναπαραγωγέα</string> <string name="crash_the_player">Κατάρρευση αναπαραγωγέα</string>
<string name="error_report_channel_name">Ειδοποίηση αναφοράς σφάλματος</string> <string name="error_report_channel_name">Ειδοποίηση αναφοράς σφάλματος</string>
@ -804,7 +804,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Κοινοποίηση λίστας</string> <string name="share_playlist">Κοινοποίηση λίστας</string>
<string name="share_playlist_with_titles_message">Μοιραστείτε τη λίστα αναπαραγωγής με λεπτομέρειες όπως το όνομα της λίστας αναπαραγωγής και τους τίτλους βίντεο ή ως μια απλή λίστα διευθύνσεων URL βίντεο</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="one">%s απάντηση</item> <item quantity="one">%s απάντηση</item>
@ -825,4 +824,5 @@
\n \n
\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;</string> \nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;</string>
<string name="import_settings_vulnerable_format">Οι ρυθμίσεις στην εξαγωγή που εισάγεται χρησιμοποιούν μια ευάλωτη μορφή που είχε καταργηθεί από το NewPipe 0.27.0. Βεβαιωθείτε ότι η εξαγωγή που εισάγεται προέρχεται από αξιόπιστη πηγή και προτιμήστε να χρησιμοποιείτε μόνο εξαγωγές που λαμβάνονται από το NewPipe 0.27.0 ή νεότερο στο μέλλον. Η υποστήριξη για εισαγωγή ρυθμίσεων σε αυτήν την ευάλωτη μορφή θα καταργηθεί σύντομα εντελώς και, στη συνέχεια, οι παλιές εκδόσεις του NewPipe δεν θα μπορούν πλέον να εισάγουν ρυθμίσεις εξαγωγών από νέες εκδόσεις.</string> <string name="import_settings_vulnerable_format">Οι ρυθμίσεις στην εξαγωγή που εισάγεται χρησιμοποιούν μια ευάλωτη μορφή που είχε καταργηθεί από το NewPipe 0.27.0. Βεβαιωθείτε ότι η εξαγωγή που εισάγεται προέρχεται από αξιόπιστη πηγή και προτιμήστε να χρησιμοποιείτε μόνο εξαγωγές που λαμβάνονται από το NewPipe 0.27.0 ή νεότερο στο μέλλον. Η υποστήριξη για εισαγωγή ρυθμίσεων σε αυτήν την ευάλωτη μορφή θα καταργηθεί σύντομα εντελώς και, στη συνέχεια, οι παλιές εκδόσεις του NewPipe δεν θα μπορούν πλέον να εισάγουν ρυθμίσεις εξαγωγών από νέες εκδόσεις.</string>
</resources> <string name="audio_track_type_secondary">δευτερεύων</string>
</resources>

View File

@ -84,4 +84,4 @@
<string name="ok">Okay</string> <string name="ok">Okay</string>
<string name="open_in_browser">Open in browser</string> <string name="open_in_browser">Open in browser</string>
<string name="no_player_found_toast">No stream player found (you can install VLC to play it).</string> <string name="no_player_found_toast">No stream player found (you can install VLC to play it).</string>
</resources> </resources>

View File

@ -12,11 +12,11 @@
<string name="share_dialog_title">Konigi kun</string> <string name="share_dialog_title">Konigi kun</string>
<string name="use_external_video_player_title">Uzi eksteran filmetoludilon</string> <string name="use_external_video_player_title">Uzi eksteran filmetoludilon</string>
<string name="use_external_audio_player_title">Uzi eksteran sonludilon</string> <string name="use_external_audio_player_title">Uzi eksteran sonludilon</string>
<string name="default_resolution_title">Defaŭlta rezolucio</string> <string name="default_resolution_title">Preferata distingumo</string>
<string name="play_with_kodi_title">Ludi per Kodi</string> <string name="play_with_kodi_title">Ludi per Kodi</string>
<string name="show_play_with_kodi_title">Montri \"Ludi per Kodi\"-opcion</string> <string name="show_play_with_kodi_title">Montri \"Ludi per Kodi\"-opcion</string>
<string name="play_audio">Sono</string> <string name="play_audio">Sono</string>
<string name="default_audio_format_title">Defaŭlta sondosierformo</string> <string name="default_audio_format_title">Preferata sona dosierformo</string>
<string name="theme_title">Etoso</string> <string name="theme_title">Etoso</string>
<string name="dark_theme_title">Malhela</string> <string name="dark_theme_title">Malhela</string>
<string name="light_theme_title">Hela</string> <string name="light_theme_title">Hela</string>
@ -24,7 +24,7 @@
<string name="download_dialog_title">Elŝuti</string> <string name="download_dialog_title">Elŝuti</string>
<string name="unsupported_url">Ligilo ne subtenita</string> <string name="unsupported_url">Ligilo ne subtenita</string>
<string name="content_language_title">Preferata enhavlingvo</string> <string name="content_language_title">Preferata enhavlingvo</string>
<string name="settings_category_video_audio_title">Filmeto kaj sono</string> <string name="settings_category_video_audio_title">Filmo kaj sono</string>
<string name="settings_category_appearance_title">Apero</string> <string name="settings_category_appearance_title">Apero</string>
<string name="background_player_playing_toast">Ludanta fone</string> <string name="background_player_playing_toast">Ludanta fone</string>
<string name="general_error">Eraro</string> <string name="general_error">Eraro</string>
@ -32,17 +32,17 @@
<string name="content_not_available">Enhavo malhavebla</string> <string name="content_not_available">Enhavo malhavebla</string>
<string name="detail_likes_img_view_description">Ŝatoj</string> <string name="detail_likes_img_view_description">Ŝatoj</string>
<string name="detail_dislikes_img_view_description">Malŝatoj</string> <string name="detail_dislikes_img_view_description">Malŝatoj</string>
<string name="no_player_found">Neniu elsendlflua ludilo trovita. Ĉu instali la aplikaĵon VLC\?</string> <string name="no_player_found">Neniu ludilo de elsendoj trovita. Ĉu instalu la programon VLC?</string>
<string name="kore_not_found">Ĉu instali la mankan aplikaĵon Kore\?</string> <string name="kore_not_found">Ĉu instalu la mankatan programon Kore?</string>
<string name="show_next_and_similar_title">Montri \'Sekvajn\' kaj \'Similajn\' videojn</string> <string name="show_next_and_similar_title">Montri Sekvajn kaj Similajn filmetojn</string>
<string name="could_not_load_thumbnails">Ĉiuj bildetoj ne ŝargeblas</string> <string name="could_not_load_thumbnails">Ĉiuj bildetoj ne ŝargeblas</string>
<string name="parsing_error">La retejo ne analizeblas</string> <string name="parsing_error">La retejo ne analizeblas</string>
<string name="detail_thumbnail_view_description">Ludi filmeton, daŭro:</string> <string name="detail_thumbnail_view_description">Ludi filmeton, daŭro:</string>
<string name="detail_uploader_thumbnail_view_description">Bildeto de la alŝutinto</string> <string name="detail_uploader_thumbnail_view_description">Bildeto de la alŝutinto</string>
<string name="download_path_title">Elŝutujo por filmetoj</string> <string name="download_path_title">Elŝutujo por filmetoj</string>
<string name="download_path_audio_title">Elŝutujo por aŭdio</string> <string name="download_path_audio_title">Elŝutujo por aŭdio</string>
<string name="show_play_with_kodi_summary">Montri opcion por ludi filmeton per la aplikaĵo Kodi</string> <string name="show_play_with_kodi_summary">Montri opcion por ludi filmeton per la programo Kodi</string>
<string name="download_path_summary">Elŝutitaj filmetoj estas konservitaj tie</string> <string name="download_path_summary">Elŝutitaj filmetoj konserviĝas ĉi tie</string>
<string name="download_path_audio_summary">Dosierujo por konservi elŝutitajn muzikojn</string> <string name="download_path_audio_summary">Dosierujo por konservi elŝutitajn muzikojn</string>
<string name="download_path_dialog_title">Elektu lokon por konservi elŝutitajn filmetojn</string> <string name="download_path_dialog_title">Elektu lokon por konservi elŝutitajn filmetojn</string>
<string name="download_path_audio_dialog_title">Elektu lokon por konservi elŝutitajn muzikojn</string> <string name="download_path_audio_dialog_title">Elektu lokon por konservi elŝutitajn muzikojn</string>
@ -57,7 +57,7 @@
<string name="main_bg_subtitle">Premi \"Serĉi\" por komenci.</string> <string name="main_bg_subtitle">Premi \"Serĉi\" por komenci.</string>
<string name="no_player_found_toast">Neniu elsendlflua ludilo trovita (instalu VLC por ludi ĝin).</string> <string name="no_player_found_toast">Neniu elsendlflua ludilo trovita (instalu VLC por ludi ĝin).</string>
<string name="open_in_popup_mode">Malfermi en ŝprucfenestran modon</string> <string name="open_in_popup_mode">Malfermi en ŝprucfenestran modon</string>
<string name="use_external_video_player_summary">Forigas aŭdon ĉe kelkaj rezolucioj</string> <string name="use_external_video_player_summary">Forigas aŭdon ĉe kelkaj distingumoj</string>
<string name="subscribe_button_title">Aboni</string> <string name="subscribe_button_title">Aboni</string>
<string name="subscribed_button_title">Abonita</string> <string name="subscribed_button_title">Abonita</string>
<string name="channel_unsubscribed">Kanalo malabonita</string> <string name="channel_unsubscribed">Kanalo malabonita</string>
@ -71,10 +71,10 @@
<string name="controls_background_title">Fono</string> <string name="controls_background_title">Fono</string>
<string name="controls_popup_title">Ŝprucfenestro</string> <string name="controls_popup_title">Ŝprucfenestro</string>
<string name="controls_add_to_playlist_title">Aldonu al</string> <string name="controls_add_to_playlist_title">Aldonu al</string>
<string name="default_popup_resolution_title">Defaŭlta rezolucio de la ŝprucfenestra ludilo</string> <string name="default_popup_resolution_title">Komenca distingumo de la ŝprucfenestra ludilo</string>
<string name="show_higher_resolutions_title">Montri pli altajn rezoluciojn</string> <string name="show_higher_resolutions_title">Montri pli altajn distingumojn</string>
<string name="show_higher_resolutions_summary">Nur kelkaj aparatoj povas ludi 2K / 4K filmetojn</string> <string name="show_higher_resolutions_summary">Nur kelkaj aparatoj povas ludi filmetojn je distingumoj 2K / 4K</string>
<string name="default_video_format_title">Defaŭlta filmetdosierformo</string> <string name="default_video_format_title">Preferata filma dosierformo</string>
<string name="popup_remember_size_pos_title">Memori ecojn de ŝprucfenestro</string> <string name="popup_remember_size_pos_title">Memori ecojn de ŝprucfenestro</string>
<string name="popup_remember_size_pos_summary">Memori lastan grandon kaj pozicion de ŝprucfenestro</string> <string name="popup_remember_size_pos_summary">Memori lastan grandon kaj pozicion de ŝprucfenestro</string>
<string name="use_inexact_seek_title">Uzi rapidan malekzaktan serĉon</string> <string name="use_inexact_seek_title">Uzi rapidan malekzaktan serĉon</string>
@ -100,7 +100,7 @@
<string name="clear">Forviŝi</string> <string name="clear">Forviŝi</string>
<string name="show_search_suggestions_title">Serĉi sugestojn</string> <string name="show_search_suggestions_title">Serĉi sugestojn</string>
<string name="show_search_suggestions_summary">Montri sugestojn kiam serĉanto</string> <string name="show_search_suggestions_summary">Montri sugestojn kiam serĉanto</string>
<string name="best_resolution">Plej bona rezolucio</string> <string name="best_resolution">Plej bona distingumo</string>
<string name="app_description">Libera malpeza torentado ĉe Android.</string> <string name="app_description">Libera malpeza torentado ĉe Android.</string>
<string name="settings_category_downloads_title">Elŝuti</string> <string name="settings_category_downloads_title">Elŝuti</string>
<string name="charset_letters_and_digits">Leteroj kaj ciferoj</string> <string name="charset_letters_and_digits">Leteroj kaj ciferoj</string>
@ -110,7 +110,7 @@
<string name="enable_search_history_title">Serĉa historio</string> <string name="enable_search_history_title">Serĉa historio</string>
<string name="enable_search_history_summary">Konservi la historio de serĉo lokale</string> <string name="enable_search_history_summary">Konservi la historio de serĉo lokale</string>
<string name="enable_watch_history_title">Rigardu historion</string> <string name="enable_watch_history_title">Rigardu historion</string>
<string name="enable_watch_history_summary">Spuri la viditajn filmetojn</string> <string name="enable_watch_history_summary">Protokoli spektitajn filmetojn</string>
<string name="notification_channel_name">Sciigo de NewPipe</string> <string name="notification_channel_name">Sciigo de NewPipe</string>
<string name="notification_channel_description">Sciigoj por ludilo de NewPipe</string> <string name="notification_channel_description">Sciigoj por ludilo de NewPipe</string>
<string name="settings_category_player_title">Ludilo</string> <string name="settings_category_player_title">Ludilo</string>
@ -121,7 +121,7 @@
<string name="top_50">Supro 50</string> <string name="top_50">Supro 50</string>
<string name="new_and_hot">Nova kaj varma</string> <string name="new_and_hot">Nova kaj varma</string>
<string name="show_hold_to_append_title">Montri la indiko « Tenu por aldoni »</string> <string name="show_hold_to_append_title">Montri la indiko « Tenu por aldoni »</string>
<string name="show_hold_to_append_summary">Montri indikon premante la fona aŭ la ŝprucfenestra butono en filmeta \"Detaloj:\"</string> <string name="show_hold_to_append_summary">Montri indikon premante la fonon aŭ la ŝprucfenestran butonon en filmeta «Detaloj:»</string>
<string name="play_all">Ludi ĉiujn</string> <string name="play_all">Ludi ĉiujn</string>
<string name="player_stream_failure">Ne povis ludi tion torenton</string> <string name="player_stream_failure">Ne povis ludi tion torenton</string>
<string name="player_unrecoverable_failure">Neatendebla eraro de ludilo okazis</string> <string name="player_unrecoverable_failure">Neatendebla eraro de ludilo okazis</string>
@ -138,7 +138,7 @@
<string name="switch_to_main">Ŝangi al Ĉefa</string> <string name="switch_to_main">Ŝangi al Ĉefa</string>
<string name="always">Ĉiam</string> <string name="always">Ĉiam</string>
<string name="just_once">Nur unufoje</string> <string name="just_once">Nur unufoje</string>
<string name="video_streams_empty">Neniuj filmeta torentoj trovitaj</string> <string name="video_streams_empty">Neniu filmofluo trovita</string>
<string name="audio_streams_empty">Neniuj sonaj torentoj trovis</string> <string name="audio_streams_empty">Neniuj sonaj torentoj trovis</string>
<string name="popup_player">Ŝprucfenestra ludilo</string> <string name="popup_player">Ŝprucfenestra ludilo</string>
<string name="import_data_title">Importi la datumbazon</string> <string name="import_data_title">Importi la datumbazon</string>
@ -191,7 +191,7 @@
<string name="clear_search_history_summary">Forviŝi la serĉajn ŝlosilvortojn</string> <string name="clear_search_history_summary">Forviŝi la serĉajn ŝlosilvortojn</string>
<string name="delete_search_history_alert">Ĉu vi volas forviŝi la totalon de la historio de serĉo \?</string> <string name="delete_search_history_alert">Ĉu vi volas forviŝi la totalon de la historio de serĉo \?</string>
<string name="search_history_deleted">Historio de serĉo forviŝita</string> <string name="search_history_deleted">Historio de serĉo forviŝita</string>
<string name="limit_mobile_data_usage_title">Limigi rezolucio kiam uzanta moveblan datumon</string> <string name="limit_mobile_data_usage_title">Limigi distingumon uzante telefonan retkonekton</string>
<string name="minimize_on_exit_popup_description">Minimumigi al ŝprucfenestra ludilo</string> <string name="minimize_on_exit_popup_description">Minimumigi al ŝprucfenestra ludilo</string>
<string name="channels">Kanaloj</string> <string name="channels">Kanaloj</string>
<string name="playlists">Ludlistoj</string> <string name="playlists">Ludlistoj</string>
@ -258,8 +258,8 @@
<string name="msg_copied">Enpoŝigita</string> <string name="msg_copied">Enpoŝigita</string>
<string name="no_available_dir">Bonvolu difini elŝutan dosierujon poste en agordoj</string> <string name="no_available_dir">Bonvolu difini elŝutan dosierujon poste en agordoj</string>
<plurals name="views"> <plurals name="views">
<item quantity="one">%s spekto</item> <item quantity="one">%s spekto</item>
<item quantity="other">%s spektoj</item> <item quantity="other">%s spektoj</item>
</plurals> </plurals>
<string name="short_thousand">k</string> <string name="short_thousand">k</string>
<string name="short_million">M</string> <string name="short_million">M</string>
@ -286,7 +286,7 @@
<item quantity="one">%s abonanto</item> <item quantity="one">%s abonanto</item>
<item quantity="other">%s abonantoj</item> <item quantity="other">%s abonantoj</item>
</plurals> </plurals>
<string name="no_views">Neniuj spektoj</string> <string name="no_views">Neniuj spektoj</string>
<string name="no_videos">Neniu filmeto</string> <string name="no_videos">Neniu filmeto</string>
<string name="delete_item_search_history">Ĉu vi volas forviŝi tion eron el la historio de serĉo \?</string> <string name="delete_item_search_history">Ĉu vi volas forviŝi tion eron el la historio de serĉo \?</string>
<string name="main_page_content">Enhavo de la ĉefpaĝo</string> <string name="main_page_content">Enhavo de la ĉefpaĝo</string>
@ -305,7 +305,7 @@
<string name="drawer_open">Malfermi la tirkeston</string> <string name="drawer_open">Malfermi la tirkeston</string>
<string name="drawer_close">Fermi la tirtekston</string> <string name="drawer_close">Fermi la tirtekston</string>
<string name="external_player_unsupported_link_type">Ekstaraj ludantoj ne suportas tiajn ligilojn</string> <string name="external_player_unsupported_link_type">Ekstaraj ludantoj ne suportas tiajn ligilojn</string>
<string name="video_player">Filmetoludilo</string> <string name="video_player">Filmludilo</string>
<string name="background_player">Fona ludilo</string> <string name="background_player">Fona ludilo</string>
<string name="preferred_player_fetcher_notification_title">Akiranta informoj…</string> <string name="preferred_player_fetcher_notification_title">Akiranta informoj…</string>
<string name="preferred_player_fetcher_notification_message">Ŝarĝante petita enhavo</string> <string name="preferred_player_fetcher_notification_message">Ŝarĝante petita enhavo</string>
@ -347,7 +347,7 @@
<string name="playback_pitch">Ludkampo</string> <string name="playback_pitch">Ludkampo</string>
<string name="unhook_checkbox">Malligi (povas kaŭzi distordon)</string> <string name="unhook_checkbox">Malligi (povas kaŭzi distordon)</string>
<string name="preferred_open_action_settings_title">Preferita \'malfermi\' ago</string> <string name="preferred_open_action_settings_title">Preferita \'malfermi\' ago</string>
<string name="preferred_open_action_settings_summary">Defaŭlta ago malfermante enhavo — %s</string> <string name="preferred_open_action_settings_summary">Implicita ago malfermante vidaŭdaĵon — %s</string>
<string name="caption_setting_title">Subtitoloj</string> <string name="caption_setting_title">Subtitoloj</string>
<string name="caption_setting_description">Modifi la dimension de la teksto kaj la fonajn stilojn de la subtitoloj de la ludilo. Ĝi bezonas restarto de la apo por efektiviĝi.</string> <string name="caption_setting_description">Modifi la dimension de la teksto kaj la fonajn stilojn de la subtitoloj de la ludilo. Ĝi bezonas restarto de la apo por efektiviĝi.</string>
<string name="one_item_deleted">1 ero forviŝita.</string> <string name="one_item_deleted">1 ero forviŝita.</string>
@ -369,9 +369,9 @@
<string name="skip_silence_checkbox">Plirapidigi dum silentoj</string> <string name="skip_silence_checkbox">Plirapidigi dum silentoj</string>
<string name="playback_step">Paŝo</string> <string name="playback_step">Paŝo</string>
<string name="playback_reset">Restarigi</string> <string name="playback_reset">Restarigi</string>
<string name="saved_tabs_invalid_json">Ne povis legi konservitajn ongletoj, tial uzante la defaŭltajn</string> <string name="saved_tabs_invalid_json">Ne povis legi agorditajn langetojn, do uzos la implicitajn</string>
<string name="restore_defaults">Restaŭri la defaŭltojn</string> <string name="restore_defaults">Restarigi implicitajn valorojn</string>
<string name="restore_defaults_confirmation">Ĉu vi volas restaŭri la defaŭltojn valorojn\?</string> <string name="restore_defaults_confirmation">Ĉu vi volas restarigi la implicitajn valorojn?</string>
<string name="subscribers_count_not_available">Abonantoj kalkulo malhaveblas</string> <string name="subscribers_count_not_available">Abonantoj kalkulo malhaveblas</string>
<string name="main_page_content_summary">Kioj ongletoj estas montritaj en la ĉefpaĝo</string> <string name="main_page_content_summary">Kioj ongletoj estas montritaj en la ĉefpaĝo</string>
<string name="updates_setting_title">Ĝisdatigoj</string> <string name="updates_setting_title">Ĝisdatigoj</string>
@ -410,7 +410,7 @@
<string name="error_progress_lost">Progreso perdita, ĉar la dosiero estis forviŝita</string> <string name="error_progress_lost">Progreso perdita, ĉar la dosiero estis forviŝita</string>
<string name="error_timeout">Eltempiĝo de Konekto</string> <string name="error_timeout">Eltempiĝo de Konekto</string>
<string name="drawer_header_description">Ŝangi la servon, nuntempe elektita:</string> <string name="drawer_header_description">Ŝangi la servon, nuntempe elektita:</string>
<string name="default_kiosk_page_summary">Defaŭlta Kiosko</string> <string name="default_kiosk_page_summary">Implicita Kiosko</string>
<string name="no_one_watching">Neniu spektas</string> <string name="no_one_watching">Neniu spektas</string>
<plurals name="watching"> <plurals name="watching">
<item quantity="one">%s spektanto</item> <item quantity="one">%s spektanto</item>
@ -443,7 +443,7 @@
<string name="videos_string">Filmetoj</string> <string name="videos_string">Filmetoj</string>
<string name="permission_display_over_apps">Doni la permeson por afiŝiĝi supre aliaj apoj</string> <string name="permission_display_over_apps">Doni la permeson por afiŝiĝi supre aliaj apoj</string>
<string name="app_language_title">Preferata aplingvo</string> <string name="app_language_title">Preferata aplingvo</string>
<string name="systems_language">Sistemnormo</string> <string name="systems_language">Sistema</string>
<string name="subtitle_activity_recaptcha">Premu “Finita” kiam solvita</string> <string name="subtitle_activity_recaptcha">Premu “Finita” kiam solvita</string>
<string name="done">Finita</string> <string name="done">Finita</string>
<plurals name="seconds"> <plurals name="seconds">
@ -499,9 +499,7 @@
<string name="notification_action_repeat">Ripeti</string> <string name="notification_action_repeat">Ripeti</string>
<string name="search_showing_result_for">Montrante rezultojn pri: %s</string> <string name="search_showing_result_for">Montrante rezultojn pri: %s</string>
<string name="open_with">Malfermi per</string> <string name="open_with">Malfermi per</string>
<string name="restricted_video">Tiu ĉi filmeto havas aĝlimon. <string name="restricted_video">Tiu ĉi filmeto havas aĝminimumon. \n \nŜaltu «%1$s» en la agordoj, se vi volus vidi ĝin.</string>
\n
\nŜalti \"%1$s\" en la agordoj, se vi volas vidi ĝin.</string>
<string name="night_theme_title">Malhela etoso</string> <string name="night_theme_title">Malhela etoso</string>
<string name="notification_colorize_title">farbi sciigon</string> <string name="notification_colorize_title">farbi sciigon</string>
<string name="notification_action_buffering">Alŝuto</string> <string name="notification_action_buffering">Alŝuto</string>
@ -530,11 +528,11 @@
<string name="description_tab_description">Priskribo</string> <string name="description_tab_description">Priskribo</string>
<string name="comments_tab_description">Komentoj</string> <string name="comments_tab_description">Komentoj</string>
<string name="show_description_title">Montri priskribon</string> <string name="show_description_title">Montri priskribon</string>
<string name="notification_scale_to_square_image_summary">Stuci la bildeton de la video en la sciigo de 16:9 ĝis 1:1 propocio</string> <string name="notification_scale_to_square_image_summary">Stuci la antaŭvidan bildeton de la filmo en la sciigo de proporcio 16:9 al 1:1</string>
<string name="show_meta_info_title">Montri metadatumojn</string> <string name="show_meta_info_title">Montri metadatumojn</string>
<string name="mark_as_watched">Marki spektita</string> <string name="mark_as_watched">Marki spektita</string>
<string name="show_meta_info_summary">Malŝatu por kaŝi metadatumujojn kio havas aldonajn informojn pri la elsendfluisto, enhavo de la fluo, aŭ serĉpto</string> <string name="show_meta_info_summary">Malŝatu por kaŝi metadatumujojn kio havas aldonajn informojn pri la elsendfluisto, enhavo de la fluo, aŭ serĉpto</string>
<string name="show_description_summary">Malŝaltu por kaŝi la videan priskribon kaj aldonan informon</string> <string name="show_description_summary">Malŝaltu por kaŝi priskribojn kaj aldonajn informojn de filmetoj</string>
<string name="clear_queue_confirmation_title">Peti por konfirmo antaŭ vakigado atendvico</string> <string name="clear_queue_confirmation_title">Peti por konfirmo antaŭ vakigado atendvico</string>
<string name="clear_queue_confirmation_summary">Via atendvico povas anstataŭigi se vi ŝanĝi al malsama ludilo</string> <string name="clear_queue_confirmation_summary">Via atendvico povas anstataŭigi se vi ŝanĝi al malsama ludilo</string>
<string name="clear_queue_confirmation_description">La aktiva ludila atendvico anstataŭigos</string> <string name="clear_queue_confirmation_description">La aktiva ludila atendvico anstataŭigos</string>
@ -549,7 +547,7 @@
<string name="volume">Laŭteco</string> <string name="volume">Laŭteco</string>
<string name="none">Neniu</string> <string name="none">Neniu</string>
<string name="notification_colorize_summary">Permesi al Android agordi koloron de sciigo laŭ la precipa koloro de videaĵminiaturo (noti, ke ĉi tio ne disponeblas en ĉiuj iloj)</string> <string name="notification_colorize_summary">Permesi al Android agordi koloron de sciigo laŭ la precipa koloro de videaĵminiaturo (noti, ke ĉi tio ne disponeblas en ĉiuj iloj)</string>
<string name="auto_queue_toggle">Aŭtomata vicigado</string> <string name="auto_queue_toggle">Memaga vicigado</string>
<string name="right_gesture_control_title">Ago de dekstra gesto</string> <string name="right_gesture_control_title">Ago de dekstra gesto</string>
<string name="notification_actions_summary">Redakti ĉiun agon de sciigo per tuŝi gin. Elekti maksimume tri agon por montri en la kompakta sciigo per markobutonoj dekstre.</string> <string name="notification_actions_summary">Redakti ĉiun agon de sciigo per tuŝi gin. Elekti maksimume tri agon por montri en la kompakta sciigo per markobutonoj dekstre.</string>
<string name="prefer_original_audio_summary">Elekti la originalan aŭdiotrakon malgraŭ lingvo</string> <string name="prefer_original_audio_summary">Elekti la originalan aŭdiotrakon malgraŭ lingvo</string>
@ -574,20 +572,19 @@
<string name="remote_search_suggestions">Foraj serĉsugestoj</string> <string name="remote_search_suggestions">Foraj serĉsugestoj</string>
<string name="error_report_channel_description">Sciigoj por raporti erarojn</string> <string name="error_report_channel_description">Sciigoj por raporti erarojn</string>
<string name="loading_metadata_title">Ŝargante metadatumoj…</string> <string name="loading_metadata_title">Ŝargante metadatumoj…</string>
<string name="hash_channel_description">Sciigo por kreado de haketaĵoj de videoj</string> <string name="hash_channel_description">Sciigo por kreado de haketaĵoj de filmetoj</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube provizas \"Limigitan Reĝimon\", kiu kaŝas enhavon, kiu potence maltaŭgas por infanoj</string> <string name="youtube_restricted_mode_enabled_summary">YouTube provizas \"Limigitan Reĝimon\", kiu kaŝas enhavon, kiu potence maltaŭgas por infanoj</string>
<string name="restricted_video_no_stream">Ĉi tiu video estas aĝo-limigita. <string name="restricted_video_no_stream">Ĉi tiu filmeto havas aĝminimumon. \nPro novaj reguloj de YouTube, kiuj rilatas filmojn kun minimuma aĝo, NewPipe ne povas atingi ajnan fluon de ĉi tiu filmo kaj tial ne povas ludi ĝin.</string>
\nPro novaj reguloj de YouTube, kiuj aplikas al aĝo-limigitaj videoj, NewPipe ne povas atingi iun ajn video-fluoj de ĉi tiu video kaj konsekvence ne povas ludi ĝin.</string>
<string name="error_report_channel_name">Sciigo por erar-raportoj</string> <string name="error_report_channel_name">Sciigo por erar-raportoj</string>
<string name="notifications">Sciigoj</string> <string name="notifications">Sciigoj</string>
<string name="settings_category_player_notification_title">Ludila sciigo</string> <string name="settings_category_player_notification_title">Ludila sciigo</string>
<string name="hash_channel_name">Sciigo por haketado de videoj</string> <string name="hash_channel_name">Sciigo por haketado de filmetoj</string>
<string name="local_search_suggestions">Lokaj serĉsugestoj</string> <string name="local_search_suggestions">Lokaj serĉsugestoj</string>
<string name="start_main_player_fullscreen_title">Ŝalti ĉefan ludilon plenekrane</string> <string name="start_main_player_fullscreen_title">Ŝalti ĉefan ludilon plenekrane</string>
<string name="streams_notification_channel_description">Sciigo por novaj fluoj de abonoj</string> <string name="streams_notification_channel_description">Sciigo por novaj fluoj de abonoj</string>
<string name="clear_cookie_title">Forigi kuketojn de reCAPTCHA</string> <string name="clear_cookie_title">Forigi kuketojn de reCAPTCHA</string>
<string name="settings_category_player_notification_summary">Agordi la sciigon por ĉi-momente ludantaj datumtorentoj</string> <string name="settings_category_player_notification_summary">Agordi la sciigon por ĉi-momente ludantaj datumtorentoj</string>
<string name="start_main_player_fullscreen_summary">Ne komenci ludi videojn en la mini-ludilo, sed ŝalti plenekranan reĝimon rekte, se aŭtomata rotacio ŝlositas. Vi ankoraŭ povus atingi mini-ludilon, se vi elirus plenekranan reĝimon.</string> <string name="start_main_player_fullscreen_summary">Ne ekludu filmojn en la etludilo, sed ŝalti plenekranan reĝimon rekte, se memaga turniĝo estas ŝaltita. Vi ankoraŭ povus aliri la etludilon elirinte la plenekranan reĝimon.</string>
<string name="clear_cookie_summary">Forigi kuketojn, kiujn NewPipe konservas, kiam vi solvas reCAPTCHA-taskojn</string> <string name="clear_cookie_summary">Forigi kuketojn, kiujn NewPipe konservas, kiam vi solvas reCAPTCHA-taskojn</string>
<string name="error_report_notification_title">NewPipe renkontis eraron, tuŝi por raporti</string> <string name="error_report_notification_title">NewPipe renkontis eraron, tuŝi por raporti</string>
<string name="main_tabs_position_title">Pozicio de la ĉefaj langetoj</string> <string name="main_tabs_position_title">Pozicio de la ĉefaj langetoj</string>
@ -605,7 +602,7 @@
<string name="import_subscriptions_hint">Importi aŭ eksporti abonojn per la tri-punkta menuo</string> <string name="import_subscriptions_hint">Importi aŭ eksporti abonojn per la tri-punkta menuo</string>
<string name="msg_calculating_hash">Kalkulado de haketaĵo</string> <string name="msg_calculating_hash">Kalkulado de haketaĵo</string>
<string name="faq_title">Oftaj demandoj</string> <string name="faq_title">Oftaj demandoj</string>
<string name="no_dir_yet">Neniu dosierujo por elŝutoj agordita, bonvolu elekti la defaŭltan elŝuto-dosierujon nun</string> <string name="no_dir_yet">Neniu dosierujo por elŝutoj agordita, bonvolu elekti la preferatan elŝuto-dosierujon nun</string>
<string name="error_report_open_github_notice">Bonvolu certigi, ĉu erarraporto, kiu diskutas pri via eraro, jam ekzistas. Kreado de duoblaĵaj erarraportoj forprenas tempon el ni, kiun ni povus uzi por ripari la veran eraron.</string> <string name="error_report_open_github_notice">Bonvolu certigi, ĉu erarraporto, kiu diskutas pri via eraro, jam ekzistas. Kreado de duoblaĵaj erarraportoj forprenas tempon el ni, kiun ni povus uzi por ripari la veran eraron.</string>
<string name="related_items_tab_description">Rilatajn erojn</string> <string name="related_items_tab_description">Rilatajn erojn</string>
<string name="recaptcha_solve">Solvi</string> <string name="recaptcha_solve">Solvi</string>
@ -613,4 +610,12 @@
<string name="downloads_storage_ask_summary_no_saf_notice">Oni petos al vi kien salvi ĉiujn elŝutojn</string> <string name="downloads_storage_ask_summary_no_saf_notice">Oni petos al vi kien salvi ĉiujn elŝutojn</string>
<string name="yes">Jes</string> <string name="yes">Jes</string>
<string name="no">Ne</string> <string name="no">Ne</string>
</resources> <string name="remove_watched_popup_title">Ĉu forigu spektitajn filmetojn?</string>
<string name="youtube_music_premium_content">Ĉu tiu filmeto sole haveblas al abonantoj de YouTube Music Premium, do ĝi ne spekteblas nek elŝuteblas de NewPipe.</string>
<string name="audio_track_present_in_video">Sono devus jam esti en ĉi tiu fluo</string>
<string name="no_video_streams_available_for_external_players">Neniu filmofluo ludeblas por ekstera ludilo</string>
<string name="channel_tab_videos">Filmetoj</string>
<string name="remove_watched_popup_warning">Filmetoj kiuj spektiĝis antaŭ aŭ post sia aldoniĝo al la ludlisto foriĝus.. \nĈu vi certas? Ĉi tio nemalfareblus!</string>
<string name="reset_settings_summary">Restarigi implicitajn agordojn</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Jes, kaj ankaŭ parte spektitajn filmetojn</string>
</resources>

View File

@ -47,7 +47,7 @@
<string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string> <string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string>
<string name="content">Contenido</string> <string name="content">Contenido</string>
<string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string> <string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string>
<string name="main_bg_subtitle">Toca la lupa para empezar.</string> <string name="main_bg_subtitle">Toca la lupa para comenzar..</string>
<string name="duration_live">En directo</string> <string name="duration_live">En directo</string>
<string name="downloads">Descargas</string> <string name="downloads">Descargas</string>
<string name="downloads_title">Descargas</string> <string name="downloads_title">Descargas</string>
@ -818,7 +818,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Compartir la lista de reproducción</string> <string name="share_playlist">Compartir la lista de reproducción</string>
<string name="share_playlist_with_titles_message">Compartir las listas de reproducción con los detalles como el nombre de la lista y los títulos de los vídeos o como una simple lista de una dirección URL con los vídeos</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="one">%s respuesta</item> <item quantity="one">%s respuesta</item>
@ -840,4 +839,5 @@
<string name="auto_update_check_description">NewPipe puede buscar automáticamente nuevas versiones de vez en cuando y notificarle cuando estén disponibles. <string name="auto_update_check_description">NewPipe puede buscar automáticamente nuevas versiones de vez en cuando y notificarle cuando estén disponibles.
\n¿Quieres habilitar esto?</string> \n¿Quieres habilitar esto?</string>
<string name="import_settings_vulnerable_format">La configuración de la exportación que se importa utiliza un formato vulnerable que quedó obsoleto desde NewPipe 0.27.0. Asegúrese de que la exportación que se está importando provenga de una fuente confiable y prefiera usar solo exportaciones obtenidas de NewPipe 0.27.0 o posterior en el futuro. La compatibilidad con la importación de configuraciones en este formato vulnerable pronto se eliminará por completo y, luego, las versiones antiguas de NewPipe ya no podrán importar configuraciones de exportaciones desde las nuevas versiones.</string> <string name="import_settings_vulnerable_format">La configuración de la exportación que se importa utiliza un formato vulnerable que quedó obsoleto desde NewPipe 0.27.0. Asegúrese de que la exportación que se está importando provenga de una fuente confiable y prefiera usar solo exportaciones obtenidas de NewPipe 0.27.0 o posterior en el futuro. La compatibilidad con la importación de configuraciones en este formato vulnerable pronto se eliminará por completo y, luego, las versiones antiguas de NewPipe ya no podrán importar configuraciones de exportaciones desde las nuevas versiones.</string>
</resources> <string name="audio_track_type_secondary">secundaria</string>
</resources>

View File

@ -89,7 +89,7 @@
<string name="duration_live">Otse</string> <string name="duration_live">Otse</string>
<string name="downloads">Allalaadimised</string> <string name="downloads">Allalaadimised</string>
<string name="downloads_title">Allalaadimised</string> <string name="downloads_title">Allalaadimised</string>
<string name="error_report_title">Vea teatamine</string> <string name="error_report_title">Veateade</string>
<string name="all">Kõik</string> <string name="all">Kõik</string>
<string name="disabled">Keelatud</string> <string name="disabled">Keelatud</string>
<string name="clear">Kustuta</string> <string name="clear">Kustuta</string>
@ -99,8 +99,8 @@
<string name="always">Alati</string> <string name="always">Alati</string>
<string name="just_once">Üks kord</string> <string name="just_once">Üks kord</string>
<string name="file">Fail</string> <string name="file">Fail</string>
<string name="notification_channel_name">NewPipe teavitus</string> <string name="notification_channel_name">NewPipe\'i teavitus</string>
<string name="notification_channel_description">Teavitused NewPipe pleierile</string> <string name="notification_channel_description">NewPipe\'i meediaesitaja teavitused</string>
<string name="unknown_content">[Tundmatu]</string> <string name="unknown_content">[Tundmatu]</string>
<string name="switch_to_background">Lülita taustale</string> <string name="switch_to_background">Lülita taustale</string>
<string name="switch_to_popup">Lülita hüpikpleierile</string> <string name="switch_to_popup">Lülita hüpikpleierile</string>
@ -180,10 +180,10 @@
<string name="dismiss">Loobu</string> <string name="dismiss">Loobu</string>
<string name="rename">Muuda nime</string> <string name="rename">Muuda nime</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="msg_name">Faili nimi</string> <string name="msg_name">Failinimi</string>
<string name="msg_threads">Lõimed</string> <string name="msg_threads">Lõimed</string>
<string name="msg_error">Viga</string> <string name="msg_error">Viga</string>
<string name="msg_running">NewPipe allalaadimine</string> <string name="msg_running">NewPipe\'i on allalaadimisel</string>
<string name="msg_running_detail">Üksikasjade nägemiseks toksa</string> <string name="msg_running_detail">Üksikasjade nägemiseks toksa</string>
<string name="msg_wait">Palun oota…</string> <string name="msg_wait">Palun oota…</string>
<string name="msg_copied">Kopeeriti lõikepuhvrisse</string> <string name="msg_copied">Kopeeriti lõikepuhvrisse</string>
@ -197,18 +197,18 @@
<string name="settings_file_replacement_character_title">Asendustähemärk</string> <string name="settings_file_replacement_character_title">Asendustähemärk</string>
<string name="charset_letters_and_digits">Tähed ja numbrid</string> <string name="charset_letters_and_digits">Tähed ja numbrid</string>
<string name="charset_most_special_characters">Erimärgid</string> <string name="charset_most_special_characters">Erimärgid</string>
<string name="title_activity_about">NewPipe rakendusest</string> <string name="title_activity_about">Rakenduse teave: NewPipe</string>
<string name="title_licenses">Kolmanda osapoole litsentsid</string> <string name="title_licenses">Kolmanda osapoole litsentsid</string>
<string name="tab_about">Rakenduse teave ja KKK</string> <string name="tab_about">Rakenduse teave ja KKK</string>
<string name="tab_licenses">Litsentsid</string> <string name="tab_licenses">Litsentsid</string>
<string name="contribution_title">Panusta</string> <string name="contribution_title">Panusta</string>
<string name="view_on_github">Vaata GitHubis</string> <string name="view_on_github">Vaata GitHubis</string>
<string name="donation_title">Anneta</string> <string name="donation_title">Anneta</string>
<string name="website_title">Veebileht</string> <string name="website_title">Veebisait</string>
<string name="website_encouragement">Enama info saamiseks külasta NewPipe veebilehte.</string> <string name="website_encouragement">Täiendava info ja uudiste lugemiseks külasta NewPipe\'i veebisaiti.</string>
<string name="privacy_policy_title">NewPipe privaatsuspoliitika</string> <string name="privacy_policy_title">NewPipe\'i privaatsuspoliitika</string>
<string name="read_privacy_policy">Loe privaatsuspoliitikat</string> <string name="read_privacy_policy">Loe privaatsuspoliitikat</string>
<string name="app_license_title">NewPipe litsents</string> <string name="app_license_title">NewPipe\'i litsents</string>
<string name="read_full_license">Loe litsentsi</string> <string name="read_full_license">Loe litsentsi</string>
<string name="title_activity_history">Ajalugu</string> <string name="title_activity_history">Ajalugu</string>
<string name="action_history">Ajalugu</string> <string name="action_history">Ajalugu</string>
@ -303,11 +303,10 @@
<string name="copyright" formatted="true">© %1$s %2$s %3$s alla</string> <string name="copyright" formatted="true">© %1$s %2$s %3$s alla</string>
<string name="app_description">Vaba ja lihtne voogesitus Androidis.</string> <string name="app_description">Vaba ja lihtne voogesitus Androidis.</string>
<string name="contribution_encouragement">Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb!</string> <string name="contribution_encouragement">Kui sul on ideid kujunduse muutmisest, koodi puhastamisest või suurtest koodi muudatustest - abi on alati teretulnud. Mida rohkem tehtud, seda paremaks läheb!</string>
<string name="donation_encouragement">NewPipe arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutamise kogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string> <string name="donation_encouragement">NewPipe\'i arendajad on vabatahtlikud, kes kulutavad oma vaba aega, toomaks sulle parimat kasutuskogemust. On aeg anda tagasi aidates arendajaid ja muuta NewPipe veel paremaks, nautides ise tassi kohvi.</string>
<string name="give_back">Anneta</string> <string name="give_back">Anneta</string>
<string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. <string name="privacy_policy_encouragement">NewPipe võtab privaatsust väga tõsiselt. Seetõttu ei kogu rakendus ilma nõusolekuta mingeid andmeid. \nNewPipe\'i privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string>
\nNewPipe privaatsuspoliitika selgitab üksikasjalikult, milliseid andmeid saadetakse ja kogutakse veateate saatmisel.</string> <string name="app_license">NewPipe on vaba ja avatud lähtekoodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string>
<string name="app_license">NewPipe vaba avatud koodiga tarkvara. Seada võid kasutada, uurida, jagada ja parandada nii, nagu õigemaks pead. Täpsemalt - seda võid levitada ja/või muuta vastavalt Vaba Tarkvara Sihtasutuse avaldatud GNU Üldise Avaliku Litsentsi v.3 (või sinu valikul hilisema versiooni) tingimustele.</string>
<string name="enable_disposed_exceptions_title">Teavita elutsüklist väljas vigadest</string> <string name="enable_disposed_exceptions_title">Teavita elutsüklist väljas vigadest</string>
<string name="import_soundcloud_instructions">Impordi SoundCloudi profiil trükkides URL või oma ID: <string name="import_soundcloud_instructions">Impordi SoundCloudi profiil trükkides URL või oma ID:
\n \n
@ -321,8 +320,7 @@
<string name="skip_silence_checkbox">Keri helitu koht edasi</string> <string name="skip_silence_checkbox">Keri helitu koht edasi</string>
<string name="playback_step">Samm</string> <string name="playback_step">Samm</string>
<string name="playback_reset">Lähtesta</string> <string name="playback_reset">Lähtesta</string>
<string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun lugege seda hoolikalt. <string name="start_accept_privacy_policy">Selleks, et täita Euroopa Üldist Andmekaitse Määrust (GDPR), juhime tähelepanu NewPipe\'i privaatsuspoliitikale. Palun loe seda hoolikalt. \nMeile veateate saatmiseks pead sellega nõustuma.</string>
\nMeile veateate saatmiseks pead sellega nõustuma.</string>
<string name="minimize_on_exit_title">Minimeeri, kui kasutad teisi rakendusi</string> <string name="minimize_on_exit_title">Minimeeri, kui kasutad teisi rakendusi</string>
<string name="minimize_on_exit_summary">Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s</string> <string name="minimize_on_exit_summary">Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s</string>
<string name="minimize_on_exit_none_description">Pole</string> <string name="minimize_on_exit_none_description">Pole</string>
@ -335,7 +333,7 @@
<string name="events">Sündmused</string> <string name="events">Sündmused</string>
<string name="file_deleted">Fail kustutati</string> <string name="file_deleted">Fail kustutati</string>
<string name="app_update_notification_channel_name">Rakenduse värskenduse teavitus</string> <string name="app_update_notification_channel_name">Rakenduse värskenduse teavitus</string>
<string name="app_update_notification_channel_description">Teavitus NewPipe uuetest versioonidest</string> <string name="app_update_notification_channel_description">Teavitus NewPipe\'i uuetest versioonidest</string>
<string name="download_to_sdcard_error_title">Väline andmekandja pole saadaval</string> <string name="download_to_sdcard_error_title">Väline andmekandja pole saadaval</string>
<string name="download_to_sdcard_error_message">Allalaadimine välisele SD-kaardile ei ole võimalik. Kas lähtestada allalaadimiste kataloogi asukoht\?</string> <string name="download_to_sdcard_error_message">Allalaadimine välisele SD-kaardile ei ole võimalik. Kas lähtestada allalaadimiste kataloogi asukoht\?</string>
<string name="saved_tabs_invalid_json">Tõrge salvestatud vahekaaride lugemisel; kasutatakse vaikeväärtusi</string> <string name="saved_tabs_invalid_json">Tõrge salvestatud vahekaaride lugemisel; kasutatakse vaikeväärtusi</string>
@ -350,7 +348,7 @@
<string name="list">Nimekiri</string> <string name="list">Nimekiri</string>
<string name="grid">Võrgustik</string> <string name="grid">Võrgustik</string>
<string name="auto">Auto</string> <string name="auto">Auto</string>
<string name="app_update_available_notification_title">NewPipe värskendus on saadaval!</string> <string name="app_update_available_notification_title">NewPipe\'i värskendus on saadaval!</string>
<string name="missions_header_finished">Lõpetatud</string> <string name="missions_header_finished">Lõpetatud</string>
<string name="missions_header_pending">Ootel</string> <string name="missions_header_pending">Ootel</string>
<string name="paused">peatatud</string> <string name="paused">peatatud</string>
@ -369,7 +367,7 @@
<string name="error_file_creation">Faili ei saa luua</string> <string name="error_file_creation">Faili ei saa luua</string>
<string name="error_ssl_exception">Turvalist ühendust ei suudetud luua</string> <string name="error_ssl_exception">Turvalist ühendust ei suudetud luua</string>
<string name="error_unknown_host">Serverit ei leitud</string> <string name="error_unknown_host">Serverit ei leitud</string>
<string name="error_connect_host">Serveriga ei saadud ühendust</string> <string name="error_connect_host">Serveriga ei saa ühendust</string>
<string name="error_http_no_content">Server ei saada andmeid</string> <string name="error_http_no_content">Server ei saada andmeid</string>
<string name="error_http_unsupported_range">Server ei toeta mitmelõimelisi allalaadimisi. Proovi uuesti kasutades @string/msg_threads = 1</string> <string name="error_http_unsupported_range">Server ei toeta mitmelõimelisi allalaadimisi. Proovi uuesti kasutades @string/msg_threads = 1</string>
<string name="error_http_not_found">Ei leitud</string> <string name="error_http_not_found">Ei leitud</string>
@ -556,7 +554,7 @@
<string name="enable_queue_limit_desc">Luba korraga vaid üks allalaadimine</string> <string name="enable_queue_limit_desc">Luba korraga vaid üks allalaadimine</string>
<string name="enable_queue_limit">Piira allalaadimiste järjekorda</string> <string name="enable_queue_limit">Piira allalaadimiste järjekorda</string>
<string name="error_progress_lost">Faili kustutamisega läks ka tööjärg kautsi</string> <string name="error_progress_lost">Faili kustutamisega läks ka tööjärg kautsi</string>
<string name="error_postprocessing_stopped">Faili töötlemisel NewPipe lõpetas töö</string> <string name="error_postprocessing_stopped">NewPipe lõpetas faili töötlemisel töö</string>
<string name="disable_media_tunneling_summary">Lülita meedia tunneldamine välja juhul, kui esitamisel tekib must ekraan või pildi kuvamine on katkendlik.</string> <string name="disable_media_tunneling_summary">Lülita meedia tunneldamine välja juhul, kui esitamisel tekib must ekraan või pildi kuvamine on katkendlik.</string>
<string name="disable_media_tunneling_title">Lülita meedia tunneldamine välja</string> <string name="disable_media_tunneling_title">Lülita meedia tunneldamine välja</string>
<string name="drawer_header_description">Vaheta teenust, hetkel on kasutusel:</string> <string name="drawer_header_description">Vaheta teenust, hetkel on kasutusel:</string>
@ -609,9 +607,7 @@
<string name="feed_use_dedicated_fetch_method_enable_button">Luba kiire režiim</string> <string name="feed_use_dedicated_fetch_method_enable_button">Luba kiire režiim</string>
<string name="feed_use_dedicated_fetch_method_title">Hangi võimalusel spetsiaalsest voost</string> <string name="feed_use_dedicated_fetch_method_title">Hangi võimalusel spetsiaalsest voost</string>
<string name="feed_load_error_fast_unknown">Kiirvoo režiim ei paku selle kohta täiendavat teavet.</string> <string name="feed_load_error_fast_unknown">Kiirvoo režiim ei paku selle kohta täiendavat teavet.</string>
<string name="feed_load_error_terminated">Autori konto on lõpetatud. <string name="feed_load_error_terminated">Autori konto on suletud. \nTulevikus ei saa NewPipe seda meediavoogu laadida. \nKas soovid tühistada selle kanali tellimuse?</string>
\nTulevikus ei saa NewPipe seda voogu laadida.
\nKas soovid tühistada selle kanali tellimuse\?</string>
<string name="feed_load_error_account_info">Voo \'%s\' laadimine nurjus.</string> <string name="feed_load_error_account_info">Voo \'%s\' laadimine nurjus.</string>
<string name="feed_load_error">Via voo laadimisel</string> <string name="feed_load_error">Via voo laadimisel</string>
<string name="feed_update_threshold_option_always_update">Värskenda alati</string> <string name="feed_update_threshold_option_always_update">Värskenda alati</string>
@ -622,17 +618,7 @@
<string name="downloads_storage_use_saf_summary_api_29">Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\'</string> <string name="downloads_storage_use_saf_summary_api_29">Android 10st alates on toetatud ainult salvestusjuurdepääsu raamistik \'Storage Access Framework\'</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Sinult küsitakse iga kord, kuhu alla laadimine salvestada</string> <string name="downloads_storage_ask_summary_no_saf_notice">Sinult küsitakse iga kord, kuhu alla laadimine salvestada</string>
<string name="detail_heart_img_view_description">Südamlik autor</string> <string name="detail_heart_img_view_description">Südamlik autor</string>
<string name="feed_use_dedicated_fetch_method_help_text">Kas sinu meelest on voo laadimine aeglane\? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu). <string name="feed_use_dedicated_fetch_method_help_text">Kas sinu meelest on voo laadimine aeglane? Sel juhul proovi lubada kiire laadimine (seda saad muuta seadetes või vajutades allolevat nuppu). \n \nNewPipe pakub kahte voo laadimise strateegiat: \n• Tellitud kanali täielik, kuid aeglane hankimine. \n• Teenuse spetsiaalse otspunkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik. \n \nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi. \n \nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga. \n \nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
\n
\nNewPipe pakub kahte voo laadimise strateegiat:
\n• Tellitud kanali täielik, kuid aeglane hankimine.
\n• Teenuse spetsiaalse lõpp-punkti kasutamine, mis on kiire, kuid tavaliselt mittetäielik.
\n
\nErinevus nende kahe vahel seisneb selles, et kiirel puudub tavaliselt teave, näiteks üksuse pikkus või tüüp (ei saa eristada reaalajas videoid tavalistest) ja see võib tagastada vähem üksusi.
\n
\nYouTube on näide teenusest, mis pakub seda kiirmeetodit oma RSS-vooga.
\n
\nNii et valik taandub sellele, mida eelistad: kiirus või täpne teave.</string>
<string name="mark_as_watched">Märgi vaadatuks</string> <string name="mark_as_watched">Märgi vaadatuks</string>
<string name="show_image_indicators_summary">Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu</string> <string name="show_image_indicators_summary">Näita piltide kohal Picasso värvides riba, mis märgib pildi allikat: punane tähistab võrku, sinine kohalikku andmekandjat ja roheline kohalikku mälu</string>
<string name="show_image_indicators_title">Näita piltide allikat</string> <string name="show_image_indicators_title">Näita piltide allikat</string>
@ -658,7 +644,7 @@
<string name="feed_new_items">Uued andmevoo kirjed</string> <string name="feed_new_items">Uued andmevoo kirjed</string>
<string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest</string> <string name="show_crash_the_player_title">Näita „Jooksuta meediamängija kokku“ nupukest</string>
<string name="show_crash_the_player_summary">Näitab valikut meediamängija kokkujooksutamiseks</string> <string name="show_crash_the_player_summary">Näitab valikut meediamängija kokkujooksutamiseks</string>
<string name="error_report_notification_title">NewPipe töös tekkis viga, sellest teavitamiseks toksa</string> <string name="error_report_notification_title">NewPipe\'i töös tekkis viga, sellest teavitamiseks toksa</string>
<string name="crash_the_player">Jooksuta meediamängija kokku</string> <string name="crash_the_player">Jooksuta meediamängija kokku</string>
<string name="show_error_snackbar">Näita veateate akent</string> <string name="show_error_snackbar">Näita veateate akent</string>
<string name="error_report_channel_name">Teavitus vigadest</string> <string name="error_report_channel_name">Teavitus vigadest</string>
@ -804,7 +790,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Jaga esitusloendit</string> <string name="share_playlist">Jaga esitusloendit</string>
<string name="share_playlist_with_titles_message">Jaga esitusloendit kas väga detailse teabega palade kohta või lihtsa url\'ide loendina</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">Näita veel</string> <string name="show_more">Näita veel</string>
<plurals name="replies"> <plurals name="replies">
@ -814,8 +799,7 @@
<string name="show_less">Näita vähem</string> <string name="show_less">Näita vähem</string>
<string name="notification_actions_summary_android13">Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta.</string> <string name="notification_actions_summary_android13">Muuda iga teavituse tegevust sellel toksates. Kolm esimest tegevust (esita/peata esitus, eelmine video, järgmine video) on süsteemsed ja neid ei saa muuta.</string>
<string name="settings_category_backup_restore_title">Varundus ja taastamine</string> <string name="settings_category_backup_restore_title">Varundus ja taastamine</string>
<string name="auto_update_check_description">NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. <string name="auto_update_check_description">NewPipe võib aeg-ajalt automaatselt kontrollida uute versioonide olemasolu ning sind vastavalt teavitada. \nKas sa soovid sellist võimalust kasutada?</string>
\nKas sa soovid sellist võimalust kasuutada?</string>
<string name="reset_settings_title">Lähtesta seadistused</string> <string name="reset_settings_title">Lähtesta seadistused</string>
<string name="reset_settings_summary">Lähtesta kõik seadistused nende vaikimisi väärtusteks</string> <string name="reset_settings_summary">Lähtesta kõik seadistused nende vaikimisi väärtusteks</string>
<string name="error_insufficient_storage">Seadmes pole enam piisavalt vaba ruumi</string> <string name="error_insufficient_storage">Seadmes pole enam piisavalt vaba ruumi</string>
@ -824,5 +808,6 @@
\nKas sa soovid jätkata?</string> \nKas sa soovid jätkata?</string>
<string name="yes">Jah</string> <string name="yes">Jah</string>
<string name="no">Ei</string> <string name="no">Ei</string>
<string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam luua ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning edaspidi loo ekspordifailid NewPipe versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string> <string name="import_settings_vulnerable_format">Imporditavad andmed kasutavad turvaprobleemidega vormingut, mida alates versioonist 0.27.0 NewPipe enam kasutada ei suuda. Palun kontrolli, et impordifail on loodud usaldusväärse osapoole poolt ning eelista ekspordifaile, mis on loodud NewPipe\'i versiooniga 0.27.0 või uuemaga. Tugi sellise vana vormingu kasutamisele kaob õige pea ja seejärel NewPipe\'i uuemad ja vanemad versioonid ei saa omavahel andmeid enam vahetada.</string>
</resources> <string name="audio_track_type_secondary">täiendav</string>
</resources>

View File

@ -734,11 +734,11 @@
<string name="use_exoplayer_decoder_fallback_title">Erabili ExoPlayer-en deskodetzailearen ordezko eginbidea</string> <string name="use_exoplayer_decoder_fallback_title">Erabili ExoPlayer-en deskodetzailearen ordezko eginbidea</string>
<string name="none">Bat ere ez</string> <string name="none">Bat ere ez</string>
<string name="loading_metadata_title">Metadatuak kargatzen…</string> <string name="loading_metadata_title">Metadatuak kargatzen…</string>
<string name="settings_category_exoplayer_summary">Kudeatu ExoPlayer-en ezarpen batzuk. Aldaketa hauek eragina izan dezaten, erreproduzitzailea berrabiarazi behar da.</string> <string name="settings_category_exoplayer_summary">Kudeatu ExoPlayer-en ezarpen batzuk. Aldaketa hauek eragina izan dezaten, erreproduzitzailea berrabiarazi behar da</string>
<string name="main_tabs_position_title">Fitxa nagusien kokapena</string> <string name="main_tabs_position_title">Fitxa nagusien kokapena</string>
<string name="feed_fetch_channel_tabs">Lortu kanalaren fitxak</string> <string name="feed_fetch_channel_tabs">Lortu kanalaren fitxak</string>
<string name="metadata_avatars">Abatarrak</string> <string name="metadata_avatars">Abatarrak</string>
<string name="use_exoplayer_decoder_fallback_summary">Gaitu aukera hau deskodetzailea hasieratzeko arazoak badituzu, hala nola, lehentasun gutxiago duen deskodetzailetara igarotzea deskodetzaile nagusiaren hasieratzeak huts egiten badu. Aukera honek erreprodukzioak kalitate urria izatea ekar dezake, deskodetzaile nagusiak erabiltzean ez bezala.</string> <string name="use_exoplayer_decoder_fallback_summary">Gaitu aukera hau deskodetzailea hasieratzeko arazoak badituzu, hala nola, lehentasun gutxiago duen deskodetzailetara igarotzea deskodetzaile nagusiaren hasieratzeak huts egiten badu. Aukera honek erreprodukzioak kalitate urria izatea ekar dezake, deskodetzaile nagusiak erabiltzean ez bezala</string>
<string name="right_gesture_control_title">Eskuineko keinuaren ekintza</string> <string name="right_gesture_control_title">Eskuineko keinuaren ekintza</string>
<string name="always_use_exoplayer_set_output_surface_workaround_title">Erabili beti ExoPlayer-en bideo-irteeraren interfazeko ezarpenaren konponbidea</string> <string name="always_use_exoplayer_set_output_surface_workaround_title">Erabili beti ExoPlayer-en bideo-irteeraren interfazeko ezarpenaren konponbidea</string>
<string name="next_stream">Hurrengo igorpena</string> <string name="next_stream">Hurrengo igorpena</string>
@ -752,7 +752,7 @@
<string name="left_gesture_control_title">Ezkerreko keinuaren ekintza</string> <string name="left_gesture_control_title">Ezkerreko keinuaren ekintza</string>
<string name="prefer_descriptive_audio_title">Hobetsi audio deskribatzailea</string> <string name="prefer_descriptive_audio_title">Hobetsi audio deskribatzailea</string>
<string name="metadata_uploader_avatars">Igotako abatarrak</string> <string name="metadata_uploader_avatars">Igotako abatarrak</string>
<string name="progressive_load_interval_summary">Aldatu eduki progresiboen kargatze-tartearen tamaina (oraingoa: %s). Balio baxu batek hasierako kargatzea bizkortu dezake.</string> <string name="progressive_load_interval_summary">Aldatu eduki progresiboen kargatze-tartearen tamaina (oraingoa: %s). Balio baxu batek hasierako kargatzea bizkortu dezake</string>
<string name="prefer_original_audio_title">Hobetsi jatorrizko audioa</string> <string name="prefer_original_audio_title">Hobetsi jatorrizko audioa</string>
<string name="play_queue_audio_track">Audioa: %s</string> <string name="play_queue_audio_track">Audioa: %s</string>
<string name="metadata_banners">Bannerrak</string> <string name="metadata_banners">Bannerrak</string>
@ -774,7 +774,6 @@
<string name="notification_actions_summary_android13">Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Lehen hiru ekintzak (erreproduzitu/pausatu, aurrekoa eta hurrengoa) sistemarengatik ezarrita daude eta ezin dira pertsonalizatu.</string> <string name="notification_actions_summary_android13">Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Lehen hiru ekintzak (erreproduzitu/pausatu, aurrekoa eta hurrengoa) sistemarengatik ezarrita daude eta ezin dira pertsonalizatu.</string>
<string name="rewind">Atzera egin</string> <string name="rewind">Atzera egin</string>
<string name="image_quality_title">Irudiaren kalitatea</string> <string name="image_quality_title">Irudiaren kalitatea</string>
<string name="share_playlist_with_titles_message">Partekatu erreprodukzio-zerrenda xehetasunekin, esate baterako, erreprodukzio-zerrendaren izena eta bideo-izenburuak edo bideo-URLen zerrenda soil gisa</string>
<string name="more_options">Aukera gehiago</string> <string name="more_options">Aukera gehiago</string>
<string name="duration">Iraupena</string> <string name="duration">Iraupena</string>
<string name="forward">Aurrera egin</string> <string name="forward">Aurrera egin</string>
@ -793,7 +792,7 @@
<string name="reset_all_settings">Ezarpenak berrezartzeak zure ezarpen gogokoenak baztertzen ditu eta aplikazioa berrabiarazten du. <string name="reset_all_settings">Ezarpenak berrezartzeak zure ezarpen gogokoenak baztertzen ditu eta aplikazioa berrabiarazten du.
\n \n
\nAurrera egin nahi duzu?</string> \nAurrera egin nahi duzu?</string>
<string name="error_insufficient_storage">Ez dago lekurik gailuan</string> <string name="error_insufficient_storage">Ez dago nahikoa lekurik gailuan</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Konponbide honek azal aldaketa bat gertatzean bideo kodekak askatu eta berrezartzen ditu, azalaren kodeka zuzenean ezarri ordez. ExoPlayer-ek dagoeneko erabiltzen du arazoak dituzten gailu batzuetan. Ezarpen honek Android 6 eta berriagotan funtzionatzen du soilik <string name="always_use_exoplayer_set_output_surface_workaround_summary">Konponbide honek azal aldaketa bat gertatzean bideo kodekak askatu eta berrezartzen ditu, azalaren kodeka zuzenean ezarri ordez. ExoPlayer-ek dagoeneko erabiltzen du arazoak dituzten gailu batzuetan. Ezarpen honek Android 6 eta berriagotan funtzionatzen du soilik
\n \n
\nAukera hau gaitzeak erreprodukzio erroreak saihestu ditzake bideo-erreproduktorea aldatzean edo pantaila osoan jartzean</string> \nAukera hau gaitzeak erreprodukzio erroreak saihestu ditzake bideo-erreproduktorea aldatzean edo pantaila osoan jartzean</string>
@ -825,4 +824,5 @@
<item quantity="other">%s erantzun</item> <item quantity="other">%s erantzun</item>
</plurals> </plurals>
<string name="show_less">Erakutsi gutxiago</string> <string name="show_less">Erakutsi gutxiago</string>
</resources> <string name="audio_track_type_secondary">bigarren mailako</string>
</resources>

View File

@ -764,4 +764,6 @@
<string name="duration">مدّت</string> <string name="duration">مدّت</string>
<string name="rewind">پسروی</string> <string name="rewind">پسروی</string>
<string name="question_mark">؟</string> <string name="question_mark">؟</string>
</resources> <string name="settings_category_backup_restore_title">پشتیبان‌گیری و بازیابی</string>
<string name="no_live_streams">بدون جریان زنده</string>
</resources>

View File

@ -759,7 +759,6 @@
<string name="rewind">Kelaa taaksepäin</string> <string name="rewind">Kelaa taaksepäin</string>
<string name="feed_fetch_channel_tabs_summary">Noudettavat välilehdet syötettä päivitettäessä. Tällä valinnalla ei ole vaikutusta, jos kanava päivitetään käyttämällä nopeaa tilaa.</string> <string name="feed_fetch_channel_tabs_summary">Noudettavat välilehdet syötettä päivitettäessä. Tällä valinnalla ei ole vaikutusta, jos kanava päivitetään käyttämällä nopeaa tilaa.</string>
<string name="delete_downloaded_files_confirm">Poistetaanko kaikki ladatut tiedostot levyltä\?</string> <string name="delete_downloaded_files_confirm">Poistetaanko kaikki ladatut tiedostot levyltä\?</string>
<string name="share_playlist_with_titles_message">Jaa soittolista, jossa on tietoja, kuten soittolistan nimi ja videon nimi, tai yksinkertainen luettelo videoiden URL-osoitteista</string>
<string name="image_quality_medium">Keskilaatu</string> <string name="image_quality_medium">Keskilaatu</string>
<string name="metadata_uploader_avatars">Lataajan avatarit</string> <string name="metadata_uploader_avatars">Lataajan avatarit</string>
<string name="percent">Prosentti</string> <string name="percent">Prosentti</string>
@ -810,4 +809,4 @@
<string name="auto_update_check_description">NewPipe voi automaattisesti tarkistaa päivitysten saatavuuden silloin tällöin ja ilmoittaa kun niitä on saatavilla. <string name="auto_update_check_description">NewPipe voi automaattisesti tarkistaa päivitysten saatavuuden silloin tällöin ja ilmoittaa kun niitä on saatavilla.
\nHaluatko ottaa tämän käyttöön?</string> \nHaluatko ottaa tämän käyttöön?</string>
<string name="error_insufficient_storage">Laitteella ei ole riittävästi vapaata tilaa</string> <string name="error_insufficient_storage">Laitteella ei ole riittävästi vapaata tilaa</string>
</resources> </resources>

View File

@ -262,4 +262,4 @@
<string name="no_one_watching">Walang nanonood</string> <string name="no_one_watching">Walang nanonood</string>
<string name="empty_list_subtitle">Wala dito</string> <string name="empty_list_subtitle">Wala dito</string>
<string name="invalid_directory">Walang folder na ganoon</string> <string name="invalid_directory">Walang folder na ganoon</string>
</resources> </resources>

View File

@ -178,7 +178,7 @@
<string name="play_queue_stream_detail">Détails</string> <string name="play_queue_stream_detail">Détails</string>
<string name="play_queue_audio_settings">Paramètres audios</string> <string name="play_queue_audio_settings">Paramètres audios</string>
<string name="show_hold_to_append_title">Afficher l\'astuce « Maintenir pour ajouter à la file »</string> <string name="show_hold_to_append_title">Afficher l\'astuce « Maintenir pour ajouter à la file »</string>
<string name="show_hold_to_append_summary">Affiche lastuce lors de lappui des boutons « Arrière-plan » ou « Flottant » sur la page de détails dune vidéo</string> <string name="show_hold_to_append_summary">Affiche lastuce lors de lappui des boutons « Arrière-plan » ou « Flottant » sur la page de détails dune vidéo</string>
<string name="unknown_content">[Inconnu]</string> <string name="unknown_content">[Inconnu]</string>
<string name="player_recoverable_failure">Récupération depuis lerreur du lecteur</string> <string name="player_recoverable_failure">Récupération depuis lerreur du lecteur</string>
<string name="kiosk_page_summary">Kiosque</string> <string name="kiosk_page_summary">Kiosque</string>
@ -197,7 +197,7 @@
<string name="switch_to_main">Basculer en principal</string> <string name="switch_to_main">Basculer en principal</string>
<string name="drawer_open">Ouvrir le menu</string> <string name="drawer_open">Ouvrir le menu</string>
<string name="drawer_close">Fermer le menu</string> <string name="drawer_close">Fermer le menu</string>
<string name="no_player_found_toast">Aucun lecteur multimédia trouvé (vous pouvez installer VLC pour continuer).</string> <string name="no_player_found_toast">Aucun lecteur de flux trouvé (vous pouvez installer VLC pour le lire).</string>
<string name="always">Toujours</string> <string name="always">Toujours</string>
<string name="just_once">Une seule fois</string> <string name="just_once">Une seule fois</string>
<string name="external_player_unsupported_link_type">Les lecteurs externes ne prennent pas en charge ces types de liens</string> <string name="external_player_unsupported_link_type">Les lecteurs externes ne prennent pas en charge ces types de liens</string>
@ -534,7 +534,7 @@
<string name="channel_created_by">Créé par %s</string> <string name="channel_created_by">Créé par %s</string>
<string name="show_original_time_ago_summary">Les textes originaux des services vont être visibles dans les items</string> <string name="show_original_time_ago_summary">Les textes originaux des services vont être visibles dans les items</string>
<string name="show_original_time_ago_title">Afficher la date originelle sur les items</string> <string name="show_original_time_ago_title">Afficher la date originelle sur les items</string>
<string name="youtube_restricted_mode_enabled_title">Activer le «Mode restreint» de YouTube</string> <string name="youtube_restricted_mode_enabled_title">Activer le « Mode restreint » de YouTube</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Afficher uniquement les abonnements non groupés</string> <string name="feed_group_show_only_ungrouped_subscriptions">Afficher uniquement les abonnements non groupés</string>
<string name="playlist_page_summary">Page des listes de lecture</string> <string name="playlist_page_summary">Page des listes de lecture</string>
<string name="no_playlist_bookmarked_yet">Aucune liste de lecture encore enregistrée</string> <string name="no_playlist_bookmarked_yet">Aucune liste de lecture encore enregistrée</string>
@ -671,7 +671,7 @@
<string name="check_for_updates">Vérifier les mises à jour</string> <string name="check_for_updates">Vérifier les mises à jour</string>
<string name="feed_new_items">Nouveaux éléments du flux</string> <string name="feed_new_items">Nouveaux éléments du flux</string>
<string name="crash_the_player">Faire planter le lecteur</string> <string name="crash_the_player">Faire planter le lecteur</string>
<string name="show_crash_the_player_title">Afficher « Faire planter le lecteur »</string> <string name="show_crash_the_player_title">Afficher « Faire planter le lecteur »</string>
<string name="show_crash_the_player_summary">Montrer une option de plantage lors de l\'utilisation du lecteur</string> <string name="show_crash_the_player_summary">Montrer une option de plantage lors de l\'utilisation du lecteur</string>
<string name="error_report_channel_name">Notification de rapport d\'erreur</string> <string name="error_report_channel_name">Notification de rapport d\'erreur</string>
<string name="error_report_channel_description">Notifications pour signaler les erreurs</string> <string name="error_report_channel_description">Notifications pour signaler les erreurs</string>
@ -817,7 +817,6 @@
<string name="forward">Avancer</string> <string name="forward">Avancer</string>
<string name="rewind">Rembobiner</string> <string name="rewind">Rembobiner</string>
<string name="replay">Rejouer</string> <string name="replay">Rejouer</string>
<string name="share_playlist_with_titles_message">Partager la liste de lecture avec des détails tel que son nom et le titre de ses vidéos ou simplement la liste des URLs des vidéos</string>
<string name="metadata_uploader_avatars">Avatars du téléverseur</string> <string name="metadata_uploader_avatars">Avatars du téléverseur</string>
<string name="image_quality_summary">Sélectionnez la qualité des images et si les images doivent être chargées, pour réduire l\'utilisation de la mémoire et de données. Les modifications vident à la fois le cache des images en mémoire et sur le disque — %s</string> <string name="image_quality_summary">Sélectionnez la qualité des images et si les images doivent être chargées, pour réduire l\'utilisation de la mémoire et de données. Les modifications vident à la fois le cache des images en mémoire et sur le disque — %s</string>
<string name="play">Lire</string> <string name="play">Lire</string>
@ -827,7 +826,7 @@
<item quantity="other">%s réponses</item> <item quantity="other">%s réponses</item>
</plurals> </plurals>
<string name="notification_actions_summary_android13">Modifiez chaque action de notification ci-dessous en appuyant dessus. Les trois premières actions (lire/pause, précédent, suivant) sont définies par le système et ne peuvent pas être personnalisées.</string> <string name="notification_actions_summary_android13">Modifiez chaque action de notification ci-dessous en appuyant dessus. Les trois premières actions (lire/pause, précédent, suivant) sont définies par le système et ne peuvent pas être personnalisées.</string>
<string name="show_more">Afficher plus</string> <string name="show_more">Voir plus</string>
<string name="show_less">Afficher moins</string> <string name="show_less">Afficher moins</string>
<string name="reset_settings_summary">Réinitialiser tous les paramètres à leurs valeurs par défaut</string> <string name="reset_settings_summary">Réinitialiser tous les paramètres à leurs valeurs par défaut</string>
<string name="no">Non</string> <string name="no">Non</string>
@ -841,4 +840,5 @@
<string name="reset_settings_title">Réinitialiser les paramètres</string> <string name="reset_settings_title">Réinitialiser les paramètres</string>
<string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string> <string name="error_insufficient_storage">Pas assez d\'espace disponible sur l\'appareil</string>
<string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string> <string name="import_settings_vulnerable_format">Les paramètres de l\'export en cours d\'importation utilisent un format vulnérable qui a été déprécié depuis NewPipe 0.27.0. Assurez-vous que l\'export en cours d\'importation provient d\'une source fiable. Privilégiez les exports obtenues à partir de NewPipe 0.27.0 ou des versions plus récentes à l\'avenir. Le support pour l\'importation des paramètres dans ce format vulnérable sera bientôt complètement supprimé et les anciennes versions de NewPipe ne pourront plus importer les paramètres des exports des nouvelles versions.</string>
</resources> <string name="audio_track_type_secondary">secondaire</string>
</resources>

View File

@ -815,10 +815,9 @@
<string name="metadata_banners">Encabezados</string> <string name="metadata_banners">Encabezados</string>
<string name="show_channel_tabs_summary">Lapelas a mostrar nas páxinas das canles</string> <string name="show_channel_tabs_summary">Lapelas a mostrar nas páxinas das canles</string>
<string name="image_quality_summary">Escolla da calidade das imaxes e se cargar as imaxes na súa totalidade, para reducir o uso de datos e memoria. Os cambios limpan a caché das imaxes na memoria e no disco - %s</string> <string name="image_quality_summary">Escolla da calidade das imaxes e se cargar as imaxes na súa totalidade, para reducir o uso de datos e memoria. Os cambios limpan a caché das imaxes na memoria e no disco - %s</string>
<string name="share_playlist_with_titles_message">Compartir a lista de reprodución con detalles como o nome da lista e os títulos dos videos ou como unha lista sinxela cos enlaces URL dos videos</string>
<string name="share_playlist_with_list">Compartir lista de URLs</string> <string name="share_playlist_with_list">Compartir lista de URLs</string>
<string name="import_settings_vulnerable_format">A configuración da exportación a ser importada emprega un formato vulnerable que fica obsoleto dende NewPipe 0.27.0. Comprobe que a exportación que está a importar proveña dunha fonte fiable e preferibelmente empregue exportacións de NewPipe 0.27.0 ou posterior. A compatibilidade coa importación deste formato vulnerable será eliminada por completo próximamente e as versión antigas de NewPipe non poderán importar configuracións de exportacións dende novas versións.</string> <string name="import_settings_vulnerable_format">A configuración da exportación a ser importada emprega un formato vulnerable que fica obsoleto dende NewPipe 0.27.0. Comprobe que a exportación que está a importar proveña dunha fonte fiable e preferibelmente empregue exportacións de NewPipe 0.27.0 ou posterior. A compatibilidade coa importación deste formato vulnerable será eliminada por completo próximamente e as versión antigas de NewPipe non poderán importar configuracións de exportacións dende novas versións.</string>
<string name="channel_tab_tracks">Pistas</string> <string name="channel_tab_tracks">Pistas</string>
<string name="feed_fetch_channel_tabs_summary">Lapelas a recuperar ao actualizar o feed. Esta opción non ten efecto se a canle se actualiza no modo rápido.</string> <string name="feed_fetch_channel_tabs_summary">Lapelas a recuperar ao actualizar o feed. Esta opción non ten efecto se a canle se actualiza no modo rápido.</string>
<string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera os códecs de video e os re-instancia cando muda a máscara, no canto de configurar a máscara directamente no códec. ExoPlayer xa emprega esta configuración nalgúns dispositivos con este problema e só afecta a Android 6 e versións posteriores.\n\nActivar esta opción pode minimizar erros de reprodución ao mudar o reprodutor de video actual ou mudar ao modo de pantalla completa</string> <string name="always_use_exoplayer_set_output_surface_workaround_summary">Esta solución alternativa libera os códecs de video e os re-instancia cando muda a máscara, no canto de configurar a máscara directamente no códec. ExoPlayer xa emprega esta configuración nalgúns dispositivos con este problema e só afecta a Android 6 e versións posteriores.\n\nActivar esta opción pode minimizar erros de reprodución ao mudar o reprodutor de video actual ou mudar ao modo de pantalla completa</string>
</resources> </resources>

View File

@ -3,7 +3,7 @@
<string name="black_theme_title">કાળું</string> <string name="black_theme_title">કાળું</string>
<string name="dark_theme_title">અંધારિયું</string> <string name="dark_theme_title">અંધારિયું</string>
<string name="light_theme_title">પ્રકાશ</string> <string name="light_theme_title">પ્રકાશ</string>
<string name="night_theme_title">નાઇટ થીમ</string> <string name="night_theme_title">રાત્રિ થીમ</string>
<string name="theme_title">થીમ</string> <string name="theme_title">થીમ</string>
<string name="default_video_format_title">ડિફોલ્ટ વિડિઓ ફોર્મેટ</string> <string name="default_video_format_title">ડિફોલ્ટ વિડિઓ ફોર્મેટ</string>
<string name="default_audio_format_title">ડિફોલ્ટ ઓડિયો ફોર્મેટ</string> <string name="default_audio_format_title">ડિફોલ્ટ ઓડિયો ફોર્મેટ</string>
@ -15,16 +15,16 @@
<string name="notification_action_shuffle">શફલ</string> <string name="notification_action_shuffle">શફલ</string>
<string name="notification_action_repeat">પુનરાવર્તન કરો</string> <string name="notification_action_repeat">પુનરાવર્તન કરો</string>
<string name="notification_actions_at_most_three">તમે કોમ્પેક્ટ સૂચનામાં બતાવવા માટે ઓછામાં ઓછી ત્રણ ક્રિયાઓ પસંદ કરી શકો છો!</string> <string name="notification_actions_at_most_three">તમે કોમ્પેક્ટ સૂચનામાં બતાવવા માટે ઓછામાં ઓછી ત્રણ ક્રિયાઓ પસંદ કરી શકો છો!</string>
<string name="notification_actions_summary">નીચેની દરેક સૂચના ક્રિયાને તેના પર ટેપ કરીને સંપાદિત કરો. જમણી બાજુના ચેકબોક્સેસનો ઉપયોગ કરીને કોમ્પેક્ટ સૂચનામાં બતાવવા માટે તેમાંથી ત્રણ સુધી પસંદ કરો</string> <string name="notification_actions_summary">નીચેની દરેક સૂચના ક્રિયાને તેના પર ટેપ કરીને સંપાદિત કરો. જમણી બાજુના ચેકબોક્સેસનો ઉપયોગ કરીને કોમ્પેક્ટ સૂચનામાં બતાવવા માટે તેમાંથી ત્રણ સુધી પસંદ કરો.</string>
<string name="notification_action_4_title">પાંચમો ક્રિયા બટન</string> <string name="notification_action_4_title">પાંચમો ક્રિયા બટન</string>
<string name="notification_action_3_title">ચોથું ક્રિયા બટન</string> <string name="notification_action_3_title">ચોથું ક્રિયા બટન</string>
<string name="notification_action_2_title">ત્રીજી ક્રિયા બટન</string> <string name="notification_action_2_title">ત્રીજી ક્રિયા બટન</string>
<string name="notification_action_1_title">બીજું ક્રિયા બટન</string> <string name="notification_action_1_title">બીજું ક્રિયા બટન</string>
<string name="notification_action_0_title">પ્રથમ ક્રિયા બટન</string> <string name="notification_action_0_title">પ્રથમ ક્રિયા બટન</string>
<string name="notification_scale_to_square_image_summary">સૂચનામાં 16: 9 થી 1: 1 અસ્પેક્ટ રેશિયોમાં બતાવેલ વિડિઓ થંબનેલને સ્કેલ કરો (વિકૃતિ રજૂ કરી શકે છે)</string> <string name="notification_scale_to_square_image_summary">સૂચનામાં બતાવેલ વિડિઓ થંબનેલને ૧૬:૯ થી ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપો</string>
<string name="notification_scale_to_square_image_title">સ્કેલ થંબનેલથી 1: 1 પાસા રેશિય</string> <string name="notification_scale_to_square_image_title">થંબનેલને ૧:૧ સાપેક્ષ ગુણોત્તરમાં કાપ</string>
<string name="show_play_with_kodi_summary">કોડ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો</string> <string name="show_play_with_kodi_summary">કોડિ મીડિયા સેન્ટર દ્વારા વિડિઓ ચલાવવાનો વિકલ્પ દર્શાવો</string>
<string name="kore_not_found">અનુપસ્થિત Kode એપ્લિકેશન ઇન્સ્ટોલ કરીએ\?</string> <string name="kore_not_found">અનુપસ્થિત Kore અનુપ્રયોગ સ્થાપિત કરીએ?</string>
<string name="show_higher_resolutions_summary">ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે</string> <string name="show_higher_resolutions_summary">ફક્ત થોડા ઉપકરણો 2K / 4K વિડિઓઝ ચલાવી શકે છે</string>
<string name="show_higher_resolutions_title">ઉચ્ચ રીઝોલ્યુશન બતાવો</string> <string name="show_higher_resolutions_title">ઉચ્ચ રીઝોલ્યુશન બતાવો</string>
<string name="default_popup_resolution_title">ડિફોલ્ટ પોપઅપ રીઝોલ્યુશન</string> <string name="default_popup_resolution_title">ડિફોલ્ટ પોપઅપ રીઝોલ્યુશન</string>
@ -40,7 +40,7 @@
<string name="controls_background_title">પૃષ્ઠભૂમિ</string> <string name="controls_background_title">પૃષ્ઠભૂમિ</string>
<string name="tab_choose">ટેબ પસંદ કરો</string> <string name="tab_choose">ટેબ પસંદ કરો</string>
<string name="tab_bookmarks">બુકમાર્ક કરેલ પ્લેલિસ્ટ્સ</string> <string name="tab_bookmarks">બુકમાર્ક કરેલ પ્લેલિસ્ટ્સ</string>
<string name="tab_subscriptions">સબ્સ્ક્રિપ્શન્સ</string> <string name="tab_subscriptions">લવાજમઓ</string>
<string name="show_info">માહિતી બતાવો</string> <string name="show_info">માહિતી બતાવો</string>
<string name="subscription_update_failed">સબ્સ્ક્રિપ્શન અપડેટ કરી શકાયું નથી</string> <string name="subscription_update_failed">સબ્સ્ક્રિપ્શન અપડેટ કરી શકાયું નથી</string>
<string name="subscription_change_failed">સબ્સ્ક્રિપ્શન બદલી શકાયું નહીં</string> <string name="subscription_change_failed">સબ્સ્ક્રિપ્શન બદલી શકાયું નહીં</string>
@ -68,4 +68,19 @@
<string name="no_player_found">કોઈ સ્ટ્રીમ પ્લેયર મળ્યો નથી. વીએલસી સ્થાપિત કરીએ\?</string> <string name="no_player_found">કોઈ સ્ટ્રીમ પ્લેયર મળ્યો નથી. વીએલસી સ્થાપિત કરીએ\?</string>
<string name="upload_date_text">%1$s પર પ્રકાશિત</string> <string name="upload_date_text">%1$s પર પ્રકાશિત</string>
<string name="main_bg_subtitle">પ્રારંભ કરવા માટે વિપુલ - દર્શક કાચને ટેપ કરો.</string> <string name="main_bg_subtitle">પ્રારંભ કરવા માટે વિપુલ - દર્શક કાચને ટેપ કરો.</string>
</resources> <string name="mark_as_watched">જોયેલું તરીકે ચિહ્નિત કરો</string>
<string name="ok">ઠીક છે</string>
<string name="yes">હા</string>
<string name="no">ના</string>
<string name="trending">વલણમાંનાં</string>
<string name="auto_queue_toggle">આપોઆપ કતારબદ્ધતા</string>
<string name="crash_the_player">પ્લેયરને ક્રેશ કરો</string>
<string name="action_history">ઇતિહાસ</string>
<string name="play_with_kodi_title">કોટિથી ચલાવો</string>
<string name="show_play_with_kodi_title">કોડિથી ચલાવવાનો વિકલ્પ દેખાડો</string>
<string name="download_dialog_title">ડાઉનલોડ કરો</string>
<string name="autoplay_title">આપમેળે ચલાવો</string>
<string name="fragment_feed_title">નવું શું છે</string>
<string name="downloads">ડાઉનલોડ્સ</string>
<string name="downloads_title">ડાઉનલોડ્સ</string>
</resources>

View File

@ -830,7 +830,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">שיתוף רשימת נגינה</string> <string name="share_playlist">שיתוף רשימת נגינה</string>
<string name="share_playlist_with_titles_message">שיתוף רשימת נגינה עם פרטים כגון שם רשימת נגינה וכותרות סרטונים או כרשימה פשוטה של כתובות סרטונים</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="show_more">להציג עוד</string> <string name="show_more">להציג עוד</string>
<string name="show_less">להציג פחות</string> <string name="show_less">להציג פחות</string>
@ -852,4 +851,5 @@
\nלהמשיך?</string> \nלהמשיך?</string>
<string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string> <string name="error_insufficient_storage">אין מספיק מקום פנוי במכשיר</string>
<string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string> <string name="import_settings_vulnerable_format">ההגדרות בייצוא המיובא משתמשות בתסדיר פגיע שהוצא משימוש מאז NewPipe 0.27.0. יש לוודא שהייצוא המיובא הוא ממקור מהימן, ועדיף להשתמש רק בייצוא שהושג מ־NewPipe 0.27.0 ומעלה בעתיד. תמיכה בייבוא הגדרות בתסדיר פגיע זה תוסר בקרוב לחלוטין, ואז גרסאות ישנות של NewPipe לא יוכלו לייבא עוד הגדרות של ייצוא מגרסאות חדשות.</string>
</resources> <string name="audio_track_type_secondary">משני</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="upload_date_text">%1$s पे प्रकाशित हुआ</string> <string name="upload_date_text">%1$s पे प्रकाशित हुआ</string>
<string name="no_player_found">स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप वीएलसी इंस्टॉल करना चाहेंगे\?</string> <string name="no_player_found">स्ट्रीमिंग के लिए प्लेयर नहीं मिला। क्या आप VLC इंस्टॉल करना चाहेंगे?</string>
<string name="install">इंस्टॉल करें</string> <string name="install">इंस्टॉल करें</string>
<string name="open_in_browser">ब्राउज़र में खोलें</string> <string name="open_in_browser">ब्राउज़र में खोलें</string>
<string name="open_in_popup_mode">पॉपअप मोड में खोलें</string> <string name="open_in_popup_mode">पॉपअप मोड में खोलें</string>
@ -183,7 +183,7 @@
<string name="hold_to_append">कतार में जोड़ने के लिए दबाकर रखें</string> <string name="hold_to_append">कतार में जोड़ने के लिए दबाकर रखें</string>
<string name="start_here_on_background">बैकग्राउंड में चलाना शुरू करें</string> <string name="start_here_on_background">बैकग्राउंड में चलाना शुरू करें</string>
<string name="start_here_on_popup">पॉपअप में चलाना शुरू करें</string> <string name="start_here_on_popup">पॉपअप में चलाना शुरू करें</string>
<string name="no_player_found_toast">स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए वीएलसी प्लेयर इंस्टॉल कर सकते हैं)।</string> <string name="no_player_found_toast">स्ट्रीमिंग करने के लिए प्लेयर नहीं मिला (आप इसे चलाने के लिए VLC प्लेयर इंस्टॉल कर सकते हैं)।</string>
<string name="controls_download_desc">स्ट्रीम फाइल डाउनलोड करें</string> <string name="controls_download_desc">स्ट्रीम फाइल डाउनलोड करें</string>
<string name="show_info">जानकारी दिखाएं</string> <string name="show_info">जानकारी दिखाएं</string>
<string name="tab_bookmarks">बुकमार्क की गई प्लेलिस्टें</string> <string name="tab_bookmarks">बुकमार्क की गई प्लेलिस्टें</string>
@ -804,7 +804,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">प्लेलिस्ट साझा करें</string> <string name="share_playlist">प्लेलिस्ट साझा करें</string>
<string name="share_playlist_with_titles_message">प्लेलिस्ट को प्लेलिस्ट नाम और वीडियो शीर्षक जैसे विवरण के साथ या वीडियो यूआरएल की एक सरल सूची के रूप में साझा करें</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="one">%s जवाब</item> <item quantity="one">%s जवाब</item>
@ -825,4 +824,5 @@
<string name="error_insufficient_storage">डिवाइस पर पर्याप्त खाली स्थान नहीं है</string> <string name="error_insufficient_storage">डिवाइस पर पर्याप्त खाली स्थान नहीं है</string>
<string name="settings_category_backup_restore_title">बैकअप और रिस्टोर</string> <string name="settings_category_backup_restore_title">बैकअप और रिस्टोर</string>
<string name="import_settings_vulnerable_format">आयात किए जा रहे निर्यात में सेटिंग्स एक कमजोर प्रारूप का उपयोग करती हैं जिसे न्यूपाइप 0.27.0 के बाद से हटा दिया गया था। सुनिश्चित करें कि आयात किया जा रहा निर्यात किसी विश्वसनीय स्रोत से है, और भविष्य में केवल न्यूपाइप 0.27.0 या नए से प्राप्त निर्यात का उपयोग करना पसंद करें। इस असुरक्षित प्रारूप में सेटिंग्स आयात करने के लिए समर्थन जल्द ही पूरी तरह से हटा दिया जाएगा, और फिर न्यूपाइप के पुराने संस्करण अब नए संस्करणों से निर्यात की सेटिंग्स आयात नहीं कर पाएंगे।</string> <string name="import_settings_vulnerable_format">आयात किए जा रहे निर्यात में सेटिंग्स एक कमजोर प्रारूप का उपयोग करती हैं जिसे न्यूपाइप 0.27.0 के बाद से हटा दिया गया था। सुनिश्चित करें कि आयात किया जा रहा निर्यात किसी विश्वसनीय स्रोत से है, और भविष्य में केवल न्यूपाइप 0.27.0 या नए से प्राप्त निर्यात का उपयोग करना पसंद करें। इस असुरक्षित प्रारूप में सेटिंग्स आयात करने के लिए समर्थन जल्द ही पूरी तरह से हटा दिया जाएगा, और फिर न्यूपाइप के पुराने संस्करण अब नए संस्करणों से निर्यात की सेटिंग्स आयात नहीं कर पाएंगे।</string>
</resources> <string name="audio_track_type_secondary">सेकेंडरी</string>
</resources>

View File

@ -424,8 +424,8 @@
<string name="feed_group_dialog_delete_message">Želiš li izbrisati ovu grupu\?</string> <string name="feed_group_dialog_delete_message">Želiš li izbrisati ovu grupu\?</string>
<string name="feed_create_new_group_button_title">Nova</string> <string name="feed_create_new_group_button_title">Nova</string>
<string name="feed_update_threshold_option_always_update">Uvijek aktualiziraj</string> <string name="feed_update_threshold_option_always_update">Uvijek aktualiziraj</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi način</string> <string name="feed_use_dedicated_fetch_method_enable_button">Uključi brzi modus</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi način</string> <string name="feed_use_dedicated_fetch_method_disable_button">Isključi brzi modus</string>
<string name="error_insufficient_storage_left">Memorija uređaja je popunjena</string> <string name="error_insufficient_storage_left">Memorija uređaja je popunjena</string>
<string name="most_liked">Najomiljeniji</string> <string name="most_liked">Najomiljeniji</string>
<string name="subtitle_activity_recaptcha">Pritisni „Gotovo” kad je riješeno</string> <string name="subtitle_activity_recaptcha">Pritisni „Gotovo” kad je riješeno</string>
@ -639,7 +639,7 @@
<string name="no_dir_yet">Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje</string> <string name="no_dir_yet">Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje</string>
<string name="comments_are_disabled">Komentari su isključeni</string> <string name="comments_are_disabled">Komentari su isključeni</string>
<string name="mark_as_watched">Označi kao pogledano</string> <string name="mark_as_watched">Označi kao pogledano</string>
<string name="feed_load_error_fast_unknown">Način rada brzog feeda ne pruža više informacija o ovome.</string> <string name="feed_load_error_fast_unknown">Brzi modus feeda ne pruža više informacija o ovome.</string>
<string name="metadata_privacy_internal">Interno</string> <string name="metadata_privacy_internal">Interno</string>
<string name="metadata_privacy">Privatnost</string> <string name="metadata_privacy">Privatnost</string>
<string name="description_select_note">Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta.</string> <string name="description_select_note">Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta.</string>
@ -800,7 +800,6 @@
<string name="image_quality_medium">Srednja kvaliteta</string> <string name="image_quality_medium">Srednja kvaliteta</string>
<string name="image_quality_high">Visoka kvaliteta</string> <string name="image_quality_high">Visoka kvaliteta</string>
<string name="question_mark">\?</string> <string name="question_mark">\?</string>
<string name="share_playlist_with_titles_message">Dijeli playlistu s detaljima kao što su ime playliste i naslovi videa ili kao jednostavan popis URL-ova videa</string>
<string name="share_playlist_with_titles">Dijeli s naslovima</string> <string name="share_playlist_with_titles">Dijeli s naslovima</string>
<string name="share_playlist_with_list">Dijeli popis URL-ova</string> <string name="share_playlist_with_list">Dijeli popis URL-ova</string>
<string name="video_details_list_item"> %1$s: %2$s</string> <string name="video_details_list_item"> %1$s: %2$s</string>
@ -833,4 +832,7 @@
<string name="reset_all_settings">Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti. <string name="reset_all_settings">Obnavljanje svih postavki odbacit će sve tvoje postavljene postavke i aplikacija će se ponovo pokrenuti.
\n \n
\nStvarno želiš nastaviti?</string> \nStvarno želiš nastaviti?</string>
</resources> <string name="always_use_exoplayer_set_output_surface_workaround_title">Uvijek koristi ExoPlayer postavku zaobilaženja videa za izlaznu površinu</string>
<string name="feed_fetch_channel_tabs_summary">Kartice za dohvaćanje prilikom aktualiziranja feeda. Ova opcija nema učinka ako se kanal aktualizira pomoću brzog modusa.</string>
<string name="audio_track_type_secondary">sekundarno</string>
</resources>

View File

@ -324,7 +324,7 @@
<string name="app_update_notification_channel_name">Alkalmazásfrissítés értesítése</string> <string name="app_update_notification_channel_name">Alkalmazásfrissítés értesítése</string>
<string name="file_deleted">Fájl törölve</string> <string name="file_deleted">Fájl törölve</string>
<string name="settings_category_updates_title">Frissítések</string> <string name="settings_category_updates_title">Frissítések</string>
<string name="show_hold_to_append_summary">Tipp megjelenítése, ha megnyomja a hátteret vagy a felugró gombot a videó részleteinél</string> <string name="show_hold_to_append_summary">Tipp megjelenítése a háttér vagy a felugró gomb megnyomásakor a videó „Részletek:” lehetőségnél</string>
<string name="autoplay_title">Automatikus lejátszás</string> <string name="autoplay_title">Automatikus lejátszás</string>
<string name="settings_category_clear_data_title">Adatok törlése</string> <string name="settings_category_clear_data_title">Adatok törlése</string>
<string name="enable_playback_state_lists_summary">Lejátszási pozíciók megjelenítése a listákban</string> <string name="enable_playback_state_lists_summary">Lejátszási pozíciók megjelenítése a listákban</string>
@ -534,7 +534,7 @@
<string name="hash_channel_description">Értesítések a videók ujjlenyomatkészítési folyamatához</string> <string name="hash_channel_description">Értesítések a videók ujjlenyomatkészítési folyamatához</string>
<string name="hash_channel_name">Videó ujjlenyomat-készítési értesítése</string> <string name="hash_channel_name">Videó ujjlenyomat-készítési értesítése</string>
<string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string> <string name="youtube_restricted_mode_enabled_summary">A YouTube biztosít egy „Korlátozott módot”, amely elrejti a lehetséges felnőtteknek szóló tartalmat</string>
<string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott módjának” bekapcsolása</string> <string name="youtube_restricted_mode_enabled_title">A YouTube „Korlátozott mód” bekapcsolása</string>
<string name="peertube_instance_add_exists">A példány már létezik</string> <string name="peertube_instance_add_exists">A példány már létezik</string>
<string name="peertube_instance_add_fail">A példány érvényesítése nem sikerült</string> <string name="peertube_instance_add_fail">A példány érvényesítése nem sikerült</string>
<string name="peertube_instance_add_help">Adja meg a példány webcímét</string> <string name="peertube_instance_add_help">Adja meg a példány webcímét</string>
@ -658,7 +658,7 @@
<string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string> <string name="show_original_time_ago_summary">A szolgáltatásokból származó eredeti szövegek láthatók lesznek a közvetítési elemeken</string>
<string name="crash_the_player">Lejátszó összeomlasztása</string> <string name="crash_the_player">Lejátszó összeomlasztása</string>
<string name="show_image_indicators_title">Képjelölők megjelenítése</string> <string name="show_image_indicators_title">Képjelölők megjelenítése</string>
<string name="show_crash_the_player_title">A „lejátszó összeomlasztása” lehetőség megjelenítése</string> <string name="show_crash_the_player_title">A „Lejátszó összeomlasztása” lehetőség megjelenítése</string>
<string name="show_crash_the_player_summary">Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor</string> <string name="show_crash_the_player_summary">Megjeleníti az összeomlasztási lehetőséget a lejátszó használatakor</string>
<string name="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string> <string name="unhook_checkbox">Hangmagasság megtartása (torzítást okozhat)</string>
<string name="check_for_updates">Frissítések keresése</string> <string name="check_for_updates">Frissítések keresése</string>
@ -796,7 +796,6 @@
<string name="image_quality_high">Magas minőségű</string> <string name="image_quality_high">Magas minőségű</string>
<string name="question_mark">\?</string> <string name="question_mark">\?</string>
<string name="share_playlist">Lejátszási lista megosztása</string> <string name="share_playlist">Lejátszási lista megosztása</string>
<string name="share_playlist_with_titles_message">Lejátszási lista megosztása olyan részletekkel, mint például a lejátszási lista neve és a videó címe, vagy a videó webcímek egyszerű listájaként</string>
<string name="share_playlist_with_titles">Megosztás címekkel</string> <string name="share_playlist_with_titles">Megosztás címekkel</string>
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
@ -825,4 +824,5 @@
\n \n
\nBiztosan folytatja?</string> \nBiztosan folytatja?</string>
<string name="import_settings_vulnerable_format">Az importálandó exportban lévő beállítások sérülékeny formátumot használnak, amely a NewPipe 0.27.0-s verziója óta elavult. Győződjön meg arról, hogy megbízható forrásból importálja, és a jövőben csak a NewPipe 0.27.0-s vagy újabb verziójából származó exportokat használjon. A beállítások ebből a sérülékeny forrásból történő importálása hamarosan végleg el lesz távolítva, és a NewPipe régi verziói nem fogják tudni importálni az újabb verziókból származó exportokat.</string> <string name="import_settings_vulnerable_format">Az importálandó exportban lévő beállítások sérülékeny formátumot használnak, amely a NewPipe 0.27.0-s verziója óta elavult. Győződjön meg arról, hogy megbízható forrásból importálja, és a jövőben csak a NewPipe 0.27.0-s vagy újabb verziójából származó exportokat használjon. A beállítások ebből a sérülékeny forrásból történő importálása hamarosan végleg el lesz távolítva, és a NewPipe régi verziói nem fogják tudni importálni az újabb verziókból származó exportokat.</string>
</resources> <string name="audio_track_type_secondary">másodlagos</string>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="main_bg_subtitle">Սեղմեք որոնման կոճակը որ սկսել</string> <string name="main_bg_subtitle">Սեղմեք խոշորացույցը որ սկսեք</string>
<string name="search">Որոնել</string> <string name="search">Որոնել</string>
<string name="downloads">Բեռնված</string> <string name="downloads">Բեռնված</string>
<string name="downloads_title">Բեռնված</string> <string name="downloads_title">Բեռնված</string>
@ -228,7 +228,7 @@
<string name="sort">Դասավորել</string> <string name="sort">Դասավորել</string>
<string name="detail_pinned_comment_view_description">Գամված մեկնաբանություն</string> <string name="detail_pinned_comment_view_description">Գամված մեկնաբանություն</string>
<string name="account_terminated">Հաշիվը կասեցված է</string> <string name="account_terminated">Հաշիվը կասեցված է</string>
<string name="channel_tab_about"></string> <string name="channel_tab_about"/>
<string name="channel_tab_albums">Ալբոմներ</string> <string name="channel_tab_albums">Ալբոմներ</string>
<string name="yes">Այո</string> <string name="yes">Այո</string>
<string name="no">Ոչ</string> <string name="no">Ոչ</string>
@ -241,4 +241,6 @@
<string name="channel_tab_channels">Ալիքներ</string> <string name="channel_tab_channels">Ալիքներ</string>
<string name="channel_tab_livestreams">Ուղիղ</string> <string name="channel_tab_livestreams">Ուղիղ</string>
<string name="unknown_audio_track">Անհայտ</string> <string name="unknown_audio_track">Անհայտ</string>
</resources> <string name="did_you_mean">Նկատի ունե՞ս «%1$s»</string>
<string name="volume">Բարձրություն</string>
</resources>

View File

@ -252,4 +252,4 @@
<string name="settings_category_player_notification_summary">Configurar le notification del fluxo in reproduction</string> <string name="settings_category_player_notification_summary">Configurar le notification del fluxo in reproduction</string>
<string name="settings_category_player_notification_title">Notification de reproductor</string> <string name="settings_category_player_notification_title">Notification de reproductor</string>
<string name="settings_category_backup_restore_title">Facer un copia de securitate e restaurar</string> <string name="settings_category_backup_restore_title">Facer un copia de securitate e restaurar</string>
</resources> </resources>

View File

@ -138,7 +138,7 @@
<string name="auto_queue_summary">Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait</string> <string name="auto_queue_summary">Melanjutkan akhir dari antrean pemutaran (tak berulang) dengan menambahkan video terkait</string>
<string name="enable_watch_history_summary">Simpan daftar video yang telah ditonton</string> <string name="enable_watch_history_summary">Simpan daftar video yang telah ditonton</string>
<string name="show_hold_to_append_title">Tip \"Tahan untuk menambahkan\"</string> <string name="show_hold_to_append_title">Tip \"Tahan untuk menambahkan\"</string>
<string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam \"Detail:\" video</string> <string name="show_hold_to_append_summary">Tampilkan tip ketika menekan tombol latar belakang atau popup di dalam video \"Detail:\"</string>
<string name="default_content_country_title">Lokasi Konten</string> <string name="default_content_country_title">Lokasi Konten</string>
<string name="settings_category_player_title">Pemutar</string> <string name="settings_category_player_title">Pemutar</string>
<string name="settings_category_player_behavior_title">Perilaku</string> <string name="settings_category_player_behavior_title">Perilaku</string>
@ -508,7 +508,7 @@
\nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi.</string> \nJadi pilihlah yang sesuai yang Anda inginkan: kecepatan atau kelengkapan informasi.</string>
<string name="show_original_time_ago_summary">Teks asli dari layanan akan ditampilkan di dalam video</string> <string name="show_original_time_ago_summary">Teks asli dari layanan akan ditampilkan di dalam video</string>
<string name="show_original_time_ago_title">Tampilkan waktu yang lalu sebenarnya pada item</string> <string name="show_original_time_ago_title">Tampilkan waktu yang lalu sebenarnya pada item</string>
<string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\" YouTube</string> <string name="youtube_restricted_mode_enabled_title">Aktifkan \"Mode Terbatas\"</string>
<string name="video_detail_by">Oleh %s</string> <string name="video_detail_by">Oleh %s</string>
<string name="channel_created_by">Dibuat oleh %s</string> <string name="channel_created_by">Dibuat oleh %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Thumbnail avatar channel</string> <string name="detail_sub_channel_thumbnail_view_description">Thumbnail avatar channel</string>
@ -790,7 +790,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Bagikan Daftar Putar</string> <string name="share_playlist">Bagikan Daftar Putar</string>
<string name="share_playlist_with_titles_message">Bagikan daftar putar dengan detail seperti nama daftar putar dan judul video atau sebagai daftar video URL yang sederhana</string>
<string name="metadata_banners">Panji</string> <string name="metadata_banners">Panji</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<string name="notification_actions_summary_android13">Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi.</string> <string name="notification_actions_summary_android13">Sentuh untuk menyunting tindakan notifikasi di bawah. Tiga tindakan pertama (mainkan/jeda, sebelumnya dan selanjutnya) disetel oleh sistem dan tidak bisa dikustomisasi.</string>
@ -811,4 +810,5 @@
\nApakah Anda ingin mengaktifkan ini?</string> \nApakah Anda ingin mengaktifkan ini?</string>
<string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string> <string name="settings_category_backup_restore_title">Cadangkan dan pulihkan</string>
<string name="import_settings_vulnerable_format">Pengaturan dalam ekspor yang diimpor menggunakan format rentan yang tidak digunakan lagi sejak NewPipe 0.27.0. Pastikan ekspor yang diimpor berasal dari sumber tepercaya, dan lebih memilih hanya menggunakan ekspor yang diperoleh dari NewPipe 0.27.0 atau yang lebih baru di masa mendatang. Dukungan untuk mengimpor pengaturan dalam format rentan ini akan segera dihapus sepenuhnya, dan NewPipe versi lama tidak akan dapat lagi mengimpor pengaturan ekspor dari versi baru.</string> <string name="import_settings_vulnerable_format">Pengaturan dalam ekspor yang diimpor menggunakan format rentan yang tidak digunakan lagi sejak NewPipe 0.27.0. Pastikan ekspor yang diimpor berasal dari sumber tepercaya, dan lebih memilih hanya menggunakan ekspor yang diperoleh dari NewPipe 0.27.0 atau yang lebih baru di masa mendatang. Dukungan untuk mengimpor pengaturan dalam format rentan ini akan segera dihapus sepenuhnya, dan NewPipe versi lama tidak akan dapat lagi mengimpor pengaturan ekspor dari versi baru.</string>
</resources> <string name="audio_track_type_secondary">sekunder</string>
</resources>

View File

@ -502,7 +502,7 @@
<string name="feed_load_error">Villa við að hlaða streymi</string> <string name="feed_load_error">Villa við að hlaða streymi</string>
<string name="feed_load_error_account_info">Gat ekki hlaðið streymi fyrir %s.</string> <string name="feed_load_error_account_info">Gat ekki hlaðið streymi fyrir %s.</string>
<string name="feed_use_dedicated_fetch_method_summary">Fáanlegt fyrir sumar þjónustur. Það er venjulega miklu hraðari en getur skilað ófullnægjandi upplýsingum (t.d. engin lengd, tegund, staða beinna útsendinga)</string> <string name="feed_use_dedicated_fetch_method_summary">Fáanlegt fyrir sumar þjónustur. Það er venjulega miklu hraðari en getur skilað ófullnægjandi upplýsingum (t.d. engin lengd, tegund, staða beinna útsendinga)</string>
<string name="feed_use_dedicated_fetch_method_help_text">Ef áskriftir eru uppfærðar of hægt máttu prófa hraðstreymissham (kveiktu á honum í stillingunum eða með hnappnum hér að neðan).\n\nNewPipe getur uppfært áskriftir á tvo vegu:\n• Sækja alla áskriftarrásina - hægvirkt en veitir allar upplýsingar.\n• Nota sérstök forritaskil - fljótvirkt en upplýsingar verða minni.\n\nHraðstreymishamur getur veitt ófullnægjandi upplýsingar, t.d. engin lengd, tegund, staða og getur skilað færri atriðum.\n\nYouTube er dæmi um þjónustu sem býður upp á þetta með RSS-streymi sínu.\n\nValið er því á milli hraða eða nákvæmra upplýsinga.</string> <string name="feed_use_dedicated_fetch_method_help_text">Ef áskriftir eru uppfærðar of hægt máttu prófa hraðstreymissham (kveiktu á honum í stillingunum eða með hnappnum hér að neðan). \n \nNewPipe getur uppfært áskriftir á tvo vegu: \n• Sækja alla áskriftarrásina - hægvirkt en veitir allar upplýsingar. \n• Nota sérstök forritaskil - fljótvirkt en upplýsingar verða minni. \n \nHraðstreymishamur getur veitt ófullnægjandi upplýsingar, t.d. engin lengd, tegund, staða og getur skilað færri atriðum. \n \nYouTube er dæmi um þjónustu sem býður upp á þetta með RSS-streymi sínu. \n \nValið er því á milli hraða eða nákvæmra upplýsinga.</string>
<string name="detail_sub_channel_thumbnail_view_description">Smámynd rásar</string> <string name="detail_sub_channel_thumbnail_view_description">Smámynd rásar</string>
<string name="soundcloud_go_plus_content">Þetta er SoundCloud Go+ lag að minnsta kosti í þínu landi — það er ekki hægt að streyma því eða sækja með NewPipe.</string> <string name="soundcloud_go_plus_content">Þetta er SoundCloud Go+ lag að minnsta kosti í þínu landi — það er ekki hægt að streyma því eða sækja með NewPipe.</string>
<string name="youtube_music_premium_content">Þetta myndskeið er aðeins í boði fyrir YouTube Tónlist Premium meðlimi, það er ekki hægt að streyma því eða sækja með NewPipe.</string> <string name="youtube_music_premium_content">Þetta myndskeið er aðeins í boði fyrir YouTube Tónlist Premium meðlimi, það er ekki hægt að streyma því eða sækja með NewPipe.</string>
@ -607,7 +607,7 @@
<string name="enable_playback_state_lists_title">Spilunarstöður í listum</string> <string name="enable_playback_state_lists_title">Spilunarstöður í listum</string>
<string name="enable_playback_state_lists_summary">Sýna spilunarstöður í listum</string> <string name="enable_playback_state_lists_summary">Sýna spilunarstöður í listum</string>
<string name="show_hold_to_append_title">Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“</string> <string name="show_hold_to_append_title">Sýna ábendinguna „Haltu niðri til að bæta við spilunarröð“</string>
<string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn í „Nánar:“ á myndskeiðinu</string> <string name="show_hold_to_append_summary">Sýna ábendingu þegar ýtt er á bakgrunninn eða sprettihnappinn á myndskeiðinu í „Nánar:\"</string>
<string name="unsupported_url_dialog_message">Óþekkt slóð. Opna með öðru forriti\?</string> <string name="unsupported_url_dialog_message">Óþekkt slóð. Opna með öðru forriti\?</string>
<string name="peertube_instance_url_summary">Veldu uppáhalds PeerTube tilvik þín</string> <string name="peertube_instance_url_summary">Veldu uppáhalds PeerTube tilvik þín</string>
<string name="peertube_instance_url_help">Þú mátt finna tilviki á %s</string> <string name="peertube_instance_url_help">Þú mátt finna tilviki á %s</string>
@ -647,7 +647,7 @@
<string name="disable_media_tunneling_summary">Slökktu á margmiðlunargöngum (media tunneling) ef vart verður við svartan skjá eða hökt við spilun myndskeiða.</string> <string name="disable_media_tunneling_summary">Slökktu á margmiðlunargöngum (media tunneling) ef vart verður við svartan skjá eða hökt við spilun myndskeiða.</string>
<string name="show_image_indicators_title">Sýna myndvísa</string> <string name="show_image_indicators_title">Sýna myndvísa</string>
<string name="show_image_indicators_summary">Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni</string> <string name="show_image_indicators_summary">Sýna Picasso litaða borða ofan á myndum sem gefa til kynna uppruna þeirra: rauðan fyrir netið, bláan fyrir disk og grænan fyrir minni</string>
<string name="show_crash_the_player_title">Sýna „Hrynja spilara“</string> <string name="show_crash_the_player_title">Sýna „Láta spilara hrynja\"</string>
<string name="show_crash_the_player_summary">Sýna valkost til að hrynja spilara</string> <string name="show_crash_the_player_summary">Sýna valkost til að hrynja spilara</string>
<string name="crash_the_app">Hrynja forrit</string> <string name="crash_the_app">Hrynja forrit</string>
<string name="create_error_notification">а til villutilkynningu</string> <string name="create_error_notification">а til villutilkynningu</string>
@ -730,7 +730,7 @@
<string name="feed_show_upcoming">Á næstunni</string> <string name="feed_show_upcoming">Á næstunni</string>
<string name="channel_tab_videos">Myndskeið</string> <string name="channel_tab_videos">Myndskeið</string>
<string name="channel_tab_tracks">Spor</string> <string name="channel_tab_tracks">Spor</string>
<string name="channel_tab_shorts">Stuttmyndir</string> <string name="channel_tab_shorts">Símamyndir</string>
<string name="channel_tab_playlists">Spilunarlistar</string> <string name="channel_tab_playlists">Spilunarlistar</string>
<string name="channel_tab_albums">Albúm</string> <string name="channel_tab_albums">Albúm</string>
<string name="channel_tab_about">Um hugbúnaðinn</string> <string name="channel_tab_about">Um hugbúnaðinn</string>
@ -792,7 +792,6 @@
<string name="unset_playlist_thumbnail">Losa varanlega smámynd</string> <string name="unset_playlist_thumbnail">Losa varanlega smámynd</string>
<string name="notification_actions_summary_android13">Breyttu hverri tilkynningu hér fyrir neðan með því að ýta á hana. Fyrstu þrjár aðgerðirnar (spila/bíða, fyrra og næsta) eru skilgreindar af kerfinu og er því ekki hægt að sérsníða.</string> <string name="notification_actions_summary_android13">Breyttu hverri tilkynningu hér fyrir neðan með því að ýta á hana. Fyrstu þrjár aðgerðirnar (spila/bíða, fyrra og næsta) eru skilgreindar af kerfinu og er því ekki hægt að sérsníða.</string>
<string name="feed_fetch_channel_tabs_summary">Flipar sem á að sækja við uppfærslu þessa streymis. Þetta hefur engin áhrif ef rás er uppfærð með hraðstreymisham.</string> <string name="feed_fetch_channel_tabs_summary">Flipar sem á að sækja við uppfærslu þessa streymis. Þetta hefur engin áhrif ef rás er uppfærð með hraðstreymisham.</string>
<string name="share_playlist_with_titles_message">Deildu spilunarlista með atriðum eins og heiti spilunarlistans og titlum myndskeiða eða sem einföldum lista yfir slóðir á myndskeið</string>
<string name="use_exoplayer_decoder_fallback_title">Nota varaeiginleika ExoPlayer-afkóðarans</string> <string name="use_exoplayer_decoder_fallback_title">Nota varaeiginleika ExoPlayer-afkóðarans</string>
<string name="new_seek_duration_toast">Vegna takmarkana í ExoPlayer-spilaranum var tímalengd hoppa sett á %d sekúndur</string> <string name="new_seek_duration_toast">Vegna takmarkana í ExoPlayer-spilaranum var tímalengd hoppa sett á %d sekúndur</string>
<string name="disable_media_tunneling_automatic_info">Margmiðlunargöng (media tunneling) voru gerð óvirk á tækinu þínu þar sem þessi gerð tækja er þekkt fyrir að styðja ekki þennan eiginleika.</string> <string name="disable_media_tunneling_automatic_info">Margmiðlunargöng (media tunneling) voru gerð óvirk á tækinu þínu þar sem þessi gerð tækja er þekkt fyrir að styðja ekki þennan eiginleika.</string>
@ -804,4 +803,5 @@
<string name="main_tabs_position_summary">Færa veljara aðalflipa neðst</string> <string name="main_tabs_position_summary">Færa veljara aðalflipa neðst</string>
<string name="show_error_snackbar">Sýna villustiku</string> <string name="show_error_snackbar">Sýna villustiku</string>
<string name="image_quality_summary">Veldu gæði mynda og hvort eigi að hlaða myndum inn yfirhöfuð, til að minnka notun gagna og minnis. Breytingar munu hreinsa bæði vinnsluminni og diskminni - %s</string> <string name="image_quality_summary">Veldu gæði mynda og hvort eigi að hlaða myndum inn yfirhöfuð, til að minnka notun gagna og minnis. Breytingar munu hreinsa bæði vinnsluminni og diskminni - %s</string>
</resources> <string name="audio_track_type_secondary">auka</string>
</resources>

View File

@ -96,8 +96,8 @@
<string name="show_higher_resolutions_title">Mostra risoluzioni più elevate</string> <string name="show_higher_resolutions_title">Mostra risoluzioni più elevate</string>
<string name="show_higher_resolutions_summary">Solo alcuni dispositivi possono riprodurre video 2K/4K</string> <string name="show_higher_resolutions_summary">Solo alcuni dispositivi possono riprodurre video 2K/4K</string>
<string name="default_video_format_title">Formato video predefinito</string> <string name="default_video_format_title">Formato video predefinito</string>
<string name="popup_remember_size_pos_title">Ricorda proprietà lettore popup</string> <string name="popup_remember_size_pos_title">Ricorda proprietà del popup</string>
<string name="popup_remember_size_pos_summary">Ricorda dimensione e posizione del lettore popup</string> <string name="popup_remember_size_pos_summary">Ricorda ultima dimensione e posizione del popup</string>
<string name="show_search_suggestions_title">Suggerimenti di ricerca</string> <string name="show_search_suggestions_title">Suggerimenti di ricerca</string>
<string name="show_search_suggestions_summary">Scegli suggerimenti di ricerca</string> <string name="show_search_suggestions_summary">Scegli suggerimenti di ricerca</string>
<string name="clear">Cancella</string> <string name="clear">Cancella</string>
@ -139,7 +139,7 @@
<string name="settings_category_player_title">Lettore multimediale</string> <string name="settings_category_player_title">Lettore multimediale</string>
<string name="settings_category_player_behavior_title">Comportamento</string> <string name="settings_category_player_behavior_title">Comportamento</string>
<string name="settings_category_history_title">Cronologia e cache</string> <string name="settings_category_history_title">Cronologia e cache</string>
<string name="undo">Annulla</string> <string name="undo">Rifai</string>
<string name="notification_channel_name">Notifica NewPipe</string> <string name="notification_channel_name">Notifica NewPipe</string>
<string name="notification_channel_description">Notifiche per il lettore multimediale di NewPipe</string> <string name="notification_channel_description">Notifiche per il lettore multimediale di NewPipe</string>
<string name="search_no_results">Nessun risultato</string> <string name="search_no_results">Nessun risultato</string>
@ -817,7 +817,6 @@
<string name="share_playlist_content_details">%1$s <string name="share_playlist_content_details">%1$s
\n%2$s</string> \n%2$s</string>
<string name="share_playlist">Condividi playlist</string> <string name="share_playlist">Condividi playlist</string>
<string name="share_playlist_with_titles_message">Condividi la playlist con dettagli come il suo nome e i titoli video o come un semplice elenco di URL video</string>
<string name="video_details_list_item">- %1$s: %2$s</string> <string name="video_details_list_item">- %1$s: %2$s</string>
<plurals name="replies"> <plurals name="replies">
<item quantity="one">%s risposta</item> <item quantity="one">%s risposta</item>
@ -839,4 +838,5 @@
<string name="auto_update_check_description">NewPipe può cercare automaticamente nuove versioni di tanto in tanto e avvisarti quando sono disponibili. <string name="auto_update_check_description">NewPipe può cercare automaticamente nuove versioni di tanto in tanto e avvisarti quando sono disponibili.
\nVuoi attivarlo?</string> \nVuoi attivarlo?</string>
<string name="import_settings_vulnerable_format">Le impostazioni nell\'export che viene importato usano un formato vulnerabile che è stato deprecato dalla versione 0.27.0 di NewPipe. Assicuratevi che l\'export importato venga da una fonte fidata, sarebbe preferibile usare solo exports ottenuti da NewPipe 0.27.0 o superiori, nel futuro. Il supporto all\'importazione di Impostazioni in questo formato vulnerabile sarà presto rimosso completamente, da quel momento le versioni di NewPipe più vecchie non saranno più in grado di importare impostazioni tramite export di versioni più recenti.</string> <string name="import_settings_vulnerable_format">Le impostazioni nell\'export che viene importato usano un formato vulnerabile che è stato deprecato dalla versione 0.27.0 di NewPipe. Assicuratevi che l\'export importato venga da una fonte fidata, sarebbe preferibile usare solo exports ottenuti da NewPipe 0.27.0 o superiori, nel futuro. Il supporto all\'importazione di Impostazioni in questo formato vulnerabile sarà presto rimosso completamente, da quel momento le versioni di NewPipe più vecchie non saranno più in grado di importare impostazioni tramite export di versioni più recenti.</string>
</resources> <string name="audio_track_type_secondary">secondaria</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More