SponsorBlock: Merge branch 'dev' into sponsorblock

This commit is contained in:
polymorphicshade 2020-11-18 13:46:07 -07:00
commit be067ddbaf
261 changed files with 4409 additions and 2940 deletions

144
README.ko.md Normal file
View file

@ -0,0 +1,144 @@
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
<h2 align="center"><b>NewPipe</b></h2>
<h4 align="center">A libre lightweight streaming frontend for Android.</h4>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"></a></p>
<p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg"></a>
<a href="https://www.bountysource.com/teams/newpipe" alt="Bountysource bounties"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f"></a>
</p>
<hr>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#updates">Updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.</b>
<b>NEWPIPE 또는 이것의 FORK을 구글 플레이스토어에 올리는 것은 그들의 이용약관을 위반합니다.</b>
## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
## Description
NewPipe는 어떤 구글 프레임워크 라이브러리나, 유튜브 API를 사용하지 않습니다. 웹사이트는 단지 필요한 정보를 가져오기 위해 구문 분석 됩니다. 따라서 이 앱은 구글 서비스의 설치 없이 기기에서 사용될 수 있습니다. 또한, 카피레프트 자유 소프트웨어인 NewPipe를 사용하기 위해 유튜브 계정이 필요하지 않습니다.
### Features
* 영상 검색
* 영상의 일반적인 정보 표시
* 유튜브 영상 보기
* 유튜브 영상 듣기
* 팝업 모드 (floating player)
* 영상 공유
* 영상 다운로드
* 음성만 다운로드
* Kodi에서 영상 열람
* 다음/관련된 영상 표시
* 특정 언어로 유튜브 검색
* 연령 제한 컨텐츠 시청/차단
* 채널에 대한 일반적인 정보 표시
* 채널 검색
* 채널에서 영상 시청
* Orbot/Tor 지원 (아직 직접적이지 않음)
* 1080p/2K/4K 지원
* 기록 보기
* 채널 구독
* 기록 검색
* 재생목록 검색/시청
* 추가된 재생목록 시청
* 영상 추가
* 지역 재생목록
* 자막
* 실시간 방송 지원
* 댓글 표시
### Supported Services
NewPipe는 여러가지 서비스를 지원합니다. 우리의 [문서](https://teamnewpipe.github.io/documentation/)는 새로운 서비스가 앱과 추출기에 어떻게 추가될 수 있는지에 대한 더 많은 정보를 제공합니다. 만약 새로운 서비스를 추가하고자 한다면, 우리에게 연락해 주시기 바랍니다. 현재 지원되는 서비스:
* YouTube
* SoundCloud \[beta\]
* media.ccc.de \[beta\]
* PeerTube instances \[beta\]
## Updates
NewPipe 코드의 변경이 있을 때(기능 추가 또는 버그 수정으로 인해), 결국 릴리즈가 발생할 것입니다. 이것들의 형식은 x.xx.x 입니다.
이 새로운 버전을 얻기 위해서, 당신은:
1. 직접 디버그 APK를 생성할 수 있습니다. 이 방법은 당신의 기기에서 새로운 기능을 얻을 수 있는 가장 빠른 방법이지만, 꽤 많이 복잡합니다.
따라서 우리는 다른 방법들 중 하나를 사용하는 것을 추천합니다.
2. 우리의 커스텀 저장소를 F-Droid에 추가하고 우리가 릴리즈를 게시하는 대로 저곳에서 릴리즈를 설치할 수 있습니다.
이에 대한 설명서는 이곳에서 확인할 수 있습니다: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
3. 우리가 릴리즈를 게시하는 대로 [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases)에서 APK를 다운받고 이것을 설치할 수 있습니다.
4. F-Droid를 통해 업데이트 할 수 있습니다. F-Droid는 변화를 인식하고, 스스로 APK를 생성하고, 이것에 서명하고, 사용자들에서 업데이트를 전달해야만 하기 때문에,
이것은 업데이트를 받는 가장 느린 방법입니다.
우리는 대부분의 사용자에게 2번쨰 방법을 추천합니다. 방법 2 또는 3을 사용하여 설치된 APK는 서로 호환되지만, 방법 4를 사용하여 설치된 것들과는 호환되지 않습니다. 이것은 방법 2 또는 3에서는 같은 (우리의)서명 키가 사용되지만, 방법 4에서는 다른 (F-Droid의)서명 키가 사용되기 때문입니다. 방법 1을 사용하여 디버그 APK를 생성하는 것에서는 키가 완전히 제외됩니다. 서명 키는 사용자가 앱에 악의적인 업데이트를 설치하는 것에 대해 속지 않도록 보장하는 것을 도와줍니다.
한편, 만약 어떠한 이유(예. NewPipe의 핵심 기능이 손상되었고 F-Droid가 아직 업데이트를 가지지 않는 경우) 때문에 소스를 바꾸길 원한다면,
우리는 다음과 같은 절차를 따르는 것을 권장합니다:
1. 당신의 기록, 구독, 그리고 재생목록을 유지할 수 있도록 Settings > Content > Export Database 를 통해 데이터를 백업하십시오.
2. NewPipe를 삭제하십시오.
3. 새로운 소스에서 APK를 다운로드하고 이것을 설치하십시오.
4. Step 1의 Settings > Content > Export Database 을 통해 데이터를 불러오십시오.
## Contribution
당신이 아이디어, 번역, 디자인 변경, 코드 정리, 또는 정말 큰 코드 수정에 대한 의견이 있다면, 도움은 항상 환영합니다.
더 많이 수행될수록 더 많이 발전할 수 있습니다!
만약 참여하고 싶다면, 우리의 [컨트리뷰션 공지](.github/CONTRIBUTING.md)를 참고하십시오.
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" />
</a>
## Donate
만약 NewPipe가 마음에 들었다면, 우리는 기부에 대해 기꺼이 환영합니다. bitcoin을 보내거나, Bountysource 또는 Liberapay를 통해 기부할 수 있습니다. NewPipe에 기부하는 것에 대한 자세한 정보를 원한다면, 우리의 [웹사이트](https://newpipe.schabi.org/donate)를 방문하여 주십시오.
<table>
<tr>
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin"></td>
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR code" width="100px"></td>
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
</tr>
<tr>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Liberapay_logo_v2_white-on-yellow.svg" alt="Liberapay" width="80px" ></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/"><img src="assets/liberapay_qr_code.png" alt="Visit NewPipe at liberapay.com" width="100px"></a></td>
<td><a href="https://liberapay.com/TeamNewPipe/donate"><img src="assets/liberapay_donate_button.svg" alt="Donate via Liberapay" height="35px"></a></td>
</tr>
<tr>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alt="Bountysource" width="190px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="assets/bountysource_qr_code.png" alt="Visit NewPipe at bountysource.com" width="100px"></a></td>
<td><a href="https://www.bountysource.com/teams/newpipe/issues"><img src="https://img.shields.io/bountysource/team/newpipe/activity.svg?colorB=cd201f" height="30px" alt="Check out how many bounties you can earn."></a></td>
</tr>
</table>
## Privacy Policy
NewPipe 프로젝트는 미디어 웹 서비스를 사용하는 것에 대한 사적의, 익명의 경험을 제공하는 것을 목표로 하고 있습니다.
그러므로, 앱은 당신의 동의 없이 어떤 데이터도 수집하지 않습니다. NewPipe의 개인정보보호정책은 당신이 충돌 리포트를 보내거나, 또는 우리의 블로그에 글을 남길 때 어떤 데이터가 보내지고 저장되는지에 대해 상세히 설명합니다. 이 문서는 [여기](https://newpipe.schabi.org/legal/privacy/)에서 확인할 수 있습니다.
## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe는 자유 소프트웨어입니다: 당신의 마음대로 이것을 사용하고, 연구하고, 공유하고, 개선할 수 있습니다.
구체적으로 당신은 자유 소프트웨어 재단에서 발행되는, 버전 3 또는 (당신의 선택에 따라)이후 버전의,
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) 하에서 이것을 재배포 및/또는 수정할 수 있습니다.

View file

@ -16,22 +16,24 @@
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [한국어](README.ko.md).*
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>
<b>PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.</b>
## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)-->
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)-->
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)-->
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
@ -80,17 +82,18 @@ NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/doc
## Updates
When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can:
* Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
* Download the APK from [releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it.
* Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
1. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
2. Add our custom repo to F-Droid and install it from there as soon as we publish a release. The instructions are here: https://newpipe.schabi.org/FAQ/tutorials/install-add-fdroid-repo/
3. Download the APK from [Github Releases](https://github.com/TeamNewPipe/NewPipe/releases) and install it as soon as we publish a release.
4. Update via F-droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, then push the update to users.
When you install an APK from one of these options, it will be incompatible with an APK from one of the other options. This is due to different signing keys being used. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app, and are independent. F-Droid and GitHub use different signing keys, and building an APK debug excludes a key. The signing key issue is being discussed in issue [#1981](https://github.com/TeamNewPipe/NewPipe/issues/1981), and may be fixed by setting up our own repository on F-Droid.
We recommend method 2 for most users. APKs installed using method 2 or 3 are compatible with each other, but not with those installed using method 4. This is due to the same signing key (ours) being using for 2 and 3, but a different signing key (F-Droid's) being used for 4. Building a debug APK using method 1 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app.
In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality was broken and F-Droid doesn't have the update yet), we recommend following this procedure:
1. Back up your data via "Settings>Content>Export Database" so you keep your history, subscriptions, and playlists
1. Back up your data via Settings > Content > Export Database so you keep your history, subscriptions, and playlists
2. Uninstall NewPipe
3. Download the APK from the new source and install it
4. Import the data from step 1 via "Settings>Content>Import Database"
4. Import the data from step 1 via Settings > Content > Import Database
## Contribution
Whether you have ideas, translations, design changes, code cleaning, or real heavy code changes, help is always welcome.

View file

@ -13,8 +13,8 @@ android {
resValue "string", "app_name", "NewPipe"
minSdkVersion 19
targetSdkVersion 29
versionCode 956
versionName "0.20.2"
versionCode 957
versionName "0.20.3"
multiDexEnabled true
@ -65,6 +65,9 @@ android {
}
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
encoding 'utf-8'
@ -144,6 +147,8 @@ afterEvaluate {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "frankiesardo:icepick:${icepickVersion}"
@ -171,7 +176,7 @@ dependencies {
// NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
implementation 'com.github.TeamNewPipe:NewPipeExtractor:62912ee8349f5d26617c039f337297628ff52ead'
implementation 'com.github.TeamNewPipe:NewPipeExtractor:6701b0fe718f6bdc385221341fa473e8aaab560e'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1"

View file

@ -1,400 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>GNU General Public License v2.0 - GNU Project - Free Software Foundation (FSF)</title>
<link rel="alternate" type="application/rdf+xml"
href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.rdf" />
</head>
<body>
<h3><a id="SEC1">GNU GENERAL PUBLIC LICENSE</a></h3>
<p>
Version 2, June 1991
</p>
<pre>
Copyright (C) 1989, 1991 Free Software Foundation, Inc.<br/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA<br/>
<br/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
</pre>
<h3 id="preamble"><a id="SEC2">Preamble</a></h3>
<p>
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
</p>
<p>
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
</p>
<p>
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
</p>
<p>
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
</p>
<p>
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
</p>
<p>
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
</p>
<p>
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
</p>
<p>
The precise terms and conditions for copying, distribution and
modification follow.
</p>
<h3 id="terms"><a id="SEC3">TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</a></h3>
<p id="section0">
<strong>0.</strong>
This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
</p>
<p>
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
</p>
<p id="section1">
<strong>1.</strong>
You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
</p>
<p>
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
</p>
<p id="section2">
<strong>2.</strong>
You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
</p>
<dl>
<dt></dt>
<dd>
<strong>a)</strong>
You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
</dd>
<dt></dt>
<dd>
<strong>b)</strong>
You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
</dd>
<dt></dt>
<dd>
<strong>c)</strong>
If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
</dd>
</dl>
<p>
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
</p>
<p>
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
</p>
<p>
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
</p>
<p id="section3">
<strong>3.</strong>
You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
</p>
<!-- we use this doubled UL to get the sub-sections indented, -->
<!-- while making the bullets as unobvious as possible. -->
<dl>
<dt></dt>
<dd>
<strong>a)</strong>
Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
</dd>
<dt></dt>
<dd>
<strong>b)</strong>
Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
</dd>
<dt></dt>
<dd>
<strong>c)</strong>
Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
</dd>
</dl>
<p>
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major softwareComponents (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
</p>
<p>
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
</p>
<p id="section4">
<strong>4.</strong>
You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
</p>
<p id="section5">
<strong>5.</strong>
You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
</p>
<p id="section6">
<strong>6.</strong>
Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
</p>
<p id="section7">
<strong>7.</strong>
If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
</p>
<p>
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
</p>
<p>
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
</p>
<p>
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
</p>
<p id="section8">
<strong>8.</strong>
If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
</p>
<p id="section9">
<strong>9.</strong>
The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
</p>
<p>
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
</p>
<p id="section10">
<strong>10.</strong>
If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
</p>
<p id="section11"><strong>NO WARRANTY</strong></p>
<p>
<strong>11.</strong>
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
</p>
<p id="section12">
<strong>12.</strong>
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
</p>
</body></html>

View file

@ -36,6 +36,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
import io.reactivex.exceptions.OnErrorNotImplementedException;
@ -65,6 +66,9 @@ public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString();
private static App app;
private Disposable disposable = null;
@NonNull
public static App getApp() {
return app;
}
@ -100,7 +104,15 @@ public class App extends MultiDexApplication {
configureRxJavaErrorHandler();
// Check for new version
new CheckForNewAppVersionTask().execute();
disposable = CheckForNewAppVersion.checkNewVersion(this);
}
@Override
public void onTerminate() {
if (disposable != null) {
disposable.dispose();
}
super.onTerminate();
}
protected Downloader getDownloader() {

View file

@ -9,9 +9,9 @@ import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
@ -35,16 +35,18 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* AsyncTask to check if there is a newer version of the NewPipe github apk available or not.
* If there is a newer version we show a notification, informing the user. On tapping
* the notification, the user will be directed to the download link.
*/
public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName();
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
public final class CheckForNewAppVersion {
private CheckForNewAppVersion() { }
private static final boolean DEBUG = MainActivity.DEBUG;
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
private static final Application APP = App.getApp();
private static final String GITHUB_APK_SHA1
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
private static final String NEWPIPE_API_URL = "https://newpipe.schabi.org/api/data.json";
@ -52,18 +54,19 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
/**
* Method to get the apk's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
*
* @param application The application
* @return String with the apk's SHA1 fingeprint in hexadecimal
*/
private static String getCertificateSHA1Fingerprint() {
final PackageManager pm = APP.getPackageManager();
final String packageName = APP.getPackageName();
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
final PackageManager pm = application.getPackageManager();
final String packageName = application.getPackageName();
final int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (final PackageManager.NameNotFoundException e) {
ErrorActivity.reportError(APP, e, null, null,
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not find package info", R.string.app_ui_crash));
}
@ -78,7 +81,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
final CertificateFactory cf = CertificateFactory.getInstance("X509");
c = (X509Certificate) cf.generateCertificate(input);
} catch (final CertificateException e) {
ErrorActivity.reportError(APP, e, null, null,
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Certificate error", R.string.app_ui_crash));
}
@ -90,7 +93,7 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
final byte[] publicKey = md.digest(c.getEncoded());
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
ErrorActivity.reportError(APP, e, null, null,
ErrorActivity.reportError(application, e, null, null,
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none",
"Could not retrieve SHA1 key", R.string.app_ui_crash));
}
@ -118,104 +121,108 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
return str.toString();
}
public static boolean isGithubApk() {
return getCertificateSHA1Fingerprint().equals(GITHUB_APK_SHA1);
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
*
* @param application The application
* @param versionName Name of new version
* @param apkLocationUrl Url with the new apk
* @param versionCode Code of new version
*/
private static void compareAppVersionAndShowNotification(@NonNull final Application application,
final String versionName,
final String apkLocationUrl,
final int versionCode) {
final int notificationId = 2000;
if (BuildConfig.VERSION_CODE < versionCode) {
// A pending intent to open the apk location url in the browser.
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
final String channelId = application
.getString(R.string.app_update_notification_channel_id);
final NotificationCompat.Builder notificationBuilder
= new NotificationCompat.Builder(application, channelId)
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(application
.getString(R.string.app_update_notification_content_title))
.setContentText(application
.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
final NotificationManagerCompat notificationManager
= NotificationManagerCompat.from(application);
notificationManager.notify(notificationId, notificationBuilder.build());
}
}
@Override
protected void onPreExecute() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(APP);
private static boolean isConnected(@NonNull final App app) {
final ConnectivityManager cm = ContextCompat.getSystemService(app,
ConnectivityManager.class);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
}
public static boolean isGithubApk(@NonNull final App app) {
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
}
@NonNull
public static Disposable checkNewVersion(@NonNull final App app) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
// Check if user has enabled/disabled update checking
// and if the current apk is a github one or not.
if (!prefs.getBoolean(APP.getString(R.string.update_app_key), true) || !isGithubApk()) {
this.cancel(true);
}
}
@Override
protected String doInBackground(final Void... voids) {
if (isCancelled() || !isConnected()) {
return null;
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true)
|| !isGithubApk(app)) {
return Disposables.empty();
}
// Make a network request to get latest NewPipe data.
try {
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
} catch (IOException | ReCaptchaException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
return Observable.fromCallable(() -> {
if (!isConnected(app)) {
return null;
}
}
return null;
}
@Override
protected void onPostExecute(final String response) {
// Parse the json from the response.
if (response != null) {
// Make a network request to get latest NewPipe data.
try {
final JsonObject githubStableObject = JsonParser.object().from(response)
.getObject("flavors").getObject("github").getObject("stable");
final String versionName = githubStableObject.getString("version");
final int versionCode = githubStableObject.getInt("version_code");
final String apkLocationUrl = githubStableObject.getString("apk");
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode);
} catch (final JsonParserException e) {
return DownloaderImpl.getInstance().get(NEWPIPE_API_URL).responseBody();
} catch (IOException | ReCaptchaException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
}
}
}
}
/**
* Method to compare the current and latest available app version.
* If a newer version is available, we show the update notification.
*
* @param versionName Name of new version
* @param apkLocationUrl Url with the new apk
* @param versionCode Code of new version
*/
private void compareAppVersionAndShowNotification(final String versionName,
final String apkLocationUrl,
final int versionCode) {
final int notificationId = 2000;
return null;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
// Parse the json from the response.
if (response != null) {
try {
final JsonObject githubStableObject = JsonParser.object().from(response)
.getObject("flavors").getObject("github").getObject("stable");
if (BuildConfig.VERSION_CODE < versionCode) {
final String versionName = githubStableObject.getString("version");
final int versionCode = githubStableObject.getInt("version_code");
final String apkLocationUrl = githubStableObject.getString("apk");
// A pending intent to open the apk location url in the browser.
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
final PendingIntent pendingIntent
= PendingIntent.getActivity(APP, 0, intent, 0);
final NotificationCompat.Builder notificationBuilder = new NotificationCompat
.Builder(APP, APP.getString(R.string.app_update_notification_channel_id))
.setSmallIcon(R.drawable.ic_newpipe_update)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setContentTitle(APP.getString(R.string.app_update_notification_content_title))
.setContentText(APP.getString(R.string.app_update_notification_content_text)
+ " " + versionName);
final NotificationManagerCompat notificationManager
= NotificationManagerCompat.from(APP);
notificationManager.notify(notificationId, notificationBuilder.build());
}
}
private boolean isConnected() {
final ConnectivityManager cm = ContextCompat.getSystemService(APP,
ConnectivityManager.class);
return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected();
compareAppVersionAndShowNotification(app, versionName, apkLocationUrl,
versionCode);
} catch (final JsonParserException e) {
// connectivity problems, do not alarm user and fail silently
if (DEBUG) {
Log.w(TAG, Log.getStackTraceString(e));
}
}
}
});
}
}

View file

@ -30,9 +30,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@ -46,6 +44,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
@ -55,6 +54,7 @@ import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.preference.PreferenceManager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.navigation.NavigationView;
@ -69,10 +69,11 @@ import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.event.OnKeyDownListener;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KioskTranslator;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
@ -87,6 +88,7 @@ import org.schabi.newpipe.views.FocusOverlayView;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
@ -152,7 +154,7 @@ public class MainActivity extends AppCompatActivity {
if (DeviceUtils.isTv(this)) {
FocusOverlayView.setupFocusObserver(this);
}
setupBroadcastReceiver();
openMiniPlayerUponPlayerStarted();
}
private void setupDrawer() throws Exception {
@ -758,32 +760,36 @@ public class MainActivity extends AppCompatActivity {
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
final String url = intent.getStringExtra(Constants.KEY_URL);
final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
final String title = intent.getStringExtra(Constants.KEY_TITLE);
switch (((StreamingService.LinkType) intent
.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
String title = intent.getStringExtra(Constants.KEY_TITLE);
if (title == null) {
title = "";
}
final StreamingService.LinkType linkType = ((StreamingService.LinkType) intent
.getSerializableExtra(Constants.KEY_LINK_TYPE));
assert linkType != null;
switch (linkType) {
case STREAM:
final boolean autoPlay = intent
.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
final String intentCacheKey = intent
.getStringExtra(VideoPlayer.PLAY_QUEUE_KEY);
final String intentCacheKey = intent.getStringExtra(
VideoPlayer.PLAY_QUEUE_KEY);
final PlayQueue playQueue = intentCacheKey != null
? SerializedCache.getInstance()
.take(intentCacheKey, PlayQueue.class)
: null;
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(),
serviceId, url, title, autoPlay, playQueue);
final boolean switchingPlayers = intent.getBooleanExtra(
VideoDetailFragment.KEY_SWITCHING_PLAYERS, false);
NavigationHelper.openVideoDetailFragment(
getApplicationContext(), getSupportFragmentManager(),
serviceId, url, title, playQueue, switchingPlayers);
break;
case CHANNEL:
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
serviceId,
url,
title);
serviceId, url, title);
break;
case PLAYLIST:
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(),
serviceId,
url,
title);
serviceId, url, title);
break;
}
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
@ -805,34 +811,47 @@ public class MainActivity extends AppCompatActivity {
}
}
private void setupBroadcastReceiver() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(VideoDetailFragment.ACTION_PLAYER_STARTED)) {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragmentPlayer == null) {
/*
* We still don't have a fragment attached to the activity.
* It can happen when a user started popup or background players
* without opening a stream inside the fragment.
* Adding it in a collapsed state (only mini player will be visible)
* */
NavigationHelper.showMiniPlayer(getSupportFragmentManager());
private void openMiniPlayerIfMissing() {
final Fragment fragmentPlayer = getSupportFragmentManager()
.findFragmentById(R.id.fragment_player_holder);
if (fragmentPlayer == null) {
// We still don't have a fragment attached to the activity. It can happen when a user
// started popup or background players without opening a stream inside the fragment.
// Adding it in a collapsed state (only mini player will be visible).
NavigationHelper.showMiniPlayer(getSupportFragmentManager());
}
}
private void openMiniPlayerUponPlayerStarted() {
if (getIntent().getSerializableExtra(Constants.KEY_LINK_TYPE)
== StreamingService.LinkType.STREAM) {
// handleIntent() already takes care of opening video detail fragment
// due to an intent containing a STREAM link
return;
}
if (PlayerHolder.isPlayerOpen()) {
// if the player is already open, no need for a broadcast receiver
openMiniPlayerIfMissing();
} else {
// listen for player start intent being sent around
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (Objects.equals(intent.getAction(),
VideoDetailFragment.ACTION_PLAYER_STARTED)) {
openMiniPlayerIfMissing();
// At this point the player is added 100%, we can unregister. Other actions
// are useless since the fragment will not be removed after that.
unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
/*
* At this point the player is added 100%, we can unregister.
* Other actions are useless since the fragment will not be removed after that
* */
unregisterReceiver(broadcastReceiver);
broadcastReceiver = null;
}
}
};
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter);
};
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VideoDetailFragment.ACTION_PLAYER_STARTED);
registerReceiver(broadcastReceiver, intentFilter);
}
}
private boolean bottomSheetHiddenOrCollapsed() {

View file

@ -40,7 +40,9 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
@ -60,8 +62,6 @@ import org.schabi.newpipe.views.FocusOverlayView;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import icepick.Icepick;
@ -116,8 +116,6 @@ public class RouterActivity extends AppCompatActivity {
}
}
internalRoute = getIntent().getBooleanExtra(INTERNAL_ROUTE_KEY, false);
setTheme(ThemeHelper.isLightThemeSelected(this)
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
}
@ -398,14 +396,22 @@ public class RouterActivity extends AppCompatActivity {
// show both "show info" and "video player", they are two different activities
returnList.add(showInfo);
returnList.add(videoPlayer);
} else if (capabilities.contains(VIDEO)
&& PlayerHelper.isAutoplayAllowedByUser(context)) {
// show only "video player" since the details activity will be opened and the video
// will be autoplayed there and "show info" would do the exact same thing
returnList.add(videoPlayer);
} else {
// show only "show info" if video player is not applicable or autoplay is disabled
returnList.add(showInfo);
final MainPlayer.PlayerType playerType = PlayerHolder.getType();
if (capabilities.contains(VIDEO)
&& PlayerHelper.isAutoplayAllowedByUser(context)
&& playerType == null || playerType == MainPlayer.PlayerType.VIDEO) {
// show only "video player" since the details activity will be opened and the
// video will be auto played there. Since "show info" would do the exact same
// thing, use that as a key to let VideoDetailFragment load the stream instead
// of using FetcherService (see comment in handleChoice())
returnList.add(new AdapterChoiceItem(
showInfo.key, videoPlayer.description, videoPlayer.icon));
} else {
// show only "show info" if video player is not applicable, auto play is
// disabled or a video is playing in a player different than the main one
returnList.add(showInfo);
}
}
if (capabilities.contains(VIDEO)) {
@ -492,12 +498,7 @@ public class RouterActivity extends AppCompatActivity {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(intent -> {
if (!internalRoute) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
startActivity(intent);
finish();
}, throwable -> handleError(throwable, currentUrl))
);
@ -515,7 +516,7 @@ public class RouterActivity extends AppCompatActivity {
@SuppressLint("CheckResult")
private void openDownloadDialog() {
ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((@NonNull StreamInfo result) -> {
@ -532,10 +533,10 @@ public class RouterActivity extends AppCompatActivity {
downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex);
downloadDialog.show(fm, "downloadDialog");
fm.executePendingTransactions();
downloadDialog.getDialog().setOnDismissListener(dialog -> finish());
downloadDialog.requireDialog().setOnDismissListener(dialog -> finish());
}, (@NonNull Throwable throwable) -> {
showUnsupportedUrlDialog(currentUrl);
});
}));
}
@Override
@ -553,66 +554,6 @@ public class RouterActivity extends AppCompatActivity {
}
}
/*//////////////////////////////////////////////////////////////////////////
// Service Fetcher
//////////////////////////////////////////////////////////////////////////*/
private String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
if (!input.substring(i, i + 1).matches("\\p{L}")) {
start = i + 1;
break;
}
}
return input.substring(start);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private String trim(final String input) {
if (input == null || input.length() < 1) {
return input;
} else {
String output = input;
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(1);
}
while (output.length() > 0
&& output.substring(output.length() - 1).matches(REGEX_REMOVE_FROM_URL)) {
output = output.substring(0, output.length() - 1);
}
return output;
}
}
/**
* Retrieves all Strings which look remotely like URLs from a text.
* Used if NewPipe was called through share menu.
*
* @param sharedText text to scan for URLs.
* @return potential URLs
*/
protected String[] getUris(final String sharedText) {
final Collection<String> result = new HashSet<>();
if (sharedText != null) {
final String[] array = sharedText.split("\\p{Space}");
for (String s : array) {
s = trim(s);
if (s.length() != 0) {
if (s.matches(".+://.+")) {
result.add(removeHeadingGibberish(s));
} else if (s.matches(".+\\..+")) {
result.add("http://" + s);
}
}
}
}
return result.toArray(new String[0]);
}
private static class AdapterChoiceItem {
final String description;
final String key;
@ -725,50 +666,34 @@ public class RouterActivity extends AppCompatActivity {
final boolean isExtAudioEnabled = preferences.getBoolean(
getString(R.string.use_external_audio_player_key), false);
PlayQueue playQueue;
final String playerChoice = choice.playerChoice;
final PlayQueue playQueue;
if (info instanceof StreamInfo) {
if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
if (choice.playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) {
NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info);
} else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
return;
} else if (choice.playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) {
NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info);
} else {
playQueue = new SinglePlayQueue((StreamInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true);
}
return;
}
playQueue = new SinglePlayQueue((StreamInfo) info);
} else if (info instanceof ChannelInfo) {
playQueue = new ChannelPlayQueue((ChannelInfo) info);
} else if (info instanceof PlaylistInfo) {
playQueue = new PlaylistPlayQueue((PlaylistInfo) info);
} else {
return;
}
if (info instanceof ChannelInfo || info instanceof PlaylistInfo) {
playQueue = info instanceof ChannelInfo
? new ChannelPlayQueue((ChannelInfo) info)
: new PlaylistPlayQueue((PlaylistInfo) info);
if (playerChoice.equals(videoPlayerKey)) {
openMainPlayer(playQueue, choice);
} else if (playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (playerChoice.equals(popupPlayerKey)) {
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
}
if (choice.playerChoice.equals(videoPlayerKey)) {
NavigationHelper.playOnMainPlayer(this, playQueue, false);
} else if (choice.playerChoice.equals(backgroundPlayerKey)) {
NavigationHelper.playOnBackgroundPlayer(this, playQueue, true);
} else if (choice.playerChoice.equals(popupPlayerKey)) {
NavigationHelper.playOnPopupPlayer(this, playQueue, true);
}
};
}
private void openMainPlayer(final PlayQueue playQueue, final Choice choice) {
NavigationHelper.playOnMainPlayer(this, playQueue, choice.linkType,
choice.url, "", true, true);
}
@Override
public void onDestroy() {
super.onDestroy();

View file

@ -12,8 +12,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
@ -34,7 +33,7 @@ public class AboutActivity extends AppCompatActivity {
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = new SoftwareComponent[]{
new SoftwareComponent("Giga Get", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL2),
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("Jsoup", "2017", "Jonathan Hedley",
@ -95,8 +94,7 @@ public class AboutActivity extends AppCompatActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter =
new SectionsPagerAdapter(getSupportFragmentManager(), getLifecycle());
mSectionsPagerAdapter = new SectionsPagerAdapter(this);
// Set up the ViewPager with the sections adapter.
mViewPager = findViewById(R.id.container);
@ -179,8 +177,8 @@ public class AboutActivity extends AppCompatActivity {
* one of the sections/tabs/pages.
*/
public static class SectionsPagerAdapter extends FragmentStateAdapter {
public SectionsPagerAdapter(final FragmentManager fm, final Lifecycle lifecycle) {
super(fm, lifecycle);
public SectionsPagerAdapter(final FragmentActivity fa) {
super(fa);
}
@NonNull

View file

@ -1,6 +1,5 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@ -19,16 +18,21 @@ import org.schabi.newpipe.util.ShareUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import io.reactivex.disposables.CompositeDisposable;
/**
* Fragment containing the software licenses.
*/
public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components";
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent componentForContextMenu;
private License activeLicense;
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
if (softwareComponents == null) {
@ -41,16 +45,6 @@ public class LicenseFragment extends Fragment {
return fragment;
}
/**
* Shows a popup containing the license.
*
* @param context the context to use
* @param license the license to show
*/
private static void showLicense(final Activity context, final License license) {
new LicenseFragmentHelper(context).execute(license);
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -64,7 +58,13 @@ public class LicenseFragment extends Fragment {
}
}
// Sort components by name
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
Arrays.sort(softwareComponents, Comparator.comparing(SoftwareComponent::getName));
}
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@Nullable
@ -76,8 +76,9 @@ public class LicenseFragment extends Fragment {
final View licenseLink = rootView.findViewById(R.id.app_read_license);
licenseLink.setOnClickListener(v -> {
activeLicense = StandardLicenses.GPL3;
showLicense(getActivity(), StandardLicenses.GPL3);
activeLicense = StandardLicenses.GPL3;
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
StandardLicenses.GPL3));
});
for (final SoftwareComponent component : softwareComponents) {
@ -94,13 +95,15 @@ public class LicenseFragment extends Fragment {
componentView.setTag(component);
componentView.setOnClickListener(v -> {
activeLicense = component.getLicense();
showLicense(getActivity(), component.getLicense());
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
component.getLicense()));
});
softwareComponentsView.addView(componentView);
registerForContextMenu(componentView);
}
if (activeLicense != null) {
showLicense(getActivity(), activeLicense);
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
activeLicense));
}
return rootView;
}
@ -128,7 +131,8 @@ public class LicenseFragment extends Fragment {
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
return true;
case R.id.action_show_license:
showLicense(getActivity(), component.getLicense());
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
component.getLicense()));
}
return false;
}

View file

@ -1,8 +1,6 @@
package org.schabi.newpipe.about;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Base64;
import android.webkit.WebView;
@ -16,18 +14,18 @@ import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
private final WeakReference<Activity> weakReference;
private License license;
public LicenseFragmentHelper(@Nullable final Activity activity) {
weakReference = new WeakReference<>(activity);
}
public final class LicenseFragmentHelper {
private LicenseFragmentHelper() { }
/**
* @param context the context to use
@ -62,7 +60,7 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
* @param context
* @return String which is a CSS stylesheet according to the context's theme
*/
private static String getLicenseStylesheet(final Context context) {
private static String getLicenseStylesheet(@NonNull final Context context) {
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;"
+ "background:#" + getHexRGBColor(context, isLightTheme
@ -84,45 +82,31 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
private static String getHexRGBColor(final Context context, final int color) {
private static String getHexRGBColor(@NonNull final Context context, final int color) {
return context.getResources().getString(color).substring(3);
}
@Nullable
private Activity getActivity() {
final Activity activity = weakReference.get();
if (activity != null && activity.isFinishing()) {
return null;
} else {
return activity;
}
}
@Override
protected Integer doInBackground(final Object... objects) {
license = (License) objects[0];
return 1;
}
@Override
protected void onPostExecute(final Integer result) {
final Activity activity = getActivity();
if (activity == null) {
return;
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
if (context == null) {
return Disposables.empty();
}
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
final WebView webView = new WebView(activity);
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
return Observable.fromCallable(() -> getFormattedLicense(context, license))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(formattedLicense -> {
final String webViewData = Base64.encodeToString(formattedLicense
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
final WebView webView = new WebView(context);
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
final AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setTitle(license.getName());
alert.setView(webView);
assureCorrectAppLanguage(activity);
alert.setNegativeButton(activity.getString(R.string.finish),
(dialog, which) -> dialog.dismiss());
alert.show();
final AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(license.getName());
alert.setView(webView);
assureCorrectAppLanguage(context);
alert.setNegativeButton(context.getString(R.string.finish),
(dialog, which) -> dialog.dismiss());
alert.show();
});
}
}

View file

@ -4,8 +4,6 @@ package org.schabi.newpipe.about;
* Class containing information about standard software licenses.
*/
public final class StandardLicenses {
public static final License GPL2
= new License("GNU General Public License, Version 2.0", "GPLv2", "gpl_2.html");
public static final License GPL3
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
public static final License APACHE2

View file

@ -5,31 +5,35 @@ import androidx.room.TypeConverter;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.local.subscription.FeedGroupIcon;
import java.util.Date;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
public final class Converters {
private Converters() { }
/**
* Convert a long value to a date.
* Convert a long value to a {@link OffsetDateTime}.
*
* @param value the long value
* @return the date
* @return the {@code OffsetDateTime}
*/
@TypeConverter
public static Date fromTimestamp(final Long value) {
return value == null ? null : new Date(value);
public static OffsetDateTime offsetDateTimeFromTimestamp(final Long value) {
return value == null ? null : OffsetDateTime.ofInstant(Instant.ofEpochMilli(value),
ZoneOffset.UTC);
}
/**
* Convert a date to a long value.
* Convert a {@link OffsetDateTime} to a long value.
*
* @param date the date
* @param offsetDateTime the {@code OffsetDateTime}
* @return the long value
*/
@TypeConverter
public static Long dateToTimestamp(final Date date) {
return date == null ? null : date.getTime();
public static Long offsetDateTimeToTimestamp(final OffsetDateTime offsetDateTime) {
return offsetDateTime == null ? null : offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC)
.toInstant().toEpochMilli();
}
@TypeConverter

View file

@ -7,7 +7,7 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import io.reactivex.Flowable
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedEntity
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
@ -58,10 +58,10 @@ abstract class FeedDAO {
INNER JOIN feed f
ON s.uid = f.stream_id
WHERE s.upload_date < :date
WHERE s.upload_date < :offsetDateTime
)
""")
abstract fun unlinkStreamsOlderThan(date: Date)
abstract fun unlinkStreamsOlderThan(offsetDateTime: OffsetDateTime)
@Query("""
DELETE FROM feed
@ -106,10 +106,10 @@ abstract class FeedDAO {
INNER JOIN feed_group_subscription_join fgs
ON fgs.subscription_id = lu.subscription_id AND fgs.group_id = :groupId
""")
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<Date>>
abstract fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>>
@Query("SELECT MIN(last_updated) FROM feed_last_updated")
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<Date>>
abstract fun oldestSubscriptionUpdateFromAll(): Flowable<List<OffsetDateTime>>
@Query("SELECT COUNT(*) FROM feed_last_updated WHERE last_updated IS NULL")
abstract fun notLoadedCount(): Flowable<Long>
@ -135,7 +135,7 @@ abstract class FeedDAO {
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
abstract fun getAllOutdated(outdatedThreshold: Date): Flowable<List<SubscriptionEntity>>
abstract fun getAllOutdated(outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
@Query("""
SELECT s.* FROM subscriptions s
@ -148,5 +148,5 @@ abstract class FeedDAO {
WHERE lu.last_updated IS NULL OR lu.last_updated < :outdatedThreshold
""")
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: Date): Flowable<List<SubscriptionEntity>>
abstract fun getAllOutdatedForGroup(groupId: Long, outdatedThreshold: OffsetDateTime): Flowable<List<SubscriptionEntity>>
}

View file

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
import org.schabi.newpipe.database.subscription.SubscriptionEntity
@ -25,9 +25,8 @@ data class FeedLastUpdatedEntity(
var subscriptionId: Long,
@ColumnInfo(name = LAST_UPDATED)
var lastUpdated: Date? = null
var lastUpdated: OffsetDateTime? = null
) {
companion object {
const val FEED_LAST_UPDATED_TABLE = "feed_last_updated"

View file

@ -6,7 +6,7 @@ import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.util.Date;
import java.time.OffsetDateTime;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
@ -24,7 +24,7 @@ public class SearchHistoryEntry {
private long id;
@ColumnInfo(name = CREATION_DATE)
private Date creationDate;
private OffsetDateTime creationDate;
@ColumnInfo(name = SERVICE_ID)
private int serviceId;
@ -32,7 +32,8 @@ public class SearchHistoryEntry {
@ColumnInfo(name = SEARCH)
private String search;
public SearchHistoryEntry(final Date creationDate, final int serviceId, final String search) {
public SearchHistoryEntry(final OffsetDateTime creationDate, final int serviceId,
final String search) {
this.serviceId = serviceId;
this.creationDate = creationDate;
this.search = search;
@ -46,11 +47,11 @@ public class SearchHistoryEntry {
this.id = id;
}
public Date getCreationDate() {
public OffsetDateTime getCreationDate() {
return creationDate;
}
public void setCreationDate(final Date creationDate) {
public void setCreationDate(final OffsetDateTime creationDate) {
this.creationDate = creationDate;
}

View file

@ -9,7 +9,7 @@ import androidx.room.Index;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import java.util.Date;
import java.time.OffsetDateTime;
import static androidx.room.ForeignKey.CASCADE;
import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID;
@ -37,12 +37,12 @@ public class StreamHistoryEntity {
@NonNull
@ColumnInfo(name = STREAM_ACCESS_DATE)
private Date accessDate;
private OffsetDateTime accessDate;
@ColumnInfo(name = STREAM_REPEAT_COUNT)
private long repeatCount;
public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate,
public StreamHistoryEntity(final long streamUid, @NonNull final OffsetDateTime accessDate,
final long repeatCount) {
this.streamUid = streamUid;
this.accessDate = accessDate;
@ -50,7 +50,7 @@ public class StreamHistoryEntity {
}
@Ignore
public StreamHistoryEntity(final long streamUid, @NonNull final Date accessDate) {
public StreamHistoryEntity(final long streamUid, @NonNull final OffsetDateTime accessDate) {
this(streamUid, accessDate, 1);
}
@ -62,11 +62,12 @@ public class StreamHistoryEntity {
this.streamUid = streamUid;
}
public Date getAccessDate() {
@NonNull
public OffsetDateTime getAccessDate() {
return accessDate;
}
public void setAccessDate(@NonNull final Date accessDate) {
public void setAccessDate(@NonNull final OffsetDateTime accessDate) {
this.accessDate = accessDate;
}

View file

@ -2,7 +2,7 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity
data class StreamHistoryEntry(
@ -13,7 +13,7 @@ data class StreamHistoryEntry(
val streamId: Long,
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
val accessDate: Date,
val accessDate: OffsetDateTime,
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
val repeatCount: Long
@ -25,6 +25,6 @@ data class StreamHistoryEntry(
fun hasEqualValues(other: StreamHistoryEntry): Boolean {
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.compareTo(other.accessDate) == 0
accessDate.isEqual(other.accessDate)
}
}

View file

@ -5,6 +5,7 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public interface PlaylistLocalItem extends LocalItem {
@ -18,15 +19,8 @@ public interface PlaylistLocalItem extends LocalItem {
items.addAll(localPlaylists);
items.addAll(remotePlaylists);
Collections.sort(items, (left, right) -> {
final String on1 = left.getOrderingName();
final String on2 = right.getOrderingName();
if (on1 == null) {
return on2 == null ? 0 : 1;
} else {
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
}
});
Collections.sort(items, Comparator.comparing(PlaylistLocalItem::getOrderingName,
Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
return items;
}

View file

@ -2,26 +2,25 @@ package org.schabi.newpipe.database.stream
import androidx.room.ColumnInfo
import androidx.room.Embedded
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.LocalItem
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
class StreamStatisticsEntry(
@Embedded
@Embedded
val streamEntity: StreamEntity,
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
val streamId: Long,
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: Date,
@ColumnInfo(name = STREAM_LATEST_DATE)
val latestAccessDate: OffsetDateTime,
@ColumnInfo(name = STREAM_WATCH_COUNT)
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration

View file

@ -7,7 +7,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import io.reactivex.Flowable
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.BasicDAO
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
@ -129,7 +129,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
var textualUploadDate: String? = null,
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
var uploadDate: OffsetDateTime? = null,
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null,

View file

@ -6,8 +6,7 @@ import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.io.Serializable
import java.util.Calendar
import java.util.Date
import java.time.OffsetDateTime
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
@ -55,18 +54,17 @@ data class StreamEntity(
var textualUploadDate: String? = null,
@ColumnInfo(name = STREAM_UPLOAD_DATE)
var uploadDate: Date? = null,
var uploadDate: OffsetDateTime? = null,
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
var isUploadDateApproximation: Boolean? = null
) : Serializable {
@Ignore
constructor(item: StreamInfoItem) : this(
serviceId = item.serviceId, url = item.url, title = item.name,
streamType = item.streamType, duration = item.duration, uploader = item.uploaderName,
thumbnailUrl = item.thumbnailUrl, viewCount = item.viewCount,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.date()?.time,
textualUploadDate = item.textualUploadDate, uploadDate = item.uploadDate?.offsetDateTime(),
isUploadDateApproximation = item.uploadDate?.isApproximation
)
@ -75,7 +73,7 @@ data class StreamEntity(
serviceId = info.serviceId, url = info.url, title = info.name,
streamType = info.streamType, duration = info.duration, uploader = info.uploaderName,
thumbnailUrl = info.thumbnailUrl, viewCount = info.viewCount,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.date()?.time,
textualUploadDate = info.textualUploadDate, uploadDate = info.uploadDate?.offsetDateTime(),
isUploadDateApproximation = info.uploadDate?.isApproximation
)
@ -95,8 +93,7 @@ data class StreamEntity(
if (viewCount != null) item.viewCount = viewCount as Long
item.textualUploadDate = textualUploadDate
item.uploadDate = uploadDate?.let {
DateWrapper(Calendar.getInstance().apply { time = it }, isUploadDateApproximation
?: false)
DateWrapper(it, isUploadDateApproximation ?: false)
}
return item

View file

@ -646,7 +646,7 @@ public class DownloadDialog extends DialogFragment
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType;
filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix;
filename += (format == MediaFormat.TTML ? MediaFormat.SRT : format).suffix;
break;
default:
throw new RuntimeException("No stream selected");

View file

@ -110,6 +110,7 @@ import org.schabi.newpipe.views.LargeTextMovementMethod;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import icepick.State;
@ -128,7 +129,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfi
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
public class VideoDetailFragment
public final class VideoDetailFragment
extends BaseStateFragment<StreamInfo>
implements BackPressable,
SharedPreferences.OnSharedPreferenceChangeListener,
@ -136,7 +137,7 @@ public class VideoDetailFragment
View.OnLongClickListener,
PlayerServiceExtendedEventListener,
OnKeyDownListener {
public static final String AUTO_PLAY = "auto_play";
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int COMMENTS_UPDATE_FLAG = 0x2;
@ -167,19 +168,23 @@ public class VideoDetailFragment
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
protected String name;
@NonNull
protected String title = "";
@State
protected String url;
protected static PlayQueue playQueue;
@Nullable
protected String url = null;
@Nullable
protected PlayQueue playQueue = null;
@State
int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED;
@State
protected boolean autoPlayEnabled = true;
private static StreamInfo currentInfo;
@Nullable
private StreamInfo currentInfo = null;
private Disposable currentWorker;
@NonNull
private CompositeDisposable disposables = new CompositeDisposable();
private final CompositeDisposable disposables = new CompositeDisposable();
@Nullable
private Disposable positionSubscriber = null;
@ -284,6 +289,7 @@ public class VideoDetailFragment
|| (currentInfo != null
&& isAutoplayEnabled()
&& player.getParentActivity() == null)) {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
}
}
@ -298,8 +304,10 @@ public class VideoDetailFragment
/*////////////////////////////////////////////////////////////////////////*/
public static VideoDetailFragment getInstance(final int serviceId, final String videoUrl,
final String name, final PlayQueue queue) {
public static VideoDetailFragment getInstance(final int serviceId,
@Nullable final String videoUrl,
@NonNull final String name,
@Nullable final PlayQueue queue) {
final VideoDetailFragment instance = new VideoDetailFragment();
instance.setInitialData(serviceId, videoUrl, name, queue);
return instance;
@ -444,8 +452,8 @@ public class VideoDetailFragment
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == Activity.RESULT_OK) {
NavigationHelper
.openVideoDetailFragment(getFM(), serviceId, url, name);
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
serviceId, url, title, null, false);
} else {
Log.e(TAG, "ReCaptcha failed");
}
@ -514,6 +522,7 @@ public class VideoDetailFragment
}
break;
case R.id.detail_thumbnail_root_layout:
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
break;
case R.id.detail_title_root_layout:
@ -530,6 +539,7 @@ public class VideoDetailFragment
player.hideControls(0, 0);
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer();
}
@ -791,7 +801,7 @@ public class VideoDetailFragment
player.onPause();
}
restoreDefaultOrientation();
setAutoplay(false);
setAutoPlay(false);
return true;
}
@ -819,14 +829,11 @@ public class VideoDetailFragment
}
private void setupFromHistoryItem(final StackItem item) {
setAutoplay(false);
setAutoPlay(false);
hideMainPlayer();
setInitialData(
item.getServiceId(),
item.getUrl(),
!TextUtils.isEmpty(item.getTitle()) ? item.getTitle() : "",
item.getPlayQueue());
setInitialData(item.getServiceId(), item.getUrl(),
item.getTitle() == null ? "" : item.getTitle(), item.getPlayQueue());
startLoading(false);
// Maybe an item was deleted in background activity
@ -860,18 +867,17 @@ public class VideoDetailFragment
}
}
public void selectAndLoadVideo(final int sid, final String videoUrl, final String title,
final PlayQueue queue) {
// Situation when user switches from players to main player.
// All needed data is here, we can start watching
if (this.playQueue != null && this.playQueue.equals(queue)) {
openVideoPlayer();
return;
}
setInitialData(sid, videoUrl, title, queue);
if (player != null) {
public void selectAndLoadVideo(final int newServiceId,
@Nullable final String newUrl,
@NonNull final String newTitle,
@Nullable final PlayQueue newQueue) {
if (player != null && newQueue != null && playQueue != null
&& !Objects.equals(newQueue.getItem(), playQueue.getItem())) {
// Preloading can be disabled since playback is surely being replaced.
player.disablePreloadingOfCurrentTrack();
}
setInitialData(newServiceId, newUrl, newTitle, newQueue);
startLoading(false, true);
}
@ -956,7 +962,7 @@ public class VideoDetailFragment
playQueue = new SinglePlayQueue(result);
}
if (stack.isEmpty() || !stack.peek().getPlayQueue().equals(playQueue)) {
stack.push(new StackItem(serviceId, url, name, playQueue));
stack.push(new StackItem(serviceId, url, title, playQueue));
}
}
if (isAutoplayEnabled()) {
@ -977,7 +983,7 @@ public class VideoDetailFragment
if (shouldShowComments()) {
pageAdapter.addFragment(
CommentsFragment.getInstance(serviceId, url, name), COMMENTS_TAB_TAG);
CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG);
}
if (showRelatedStreams && null == relatedStreamsLayout) {
@ -1068,7 +1074,7 @@ public class VideoDetailFragment
}
}
private void openVideoPlayer() {
public void openVideoPlayer() {
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
showExternalPlaybackDialog();
@ -1094,7 +1100,7 @@ public class VideoDetailFragment
private void openMainPlayer() {
if (playerService == null) {
PlayerHolder.startService(App.getApp(), true, this);
PlayerHolder.startService(App.getApp(), autoPlayEnabled, this);
return;
}
if (currentInfo == null) {
@ -1105,11 +1111,13 @@ public class VideoDetailFragment
// Video view can have elements visible from popup,
// We hide it here but once it ready the view will be shown in handleIntent()
playerService.getView().setVisibility(View.GONE);
if (playerService.getView() != null) {
playerService.getView().setVisibility(View.GONE);
}
addVideoPlayerView();
final Intent playerIntent = NavigationHelper
.getPlayerIntent(requireContext(), MainPlayer.class, queue, null, true);
.getPlayerIntent(requireContext(), MainPlayer.class, queue, true, autoPlayEnabled);
activity.startService(playerIntent);
}
@ -1143,8 +1151,8 @@ public class VideoDetailFragment
// Utils
//////////////////////////////////////////////////////////////////////////*/
public void setAutoplay(final boolean autoplay) {
this.autoPlayEnabled = autoplay;
public void setAutoPlay(final boolean autoPlay) {
this.autoPlayEnabled = autoPlay;
}
private void startOnExternalPlayer(@NonNull final Context context,
@ -1166,7 +1174,7 @@ public class VideoDetailFragment
.getBoolean(getString(R.string.use_external_video_player_key), false);
}
// This method overrides default behaviour when setAutoplay() is called.
// This method overrides default behaviour when setAutoPlay() is called.
// Don't auto play if the user selected an external player or disabled it in settings
private boolean isAutoplayEnabled() {
return autoPlayEnabled
@ -1246,9 +1254,9 @@ public class VideoDetailFragment
final DisplayMetrics metrics = getResources().getDisplayMetrics();
if (getView() != null) {
final int height = isInMultiWindow()
? requireView().getHeight()
: activity.getWindow().getDecorView().getHeight();
final int height = (isInMultiWindow()
? requireView()
: activity.getWindow().getDecorView()).getHeight();
setHeightThumbnail(height, metrics);
getView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
}
@ -1269,9 +1277,9 @@ public class VideoDetailFragment
requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
if (player != null && player.isFullscreen()) {
final int height = isInMultiWindow()
? requireView().getHeight()
: activity.getWindow().getDecorView().getHeight();
final int height = (isInMultiWindow()
? requireView()
: activity.getWindow().getDecorView()).getHeight();
// Height is zero when the view is not yet displayed like after orientation change
if (height != 0) {
setHeightThumbnail(height, metrics);
@ -1279,9 +1287,9 @@ public class VideoDetailFragment
requireView().getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
} else {
final int height = isPortrait
? (int) (metrics.widthPixels / (16.0f / 9.0f))
: (int) (metrics.heightPixels / 2.0f);
final int height = (int) (isPortrait
? metrics.widthPixels / (16.0f / 9.0f)
: metrics.heightPixels / 2.0f);
setHeightThumbnail(height, metrics);
}
}
@ -1302,12 +1310,14 @@ public class VideoDetailFragment
contentRootLayoutHiding.setVisibility(View.VISIBLE);
}
protected void setInitialData(final int sid, final String u, final String title,
final PlayQueue queue) {
this.serviceId = sid;
this.url = u;
this.name = !TextUtils.isEmpty(title) ? title : "";
this.playQueue = queue;
protected void setInitialData(final int newServiceId,
@Nullable final String newUrl,
@NonNull final String newTitle,
@Nullable final PlayQueue newPlayQueue) {
this.serviceId = newServiceId;
this.url = newUrl;
this.title = newTitle;
this.playQueue = newPlayQueue;
}
private void setErrorImage(final int imageResource) {
@ -1400,7 +1410,7 @@ public class VideoDetailFragment
animateView(detailPositionView, false, 100);
animateView(positionView, false, 50);
videoTitleTextView.setText(name != null ? name : "");
videoTitleTextView.setText(title);
videoTitleTextView.setMaxLines(1);
animateView(videoTitleTextView, true, 0);
@ -1445,7 +1455,7 @@ public class VideoDetailFragment
}
}
animateView(thumbnailPlayButton, true, 200);
videoTitleTextView.setText(name);
videoTitleTextView.setText(title);
if (!TextUtils.isEmpty(info.getSubChannelName())) {
displayBothUploaderAndSubChannel(info);
@ -1527,7 +1537,7 @@ public class VideoDetailFragment
if (info.getUploadDate() != null) {
videoUploadDateView.setText(Localization
.localizeUploadDate(activity, info.getUploadDate().date().getTime()));
.localizeUploadDate(activity, info.getUploadDate().offsetDateTime()));
videoUploadDateView.setVisibility(View.VISIBLE);
} else {
videoUploadDateView.setText(null);
@ -1735,31 +1745,33 @@ public class VideoDetailFragment
@Override
public void onQueueUpdate(final PlayQueue queue) {
playQueue = queue;
if (DEBUG) {
Log.d(TAG, "onQueueUpdate() called with: serviceId = ["
+ serviceId + "], videoUrl = [" + url + "], name = ["
+ title + "], playQueue = [" + playQueue + "]");
}
// This should be the only place where we push data to stack.
// It will allow to have live instance of PlayQueue with actual information about
// deleted/added items inside Channel/Playlist queue and makes possible to have
// a history of played items
if ((stack.isEmpty() || !stack.peek().getPlayQueue().equals(queue)
&& queue.getItem() != null)) {
stack.push(new StackItem(queue.getItem().getServiceId(),
queue.getItem().getUrl(),
queue.getItem().getTitle(),
queue));
} else {
final StackItem stackWithQueue = findQueueInStack(queue);
if (stackWithQueue != null) {
// On every MainPlayer service's destroy() playQueue gets disposed and
// no longer able to track progress. That's why we update our cached disposed
// queue with the new one that is active and have the same history.
// Without that the cached playQueue will have an old recovery position
stackWithQueue.setPlayQueue(queue);
}
@Nullable final StackItem stackPeek = stack.peek();
if (stackPeek != null && !stackPeek.getPlayQueue().equals(queue)) {
@Nullable final PlayQueueItem playQueueItem = queue.getItem();
if (playQueueItem != null) {
stack.push(new StackItem(playQueueItem.getServiceId(), playQueueItem.getUrl(),
playQueueItem.getTitle(), queue));
return;
} // else continue below
}
if (DEBUG) {
Log.d(TAG, "onQueueUpdate() called with: serviceId = ["
+ serviceId + "], videoUrl = [" + url + "], name = ["
+ name + "], playQueue = [" + playQueue + "]");
@Nullable final StackItem stackWithQueue = findQueueInStack(queue);
if (stackWithQueue != null) {
// On every MainPlayer service's destroy() playQueue gets disposed and
// no longer able to track progress. That's why we update our cached disposed
// queue with the new one that is active and have the same history.
// Without that the cached playQueue will have an old recovery position
stackWithQueue.setPlayQueue(queue);
}
}
@ -1821,7 +1833,7 @@ public class VideoDetailFragment
currentInfo = info;
setInitialData(info.getServiceId(), info.getUrl(), info.getName(), queue);
setAutoplay(false);
setAutoPlay(false);
// Delay execution just because it freezes the main thread, and while playing
// next/previous video you see visual glitches
// (when non-vertical video goes after vertical video)
@ -2035,7 +2047,7 @@ public class VideoDetailFragment
private void checkLandscape() {
if ((!player.isPlaying() && player.getPlayQueue() != playQueue)
|| player.getPlayQueue() == null) {
setAutoplay(true);
setAutoPlay(true);
}
player.checkLandscape();
@ -2062,6 +2074,7 @@ public class VideoDetailFragment
return url == null;
}
@Nullable
private StackItem findQueueInStack(final PlayQueue queue) {
StackItem item = null;
final Iterator<StackItem> iterator = stack.descendingIterator();
@ -2284,10 +2297,10 @@ public class VideoDetailFragment
});
}
private void updateOverlayData(@Nullable final String title,
private void updateOverlayData(@Nullable final String overlayTitle,
@Nullable final String uploader,
@Nullable final String thumbnailUrl) {
overlayTitleTextView.setText(TextUtils.isEmpty(title) ? "" : title);
overlayTitleTextView.setText(TextUtils.isEmpty(overlayTitle) ? "" : overlayTitle);
overlayChannelTextView.setText(TextUtils.isEmpty(uploader) ? "" : uploader);
overlayThumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (!TextUtils.isEmpty(thumbnailUrl)) {

View file

@ -321,8 +321,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
private void onStreamSelected(final StreamInfoItem selectedItem) {
onItemSelected(selectedItem);
NavigationHelper.openVideoDetailFragment(getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName(),
null, false);
}
protected void onScrollToBottom() {
@ -378,11 +379,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
final ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(true);
if (useAsFrontPage) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
} else {
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
supportActionBar.setDisplayHomeAsUpEnabled(!useAsFrontPage);
}
}

View file

@ -50,7 +50,6 @@ import org.schabi.newpipe.util.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -495,13 +494,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
// handling ContentNotSupportedException not to show the error but an appropriate string
// so that crashes won't be sent uselessly and the user will understand what happened
for (Iterator<Throwable> it = errors.iterator(); it.hasNext();) {
final Throwable throwable = it.next();
errors.removeIf(throwable -> {
if (throwable instanceof ContentNotSupportedException) {
showContentNotSupported();
it.remove();
}
}
return throwable instanceof ContentNotSupportedException;
});
if (!errors.isEmpty()) {
showSnackBarError(errors, UserAction.REQUESTED_CHANNEL,
@ -519,7 +517,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
monitorSubscription(result);
headerPlayAllButton.setOnClickListener(view -> NavigationHelper
.playOnMainPlayer(activity, getPlayQueue(), true));
.playOnMainPlayer(activity, getPlayQueue()));
headerPopupButton.setOnClickListener(view -> NavigationHelper
.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view -> NavigationHelper

View file

@ -319,7 +319,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
.subscribe(getPlaylistBookmarkSubscriber());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->

View file

@ -61,7 +61,6 @@ import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
@ -758,16 +757,9 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
// Remove duplicates
final Iterator<SuggestionItem> iterator = networkResult.iterator();
while (iterator.hasNext() && localResult.size() > 0) {
final SuggestionItem next = iterator.next();
for (final SuggestionItem item : localResult) {
if (item.query.equals(next.query)) {
iterator.remove();
break;
}
}
}
networkResult.removeIf(networkItem ->
localResult.stream().anyMatch(localItem ->
localItem.query.equals(networkItem.query)));
if (networkResult.size() > 0) {
result.addAll(networkResult);

View file

@ -0,0 +1,10 @@
package org.schabi.newpipe.ktx
import java.time.OffsetDateTime
import java.time.ZoneId
import java.util.Calendar
import java.util.GregorianCalendar
fun OffsetDateTime.toCalendar(zoneId: ZoneId = ZoneId.systemDefault()): Calendar {
return GregorianCalendar.from(if (zoneId != offset) atZoneSameInstant(zoneId) else toZonedDateTime())
}

View file

@ -26,7 +26,8 @@ import org.schabi.newpipe.util.FallbackViewHolder;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.OnClickGesture;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
@ -69,7 +70,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
private final LocalItemBuilder localItemBuilder;
private final ArrayList<LocalItem> localItems;
private final HistoryRecordManager recordManager;
private final DateFormat dateFormat;
private final DateTimeFormatter dateTimeFormatter;
private boolean showFooter = false;
private boolean useGridVariant = false;
@ -80,8 +81,8 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
recordManager = new HistoryRecordManager(context);
localItemBuilder = new LocalItemBuilder(context);
localItems = new ArrayList<>();
dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,
Localization.getPreferredLocale(context));
dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Localization.getPreferredLocale(context));
}
public void setSelectedListener(final OnClickGesture<LocalItem> listener) {
@ -303,7 +304,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View
}
((LocalItemHolder) holder)
.updateFromItem(localItems.get(position), recordManager, dateFormat);
.updateFromItem(localItems.get(position), recordManager, dateTimeFormatter);
} else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) {
((HeaderFooterHolder) holder).view = header;
} else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader()

View file

@ -7,8 +7,9 @@ import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.Calendar
import java.util.Date
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneOffset
import org.schabi.newpipe.MainActivity.DEBUG
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.database.feed.model.FeedEntity
@ -29,13 +30,8 @@ class FeedDatabaseManager(context: Context) {
/**
* Only items that are newer than this will be saved.
*/
val FEED_OLDEST_ALLOWED_DATE: Calendar = Calendar.getInstance().apply {
add(Calendar.WEEK_OF_YEAR, -13)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val FEED_OLDEST_ALLOWED_DATE: OffsetDateTime = LocalDate.now().minusWeeks(13)
.atStartOfDay().atOffset(ZoneOffset.UTC)
}
fun groups() = feedGroupTable.getAll()
@ -50,12 +46,12 @@ class FeedDatabaseManager(context: Context) {
return streams.map<List<StreamInfoItem>> {
val items = ArrayList<StreamInfoItem>(it.size)
for (streamEntity in it) items.add(streamEntity.toStreamInfoItem())
it.mapTo(items) { it.toStreamInfoItem() }
return@map items
}
}
fun outdatedSubscriptions(outdatedThreshold: Date) = feedTable.getAllOutdated(outdatedThreshold)
fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold)
fun notLoadedCount(groupId: Long = FeedGroupEntity.GROUP_ALL_ID): Flowable<Long> {
return when (groupId) {
@ -64,7 +60,7 @@ class FeedDatabaseManager(context: Context) {
}
}
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: Date) =
fun outdatedSubscriptionsForGroup(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, outdatedThreshold: OffsetDateTime) =
feedTable.getAllOutdatedForGroup(groupId, outdatedThreshold)
fun markAsOutdated(subscriptionId: Long) = feedTable
@ -73,7 +69,7 @@ class FeedDatabaseManager(context: Context) {
fun upsertAll(
subscriptionId: Long,
items: List<StreamInfoItem>,
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time
oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE
) {
val itemsToInsert = ArrayList<StreamInfoItem>()
loop@ for (streamItem in items) {
@ -81,7 +77,7 @@ class FeedDatabaseManager(context: Context) {
itemsToInsert += when {
uploadDate == null && streamItem.streamType == StreamType.LIVE_STREAM -> streamItem
uploadDate != null && uploadDate.date().time >= oldestAllowedDate -> streamItem
uploadDate != null && uploadDate.offsetDateTime() >= oldestAllowedDate -> streamItem
else -> continue@loop
}
}
@ -96,10 +92,11 @@ class FeedDatabaseManager(context: Context) {
feedTable.insertAll(feedEntities)
}
feedTable.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, Calendar.getInstance().time))
feedTable.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId,
OffsetDateTime.now(ZoneOffset.UTC)))
}
fun removeOrphansOrOlderStreams(oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time) {
fun removeOrphansOrOlderStreams(oldestAllowedDate: OffsetDateTime = FEED_OLDEST_ALLOWED_DATE) {
feedTable.unlinkStreamsOlderThan(oldestAllowedDate)
streamTable.deleteOrphans()
}
@ -159,7 +156,7 @@ class FeedDatabaseManager(context: Context) {
.observeOn(AndroidSchedulers.mainThread())
}
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<Date>> {
fun oldestSubscriptionUpdate(groupId: Long): Flowable<List<OffsetDateTime>> {
return when (groupId) {
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
else -> feedTable.oldestSubscriptionUpdate(groupId)

View file

@ -9,11 +9,11 @@ import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Function4
import io.reactivex.schedulers.Schedulers
import java.util.Calendar
import java.util.Date
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.ktx.toCalendar
import org.schabi.newpipe.local.feed.service.FeedEventManager
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
@ -41,7 +41,7 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
feedDatabaseManager.notLoadedCount(groupId),
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<Date> ->
Function4 { t1: FeedEventManager.Event, t2: List<StreamInfoItem>, t3: Long, t4: List<OffsetDateTime> ->
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
}
)
@ -51,8 +51,7 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
.subscribe {
val (event, listFromDB, notLoadedCount, oldestUpdate) = it
val oldestUpdateCalendar =
oldestUpdate?.let { Calendar.getInstance().apply { time = it } }
val oldestUpdateCalendar = oldestUpdate?.toCalendar()
mutableStateLiveData.postValue(when (event) {
is IdleEvent -> FeedState.LoadedState(listFromDB, oldestUpdateCalendar, notLoadedCount)
@ -71,5 +70,5 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
combineDisposable.dispose()
}
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: OffsetDateTime?)
}

View file

@ -41,7 +41,8 @@ import io.reactivex.functions.Function
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import java.io.IOException
import java.util.Calendar
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
@ -161,8 +162,8 @@ class FeedLoadService : Service() {
companion object {
fun wrapList(subscriptionId: Long, info: ListInfo<StreamInfoItem>): List<Throwable> {
val toReturn = ArrayList<Throwable>(info.errors.size)
for (error in info.errors) {
toReturn.add(RequestException(subscriptionId, info.serviceId.toString() + ":" + info.url, error))
info.errors.mapTo(toReturn) {
RequestException(subscriptionId, info.serviceId.toString() + ":" + info.url, it)
}
return toReturn
}
@ -172,9 +173,7 @@ class FeedLoadService : Service() {
private fun startLoading(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, useFeedExtractor: Boolean, thresholdOutdatedSeconds: Int) {
feedResultsHolder = ResultsHolder()
val outdatedThreshold = Calendar.getInstance().apply {
add(Calendar.SECOND, -thresholdOutdatedSeconds)
}.time
val outdatedThreshold = OffsetDateTime.now(ZoneOffset.UTC).minusSeconds(thresholdOutdatedSeconds.toLong())
val subscriptions = when (groupId) {
FeedGroupEntity.GROUP_ALL_ID -> feedDatabaseManager.outdatedSubscriptions(outdatedThreshold)

View file

@ -44,9 +44,10 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import io.reactivex.Completable;
@ -85,7 +86,7 @@ public class HistoryRecordManager {
return Maybe.empty();
}
final Date currentTime = new Date();
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
return Maybe.fromCallable(() -> database.runInTransaction(() -> {
final long streamId = streamTable.upsert(new StreamEntity(info));
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
@ -161,7 +162,7 @@ public class HistoryRecordManager {
return Maybe.empty();
}
final Date currentTime = new Date();
final OffsetDateTime currentTime = OffsetDateTime.now(ZoneOffset.UTC);
final SearchHistoryEntry newEntry = new SearchHistoryEntry(currentTime, serviceId, search);
return Maybe.fromCallable(() -> database.runInTransaction(() -> {

View file

@ -25,6 +25,7 @@ import org.reactivestreams.Subscription;
import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.stream.StreamStatisticsEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog;
@ -43,6 +44,7 @@ import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import icepick.State;
@ -68,18 +70,19 @@ public class StatisticsPlaylistFragment
private HistoryRecordManager recordManager;
private List<StreamStatisticsEntry> processResult(final List<StreamStatisticsEntry> results) {
final Comparator<StreamStatisticsEntry> comparator;
switch (sortMode) {
case LAST_PLAYED:
Collections.sort(results, (left, right) ->
right.getLatestAccessDate().compareTo(left.getLatestAccessDate()));
return results;
comparator = Comparator.comparing(StreamStatisticsEntry::getLatestAccessDate);
break;
case MOST_PLAYED:
Collections.sort(results, (left, right) ->
Long.compare(right.getWatchCount(), left.getWatchCount()));
return results;
comparator = Comparator.comparingLong(StreamStatisticsEntry::getWatchCount);
break;
default:
return null;
}
Collections.sort(results, comparator.reversed());
return results;
}
///////////////////////////////////////////////////////////////////////////
@ -147,11 +150,10 @@ public class StatisticsPlaylistFragment
@Override
public void selected(final LocalItem selectedItem) {
if (selectedItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem;
NavigationHelper.openVideoDetailFragment(getFM(),
item.getStreamEntity().getServiceId(),
item.getStreamEntity().getUrl(),
item.getStreamEntity().getTitle());
final StreamEntity item =
((StreamStatisticsEntry) selectedItem).getStreamEntity();
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
item.getServiceId(), item.getUrl(), item.getTitle(), null, false);
}
}
@ -323,7 +325,7 @@ public class StatisticsPlaylistFragment
}
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->

View file

@ -9,7 +9,7 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
/*
* Created by Christian Schabesberger on 12.02.17.
@ -41,7 +41,7 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder {
}
public abstract void updateFromItem(LocalItem item, HistoryRecordManager historyRecordManager,
DateFormat dateFormat);
DateTimeFormatter dateTimeFormatter);
public void updateState(final LocalItem localItem,
final HistoryRecordManager historyRecordManager) { }

View file

@ -10,7 +10,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
public class LocalPlaylistItemHolder extends PlaylistItemHolder {
public LocalPlaylistItemHolder(final LocalItemBuilder infoItemBuilder, final ViewGroup parent) {
@ -25,7 +25,7 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
@Override
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
if (!(localItem instanceof PlaylistMetadataEntry)) {
return;
}
@ -39,6 +39,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
super.updateFromItem(localItem, historyRecordManager, dateFormat);
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
}
}

View file

@ -20,7 +20,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@ -52,7 +52,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
@Override
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
if (!(localItem instanceof PlaylistStreamEntry)) {
return;
}

View file

@ -20,7 +20,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.views.AnimatedProgressBar;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@ -71,10 +71,10 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
private String getStreamInfoDetailLine(final StreamStatisticsEntry entry,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
final String watchCount = Localization
.shortViewCount(itemBuilder.getContext(), entry.getWatchCount());
final String uploadDate = dateFormat.format(entry.getLatestAccessDate());
final String uploadDate = dateTimeFormatter.format(entry.getLatestAccessDate());
final String serviceName = NewPipe.getNameOfService(entry.getStreamEntity().getServiceId());
return Localization.concatenateStrings(watchCount, uploadDate, serviceName);
}
@ -82,7 +82,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
@Override
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
if (!(localItem instanceof StreamStatisticsEntry)) {
return;
}
@ -116,7 +116,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
}
if (itemAdditionalDetails != null) {
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat));
itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateTimeFormatter));
}
// Default thumbnail is shown on error, while loading and if the url is empty

View file

@ -9,7 +9,7 @@ import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.local.LocalItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
public abstract class PlaylistItemHolder extends LocalItemHolder {
public final ImageView itemThumbnailView;
@ -34,7 +34,7 @@ public abstract class PlaylistItemHolder extends LocalItemHolder {
@Override
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
itemView.setOnClickListener(view -> {
if (itemBuilder.getOnItemSelectedListener() != null) {
itemBuilder.getOnItemSelectedListener().selected(localItem);

View file

@ -11,7 +11,7 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
public class RemotePlaylistItemHolder extends PlaylistItemHolder {
public RemotePlaylistItemHolder(final LocalItemBuilder infoItemBuilder,
@ -27,7 +27,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
@Override
public void updateFromItem(final LocalItem localItem,
final HistoryRecordManager historyRecordManager,
final DateFormat dateFormat) {
final DateTimeFormatter dateTimeFormatter) {
if (!(localItem instanceof PlaylistRemoteEntity)) {
return;
}
@ -48,6 +48,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView,
ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS);
super.updateFromItem(localItem, historyRecordManager, dateFormat);
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
}
}

View file

@ -30,6 +30,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.LocalItem;
import org.schabi.newpipe.database.history.model.StreamHistoryEntry;
import org.schabi.newpipe.database.playlist.PlaylistStreamEntry;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.database.stream.model.StreamStateEntity;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
@ -178,10 +179,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public void selected(final LocalItem selectedItem) {
if (selectedItem instanceof PlaylistStreamEntry) {
final PlaylistStreamEntry item = (PlaylistStreamEntry) selectedItem;
NavigationHelper.openVideoDetailFragment(getFM(),
item.getStreamEntity().getServiceId(), item.getStreamEntity().getUrl(),
item.getStreamEntity().getTitle());
final StreamEntity item =
((PlaylistStreamEntry) selectedItem).getStreamEntity();
NavigationHelper.openVideoDetailFragment(requireContext(), getFM(),
item.getServiceId(), item.getUrl(), item.getTitle(), null, false);
}
}
@ -494,7 +495,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
setVideoCount(itemListAdapter.getItemsList().size());
headerPlayAllButton.setOnClickListener(view ->
NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), true));
NavigationHelper.playOnMainPlayer(activity, getPlayQueue()));
headerPopupButton.setOnClickListener(view ->
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false));
headerBackgroundButton.setOnClickListener(view ->

View file

@ -37,8 +37,8 @@ class FeedGroupDialogViewModel(
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
)
.distinctUntilChanged()
.switchMap { filter ->
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
.switchMap { (query, showOnlyUngrouped) ->
subscriptionManager.getSubscriptions(groupId, query, showOnlyUngrouped)
}.map { list -> list.map { PickerSubscriptionItem(it) } }
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()

View file

@ -2,11 +2,8 @@ package org.schabi.newpipe.player;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
public final class BackgroundPlayerActivity extends ServicePlayerActivity {
@ -46,31 +43,6 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity {
return R.menu.menu_play_queue_bg;
}
@Override
public boolean onPlayerOptionSelected(final MenuItem item) {
if (item.getItemId() == R.id.action_switch_popup) {
if (!PermissionHelper.isPopupEnabled(this)) {
PermissionHelper.showPopupEnablementToast(this);
return true;
}
this.player.setRecovery();
NavigationHelper.playOnPopupPlayer(
getApplicationContext(), player.playQueue, this.player.isPlaying());
return true;
}
if (item.getItemId() == R.id.action_switch_background) {
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(
getApplicationContext(), player.playQueue, this.player.isPlaying());
return true;
}
return false;
}
@Override
public void setupMenu(final Menu menu) {
if (player == null) {

View file

@ -136,7 +136,7 @@ public abstract class BasePlayer implements
@NonNull
public static final String RESUME_PLAYBACK = "resume_playback";
@NonNull
public static final String START_PAUSED = "start_paused";
public static final String PLAY_WHEN_READY = "play_when_ready";
@NonNull
public static final String SELECT_ON_APPEND = "select_on_append";
@NonNull
@ -236,7 +236,7 @@ public abstract class BasePlayer implements
this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
final TrackSelection.Factory trackSelectionFactory = PlayerHelper
.getQualitySelector(context);
.getQualitySelector();
this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
this.loadControl = new LoadController();
@ -316,6 +316,7 @@ public abstract class BasePlayer implements
final boolean samePlayQueue = playQueue != null && playQueue.equals(queue);
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
final boolean isMuted = intent
.getBooleanExtra(IS_MUTED, simpleExoPlayer != null && isMuted());
@ -341,16 +342,20 @@ public abstract class BasePlayer implements
simpleExoPlayer.retry();
}
simpleExoPlayer.seekTo(playQueue.getIndex(), queue.getItem().getRecoveryPosition());
return;
simpleExoPlayer.setPlayWhenReady(playWhenReady);
} else if (samePlayQueue && !playQueue.isDisposed() && simpleExoPlayer != null) {
} else if (simpleExoPlayer != null
&& samePlayQueue
&& playQueue != null
&& !playQueue.isDisposed()) {
// Do not re-init the same PlayQueue. Save time
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
if (simpleExoPlayer.getPlaybackState() == Player.STATE_IDLE) {
simpleExoPlayer.retry();
}
return;
simpleExoPlayer.setPlayWhenReady(playWhenReady);
} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
&& isPlaybackResumeEnabled()
&& !samePlayQueue) {
@ -365,7 +370,7 @@ public abstract class BasePlayer implements
state -> {
queue.setRecovery(queue.getIndex(), state.getProgressTime());
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
playbackSkipSilence, playWhenReady, isMuted);
},
error -> {
if (DEBUG) {
@ -373,24 +378,22 @@ public abstract class BasePlayer implements
}
// In case any error we can start playback without history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
playbackSkipSilence, playWhenReady, isMuted);
},
() -> {
// Completed but not found in history
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch,
playbackSkipSilence, true, isMuted);
playbackSkipSilence, playWhenReady, isMuted);
}
);
databaseUpdateReactor.add(stateLoader);
return;
}
} else {
// Good to go...
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
initPlayback(samePlayQueue ? playQueue : queue, repeatMode, playbackSpeed,
playbackPitch, playbackSkipSilence, playWhenReady, isMuted);
}
// Good to go...
// In a case of equal PlayQueues we can re-init old one but only when it is disposed
initPlayback(samePlayQueue ? playQueue : queue, repeatMode,
playbackSpeed, playbackPitch, playbackSkipSilence,
!intent.getBooleanExtra(START_PAUSED, false),
isMuted);
}
private PlaybackParameters retrievePlaybackParametersFromPreferences() {

View file

@ -30,6 +30,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
@ -225,12 +226,13 @@ public final class MainPlayer extends Service {
// DisplayMetrics from activity context knows about MultiWindow feature
// while DisplayMetrics from app context doesn't
final DisplayMetrics metrics = (playerImpl != null
&& playerImpl.getParentActivity() != null)
? playerImpl.getParentActivity().getResources().getDisplayMetrics()
: getResources().getDisplayMetrics();
&& playerImpl.getParentActivity() != null
? playerImpl.getParentActivity().getResources()
: getResources()).getDisplayMetrics();
return metrics.heightPixels < metrics.widthPixels;
}
@Nullable
public View getView() {
if (playerImpl == null) {
return null;
@ -240,7 +242,7 @@ public final class MainPlayer extends Service {
}
public void removeViewFromParent() {
if (getView().getParent() != null) {
if (getView() != null && getView().getParent() != null) {
if (playerImpl.getParentActivity() != null) {
// This means view was added to fragment
final ViewGroup parent = (ViewGroup) getView().getParent();

View file

@ -120,7 +120,10 @@ public final class NotificationUtil {
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setShowWhen(false)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setColor(ContextCompat.getColor(player.context, R.color.gray))
.setColor(ContextCompat.getColor(player.context, R.color.dark_background_color))
.setColorized(player.sharedPreferences.getBoolean(
player.context.getString(R.string.notification_colorize_key),
true))
.setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT));

View file

@ -27,9 +27,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
@ -42,9 +40,9 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.Collections;
@ -113,9 +111,8 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
public abstract int getPlayerOptionMenuResource();
public abstract boolean onPlayerOptionSelected(MenuItem item);
public abstract void setupMenu(Menu m);
////////////////////////////////////////////////////////////////////////////
// Activity Lifecycle
////////////////////////////////////////////////////////////////////////////
@ -187,12 +184,22 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
return true;
case R.id.action_switch_main:
this.player.setRecovery();
getApplicationContext().startActivity(
getSwitchIntent(MainActivity.class, MainPlayer.PlayerType.VIDEO)
.putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()));
NavigationHelper.playOnMainPlayer(this, player.getPlayQueue(), true);
return true;
case R.id.action_switch_popup:
if (PermissionHelper.isPopupEnabled(this)) {
this.player.setRecovery();
NavigationHelper.playOnPopupPlayer(this, player.playQueue, true);
} else {
PermissionHelper.showPopupEnablementToast(this);
}
return true;
case R.id.action_switch_background:
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(this, player.playQueue, true);
return true;
}
return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
@Override
@ -201,24 +208,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
unbind();
}
protected Intent getSwitchIntent(final Class clazz, final MainPlayer.PlayerType playerType) {
return NavigationHelper.getPlayerIntent(getApplicationContext(), clazz,
this.player.getPlayQueue(), this.player.getRepeatMode(),
this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(),
this.player.getPlaybackSkipSilence(),
null,
true,
!this.player.isPlaying(),
this.player.isMuted())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM)
.putExtra(Constants.KEY_URL, this.player.getVideoUrl())
.putExtra(Constants.KEY_TITLE, this.player.getVideoTitle())
.putExtra(Constants.KEY_SERVICE_ID,
this.player.getCurrentMetadata().getMetadata().getServiceId())
.putExtra(VideoPlayer.PLAYER_TYPE, playerType);
}
////////////////////////////////////////////////////////////////////////////
// Service Connection
////////////////////////////////////////////////////////////////////////////
@ -367,7 +356,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
final MenuItem detail = popupMenu.getMenu().add(RECYCLER_ITEM_POPUP_MENU_GROUP_ID, 1,
Menu.NONE, R.string.play_queue_stream_detail);
detail.setOnMenuItemClickListener(menuItem -> {
onOpenDetail(item.getServiceId(), item.getUrl(), item.getTitle());
// playQueue is null since we don't want any queue change
NavigationHelper.openVideoDetail(this, item.getServiceId(), item.getUrl(),
item.getTitle(), null, false);
return true;
});
@ -454,11 +445,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
};
}
private void onOpenDetail(final int serviceId, final String videoUrl,
final String videoTitle) {
NavigationHelper.openVideoDetail(this, serviceId, videoUrl, videoTitle);
}
private void scrollToSelected() {
if (player == null) {
return;
@ -748,11 +734,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
//2) Icon change accordingly to current App Theme
// using rootView.getContext() because getApplicationContext() didn't work
item.setIcon(player.isMuted()
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
R.attr.ic_volume_off)
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
R.attr.ic_volume_up));
item.setIcon(ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
player.isMuted()
? R.attr.ic_volume_off
: R.attr.ic_volume_up));
}
}
}

View file

@ -76,9 +76,7 @@ import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
@ -97,7 +95,6 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
import org.schabi.newpipe.player.resolver.MediaSourceTag;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.AnimationUtils;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.KoreUtil;
import org.schabi.newpipe.util.ListHelper;
@ -264,7 +261,12 @@ public class VideoPlayerImpl extends VideoPlayer
onQueueClosed();
// Android TV: without it focus will frame the whole player
playPauseButton.requestFocus();
onPlay();
if (simpleExoPlayer.getPlayWhenReady()) {
onPlay();
} else {
onPause();
}
}
NavigationHelper.sendPlayerStartedEvent(service);
}
@ -800,40 +802,6 @@ public class VideoPlayerImpl extends VideoPlayer
setupScreenRotationButton();
}
public void switchFromPopupToMain() {
if (DEBUG) {
Log.d(TAG, "switchFromPopupToMain() called");
}
if (!popupPlayerSelected() || simpleExoPlayer == null || getCurrentMetadata() == null) {
return;
}
setRecovery();
service.removeViewFromParent();
final Intent intent = NavigationHelper.getPlayerIntent(
service,
MainActivity.class,
this.getPlayQueue(),
this.getRepeatMode(),
this.getPlaybackSpeed(),
this.getPlaybackPitch(),
this.getPlaybackSkipSilence(),
null,
true,
!isPlaying(),
isMuted()
);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constants.KEY_SERVICE_ID,
getCurrentMetadata().getMetadata().getServiceId());
intent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
intent.putExtra(Constants.KEY_URL, getVideoUrl());
intent.putExtra(Constants.KEY_TITLE, getVideoTitle());
intent.putExtra(VideoDetailFragment.AUTO_PLAY, true);
service.onDestroy();
context.startActivity(intent);
}
@Override
public void onClick(final View v) {
super.onClick(v);
@ -861,7 +829,9 @@ public class VideoPlayerImpl extends VideoPlayer
} else if (v.getId() == openInBrowser.getId()) {
onOpenInBrowserClicked();
} else if (v.getId() == fullscreenButton.getId()) {
switchFromPopupToMain();
setRecovery();
NavigationHelper.playOnMainPlayer(context, getPlayQueue(), true);
return;
} else if (v.getId() == screenRotationButton.getId()) {
// Only if it's not a vertical video or vertical video but in landscape with locked
// orientation a screen orientation can be changed automatically
@ -1766,10 +1736,13 @@ public class VideoPlayerImpl extends VideoPlayer
updateScreenSize();
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(service);
final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width);
final SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(service);
popupWidth = sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize);
popupWidth = popupRememberSizeAndPos
? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize)
: defaultSize;
popupHeight = getMinimumVideoHeight(popupWidth);
popupLayoutParams = new WindowManager.LayoutParams(
@ -1783,8 +1756,10 @@ public class VideoPlayerImpl extends VideoPlayer
final int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
final int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
popupLayoutParams.x = sharedPreferences.getInt(POPUP_SAVED_X, centerX);
popupLayoutParams.y = sharedPreferences.getInt(POPUP_SAVED_Y, centerY);
popupLayoutParams.x = popupRememberSizeAndPos
? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX;
popupLayoutParams.y = popupRememberSizeAndPos
? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
checkPopupPositionBounds();

View file

@ -0,0 +1,500 @@
package org.schabi.newpipe.player.event
import android.content.Context
import android.os.Handler
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import org.schabi.newpipe.player.BasePlayer
import org.schabi.newpipe.player.MainPlayer
import org.schabi.newpipe.player.VideoPlayerImpl
import org.schabi.newpipe.player.helper.PlayerHelper
import org.schabi.newpipe.util.AnimationUtils
import kotlin.math.abs
import kotlin.math.hypot
import kotlin.math.max
/**
* Base gesture handling for [VideoPlayerImpl]
*
* This class contains the logic for the player gestures like View preparations
* and provides some abstract methods to make it easier separating the logic from the UI.
*/
abstract class BasePlayerGestureListener(
@JvmField
protected val playerImpl: VideoPlayerImpl,
@JvmField
protected val service: MainPlayer
) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener {
// ///////////////////////////////////////////////////////////////////
// Abstract methods for VIDEO and POPUP
// ///////////////////////////////////////////////////////////////////
abstract fun onDoubleTap(event: MotionEvent, portion: DisplayPortion)
abstract fun onSingleTap(playerType: MainPlayer.PlayerType)
abstract fun onScroll(
playerType: MainPlayer.PlayerType,
portion: DisplayPortion,
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
)
abstract fun onScrollEnd(playerType: MainPlayer.PlayerType, event: MotionEvent)
// ///////////////////////////////////////////////////////////////////
// Abstract methods for POPUP (exclusive)
// ///////////////////////////////////////////////////////////////////
abstract fun onPopupResizingStart()
abstract fun onPopupResizingEnd()
private var initialPopupX: Int = -1
private var initialPopupY: Int = -1
private var isMovingInMain = false
private var isMovingInPopup = false
private var isResizing = false
private val tossFlingVelocity = PlayerHelper.getTossFlingVelocity()
// [popup] initial coordinates and distance between fingers
private var initPointerDistance = -1.0
private var initFirstPointerX = -1f
private var initFirstPointerY = -1f
private var initSecPointerX = -1f
private var initSecPointerY = -1f
// ///////////////////////////////////////////////////////////////////
// onTouch implementation
// ///////////////////////////////////////////////////////////////////
override fun onTouch(v: View, event: MotionEvent): Boolean {
return if (playerImpl.popupPlayerSelected()) {
onTouchInPopup(v, event)
} else {
onTouchInMain(v, event)
}
}
private fun onTouchInMain(v: View, event: MotionEvent): Boolean {
playerImpl.gestureDetector.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_UP && isMovingInMain) {
isMovingInMain = false
onScrollEnd(MainPlayer.PlayerType.VIDEO, event)
}
return when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
v.parent.requestDisallowInterceptTouchEvent(playerImpl.isFullscreen)
true
}
MotionEvent.ACTION_UP -> {
v.parent.requestDisallowInterceptTouchEvent(false)
false
}
else -> true
}
}
private fun onTouchInPopup(v: View, event: MotionEvent): Boolean {
playerImpl.gestureDetector.onTouchEvent(event)
if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.")
}
onPopupResizingStart()
// record coordinates of fingers
initFirstPointerX = event.getX(0)
initFirstPointerY = event.getY(0)
initSecPointerX = event.getX(1)
initSecPointerY = event.getY(1)
// record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX.toDouble(),
initFirstPointerY - initSecPointerY.toDouble())
isResizing = true
}
if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
}
return handleMultiDrag(event)
}
if (event.action == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [$v], e1.getRaw = [${event.rawX}" +
", ${event.rawY}]")
}
if (isMovingInPopup) {
isMovingInPopup = false
onScrollEnd(MainPlayer.PlayerType.POPUP, event)
}
if (isResizing) {
isResizing = false
initPointerDistance = (-1).toDouble()
initFirstPointerX = (-1).toFloat()
initFirstPointerY = (-1).toFloat()
initSecPointerX = (-1).toFloat()
initSecPointerY = (-1).toFloat()
onPopupResizingEnd()
playerImpl.changeState(playerImpl.currentState)
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize()
}
}
v.performClick()
return true
}
private fun handleMultiDrag(event: MotionEvent): Boolean {
if (initPointerDistance != -1.0 && event.pointerCount == 2) {
// get the movements of the fingers
val firstPointerMove = hypot(event.getX(0) - initFirstPointerX.toDouble(),
event.getY(0) - initFirstPointerY.toDouble())
val secPointerMove = hypot(event.getX(1) - initSecPointerX.toDouble(),
event.getY(1) - initSecPointerY.toDouble())
// minimum threshold beyond which pinch gesture will work
val minimumMove = ViewConfiguration.get(service).scaledTouchSlop
if (max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
val currentPointerDistance = hypot(event.getX(0) - event.getX(1).toDouble(),
event.getY(0) - event.getY(1).toDouble())
val popupWidth = playerImpl.popupWidth.toDouble()
// change co-ordinates of popup so the center stays at the same position
val newWidth = popupWidth * currentPointerDistance / initPointerDistance
initPointerDistance = currentPointerDistance
playerImpl.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt()
playerImpl.checkPopupPositionBounds()
playerImpl.updateScreenSize()
playerImpl.updatePopupSize(
Math.min(playerImpl.screenWidth.toDouble(), newWidth).toInt(),
-1)
return true
}
}
return false
}
// ///////////////////////////////////////////////////////////////////
// Simple gestures
// ///////////////////////////////////////////////////////////////////
override fun onDown(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onDown called with e = [$e]")
if (isDoubleTapping && isDoubleTapEnabled) {
doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e))
return true
}
return if (playerImpl.popupPlayerSelected())
onDownInPopup(e)
else
true
}
private fun onDownInPopup(e: MotionEvent): Boolean {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize()
playerImpl.checkPopupPositionBounds()
initialPopupX = playerImpl.popupLayoutParams.x
initialPopupY = playerImpl.popupLayoutParams.y
playerImpl.popupWidth = playerImpl.popupLayoutParams.width.toFloat()
playerImpl.popupHeight = playerImpl.popupLayoutParams.height.toFloat()
return super.onDown(e)
}
override fun onDoubleTap(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onDoubleTap called with e = [$e]")
onDoubleTap(e, getDisplayPortion(e))
return true
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (DEBUG)
Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]")
if (isDoubleTapping)
return true
if (playerImpl.popupPlayerSelected()) {
if (playerImpl.player == null)
return false
onSingleTap(MainPlayer.PlayerType.POPUP)
return true
} else {
super.onSingleTapConfirmed(e)
if (playerImpl.currentState == BasePlayer.STATE_BLOCKED)
return true
onSingleTap(MainPlayer.PlayerType.VIDEO)
}
return true
}
override fun onLongPress(e: MotionEvent?) {
if (playerImpl.popupPlayerSelected()) {
playerImpl.updateScreenSize()
playerImpl.checkPopupPositionBounds()
playerImpl.updatePopupSize(playerImpl.screenWidth.toInt(), -1)
}
}
override fun onScroll(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
return if (playerImpl.popupPlayerSelected()) {
onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY)
} else {
onScrollInMain(initialEvent, movingEvent, distanceX, distanceY)
}
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
return if (playerImpl.popupPlayerSelected()) {
val absVelocityX = abs(velocityX)
val absVelocityY = abs(velocityY)
if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.popupLayoutParams.x = velocityX.toInt()
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.popupLayoutParams.y = velocityY.toInt()
}
playerImpl.checkPopupPositionBounds()
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
return true
}
return false
} else {
true
}
}
private fun onScrollInMain(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (!playerImpl.isFullscreen) {
return false
}
val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service)
val isTouchingNavigationBar: Boolean = (initialEvent.y
> playerImpl.rootView.height - getNavigationBarHeight(service))
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false
}
val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD
if (!isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) ||
playerImpl.currentState == BasePlayer.STATE_COMPLETED) {
return false
}
isMovingInMain = true
onScroll(MainPlayer.PlayerType.VIDEO, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
return true
}
private fun onScrollInPopup(
initialEvent: MotionEvent,
movingEvent: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (isResizing) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY)
}
if (!isMovingInPopup) {
AnimationUtils.animateView(playerImpl.closeOverlayButton, true, 200)
}
isMovingInPopup = true
val diffX: Float = (movingEvent.rawX - initialEvent.rawX)
var posX: Float = (initialPopupX + diffX)
val diffY: Float = (movingEvent.rawY - initialEvent.rawY)
var posY: Float = (initialPopupY + diffY)
if (posX > playerImpl.screenWidth - playerImpl.popupWidth) {
posX = (playerImpl.screenWidth - playerImpl.popupWidth)
} else if (posX < 0) {
posX = 0f
}
if (posY > playerImpl.screenHeight - playerImpl.popupHeight) {
posY = (playerImpl.screenHeight - playerImpl.popupHeight)
} else if (posY < 0) {
posY = 0f
}
playerImpl.popupLayoutParams.x = posX.toInt()
playerImpl.popupLayoutParams.y = posY.toInt()
onScroll(MainPlayer.PlayerType.POPUP, getDisplayHalfPortion(initialEvent),
initialEvent, movingEvent, distanceX, distanceY)
playerImpl.windowManager
.updateViewLayout(playerImpl.rootView, playerImpl.popupLayoutParams)
return true
}
// ///////////////////////////////////////////////////////////////////
// Multi double tapping
// ///////////////////////////////////////////////////////////////////
var doubleTapControls: DoubleTapListener? = null
private set
val isDoubleTapEnabled: Boolean
get() = doubleTapDelay > 0
var isDoubleTapping = false
private set
fun doubleTapControls(listener: DoubleTapListener) = apply {
doubleTapControls = listener
}
private var doubleTapDelay = DOUBLE_TAP_DELAY
private val doubleTapHandler: Handler = Handler()
private val doubleTapRunnable = Runnable {
if (DEBUG)
Log.d(TAG, "doubleTapRunnable called")
isDoubleTapping = false
doubleTapControls?.onDoubleTapFinished()
}
fun startMultiDoubleTap(e: MotionEvent) {
if (!isDoubleTapping) {
if (DEBUG)
Log.d(TAG, "startMultiDoubleTap called with e = [$e]")
keepInDoubleTapMode()
doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e))
}
}
fun keepInDoubleTapMode() {
if (DEBUG)
Log.d(TAG, "keepInDoubleTapMode called")
isDoubleTapping = true
doubleTapHandler.removeCallbacks(doubleTapRunnable)
doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay)
}
fun endMultiDoubleTap() {
if (DEBUG)
Log.d(TAG, "endMultiDoubleTap called")
isDoubleTapping = false
doubleTapHandler.removeCallbacks(doubleTapRunnable)
doubleTapControls?.onDoubleTapFinished()
}
fun enableMultiDoubleTap(enable: Boolean) = apply {
doubleTapDelay = if (enable) DOUBLE_TAP_DELAY else 0
}
// ///////////////////////////////////////////////////////////////////
// Utils
// ///////////////////////////////////////////////////////////////////
private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
when {
e.x < playerImpl.popupWidth / 3.0 -> DisplayPortion.LEFT
e.x > playerImpl.popupWidth * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
} else /* MainPlayer.PlayerType.VIDEO */ {
when {
e.x < playerImpl.rootView.width / 3.0 -> DisplayPortion.LEFT
e.x > playerImpl.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT
else -> DisplayPortion.MIDDLE
}
}
}
// Currently needed for scrolling since there is no action more the middle portion
private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion {
return if (playerImpl.playerType == MainPlayer.PlayerType.POPUP) {
when {
e.x < playerImpl.popupWidth / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
} else /* MainPlayer.PlayerType.VIDEO */ {
when {
e.x < playerImpl.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF
else -> DisplayPortion.RIGHT_HALF
}
}
}
private fun getNavigationBarHeight(context: Context): Int {
val resId = context.resources
.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resId > 0) {
context.resources.getDimensionPixelSize(resId)
} else 0
}
private fun getStatusBarHeight(context: Context): Int {
val resId = context.resources
.getIdentifier("status_bar_height", "dimen", "android")
return if (resId > 0) {
context.resources.getDimensionPixelSize(resId)
} else 0
}
companion object {
private const val TAG = "BasePlayerGestListener"
private val DEBUG = BasePlayer.DEBUG
private const val DOUBLE_TAP_DELAY = 550L
private const val MOVEMENT_THRESHOLD = 40
}
}

View file

@ -0,0 +1,5 @@
package org.schabi.newpipe.player.event
enum class DisplayPortion {
LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF
}

View file

@ -0,0 +1,7 @@
package org.schabi.newpipe.player.event
interface DoubleTapListener {
fun onDoubleTapStarted(portion: DisplayPortion) {}
fun onDoubleTapProgressDown(portion: DisplayPortion) {}
fun onDoubleTapFinished() {}
}

View file

@ -1,16 +1,16 @@
package org.schabi.newpipe.player.event;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ProgressBar;
import androidx.appcompat.content.res.AppCompatResources;
import org.jetbrains.annotations.NotNull;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer;
@ -23,217 +23,116 @@ import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME;
import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA;
import static org.schabi.newpipe.util.AnimationUtils.animateView;
/**
* GestureListener for the player
*
* While {@link BasePlayerGestureListener} contains the logic behind the single gestures
* this class focuses on the visual aspect like hiding and showing the controls or changing
* volume/brightness during scrolling for specific events.
*/
public class PlayerGestureListener
extends GestureDetector.SimpleOnGestureListener
extends BasePlayerGestureListener
implements View.OnTouchListener {
private static final String TAG = ".PlayerGestureListener";
private static final boolean DEBUG = BasePlayer.DEBUG;
private final VideoPlayerImpl playerImpl;
private final MainPlayer service;
private int initialPopupX;
private int initialPopupY;
private boolean isMovingInMain;
private boolean isMovingInPopup;
private boolean isResizing;
private final int tossFlingVelocity;
private final boolean isVolumeGestureEnabled;
private final boolean isBrightnessGestureEnabled;
private final int maxVolume;
private static final int MOVEMENT_THRESHOLD = 40;
// [popup] initial coordinates and distance between fingers
private double initPointerDistance = -1;
private float initFirstPointerX = -1;
private float initFirstPointerY = -1;
private float initSecPointerX = -1;
private float initSecPointerY = -1;
public PlayerGestureListener(final VideoPlayerImpl playerImpl, final MainPlayer service) {
this.playerImpl = playerImpl;
this.service = service;
this.tossFlingVelocity = PlayerHelper.getTossFlingVelocity(service);
super(playerImpl, service);
isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service);
isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(service);
maxVolume = playerImpl.getAudioReactor().getMaxVolume();
}
/*//////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////*/
/*
* Main and popup players' gesture listeners is too different.
* So it will be better to have different implementations of them
* */
@Override
public boolean onDoubleTap(final MotionEvent e) {
public void onDoubleTap(@NotNull final MotionEvent event,
@NotNull final DisplayPortion portion) {
if (DEBUG) {
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = "
+ e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
Log.d(TAG, "onDoubleTap called with playerType = ["
+ playerImpl.getPlayerType() + "], portion = ["
+ portion + "]");
}
if (playerImpl.isSomePopupMenuVisible()) {
playerImpl.hideControls(0, 0);
}
if (playerImpl.popupPlayerSelected()) {
return onDoubleTapInPopup(e);
} else {
return onDoubleTapInMain(e);
}
}
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onSingleTapConfirmedInPopup(e);
} else {
return onSingleTapConfirmedInMain(e);
}
}
@Override
public boolean onDown(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onDown() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onDownInPopup(e);
} else {
return true;
}
}
@Override
public void onLongPress(final MotionEvent e) {
if (DEBUG) {
Log.d(TAG, "onLongPress() called with: e = [" + e + "]");
}
if (playerImpl.popupPlayerSelected()) {
onLongPressInPopup(e);
}
}
@Override
public boolean onScroll(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (playerImpl.popupPlayerSelected()) {
return onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY);
} else {
return onScrollInMain(initialEvent, movingEvent, distanceX, distanceY);
}
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2,
final float velocityX, final float velocityY) {
if (DEBUG) {
Log.d(TAG, "onFling() called with velocity: dX=["
+ velocityX + "], dY=[" + velocityY + "]");
}
if (playerImpl.popupPlayerSelected()) {
return onFlingInPopup(e1, e2, velocityX, velocityY);
} else {
return true;
}
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
/*if (DEBUG && false) {
Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
}*/
if (playerImpl.popupPlayerSelected()) {
return onTouchInPopup(v, event);
} else {
return onTouchInMain(v, event);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Main player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInMain(final MotionEvent e) {
if (e.getX() > playerImpl.getRootView().getWidth() * 2.0 / 3.0) {
playerImpl.onFastForward();
} else if (e.getX() < playerImpl.getRootView().getWidth() / 3.0) {
if (portion == DisplayPortion.LEFT) {
playerImpl.onFastRewind();
} else {
playerImpl.getPlayPauseButton().performClick();
} else if (portion == DisplayPortion.MIDDLE) {
playerImpl.onPlayPause();
} else if (portion == DisplayPortion.RIGHT) {
playerImpl.onFastForward();
}
return true;
}
private boolean onSingleTapConfirmedInMain(final MotionEvent e) {
@Override
public void onSingleTap(@NotNull final MainPlayer.PlayerType playerType) {
if (DEBUG) {
Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]");
Log.d(TAG, "onSingleTap called with playerType = ["
+ playerImpl.getPlayerType() + "]");
}
if (playerType == MainPlayer.PlayerType.POPUP) {
if (playerImpl.getCurrentState() == BasePlayer.STATE_BLOCKED) {
return true;
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(100, 100);
} else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide();
}
} else /* playerType == MainPlayer.PlayerType.VIDEO */ {
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(150, 0);
} else {
if (playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
playerImpl.showControls(0);
} else {
playerImpl.showControlsThenHide();
}
}
}
return true;
}
private boolean onScrollInMain(final MotionEvent initialEvent, final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if ((!isVolumeGestureEnabled && !isBrightnessGestureEnabled)
|| !playerImpl.isFullscreen()) {
return false;
@Override
public void onScroll(@NotNull final MainPlayer.PlayerType playerType,
@NotNull final DisplayPortion portion,
@NotNull final MotionEvent initialEvent,
@NotNull final MotionEvent movingEvent,
final float distanceX, final float distanceY) {
if (DEBUG) {
Log.d(TAG, "onScroll called with playerType = ["
+ playerImpl.getPlayerType() + "], portion = ["
+ portion + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (portion == DisplayPortion.LEFT_HALF) {
onScrollMainBrightness(distanceX, distanceY);
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight(service);
final boolean isTouchingNavigationBar = initialEvent.getY()
> playerImpl.getRootView().getHeight() - getNavigationBarHeight(service);
if (isTouchingStatusBar || isTouchingNavigationBar) {
return false;
} else /* DisplayPortion.RIGHT_HALF */ {
onScrollMainVolume(distanceX, distanceY);
}
} else /* MainPlayer.PlayerType.POPUP */ {
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
}
}
/*if (DEBUG && false) Log.d(TAG, "onScrollInMain = " +
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");*/
final boolean insideThreshold =
Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
if (!isMovingInMain && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
return false;
}
isMovingInMain = true;
final boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
final boolean acceptVolumeArea = acceptAnyArea
|| initialEvent.getX() > playerImpl.getRootView().getWidth() / 2.0;
if (isVolumeGestureEnabled && acceptVolumeArea) {
private void onScrollMainVolume(final float distanceX, final float distanceY) {
if (isVolumeGestureEnabled) {
playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY);
final float currentProgressPercent = (float) playerImpl
.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength();
@ -258,10 +157,14 @@ public class PlayerGestureListener
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE);
}
} else {
}
}
private void onScrollMainBrightness(final float distanceX, final float distanceY) {
if (isBrightnessGestureEnabled) {
final Activity parent = playerImpl.getParentActivity();
if (parent == null) {
return true;
return;
}
final Window window = parent.getWindow();
@ -299,330 +202,71 @@ public class PlayerGestureListener
playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE);
}
}
return true;
}
private void onScrollEndInMain() {
@Override
public void onScrollEnd(@NotNull final MainPlayer.PlayerType playerType,
@NotNull final MotionEvent event) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
Log.d(TAG, "onScrollEnd called with playerType = ["
+ playerImpl.getPlayerType() + "]");
}
if (playerType == MainPlayer.PlayerType.VIDEO) {
if (DEBUG) {
Log.d(TAG, "onScrollEnd() called");
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200);
}
if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA,
false, 200, 200);
}
if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) {
animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA,
false, 200, 200);
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
} else {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
}
}
private boolean onTouchInMain(final View v, final MotionEvent event) {
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMovingInMain) {
isMovingInMain = false;
onScrollEndInMain();
}
// This hack allows to stop receiving touch events on appbar
// while touching video player's view
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
v.getParent().requestDisallowInterceptTouchEvent(playerImpl.isFullscreen());
return true;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
return false;
default:
return true;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Popup player listener
//////////////////////////////////////////////////////////////////////////*/
private boolean onDoubleTapInPopup(final MotionEvent e) {
if (playerImpl == null || !playerImpl.isPlaying()) {
return false;
@Override
public void onPopupResizingStart() {
if (DEBUG) {
Log.d(TAG, "onPopupResizingStart called");
}
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
if (e.getX() > playerImpl.getPopupWidth() / 2) {
playerImpl.onFastForward();
} else {
playerImpl.onFastRewind();
}
return true;
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
}
private boolean onSingleTapConfirmedInPopup(final MotionEvent e) {
if (playerImpl == null || playerImpl.getPlayer() == null) {
return false;
@Override
public void onPopupResizingEnd() {
if (DEBUG) {
Log.d(TAG, "onPopupResizingEnd called");
}
if (playerImpl.isControlsVisible()) {
playerImpl.hideControls(100, 100);
} else {
playerImpl.getPlayPauseButton().requestFocus();
playerImpl.showControlsThenHide();
}
return true;
}
private boolean onDownInPopup(final MotionEvent e) {
// Fix popup position when the user touch it, it may have the wrong one
// because the soft input is visible (the draggable area is currently resized).
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
initialPopupX = playerImpl.getPopupLayoutParams().x;
initialPopupY = playerImpl.getPopupLayoutParams().y;
playerImpl.setPopupWidth(playerImpl.getPopupLayoutParams().width);
playerImpl.setPopupHeight(playerImpl.getPopupLayoutParams().height);
return super.onDown(e);
}
private void onLongPressInPopup(final MotionEvent e) {
playerImpl.updateScreenSize();
playerImpl.checkPopupPositionBounds();
playerImpl.updatePopupSize((int) playerImpl.getScreenWidth(), -1);
}
private boolean onScrollInPopup(final MotionEvent initialEvent,
final MotionEvent movingEvent,
final float distanceX,
final float distanceY) {
if (isResizing || playerImpl == null) {
return super.onScroll(initialEvent, movingEvent, distanceX, distanceY);
}
if (!isMovingInPopup) {
animateView(playerImpl.getCloseOverlayButton(), true, 200);
}
isMovingInPopup = true;
final float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX());
float posX = (int) (initialPopupX + diffX);
final float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY());
float posY = (int) (initialPopupY + diffY);
if (posX > (playerImpl.getScreenWidth() - playerImpl.getPopupWidth())) {
posX = (int) (playerImpl.getScreenWidth() - playerImpl.getPopupWidth());
} else if (posX < 0) {
posX = 0;
}
if (posY > (playerImpl.getScreenHeight() - playerImpl.getPopupHeight())) {
posY = (int) (playerImpl.getScreenHeight() - playerImpl.getPopupHeight());
} else if (posY < 0) {
posY = 0;
}
playerImpl.getPopupLayoutParams().x = (int) posX;
playerImpl.getPopupLayoutParams().y = (int) posY;
final View closingOverlayView = playerImpl.getClosingOverlayView();
if (playerImpl.isInsideClosingRadius(movingEvent)) {
if (closingOverlayView.getVisibility() == View.GONE) {
animateView(closingOverlayView, true, 250);
}
} else {
if (closingOverlayView.getVisibility() == View.VISIBLE) {
animateView(closingOverlayView, false, 0);
}
}
// if (DEBUG) {
// Log.d(TAG, "onScrollInPopup = "
// + "e1.getRaw = [" + initialEvent.getRawX() + ", "
// + initialEvent.getRawY() + "], "
// + "e1.getX,Y = [" + initialEvent.getX() + ", "
// + initialEvent.getY() + "], "
// + "e2.getRaw = [" + movingEvent.getRawX() + ", "
// + movingEvent.getRawY() + "], "
// + "e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "], "
// + "distanceX,Y = [" + distanceX + ", " + distanceY + "], "
// + "posX,Y = [" + posX + ", " + posY + "], "
// + "popupW,H = [" + popupWidth + " x " + popupHeight + "]");
// }
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
private void onScrollEndInPopup(final MotionEvent event) {
if (playerImpl == null) {
return;
}
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) {
playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
if (playerImpl.isInsideClosingRadius(event)) {
playerImpl.closePopup();
} else {
animateView(playerImpl.getClosingOverlayView(), false, 0);
if (!playerImpl.isPopupClosing) {
animateView(playerImpl.getCloseOverlayButton(), false, 200);
}
}
}
private boolean onFlingInPopup(final MotionEvent e1,
final MotionEvent e2,
final float velocityX,
final float velocityY) {
if (playerImpl == null) {
return false;
}
final float absVelocityX = Math.abs(velocityX);
final float absVelocityY = Math.abs(velocityY);
if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) {
if (absVelocityX > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().x = (int) velocityX;
}
if (absVelocityY > tossFlingVelocity) {
playerImpl.getPopupLayoutParams().y = (int) velocityY;
}
playerImpl.checkPopupPositionBounds();
playerImpl.windowManager
.updateViewLayout(playerImpl.getRootView(), playerImpl.getPopupLayoutParams());
return true;
}
return false;
}
private boolean onTouchInPopup(final View v, final MotionEvent event) {
if (playerImpl == null) {
return false;
}
playerImpl.getGestureDetector().onTouchEvent(event);
if (event.getPointerCount() == 2 && !isMovingInPopup && !isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.");
}
playerImpl.showAndAnimateControl(-1, true);
playerImpl.getLoadingPanel().setVisibility(View.GONE);
playerImpl.hideControls(0, 0);
animateView(playerImpl.getCurrentDisplaySeek(), false, 0, 0);
animateView(playerImpl.getResizingIndicator(), true, 200, 0);
//record coordinates of fingers
initFirstPointerX = event.getX(0);
initFirstPointerY = event.getY(0);
initSecPointerX = event.getX(1);
initSecPointerY = event.getY(1);
//record distance between fingers
initPointerDistance = Math.hypot(initFirstPointerX - initSecPointerX,
initFirstPointerY - initSecPointerY);
isResizing = true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_MOVE > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
return handleMultiDrag(event);
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (DEBUG) {
Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], "
+ "e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]");
}
if (isMovingInPopup) {
isMovingInPopup = false;
onScrollEndInPopup(event);
}
if (isResizing) {
isResizing = false;
initPointerDistance = -1;
initFirstPointerX = -1;
initFirstPointerY = -1;
initSecPointerX = -1;
initSecPointerY = -1;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
playerImpl.changeState(playerImpl.getCurrentState());
}
if (!playerImpl.isPopupClosing) {
playerImpl.savePositionAndSize();
}
}
v.performClick();
return true;
}
private boolean handleMultiDrag(final MotionEvent event) {
if (initPointerDistance != -1 && event.getPointerCount() == 2) {
// get the movements of the fingers
final double firstPointerMove = Math.hypot(event.getX(0) - initFirstPointerX,
event.getY(0) - initFirstPointerY);
final double secPointerMove = Math.hypot(event.getX(1) - initSecPointerX,
event.getY(1) - initSecPointerY);
// minimum threshold beyond which pinch gesture will work
final int minimumMove = ViewConfiguration.get(service).getScaledTouchSlop();
if (Math.max(firstPointerMove, secPointerMove) > minimumMove) {
// calculate current distance between the pointers
final double currentPointerDistance =
Math.hypot(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
final double popupWidth = playerImpl.getPopupWidth();
// change co-ordinates of popup so the center stays at the same position
final double newWidth = (popupWidth * currentPointerDistance / initPointerDistance);
initPointerDistance = currentPointerDistance;
playerImpl.getPopupLayoutParams().x += (popupWidth - newWidth) / 2;
playerImpl.checkPopupPositionBounds();
playerImpl.updateScreenSize();
playerImpl.updatePopupSize(
(int) Math.min(playerImpl.getScreenWidth(), newWidth),
-1);
return true;
}
}
return false;
}
/*
* Utils
* */
private int getNavigationBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
}
private int getStatusBarHeight(final Context context) {
final int resId = context.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resId > 0) {
return context.getResources().getDimensionPixelSize(resId);
}
return 0;
animateView(playerImpl.getResizingIndicator(), false, 100, 0);
}
}

View file

@ -164,7 +164,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
@Override
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
if (!PlayerHelper.isUsingDSP(context)) {
if (!PlayerHelper.isUsingDSP()) {
return;
}

View file

@ -84,12 +84,12 @@ public final class PlayerHelper {
final int days = (milliSeconds % (86400000 * 7)) / 86400000;
STRING_BUILDER.setLength(0);
return days > 0
return (days > 0
? STRING_FORMATTER.format("%d:%02d:%02d:%02d", days, hours, minutes, seconds)
.toString()
: hours > 0
? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds).toString()
: STRING_FORMATTER.format("%02d:%02d", minutes, seconds).toString();
? STRING_FORMATTER.format("%d:%02d:%02d", hours, minutes, seconds)
: STRING_FORMATTER.format("%02d:%02d", minutes, seconds)
).toString();
}
public static String formatSpeed(final double speed) {
@ -210,6 +210,10 @@ public final class PlayerHelper {
return isBrightnessGestureEnabled(context, true);
}
public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
return isRememberingPopupDimensions(context, true);
}
public static boolean isAutoQueueEnabled(@NonNull final Context context) {
return isAutoQueueEnabled(context, false);
}
@ -295,7 +299,7 @@ public final class PlayerHelper {
return 60000;
}
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context) {
public static TrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory(
1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
@ -303,11 +307,11 @@ public final class PlayerHelper {
AdaptiveTrackSelection.DEFAULT_BANDWIDTH_FRACTION);
}
public static boolean isUsingDSP(@NonNull final Context context) {
public static boolean isUsingDSP() {
return true;
}
public static int getTossFlingVelocity(@NonNull final Context context) {
public static int getTossFlingVelocity() {
return 2500;
}
@ -390,6 +394,12 @@ public final class PlayerHelper {
.getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
}
private static boolean isRememberingPopupDimensions(@NonNull final Context context,
final boolean b) {
return getPreferences(context)
.getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
}
private static boolean isUsingInexactSeek(@NonNull final Context context) {
return getPreferences(context)
.getBoolean(context.getString(R.string.use_inexact_seek_key), false);

View file

@ -49,6 +49,17 @@ public final class PlayerHolder {
return player.getPlayerType();
}
public static boolean isPlaying() {
if (player == null) {
return false;
}
return player.isPlaying();
}
public static boolean isPlayerOpen() {
return player != null;
}
public static void setListener(final PlayerServiceExtendedEventListener newListener) {
listener = newListener;
// Force reload data from service

View file

@ -1,12 +1,8 @@
package org.schabi.newpipe.player.playqueue;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
@ -43,7 +39,6 @@ import io.reactivex.subjects.BehaviorSubject;
* </p>
*/
public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
public static final boolean DEBUG = MainActivity.DEBUG;
private ArrayList<PlayQueueItem> backup;
@ -55,7 +50,6 @@ public abstract class PlayQueue implements Serializable {
private transient BehaviorSubject<PlayQueueEvent> eventBroadcast;
private transient Flowable<PlayQueueEvent> broadcastReceiver;
private transient Subscription reportingReactor;
private transient boolean disposed;
@ -87,10 +81,6 @@ public abstract class PlayQueue implements Serializable {
broadcastReceiver = eventBroadcast.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(AndroidSchedulers.mainThread())
.startWith(new InitEvent());
if (DEBUG) {
broadcastReceiver.subscribe(getSelfReporter());
}
}
/**
@ -100,13 +90,9 @@ public abstract class PlayQueue implements Serializable {
if (eventBroadcast != null) {
eventBroadcast.onComplete();
}
if (reportingReactor != null) {
reportingReactor.cancel();
}
eventBroadcast = null;
broadcastReceiver = null;
reportingReactor = null;
disposed = true;
}
@ -167,19 +153,20 @@ public abstract class PlayQueue implements Serializable {
}
/**
* @return the current item that should be played
* @return the current item that should be played, or null if the queue is empty
*/
@Nullable
public PlayQueueItem getItem() {
return getItem(getIndex());
}
/**
* @param index the index of the item to return
* @return the item at the given index
* @throws IndexOutOfBoundsException
* @return the item at the given index, or null if the index is out of bounds
*/
@Nullable
public PlayQueueItem getItem(final int index) {
if (index < 0 || index >= streams.size() || streams.get(index) == null) {
if (index < 0 || index >= streams.size()) {
return null;
}
return streams.get(index);
@ -543,35 +530,5 @@ public abstract class PlayQueue implements Serializable {
eventBroadcast.onNext(event);
}
}
private Subscriber<PlayQueueEvent> getSelfReporter() {
return new Subscriber<PlayQueueEvent>() {
@Override
public void onSubscribe(final Subscription s) {
if (reportingReactor != null) {
reportingReactor.cancel();
}
reportingReactor = s;
reportingReactor.request(1);
}
@Override
public void onNext(final PlayQueueEvent event) {
Log.d(TAG, "Received broadcast: " + event.type().name() + ". "
+ "Current index: " + getIndex() + ", play queue length: " + size() + ".");
reportingReactor.request(1);
}
@Override
public void onError(final Throwable t) {
Log.e(TAG, "Received broadcast error", t);
}
@Override
public void onComplete() {
Log.d(TAG, "Broadcast is shutting down.");
}
};
}
}

View file

@ -64,6 +64,20 @@ public class PlayQueueItem implements Serializable {
this.recoveryPosition = RECOVERY_UNSET;
}
@Override
public boolean equals(final Object o) {
if (o instanceof PlayQueueItem) {
return url.equals(((PlayQueueItem) o).url);
} else {
return false;
}
}
@Override
public int hashCode() {
return url.hashCode();
}
@NonNull
public String getTitle() {
return title;

View file

@ -4,7 +4,8 @@ import android.os.Bundle;
import androidx.preference.Preference;
import org.schabi.newpipe.CheckForNewAppVersionTask;
import org.schabi.newpipe.App;
import org.schabi.newpipe.CheckForNewAppVersion;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
@ -15,7 +16,7 @@ public class MainSettingsFragment extends BasePreferenceFragment {
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.main_settings);
if (!CheckForNewAppVersionTask.isGithubApk()) {
if (!CheckForNewAppVersion.isGithubApk(App.getApp())) {
final Preference update = findPreference(getString(R.string.update_pref_screen_key));
getPreferenceScreen().removePreference(update);

View file

@ -0,0 +1,19 @@
package org.schabi.newpipe.settings
import android.os.Build
import android.os.Bundle
import androidx.preference.Preference
import org.schabi.newpipe.R
class NotificationSettingsFragment : BasePreferenceFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.notification_settings)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key))
colorizePref?.let {
preferenceScreen.removePreference(it)
}
}
}
}

View file

@ -1,7 +1,6 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -34,6 +33,7 @@ import java.util.List;
import java.util.Vector;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
public class SelectPlaylistFragment extends DialogFragment {
@ -46,12 +46,11 @@ public class SelectPlaylistFragment extends DialogFragment {
private final ImageLoader imageLoader = ImageLoader.getInstance();
private OnSelectedListener onSelectedListener = null;
private OnCancelListener onCancelListener = null;
private ProgressBar progressBar;
private TextView emptyView;
private RecyclerView recyclerView;
private Disposable playlistsSubscriber;
private Disposable disposable = null;
private List<PlaylistLocalItem> playlists = new Vector<>();
@ -59,10 +58,6 @@ public class SelectPlaylistFragment extends DialogFragment {
onSelectedListener = listener;
}
public void setOnCancelListener(final OnCancelListener listener) {
onCancelListener = listener;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
@ -70,15 +65,32 @@ public class SelectPlaylistFragment extends DialogFragment {
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final View v =
inflater.inflate(R.layout.select_playlist_fragment, container, false);
final View v = inflater.inflate(R.layout.select_playlist_fragment, container, false);
progressBar = v.findViewById(R.id.progressBar);
recyclerView = v.findViewById(R.id.items_list);
emptyView = v.findViewById(R.id.empty_state_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
recyclerView.setAdapter(playlistAdapter);
progressBar = v.findViewById(R.id.progressBar);
emptyView = v.findViewById(R.id.empty_state_view);
loadPlaylists();
return v;
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposable != null) {
disposable.dispose();
}
}
/*//////////////////////////////////////////////////////////////////////////
// Load and display playlists
//////////////////////////////////////////////////////////////////////////*/
private void loadPlaylists() {
progressBar.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);
@ -87,43 +99,36 @@ public class SelectPlaylistFragment extends DialogFragment {
final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database);
final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database);
playlistsSubscriber = Flowable.combineLatest(localPlaylistManager.getPlaylists(),
disposable = Flowable.combineLatest(localPlaylistManager.getPlaylists(),
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayPlaylists, this::onError);
return v;
}
@Override
public void onDestroy() {
super.onDestroy();
private void displayPlaylists(final List<PlaylistLocalItem> newPlaylists) {
playlists = newPlaylists;
progressBar.setVisibility(View.GONE);
emptyView.setVisibility(newPlaylists.isEmpty() ? View.VISIBLE : View.GONE);
recyclerView.setVisibility(newPlaylists.isEmpty() ? View.GONE : View.VISIBLE);
}
if (playlistsSubscriber != null) {
playlistsSubscriber.dispose();
playlistsSubscriber = null;
}
protected void onError(final Throwable e) {
final Activity activity = requireActivity();
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo
.make(UserAction.UI_ERROR, "none", "load_playlists", R.string.app_ui_crash));
}
/*//////////////////////////////////////////////////////////////////////////
// Handle actions
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCancel(final DialogInterface dialogInterface) {
super.onCancel(dialogInterface);
if (onCancelListener != null) {
onCancelListener.onCancel();
}
}
private void clickedItem(final int position) {
if (onSelectedListener != null) {
final LocalItem selectedItem = playlists.get(position);
if (selectedItem instanceof PlaylistMetadataEntry) {
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
onSelectedListener
.onLocalPlaylistSelected(entry.uid, entry.name);
onSelectedListener.onLocalPlaylistSelected(entry.uid, entry.name);
} else if (selectedItem instanceof PlaylistRemoteEntity) {
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
@ -134,31 +139,6 @@ public class SelectPlaylistFragment extends DialogFragment {
dismiss();
}
/*//////////////////////////////////////////////////////////////////////////
// Item handling
//////////////////////////////////////////////////////////////////////////*/
private void displayPlaylists(final List<PlaylistLocalItem> newPlaylists) {
this.playlists = newPlaylists;
progressBar.setVisibility(View.GONE);
if (newPlaylists.isEmpty()) {
emptyView.setVisibility(View.VISIBLE);
return;
}
recyclerView.setVisibility(View.VISIBLE);
}
/*//////////////////////////////////////////////////////////////////////////
// Error
//////////////////////////////////////////////////////////////////////////*/
protected void onError(final Throwable e) {
final Activity activity = getActivity();
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
}
/*//////////////////////////////////////////////////////////////////////////
// Interfaces
//////////////////////////////////////////////////////////////////////////*/
@ -168,22 +148,20 @@ public class SelectPlaylistFragment extends DialogFragment {
void onRemotePlaylistSelected(int serviceId, String url, String name);
}
public interface OnCancelListener {
void onCancel();
}
private class SelectPlaylistAdapter
extends RecyclerView.Adapter<SelectPlaylistAdapter.SelectPlaylistItemHolder> {
@NonNull
@Override
public SelectPlaylistItemHolder onCreateViewHolder(final ViewGroup parent,
final int viewType) {
final int viewType) {
final View item = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_playlist_mini_item, parent, false);
return new SelectPlaylistItemHolder(item);
}
@Override
public void onBindViewHolder(final SelectPlaylistItemHolder holder, final int position) {
public void onBindViewHolder(@NonNull final SelectPlaylistItemHolder holder,
final int position) {
final PlaylistLocalItem selectedItem = playlists.get(position);
if (selectedItem instanceof PlaylistMetadataEntry) {

View file

@ -1,10 +1,10 @@
package org.schabi.newpipe.settings;
package org.schabi.newpipe.settings.custom;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -13,18 +13,16 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import java.util.List;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.NotificationConstants;
@ -32,56 +30,35 @@ import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
import java.util.List;
public class NotificationActionsPreference extends Preference {
public NotificationActionsPreference(final Context context, final AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.settings_notification);
}
public class NotificationSettingsFragment extends Fragment {
private Switch scaleSwitch;
private NotificationSlot[] notificationSlots;
private SharedPreferences pref;
private List<Integer> compactSlots;
private String scaleKey;
////////////////////////////////////////////////////////////////////////////
// Lifecycle
////////////////////////////////////////////////////////////////////////////
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pref = PreferenceManager.getDefaultSharedPreferences(requireContext());
scaleKey = getString(R.string.scale_to_square_image_in_notifications_key);
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.itemView.setClickable(false);
setupActions(holder.itemView);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.settings_notification, container, false);
}
@Override
public void onViewCreated(@NonNull final View rootView,
@Nullable final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
setupScaleSwitch(rootView);
setupActions(rootView);
}
@Override
public void onResume() {
super.onResume();
ThemeHelper.setTitleToAppCompatActivity(getActivity(),
getString(R.string.settings_category_notification_title));
}
@Override
public void onPause() {
super.onPause();
public void onDetached() {
super.onDetached();
saveChanges();
requireContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION));
getContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION));
}
@ -89,17 +66,10 @@ public class NotificationSettingsFragment extends Fragment {
// Setup
////////////////////////////////////////////////////////////////////////////
private void setupScaleSwitch(@NonNull final View view) {
scaleSwitch = view.findViewById(R.id.notificationScaleSwitch);
scaleSwitch.setChecked(pref.getBoolean(scaleKey, false));
view.findViewById(R.id.notificationScaleSwitchClickableArea)
.setOnClickListener(v -> scaleSwitch.toggle());
}
private void setupActions(@NonNull final View view) {
compactSlots =
NotificationConstants.getCompactSlotsFromPreferences(requireContext(), pref, 5);
NotificationConstants.getCompactSlotsFromPreferences(
getContext(), getSharedPreferences(), 5);
notificationSlots = new NotificationSlot[5];
for (int i = 0; i < 5; i++) {
notificationSlots[i] = new NotificationSlot(i, view);
@ -112,16 +82,15 @@ public class NotificationSettingsFragment extends Fragment {
////////////////////////////////////////////////////////////////////////////
private void saveChanges() {
final SharedPreferences.Editor editor = pref.edit();
editor.putBoolean(scaleKey, scaleSwitch.isChecked());
final SharedPreferences.Editor editor = getSharedPreferences().edit();
for (int i = 0; i < 3; i++) {
editor.putInt(getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
editor.putInt(getContext().getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
(i < compactSlots.size() ? compactSlots.get(i) : -1));
}
for (int i = 0; i < 5; i++) {
editor.putInt(getString(NotificationConstants.SLOT_PREF_KEYS[i]),
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
notificationSlots[i].selectedAction);
}
@ -183,7 +152,7 @@ public class NotificationSettingsFragment extends Fragment {
} else if (compactSlots.size() < 3) {
compactSlots.add(i);
} else {
Toast.makeText(requireContext(),
Toast.makeText(getContext(),
R.string.notification_actions_at_most_three,
Toast.LENGTH_SHORT).show();
return;
@ -196,7 +165,8 @@ public class NotificationSettingsFragment extends Fragment {
void setupSelectedAction(final View view) {
icon = view.findViewById(R.id.notificationActionIcon);
summary = view.findViewById(R.id.notificationActionSummary);
selectedAction = pref.getInt(getString(NotificationConstants.SLOT_PREF_KEYS[i]),
selectedAction = getSharedPreferences().getInt(
getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
NotificationConstants.SLOT_DEFAULTS[i]);
updateInfo();
}
@ -205,20 +175,20 @@ public class NotificationSettingsFragment extends Fragment {
if (NotificationConstants.ACTION_ICONS[selectedAction] == 0) {
icon.setImageDrawable(null);
} else {
icon.setImageDrawable(AppCompatResources.getDrawable(requireContext(),
icon.setImageDrawable(AppCompatResources.getDrawable(getContext(),
NotificationConstants.ACTION_ICONS[selectedAction]));
}
summary.setText(NotificationConstants.getActionName(requireContext(), selectedAction));
summary.setText(NotificationConstants.getActionName(getContext(), selectedAction));
}
void openActionChooserDialog() {
final LayoutInflater inflater = LayoutInflater.from(requireContext());
final LayoutInflater inflater = LayoutInflater.from(getContext());
final LinearLayout rootLayout = (LinearLayout) inflater.inflate(
R.layout.single_choice_dialog_view, null, false);
final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list);
final AlertDialog alertDialog = new AlertDialog.Builder(requireContext())
final AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setTitle(SLOT_TITLES[i])
.setView(radioGroup)
.setCancelable(true)
@ -237,10 +207,10 @@ public class NotificationSettingsFragment extends Fragment {
// if present set action icon with correct color
if (NotificationConstants.ACTION_ICONS[action] != 0) {
Drawable drawable = AppCompatResources.getDrawable(requireContext(),
Drawable drawable = AppCompatResources.getDrawable(getContext(),
NotificationConstants.ACTION_ICONS[action]);
if (drawable != null) {
final int color = ThemeHelper.resolveColorFromAttr(requireContext(),
final int color = ThemeHelper.resolveColorFromAttr(getContext(),
android.R.attr.textColorPrimary);
drawable = DrawableCompat.wrap(drawable).mutate();
DrawableCompat.setTint(drawable, color);
@ -249,7 +219,7 @@ public class NotificationSettingsFragment extends Fragment {
}
}
radioButton.setText(NotificationConstants.getActionName(requireContext(), action));
radioButton.setText(NotificationConstants.getActionName(getContext(), action));
radioButton.setChecked(action == selectedAction);
radioButton.setId(id);
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
@ -259,7 +229,7 @@ public class NotificationSettingsFragment extends Fragment {
}
alertDialog.show();
if (DeviceUtils.isTv(requireContext())) {
if (DeviceUtils.isTv(getContext())) {
FocusOverlayView.setupFocusObserver(alertDialog);
}
}

View file

@ -18,6 +18,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@ -265,10 +266,8 @@ public final class ListHelper {
*/
private static void sortStreamList(final List<VideoStream> videoStreams,
final boolean ascendingOrder) {
Collections.sort(videoStreams, (o1, o2) -> {
final int result = compareVideoStreamResolution(o1, o2);
return result == 0 ? 0 : (ascendingOrder ? result : -result);
});
final Comparator<VideoStream> comparator = ListHelper::compareVideoStreamResolution;
Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed());
}
/**

View file

@ -23,11 +23,13 @@ import org.schabi.newpipe.extractor.localization.ContentCountry;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@ -139,13 +141,16 @@ public final class Localization {
return nf.format(number);
}
public static String formatDate(final Date date, final Context context) {
return DateFormat.getDateInstance(DateFormat.MEDIUM, getAppLocale(context)).format(date);
public static String formatDate(final OffsetDateTime offsetDateTime, final Context context) {
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(getAppLocale(context)).format(offsetDateTime
.atZoneSameInstant(ZoneId.systemDefault()));
}
@SuppressLint("StringFormatInvalid")
public static String localizeUploadDate(final Context context, final Date date) {
return context.getString(R.string.upload_date_text, formatDate(date, context));
public static String localizeUploadDate(final Context context,
final OffsetDateTime offsetDateTime) {
return context.getString(R.string.upload_date_text, formatDate(offsetDateTime, context));
}
public static String localizeViewCount(final Context context, final long viewCount) {
@ -186,7 +191,7 @@ public final class Localization {
}
public static String shortCount(final Context context, final long count) {
if (Build.VERSION.SDK_INT >= 24) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return CompactDecimalFormat.getInstance(getAppLocale(context),
CompactDecimalFormat.CompactStyle.SHORT).format(count);
}

View file

@ -18,7 +18,6 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.ImageLoader;
@ -38,7 +37,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
import org.schabi.newpipe.fragments.list.search.SearchFragment;
@ -52,13 +50,14 @@ import org.schabi.newpipe.player.BackgroundPlayerActivity;
import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.VideoPlayer;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity;
import java.util.ArrayList;
@SuppressWarnings({"unused"})
public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
@ -73,7 +72,6 @@ public final class NavigationHelper {
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
@Nullable final String quality,
final boolean resumePlayback) {
final Intent intent = new Intent(context, targetClazz);
@ -83,9 +81,6 @@ public final class NavigationHelper {
intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
}
}
if (quality != null) {
intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality);
}
intent.putExtra(VideoPlayer.RESUME_PLAYBACK, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_VIDEO);
@ -96,8 +91,10 @@ public final class NavigationHelper {
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
final boolean resumePlayback) {
return getPlayerIntent(context, targetClazz, playQueue, null, resumePlayback);
final boolean resumePlayback,
final boolean playWhenReady) {
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
.putExtra(BasePlayer.PLAY_WHEN_READY, playWhenReady);
}
@NonNull
@ -111,61 +108,25 @@ public final class NavigationHelper {
.putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend);
}
@NonNull
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
final int repeatMode,
final float playbackSpeed,
final float playbackPitch,
final boolean playbackSkipSilence,
@Nullable final String playbackQuality,
final boolean resumePlayback,
final boolean startPaused,
final boolean isMuted) {
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback)
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
.putExtra(BasePlayer.START_PAUSED, startPaused)
.putExtra(BasePlayer.IS_MUTED, isMuted);
}
public static void playOnMainPlayer(final AppCompatActivity activity,
final PlayQueue queue,
final boolean autoPlay) {
playOnMainPlayer(activity.getSupportFragmentManager(), queue, autoPlay);
@NonNull final PlayQueue playQueue) {
final PlayQueueItem item = playQueue.getItem();
assert item != null;
openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, false);
}
public static void playOnMainPlayer(final FragmentManager fragmentManager,
final PlayQueue queue,
final boolean autoPlay) {
final PlayQueueItem currentStream = queue.getItem();
openVideoDetailFragment(
fragmentManager,
currentStream.getServiceId(),
currentStream.getUrl(),
currentStream.getTitle(),
autoPlay,
queue);
public static void playOnMainPlayer(final Context context,
@NonNull final PlayQueue playQueue,
final boolean switchingPlayers) {
final PlayQueueItem item = playQueue.getItem();
assert item != null;
openVideoDetail(context,
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, switchingPlayers);
}
public static void playOnMainPlayer(@NonNull final Context context,
@Nullable final PlayQueue queue,
@NonNull final StreamingService.LinkType linkType,
@NonNull final String url,
@NonNull final String title,
final boolean autoPlay,
final boolean resumePlayback) {
final Intent intent = getPlayerIntent(context, MainActivity.class, queue, resumePlayback);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constants.KEY_LINK_TYPE, linkType);
intent.putExtra(Constants.KEY_URL, url);
intent.putExtra(Constants.KEY_TITLE, title);
intent.putExtra(VideoDetailFragment.AUTO_PLAY, autoPlay);
context.startActivity(intent);
}
public static void playOnPopupPlayer(final Context context, final PlayQueue queue,
public static void playOnPopupPlayer(final Context context,
final PlayQueue queue,
final boolean resumePlayback) {
if (!PermissionHelper.isPopupEnabled(context)) {
PermissionHelper.showPopupEnablementToast(context);
@ -300,9 +261,6 @@ public final class NavigationHelper {
.setNegativeButton(R.string.cancel, (dialog, which)
-> Log.i("NavigationHelper", "You unlocked a secret unicorn."))
.show();
// Log.e("NavigationHelper",
// "Either no Streaming player for audio was installed, "
// + "or something important crashed:");
} else {
Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show();
}
@ -358,41 +316,6 @@ public final class NavigationHelper {
.commit();
}
public static void openVideoDetailFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
final String title) {
openVideoDetailFragment(fragmentManager, serviceId, url, title, true, null);
}
public static void openVideoDetailFragment(
final FragmentManager fragmentManager,
final int serviceId,
final String url,
final String title,
final boolean autoPlay,
final PlayQueue playQueue) {
final Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
expandMainPlayer(fragment.requireActivity());
final VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
detailFragment.setAutoplay(autoPlay);
detailFragment
.selectAndLoadVideo(serviceId, url, title == null ? "" : title, playQueue);
detailFragment.scrollToTop();
return;
}
final VideoDetailFragment instance = VideoDetailFragment
.getInstance(serviceId, url, title == null ? "" : title, playQueue);
instance.setAutoplay(autoPlay);
defaultTransaction(fragmentManager)
.replace(R.id.fragment_player_holder, instance)
.runOnCommit(() -> expandMainPlayer(instance.requireActivity()))
.commit();
}
public static void expandMainPlayer(final Context context) {
context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_SHOW_MAIN_PLAYER));
}
@ -409,33 +332,76 @@ public final class NavigationHelper {
.commitAllowingStateLoss();
}
public static void openChannelFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
final String name) {
defaultTransaction(fragmentManager)
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url,
name == null ? "" : name))
.addToBackStack(null)
.commit();
private interface RunnableWithVideoDetailFragment {
void run(VideoDetailFragment detailFragment);
}
public static void openCommentsFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
final String name) {
fragmentManager.beginTransaction()
.setCustomAnimations(R.anim.switch_service_in, R.anim.switch_service_out)
.replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url,
name == null ? "" : name))
public static void openVideoDetailFragment(@NonNull final Context context,
@NonNull final FragmentManager fragmentManager,
final int serviceId,
@Nullable final String url,
@NonNull final String title,
@Nullable final PlayQueue playQueue,
final boolean switchingPlayers) {
final boolean autoPlay;
@Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getType();
if (playerType == null) {
// no player open
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
} else if (switchingPlayers) {
// switching player to main player
autoPlay = PlayerHolder.isPlaying(); // keep play/pause state
} else if (playerType == MainPlayer.PlayerType.VIDEO) {
// opening new stream while already playing in main player
autoPlay = PlayerHelper.isAutoplayAllowedByUser(context);
} else {
// opening new stream while already playing in another player
autoPlay = false;
}
final RunnableWithVideoDetailFragment onVideoDetailFragmentReady = (detailFragment) -> {
expandMainPlayer(detailFragment.requireActivity());
detailFragment.setAutoPlay(autoPlay);
if (switchingPlayers) {
// Situation when user switches from players to main player. All needed data is
// here, we can start watching (assuming newQueue equals playQueue).
detailFragment.openVideoPlayer();
} else {
detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue);
}
detailFragment.scrollToTop();
};
final Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_player_holder);
if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
onVideoDetailFragmentReady.run((VideoDetailFragment) fragment);
} else {
final VideoDetailFragment instance = VideoDetailFragment
.getInstance(serviceId, url, title, playQueue);
instance.setAutoPlay(autoPlay);
defaultTransaction(fragmentManager)
.replace(R.id.fragment_player_holder, instance)
.runOnCommit(() -> onVideoDetailFragmentReady.run(instance))
.commit();
}
}
public static void openChannelFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
@NonNull final String name) {
defaultTransaction(fragmentManager)
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}
public static void openPlaylistFragment(final FragmentManager fragmentManager,
final int serviceId, final String url,
final String name) {
@NonNull final String name) {
defaultTransaction(fragmentManager)
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url,
name == null ? "" : name))
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}
@ -511,33 +477,26 @@ public final class NavigationHelper {
context.startActivity(mIntent);
}
public static void openChannel(final Context context, final int serviceId, final String url) {
openChannel(context, serviceId, url, null);
}
public static void openVideoDetail(final Context context,
final int serviceId,
final String url,
@NonNull final String title,
@Nullable final PlayQueue playQueue,
final boolean switchingPlayers) {
public static void openChannel(final Context context, final int serviceId,
final String url, final String name) {
final Intent openIntent = getOpenIntent(context, url, serviceId,
StreamingService.LinkType.CHANNEL);
if (name != null && !name.isEmpty()) {
openIntent.putExtra(Constants.KEY_TITLE, name);
}
context.startActivity(openIntent);
}
public static void openVideoDetail(final Context context, final int serviceId,
final String url) {
openVideoDetail(context, serviceId, url, null);
}
public static void openVideoDetail(final Context context, final int serviceId,
final String url, final String title) {
final Intent openIntent = getOpenIntent(context, url, serviceId,
final Intent intent = getOpenIntent(context, url, serviceId,
StreamingService.LinkType.STREAM);
if (title != null && !title.isEmpty()) {
openIntent.putExtra(Constants.KEY_TITLE, title);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Constants.KEY_TITLE, title);
intent.putExtra(VideoDetailFragment.KEY_SWITCHING_PLAYERS, switchingPlayers);
if (playQueue != null) {
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
if (cacheKey != null) {
intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey);
}
}
context.startActivity(openIntent);
context.startActivity(intent);
}
public static void openMainActivity(final Context context) {
@ -550,7 +509,6 @@ public final class NavigationHelper {
public static void openRouterActivity(final Context context, final String url) {
final Intent mIntent = new Intent(context, RouterActivity.class);
mIntent.setData(Uri.parse(url));
mIntent.putExtra(RouterActivity.INTERNAL_ROUTE_KEY, true);
context.startActivity(mIntent);
}
@ -564,14 +522,12 @@ public final class NavigationHelper {
context.startActivity(intent);
}
public static boolean openDownloads(final Activity activity) {
if (!PermissionHelper.checkStoragePermissions(
public static void openDownloads(final Activity activity) {
if (PermissionHelper.checkStoragePermissions(
activity, PermissionHelper.DOWNLOADS_REQUEST_CODE)) {
return false;
final Intent intent = new Intent(activity, DownloadActivity.class);
activity.startActivity(intent);
}
final Intent intent = new Intent(activity, DownloadActivity.class);
activity.startActivity(intent);
return true;
}
public static Intent getPlayQueueActivityIntent(final Context context) {
@ -600,7 +556,8 @@ public final class NavigationHelper {
return getIntentByLink(context, NewPipe.getServiceByUrl(url), url);
}
public static Intent getIntentByLink(final Context context, final StreamingService service,
public static Intent getIntentByLink(final Context context,
final StreamingService service,
final String url) throws ExtractionException {
final StreamingService.LinkType linkType = service.getLinkTypeByUrl(url);
@ -609,15 +566,7 @@ public final class NavigationHelper {
+ " url=" + url);
}
final Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType);
if (linkType == StreamingService.LinkType.STREAM) {
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY,
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getString(R.string.autoplay_through_intent_key), false));
}
return rIntent;
return getOpenIntent(context, url, service.getServiceId(), linkType);
}
private static Uri openMarketUrl(final String packageName) {

View file

@ -1,8 +1,7 @@
package org.schabi.newpipe.views;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Build;
import android.util.AttributeSet;
import android.view.SurfaceView;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
@ -47,7 +46,8 @@ public class ExpandableSurfaceView extends SurfaceView {
if (resizeMode == RESIZE_MODE_FIT
// KitKat doesn't work well when a view has a scale like needed for ZOOM
|| (resizeMode == RESIZE_MODE_ZOOM && VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)) {
|| (resizeMode == RESIZE_MODE_ZOOM
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)) {
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {

View file

@ -270,7 +270,7 @@ public final class FocusOverlayView extends Drawable implements
clearFocusObstacles((ViewGroup) decor);
}
@RequiresApi(api = 26)
@RequiresApi(api = Build.VERSION_CODES.O)
private static void clearFocusObstacles(final ViewGroup viewGroup) {
viewGroup.setTouchscreenBlocksFocus(false);

View file

@ -35,6 +35,10 @@ public abstract class Mission implements Serializable {
*/
public StoredFileHelper storage;
public long getTimestamp() {
return timestamp;
}
/**
* Delete the downloaded file
*

View file

@ -212,7 +212,7 @@ public class StoredDirectoryHelper {
@NonNull
@Override
public String toString() {
return docTree == null ? Uri.fromFile(ioTree).toString() : docTree.getUri().toString();
return (docTree == null ? Uri.fromFile(ioTree) : docTree.getUri()).toString();
}

View file

@ -12,7 +12,8 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Comparator;
import java.util.List;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission;
@ -198,7 +199,7 @@ public class DownloadManager {
}
if (mMissionsPending.size() > 1)
Collections.sort(mMissionsPending, (mission1, mission2) -> Long.compare(mission1.timestamp, mission2.timestamp));
Collections.sort(mMissionsPending, Comparator.comparingLong(Mission::getTimestamp));
}
/**
@ -563,14 +564,10 @@ public class DownloadManager {
synchronized (DownloadManager.this) {
ArrayList<Mission> pending = new ArrayList<>(mMissionsPending);
ArrayList<Mission> finished = new ArrayList<>(mMissionsFinished);
ArrayList<Mission> remove = new ArrayList<>(hidden);
List<Mission> remove = new ArrayList<>(hidden);
// hide missions (if required)
Iterator<Mission> iterator = remove.iterator();
while (iterator.hasNext()) {
Mission mission = iterator.next();
if (pending.remove(mission) || finished.remove(mission)) iterator.remove();
}
remove.removeIf(mission -> pending.remove(mission) || finished.remove(mission));
int fakeTotal = pending.size();
if (fakeTotal > 0) fakeTotal++;

View file

@ -1,13 +1,11 @@
package us.shandian.giga.ui.adapter;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
@ -26,10 +24,8 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.DiffUtil;
@ -47,12 +43,15 @@ import org.schabi.newpipe.report.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
import java.io.File;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission;
@ -117,11 +116,13 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
private final Runnable rUpdater = this::updater;
private final Runnable rDelete = this::deleteFinishedDownloads;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public MissionAdapter(Context context, @NonNull DownloadManager downloadManager, View emptyMessage, View root) {
mContext = context;
mDownloadManager = downloadManager;
mInflater = ContextCompat.getSystemService(mContext, LayoutInflater.class);
mInflater = LayoutInflater.from(mContext);
mLayout = R.layout.mission_item;
mHandler = new Handler(context.getMainLooper());
@ -676,7 +677,30 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
return true;
case R.id.md5:
case R.id.sha1:
new ChecksumTask(mContext).execute(h.item.mission.storage, ALGORITHMS.get(id));
ProgressDialog progressDialog = null;
if (mContext != null) {
// Create dialog
progressDialog = new ProgressDialog(mContext);
progressDialog.setCancelable(false);
progressDialog.setMessage(mContext.getString(R.string.msg_wait));
progressDialog.show();
}
final ProgressDialog finalProgressDialog = progressDialog;
final StoredFileHelper storage = h.item.mission.storage;
compositeDisposable.add(
Observable.fromCallable(() -> Utility.checksum(storage, ALGORITHMS.get(id)))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (finalProgressDialog != null) {
Utility.copyToClipboard(finalProgressDialog.getContext(),
result);
if (mContext != null) {
finalProgressDialog.dismiss();
}
}
})
);
return true;
case R.id.source:
/*Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(h.item.mission.source));
@ -759,8 +783,8 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
}
}
public void onDestroy() {
compositeDisposable.dispose();
mDeleter.dispose();
}
@ -961,60 +985,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
}
}
static class ChecksumTask extends AsyncTask<Object, Void, String> {
ProgressDialog progressDialog;
WeakReference<Activity> weakReference;
ChecksumTask(@NonNull Context context) {
weakReference = new WeakReference<>((Activity) context);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
Activity activity = getActivity();
if (activity != null) {
// Create dialog
progressDialog = new ProgressDialog(activity);
progressDialog.setCancelable(false);
progressDialog.setMessage(activity.getString(R.string.msg_wait));
progressDialog.show();
}
}
@Override
protected String doInBackground(Object... params) {
return Utility.checksum((StoredFileHelper) params[0], (String) params[1]);
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (progressDialog != null) {
Utility.copyToClipboard(progressDialog.getContext(), result);
if (getActivity() != null) {
progressDialog.dismiss();
}
}
}
@Nullable
private Activity getActivity() {
Activity activity = weakReference.get();
if (activity != null && activity.isFinishing()) {
return null;
} else {
return activity;
}
}
}
public interface RecoverHelper {
void tryRecover(DownloadMission mission);
}
}

View file

@ -83,8 +83,8 @@ public class ProgressDrawable extends Drawable {
// render marquee
width += size * 2;
Path marquee = new Path();
for (float i = -size; i < width; i += size) {
marquee.addPath(mMarqueeLine, i + mMarqueeProgress, 0);
for (int i = -size; i < width; i += size) {
marquee.addPath(mMarqueeLine, ((float)i + mMarqueeProgress), 0);
}
marquee.close();

View file

@ -224,9 +224,10 @@ public class MissionsFragment extends Fragment {
mList.setAdapter(mAdapter);
if (mSwitch != null) {
mSwitch.setIcon(mLinear
? ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_grid)
: ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_list));
mSwitch.setIcon(ThemeHelper.resolveResourceIdFromAttr(
requireContext(), mLinear
? R.attr.ic_grid
: R.attr.ic_list));
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
mPrefs.edit().putBoolean("linear", mLinear).apply();
}

View file

@ -1,78 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingTop="16dp">
<Switch
android:id="@+id/notificationScaleSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:clickable="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="@+id/textView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView" />
<TextView
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@string/notification_scale_to_square_image_title"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@+id/notificationScaleSwitch"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/notification_scale_to_square_image_summary"
app:layout_constraintEnd_toStartOf="@+id/notificationScaleSwitch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<View
android:id="@+id/notificationScaleSwitchClickableArea"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toTopOf="@+id/divider"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView
android:id="@+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:clickable="false"
android:focusable="false"
@ -81,7 +20,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider" />
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/notificationAction0"
@ -91,7 +30,7 @@
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4" />
app:layout_constraintTop_toBottomOf="@+id/textView" />
<include
android:id="@+id/notificationAction1"
@ -130,4 +69,3 @@
app:layout_constraintTop_toBottomOf="@+id/notificationAction3" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -49,7 +49,7 @@
<string name="use_external_video_player_title">استخدام مشغل فيديو خارجي</string>
<string name="use_tor_summary">(إختبارية) إجراء التنزيلات من خلال استخدام بروكسي Tor لزيادة الخصوصية ( تشغيل الفيديو المباشر غير مدعوم حتى الأن ).</string>
<string name="use_tor_title">استخدام تور</string>
<string name="view_count_text">%1$s مشاهدات</string>
<string name="view_count_text">%1$s مشاهدة</string>
<string name="content_not_available">محتوى غير متوفر</string>
<string name="could_not_load_thumbnails">تعذرت عملية تحميل كافة صور المعاينة</string>
<string name="general_error">خطأ</string>
@ -90,6 +90,8 @@
<string name="show_higher_resolutions_title">عرض أعلى جودة</string>
<string name="show_higher_resolutions_summary">بعض الأجهزة فقط تدعم تشغيل مقاطع الفيديو 2K/4K</string>
<string name="default_video_format_title">تنسيق الفيديو الافتراضي</string>
<string name="popup_remember_size_pos_title">تذكر خصائص النوافذ المنبثقة</string>
<string name="popup_remember_size_pos_summary">تذكر آخر مكان و حجم للنافذة المنبثقة</string>
<string name="player_gesture_controls_title">اعدادات إيماءة المشغل</string>
<string name="player_gesture_controls_summary">استخدم الإيماءات للتحكم في سطوع وصوت المشغل</string>
<string name="show_search_suggestions_title">اقتراحات البحث</string>
@ -98,17 +100,16 @@
<string name="enable_search_history_summary">تخزين طلبات البحث محليا</string>
<string name="enable_watch_history_summary">تتبع مقاطع الفيديو التي تمت مشاهدتها</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_summary">إظهار تلميح عندما الضغط على الخلفية أو الزر المنبثق في الفيديو \"تفاصيل:\"</string>
<string name="show_hold_to_append_summary">عرض تلميح عند الضغط على الخلفية أو الزر المنبثق في الفيديو \"التفاصيل:\"</string>
<string name="settings_category_player_title">المشغل</string>
<string name="settings_category_player_behavior_title">السلوك</string>
<string name="settings_category_popup_title">الوضع المنبثق</string>
<string name="popup_playing_toast">تشغيل في وضع منبثق</string>
<string name="background_player_append">تم وضعه على قائمة الانتظار في مشغل الخلفية</string>
<string name="popup_playing_append">تم وضعه على قائمة الانتظار في مشغل النافذة المنبثقة</string>
<string name="show_age_restricted_content_title">محتوى مقيد بحسب العمر</string>
<string name="video_is_age_restricted">إظهار الفيديو المقيد بحسب العمر. التغييرات المستقبلية ممكنة من \"الإعدادات\".</string>
<string name="show_age_restricted_content_title">إظهار محتوى مقيد حسب العمر</string>
<string name="duration_live">بث مباشر</string>
<string name="error_report_title">تقرير خطأ</string>
<string name="playlist">قائمة التشغيل</string>
@ -155,9 +156,9 @@
<string name="short_billion">بليون</string>
<string name="no_subscribers">ليس هناك مشترِكون</string>
<plurals name="subscribers">
<item quantity="zero">%s لا مشترِك</item>
<item quantity="one">%s مشترِك</item>
<item quantity="two">مشتركين</item>
<item quantity="zero">%s مشترك</item>
<item quantity="one">%s مشترك</item>
<item quantity="two">%s مشتركين</item>
<item quantity="few">%s مشتركين</item>
<item quantity="many">%s مشتركين</item>
<item quantity="other">%s مشتركين</item>
@ -232,12 +233,12 @@
<string name="title_activity_recaptcha">تحدي الكابتشا</string>
<string name="hold_to_append">ضغط مطول للإدراج الى قائمة الانتظار</string>
<plurals name="views">
<item quantity="zero">%s بدون مشاهد</item>
<item quantity="one">%s مشاهدة</item>
<item quantity="two">%s مشاهدات</item>
<item quantity="few">%s مشاهدات</item>
<item quantity="many">%s مشاهدات</item>
<item quantity="other">%s مشاهدات</item>
<item quantity="zero">%s مشاهدة</item>
<item quantity="one">%s مشاهد</item>
<item quantity="two">%s مشاهدة</item>
<item quantity="few">%s مشاهدة</item>
<item quantity="many">%s مشاهدة</item>
<item quantity="other">%s مشاهدة</item>
</plurals>
<plurals name="videos">
<item quantity="zero">%s فيديو</item>
@ -302,7 +303,7 @@
<string name="controls_download_desc">تنزيل ملف البث</string>
<string name="tab_bookmarks">الإشارات المرجعية</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="download_thumbnail_title">تحميل الصور المصغرة</string>
<string name="thumbnail_cache_wipe_complete_notice">تم إفراغ مساحة ذاكرة التخزين المؤقتة الخاصة بالصور</string>
<string name="file">الملف</string>
@ -326,7 +327,7 @@
<string name="metadata_cache_wipe_summary">إزالة جميع بيانات صفحات الويب المخزنة مؤقتًا</string>
<string name="metadata_cache_wipe_complete_notice">تم محو ذاكرة التخزين المؤقت للبيانات الوصفية</string>
<string name="auto_queue_title">وضع البث القادم تلقائيا في قائمة الإنتظار</string>
<string name="auto_queue_summary">متابعة إنهاء قائمة انتظار التشغيل (غير المتكررة) من خلال إلحاق تدفق ذي صلة</string>
<string name="auto_queue_summary">استمر في إنهاء قائمة انتظار التشغيل (غير-المتكررة) من خلال إلحاق تدفق ذي صلة</string>
<string name="set_as_playlist_thumbnail">إضافة صورة مصغرة إلى قائمة التشغيل</string>
<string name="bookmark_playlist">تفضيل قائمة التشغيل</string>
<string name="playlist_thumbnail_change_success">تم تغيير الصورة المصغرة لقائمة التشغيل.</string>
@ -363,11 +364,14 @@
<string name="previous_export">نسخة احتياطية</string>
<string name="subscriptions_import_unsuccessful">تعذر استيراد الاشتراكات</string>
<string name="subscriptions_export_unsuccessful">لا يمكن تصدير الاشتراكات</string>
<string name="import_youtube_instructions">استيراد اشتراكات YouTube عن طريق تنزيل ملف التصدير:
<string name="import_youtube_instructions">استيراد اشتراكات YouTube من Google Takeout
\n
\n1. انتقل إلى عنوان URL هذا: %1$s
\n2. تسجيل الدخول عندما يطلب منك
\n3. يجب أن يبدأ التنزيل (وهذا ملف التصدير)</string>
\n1. انتقل إلى عنوان URL هذا : %1$s
\n2. تسجيل الدخول عندما يُطلب منك ذلك
\n3. انقر على \"جميع البيانات المدرجة\" ، ثم على \"إلغاء تحديد الكل\" ، ثم حدد \"الاشتراكات\" فقط وانقر على \"موافق\"
\n4. انقر على \"الخطوة التالية\" ثم على \"إنشاء تصدير\"
\n5. انقر فوق الزر \"تنزيل\" بعد ظهوره و
\n6. من الملف المضغوط الذي تم تنزيله ، استخرج ملف .json (عادةً ضمن \"YouTube و YouTube)Music/subscriptions/subscriptions.json\") واستورده</string>
<string name="import_soundcloud_instructions">قم باستيراد ملف تعريف SoundCloud عن طريق كتابة عنوان URL أو معرفك:
\n
\n1. تمكين \"وضع سطح المكتب\" في متصفح الويب (الموقع غير متاح للأجهزة المحمولة)
@ -409,7 +413,7 @@
<string name="tab_choose">اختر علامة التبويب</string>
<string name="volume_gesture_control_summary">استخدم إيماءات التحكم في صوت المشغل</string>
<string name="brightness_gesture_control_title">التحكم بالإيماءات السطوع</string>
<string name="brightness_gesture_control_summary">استخدم الإيماءات للتحكم في سطوع المشغل</string>
<string name="brightness_gesture_control_summary">استخدام الإيماءات للتحكم في سطوع المشغل</string>
<string name="settings_category_updates_title">التحديثات</string>
<string name="file_deleted">تم حذف الملف</string>
<string name="app_update_notification_channel_name">تتبيه تحديث التطبيق</string>
@ -503,7 +507,7 @@
<string name="download_choose_new_path">تغيير مجلدات التنزيل إلى حيز التنفيذ‮‮‮</string>
<string name="drawer_header_description">تبديل الخدمة ، المحدد حاليًا:</string>
<string name="default_kiosk_page_summary">الكشك الافتراضي</string>
<string name="no_one_watching">لاتوجد مشاهدة</string>
<string name="no_one_watching">لا توجد مشاهدة</string>
<string name="no_one_listening">لا أحد يستمع</string>
<string name="localization_changes_requires_app_restart">ستتغير اللغة بمجرد إعادة تشغيل التطبيق.</string>
<plurals name="watching">
@ -630,7 +634,7 @@
<string name="songs">الأغاني</string>
<string name="restricted_video">هذا الفيديو مقيد بالفئة العمرية.
\n
\nقم بتشغيل \"المحتوى المقيد بالفئة العمرية\" في الإعدادات إذا كنت تريد مشاهدته.</string>
\nقم بتشغيل \"%1$s\" في الإعدادات إذا كنت تريد رؤيته.</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">نعم، ومقاطع الفيديو التي تمت مشاهدتها جزئيًا</string>
<string name="remove_watched_popup_warning">ستتم إزالة مقاطع الفيديو التي تمت مشاهدتها قبل وبعد إضافتها إلى قائمة التشغيل.
\nهل أنت واثق؟ هذا لا يمكن التراجع عنها!</string>
@ -638,7 +642,7 @@
<string name="remove_watched">إزالة ماتمت مشاهدته</string>
<string name="show_original_time_ago_summary">ستكون النصوص الأصلية من الخدمات مرئية في عناصر البث</string>
<string name="show_original_time_ago_title">عرض الوقت الأصلي على العناصر</string>
<string name="youtube_restricted_mode_enabled_title">وضع مقيد يوتيوب</string>
<string name="youtube_restricted_mode_enabled_title">شغيل \"وضع تقييد المحتوى\" في يوتيوب</string>
<string name="video_detail_by">لـ %s</string>
<string name="channel_created_by">أنشأها %s</string>
<string name="detail_sub_channel_thumbnail_view_description">الصورة الرمزية للقناة</string>
@ -673,4 +677,14 @@
<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_title">مقياس الصورة المصغرة إلى نسبة عرض إلى ارتفاع 1: 1</string>
<string name="clear_cookie_summary">امسح ملفات تعريف الارتباط التي يخزنها NewPipe عند حل reCAPTCHA</string>
<string name="recaptcha_cookies_cleared">تم مسح ملفات تعريف الارتباط reCAPTCHA</string>
<string name="clear_cookie_title">امسح ملفات تعريف الارتباط reCAPTCHA</string>
<string name="youtube_restricted_mode_enabled_summary">يوفر YouTube \"وضع تقييد المحتوى\" الذي يخفي المحتوى المحتمل للكبار.</string>
<string name="show_age_restricted_content_summary">عرض المحتوى الذي يُحتمل أن يكون غير مناسب للأطفال لأن له حدًا عمريًا (مثل 18+).</string>
<string name="show_memory_leaks">إظهار تسرب الذاكرة</string>
<string name="enqueued">قائمة الانتظار</string>
<string name="enqueue_stream">قائمة الانتظار</string>
<string name="notification_colorize_summary">اجعل أندرويد يخصص لون الإشعار وفقا للون الرئيسي في الصورة المصغرة (لاحظ أن هذا غير متوفر على جميع الأجهزة</string>
<string name="notification_colorize_title">تلوين الاشعارات</string>
</resources>

View file

@ -88,8 +88,6 @@
<string name="use_inexact_seek_title">Qeyri-dəqiq axtarış (videonu irəli/geri çəkmə) istifadə edin</string>
<string name="use_inexact_seek_summary">Qeyri-dəqiq axtarış pleyerə azaldılmış həssaslıqla mövqeləri daha sürətlə axtarmağa imkan verir. 5, 15 və ya 25 saniyəlik axtarış bununla işləmir.</string>
<string name="seek_duration_title">Cəld irəli/geri çəkmə müddəti</string>
<string name="popup_remember_size_pos_summary">Ani pəncərənin sonuncu ölçü və mövqeyini xatırla</string>
<string name="popup_remember_size_pos_title">Ani pəncərə xüsusiyyətlərini xatırla</string>
<string name="notification_action_nothing">Heç nə</string>
<string name="notification_action_buffering">Buferizasiya olunur</string>
<string name="notification_action_shuffle">Qarışdır</string>
@ -110,4 +108,32 @@
<string name="subscription_update_failed">Abunəlik yenilənmədi</string>
<string name="subscription_change_failed">Abunəlik dəyişdirilmədi</string>
<string name="search_showing_result_for">Nəticələr göstərilir: %s</string>
<string name="channels">Kanallar</string>
<string name="channel">Kanal</string>
<string name="video_detail_by">%s tərəfindən</string>
<string name="youtube_restricted_mode_enabled_title">\"Youtube\"un \"Məhdudiyyətli Rejimi\"ni aktivləşdir</string>
<string name="show_age_restricted_content_summary">Yaş limiti olduğuna görə (məs. 18+) böyük ehtimal uşaqlar üçün uyğun olmayan məzmunu göstər.</string>
<string name="show_age_restricted_content_title">Yaş məhdudiyyətli məzmunu göstər</string>
<string name="content">Məzmun</string>
<string name="popup_playing_append">Ani pəncərə növbəyə salındı</string>
<string name="background_player_append">Fon pleyeri növbəyə salındı</string>
<string name="popup_playing_toast">Ani pəncərədə oxudulur</string>
<string name="background_player_playing_toast">Fonda oxudulur</string>
<string name="settings_category_notification_title">Bildiriş</string>
<string name="settings_category_updates_title">Yeniləmələr</string>
<string name="settings_category_debug_title">Sazlama</string>
<string name="settings_category_other_title">Digər</string>
<string name="settings_category_appearance_title">Görünüş</string>
<string name="settings_category_popup_title">Ani pəncərə</string>
<string name="settings_category_history_title">Tarix və keş</string>
<string name="settings_category_video_audio_title">Video və səs</string>
<string name="settings_category_player_behavior_title">Davranış</string>
<string name="settings_category_player_title">Pleyer</string>
<string name="content_language_title">İlkin məzmun dili</string>
<string name="service_title">Xidmət</string>
<string name="default_content_country_title">Məzmun üçün ilkin ölkə</string>
<string name="unsupported_url_dialog_message">URL tanınmadı. Başqa bir tətbiq ilə açılsın\?</string>
<string name="unsupported_url">Dəstəklənməyən URL</string>
<string name="show_hold_to_append_title">\"Əlavə etmək üçün basılı tutun\" məsləhətini göstər</string>
<string name="show_next_and_similar_title">\"Növbəti\" və \"Bənzər\" videoları göstər</string>
</resources>

View file

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="use_external_video_player_title">Tashqi video pleerdan foydalanish</string>
<string name="screen_rotation">aylantirish</string>
<string name="choose_browser">Brauzer tanlang</string>
<string name="share_dialog_title">Bilan baham ko\'rish</string>
<string name="search_showing_result_for">%s uchun natijalar ko\'rsatilmoqda</string>
<string name="did_you_mean">\"%1$s\" demoqchimisiz\?</string>
<string name="settings">Sozlashlar</string>
<string name="search">Qidirish</string>
<string name="controls_download_desc">stream file yuklab olish</string>
<string name="download">Yuklab olish</string>
<string name="share">Baham ko\'rish</string>
<string name="install">O\'rnatish</string>
<string name="open_in_browser">Brauzerda ochish</string>
<string name="open_in_popup_mode">Qalqib chiqadigan rejimda ochish</string>
<string name="cancel">Bekor qilish</string>
<string name="no_player_found_toast">Stream pleer topilmadi (uni ijro etish uchun VLC o\'rnatishingiz mumkin).</string>
<string name="no_player_found">Hech qanday translatsiya pleyeri topilmadi. VLC o\'rnatilsinmi\?</string>
<string name="upload_date_text">%1$s tomonidan elon qilingan</string>
<string name="view_count_text">%1$s marta korilgan</string>
<string name="main_bg_subtitle">Boshlash uchun \"Izlash\" tugmasini bosing
\n</string>
<string name="player_gesture_controls_summary">Player yorqinligini va ovoz balandligini boshqarish uchun imo-ishoralardan foydalanish</string>
<string name="player_gesture_controls_title">Player imo-ishoralarini boshqarish</string>
<string name="volume_gesture_control_summary">Player tovushini boshqarish uchun imo-ishoralardan foydalanish</string>
<string name="brightness_gesture_control_summary">Player yorqinligini boshqarish uchun imo-ishoralardan foydalaning</string>
<string name="brightness_gesture_control_title">Yorqinlik ishoralarini boshqarish</string>
<string name="volume_gesture_control_title">Ovoz balandligini ishoralarni boshqarish</string>
<string name="auto_queue_toggle">Avto-navbat</string>
<string name="auto_queue_summary">Tegishli stream qo\'shib, ijro etish navbatini tugatishni (takrorlanmaydigan) davom ettirish</string>
<string name="auto_queue_title">avtomatik navbat next stream</string>
<string name="metadata_cache_wipe_complete_notice">Metadata keshi o\'chirildi</string>
<string name="metadata_cache_wipe_summary">Barcha keshlangan veb-sahifa ma\'lumotlarini olib tashlash</string>
<string name="metadata_cache_wipe_title">Keshlangan metadatalarni o\'chirish</string>
<string name="thumbnail_cache_wipe_complete_notice">Rasm keshi o\'chirildi</string>
<string name="download_thumbnail_summary">Eskizlarni yuklash, ma\'lumotlarni tejash va xotiradan foydalanishni oldini olish uchun o\'chirib qo\'ying. O\'zgarishlar xotiradagi va diskdagi rasm keshini tozalaydi.</string>
<string name="show_comments_summary">sharhlarni yashirishni o\'chirish</string>
<string name="show_comments_title">Izohlarni ko\'rsatish</string>
<string name="download_thumbnail_title">Eskizlarni yuklang</string>
<string name="clear_queue_confirmation_description">Aktiv ijro etish navbati almashtiriladi</string>
<string name="clear_queue_confirmation_summary">Bir ijro etishdan boshqasiga o\'tish sizning navbatingizni almashtirishi mumkin</string>
<string name="clear_queue_confirmation_title">Navbatni tozalashdan oldin tasdiqlashni so\'rash</string>
<string name="seek_duration_title">Oldinga tez / oldinga siljish davomiyligini qidirish</string>
<string name="use_inexact_seek_summary">Noto\'g\'ri izlash ijro etuvchiga aniqlikni pasayishi bilan tezroq pozitsiyalarni qidirishga imkon beradi. 5, 15 yoki 25 soniyani qidirish bu bilan ishlamaydi.</string>
<string name="use_inexact_seek_title">Tez aniq bo\'lmagan izlashdan foydalanish</string>
<string name="black_theme_title">Qora</string>
<string name="dark_theme_title">qorong\'i</string>
<string name="light_theme_title">Yorug\'</string>
<string name="theme_title">Tema</string>
<string name="default_video_format_title">Standart video format</string>
<string name="default_audio_format_title">Standart audio format</string>
<string name="play_audio">Audio</string>
<string name="notification_action_nothing">Hech narsa</string>
<string name="notification_action_buffering">Buferlash</string>
<string name="notification_action_shuffle">Aralash</string>
<string name="notification_action_repeat">Takrorlash</string>
<string name="notification_actions_at_most_three">Bildirishnomada ko\'rsatish uchun eng ko\'p uchta amalni tanlashingiz mumkin!</string>
<string name="notification_actions_summary">Quyidagi har bir bildirishnomani ustiga bosib uni tahrir qiling. O\'ng tomondagi katakchalar yordamida ixcham bildirishnomada ko\'rsatilishi uchun ulardan uchtasini tanlang.</string>
<string name="notification_action_4_title">Beshinchi harakat tugmasi</string>
<string name="notification_action_3_title">To\'rtinchi harakat tugmasi</string>
<string name="notification_action_2_title">Uchinchi harakat tugmasi</string>
<string name="notification_action_1_title">Ikkinchi harakat tugmasi</string>
<string name="notification_action_0_title">Birinchi harakat tugmasi</string>
<string name="notification_scale_to_square_image_summary">Bildirishnomada ko\'rsatilgan video eskizini 16: 9dan 1: 1 gacha tomonlarning nisbatiga qarab o\'lchamang (buzilishlarni keltirib chiqarishi mumkin)</string>
<string name="notification_scale_to_square_image_title">Eskizini 1: 1 tomonlar nisbatiga olchash</string>
<string name="show_play_with_kodi_summary">Kodi media-markazi orqali videoni ijro etish variantini ko\'rsatish</string>
<string name="show_play_with_kodi_title">\"Kodi bilan ijro etish\" parametrini ko\'rsatish</string>
<string name="kore_not_found">missing Kore dasturini o\'rnatasizmi\?</string>
<string name="play_with_kodi_title">Kodi bilan ijro etish</string>
<string name="show_higher_resolutions_summary">Faqat ba\'zi qurilmalar 2K / 4K videolarni ijro etishi mumkin</string>
<string name="show_higher_resolutions_title">Yuqori o\'lchamlarni ko\'rsatish</string>
<string name="default_popup_resolution_title">"Standart pop-up o\'lchamlari"</string>
<string name="default_resolution_title">Standart o\'lchamlari</string>
<string name="autoplay_by_calling_app_summary">NewPipe boshqa dasturdan chaqirilganda videoni ijro etadi</string>
<string name="autoplay_by_calling_app_title">Avtomatikplay</string>
<string name="download_choose_new_path">Yuklash papkalarini kuchga kirishi uchun o\'zgartirish</string>
<string name="download_path_audio_dialog_title">Audio fayllar uchun yuklab olish papkasini tanlash</string>
<string name="download_path_summary">Yuklab olingan videofayllar shu yerda saqlanadi</string>
<string name="download_path_audio_summary">Yuklab olingan audio fayllar shu yerda saqlanadi</string>
<string name="download_path_audio_title">Ovozni yuklab olish papkasi</string>
<string name="download_path_dialog_title">Video fayllar uchun yuklab olish papkasini tanlash</string>
<string name="download_path_title">Videoni yuklab olish jildi</string>
<string name="controls_add_to_playlist_title">Qo\'shish</string>
<string name="controls_popup_title">Qalqib ko\'rinish</string>
<string name="controls_background_title">Fon ko\'rinishi</string>
<string name="tab_bookmarks">Yorliqlangan pleylistlar</string>
<string name="tab_choose">Yorliqni tanlash</string>
<string name="tab_new">Yangi yorliq</string>
<string name="tab_subscriptions">Obunalar</string>
<string name="tab_main">Asosiy</string>
<string name="show_info">Ma\'lumotni ko\'rsatish</string>
<string name="subscription_update_failed">Obunani yangilab bo\'lmadi</string>
<string name="subscription_change_failed">Obunani o\'zgartirib bo\'lmadi</string>
<string name="channel_unsubscribed">Kanal obunasi bekor qilindi</string>
<string name="unsubscribe">Obunani bekor qilish</string>
<string name="subscribed_button_title">Obuna bo\'lindi</string>
<string name="subscribe_button_title">Obuna bo\'lish</string>
<string name="popup_mode_share_menu_title">Qalqib ko\'rinadigan rejim</string>
<string name="use_external_audio_player_title">Tashqi audio pleerdan foydalanish</string>
<string name="use_external_video_player_summary">Ba\'zi piksellarda ovozni o\'chirish</string>
<string name="clear_views_history_title">Tomosha tarixini tozalash</string>
<string name="clear_cookie_summary">ReCAPTCHA-ni hal qilganingizda NewPipe-da saqlanadigan cookie-fayllarni o\'chirib tashlang</string>
<string name="export_data_summary">Eksport tarixi, obunalari va pleylistlari</string>
<string name="import_data_summary">Joriy tarixingiz va obunalaringizni bekor qiladi</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA cookies fayllari o\'chirildi</string>
<string name="clear_cookie_title">ReCAPTCHA cookie-fayllarini o\'chirish</string>
<string name="export_data_title">Ma\'lumotlar bazasini eksport qilish</string>
<string name="import_data_title">Ma\'lumotlar bazasini import qilish</string>
<string name="switch_to_main">Asosiyga o\'tish</string>
<string name="switch_to_popup">Pop-upga o\'tish</string>
<string name="switch_to_background">Orqa fonga o\'tish</string>
<string name="toggle_orientation">Yo\'nalishni almashtirish / yoqish</string>
<string name="unknown_content">[Noma\'lum]</string>
<string name="app_update_notification_channel_description">NewPipe yangi versiyasi haqida bildirishnomalar</string>
<string name="app_update_notification_channel_name">Ilovani yangilash bildirishnomasi</string>
<string name="notification_channel_description">NewPipe fon va popup pleyerlari uchun bildirishnomalar</string>
<string name="notification_channel_name">NewPipe bildirishnomasi</string>
<string name="file">Fayl</string>
<string name="just_once">Faqat bittasi</string>
<string name="always">Har doim</string>
<string name="play_all">Barchasini ijro etish</string>
<string name="file_deleted">Fayl o\'chirildi</string>
<string name="undo">Bekor qilish</string>
<string name="best_resolution">Eng yaxshi qaror</string>
<string name="popup_resizing_indicator_title">Hajmi o\'zgartirilmoqda</string>
<string name="clear">Tozalash</string>
<string name="refresh">Yangilash</string>
<string name="filter">Filter</string>
<string name="disabled">Ijrochilar o\'chirib qo\'yilgan</string>
<string name="later">Keyin</string>
<string name="yes">Ha</string>
<string name="artists">Artistlar</string>
<string name="albums">Albomlar</string>
<string name="songs">Qo\'shiqlar</string>
<string name="events">Natijalar</string>
<string name="users">Foydalanuvchilar</string>
<string name="tracks">Treklar</string>
<string name="videos_string">Videolar</string>
<string name="playlists">Playlistlar</string>
<string name="playlist">Playlist</string>
<string name="channels">Kanallar</string>
<string name="channel">Kanal</string>
<string name="all">Hammasi</string>
<string name="error_report_title">Xato haqida xabar berish</string>
<string name="downloads_title">Yuklanganlar</string>
<string name="downloads">Yuklanganlar</string>
<string name="duration_live">Jonli</string>
<string name="restricted_video">Ushbu video yoshga cheklangan.
\n
\nAgar xohlasangiz, sozlamalarda \"%1$s\" ni yoqing.</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube \"cheklangan rejim\" ni taqdim etadi, u katta yoshlilar uchun tarkibni yashiradi.</string>
<string name="youtube_restricted_mode_enabled_title">YouTube-ning \"Cheklangan rejimi\" ni yoqish</string>
<string name="show_age_restricted_content_summary">Tarkibni bolalar uchun yaroqsiz deb ko\'rsating, chunki uning yosh chegarasi bor (18+ kabi).</string>
<string name="show_age_restricted_content_title">Yoshi cheklangan tarkibni ko\'rsatish</string>
<string name="content">Tarkib</string>
<string name="popup_playing_append">Pop-up pleyerida navbat ketma-ketlikda</string>
<string name="background_player_append">Orqa fon pleyerida navbat ketma-ketlikda</string>
<string name="popup_playing_toast">Pop-up rejimda ijro etish</string>
<string name="background_player_playing_toast">Ijro etish foni</string>
<string name="settings_category_notification_title">Bildirishnoma</string>
<string name="settings_category_updates_title">Yangilanishlar</string>
<string name="settings_category_debug_title">Nosozliklarni tuzatish</string>
<string name="settings_category_other_title">Boshqalar</string>
<string name="settings_category_appearance_title">Tashqi ko\'rinish</string>
<string name="settings_category_popup_title">Pop-up</string>
<string name="settings_category_history_title">Tarix va kesh</string>
<string name="settings_category_video_audio_title">Video va audio</string>
<string name="settings_category_player_behavior_title">Xatti-harakat</string>
<string name="settings_category_player_title">Ijro etish</string>
<string name="peertube_instance_add_exists">Namuna allaqachon mavjud</string>
<string name="peertube_instance_add_https_only">Faqat HTTPS URL-lari qo\'llab-quvvatlanadi</string>
<string name="peertube_instance_add_fail">Namunani tasdiqlab bo\'lmadi</string>
<string name="peertube_instance_add_help">Namuna URL manzilini kiriting</string>
<string name="peertube_instance_add_title">Namuna qo\'shish</string>
<string name="peertube_instance_url_help">%s da sizga yoqadigan misollarni toping</string>
<string name="peertube_instance_url_summary">Sevimli PeerTube nusxalarini tanlang</string>
<string name="peertube_instance_url_title">PeerTube misollari</string>
<string name="content_language_title">Standart kontent tili</string>
<string name="service_title">Xizmatlar</string>
<string name="default_content_country_title">Standart kontent mamlakati</string>
<string name="unsupported_url_dialog_message">URL manzili aniqlanmadi. Boshqa ilova bilan ochilsinmi\?</string>
<string name="unsupported_url">Qo\'llab-quvvatlanmaydigan URL manzili</string>
<string name="show_hold_to_append_summary">\"Tafsilotlar:\" videodagi fon yoki po-pup tugmachasini bosganda ko\'rsatma.</string>
<string name="show_hold_to_append_title">\"Qo\'shish uchun ushlab turish\" maslahatini ko\'rsatish</string>
<string name="show_next_and_similar_title">\'Keyingi\' va \'O\'xshash\' videolarni namoyish etish</string>
<string name="autoplay_title">Avtoplay</string>
<string name="resume_on_audio_focus_gain_summary">Uzilishlardan keyin ijro etishni davom ettirish (masalan. phonecalls)</string>
<string name="download_dialog_title">Yuklab olish</string>
<string name="resume_on_audio_focus_gain_title">Ijro etishni davom ettirish</string>
<string name="enable_watch_history_summary">Ko\'rilgan videolarni kuzatib borish</string>
<string name="settings_category_clear_data_title">Ma\'lumotlarni tozalash</string>
<string name="enable_playback_state_lists_summary">Ro\'yxatlarda ijro holati ko\'rsatkichlarini ko\'rsatish</string>
<string name="enable_playback_state_lists_title">Ro\'yxatlardagi pozitsiyalar</string>
<string name="enable_playback_resume_summary">Oxirgi ijro holatini tiklash</string>
<string name="enable_playback_resume_title">Ijro etishni davom ettirish</string>
<string name="show_search_suggestions_summary">Qidirayotganda takliflarni ko\'rsatish</string>
<string name="enable_watch_history_title">Tarixni ko\'rish</string>
<string name="enable_search_history_summary">Qidiruv so\'rovlarini mahalliy sifatida saqlash</string>
<string name="enable_search_history_title">Qidiruv tarixi</string>
<string name="show_search_suggestions_title">Takliflarni qidirish</string>
<string name="could_not_import_all_files">Ogohlantirish: Barcha fayllarni import qilib bo\'lmadi.</string>
<string name="no_valid_zip_file">Haqiqiy ZIP fayli yoq</string>
<string name="import_complete_toast">Import qilindi</string>
<string name="export_complete_toast">Eksport qilindi</string>
<string name="select_a_kiosk">kiosk tanlash</string>
<string name="no_playlist_bookmarked_yet">Hali pleylist xatcho\'plari yo\'q</string>
<string name="select_a_playlist">Pleylistni tanlang</string>
<string name="no_channel_subscribed_yet">Hech qanday kanal obunasi yo\'q</string>
<string name="select_a_channel">Kanal tanlash</string>
<string name="channel_page_summary">Kanal sahifasi</string>
<string name="feed_page_summary">Feed sahifasi</string>
<string name="subscription_page_summary">Obuna sahifasi</string>
<string name="default_kiosk_page_summary">Standart kiosk</string>
<string name="kiosk_page_summary">Kiosk sahifasi</string>
<string name="blank_page_summary">Bo\'sh sahifa</string>
<string name="selection">Tanlash</string>
<string name="main_page_content_summary">Asosiy sahifada qanday yorliqlar ko\'rsatilgan</string>
<string name="main_page_content">Asosiy sahifaning tarkibi</string>
<string name="title_most_played">Eng ko\'p ijrolar etilganlar</string>
<string name="title_last_played">Oxirgi ijro</string>
<string name="delete_all_history_prompt">Haqiqatan ham barcha narsalarni tarixdan o\'chirishni xohlaysizmi\?</string>
<string name="delete_stream_history_prompt">Ushbu narsani tomosha tarixidan o\'chirishni xohlaysizmi\?</string>
<string name="delete_item_search_history">Ushbu narsani qidiruv tarixidan o\'chirmoqchimisiz\?</string>
<string name="item_deleted">Element o\'chirildi</string>
<string name="history_cleared">Tarix tozlandi</string>
<string name="history_empty">Tarix bo\'sh</string>
<string name="action_history">Tarix</string>
<string name="history_disabled">Tarix o\'chirilgan</string>
<string name="title_history_search">Qidirilgan</string>
<string name="title_history_view">Ko\'rilgan</string>
<string name="title_activity_history">Tarix</string>
<string name="read_full_license">Litsenziyani o\'qish</string>
<string name="app_license">NewPipe - bu nusxa ko\'chirish dasturiy ta\'minotidir: Siz foydalanishingiz, baham ko\'rishingiz va o\'zingizning xohishingiz bilan yaxshilashingiz mumkin. Xususan, siz uni bepul dasturiy ta\'minot fondi tomonidan e\'lon qilingan GNU umumiy jamoat litsenziyasi shartlari asosida qayta tarqatishingiz va / yoki o\'zgartirishingiz mumkin, Litsenziyaning 3-versiyasi yoki (sizning xohishingizga ko\'ra) har qanday keyingi versiyada.</string>
<string name="app_license_title">NewPipe litsenziyasi</string>
<string name="read_privacy_policy">Maxfiylik siyosatini o\'qish</string>
<string name="privacy_policy_encouragement">NewPipe loyihasi sizning shaxsiy hayotingizga jiddiy e\'tibor beradi. Shuning uchun ilova sizning roziligingizsiz biron bir ma\'lumot to\'plamaydi.
\nNewPipe-ning maxfiylik siyosati halokat to\'g\'risidagi hisobotni yuborganingizda qanday ma\'lumotlar yuborilishi va saqlanishi haqida batafsil ma\'lumot beradi.</string>
<string name="privacy_policy_title">NewPipe-ning maxfiylik siyosati</string>
<string name="website_encouragement">Qo\'shimcha ma\'lumot va yangiliklar uchun NewPipe veb-saytiga tashrif buyuring.</string>
<string name="website_title">Websayt</string>
<string name="give_back">Qaytarib berish</string>
<string name="donation_title">Hadya etish</string>
<string name="donation_encouragement">NewPipe ko\'ngillilar tomonidan bo\'sh vaqtlarini sarflash orqali sizga eng yaxshi foydalanuvchi tajribasini taqdim etadi. Ishlab chiquvchilarga bir chashka qahvadan zavqlanib, NewPipe-ni yanada yaxshiroq qilishlariga yordam berish.</string>
<string name="view_on_github">GitHubda ko\'rish</string>
<string name="contribution_encouragement">Sizda g\'oyalar bormi; tarjima, dizayndagi o\'zgarishlar, kodni tozalash yoki haqiqiy og\'ir kodni o\'zgartirish - yordam har doim mamnuniyat bilan qabul qilinadi. Qancha ko\'p ish qilinsa, shuncha yaxshi bo\'ladi!</string>
<string name="contribution_title">Hissa qo\'shish</string>
<string name="app_description">Androidda Libre yengil streaming.</string>
<string name="tab_licenses">Litsenziyalar</string>
<string name="tab_contributors">Xissadorlar</string>
<string name="tab_about">Haqida</string>
<string name="action_open_website">Veb-saytni ochish</string>
<string name="error_unable_to_load_license">Litsenziyani yuklab bo\'lmadi</string>
<string name="copyright">© %1$s tomonidan %2$s gacha %3$s</string>
<string name="title_licenses">Uchinchi tomon litsenziyalari</string>
<string name="action_about">Haqida</string>
<string name="action_settings">Sozlamalar</string>
<string name="title_activity_about">NewPipe haqida</string>
<string name="toast_no_player">Ushbu faylni ijro etish uchun dastur o\'rnatilmagan</string>
<string name="charset_most_special_characters">Ko\'pchilik maxsus belgilar</string>
<string name="charset_letters_and_digits">Yozuvlar va raqamlar</string>
<string name="settings_file_replacement_character_title">O\'zgartirish belgisi</string>
<string name="settings_file_replacement_character_summary">Noto\'g\'ri belgilar ushbu qiymat bilan almashtiriladi</string>
<string name="settings_file_charset_title">Fayl nomidagi ruxsat berilgan belgilar</string>
<string name="settings_category_downloads_title">Yuklab olish</string>
<string name="recaptcha_done_button">Bajarildi</string>
<string name="recaptcha_request_toast">reCAPTCHA muammosi so\'raldi</string>
<string name="subtitle_activity_recaptcha">Hal etilganda \"Bajarildi\" tugmasini bosing</string>
<string name="title_activity_recaptcha">reCAPTCHA muammosi</string>
<string name="one_item_deleted">1 ta element o\'chirildi.</string>
<string name="msg_popup_permission">Ushbu ruxsat zarur
\npopup rejimida oching</string>
<string name="no_available_dir">Yuklab olish papkasini keyinroq sozlamalarda belgilang</string>
<string name="msg_copied">Buferga nusxa olindi</string>
<string name="msg_wait">Iltimos kuting…</string>
<string name="msg_running_detail">Tafsilotlar uchun bosing</string>
<string name="msg_running">NePipe yuklab olinmoqda</string>
<string name="msg_url_malform">Noto\'g\'ri shakllangan URL yoki Internet mavjud emas</string>
<string name="msg_exists">Fayl allaqachon mavjud</string>
<string name="msg_server_unsupported">Qo\'llab-quvvatlanmaydigan server</string>
<string name="msg_error">Xato</string>
<string name="msg_threads">Iplar</string>
<string name="msg_name">Faylnomi</string>
<string name="finish">Ok</string>
<string name="add">Yangi missiya</string>
<string name="rename">Nomni o\'zgartirish</string>
<string name="dismiss">Tarqatish</string>
<string name="checksum">Sumnazorat</string>
<string name="delete_all">Hammasini o\'chirish</string>
<string name="delete_one">Bittasini o\'chirish</string>
<string name="delete">O\'chirish</string>
<string name="create">Yaratish</string>
<string name="view">Ijro etish</string>
<string name="pause">Pauza</string>
<string name="start">Boshlash</string>
<string name="no_comments">Izohlar yo\'q</string>
<plurals name="videos">
<item quantity="one">%s video</item>
<item quantity="other">%s videolar</item>
</plurals>
<string name="infinite_videos">∞ videolar</string>
<string name="more_than_100_videos">100+ videolar</string>
<string name="no_videos">Videolar yo\'q</string>
<plurals name="listening">
<item quantity="one">%s tinglovchi</item>
<item quantity="other">%s tinglovchilar</item>
</plurals>
<string name="no_one_listening">Hech kim tinglamayapti</string>
<plurals name="watching">
<item quantity="one">%s ko\'ryapti</item>
<item quantity="other">%s ko\'ryaptilar</item>
</plurals>
<string name="no_one_watching">Hech kim ko\'rmayapti</string>
<plurals name="views">
<item quantity="one">%s ko\'rish</item>
<item quantity="other">%s ko\'rishlar</item>
</plurals>
<string name="no_views">Ko\'rishlar yo\'q</string>
<string name="subscribers_count_not_available">Obunachilar soni mavjud emas</string>
<plurals name="subscribers">
<item quantity="one">%s bunachisi</item>
<item quantity="other">%s obunachilar</item>
</plurals>
<string name="no_subscribers">Obunachilar yo\'q</string>
<string name="drawer_header_description">Hozirda tanlangan xizmatni yoqish:</string>
<string name="short_billion">B</string>
<string name="short_million">M</string>
<string name="short_thousand">k</string>
<string name="storage_permission_denied">Avval omborga kirishga ruxsat berish</string>
<string name="retry">Qayta</string>
<string name="audio">Audio</string>
<string name="video">Video</string>
<string name="info_dir_created">\'%1$s\' yuklab olish katalogi yaratildi</string>
<string name="err_dir_create">\'%1$s \' yuklab olish katalogini yaratib bo\'lmadi</string>
<string name="detail_drag_description">Qayta tartiblash uchun tortish</string>
<string name="empty_subscription_feed_subtitle">Bu erda kriketlardan boshqa hech narsa yo\'q</string>
<string name="search_no_results">Natija yo\'q</string>
<string name="user_report">Foydalanuvchi hisoboti</string>
<string name="report_error">Hisobotda xato</string>
<string name="use_tor_summary">(Eksperimental) Maxfiylikni oshirish uchun Tor orqali trafikni majburan yuklab oling (videolarni streamlash hali qo\'llab-quvvatlanmaydi).</string>
<string name="use_tor_title">Tor-dan foydalanish</string>
<string name="detail_dislikes_img_view_description">Dislayklar</string>
<string name="detail_likes_img_view_description">Layklar</string>
<string name="detail_uploader_thumbnail_view_description">Yuklovchining avatar eskizi</string>
<string name="detail_thumbnail_view_description">Videoni ijro etish muddati, davomiyligi:</string>
<string name="list_thumbnail_view_description">Videoni oldindan ko\'rish uchun eskiz</string>
<string name="error_details_headline">Detallar:</string>
<string name="your_comment">Sizning sharhingiz (ingliz tilida):</string>
<string name="info_labels">Nima: \\n So\'rov: \\nTarkib tili: \\nTarkib mamlakati: \\nIlova tili: \\ nXizmat: \\ nGMT vaqti: \\ nPaket: \\ nVersion: \\ nOS versiyasi:</string>
<string name="what_happened_headline">Nima sodir bo\'ldi:</string>
<string name="what_device_headline">Info:</string>
<string name="error_snackbar_action">Hisobot</string>
<string name="error_snackbar_message">Kechirasiz, biron bir xato yuz berdi.</string>
<string name="error_report_open_github_notice">Iltimos, sizning harakatingizni muhokama qiladigan muammo allaqachon mavjudligini tekshiring. Ikki nusxadagi ticketlarni yaratishda siz bizdan vaqt ajratib, biz haqiqiy xatolarni tuzatishga sarflashimiz mumkin edi.</string>
<string name="error_report_open_issue_button_text">GitHub haqida hisobot</string>
<string name="copy_for_github">Formatlangan hisobotni nusxalash</string>
<string name="error_report_button_text">Ushbu xato haqida elektron pochta orqali xabar berish</string>
<string name="sorry_string">Kechirasiz, bunday bo\'lmasligi kerak edi.</string>
<string name="permission_display_over_apps">Boshqa ilovalar orqali ko\'rsatishga ruxsat berish</string>
<string name="restore_defaults_confirmation">Birlamchi parametrlarni tiklashni xohlaysizmi\?</string>
<string name="restore_defaults">Birlamchi parametrlarni tiklash</string>
<string name="saved_tabs_invalid_json">Saqlangan yorliqlarni o\'qib bo\'lmadi, shuning uchun standartlardan foydalaning</string>
<string name="no_streams_available_download">Yuklash uchun stream mavjud emas</string>
<string name="error_occurred_detail">Xato yuz berdi: %1$s</string>
<string name="file_name_empty_error">Fayl nomi bo\'sh bo\'lishi mumkin emas</string>
<string name="invalid_file">Fayl mavjud emas yoki uni o\'qish yoki yozish uchun ruxsat yo\'q</string>
<string name="invalid_source">Bunday fayl / tarkib manbai yo\'q</string>
<string name="invalid_directory">Bunday papka yo\'q</string>
<string name="missing_file">Fayl ko\'chirildi yoki o\'chirildi</string>
<string name="audio_streams_empty">Hech qanday audio stream topilmadi</string>
<string name="video_streams_empty">Hech qanday video stream topilmadi</string>
<string name="invalid_url_toast">URL manzili yaroqsiz</string>
<string name="external_player_unsupported_link_type">Tashqi playerlar ushbu turdagi havolalarni qo\'llab-quvvatlamaydilar</string>
<string name="player_recoverable_failure">Player xatosidan qutulish</string>
<string name="player_unrecoverable_failure">Qayta tiklanmaydigan pleyerda xatolik yuz berdi</string>
<string name="player_stream_failure">Ushbu stream ijro etilmadi</string>
<string name="could_not_load_image">Rasm yuklanmadi</string>
<string name="could_not_get_stream">Hech qanday stream olinmadi</string>
<string name="live_streams_not_supported">Live streamlar hali qo\'llab-quvvatlanmaydi</string>
<string name="could_not_setup_download_menu">Yuklab olish menyusi sozlanmadi</string>
<string name="content_not_available">Tarkib mavjud emas</string>
<string name="light_parsing_error">Veb-saytni to\'liq tahlil qilib bo\'lmadi</string>
<string name="parsing_error">Veb-saytni tahlil qilib bo\'lmadi</string>
<string name="youtube_signature_deobfuscation_error">Videoning URL manzilini o\'chirib bo\'lmadi</string>
<string name="could_not_load_thumbnails">Barcha eskizlarni yuklab bo\'lmadi</string>
<string name="network_error">Tarmoqda xato</string>
<string name="download_to_sdcard_error_message">Tashqi SD-kartaga yuklab olishning iloji yo\'q. Yuklash papkasining joylashuvi tiklansinmi\?</string>
<string name="download_to_sdcard_error_title">Tashqi xotira mavjud emas</string>
<string name="general_error">Xato</string>
<string name="help">Yordam</string>
<string name="search_history_deleted">Qidiruv tarixi o\'chirildi.</string>
<string name="delete_search_history_alert">Butun qidiruv tarixi o\'chirilsinmi\?</string>
<string name="clear_search_history_summary">Qidiruv kalit so\'zlar tarixini o\'chiradi</string>
<string name="clear_search_history_title">Qidiruv tarixini tozalash</string>
<string name="watch_history_states_deleted">Ijro pozitsiyalari o\'chirildi.</string>
<string name="delete_playback_states_alert">Barcha ijro holatlari o\'chirilsinmi\?</string>
<string name="clear_playback_states_summary">Barcha ijro holatlarini o\'chiradi</string>
<string name="clear_playback_states_title">Ijro pozitsiyalarini o\'chirib tashlash</string>
<string name="watch_history_deleted">Tomosha tarixi o\'chirildi.</string>
<string name="delete_view_history_alert">Tomosha tarixi butunlay o\'chirib tashlansinmi\?</string>
<string name="clear_views_history_summary">Ijro etilgan streamlar tarixi va ijro holatlarini o\'chiradi</string>
</resources>

View file

@ -167,8 +167,7 @@
<string name="settings_category_video_audio_title">视频和音频</string>
<string name="background_player_playing_toast">在后台播放</string>
<string name="content">内容</string>
<string name="show_age_restricted_content_title">受年龄限制的内容</string>
<string name="video_is_age_restricted">显示受年龄限制的视频。可从设置允许此类内容。</string>
<string name="show_age_restricted_content_title">展示年龄限制的内容</string>
<string name="duration_live">直播</string>
<string name="downloads">下载</string>
<string name="downloads_title">下载</string>
@ -210,6 +209,8 @@
<string name="show_higher_resolutions_title">使用更高的分辨率</string>
<string name="show_higher_resolutions_summary">仅某些设备支持播放2K / 4K视频</string>
<string name="clear">清除</string>
<string name="popup_remember_size_pos_title">记住悬浮窗属性</string>
<string name="popup_remember_size_pos_summary">记住最后一次使用悬浮窗的大小和位置</string>
<string name="settings_category_popup_title">悬浮窗</string>
<string name="popup_resizing_indicator_title">调整大小</string>
<string name="use_external_video_player_summary">隐藏部分没有音频的分辨率</string>
@ -358,11 +359,14 @@
<string name="previous_export">以前的导出</string>
<string name="subscriptions_import_unsuccessful">无法导入订阅</string>
<string name="subscriptions_export_unsuccessful">无法导出订阅</string>
<string name="import_youtube_instructions">通过下载导出文件来导入 YouTube 订阅:
<string name="import_youtube_instructions">从 Google takeout 导入YouTube 订阅:
\n
\n1. 转到此网站: %1$s
\n2. 登录(如果需要)
\n3. 应该立即开始下载(即导出文件)</string>
\n1. 转到这个URL%1$s
\n2. 登录谷歌账户
\n3. 点击“所有包含的数据”,然后点击“取消选择全部”,然后只选择“订阅”,然后点击“确定”
\n4. 点击“下一步”然后点击“创建导出”
\n5. 在“下载”按钮出现后,点击它
\n6. 从下载的takeout压缩包提取.json文件 (通常能够位于\"YouTube and YouTube Music/subscriptions/subscriptions.json\")并在此导入它。</string>
<string name="import_soundcloud_instructions">通过输入网址或你的 ID 导入 SoundCloud 配置文件:
\n
\n1. 在浏览器中启用\"电脑模式\"(该网站不适用于移动设备)
@ -581,7 +585,7 @@
<string name="songs">歌曲</string>
<string name="restricted_video">该视频有年龄限制。
\n
\n如果您想要观看请在设置中启用“年龄限制内容”</string>
\n如果您想要观看请在设置中启用\"%1$s\"</string>
<string name="video_detail_by">由 %s</string>
<string name="channel_created_by">由%s创建</string>
<string name="detail_sub_channel_thumbnail_view_description">频道的头像缩略图</string>
@ -592,7 +596,7 @@
<string name="remove_watched">移除看过的视频</string>
<string name="show_original_time_ago_summary">来自服务的原始文本将在流项目中可见</string>
<string name="show_original_time_ago_title">在项目上显示原始时间</string>
<string name="youtube_restricted_mode_enabled_title">YouTube受限模式</string>
<string name="youtube_restricted_mode_enabled_title">打开YouTube\"受限模式\"</string>
<string name="feed_group_show_only_ungrouped_subscriptions">仅显示未分组订阅</string>
<string name="playlist_page_summary">播放列表页</string>
<string name="no_playlist_bookmarked_yet">尚无播放列表书签</string>
@ -624,4 +628,14 @@
<string name="notification_scale_to_square_image_summary">将通知中显示的视频缩略图长宽比从16:9缩放到1:1(可能会导致失真)</string>
<string name="notification_scale_to_square_image_title">缩放缩略图到1:1的长宽比</string>
<string name="settings_category_notification_title">通知</string>
<string name="show_memory_leaks">显示内存泄漏</string>
<string name="enqueued">已加入队列</string>
<string name="enqueue_stream">加入队列</string>
<string name="clear_cookie_summary">清理你在解决验证码时 NewPipe 存储的cookies</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA cookies 已被清理</string>
<string name="clear_cookie_title">清理 reCAPTCHA cookies</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube提供了一个“受限模式”会隐藏潜在的成人内容。</string>
<string name="show_age_restricted_content_summary">展示可能不适合儿童观看的内容,因为它有年龄限制(比如18岁以上)。</string>
<string name="notification_colorize_summary">让安卓系统根据视频缩略图的主色彩自定义通知的颜色(注意,该特性并非在所有设备上都可用)</string>
<string name="notification_colorize_title">对通知着色</string>
</resources>

View file

@ -58,6 +58,8 @@
<string name="light_theme_title">Светлая</string>
<string name="dark_theme_title">Цёмная</string>
<string name="black_theme_title">Чорная</string>
<string name="popup_remember_size_pos_title">Аднавіць акно</string>
<string name="popup_remember_size_pos_summary">Запамінаць памер і становішча ўсплываючага акна</string>
<string name="use_inexact_seek_title">Хуткі пошук пазіцыі</string>
<string name="use_inexact_seek_summary">Недакладны пошук дазваляе плэеру шукаць пазіцыю хутчэй, але менш дакладна</string>
<string name="download_thumbnail_title">Загружаць мініяцюры</string>
@ -100,7 +102,6 @@
<string name="popup_playing_append">Дададзена ў чаргу ў акне</string>
<string name="content">Кантэнт</string>
<string name="show_age_restricted_content_title">Кантэнт 18+</string>
<string name="video_is_age_restricted">Відэа з узроставымі абмежаваннямі. Дазволіць падобны кантэнт можна ў \"Наладах\".</string>
<string name="duration_live">Трансляцыя</string>
<string name="downloads">Загрузкі</string>
<string name="downloads_title">Загрузкі</string>

View file

@ -52,6 +52,8 @@
<string name="light_theme_title">Светла</string>
<string name="dark_theme_title">Тъмна</string>
<string name="black_theme_title">Черна</string>
<string name="popup_remember_size_pos_title">Помни размера и позицията на прозореца</string>
<string name="popup_remember_size_pos_summary">Използвай размера и позицията на прозореца от предишния път</string>
<string name="player_gesture_controls_title">Контролиране на плейъра чрез жестове</string>
<string name="player_gesture_controls_summary">Позволи използване на жестове за контрол на яркостта и силата на звука на плейъра</string>
<string name="show_search_suggestions_title">Предложения за търсене</string>
@ -80,7 +82,6 @@
<string name="popup_playing_append">Включен в опашката в нов прозорец</string>
<string name="content">Съдържание</string>
<string name="show_age_restricted_content_title">Съдържание за възрастни</string>
<string name="video_is_age_restricted">Покажи съдържание за възрастни. Разрешаването на такова съдържание става от Настройки.</string>
<string name="duration_live">НА ЖИВО</string>
<string name="downloads">Изтегляния</string>
<string name="downloads_title">Изтегляния</string>

View file

@ -44,6 +44,8 @@
<string name="light_theme_title">উজ্জ্বল</string>
<string name="dark_theme_title">অন্ধকার</string>
<string name="black_theme_title">কালো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string>
<string name="download_dialog_title">ডাউনলোড</string>
<string name="show_next_and_similar_title">পরবর্তী এবং অনুরূপ ভিডিওগুলি দেখাও</string>
<string name="unsupported_url">URL সমর্থিত নয়</string>
@ -56,7 +58,6 @@
<string name="popup_playing_toast">পপআপ মোডে চলছে</string>
<string name="content">কন্টেন্ট</string>
<string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string>
<string name="video_is_age_restricted">ভিডিওটিকে বয়স সীমিত করা হয়েছে। প্রথমে সেটিংসে বয়স সীমাবদ্ধ ভিডিওগুলি সক্ষম করো।</string>
<string name="duration_live">লাইভ</string>
<string name="downloads">ডাউনলোডগুলি</string>
<string name="downloads_title">ডাউনলোডগুলি</string>
@ -359,4 +360,5 @@
<string name="notification_action_repeat">পুনরায়</string>
<string name="notification_action_0_title">প্রথম ক্রিয়া বোতাম</string>
<string name="notification_scale_to_square_image_title">থাম্বনেলে ১:১ অনুপাতে করো</string>
<string name="import_export_title">আমদানি/রপ্তানি</string>
</resources>

View file

@ -82,7 +82,6 @@
<string name="downloads_title">ডাউনলোডগুলি</string>
<string name="downloads">ডাউনলোডগুলি</string>
<string name="duration_live">লাইভ</string>
<string name="video_is_age_restricted">ভিডিওটিকে বয়স সীমিত করা হয়েছে। প্রথমে সেটিংসে বয়স সীমাবদ্ধ ভিডিওগুলি সক্ষম করো।</string>
<string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string>
<string name="content">কন্টেন্ট</string>
<string name="popup_playing_toast">পপআপ মোডে চলছে</string>
@ -119,6 +118,8 @@
<string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string>
<string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷</string>
<string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
<string name="black_theme_title">কালো</string>
<string name="dark_theme_title">অন্ধকার</string>
<string name="light_theme_title">উজ্জ্বল</string>

View file

@ -233,7 +233,6 @@
<string name="downloads">ডাউনলোডগুলি</string>
<string name="duration_live">লাইভ</string>
<string name="youtube_restricted_mode_enabled_title">YouTube নিষিদ্ধ মোড</string>
<string name="video_is_age_restricted">ভিডিওটিকে বয়স সীমিত করা হয়েছে। প্রথমে সেটিংসে বয়স সীমাবদ্ধ ভিডিওগুলি সক্ষম করো।</string>
<string name="show_age_restricted_content_title">বয়স সীমাবদ্ধ কন্টেন্ট দেখাও</string>
<string name="content">কন্টেন্ট</string>
<string name="popup_playing_toast">পপআপ মোডে চলছে</string>
@ -287,6 +286,8 @@
<string name="seek_duration_title">দ্রুত-ফরওয়ার্ড/-পুনরায় সন্ধান সময়কাল</string>
<string name="use_inexact_seek_summary">অনির্দিষ্ট সন্ধান প্লেয়ারকে আরো দ্রুত গতিতে সন্ধান করার সুবিধা দেয়, কিন্তু এটি সম্পূর্ণ নির্ভুল নাও হতে পারে ৷ ৫, ১৫ ও ২৫ সেকেন্ডের জন্য এটা কাজ করবে না ৷</string>
<string name="use_inexact_seek_title">দ্রুত টানা ব্যাবহার করুন</string>
<string name="popup_remember_size_pos_summary">শেষ আকার এবং পপআপ সেট অবস্থান মনে রাখো</string>
<string name="popup_remember_size_pos_title">পপআপ আকার এবং অবস্থান মনে রাখো</string>
<string name="black_theme_title">কালো</string>
<string name="dark_theme_title">অন্ধকার</string>
<string name="light_theme_title">উজ্জ্বল</string>
@ -359,4 +360,119 @@
<string name="notification_action_3_title">চতুর্থ অ্যাকশন বাটন</string>
<string name="notification_action_2_title">তৃতীয় অ্যাকশন বাটন</string>
<string name="notification_action_1_title">দ্বিতীয় অ্যাকশান বাটন</string>
<string name="auto_queue_summary">একটি সংশ্লিষ্ট স্ট্রিম যোগ করে প্লেব্যাক সারি শেষ করা অব্যাহত রাখো (পুনরাবৃত্তি ছাড়া)</string>
<string name="clear_queue_confirmation_description">সক্রিয় প্লেয়ার সারি প্রতিস্থাপন করা হবে</string>
<string name="clear_queue_confirmation_summary">এক প্লেয়ার থেকে অন্য প্লেয়ারে পরিবর্তন করলে তোমার সারি প্রতিস্থাপিত হতে পারে</string>
<string name="clear_queue_confirmation_title">কিউ মোছার আগে নিশ্চিত করো</string>
<string name="notification_actions_at_most_three">কমপ্যাক্ট বিজ্ঞপ্তিতে প্রদর্শন করতে তুমি সর্বাধিক তিনটি ক্রিয়া নির্বাচন করতে পারো!</string>
<string name="notification_actions_summary">নিচের প্রতিটি প্রজ্ঞাপন ক্রিয়া সম্পাদনা করো। ডান দিকের চেকবাক্স ব্যবহার করে কম্প্যাক্ট নোটিফিকেশনে দেখানোর জন্য তিনটি পর্যন্ত নির্বাচন করো।</string>
<string name="notification_scale_to_square_image_summary">১৬:৯ থেকে ১:১অনুপাতে প্রদর্শিত ভিডিও থাম্বনেইল পরিবর্তন করো (বিকৃতি প্রবর্তন করতে পারে)</string>
<string name="settings_category_feed_title">ফিড</string>
<string name="overwrite">ওভাররাইট</string>
<string name="enqueue">সারিবদ্ধ</string>
<string name="recovering">পুনরুদ্ধাররত</string>
<string name="post_processing">পরে-প্রক্রিয়াকরণ</string>
<string name="queued">সারিবদ্ধ</string>
<string name="missions_header_pending">প্রক্রিয়ারত</string>
<string name="never">কখনো না</string>
<string name="updates_setting_title">হালনাগাদ</string>
<string name="playback_reset">পুনশুরু</string>
<string name="playback_step">স্টেপ</string>
<string name="playback_pitch">পিচ</string>
<string name="playback_tempo">টেম্পো</string>
<string name="export_ongoing">রপ্তানি করা হচ্ছে…</string>
<string name="import_ongoing">আমদানি করা হচ্ছে…</string>
<plurals name="subscribers">
<item quantity="one">%s সদস্যতা</item>
<item quantity="other">%s সদস্যতাগণ</item>
</plurals>
<string name="users">ব্যবহারকারীরা</string>
<string name="settings_category_notification_title">বিজ্ঞপ্তি</string>
<string name="resume_on_audio_focus_gain_summary">বাধার পর প্লে চালিয়ে যাও (উদাহরণস্বরূপ ফোনকল)</string>
<string name="subscriptions_export_unsuccessful">সদস্যতা রপ্তানি করা যায়নি</string>
<string name="subscriptions_import_unsuccessful">সদস্যতা/সাবস্ক্রিপশন আমদানি করা যায়নি</string>
<string name="playlist_no_uploader">স্বয়ংক্রিয়ভাবে উৎপাদিত (কোনও আপলোডার পাওয়া যায়নি)</string>
<string name="playlist_delete_failure">পছন্দ-তালিকা মুছে ফেলা যায়নি।</string>
<string name="set_as_playlist_thumbnail">প্লে-তালিকা থাম্বনেইল হিসেবে সেট করো</string>
<string name="no_valid_zip_file">কোনও বৈধ জিপ ফাইল নেই</string>
<string name="no_playlist_bookmarked_yet">এখনো কোন প্লে-তালিকা বুকমার্ক নেই</string>
<string name="no_channel_subscribed_yet">এখনও কোনও চ্যানেল সাবস্ক্রিপশন নেই</string>
<string name="main_page_content">মূল পৃষ্ঠার বিষয়বস্তু</string>
<string name="settings_file_charset_title">ফাইলের নামে অনুমোদিত অক্ষরসমূহ</string>
<string name="subtitle_activity_recaptcha">সমাধান হয়ে গেলে \"সম্পন্ন\" টিপো</string>
<string name="no_one_listening">কেউ শুনছে না</string>
<string name="no_one_watching">কেউ দেখছে না</string>
<string name="drawer_header_description">সেবাটি পরিবর্তন করো, বর্তমানে নির্বাচিত:</string>
<string name="empty_subscription_feed_subtitle">এখানে ঝিঝিপোকা ছাড়া আর কিছু নেই</string>
<string name="invalid_source">এই ধরনের কোন ফাইল/বিষয়বস্তুর উৎস নেই</string>
<string name="player_unrecoverable_failure">অপুনরুদ্ধারযোগ্য প্লেয়ার ত্রুটি ঘটেছে</string>
<string name="popup_playing_append">পপআপ প্লেয়ারে সারিবদ্ধ</string>
<string name="background_player_append">পটভূমি প্লেয়ারে সারিবদ্ধ</string>
<string name="peertube_instance_add_fail">ইন্সট্যান্সটি যাচাই করা যায়নি</string>
<string name="recaptcha_cookies_cleared">রিক্যাপচা কুকিগুলো পরিষ্কার করা হয়েছে</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">হ্যাঁ, এবং আংশিকভাবে দেখা ভিডিও</string>
<string name="error_permission_denied">সিস্টেম দ্বারা অনুমতি অগ্রাহ্য করা হয়েছে</string>
<string name="permission_denied">ব্যবস্থা দ্বারা ক্রিয়া অস্বীকার করা হয়েছে</string>
<string name="autoplay_summary">"স্বয়ংক্রিয়ভাবে প্লেব্যাক শুরু করো %s — তে"</string>
<string name="start_here_on_popup">একটি পপ-আপে প্লে শুরু করো</string>
<string name="start_here_on_background">পটভূমিতে প্লে শুরু করো</string>
<string name="app_description">অ্যান্ড্রয়েডে মুক্ত সহজ স্ট্রিমিং।</string>
<string name="export_data_summary">ইতিহাস, সদস্যতা এবং পছন্দ-তালিকা রপ্তানি করো</string>
<string name="show_hold_to_append_title">\"সংযোজন করতে ধরে রাখো\" পরামর্শ দেখাও</string>
<string name="feed_use_dedicated_fetch_method_title">উপলব্ধ হলে আলাদা ফিড থেকে এনো</string>
<string name="error_http_no_content">সার্ভার ডেটা পাঠায় না</string>
<string name="error_connect_host">সার্ভারে সংযোগ করা যাচ্ছে না</string>
<string name="import_title">আমদানি</string>
<string name="import_export_title">আমদানি/রপ্তানি</string>
<string name="conferences">সম্মেলন</string>
<string name="selection">নির্বাচন</string>
<string name="enable_playback_state_lists_summary">তালিকায় প্লেব্যাক অবস্থান সূচক দেখাও</string>
<string name="auto_queue_toggle">স্বত-সারি</string>
<string name="error_timeout">সংযোগের সময় শেষ</string>
<string name="error_postprocessing_failed">পোস্ট-প্রক্রিয়াকরণ ব্যর্থ হয়েছে</string>
<string name="switch_view">প্রদর্শন পরিবর্তন করো</string>
<string name="import_soundcloud_instructions_hint">তোমার আইডি, soundcloud.com/আইডি</string>
<string name="previous_export">আগের রপ্তানি</string>
<string name="import_file_title">ফাইল আমদানি করো</string>
<string name="export_to">রপ্তানি করো</string>
<string name="import_from">আমদানি করো</string>
<string name="bookmark_playlist">বুকমার্ক প্লেলিস্ট</string>
<string name="preferred_player_fetcher_notification_title">তথ্য আনা হচ্ছে…</string>
<string name="popup_player">পপআপ প্লেয়ার</string>
<string name="background_player">পটভূমি প্লেয়ার</string>
<string name="title_activity_play_queue">প্লে সারি</string>
<string name="most_liked">সর্বোচ্চ পছন্দ</string>
<string name="feed_page_summary">ফিড পৃষ্ঠা</string>
<string name="subscription_page_summary">সদস্যতা পৃষ্ঠা</string>
<string name="default_kiosk_page_summary">ডিফল্ট কিয়স্ক</string>
<string name="kiosk_page_summary">কিয়স্ক পৃষ্ঠা</string>
<string name="settings_file_replacement_character_title">প্রতিস্থাপক অক্ষর</string>
<plurals name="videos">
<item quantity="one">%sটি ভিডিও</item>
<item quantity="other">%sটি ভিডিও</item>
</plurals>
<string name="infinite_videos">∞ ভিডিও</string>
<string name="more_than_100_videos">১০০+ ভিডিও</string>
<plurals name="listening">
<item quantity="one">%s জন শ্রোতা</item>
<item quantity="other">%s জন শ্রোতা</item>
</plurals>
<plurals name="watching">
<item quantity="one">%s জন দেখছে</item>
<item quantity="other">%s জন দেখছে</item>
</plurals>
<plurals name="views">
<item quantity="one">%s বার দেখা</item>
<item quantity="other">%s বার দেখা</item>
</plurals>
<string name="enqueued">সারিবদ্ধ করা হয়েছে</string>
<string name="enqueue_stream">এনকুই</string>
<plurals name="seconds">
<item quantity="one">%d সেকেন্ড</item>
<item quantity="other">%d সেকেন্ড</item>
</plurals>
<string name="remove_watched">দেখা থেকে অপসারণ করো</string>
<string name="systems_language">সিস্টেম ডিফল্ট</string>
<string name="downloads_storage_use_saf_title">সাফ ব্যবহার করো</string>
<string name="notification_colorize_title">বিজ্ঞপ্তি রঙিন করো</string>
</resources>

View file

@ -37,7 +37,6 @@
<string name="settings_category_debug_title">Depuració</string>
<string name="content">Contingut</string>
<string name="show_age_restricted_content_title">Desactiva les restriccions per edat</string>
<string name="video_is_age_restricted">Mostra el vídeo restringit per edat. Podeu permetre aquesta mena de continguts des dels paràmetres.</string>
<string name="duration_live">Directe</string>
<string name="downloads">Baixades</string>
<string name="downloads_title">Baixades</string>
@ -143,6 +142,8 @@
<string name="kore_not_found">No s\'ha trobat l\'aplicació Kore. Voleu instal·lar-la\?</string>
<string name="show_play_with_kodi_title">Mostra «Reprodueix amb el Kodi»</string>
<string name="show_play_with_kodi_summary">Mostra una opció per reproduir un vídeo amb el centre multimèdia Kodi</string>
<string name="popup_remember_size_pos_title">Reproductor emergent intel·ligent</string>
<string name="popup_remember_size_pos_summary">Recorda la darrera mida i posició del reproductor emergent</string>
<string name="use_inexact_seek_title">Cerca ràpida poc precisa</string>
<string name="use_inexact_seek_summary">La cerca poc precisa permet que el reproductor cerqui una posició més ràpidament amb menys precisió. Cerques de 5, 15 o 25 segons no hi funcionaran.</string>
<string name="download_thumbnail_title">Carrega les miniatures</string>

View file

@ -91,6 +91,7 @@
<string name="live_streams_not_supported">پەخشی ڕاستەوخۆ پشتگیری ناکرێ لەئێستادا</string>
<string name="channel_unsubscribed">به‌شداریت نەما له‌ كه‌ناڵ</string>
<string name="player_stream_failure">ناتوانرێ ئەم پەخشە کارپێبکرێ</string>
<string name="popup_remember_size_pos_title">بیرهاتنه‌وه‌ی شوێن و قه‌باره‌ی په‌نجه‌ره‌ی بچووک</string>
<string name="player_recoverable_failure">گێڕانەوەی کارپێکەر بۆکاتی پێش کێشە</string>
<string name="minimize_on_exit_none_description">هیچیان</string>
<string name="pause_downloads_on_mobile_desc">بەسوودە بۆ کاتی گۆڕینی هێڵ بۆ داتای مۆبایل, لەگەڵ ئەوەشدا زۆربەی داگرتنەکان ڕاناگرێت</string>
@ -341,6 +342,7 @@
<string name="recaptcha_done_button">تەواو</string>
<string name="detail_likes_img_view_description">بەدڵبوون</string>
<string name="error_unable_to_load_license">ناتوانرێ مۆڵەت باربکرێ</string>
<string name="popup_remember_size_pos_summary">بیرهاتنه‌وه‌ی كۆتا قه‌باره‌ و شوێنی په‌نجه‌ره‌ی بچووك</string>
<string name="create">دروستکردن</string>
<string name="import_network_expensive_warning">ئەوە بزانە ئەم کردارە پێویستی بە هێڵێکی گران هەیە.
\n
@ -585,7 +587,6 @@
<string name="cancel">پاشگه‌زبوونه‌وه‌</string>
<string name="tracks">تراکەکان</string>
<string name="play_queue_audio_settings">ڕێکخستنەکانی دەنگ</string>
<string name="video_is_age_restricted">پیشاندانی ئەو ڤیدیۆیانەی سنوری تەمەنیان بۆ دانراوە. لە ڕێکخستنەکانەوە ڕێگەی پێدەدرێت.</string>
<string name="downloads_storage_ask_summary">پرسیارت لێ دەکرێت بۆ شوێنی داگرتنی هەر پەڕگەیەک</string>
<string name="title_last_played">دواین کارپێکراو</string>
<string name="could_not_setup_download_menu">ناتوانرێ لیستی داگرتن دابنرێ</string>

View file

@ -58,8 +58,7 @@
<string name="autoplay_by_calling_app_title">Automaticky přehrávat</string>
<string name="autoplay_by_calling_app_summary">Přehrává video, když je NewPipe otevřen z jiné aplikace</string>
<string name="content">Obsah</string>
<string name="show_age_restricted_content_title">Věkově omezený obsah</string>
<string name="video_is_age_restricted">Zobrazit video s věkovým omezením. Změnit tuto volbu v budoucnu lze v \"Nastavení\".</string>
<string name="show_age_restricted_content_title">Zobrazit věkově omezený obsah</string>
<string name="duration_live">Živě</string>
<string name="light_parsing_error">Nebylo možné kompletně analyzovat stránku</string>
<string name="main_bg_subtitle">Začít klepnutím na \"Hledat\"
@ -121,6 +120,8 @@
<string name="show_higher_resolutions_title">Zobrazovat vyšší rozlišení</string>
<string name="show_higher_resolutions_summary">Pouze některá zařízení dokáží přehrát 2K/4K videa</string>
<string name="default_video_format_title">Výchozí formát videa</string>
<string name="popup_remember_size_pos_title">Pamatovat si vlastnosti vyskakovacího okna</string>
<string name="popup_remember_size_pos_summary">Pamatovat si poslední velikost a pozici vyskakovacího okna</string>
<string name="popup_mode_share_menu_title">Režim vyskakovacího okna</string>
<string name="subscribe_button_title">Odebírat</string>
<string name="subscribed_button_title">Odebíráno</string>
@ -600,7 +601,7 @@
<string name="songs">Písně</string>
<string name="restricted_video">Toto je video s věkovým omezením.
\n
\nPokud ho chcete vidět, povolte \"Věkově omezený obsah\" v Nastavení.</string>
\nPokud ho chcete vidět, povolte \"%1$s\" v Nastavení.</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Ano, i zčásti shlédnutá videa</string>
<string name="remove_watched_popup_title">Odstranit shlédnutá videa\?</string>
<string name="remove_watched">Odstranit shlédnutá</string>
@ -608,7 +609,7 @@
\nJste se jisti\? Nelze zvrátit!</string>
<string name="show_original_time_ago_summary">Původní texty služeb budou viditelné u položek streamů</string>
<string name="show_original_time_ago_title">U položek ukázat původní čas \"před\"</string>
<string name="youtube_restricted_mode_enabled_title">Omezený režim YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Zapnout \"Omezený režim YouTube\"</string>
<string name="video_detail_by">Od %s</string>
<string name="channel_created_by">Vytvořil %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Miniatura avatara kanálu</string>
@ -643,4 +644,12 @@
<string name="notification_action_0_title">První akční tlačítko</string>
<string name="notification_scale_to_square_image_summary">Zmenšit miniaturu videa zobrazenou v oznámení z poměru stran 16: 9 na 1: 1 (může způsobit zkreslení)</string>
<string name="notification_scale_to_square_image_title">Změnit poměr stran miniatury na 1:1</string>
<string name="show_memory_leaks">Ukázat memory leaks</string>
<string name="enqueued">Zařazeno do fronty</string>
<string name="enqueue_stream">Zařadit do fronty</string>
<string name="clear_cookie_summary">Vymazat cookies, které NewPipe uloží, po vyřešení reCAPTCHA</string>
<string name="recaptcha_cookies_cleared">Cookies reCAPTCHA byly vymazány</string>
<string name="clear_cookie_title">Vymazat cookies reCAPTCHA</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube poskytuje \"Omezený režim\", který skrývá potenciální obsahy pro dospělé.</string>
<string name="show_age_restricted_content_summary">Zobrazit obsah, i když je patrně nevhodný pro děti, protože odkazuje na věkové omezení (např. 18+).</string>
</resources>

View file

@ -61,6 +61,8 @@
<string name="light_theme_title">Lyst</string>
<string name="dark_theme_title">Mørkt</string>
<string name="black_theme_title">Sort</string>
<string name="popup_remember_size_pos_title">Husk størrelse og placering af pop op</string>
<string name="popup_remember_size_pos_summary">Husk sidste størrelse og placering af pop op-afspiller</string>
<string name="use_inexact_seek_title">Brug hurtig og upræcis søgning</string>
<string name="use_inexact_seek_summary">Upræcis søgning lader afspilleren finde placeringer hurtigere, men mindre præcist</string>
<string name="download_thumbnail_title">Indlæs miniaturebilleder</string>
@ -108,7 +110,6 @@
<string name="popup_playing_append">Føjet til pop op-afspilningskøen</string>
<string name="content">Indhold</string>
<string name="show_age_restricted_content_title">Aldersbegrænset indhold</string>
<string name="video_is_age_restricted">Vis aldersbegrænsede videoer. Du kan tillade denne type videoer under Indstillinger.</string>
<string name="duration_live">LIVE</string>
<string name="downloads">Downloads</string>
<string name="downloads_title">Downloads</string>

View file

@ -2,7 +2,7 @@
<resources>
<string name="view_count_text">%1$s Aufrufe</string>
<string name="upload_date_text">Veröffentlicht am %1$s</string>
<string name="no_player_found">Keinen Stream-Player gefunden. VLC installieren\?</string>
<string name="no_player_found">Kein Stream-Player gefunden. VLC installieren\?</string>
<string name="install">Installieren</string>
<string name="cancel">Abbrechen</string>
<string name="open_in_browser">Im Browser öffnen</string>
@ -10,7 +10,7 @@
<string name="download">Herunterladen</string>
<string name="search">Suchen</string>
<string name="settings">Einstellungen</string>
<string name="did_you_mean">Meintest du: \"%1$s\"\?</string>
<string name="did_you_mean">Meintest du „%1$s“\?</string>
<string name="share_dialog_title">Teilen mit</string>
<string name="choose_browser">Browser auswählen</string>
<string name="screen_rotation">Bildschirm drehen</string>
@ -56,8 +56,7 @@
<string name="parsing_error">Konnte Webseite nicht analysieren</string>
<string name="content_not_available">Inhalt nicht verfügbar</string>
<string name="content">Inhalt</string>
<string name="show_age_restricted_content_title">Altersbeschränkte Inhalte</string>
<string name="video_is_age_restricted">Altersbeschränktes Video anzeigen. Spätere Änderungen sind in den Einstellungen möglich.</string>
<string name="show_age_restricted_content_title">Altersbeschränkte Inhalte anzeigen</string>
<string name="could_not_setup_download_menu">Konnte Download-Menü nicht einrichten</string>
<string name="live_streams_not_supported">Live-Streams werden noch nicht unterstützt</string>
<string name="light_parsing_error">Konnte Webseite nicht vollständig analysieren</string>
@ -129,7 +128,9 @@
<string name="show_higher_resolutions_summary">Nur manche Geräte können Videos in 2K/4K abspielen</string>
<string name="controls_background_title">Hintergrund</string>
<string name="controls_popup_title">Pop-up</string>
<string name="popup_remember_size_pos_title">Pop-up Eigenschaften merken</string>
<string name="use_external_video_player_summary">Entfernt Tonspur bei manchen Auflösungen</string>
<string name="popup_remember_size_pos_summary">Letzte Größe und Position des Pop-ups merken</string>
<string name="player_gesture_controls_title">Gestensteuerung</string>
<string name="player_gesture_controls_summary">Helligkeit und Lautstärke mittels Gesten einstellen</string>
<string name="show_search_suggestions_title">Suchvorschläge</string>
@ -229,7 +230,7 @@
<string name="kiosk_page_summary">Kiosk-Seite</string>
<string name="select_a_kiosk">Kiosk auswählen</string>
<string name="kiosk">Kiosk</string>
<string name="show_hold_to_append_summary">Tipp anzeigen, wenn der Hintergrundwiedergabe- oder Pop-up-Button „Details:“ im Video gedrückt wird</string>
<string name="show_hold_to_append_summary">Tipp anzeigen, wenn der Hintergrundwiedergabe- oder Pop-up-Knopf „Details:“ im Video gedrückt wird</string>
<string name="background_player_append">In der Warteschlange der Hintergrundwiedergabe</string>
<string name="new_and_hot">Neu und Heiß</string>
<string name="hold_to_append">Halten, um zur Wiedergabeliste hinzuzufügen</string>
@ -343,12 +344,15 @@
<string name="enable_leak_canary_summary">Die Überwachung von Speicherlecks kann dazu führen, dass die App beim Heap-Dumping nicht mehr reagiert</string>
<string name="enable_disposed_exceptions_title">Fehler außerhalb des Lebenszyklus melden</string>
<string name="enable_disposed_exceptions_summary">Erzwingen der Meldung unzustellbarer Rx-Ausnahmen außerhalb des Lebenszyklus von Fragmenten oder Aktivitäten nach der Entsorgung</string>
<string name="import_youtube_instructions">Importiere YouTube-Abonnements, indem du die Exportdatei herunterlädst:
<string name="import_youtube_instructions">Importiere YouTube-Abonnements aus dem Google Takeout:
\n
\n1. Gehe zu dieser URL: %1$s
\n2. Melde dich an, falls du dazu aufgefordert wirst.
\n3. Der Ladevorgang sollte beginnen (das ist die Exportdatei)</string>
<string name="import_soundcloud_instructions">Importiere ein SoundCloud-Profil, indem du entweder die URL oder deine ID eingibst:
\n2. Melde dich an, falls du dazu aufgefordert wirst
\n3. Klicke auf \"Alle Daten enthalten\", dann auf \"Alle abwählen\", wähle dann nur \"Abonnements\" und klicke auf \"OK\"
\n4. Klicke auf \"Nächster Schritt\" und dann auf \"Export erstellen\"
\n5. Klicke auf die Schaltfläche \"Download\", nachdem sie erscheint und
\n6. Entpacke aus dem heruntergeladenen Takeout-Zip die .json-Datei (normalerweise unter \"YouTube und YouTube Music/subscriptions/subscriptions.json\") und importiere sie hier.</string>
<string name="import_soundcloud_instructions">Importiere ein SoundCloud-Profil, indem die URL oder deine ID eingegeben wird:
\n
\n1. Aktiviere den Desktop-Modus in einem Web-Browser (die Seite ist für mobile Geräte nicht verfügbar)
\n2. Gehe zu dieser URL: %1$s
@ -398,7 +402,7 @@
<string name="tab_new">Neuer Tab</string>
<string name="tab_choose">Tab wählen</string>
<string name="volume_gesture_control_title">Gestensteuerung für Lautstärke</string>
<string name="volume_gesture_control_summary">Player-Lautstärke über Gesten steuern</string>
<string name="volume_gesture_control_summary">Verwende Gesten um die Abspielerlautstärke einzustellen</string>
<string name="brightness_gesture_control_title">Gestensteuerung für Helligkeit</string>
<string name="brightness_gesture_control_summary">Player-Helligkeit über Gesten steuern</string>
<string name="settings_category_updates_title">Aktualisierungen</string>
@ -420,7 +424,7 @@
<string name="grid">Gitter</string>
<string name="auto">Auto</string>
<string name="switch_view">Ansicht wechseln</string>
<string name="app_update_notification_content_title">NewPipe-Update ist verfügbar!</string>
<string name="app_update_notification_content_title">Eine NewPipe-Aktualisierung ist verfügbar!</string>
<string name="app_update_notification_content_text">Zum Herunterladen antippen</string>
<string name="missions_header_finished">Fertig</string>
<string name="missions_header_pending">Ausstehend</string>
@ -481,7 +485,7 @@
<string name="start_downloads">Downloads starten</string>
<string name="pause_downloads">Downloads anhalten</string>
<string name="downloads_storage_ask_title">Download-Ziel abfragen</string>
<string name="downloads_storage_ask_summary">Du wirst gefragt, wohin du jeden Download speichern willst</string>
<string name="downloads_storage_ask_summary">Du wirst gefragt, wo jede heruntergeladene Datei gespeichert werden soll</string>
<string name="downloads_storage_ask_summary_kitkat">Du wirst gefragt, wohin du jeden Download speichern willst.
\nAktiviere diese Option, wenn du auf die externe SD-Karte herunterladen möchtest</string>
<string name="downloads_storage_use_saf_title">SAF verwenden</string>
@ -490,7 +494,7 @@
<string name="clear_playback_states_title">Wiedergabepositionen löschen</string>
<string name="clear_playback_states_summary">Alle Wiedergabepositionen löschen</string>
<string name="delete_playback_states_alert">Alle Wiedergabepositionen löschen\?</string>
<string name="download_choose_new_path">Ändere die Downloadordner, damit sie wirksam werden</string>
<string name="download_choose_new_path">Wähle einen neuen Heruntergeladen-Ordner</string>
<string name="drawer_header_description">Dienst umschalten, aktuell ausgewählt:</string>
<string name="default_kiosk_page_summary">Standard-Kiosk</string>
<string name="no_one_watching">Niemand schaut zu</string>
@ -592,7 +596,7 @@
\nEs wird hoffentlich in einer zukünftigen Version unterstützt.</string>
<string name="restricted_video">Dieses Video ist altersbeschränkt.
\n
\nAktiviere in den Einstellungen „Altersbeschränkte Inhalte“, falls du diese sehen möchtest.</string>
\nAktiviere in den Einstellungen „%1$s“, falls du diese sehen möchtest.</string>
<string name="remove_watched_popup_warning">Videos, die vor und nach dem Hinzufügen zur Wiedergabeliste angeschaut wurden, werden entfernt.
\nBist du sicher\? Dies kann nicht rückgängig gemacht werden!</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Ja, und teilweise gesehene Videos</string>
@ -600,7 +604,7 @@
<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_summary">Originaltexte von Diensten werden in Stream-Elementen sichtbar sein</string>
<string name="youtube_restricted_mode_enabled_title">Eingeschränkter YouTube-Modus</string>
<string name="youtube_restricted_mode_enabled_title">Aktivieren des „Eingeschränkten Modus“ von YouTube</string>
<string name="detail_sub_channel_thumbnail_view_description">Avatarbild des Kanals</string>
<string name="channel_created_by">Erstellt von %s</string>
<string name="video_detail_by">Von %s</string>
@ -633,4 +637,14 @@
<string name="clear_queue_confirmation_summary">Den Player zu wechseln könnte deine Warteschlange überschreiben</string>
<string name="clear_queue_confirmation_title">Bestätige das Leeren der Warteschlange</string>
<string name="clear_queue_confirmation_description">Die aktive Wiedergabeliste wird ersetzt werden</string>
<string name="enqueued">Eingereiht</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube bietet einen „Eingeschränkten Modus“, der potenzielle Inhalte für Erwachsene ausblendet.</string>
<string name="show_memory_leaks">Speicherlecks anzeigen</string>
<string name="clear_cookie_summary">Lösche Cookies, die NewPipe speichert, wenn du ein reCAPTCHA löst</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA-Cookies wurden gelöscht</string>
<string name="clear_cookie_title">reCAPTCHA-Cookies löschen</string>
<string name="show_age_restricted_content_summary">Zeige Inhalt, der möglicherweise unpassend für Kinder ist, da er eine Altersbeschränkung (wie z.B. 18+) hat.</string>
<string name="enqueue_stream">Wiedergabe einreihen</string>
<string name="notification_colorize_summary">Android kann die Farbe der Benachrichtigung entsprechend der Hauptfarbe in der Miniaturansicht anpassen (beachte, dass dies nicht auf allen Geräten verfügbar ist)</string>
<string name="notification_colorize_title">Benachrichtigung farblich anpassen</string>
</resources>

View file

@ -19,7 +19,7 @@
<string name="download_path_title">Φάκελος λήψης βίντεο</string>
<string name="download_path_summary">Τα ληφθέντα αρχεία βίντεο αποθηκεύονται εδώ</string>
<string name="download_path_dialog_title">Επιλέξτε φάκελο λήψης για αρχεία βίντεο</string>
<string name="download_path_audio_title">Διαδρομή λήψης αρχείων ήχου</string>
<string name="download_path_audio_title">Φάκελος λήψης ήχου</string>
<string name="download_path_audio_summary">Τα ληφθέντα αρχεία ήχου αποθηκεύονται εδώ</string>
<string name="download_path_audio_dialog_title">Επιλέξτε φάκελο λήψης για αρχεία ήχου</string>
<string name="default_resolution_title">Προεπιλεγμένη ανάλυση</string>
@ -41,16 +41,16 @@
<string name="settings_category_other_title">Άλλα</string>
<string name="background_player_playing_toast">Αναπαραγωγή στο παρασκήνιο</string>
<string name="network_error">Σφάλμα δικτύου</string>
<string name="list_thumbnail_view_description">Μικρογραφία προεπισκόπισης βίντεο</string>
<string name="list_thumbnail_view_description">Μικρογραφία προεπισκόπησης βίντεο</string>
<string name="detail_thumbnail_view_description">Αναπαραγωγή βίντεο, διάρκεια:</string>
<string name="detail_uploader_thumbnail_view_description">Μικρογραφία εικόνας προφίλ του χρήστη</string>
<string name="detail_likes_img_view_description">Like</string>
<string name="detail_dislikes_img_view_description">Dislike</string>
<string name="use_tor_title">Χρήση του Tor</string>
<string name="use_tor_summary">(Πειραματικό) Αναγκάζει την κίνηση λήψης μέσω Tor για αυξημένη προστασία προσωπικών δεδομένων (η αναπαραγωγή δεν υποστηρίζεται ακόμη).</string>
<string name="err_dir_create">Δεν μπόρεσε να δημιουργηθεί ο φάκελος \'%1$s\'</string>
<string name="info_dir_created">Δημιουργήθηκε ο φάκελος \'%1$s\'</string>
<string name="short_billion">Δις</string>
<string name="use_tor_summary">(Πειραματικό) Εξαναγκάζει τη λήψη μέσω του Tor για αυξημένη ιδιωτικότητα (η αναπαραγωγή δεν υποστηρίζεται ακόμη).</string>
<string name="err_dir_create">Αδυναμία δημιουργίας φακέλου λήψεων \'%1$s\'</string>
<string name="info_dir_created">Δημιουργήθηκε ο φάκελος λήψεων \'%1$s\'</string>
<string name="short_billion">δισ/ρια</string>
<string name="open_in_popup_mode">Άνοιγμα σε αναδυόμενο παράθυρο</string>
<string name="subscribe_button_title">Εγγραφή</string>
<string name="subscribed_button_title">Εγγεγραμμένος</string>
@ -71,7 +71,7 @@
<string name="what_happened_headline">Τι συνέβη:</string>
<string name="your_comment">Το σχόλιό σας (στα Αγγλικά):</string>
<string name="error_details_headline">Λεπτομέρειες:</string>
<string name="report_error">Αναφορά σφάλματος</string>
<string name="report_error">Αναφορά Σφάλματος</string>
<string name="video">Βίντεο</string>
<string name="audio">Ήχος</string>
<string name="pause">Παύση</string>
@ -87,18 +87,19 @@
<string name="title_activity_history">Ιστορικό</string>
<string name="action_history">Ιστορικό</string>
<string name="show_info">Εμφάνιση πληροφοριών</string>
<string name="main_bg_subtitle">Πατήστε \"Αναζήτηση\" για να ξεκινήσετε</string>
<string name="main_bg_subtitle">Πατήστε \"Αναζήτηση\" για να ξεκινήσετε
\n</string>
<string name="no_player_found_toast">Δε βρέθηκε πρόγραμμα αναπαραγωγής ροής δεδομένων (μπορείτε να εγκαταστήσετε το VLC για να κάνετε αναπαραγωγή).</string>
<string name="controls_download_desc">Λήψη του αρχείου ροής</string>
<string name="use_external_video_player_summary">Αφαίρεση του ήχου από κάποιες αναλύσεις</string>
<string name="use_external_video_player_summary">Αφαιρείται ο ήχος από κάποιες αναλύσεις</string>
<string name="popup_mode_share_menu_title">Λειτουργία αναδυόμενου παραθύρου</string>
<string name="channel_unsubscribed">Απεγγραφή από το κανάλι</string>
<string name="channel_unsubscribed">Το κανάλι διαγράφηκε</string>
<string name="subscription_change_failed">Αδύνατη η αλλαγή της εγγραφής</string>
<string name="subscription_update_failed">Αδύνατη η ενημέρωση της εγγραφής</string>
<string name="tab_main">Κύριο</string>
<string name="tab_subscriptions">Συνδρομές</string>
<string name="tab_bookmarks">Αγαπημένες λίστες αναπαραγωγής</string>
<string name="fragment_feed_title">Νέα</string>
<string name="fragment_feed_title">Τι νέο υπάρχει</string>
<string name="controls_background_title">Στο παρασκήνιο</string>
<string name="controls_popup_title">Αναδυόμενο παράθυρο</string>
<string name="controls_add_to_playlist_title">Προσθήκη σε</string>
@ -107,39 +108,40 @@
<string name="default_popup_resolution_title">Προεπιλεγμένη ανάλυση αναδυόμενου παραθύρου</string>
<string name="show_higher_resolutions_title">Εμφάνιση υψηλότερων αναλύσεων</string>
<string name="default_video_format_title">Προεπιλεγμένη μορφή βίντεο</string>
<string name="popup_remember_size_pos_title">Ενθύμιση τις ιδιότητες του αναδυόμενου παραθύρου</string>
<string name="popup_remember_size_pos_summary">Ενθύμιση του τελευταίου μεγέθους και θέσης του παραθύρου</string>
<string name="use_inexact_seek_title">Χρήση γρήγορης ανακριβούς αναζήτησης</string>
<string name="use_inexact_seek_summary">Η μην ακριβής αναζήτηση επιτρέπει στην εφαρμογή να αναζητεί θέσεις στο βίντεο γρηγορότερα με μειωμένη ακρίβεια. Δε λειτουργεί για διαστήματα των 5, 15 ή 25 δευτερολέπτων.</string>
<string name="download_thumbnail_title">Φόρτωση μικρογραφιών</string>
<string name="download_thumbnail_summary">Με την απενεργοποίηση δε φορτώνονται οι μικρογραφίες, χρησιμοποιώντας λιγότερα δεδομένα και μνήμη. Οι αλλαγές σβήνουν τις προσωρινά αποθηκευμένες εικόνες στη μνήμη και στον δίσκο.</string>
<string name="download_thumbnail_summary">Με την απενεργοποίηση δε φορτώνονται οι μικρογραφίες, εξοικονομώντας δεδομένα και μνήμη. Οι αλλαγές σβήνουν τις προσωρινά αποθηκευμένες εικόνες στη μνήμη και στον δίσκο.</string>
<string name="thumbnail_cache_wipe_complete_notice">Εκκαθαρίστηκε η προσωρινή μνήμη εικόνων</string>
<string name="metadata_cache_wipe_title">Εκκαθάριση προσωρινά αποθηκευμένων μεταδεδομένων</string>
<string name="metadata_cache_wipe_summary">Αφαίρεση όλων των προσωρινά αποθηκευμένων δεδομένων ιστοσελίδων</string>
<string name="metadata_cache_wipe_complete_notice">Η προσωρινή μνήμη μεταδεδομένων εκκαθαρίστηκε</string>
<string name="auto_queue_title">Αυτόματη πρόσθεση της επόμενης ροής στην ουρά</string>
<string name="auto_queue_summary">Συνεχίστε να τερματίζετε (μη επαναλαμβανόμενη) τη σειρά αναπαραγωγής προσθέτοντας μια σχετική ροή</string>
<string name="auto_queue_summary">Συνέχεια της τρέχουσας (μη επαναλαμβανόμενης) ουράς μετά τη λήξη της, με την προσθήκη μιας σχετικής ροής</string>
<string name="player_gesture_controls_title">Έλεγχος αναπαραγωγής με χειρονομίες</string>
<string name="player_gesture_controls_summary">Χρήση χειρονομιών για τον έλεγχο της φωτεινότητας και της έντασης ήχου</string>
<string name="show_search_suggestions_summary">Εμφάνιση προτάσεων ενώ κάνετε αναζήτηση</string>
<string name="enable_search_history_summary">Αποθήκευση αναζητήσεων στη συσκευή</string>
<string name="enable_watch_history_title">Προβολή Ιστορικού</string>
<string name="enable_watch_history_summary">Κρατήστε ιστορικό των βίντεο που έχετε δει</string>
<string name="resume_on_audio_focus_gain_title">Συνέχεια αναπαραγωγής</string>
<string name="enable_watch_history_summary">Κρατήστε ιστορικό των αναπαραχθέντων βίντεο</string>
<string name="resume_on_audio_focus_gain_title">Ανάκτηση αναπαραγωγής</string>
<string name="resume_on_audio_focus_gain_summary">Συνέχιση της αναπαραγωγής έπειτα από διακοπές (π.χ. κλήσεις)</string>
<string name="show_hold_to_append_title">Εμφάνιση της βοήθειας \"Πιέστε παρατεταμένα για πρόσθεση\"</string>
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί Παρασκηνίου ή Αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο</string>
<string name="show_hold_to_append_title">Εμφάνιση επεξήγησης του \"Πιέστε παρατεταμένα για προσθήκη\"</string>
<string name="show_hold_to_append_summary">Εμφάνιση υπόδειξης όταν πατηθεί το κουμπί παρασκηνίου ή αναδυόμενου παραθύρου στη σελίδα λεπτομερειών του βίντεο</string>
<string name="default_content_country_title">Προεπιλεγμένη χώρα περιεχομένου</string>
<string name="service_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_history_title">Ιστορικό και προσωρινή αποθήκευση</string>
<string name="settings_category_history_title">Ιστορικό και προσωρινή μνήμη</string>
<string name="settings_category_popup_title">Αναδυόμενο παράθυρο</string>
<string name="settings_category_debug_title">Απασφαλμάτωση</string>
<string name="settings_category_debug_title">Αποσφαλμάτωση</string>
<string name="popup_playing_toast">Αναπαραγωγή σε αναδυόμενο παράθυρο</string>
<string name="background_player_append">Προστέθηκε στη λίστα αναπαραγωγής παρασκηνίου</string>
<string name="popup_playing_append">Προστέθηκε στη λίστα αναπαραγωγής αναδυόμενου παραθύρου</string>
<string name="content">Περιεχόμενο</string>
<string name="show_age_restricted_content_title">Περιεχόμενο περιορισμένης ηλικίας</string>
<string name="video_is_age_restricted">Εμφάνιση βίντεο με περιορισμό ηλικίας. Μελλοντικές αλλαγές είναι δυνατές από τις ρυθμίσεις.</string>
<string name="show_age_restricted_content_title">Εμφάνιση περιεχομένου περιορισμένης ηλικίας</string>
<string name="duration_live">Ζωντανά</string>
<string name="error_report_title">Αναφορά σφαλμάτων</string>
<string name="channels">Κανάλια</string>
@ -158,7 +160,7 @@
<string name="just_once">Μόνο μία φορά</string>
<string name="file">Αρχείο</string>
<string name="notification_channel_name">Ειδοποίηση NewPipe</string>
<string name="notification_channel_description">Ειδοποιήσεις για την αναπαραγωγή Παρασκηνίου και Αναδυόμενου Παραθύρου</string>
<string name="notification_channel_description">Ειδοποιήσεις αναπαραγωγής παρασκηνίου και αναδυόμενου παραθύρου</string>
<string name="unknown_content">[Άγνωστο]</string>
<string name="toggle_orientation">Αλλαγή προσανατολισμού</string>
<string name="switch_to_background">Αλλαγή σε Παρασκήνιο</string>
@ -166,10 +168,10 @@
<string name="switch_to_main">Αλλαγή σε Κύριο</string>
<string name="import_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="clear_views_history_title">Εκκαθάριση ιστορικού προβολής</string>
<string name="clear_views_history_summary">Διαγράφει το ιστορικό των ροών που έχουν αναπαραχθεί</string>
<string name="clear_views_history_summary">Διαγράφει το ιστορικό των αναπαραχθέντων ροών και των θέσεων αναπαραγωγής</string>
<string name="delete_view_history_alert">Διαγραφή ολόκληρου του ιστορικού προβολής;</string>
<string name="watch_history_deleted">Το στορικό προβολής διαγράφηκε.</string>
<string name="clear_search_history_title">Διαγραφή ιστορικού αναζητήσεων</string>
@ -177,25 +179,25 @@
<string name="delete_search_history_alert">Διαγραφή ολόκληρου του ιστορικού αναζητήσεων;</string>
<string name="search_history_deleted">Το ιστορικό αναζητήσεων διαγράφηκε.</string>
<string name="could_not_load_thumbnails">Δεν ήταν δυνατή η φόρτωση όλων των εικονιδίων</string>
<string name="youtube_signature_deobfuscation_error">Δεν ήταν δυνατή η αποκρυπτογράφηση της υπογραφής του URL του βίντεο</string>
<string name="youtube_signature_deobfuscation_error">Δεν ήταν δυνατή η αποκρυπτογράφηση της υπογραφής της URL του βίντεο</string>
<string name="parsing_error">Δεν ήταν δυνατή η ανάλυση του ιστοτόπου</string>
<string name="light_parsing_error">Δεν ήταν δυνατή η ανάλυση ολόκληρου του ιστοτόπου</string>
<string name="content_not_available">Το περιεχόμενο δεν είναι διαθέσιμο</string>
<string name="could_not_setup_download_menu">Δεν ήταν δυνατή η ρύθμιση του μενού λήψεων</string>
<string name="live_streams_not_supported">Η Ζωντανή Ροή δεν υποστηρίζεται ακόμα</string>
<string name="could_not_get_stream">Δεν ήταν δυνατή η λήψη καμίας ροής</string>
<string name="live_streams_not_supported">Οι ζωντανές ροές δεν υποστηρίζονται ακόμα</string>
<string name="could_not_get_stream">Δεν ήταν δυνατή η λήψη οποιασδήποτε ροής</string>
<string name="could_not_load_image">Δεν ήταν δυνατή η φόρτωση της εικόνας</string>
<string name="app_ui_crash">Η εφαρμογή κράσαρε</string>
<string name="app_ui_crash">Η εφαρμογή κατέρρευσε</string>
<string name="player_stream_failure">Δεν ήταν δυνατή η αναπαραγωγή αυτής της ροής</string>
<string name="player_unrecoverable_failure">Συνέβη ένα μη ανακτήσιμο σφάλμα στη συσκευή αναπαραγωγής</string>
<string name="player_recoverable_failure">Ανάκτηση από σφάλμα της συσκευής αναπαραγωγής</string>
<string name="external_player_unsupported_link_type">Οι εξωτερικές συσκευές αναπαραγωγής δεν υποστηρίζουν αυτού του είδους συνδέσμους</string>
<string name="invalid_url_toast">Μη έγκυρο URL</string>
<string name="invalid_url_toast">Μη έγκυρη URL</string>
<string name="video_streams_empty">Δε βρέθηκαν ροές βίντεο</string>
<string name="audio_streams_empty">Δε βρέθηκαν ροές ήχου</string>
<string name="invalid_directory">Δεν υπάρχει αυτός ο φάκελος</string>
<string name="invalid_source">Δεν υπάρχει το αρχείο/πηγή περιεχομένου</string>
<string name="invalid_file">Το αρχείο δεν υπάρχει ή δεν έχουμε επαρκή εξουσιοδότηση για να διαβάσουμε ή να γράψουμε σε αυτό</string>
<string name="invalid_file">Το αρχείο δεν υπάρχει ή δεν υπάρχει επαρκής εξουσιοδότηση ανάγνωσης ή εγγραφής σε αυτό</string>
<string name="file_name_empty_error">Το όνομα αρχείου δεν μπορεί να είναι κενό</string>
<string name="error_occurred_detail">Προέκυψε ένα σφάλμα: %1$s</string>
<string name="no_streams_available_download">Δεν υπάρχουν διαθέσιμες ροές για λήψη</string>
@ -208,13 +210,13 @@
<string name="empty_subscription_feed_subtitle">Δεν υπάρχει τίποτα εδώ</string>
<string name="detail_drag_description">Σύρετε για ταξινόμηση</string>
<string name="retry">Προσπάθεια εκ νέου</string>
<string name="storage_permission_denied">Παραχώρηση πρόσβασης πρώτα στον αποθηκευτικό χώρο</string>
<string name="short_thousand">χιλ</string>
<string name="short_million">Εκ</string>
<string name="no_subscribers">Κανένας εγγεγραμένος χρήστης</string>
<string name="storage_permission_denied">Παραχώρηση πρώτα πρόσβασης στον αποθηκευτικό χώρο</string>
<string name="short_thousand">χιλ.</string>
<string name="short_million">εκ/ρια</string>
<string name="no_subscribers">Κανένας συνδρομητής</string>
<plurals name="subscribers">
<item quantity="one">%s εγγεγραμένος χρήστης</item>
<item quantity="other">%s εγγεγραμένοι χρήστες</item>
<item quantity="one">%s συνδρομητής</item>
<item quantity="other">%s συνδρομητές</item>
</plurals>
<string name="no_views">Καμία προβολή</string>
<plurals name="views">
@ -223,8 +225,8 @@
</plurals>
<string name="no_videos">Κανένα βίντεο</string>
<plurals name="videos">
<item quantity="one">%s Βίντεο</item>
<item quantity="other">%s Βίντεο</item>
<item quantity="one">%s βίντεο</item>
<item quantity="other">%s βίντεο</item>
</plurals>
<string name="start">Εκκίνηση</string>
<string name="view">Αναπαραγωγή</string>
@ -234,7 +236,7 @@
<string name="checksum">Άθροισμα ελέγχου</string>
<string name="dismiss">Αγνόηση</string>
<string name="rename">Μετονομασία</string>
<string name="add">Νέα αποστολη</string>
<string name="add">Νέα αποστολή</string>
<string name="finish">ΟΚ</string>
<string name="msg_name">Όνομα αρχείου</string>
<string name="msg_threads">Νήματα</string>
@ -243,40 +245,40 @@
<string name="msg_running">Λήψη NewPipe</string>
<string name="msg_wait">Παρακαλώ περιμένετε…</string>
<string name="msg_copied">Αντιγράφηκε στο πρόχειρο</string>
<string name="no_available_dir">Παρακαλώ ορίστε έναν διαθέσιμο φάκελο λήψεων αργότερα στις ρυθμίσεις</string>
<string name="no_available_dir">Παρακαλώ ορίστε έναν φάκελο λήψεων αργότερα στις ρυθμίσεις</string>
<string name="msg_popup_permission">Αυτή η άδεια είναι απαραίτητη για
\nτο άνοιγμα αναδυόμενων παραθύρων</string>
<string name="one_item_deleted">1 αντικείμενο διαγράφηκε.</string>
<string name="title_activity_recaptcha">Πρόκληση reCAPTCHA</string>
<string name="recaptcha_request_toast">Ζητήθηκε πρόκληση reCAPTCHA</string>
<string name="title_activity_recaptcha">Δοκιμασία reCAPTCHA</string>
<string name="recaptcha_request_toast">Ζητήθηκε δοκιμασία reCAPTCHA</string>
<string name="settings_file_charset_title">Επιτρεπόμενοι χαρακτήρες σε ονόματα αρχείων</string>
<string name="settings_file_replacement_character_summary">Οι μη έγκυροι χαρακτήρες αντικαθίστανται με αυτήν την τιμή</string>
<string name="settings_file_replacement_character_title">Αντικαταστάτης χαρακτήρας</string>
<string name="charset_most_special_characters">Οι περισσότεροι ειδικοί χαρακτήρες</string>
<string name="toast_no_player">Δεν υπάρχει εφαρμογή εγκατεστημένη για την αναπαραγωγή αυτού του αρχείου</string>
<string name="toast_no_player">Δεν υπάρχει εγκατεστημένη εφαρμογή για την αναπαραγωγή αυτού του αρχείου</string>
<string name="title_activity_about">Σχετικά με το NewPipe</string>
<string name="action_about">Περί</string>
<string name="title_licenses">Άδειες Τρίτων</string>
<string name="copyright" formatted="true">© %1$s από %2$s υπό %3$s</string>
<string name="error_unable_to_load_license">Δεν ήταν δυνατή η φόρτωση της άδειας</string>
<string name="tab_about">Περί</string>
<string name="tab_contributors">Συνεισφέροντες</string>
<string name="tab_contributors">Συντελεστές</string>
<string name="app_description">Ανοιχτού κώδικα, ελαφριά εφαρμογή Android, για την αναπαραγωγή πολυμέσων από το διαδίκτυο.</string>
<string name="contribution_title">Συνεισφέρετε</string>
<string name="contribution_encouragement">Αν έχετε ιδέες για μετάφραση, αλλαγή σχεδιασμού, εκκαθάριση ή ριζικές αλλαγές κώδικα της εφαρμογήςη βοήθεια σας είναι πάντα ευπρόσδεκτη. Όσο περισσότερη έχουμε, τόσο καλύτεροι γινόμαστε!</string>
<string name="contribution_encouragement">Αν έχετε ιδέες για μετάφραση, αλλαγή σχεδιασμού, εκκαθάριση ή ριζικές αλλαγές κώδικα της εφαρμογής, η βοήθεια σας είναι πάντα ευπρόσδεκτη. Όσο περισσότερη έχουμε, τόσο καλύτεροι γινόμαστε!</string>
<string name="view_on_github">Δείτε το στο GitHub</string>
<string name="donation_title">Κάντε μια δωρεά</string>
<string name="donation_encouragement">Το NewPipe αναπτύσσεται από εθελοντές που δαπανούν τον ελεύθερο χρόνο τους για να σας προσφέρουν τη βέλτιστη δυνατή εμπειρία χρήστη. Δώστε πίσω για να βοηθήσετε τους προγραμματιστές του NewPipe να το κάνουν ακόμα καλύτερο, όσο απολαμβάνουν ένα φλιτζάνι καφέ.</string>
<string name="give_back">Προσφέρτε</string>
<string name="donation_encouragement">Το NewPipe αναπτύσσεται από εθελοντές που δαπανούν τον ελεύθερο χρόνο τους για να σας προσφέρουν τη βέλτιστη δυνατή εμπειρία χρήστη. Ανταποδώστε το, για να βοηθήσετε τους προγραμματιστές του NewPipe να το κάνουν ακόμα καλύτερο, όσο απολαμβάνουν ένα φλιτζάνι καφέ.</string>
<string name="give_back">Προσφέρετε</string>
<string name="website_title">Ιστότοπος</string>
<string name="website_encouragement">Επισκευτείτε τον ιστότοπο του NewPipe για περισσότερες πληροφορίες και νέα.</string>
<string name="privacy_policy_title">Η πολιτική ιδιωτικού απόρρητου του NewPipe</string>
<string name="privacy_policy_encouragement">Το NewPipe παίρνει πολύ σοβαρά το ιδιωτικό σας απόρρητο. Έτσι, η εφαρμογή αυτή δεν συλλέγει δεδομένα από εσάς χωρίς τη συγκατάθεσή σας.
<string name="privacy_policy_title">Πολιτική ιδιωτικού απόρρητου του NewPipe</string>
<string name="privacy_policy_encouragement">Το NewPipe παίρνει πολύ σοβαρά την ιδιωτικότητα σας. Έτσι, η εφαρμογή αυτή δεν συλλέγει δεδομένα από εσάς χωρίς τη συγκατάθεσή σας.
\nΗ πολιτική ιδιωτικού απόρρητου του NewPipe εξηγεί λεπτομερώς ποια δεδομένα αποστέλλονται και αποθηκεύονται όταν επιλέγετε να στείλετε μια αναφορά σφαλμάτων.</string>
<string name="read_privacy_policy">Διαβάστε την πολιτική ιδιωτικού απόρρητου</string>
<string name="read_privacy_policy">Ανάγνωση της πολιτικής ιδιωτικού απόρρητου</string>
<string name="app_license_title">Η άδεια του NewPipe</string>
<string name="app_license">Το NewPipe είναι copylelft ελεύθερο λογισμικό: Μπορείτε να το χρησιμοποιήσετε, να το μελετήσετε, να το μοιραστείτε και να το βελτιώσετε κατά βούληση. Ειδικότερα, μπορείτε να το αναδιανείμετε ή/και να το τροποποιήσετε υπό την άδεια GNU General Public Licence όπως αυτή εκδόθηκε από το Free Software Foundation, είτε υπό την έκδοση 3 της Άδειας είτε (προεραιτικά) υπό οποιαδήποτε μεταγενέστερη άδεια.</string>
<string name="read_full_license">Διαβάστε την άδεια</string>
<string name="read_full_license">Ανάγνωση της άδειας</string>
<string name="title_history_search">Αναζητήθηκαν</string>
<string name="title_history_view">Προβλήθηκαν</string>
<string name="history_disabled">Το ιστορικό έχει απενεργοποιηθεί</string>
@ -291,7 +293,7 @@
<string name="main_page_content">Περιεχόμενο της κεντρικής σελίδας</string>
<string name="blank_page_summary">Κενή σελίδα</string>
<string name="kiosk_page_summary">Σελίδα περιπτέρου</string>
<string name="subscription_page_summary">Σελίδα εγγραφών</string>
<string name="subscription_page_summary">Σελίδα συνδρομών</string>
<string name="channel_page_summary">Σελίδα καναλιών</string>
<string name="select_a_channel">Επιλέξτε ένα κανάλι</string>
<string name="no_channel_subscribed_yet">Δεν έχει γίνει εγγραφή σε κάποιο κανάλι ακόμα</string>
@ -306,31 +308,31 @@
<string name="play_queue_remove">Αφαίρεση</string>
<string name="play_queue_stream_detail">Λεπτομέρειες</string>
<string name="play_queue_audio_settings">Ρυθμίσεις ήχου</string>
<string name="hold_to_append">Πιέστε για να προστεθεί στην ουρά</string>
<string name="start_here_on_main">Εκκίνηση Αναπαραγωγής εδώ</string>
<string name="hold_to_append">Πιέστε παρατεταμένα για να προστεθεί στην ουρά</string>
<string name="start_here_on_main">Εκκίνηση αναπαραγωγής εδώ</string>
<string name="start_here_on_background">Εκκίνηση αναπαραγωγής στο παρασκήνιο</string>
<string name="start_here_on_popup">Εκκίνηση αναπαραγωγής σε ένα αναδυόμενο παράθυρο</string>
<string name="drawer_open">Άνοιγμα Συρταριού</string>
<string name="drawer_close">Κλείσιμο Συρταριού</string>
<string name="drawer_header_action_paceholder_text">Κάτι θα παιχτεί εδω σύντομα ;D</string>
<string name="drawer_open">Άνοιγμα συρταριού</string>
<string name="drawer_close">Κλείσιμο συρταριού</string>
<string name="drawer_header_action_paceholder_text">Κάτι θα εμφανιστεί εδώ σύντομα ;D</string>
<string name="top_50">Τοπ 50</string>
<string name="new_and_hot">Καινούρια και δημοφιλή</string>
<string name="preferred_open_action_settings_title">Προτιμώμενη ενέργεια ανοίγματος</string>
<string name="preferred_open_action_settings_title">Προτιμώμενη ενέργεια κοινοποίησης</string>
<string name="preferred_open_action_settings_summary">Προεπιλεγμένη ενέργεια για το άνοιγμα περιεχομένου — %s</string>
<string name="video_player">Συσκευή αναπαραγωγής βίντεο</string>
<string name="background_player">Αναπαραγωγή Παρασκηνίου</string>
<string name="popup_player">Αναπαραγωγή σε Αναδυόμενο Παράθυρο</string>
<string name="background_player">Αναπαραγωγή παρασκηνίου</string>
<string name="popup_player">Αναπαραγωγή σε αναδυόμενο παράθυρο</string>
<string name="always_ask_open_action">Πάντα ερώτηση</string>
<string name="preferred_player_fetcher_notification_title">Γίνεται λήψη πληροφοριών…</string>
<string name="preferred_player_fetcher_notification_message">Γίνεται φόρτωση του ζητούμενου περιεχομένου</string>
<string name="create_playlist">Νέα Λίστα Αναπαραγωγής</string>
<string name="create_playlist">Νέα λίστα αναπαραγωγής</string>
<string name="delete_playlist">Διαγραφή</string>
<string name="rename_playlist">Μετονομασία</string>
<string name="name">Όνομα</string>
<string name="append_playlist">Προσθήκη στη Λίστα</string>
<string name="append_playlist">Προσθήκη σε λίστα αναπαραγωγής</string>
<string name="set_as_playlist_thumbnail">Ορισμός ως μικρογραφία λίστας αναπαραγωγής</string>
<string name="bookmark_playlist">Προσθήκη Σελιδοδείκτη στη Λίστα</string>
<string name="unbookmark_playlist">Διαγραφή Σελιδοδείκτη</string>
<string name="bookmark_playlist">Προσθήκη σελιδοδείκτη στη λίστα</string>
<string name="unbookmark_playlist">Διαγραφή σελιδοδείκτη</string>
<string name="delete_playlist_prompt">Διαγραφή αυτής της λίστας αναπαραγωγής;</string>
<string name="playlist_creation_success">Η λίστα αναπαραγωγής δημιουργήθηκε</string>
<string name="playlist_add_stream_success">Προστέθηκε στη λίστα αναπαραγωγής</string>
@ -354,10 +356,10 @@
<string name="import_file_title">Εισαγωγή αρχείου</string>
<string name="previous_export">Προηγούμενη εξαγωγή</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 κατεβάζοντας το εξής αρχείο:
\n
\n1. Πλοηγηθήτε στο: %1$s
\n1. Πλοηγηθείτε στο: %1$s
\n2. Εισέλθετε στο λογαριασμό σας, όταν σας ζητηθεί
\n3. Η λήψη του αρχείου των εγγραφών σας θα ξεκινήσει</string>
<string name="import_soundcloud_instructions">Για να εισάγετε τον λογαριασμό SoundCloud σας, πληκτρολογήστε τον σύνδεσμο ή το ID σας:
@ -365,7 +367,7 @@
\n1. Ενεργοποιήστε τη λειτουργία \"Desktop mode\" στον φυλλομετρητή σας (καθώς η ιστοσελίδα δεν είναι διαθέσιμη για κινητά)
\n2. Πλοηθηθείτε στο %1$s
\n3. Εισέλθετε στο λογαριασμό σας, όταν σας ζητηθεί
\n4. Αντιγράψτε τον σύνδεσμο του λογαριαμού στον οποίο ανακατευθυνθήκατε.</string>
\n4. Αντιγράψτε τον σύνδεσμο του λογαριασμού στον οποίο ανακατευθυνθήκατε.</string>
<string name="import_network_expensive_warning">Αυτή η διαδικασία μπορεί να χρησιμοποιήσει μεγάλο όγκο δεδομένων.
\n
\nΕπιθυμείτε να συνεχίσετε;</string>
@ -373,16 +375,16 @@
<string name="playback_tempo">Τέμπο</string>
<string name="playback_pitch">Τόνος</string>
<string name="minimize_on_exit_summary">Ενέργεια κατά τη μετάβαση σε άλλη εφαρμογή — %s</string>
<string name="feed_page_summary">Σελίδα Ροής</string>
<string name="feed_page_summary">Σελίδα ροής</string>
<string name="trending">Δημοφιλή</string>
<string name="enable_disposed_exceptions_title">Αναφορά σφαλμάτων εκτός κύκλου ζωής</string>
<string name="import_soundcloud_instructions_hint">Το όνομα χρήστη σας, soundcloud.com/όνομαχρήστη</string>
<string name="unhook_checkbox">Αποσύνδεση (μπορεί να προκαλέσει παραμόρφωση)</string>
<string name="skip_silence_checkbox">Επιτάχυνση αναπαραγωγής κατά τη διάρκεια σιωπής</string>
<string name="unhook_checkbox">Απαγκίστρωση (μπορεί να προκαλέσει παραμόρφωση)</string>
<string name="skip_silence_checkbox">Γρήγορη αναπαραγωγή κατά τη διάρκεια της σίγασης</string>
<string name="playback_step">Βήμα</string>
<string name="playback_reset">Επαναφορά</string>
<string name="start_accept_privacy_policy">Προς συμμόρφωση με τον Ευρωπαϊκό Γενικό Κανονισμό για την Προστασία Δεδομένων (GDPR), σας επιστούμε την προσοχή στην πολιτική προστασίας προσωπικών δεδομένων του NewPipe. Παραλούμε, διαβάστε την προσεκτικά.
\nΘα πρέπει να την αποδεχτέιτε προκειμένου να μας αποστείλετε την αναφορά σφάλματος.</string>
<string name="start_accept_privacy_policy">Προς συμμόρφωση με τον Ευρωπαϊκό Γενικό Κανονισμό για την Προστασία Δεδομένων (GDPR), σας εφιστούμε την προσοχή στην πολιτική προστασίας προσωπικών δεδομένων του NewPipe. Παραλούμε, διαβάστε την προσεκτικά.
\nΘα πρέπει να την αποδεχτείτε προκειμένου να μας αποστείλετε την αναφορά σφάλματος.</string>
<string name="accept">Αποδοχή</string>
<string name="decline">Απόρριψη</string>
<string name="limit_data_usage_none_description">Χωρίς όριο</string>
@ -394,9 +396,9 @@
<string name="unsubscribe">Απεγγραφή</string>
<string name="tab_new">Νέα Καρτέλα</string>
<string name="tab_choose">Επιλογή Καρτέλας</string>
<string name="volume_gesture_control_title">Ρυθμίσεις χειρονομιών ήχου</string>
<string name="volume_gesture_control_summary">Χρησιμοποιήστε χειρονομίες για τον έλεγχο έντασης του ήχου</string>
<string name="brightness_gesture_control_title">Ρυθμίσεις χειρονομιών φωτεινότητας</string>
<string name="volume_gesture_control_title">Έλεγχος ήχου με χειρονομιές</string>
<string name="volume_gesture_control_summary">Χρησιμοποιήστε χειρονομίες για τον έλεγχο της έντασης του ήχου</string>
<string name="brightness_gesture_control_title">Έλεγχος φωτεινότητας με χειρονομίες</string>
<string name="brightness_gesture_control_summary">Χρησιμοποιήστε χειρονομίες για τον έλεγχο φωτεινότητας</string>
<string name="settings_category_updates_title">Ενημερώσεις</string>
<string name="events">Συμβάντα</string>
@ -405,51 +407,51 @@
<string name="app_update_notification_channel_description">Ειδοποίηση για νεότερη έκδοση του NewPipe</string>
<string name="download_to_sdcard_error_title">Εξωτερική μνήμη αποθήκευσης μη διαθέσιμη</string>
<string name="download_to_sdcard_error_message">Η αποθήκευση στην SD κάρτα δεν είναι δυνατή. Επαναφορά στην αρχική τοποθεσία λήψης;</string>
<string name="saved_tabs_invalid_json">Δεν ήταν δυνατή η ανάγνωση αποθηκευμένων καρτελών, επομένως χρήση προεπιλεγμένων καρτελών</string>
<string name="saved_tabs_invalid_json">Δεν ήταν δυνατή η ανάγνωση των αποθηκευμένων καρτελών. Θα γίνει χρήση των προεπιλεγμένων</string>
<string name="restore_defaults">Επαναφορά προεπιλεγμένων ρυθμίσεων</string>
<string name="restore_defaults_confirmation">Θέλετε να επαναφέρετε τις προεπιλογές;</string>
<string name="subscribers_count_not_available">Ο αριθμός συνδρομητών δεν είναι διαθέσιμος</string>
<string name="restore_defaults_confirmation">Θέλετε να επαναφέρετε τις προεπιλεγμένες ρυθμίσεις;</string>
<string name="subscribers_count_not_available">Το πλήθος των συνδρομητών δεν είναι διαθέσιμο</string>
<string name="main_page_content_summary">Ποιές καρτέλες θα εμφανίζονται στην αρχική σελίδα</string>
<string name="selection">Επιλογή</string>
<string name="conferences">Συνέδρια</string>
<string name="updates_setting_title">Ενημερώσεις</string>
<string name="updates_setting_description">Εμφάνιση ειδοποίησης όταν μια υπάρχει μια νεότερη έκδοση</string>
<string name="list_view_mode">Λειτουργία προβολής ως λίστα</string>
<string name="list_view_mode">Προβολή λίστας</string>
<string name="list">Λίστα</string>
<string name="grid">Πλέγμα</string>
<string name="auto">Αυτόματα</string>
<string name="switch_view">Αλλαγή τρόπου προβολής</string>
<string name="app_update_notification_content_title">Νέα έκδοση του NewPipe είναι διαθέσιμη!</string>
<string name="app_update_notification_content_title">Μια νέα έκδοση του NewPipe είναι διαθέσιμη!</string>
<string name="app_update_notification_content_text">Πατήστε για λήψη</string>
<string name="missions_header_finished">Ολοκληρώθηκε</string>
<string name="missions_header_pending">Εκκρεμεί</string>
<string name="paused">Παύση</string>
<string name="queued">στην ουρά</string>
<string name="post_processing">Μετεπεξεργασία</string>
<string name="paused">σε παύση</string>
<string name="queued">σε ουρά</string>
<string name="post_processing">σε μετεπεξεργασία</string>
<string name="enqueue">Ουρά</string>
<string name="permission_denied">Η δράση απορρίφθηκε από το σύστημα</string>
<string name="permission_denied">Η ενέργεια απορρίφθηκε από το σύστημα</string>
<string name="download_failed">Η λήψη απέτυχε</string>
<string name="download_finished">Η λήψη ολοκληρώθηκε</string>
<string name="download_finished_more">%s λήψεις ολοκρηρώθηκαν</string>
<string name="download_finished_more">%s λήψεις ολοκληρώθηκαν</string>
<string name="generate_unique_name">Δημιουργία μοναδικού ονόματος</string>
<string name="overwrite">Αντικατάσταση</string>
<string name="overwrite_unrelated_warning">Ένα αρχείο με αυτό το όνομα υπάρχει ήδη</string>
<string name="overwrite_finished_warning">Ένα αρχείο που έχει ληφθεί με αυτό το όνομα υπάρχει ήδη</string>
<string name="overwrite_finished_warning">Ένα ληφθέν αρχείο με αυτό το όνομα υπάρχει ήδη</string>
<string name="download_already_running">Υπάρχει μια λήψη σε εξέλιξη με αυτό το όνομα</string>
<string name="show_error">Εμφάνιση σφάλματος</string>
<string name="label_code">Κωδικός</string>
<string name="label_code">Κώδικας</string>
<string name="error_path_creation">Δεν είναι δυνατή η δημιουργία του φακέλου προορισμού</string>
<string name="error_file_creation">Δεν είναι δυνατή η δημιουργία του αρχείου</string>
<string name="error_permission_denied">Η αδειοδότηση απορρίφθηκε απο το σύστημα</string>
<string name="error_permission_denied">Η άδεια απορρίφθηκε από το σύστημα</string>
<string name="error_ssl_exception">Δεν ήταν δυνατή η δημιουργία ασφαλούς σύνδεσης</string>
<string name="error_unknown_host">Αδυναμία εύρεσης του εξυπηρετητή</string>
<string name="error_connect_host">Αδυναμία σύνδεσης με τον εξυπηρετητή</string>
<string name="error_http_no_content">Ο εξυπηρετητής δεν μπορεί να στείλει τα δεδομένα</string>
<string name="error_http_unsupported_range">Ο εξυπηρετητής δέν υποστηρίζει πολυνηματικές λήψεις, ξαναπροσπαθήστε με @string/msg_threads = 1</string>
<string name="error_http_not_found">Δεν βρέθηκε</string>
<string name="error_postprocessing_failed">Μετεπεξεργασία απέτυχε</string>
<string name="error_http_no_content">Ο εξυπηρετητής δεν στέλνει δεδομένα</string>
<string name="error_http_unsupported_range">Ο εξυπηρετητής δεν υποστηρίζει πολυνηματικές λήψεις, ξαναπροσπαθήστε με @string/msg_threads = 1</string>
<string name="error_http_not_found">Δε βρέθηκε</string>
<string name="error_postprocessing_failed">Η μετεπεξεργασία απέτυχε</string>
<string name="stop">Διακοπή</string>
<string name="max_retry_msg">Μέγιστες επαναπροσπάθειες</string>
<string name="max_retry_msg">Μέγιστος αριθμός προσπαθειών</string>
<string name="max_retry_desc">Μέγιστος αριθμός προσπαθειών προτού γίνει ακύρωση της λήψης</string>
<string name="pause_downloads_on_mobile">Διακοπή σε δίκτυα με ογκοχρέωση</string>
<string name="pause_downloads_on_mobile_desc">Χρήσιμο κατά τη μετάβαση σε δεδομένα κινητής τηλεφωνίας, αν και ορισμένες λήψεις δεν μπορούν να ανασταλούν</string>
@ -459,7 +461,7 @@
<string name="no_comments">Χωρίς σχόλια</string>
<string name="error_unable_to_load_comments">Δεν ήταν δυνατή η φόρτωση σχολίων</string>
<string name="close">Κλείσιμο</string>
<string name="enable_playback_resume_title">Συνέχιση αναπαραγωγής</string>
<string name="enable_playback_resume_title">Ανάκτηση αναπαραγωγής</string>
<string name="enable_playback_resume_summary">Επαναφορά της τελευταίας θέσης αναπαραγωγής</string>
<string name="enable_playback_state_lists_title">Θέσεις στις λίστες</string>
<string name="enable_playback_state_lists_summary">Εμφάνιση ενδείξεων θέσης αναπαραγωγής στις λίστες</string>
@ -467,17 +469,17 @@
<string name="watch_history_states_deleted">Οι θέσεις αναπαραγωγής διαγράφηκαν.</string>
<string name="missing_file">Το αρχείο μετακινήθηκε ή διαγράφηκε</string>
<string name="overwrite_failed">δεν είναι δυνατή η αντικατάσταση του αρχείου</string>
<string name="download_already_pending">Υπάρχει μια εκκρεμή λήψη με αυτό το όνομα</string>
<string name="error_postprocessing_stopped">Το NewPipe έκλεισε, ενώ εργάζονται στο αρχείο</string>
<string name="error_insufficient_storage">Δεν είναι αρκετός ο χώρος στη συσκευή</string>
<string name="download_already_pending">Υπάρχει μια εκκρεμής λήψη με αυτό το όνομα</string>
<string name="error_postprocessing_stopped">Το NewPipe τερματίστηκε ενώ επεξεργάζονταν το αρχείο</string>
<string name="error_insufficient_storage">Δεν υπάρχει αρκετός χώρος στη συσκευή</string>
<string name="error_progress_lost">Η πρόοδος χάθηκε, επειδή το αρχείο διαγράφηκε</string>
<string name="error_timeout">Λήξη χρονικού ορίου σύνδεσης</string>
<string name="confirm_prompt">Θέλετε να διαγράψετε το ιστορικό λήψεων σας ή να διαγράψετε όλα τα αρχεία που έχετε λάβει;</string>
<string name="enable_queue_limit">Περιορισμός ουράς λήψης</string>
<string name="enable_queue_limit_desc">Μια λήψη θα εκτελεστεί ταυτόχρονα</string>
<string name="enable_queue_limit_desc">Μόνο μια λήψη θα εκτελείται κάθε φορά</string>
<string name="start_downloads">Έναρξη λήψεων</string>
<string name="pause_downloads">Παύση λήψεων</string>
<string name="downloads_storage_ask_title">Ερώτηση που να γίνει η λήψη</string>
<string name="downloads_storage_ask_title">Ερώτηση πού να γίνει η λήψη</string>
<string name="downloads_storage_ask_summary">Θα ερωτηθείτε πού να αποθηκεύσετε κάθε λήψη</string>
<string name="downloads_storage_ask_summary_kitkat">Θα σας ζητηθεί πού να αποθηκεύσετε κάθε λήψη.
\nΕπιλέξτε SAF αν θέλετε να κατεβάσετε σε μια εξωτερική κάρτα SD</string>
@ -488,7 +490,7 @@
<string name="clear_playback_states_summary">Διαγράφει όλες τις θέσεις αναπαραγωγής</string>
<string name="delete_playback_states_alert">Να διαγραφούν όλες οι θέσεις αναπαραγωγής;</string>
<string name="download_choose_new_path">Αλλαγή των φακέλων λήψης για να τεθούν σε ισχύ</string>
<string name="drawer_header_description">Εναλλαγή υπηρεσιών, επιλεγμένη αυτήν τη στιγμή:</string>
<string name="drawer_header_description">Εναλλαγή υπηρεσιών, επιλεγμένη αυτή τη στιγμή:</string>
<string name="no_one_watching">Κανείς δεν παρακολουθεί</string>
<plurals name="watching">
<item quantity="one">%s παρακολουθεί</item>
@ -499,15 +501,15 @@
<item quantity="one">%s ακροατής</item>
<item quantity="other">%s ακροατές</item>
</plurals>
<string name="localization_changes_requires_app_restart">Η γλώσσα θα αλλάξει μόλις θα επανεκκινηθεί η εφαρμογή.</string>
<string name="localization_changes_requires_app_restart">Η γλώσσα θα αλλάξει αφού επανεκκινηθεί η εφαρμογή.</string>
<string name="default_kiosk_page_summary">Προεπιλεγμένο περίπτερο</string>
<string name="peertube_instance_add_https_only">Υποστηρίζονται μόνο HTTPS URLς</string>
<string name="local">Τοπικό</string>
<string name="recently_added">Προστέθηκε πρόσφατα</string>
<string name="peertube_instance_add_https_only">Μόνο HTTPS σύνδεσμοι υποστηρίζονται</string>
<string name="local">Τοπικά</string>
<string name="recently_added">Προστέθηκαν πρόσφατα</string>
<string name="playlist_no_uploader">Δημιουργήθηκε αυτόματα (δεν βρέθηκε χρήστης μεταφόρτωσης)</string>
<string name="recovering">Ανάκτηση</string>
<string name="recovering">σε ανάκτηση</string>
<string name="error_download_resource_gone">Δεν είναι δυνατή η ανάκτηση αυτής της λήψης</string>
<string name="seek_duration_title">Διάρκεια fastforward και rewind</string>
<string name="seek_duration_title">Διάρκεια αναζήτησης fast-forward και rewind</string>
<string name="systems_language">Προεπιλογή συστήματος</string>
<string name="videos_string">Βίντεο</string>
<string name="search_showing_result_for">Εμφάνιση αποτελεσμάτων για: %s</string>
@ -520,15 +522,15 @@
<string name="channel_created_by">Δημιουργήθηκε από %s</string>
<string name="playlist_page_summary">Σελίδα λίστας αναπαραγωγής</string>
<string name="video_detail_by">Από %s</string>
<string name="feed_use_dedicated_fetch_method_summary">Διαθέσιμο σε ορισμένες υπηρεσίες, είναι συνήθως πολύ πιο γρήγορο, αλλά μπορεί να επιστρέψει έναν περιορισμένο αριθμό αντικειμένων και συχνά ελλιπείς πληροφορίες (π.χ. χωρίς διάρκεια, τύπο αντικειμένου, χωρίς ζωντανή κατάσταση).</string>
<string name="feed_use_dedicated_fetch_method_summary">Διαθέσιμο σε ορισμένες υπηρεσίες, είναι συνήθως πολύ πιο γρήγορο, αλλά μπορεί να επιστρέψει έναν περιορισμένο αριθμό αντικειμένων και συχνά ελλειπείς πληροφορίες (π.χ. χωρίς διάρκεια, τύπο αντικειμένου).</string>
<string name="feed_use_dedicated_fetch_method_title">Λήψη από ειδική ροή όταν είναι διαθέσιμη</string>
<string name="feed_update_threshold_option_always_update">Να γίνεται πάντα ενημέρωση</string>
<string name="feed_update_threshold_summary">Ώρα μετά την τελευταία ενημέρωση πριν από μια συνδρομή θεωρείται ξεπερασμένη — %s</string>
<string name="feed_update_threshold_summary">Χρόνος μετά την τελευταία ενημέρωση πριν μια συνδρομή θεωρηθεί ξεπερασμένη — %s</string>
<string name="feed_update_threshold_title">Όριο ενημέρωσης ροής</string>
<string name="settings_category_feed_title">Ροή</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Εμφάνιση μόνο μη ομαδοποιημένων συνδρομών</string>
<string name="feed_create_new_group_button_title">Νέα</string>
<string name="feed_group_dialog_delete_message">Θέλετε να διαγράψετε αυτήν την ομάδα;</string>
<string name="feed_group_dialog_delete_message">Θέλετε να διαγράψετε αυτή την ομάδα;</string>
<string name="feed_group_dialog_empty_name">Κενό όνομα ομάδας</string>
<string name="feed_group_dialog_empty_selection">Δεν έχει επιλεγεί συνδρομή</string>
<string name="feed_group_dialog_select_subscriptions">Επιλέξτε συνδρομές</string>
@ -549,13 +551,13 @@
<string name="songs">Τραγούδια</string>
<string name="restricted_video">Αυτό το βίντεο έχει περιορισμό ηλικίας.
\n
\nΕνεργοποιήστε το \"Περιεχόμενο περιορισμένης ηλικίας\" στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
<string name="youtube_restricted_mode_enabled_title">Λειτουργία περιορισμένης πρόσβασης στο YouTube</string>
\nΕνεργοποιήστε το \"%1$s\" στις ρυθμίσεις εάν θέλετε να το δείτε.</string>
<string name="youtube_restricted_mode_enabled_title">Λειτουργία περιορισμένης πρόσβασης του YouTube</string>
<string name="settings_category_notification_title">Ειδοποίηση</string>
<string name="unsupported_url_dialog_message">Δεν ήταν δυνατή η αναγνώριση της διεύθυνσης URL. Άνοιγμα με άλλη εφαρμογή;</string>
<string name="auto_queue_toggle">Αυτόματη ουρά</string>
<string name="clear_queue_confirmation_description">Η ουρά ενεργού παίκτη θα αντικατασταθεί</string>
<string name="clear_queue_confirmation_summary">Η εναλλαγή από έναν παίκτη σε άλλο μπορεί να αντικαταστήσει την ουρά σας</string>
<string name="clear_queue_confirmation_description">Η ουρά του ενεργού αναπαραγωγού θα αντικατασταθεί</string>
<string name="clear_queue_confirmation_summary">Η εναλλαγή από έναν αναπαραγωγό σε άλλον, μπορεί να αντικαταστήσει την ουρά σας</string>
<string name="clear_queue_confirmation_title">Ζητήστε επιβεβαίωση πριν από την εκκαθάριση μιας ουράς</string>
<string name="notification_action_nothing">Τίποτα</string>
<string name="notification_action_shuffle">Ανάμιξη</string>
@ -567,7 +569,76 @@
<string name="notification_action_2_title">Κουμπί τρίτης ενέργειας</string>
<string name="notification_action_1_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">Κλιμάκωση της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 (μπορεί να προκαλέσει παραμορφώσεις)</string>
<string name="notification_scale_to_square_image_title">Κλιμάκωση μικρογραφίας σε αναλογία διαστάσεων 1:1</string>
<string name="notification_action_buffering">Φόρτωση</string>
<string name="feed_use_dedicated_fetch_method_help_text">Πιστεύετε ότι η ροή φορτώνει πολύ αργά; Δοκιμάστε να ενεργοποιήσετε τη γρήγορη φόρτωση (από τις ρυθμίσεις ή το παρακάτω κουμπί).
\n
\nΤο NewPipe προσφέρει δύο στρατηγικές φόρτωσης:
\n- Λήψη ολόκληρου του καναλιού της συνδρομής, η οποία είναι αργή αλλά πλήρης.
\n- Χρήση ενός αποκλειστικού τελικού σημείου υπηρεσίας, η οποία είναι γρήγορη αλλά συνήθως όχι πλήρης.
\n
\nΤο YouTube είναι ένα παράδειγμα χρήσης της δεύτερης μεθόδου.
\n
\nΣυνεπώς επιλέγετε ανάλογα: ταχύτητα ή ακριβείς πληροφορίες.</string>
<string name="feed_oldest_subscription_update">Η ροή ανανεώθηκε: %s</string>
<string name="feed_notification_loading">Φόρτωση ροής…</string>
<string name="feed_processing_message">Επεξεργασία ροής…</string>
<plurals name="feed_group_dialog_selection_count">
<item quantity="one">%d επιλέχθηκε</item>
<item quantity="other">%d επιλέχθηκαν</item>
</plurals>
<string name="feed_subscription_not_loaded_count">Δεν φορτώθηκε: %d</string>
<string name="feed_groups_header_title">Οι ομάδες του καναλιού</string>
<plurals name="days">
<item quantity="one">%d ημέρα</item>
<item quantity="other">%d ημέρες</item>
</plurals>
<plurals name="hours">
<item quantity="one">%d ώρα</item>
<item quantity="other">%d ώρες</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%d λεπτό</item>
<item quantity="other">%d λεπτά</item>
</plurals>
<plurals name="seconds">
<item quantity="one">%d δευτερόλεπτο</item>
<item quantity="other">%d δευτερόλεπτα</item>
</plurals>
<string name="new_seek_duration_toast">Λόγω περιορισμών του ExoPlayer, η διάρκεια αναζήτησης ορίστηκε στα %d δευτερόλεπτα</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Ναι. Και τα μερικώς θεαθέντα βίντεο</string>
<string name="remove_watched_popup_warning">Τα βίντεο που εθεάθησαν πριν και αφού προστέθηκαν στη λίστα αναπαραγωγής θα απομακρυνθούν
\nΕίστε σίγουρος; Δεν μπορεί να αναιρεθεί!</string>
<string name="remove_watched_popup_title">Απομάκρυνση θεαθέντων βίντεο;</string>
<string name="remove_watched">Απομάκρυνση όσων θεάθησαν</string>
<string name="app_language_title">Γλώσσα εφαρμογής</string>
<string name="choose_instance_prompt">Επιλογή μιας instance</string>
<string name="deleted_downloads">Διαγράφηκαν %1$d λήψεις</string>
<string name="delete_downloaded_files">Διαγραφή ληφθέντων αρχείων</string>
<string name="clear_download_history">Εκκαθάριση ιστορικού λήψεων</string>
<string name="never">Ποτέ</string>
<string name="wifi_only">Μόνο με Wi-Fi</string>
<string name="autoplay_summary">Αυτόματη έναρξη αναπαραγωγής — %s</string>
<string name="show_original_time_ago_summary">Τα αυθεντικά κείμενα των υπηρεσιών θα εμφανίζονται στα αντικείμενα ροής</string>
<string name="show_memory_leaks">Εμφάνιση διαρροών μνήμης</string>
<string name="unmute">Αποσίγαση</string>
<string name="enqueued">Προστέθηκε στην ουρά</string>
<string name="enqueue_stream">Προσθήκη στην ουρά</string>
<string name="most_liked">Πιο αγαπημένα</string>
<string name="error_report_open_github_notice">Παρακαλούμε ελέγξτε αν το πρόβλημα σας έχει ήδη αναφερθεί. Οι διπλές αναφορές μας στερούν το χρόνο που θα μπορούσαμε να διαθέσουμε για την επίλυση του προβλήματος.</string>
<string name="clear_cookie_summary">Εκκαθάριση των cookies που αποθηκεύει η εφαρμογή όταν λύνετε ένα reCAPTCHA</string>
<string name="recaptcha_cookies_cleared">Τα reCAPTCHA cookies εκκαθαρίστηκαν</string>
<string name="clear_cookie_title">Εκκαθάριση reCAPTCHA cookies</string>
<string name="youtube_restricted_mode_enabled_summary">Το YouTube διαθέτει \"Περιορισμένη Λειτουργία\" η οποία κρύβει πιθανώς ακατάλληλο περιεχόμενο.</string>
<string name="show_age_restricted_content_summary">Εμφάνιση πιθανώς ακατάλληλου περιεχομένου (18+).</string>
<string name="peertube_instance_add_exists">Το instance υπάρχει ήδη</string>
<string name="peertube_instance_add_fail">Αδυναμία πιστοποίησης του instance</string>
<string name="peertube_instance_add_help">Προσθέστε την URL του instance</string>
<string name="peertube_instance_add_title">Προσθήκη instance</string>
<string name="peertube_instance_url_help">Βρείτε τα instances που σας αρέσουν στο %s</string>
<string name="peertube_instance_url_summary">Επιλογή των αγαπημένων σας PeerTube instances</string>
<string name="show_original_time_ago_title">Εμφάνιση αυθεντικού παρελθόντος χρόνου στα αντικείμενα</string>
<string name="peertube_instance_url_title">PeerTube instances</string>
<string name="notification_colorize_title">Χρωματισμός ειδοποιήσεων</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -9,7 +9,7 @@
<string name="download">Elŝuti</string>
<string name="search">Serĉi</string>
<string name="settings">Agordoj</string>
<string name="did_you_mean">Ĉu vi signifis: %1$s\?</string>
<string name="did_you_mean">Ĉu vi signifis \"%1$s\"\?</string>
<string name="share_dialog_title">Konigi kun</string>
<string name="choose_browser">Elekti retumilon</string>
<string name="screen_rotation">turno</string>
@ -64,7 +64,8 @@
<string name="report_error">Signali eraron</string>
<string name="video">Filmeto</string>
<string name="retry">Reprovi</string>
<string name="main_bg_subtitle">Premi \"Serĉi\" por komenci</string>
<string name="main_bg_subtitle">Premi \"Serĉi\" por komenci
\n</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="use_external_video_player_summary">Forigas aŭdon ĉe kelkaj rezolucioj</string>
@ -90,13 +91,14 @@
<string name="show_higher_resolutions_summary">Nur kelkaj aparatoj povas ludi 2K / 4K filmetojn</string>
<string name="default_video_format_title">Defaŭlta fomato de filmeto</string>
<string name="black_theme_title">Nigra</string>
<string name="popup_remember_size_pos_title">Memoru ŝprucfenestran grandecon kaj pozicion</string>
<string name="popup_remember_size_pos_summary">Memoru lastan grandecon kaj pozicion de ŝprucfenestro</string>
<string name="use_inexact_seek_title">Uzi rapide, ne precizan serĉon</string>
<string name="use_inexact_seek_summary">Ne preciza serĉo permesas al la ludanto serĉi poziciojn pli rapide kun malalta precizeco. Serĉi por 5, 15 kaj 25 sekundoj ne funckios kun tio opcio.</string>
<string name="download_thumbnail_title">Ŝarĝi bildetojn</string>
<string name="could_not_setup_download_menu">Ne povis konstrui la dosierujon de elŝuto</string>
<string name="live_streams_not_supported">Nunaj filmetoj ne estas ankoraŭ subtenataj</string>
<string name="show_age_restricted_content_title">Enhavo limigita al aĝo</string>
<string name="video_is_age_restricted">Montri limigitan al aĝo filmeto. Postaj ŝanĝoj eblas ĉe la agordoj.</string>
<string name="light_parsing_error">Ne povis tute analizi la retejon</string>
<string name="could_not_get_stream">Ne povis akiri ajnan torenton</string>
<string name="duration_live">Nuna</string>
@ -149,7 +151,7 @@
<string name="kiosk">Kiosko</string>
<string name="trending">Tendencoj</string>
<string name="top_50">Supro 50</string>
<string name="new_and_hot">Nova &amp; 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_summary">Montri indikon premante la fona aŭ la ŝprucfenestra butono en filmeta \"Detaloj:\"</string>
<string name="background_player_append">Viciĝita en la fona ludilo</string>
@ -206,7 +208,10 @@
\n
\n1. Iru ĉe tiu retpaĝo: %1$s
\n2. Ensalutu kiam oni petas vin
\n3. Elŝuto devus komenci (ĝi estas la dosiero de eksporto)</string>
\n3. Click on \"All data included\", then on \"Deselect all\", then select only \"subscriptions\" and click \"OK\"
\n4. Click on \"Next step\" and then on \"Create export\"
\n5. Click on the \"Download\" button after it appears and
\n6. From the downloaded takeout zip extract the .json file (usually under \"YouTube and YouTube Music/subscriptions/subscriptions.json\") and import it here.</string>
<string name="import_soundcloud_instructions">Importu Soundcloud-n profilon tajpante ĉu la ligilon, ĉu vian ID :
\n
\n1. Ebligu komputilon modon en retumilon (la retejo malhaveblas por poŝtelefonoj)
@ -285,9 +290,9 @@
<string name="delete_playback_states_alert">Ĉu vi volas forviŝi ĉiujn ludajn poziciojn \?</string>
<string name="download_choose_new_path">Ŝanĝu la elŝutojn dosierujojn por efekti</string>
<string name="sorry_string">Pardonu, eraro okazis.</string>
<string name="error_snackbar_message">Pardonu, kelkaj eraroj okazis.</string>
<string name="error_snackbar_message">Pardonon, io mizokasis.</string>
<string name="what_happened_headline">Kio okazis:</string>
<string name="info_labels">Kio:\\nPeto:\\nEnhavlingvo:\\nServo:\\nGMT Horo:\\nPako:\\nVersio:\\nOperaciumo versio:</string>
<string name="info_labels">Kio:\\nPeto:\\nEnhavlingvo:\\nEnhavlando:\\nAplingvo:\\nServo:\\nGMT Horo:\\nPako:\\nVersio:\\nOperaciumo versio:</string>
<string name="audio">Aŭdio</string>
<string name="storage_permission_denied">Permesi la konservadon unue</string>
<string name="user_report">Uzantosignalo</string>
@ -435,7 +440,7 @@
<string name="decline">Rifuzi</string>
<string name="limit_data_usage_none_description">Neniu limo</string>
<string name="minimize_on_exit_title">Minimumigi dum la apo ŝanĝo</string>
<string name="minimize_on_exit_summary">Ago dum ŝanĝante al alia apo el la ĉefa filmetludilo%s</string>
<string name="minimize_on_exit_summary">Ago dum ŝanĝante al alia apo el la ĉefa filmetludilo%s</string>
<string name="minimize_on_exit_none_description">Neniu</string>
<string name="minimize_on_exit_background_description">Minimumigi por ludi fone</string>
<string name="skip_silence_checkbox">Plirapidigi dum silentoj</string>

View file

@ -10,7 +10,7 @@
<string name="download">Descargar</string>
<string name="search">Buscar</string>
<string name="settings">Ajustes</string>
<string name="did_you_mean">¿Quiso decir \"%1$s\"\?</string>
<string name="did_you_mean">¿Quiso decir «%1$s»\?</string>
<string name="share_dialog_title">Compartir con</string>
<string name="choose_browser">Elegir navegador</string>
<string name="screen_rotation">giro</string>
@ -18,13 +18,13 @@
<string name="download_path_summary">Los archivos de vídeo descargados se almacenan aquí</string>
<string name="download_path_dialog_title">Elija la carpeta de descarga para los archivos de vídeo</string>
<string name="download_choose_new_path">Cambie las carpetas de descarga para que surtan efecto</string>
<string name="default_resolution_title">Resolución predeterminada</string>
<string name="default_resolution_title">Resolución predefinida</string>
<string name="play_with_kodi_title">Reproducir con Kodi</string>
<string name="kore_not_found">¿Instalar la app Kore que falta\?</string>
<string name="kore_not_found">¿Instalar la aplicación Kore que falta\?</string>
<string name="show_play_with_kodi_title">Mostrar opción «Reproducir con Kodi»</string>
<string name="show_play_with_kodi_summary">Mostrar opción para reproducir vídeo a través del centro de medios Kodi</string>
<string name="play_audio">Audio</string>
<string name="default_audio_format_title">Formato de audio predeterminado</string>
<string name="default_audio_format_title">Formato de audio predefinido</string>
<string name="download_dialog_title">Descargar</string>
<string name="unsupported_url">No se admite el URL</string>
<string name="use_external_video_player_title">Usar reproductor de vídeo externo</string>
@ -50,7 +50,7 @@
<string name="youtube_signature_deobfuscation_error">No se pudo descifrar la URL del vídeo</string>
<string name="parsing_error">No se pudo analizar el sitio web</string>
<string name="show_next_and_similar_title">Mostrar vídeos \'Siguientes\' y \'Similares\'</string>
<string name="content_language_title">Idioma predeterminado del contenido</string>
<string name="content_language_title">Idioma predefinido del contenido</string>
<string name="list_thumbnail_view_description">Miniatura de previsualización del vídeo</string>
<string name="detail_thumbnail_view_description">Reproducir vídeo; duración:</string>
<string name="detail_likes_img_view_description">Me gusta</string>
@ -58,12 +58,11 @@
<string name="detail_uploader_thumbnail_view_description">Miniatura del avatar del usuario</string>
<string name="live_streams_not_supported">Las transmisiones en vivo no son soportadas aún</string>
<string name="content">Contenido</string>
<string name="show_age_restricted_content_title">Contenido restringido por edad</string>
<string name="video_is_age_restricted">Mostrar vídeo restringido por edad. Se pueden realizar cambios futuros desde los ajustes.</string>
<string name="show_age_restricted_content_title">Mostrar contenido con restricción de edad</string>
<string name="main_bg_subtitle">Toque «Buscar» para empezar
\n</string>
<string name="autoplay_by_calling_app_title">Reproducción automática</string>
<string name="autoplay_by_calling_app_summary">Reproducir un vídeo cuando NewPipe es llamado desde otra app</string>
<string name="autoplay_by_calling_app_summary">Reproducir un vídeo cuando NewPipe es llamado desde otra aplicación</string>
<string name="duration_live">En directo</string>
<string name="downloads">Descargas</string>
<string name="downloads_title">Descargas</string>
@ -72,7 +71,7 @@
<string name="could_not_setup_download_menu">No se pudo configurar el menú de descarga</string>
<string name="could_not_get_stream">No se pudo obtener ninguna transmisión</string>
<string name="sorry_string">Lo siento, esto no debería haber ocurrido.</string>
<string name="error_report_button_text">Informar de este error vía email</string>
<string name="error_report_button_text">Informar de este error vía correo electrónico</string>
<string name="error_snackbar_message">Lo siento, algo salió mal.</string>
<string name="error_snackbar_action">Informar</string>
<string name="what_device_headline">Información:</string>
@ -80,7 +79,7 @@
<string name="your_comment">Su comentario (en Inglés):</string>
<string name="error_details_headline">Detalles:</string>
<string name="report_error">Informar de un error</string>
<string name="user_report">Reporte de usuario</string>
<string name="user_report">Informe de usuario</string>
<string name="video">Vídeo</string>
<string name="audio">Audio</string>
<string name="retry">Reintentar</string>
@ -88,7 +87,7 @@
<string name="start">Iniciar</string>
<string name="pause">Pausar</string>
<string name="view">Reproducir</string>
<string name="delete">Eliminar</string>
<string name="delete">Borrar</string>
<string name="checksum">Suma de comprobación</string>
<string name="add">Misión nueva</string>
<string name="finish">Aceptar</string>
@ -102,9 +101,9 @@
<string name="msg_running_detail">Toque para ver detalles</string>
<string name="msg_wait">Espere, por favor…</string>
<string name="msg_copied">Copiado en el portapapeles</string>
<string name="no_available_dir">Defina una carpeta de descargas más tarde en la configuración</string>
<string name="no_available_dir">Defina una carpeta de descargas más tarde en los ajustes</string>
<string name="could_not_load_image">No se pudo cargar la imagen</string>
<string name="app_ui_crash">La interfaz de la app dejó de funcionar</string>
<string name="app_ui_crash">La interfaz de la aplicación dejó de funcionar</string>
<string name="info_labels">Lo sucedido:\\nPetición:\\nIdioma del Contenido:\\nPaís del contenido:\\nIdioma de la aplicación:\\nServicio:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO:</string>
<string name="black_theme_title">Negro</string>
<string name="all">Todo</string>
@ -121,23 +120,25 @@
<string name="recaptcha_request_toast">Reto reCAPTCHA requerido</string>
<string name="popup_mode_share_menu_title">Modo emergente</string>
<string name="popup_playing_toast">Reproduciendo en modo emergente</string>
<string name="default_video_format_title">Formato de vídeo predeterminado</string>
<string name="default_video_format_title">Formato de vídeo predefinido</string>
<string name="disabled">Desactivado</string>
<string name="show_higher_resolutions_title">Mostrar resoluciones más altas</string>
<string name="show_higher_resolutions_summary">Sólo algunos dispositivos pueden reproducir vídeos en 2K / 4K</string>
<string name="default_popup_resolution_title">Resolución predeterminada de emergente</string>
<string name="default_popup_resolution_title">Resolución predefinida de emergente</string>
<string name="controls_background_title">Segundo plano</string>
<string name="controls_popup_title">Emergente</string>
<string name="filter">Filtro</string>
<string name="refresh">Actualizar</string>
<string name="clear">Limpiar</string>
<string name="popup_remember_size_pos_title">Recordar propiedades del reproductor emergente</string>
<string name="popup_remember_size_pos_summary">Recordar el último tamaño y posición del reproductor emergente</string>
<string name="settings_category_popup_title">Emergente</string>
<string name="popup_resizing_indicator_title">Redimensionando</string>
<string name="use_external_video_player_summary">Elimina el audio en algunas resoluciones</string>
<string name="use_external_video_player_summary">Quita el audio en algunas resoluciones</string>
<string name="player_gesture_controls_title">Controles del reproductor por gestos</string>
<string name="player_gesture_controls_summary">Usar gestos para controlar el brillo y volumen del reproductor</string>
<string name="show_search_suggestions_title">Sugerencias de búsqueda</string>
<string name="show_search_suggestions_summary">Mostrar sugerencias cuando esté buscando</string>
<string name="show_search_suggestions_summary">Mostrar sugerencias al buscar</string>
<string name="best_resolution">Mejor resolución</string>
<string name="title_activity_about">Acerca de NewPipe</string>
<string name="action_settings">Ajustes</string>
@ -158,7 +159,7 @@
<string name="subscribe_button_title">Suscribirse</string>
<string name="subscribed_button_title">Suscrito</string>
<string name="channel_unsubscribed">Canal no suscrito</string>
<string name="subscription_change_failed">No se pudo cambiar la suscripción</string>
<string name="subscription_change_failed">No se puede cambiar la suscripción</string>
<string name="subscription_update_failed">No se pudo actualizar la suscripción</string>
<string name="tab_main">Principal</string>
<string name="tab_subscriptions">Suscripciones</string>
@ -206,8 +207,8 @@
<item quantity="one">%s vídeo</item>
<item quantity="other">%s vídeos</item>
</plurals>
<string name="item_deleted">Se eliminó el elemento</string>
<string name="delete_item_search_history">¿Quiere eliminar este elemento del historial de búsquedas\?</string>
<string name="item_deleted">Elemento borrado</string>
<string name="delete_item_search_history">¿Quieres borrar este elemento del historial de búsquedas\?</string>
<string name="main_page_content">Contenido de la página principal</string>
<string name="blank_page_summary">Página en blanco</string>
<string name="kiosk_page_summary">Página del quiosco</string>
@ -220,7 +221,7 @@
<string name="kiosk">Quiosco</string>
<string name="trending">Tendencias</string>
<string name="top_50">50 mejores</string>
<string name="show_hold_to_append_summary">Mostrar sugerencia cuando se presiona el botón de segundo plano o emergente en la página \"Detalles:\" del vídeo</string>
<string name="show_hold_to_append_summary">Mostrar sugerencia al pulsar el botón de segundo plano o emergente en la página «Detalles:» del vídeo</string>
<string name="background_player_append">En cola en el reproductor de 2.º plano</string>
<string name="popup_playing_append">En cola en el reproductor emergente</string>
<string name="play_all">Reproducir todo</string>
@ -229,20 +230,20 @@
<string name="player_recoverable_failure">Recuperándose del error del reproductor</string>
<string name="play_queue_remove">Quitar</string>
<string name="play_queue_stream_detail">Detalles</string>
<string name="play_queue_audio_settings">Configuración de audio</string>
<string name="play_queue_audio_settings">Ajustes del audio</string>
<string name="unknown_content">[Desconocido]</string>
<string name="start_here_on_main">Comenzar a reproducir aquí</string>
<string name="start_here_on_background">Comenzar a reproducir en segundo plano</string>
<string name="start_here_on_popup">Reproducir en modo emergente</string>
<string name="show_hold_to_append_title">Mostrar consejo \"Mantener presionado para añadir\"</string>
<string name="show_hold_to_append_title">Mostrar consejo «Mantener pulsado para añadir»</string>
<string name="new_and_hot">Nuevo y lo mejor</string>
<string name="hold_to_append">Mantener presionado para agregar a la cola</string>
<string name="hold_to_append">Mantener pulsado para añadir a la cola</string>
<string name="donation_title">Donar</string>
<string name="donation_encouragement">NewPipe es desarrollado por voluntarios que emplean su tiempo libre para brindarle la mejor experiencia. Haga una aportación para ayudarlos a crear un NewPipe mejor mientras disfrutan de una taza de café.</string>
<string name="give_back">Dar de vuelta</string>
<string name="website_title">Sitio web</string>
<string name="website_encouragement">Visite el sitio web de NewPipe para más información y noticias.</string>
<string name="default_content_country_title">País predeterminado del contenido</string>
<string name="default_content_country_title">País predefinido del contenido</string>
<string name="toggle_orientation">Alternar orientación</string>
<string name="switch_to_background">Cambiar a segundo plano</string>
<string name="switch_to_popup">Cambiar a emergente</string>
@ -277,28 +278,28 @@
<string name="controls_add_to_playlist_title">Añadir a</string>
<string name="detail_drag_description">Arrastrar para reordenar</string>
<string name="create">Crear</string>
<string name="delete_one">Eliminar uno</string>
<string name="delete_all">Eliminar todos</string>
<string name="delete_one">Borrar uno</string>
<string name="delete_all">Borrar todos</string>
<string name="dismiss">Descartar</string>
<string name="rename">Cambiar nombre</string>
<string name="delete_stream_history_prompt">¿Quiere eliminar este elemento del historial de reproducciones\?</string>
<string name="delete_all_history_prompt">¿Confirma que quiere eliminar todos los elementos del historial\?</string>
<string name="delete_stream_history_prompt">¿Quieres borrar este elemento del historial de reproducciones\?</string>
<string name="delete_all_history_prompt">¿Quieres borrar todos los elementos del historial\?</string>
<string name="title_last_played">Última reproducción</string>
<string name="title_most_played">Más reproducido</string>
<string name="always_ask_open_action">Preguntar siempre</string>
<string name="create_playlist">Lista de reproducción nueva</string>
<string name="delete_playlist">Eliminar</string>
<string name="delete_playlist">Borrar</string>
<string name="rename_playlist">Cambiar nombre</string>
<string name="name">Nombre</string>
<string name="append_playlist">Añadir a la lista de reproducción</string>
<string name="set_as_playlist_thumbnail">Definir como miniatura de lista de reproducción</string>
<string name="bookmark_playlist">Marcar lista de reproducción</string>
<string name="unbookmark_playlist">Eliminar marcador</string>
<string name="delete_playlist_prompt">¿Quiere eliminar esta lista\?</string>
<string name="unbookmark_playlist">Quitar marcador</string>
<string name="delete_playlist_prompt">¿Quieres borrar esta lista\?</string>
<string name="playlist_creation_success">Lista de reproducción creada</string>
<string name="playlist_add_stream_success">Añadido a la lista de reproducción</string>
<string name="playlist_thumbnail_change_success">Miniatura de lista de reproducción cambiada.</string>
<string name="playlist_delete_failure">No se pudo eliminar la lista de reproducción.</string>
<string name="playlist_delete_failure">No se pudo borrar la lista de reproducción.</string>
<string name="drawer_header_action_paceholder_text">Algo aparecerá aquí pronto ;D</string>
<string name="caption_none">Sin subtítulos</string>
<string name="resize_fit">Ajustar</string>
@ -306,15 +307,15 @@
<string name="resize_zoom">Zoom</string>
<string name="settings_category_debug_title">Depuración</string>
<string name="caption_auto_generated">Auto generados</string>
<string name="enable_leak_canary_summary">La monitorización de fugas de memoria puede causar que la app no responda cuando hay Heap Dump</string>
<string name="enable_disposed_exceptions_title">Reportar errores fuera del ciclo de duración</string>
<string name="enable_disposed_exceptions_summary">Forzar reporte de excepciones no entregables de RX fuera del fragmento o del ciclo de actividad después del descarte</string>
<string name="enable_leak_canary_summary">La monitorización de fugas de memoria puede causar que la aplicación no responda al realizar el volcado de memoria</string>
<string name="enable_disposed_exceptions_title">Informar errores fuera del ciclo de duración</string>
<string name="enable_disposed_exceptions_summary">Forzar informe de excepciones no entregables de RX fuera del fragmento o del ciclo de actividad después del descarte</string>
<string name="use_inexact_seek_title">Usar búsqueda rápida e inexacta</string>
<string name="use_inexact_seek_summary">La búsqueda inexacta permite al reproductor buscar posiciones más rápido con menor precisión. Buscar de a 5, 15 o 25 segundos no funciona.</string>
<string name="auto_queue_title">Poner en cola vídeo relacionado siguiente</string>
<string name="auto_queue_summary">Continuar reproducción sin repetir al añadir de forma automática un vídeo relacionado con el último visto</string>
<string name="file">Archivo</string>
<string name="missing_file">Archivo movido o eliminado</string>
<string name="missing_file">Archivo movido o borrado</string>
<string name="invalid_directory">La carpeta no existe</string>
<string name="invalid_source">No existe tal archivo/origen del contenido</string>
<string name="invalid_file">El archivo no existe o carece de los permisos para leer o escribir en él</string>
@ -337,7 +338,7 @@
\n3. Una descarga debería empezar (ese es el archivo de exportación)</string>
<string name="import_soundcloud_instructions">Importe un perfil de SoundCloud escribiendo la URL o su ID:
\n
\n1. Active el \"modo escritorio\" en un navegador web (el sitio no está disponible para dispositivos móviles)
\n1. Active el «modo escritorio» en un navegador web (el sitio no está disponible para dispositivos móviles)
\n2. Vaya a esta URL: %1$s
\n3. Inicie sesión cuando se le pida
\n4. Copie la URL del perfil a la que fue redireccionado.</string>
@ -349,7 +350,7 @@
<string name="download_thumbnail_summary">Desactivar para evitar la carga de miniaturas y ahorrar datos y memoria. Se vaciará la caché de imágenes en la memoria volátil y en el disco.</string>
<string name="thumbnail_cache_wipe_complete_notice">Se vació la caché de imágenes</string>
<string name="metadata_cache_wipe_title">Vaciar metadatos en memoria caché</string>
<string name="metadata_cache_wipe_summary">Eliminar todos los datos de páginas web en antememoria</string>
<string name="metadata_cache_wipe_summary">Quitar todos los datos guardados de páginas web</string>
<string name="metadata_cache_wipe_complete_notice">Se vació la caché de metadatos</string>
<string name="playback_speed_control">Controles de velocidad de reproducción</string>
<string name="playback_tempo">Tiempo</string>
@ -357,31 +358,31 @@
<string name="unhook_checkbox">Desvincular (puede causar distorsión)</string>
<string name="no_streams_available_download">No hay streams disponibles para descargar</string>
<string name="preferred_open_action_settings_title">Acción de apertura preferida</string>
<string name="preferred_open_action_settings_summary">Acción predeterminada al abrir contenido: %s</string>
<string name="preferred_open_action_settings_summary">Acción predefinida al abrir contenido: %s</string>
<string name="toast_no_player">No se encontró ninguna aplicación que reproduzca este archivo</string>
<string name="caption_setting_title">Subtítulos</string>
<string name="caption_setting_description">Modificar la escala de texto de los subtítulos y los estilos de fondo. Requiere reiniciar la app para que surta efecto.</string>
<string name="caption_setting_description">Modificar la escala de texto de los subtítulos y los estilos de fondo. Requiere reiniciar la aplicación para que surta efecto.</string>
<string name="clear_views_history_title">Vaciar historial de reproducciones</string>
<string name="clear_views_history_summary">Elimina el historial de contenido visto y posiciones de reproducción</string>
<string name="delete_view_history_alert">¿Eliminar todo el historial de reproducciones\?</string>
<string name="watch_history_deleted">Se eliminó el historial de reproducciones.</string>
<string name="clear_views_history_summary">Borra el historial de contenido visto y posiciones de reproducción</string>
<string name="delete_view_history_alert">¿Borrar todo el historial de reproducciones\?</string>
<string name="watch_history_deleted">Historial de reproducciones borrado.</string>
<string name="clear_search_history_title">Vaciar historial de búsquedas</string>
<string name="clear_search_history_summary">Elimina el historial de palabras clave de búsqueda</string>
<string name="delete_search_history_alert">¿Eliminar todo el historial de búsqueda\?</string>
<string name="search_history_deleted">Historial de búsquedas eliminado.</string>
<string name="one_item_deleted">Se eliminó 1 elemento.</string>
<string name="clear_search_history_summary">Borra el historial de búsqueda de palabras clave</string>
<string name="delete_search_history_alert">¿Borrar todo el historial de búsqueda\?</string>
<string name="search_history_deleted">Historial de búsquedas borrado.</string>
<string name="one_item_deleted">Se ha borrado 1 elemento.</string>
<string name="app_license">NewPipe es un software copyleft libre: puedes usarlo, estudiarlo, compartirlo y mejorarlo a voluntad. Específicamente, puedes redistribuirlo y/o modificarlo bajo los términos de la Licencia Pública General GNU publicada por la Free Software Foundation, ya sea la versión 3 de la Licencia, o (a tu elección) cualquier versión posterior.</string>
<string name="import_settings">¿Quiere importar también la configuración\?</string>
<string name="import_settings">¿Quiere importar también los ajustes\?</string>
<string name="privacy_policy_title">Normativa de privacidad de NewPipe</string>
<string name="privacy_policy_encouragement">El proyecto NewPipe toma su privacidad muy en serio. Por ello, la aplicación no recopila algún dato sin su consentimiento.
\nLa normativa de privacidad de NewPipe explica en detalle qué datos se envían y almacenan cuando envía un informe de fallo.</string>
\nLa normativa de privacidad de NewPipe explica en detalle qué datos se envían y almacenan al enviar un informe de fallo.</string>
<string name="read_privacy_policy">Leer la normativa de privacidad</string>
<string name="start_accept_privacy_policy">Para cumplir con el Reglamento general europeo de protección de datos (GDPR), atraemos su atención sobre la política de privacidad de NewPipe. Por favor léase cuidadosamente.
<string name="start_accept_privacy_policy">Para cumplir con el «Reglamento general europeo de protección de datos (GDPR)», atraemos su atención sobre la política de privacidad de NewPipe. Por favor léase cuidadosamente.
\nDebe aceptarlo para enviarnos el informe de error.</string>
<string name="accept">Aceptar</string>
<string name="decline">Declinar</string>
<string name="limit_data_usage_none_description">Sin límite</string>
<string name="limit_mobile_data_usage_title">Limitar la resolución cuando se usen datos móviles</string>
<string name="limit_mobile_data_usage_title">Limitar la resolución al usar datos móviles</string>
<string name="minimize_on_exit_title">Minimizar al cambiar de aplicación</string>
<string name="minimize_on_exit_summary">Acción de cambiar a otra aplicación desde el reproductor principal — %s</string>
<string name="minimize_on_exit_none_description">Ninguna</string>
@ -402,7 +403,7 @@
<string name="recovering">recuperando</string>
<string name="enqueue">Añadir a cola</string>
<string name="permission_denied">Acción denegada por el sistema</string>
<string name="file_deleted">Se eliminó el archivo</string>
<string name="file_deleted">Archivo borrado</string>
<!-- download notifications -->
<string name="download_failed">Descarga fallida</string>
<string name="download_finished">Descarga finalizada</string>
@ -417,7 +418,7 @@
<string name="download_already_pending">Hay una descarga pendiente con este nombre</string>
<string name="grid">Mostrar como grilla</string>
<string name="list">Mostrar como lista</string>
<string name="confirm_prompt">¿Quiere limpiar su historial de descargas o eliminar todos los ficheros descargados\?</string>
<string name="confirm_prompt">¿Quieres vaciar el historial de descargas o borrar todos los ficheros descargados\?</string>
<string name="stop">Detener</string>
<string name="max_retry_msg">Intentos máximos</string>
<string name="max_retry_desc">Cantidad máxima de intentos antes de cancelar la descarga</string>
@ -442,7 +443,7 @@
<string name="error_postprocessing_failed">Falló el posprocesamiento</string>
<string name="error_postprocessing_stopped">NewPipe se cerró mientras se trabajaba en el archivo</string>
<string name="error_insufficient_storage">No hay suficiente espacio disponible en el dispositivo</string>
<string name="error_progress_lost">Se perdió el progreso porque el archivo fue eliminado</string>
<string name="error_progress_lost">Se perdió el progreso porque el archivo fue borrado</string>
<string name="error_timeout">Tiempo de espera excedido</string>
<string name="error_download_resource_gone">No se puede recuperar esta descarga</string>
<string name="downloads_storage_ask_title">Preguntar dónde descargar</string>
@ -465,15 +466,15 @@
<string name="app_update_notification_channel_description">Notificaciones de versiones nuevas de NewPipe</string>
<string name="download_to_sdcard_error_title">Almacenamiento externo no disponible</string>
<string name="download_to_sdcard_error_message">No es posible descargar a una tarjeta SD externa. \¿Restablecer la ubicación de la carpeta de descarga\?</string>
<string name="saved_tabs_invalid_json">No se pudo leer las pestañas guardadas, se usarán las pestañas por defecto</string>
<string name="restore_defaults">Restaurar valores por defecto</string>
<string name="restore_defaults_confirmation">¿Quieres restaurar los valores por defecto\?</string>
<string name="saved_tabs_invalid_json">No se pudo leer las pestañas guardadas, se usarán las pestañas predefinidas</string>
<string name="restore_defaults">Restaurar valores predefinidos</string>
<string name="restore_defaults_confirmation">¿Quieres restaurar los valores predefinidos\?</string>
<string name="subscribers_count_not_available">Número de suscriptores no disponible</string>
<string name="main_page_content_summary">Qué pestañas aparecen en la página principal</string>
<string name="selection">Selección</string>
<string name="conferences">Conferencias</string>
<string name="updates_setting_title">Actualizaciones</string>
<string name="updates_setting_description">Mostrar una notificación para solicitar actualizar la aplicación cuando haya una nueva versión disponible</string>
<string name="updates_setting_description">Mostrar una notificación para solicitar actualizar la aplicación al haber una nueva versión disponible</string>
<string name="list_view_mode">Modo de vista de lista</string>
<string name="auto">Automático</string>
<string name="switch_view">Cambiar vista</string>
@ -490,12 +491,12 @@
<string name="enable_playback_state_lists_title">Posiciones en listas</string>
<string name="enable_playback_state_lists_summary">Mostrar indicador de posición en listas de reproducción</string>
<string name="settings_category_clear_data_title">Vaciar datos</string>
<string name="watch_history_states_deleted">Se eliminaron las posiciones de reproducción.</string>
<string name="clear_playback_states_title">Eliminar posiciones de reproducción</string>
<string name="clear_playback_states_summary">Elimina todas las posiciones de reproducción</string>
<string name="delete_playback_states_alert">¿Quiere eliminar todas las posiciones de reproducción\?</string>
<string name="watch_history_states_deleted">Posiciones de reproducción borradas.</string>
<string name="clear_playback_states_title">Borrar posiciones de reproducción</string>
<string name="clear_playback_states_summary">Borra todas las posiciones de reproducción</string>
<string name="delete_playback_states_alert">¿Quieres borrar todas las posiciones de reproducción\?</string>
<string name="drawer_header_description">Activar/desactivar servicio, seleccionados actualmente:</string>
<string name="default_kiosk_page_summary">Quiosco predeterminado</string>
<string name="default_kiosk_page_summary">Quiosco predefinido</string>
<string name="no_one_watching">Nadie está viendo</string>
<plurals name="watching">
<item quantity="one">%s viendo</item>
@ -511,30 +512,30 @@
<string name="peertube_instance_url_title">Instancias de PeerTube</string>
<string name="peertube_instance_url_summary">Selecciona tus instancias favoritas de PeerTube</string>
<string name="peertube_instance_url_help">Encuentra las instancias que te gusten en %s</string>
<string name="peertube_instance_add_title">Agregar instancia</string>
<string name="peertube_instance_add_title">Añadir instancia</string>
<string name="peertube_instance_add_help">Ingresar URL de la instancia</string>
<string name="peertube_instance_add_fail">No se pudo validar la instancia</string>
<string name="peertube_instance_add_https_only">Solo se admiten URL HTTPS</string>
<string name="peertube_instance_add_exists">La instancia ya existe</string>
<string name="local">Local</string>
<string name="recently_added">Agregados recientemente</string>
<string name="recently_added">Añadidos recientemente</string>
<string name="most_liked">Más gustados</string>
<string name="playlist_no_uploader">Generado automáticamente (no se encontró creador)</string>
<string name="choose_instance_prompt">Elige una instancia</string>
<string name="clear_download_history">Limpiar historial de descargas</string>
<string name="delete_downloaded_files">Eliminar archivos descargados</string>
<string name="deleted_downloads">Eliminadas %1$d descargas</string>
<string name="delete_downloaded_files">Borrar archivos descargados</string>
<string name="deleted_downloads">Borradas %1$d descargas</string>
<string name="permission_display_over_apps">Permitir mostrar sobre otras aplicaciones</string>
<string name="app_language_title">Idioma de aplicación</string>
<string name="systems_language">Predeterminado del sistema</string>
<string name="subtitle_activity_recaptcha">Pulse en «Hecho» cuando esté resuelto</string>
<string name="systems_language">Predefinido del sistema</string>
<string name="subtitle_activity_recaptcha">Pulse en «Hecho» al resolverlo</string>
<string name="recaptcha_done_button">Hecho</string>
<string name="videos_string">Vídeos</string>
<plurals name="seconds">
<item quantity="one">%d segundo</item>
<item quantity="other">%d segundos</item>
</plurals>
<string name="new_seek_duration_toast">Debido a limitaciones de ExoPlayer la duración de la búsqueda fue fijada en %d segundos</string>
<string name="new_seek_duration_toast">Debido a limitaciones de ExoPlayer, la duración de la búsqueda fue definida en %d segundos</string>
<string name="mute">Silenciar</string>
<string name="unmute">Desactivar silencio</string>
<string name="help">Ayuda</string>
@ -572,7 +573,7 @@
<string name="feed_use_dedicated_fetch_method_summary">Disponible para algunos servicios, suele ser más rápido pero puede mostrar una cantidad limitada de ítems y a menudo información incompleta (por ejemplo falta de duración, tipo de ítem o estado).</string>
<string name="feed_use_dedicated_fetch_method_enable_button">Activar modo rápido</string>
<string name="feed_use_dedicated_fetch_method_disable_button">Desactivar modo rápido</string>
<string name="feed_use_dedicated_fetch_method_help_text">¿Piensas que la carga de contenidos es muy lenta\? Entonces intenta habilitar la carga rápida (puedes cambiarlo en los ajustes o presionando el botón debajo).
<string name="feed_use_dedicated_fetch_method_help_text">¿Piensas que la carga de contenidos es muy lenta\? Entonces intenta habilitar la carga rápida (puedes cambiarlo en los ajustes o pulsando el botón debajo).
\n
\nNewpipe ofrece dos formas de cargar los contenidos:
\n• Obtener todos los canales con suscripciones, lento pero completo.
@ -593,25 +594,25 @@
<string name="songs">Canciones</string>
<string name="restricted_video">Este video tiene restricción por edades.
\n
\nHabilita \"Contenido restringido por edades\" en los ajustes si quieres verlo.</string>
\nHabilita «%1$s» en los ajustes si quieres verlo.</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Sí, y también videos vistos parcialmente</string>
<string name="remove_watched_popup_warning">Los videos que ya se hayan visto luego de agregados a la lista de reproducción, serán eliminados.
<string name="remove_watched_popup_warning">Los videos que ya se hayan visto luego de añadidos a la lista de reproducción, serán quitados.
\n¿Estás seguro\? ¡Esta acción no se puede deshacer!</string>
<string name="remove_watched_popup_title">¿Borrar videos ya vistos\?</string>
<string name="remove_watched">Borrar videos ya vistos</string>
<string name="remove_watched_popup_title">¿Quitar videos ya vistos\?</string>
<string name="remove_watched">Quitar videos ya vistos</string>
<string name="video_detail_by">Por %s</string>
<string name="channel_created_by">Creado por %s</string>
<string name="detail_sub_channel_thumbnail_view_description">Miniatura de avatar del canal</string>
<string name="show_original_time_ago_summary">Los textos originales de los servicios serán visibles en los ítems de transmisiones</string>
<string name="show_original_time_ago_title">Mostrar tiempo atrás original en ítems</string>
<string name="youtube_restricted_mode_enabled_title">Modo restringido de YouTube</string>
<string name="youtube_restricted_mode_enabled_title">Activar el «Modo restringido» de YouTube</string>
<string name="playlist_page_summary">Página de lista de reproducción</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Mostrar sólo suscripciones desagrupadas</string>
<string name="no_playlist_bookmarked_yet">Sin marcadores de lista de reproducción aún</string>
<string name="select_a_playlist">Seleccione una lista de reproducción</string>
<string name="error_report_open_github_notice">Por favor revise si ya existe una discusión sobre su problema. Cuando se crean entradas duplicadas, toma tiempo de nosotros que podríamos usar para arreglar tal problema.</string>
<string name="error_report_open_issue_button_text">Reportar en Github</string>
<string name="copy_for_github">Copiar reporte con formato</string>
<string name="error_report_open_github_notice">Por favor revise si ya existe una discusión sobre su problema. Al crear entradas duplicadas, toma tiempo de nosotros que podríamos usar para arreglar tal problema.</string>
<string name="error_report_open_issue_button_text">Informar en Github</string>
<string name="copy_for_github">Copiar informe con formato</string>
<string name="search_showing_result_for">Mostrando resultados para: %s</string>
<string name="notification_action_shuffle">Orden aleatorio</string>
<string name="notification_scale_to_square_image_title">Escalar miniatura a relación de aspecto 1:1</string>
@ -624,7 +625,7 @@
<string name="auto_queue_toggle">Poner en cola</string>
<string name="clear_queue_confirmation_summary">Cambiar de un reproductor a otro puede reemplazar la cola de reproducción</string>
<string name="clear_queue_confirmation_description">La cola de reproducción activa será reemplazada</string>
<string name="clear_queue_confirmation_title">Pedir confirmación antes de eliminar una cola</string>
<string name="clear_queue_confirmation_title">Pedir confirmación antes de vaciar una cola</string>
<string name="notification_action_nothing">Nada</string>
<string name="notification_action_buffering">Almacenar en memoria (búfer)</string>
<string name="notification_action_repeat">Repetir</string>
@ -636,4 +637,12 @@
<string name="notification_action_1_title">Botón de segunda acción</string>
<string name="notification_action_0_title">Botón de primera acción</string>
<string name="notification_scale_to_square_image_summary">Escalar la miniatura del vídeo mostrada en la notificación de relación de aspecto 16:9 a 1:1 (puede ocasionar distorsiones)</string>
<string name="clear_cookie_summary">Vaciar las cookies que NewPipe guarda al resolver un reCAPTCHA</string>
<string name="show_age_restricted_content_summary">Mostrar contenido inapropiado para niños porque tiene un limite de edad (como 18+).</string>
<string name="show_memory_leaks">Mostrar pérdidas de memoria</string>
<string name="enqueued">Añadido a la cola</string>
<string name="enqueue_stream">Añadir a la cola</string>
<string name="recaptcha_cookies_cleared">Las cookies reCAPTCHA han sido limpiadas</string>
<string name="clear_cookie_title">Limpiar las cookies reCAPTCHA</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube provee un «Modo restringido», el cual oculta contenido potencialmente sólo apto para adultos.</string>
</resources>

View file

@ -57,6 +57,8 @@
<string name="light_theme_title">Hele</string>
<string name="dark_theme_title">Tume</string>
<string name="black_theme_title">Must</string>
<string name="popup_remember_size_pos_title">Pea hüpikakna suurus ja asukoht meeles</string>
<string name="popup_remember_size_pos_summary">Pea hüpikakna viimane suurus ja asukoht meeles</string>
<string name="use_inexact_seek_title">Kasuta ebatäpset kerimist</string>
<string name="use_inexact_seek_summary">Ebatäpne kerimine lubab pleieril otsida asukohta kiiremini täpsuse arvel</string>
<string name="download_thumbnail_title">Laadi pisipildid</string>
@ -99,7 +101,6 @@
<string name="popup_playing_append">Lisati hüpikpleieri järjekorda</string>
<string name="content">Sisu</string>
<string name="show_age_restricted_content_title">Vanusepiiranguga sisu</string>
<string name="video_is_age_restricted">Kuva vanusepiiranguga video. Sellist sisu saab lubada seadetes.</string>
<string name="duration_live">OTSE</string>
<string name="downloads">Allalaadimised</string>
<string name="downloads_title">Allalaadimised</string>

View file

@ -26,7 +26,7 @@
<string name="show_next_and_similar_title">Erakutsi \'hurrengo\' eta \'antzeko\' bideoak</string>
<string name="unsupported_url">URLak ez du euskarririk</string>
<string name="content_language_title">Edukiaren hizkuntz lehenetsia</string>
<string name="settings_category_video_audio_title">Bideoa eta Audioa</string>
<string name="settings_category_video_audio_title">Bideoa eta audioa</string>
<string name="list_thumbnail_view_description">Bideoaren aurreikuspen argazkitxoa</string>
<string name="detail_thumbnail_view_description">Erreproduzitu bideoa, iraupena:</string>
<string name="detail_uploader_thumbnail_view_description">Igotzailearen abatarraren iruditxoa</string>
@ -39,7 +39,8 @@
<string name="use_external_video_player_title">Erabili kanpo bideo-erreproduzigailua</string>
<string name="use_external_audio_player_title">Erabili kanpo audio-erreproduzigailua</string>
<string name="background_player_playing_toast">Atzeko planoan erreproduzitzen</string>
<string name="main_bg_subtitle">Sakatu \"Bilatu\" hasteko</string>
<string name="main_bg_subtitle">\"Bilatu\" sakatu hasteko
\n</string>
<string name="download_path_audio_title">Audioa deskargatzeko karpeta</string>
<string name="download_path_audio_dialog_title">Aukeratu audio fitxategiak deskargatzeko karpeta</string>
<string name="download_path_audio_summary">Deskargatutako audio fitxategiak hemen gordetzen dira</string>
@ -59,6 +60,8 @@
<string name="default_video_format_title">Hobetsitako bideo-formatua</string>
<string name="theme_title">Gaia</string>
<string name="black_theme_title">Beltza</string>
<string name="popup_remember_size_pos_title">Gogoratu laster-leihoaren tamaina eta posizioa</string>
<string name="popup_remember_size_pos_summary">Gogoratu laster-leihoaren azken tamaina eta posizioa</string>
<string name="player_gesture_controls_title">Erreproduzigailuaren keinu bidezko kontrola</string>
<string name="player_gesture_controls_summary">Erabili keinuak erreproduzigailuaren distira eta bolumena kontrolatzeko</string>
<string name="show_search_suggestions_title">Bilaketa-iradokizunak</string>
@ -67,8 +70,7 @@
<string name="settings_category_other_title">Besteak</string>
<string name="popup_playing_toast">Laster-leiho moduan erreproduzitzen</string>
<string name="content">Edukia</string>
<string name="show_age_restricted_content_title">Adinez mugatutako edukia</string>
<string name="video_is_age_restricted">Erakutsi adinez mugatutako bideoa. Ezarpenetan aldaketak egin daitezke gero.</string>
<string name="show_age_restricted_content_title">Adinez mugatutako edukia erakutsi</string>
<string name="duration_live">Zuzenean</string>
<string name="downloads">Deskargak</string>
<string name="downloads_title">Deskargak</string>
@ -97,11 +99,11 @@
<string name="app_ui_crash">Aplikazioa/interfazea kraskatu da</string>
<string name="sorry_string">Hori ez litzateke gertatu behar.</string>
<string name="error_report_button_text">Eman errore honen berri e-posta bidez</string>
<string name="error_snackbar_message">Erroreak gertatu dira.</string>
<string name="error_snackbar_message">Barkatu, zerbait gaizki atera da.</string>
<string name="error_snackbar_action">Salatu</string>
<string name="what_device_headline">Informazioa:</string>
<string name="what_happened_headline">Zer gertatu da:</string>
<string name="info_labels">Zer:\\nEskaria:\\nEdukiaren hizkuntza:\\nZerbitzua:\\nGMT Ordua:\\nPaketea:\\nBertsioa:\\nSE bertsioa:</string>
<string name="info_labels">Zer:\\nEskaria:\\nEdukiaren hizkuntza:\\nEdukiaren herrialdea:\\nAplikazioaren hizkuntza:\\nZerbitzua:\\nGMT Ordua:\\nPaketea:\\nBertsioa:\\nSE bertsioa:</string>
<string name="your_comment">Zure iruzkina (Ingelesez):</string>
<string name="error_details_headline">Xehetasunak:</string>
<string name="report_error">Salatu errorea</string>
@ -176,7 +178,7 @@
<string name="notification_channel_name">NewPipe jakinarazpena</string>
<string name="settings_category_player_title">Erreproduzigailua</string>
<string name="settings_category_player_behavior_title">Portaera</string>
<string name="settings_category_history_title">Historia eta cachea</string>
<string name="settings_category_history_title">Historia eta cache-a</string>
<string name="playlist">Erreprodukzio-zerrenda</string>
<string name="undo">Desegin</string>
<string name="notification_channel_description">Atzeko planoko eta laster-leihoko NewPipe erreproduzigailuen jakinarazpenak</string>
@ -245,7 +247,7 @@
<string name="hold_to_append">Mantendu ilaran jartzeko</string>
<string name="start_here_on_main">Hasi hemen erreproduzitzen</string>
<string name="start_here_on_background">Hasi erreproduzitzen bigarren planoan</string>
<string name="start_here_on_popup">Hasi erreproduzitzen laster-leihoan</string>
<string name="start_here_on_popup">Laster-leihoan erreproduzitzen hasi</string>
<string name="drawer_open">Ireki tiradera</string>
<string name="drawer_close">Itxi tiradera</string>
<string name="no_player_found_toast">Ez da jarioen erreproduzigailurik aurkitu (VLC instalatu dezakezu).</string>
@ -419,7 +421,7 @@
<string name="grid">Sareta</string>
<string name="auto">Automatikoa</string>
<string name="switch_view">Aldatu ikuspegia</string>
<string name="app_update_notification_content_title">NewPipe eguneraketa eskuragarri!</string>
<string name="app_update_notification_content_title">NewPipe-ren eguneraketa eskuragarri dago!</string>
<string name="app_update_notification_content_text">Sakatu deskargatzeko</string>
<string name="missions_header_finished">Amaituta</string>
<string name="missions_header_pending">Zain</string>
@ -589,5 +591,55 @@
<string name="songs">Abestiak</string>
<string name="restricted_video">Bideo hau adinez mugatua dago.
\n
\nIkusi nahi baduzu, gaitu ezazu \"Adinez mugatutako edukia\" ezarpenetan.</string>
\nIkusi nahi baduzu, piztu ezazu \"%1$s\" ezarpenetan.</string>
<string name="video_detail_by">Egilea: %s</string>
<string name="playlist_page_summary">Erreprodukzio zerrendaren orria</string>
<string name="channel_created_by">%s-k sortua</string>
<string name="detail_sub_channel_thumbnail_view_description">Kanalaren avatar-earen miniatura</string>
<string name="feed_group_show_only_ungrouped_subscriptions">Erakutsi agrupatuta ez dauden harpidetzak bakarrik</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Bai, partzialki ikusitako bideoak ere bai</string>
<string name="remove_watched_popup_warning">Jada ikusi eta gero erreprodukzio zerrendara gehitu diren bideoak ezabatuak izango dira.
\nJarraitu nahi duzu\? Ekintza hau ezin da desegin!</string>
<string name="remove_watched_popup_title">Ikusitako bideoak ezabatu\?</string>
<string name="remove_watched">Ikusitako bideoak ezabatu</string>
<string name="never">Inoiz ez</string>
<string name="wifi_only">Bakarrik WiFi-arekin</string>
<string name="autoplay_summary">Erreprodukzioa automatikoki hasi — %s</string>
<string name="show_memory_leaks">Erakutsi memoria galerak</string>
<string name="title_activity_play_queue">Ilara erreproduzitu</string>
<string name="no_playlist_bookmarked_yet">Oraindik ez dago erreprodukzio-zerrenda laster-markarik</string>
<string name="select_a_playlist">Playlist bat aukeratu</string>
<string name="error_report_open_github_notice">Mesedez, egiaztatu jada zure arazoarekin diskusiorik sortuta badagoen. Sarrera duplikatuak daudenean, arazoa ebazteko erabili dezakegun denbora galtzen ari gara.</string>
<string name="copy_for_github">Formatodun erreportea kopiatu</string>
<string name="error_report_open_issue_button_text">GitHub-en erreportatu</string>
<string name="clear_cookie_summary">reCAPTCHA bat egiten duzunean NewPipe-k gordetzen dituen kookiak ezabatu</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA kookiak garbitu dira</string>
<string name="clear_cookie_title">Ezabatu reCAPTCHA-ren kookiak</string>
<string name="show_age_restricted_content_summary">Adinez mugatuta dagoen eta haurrentzako desegokia izan daitezkeen edukia erakutsi (+18 adibidez).</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube-ren \"Modu Murriztua\" helduentzako edukia izan daitekeen edukia ezkutatzen du.</string>
<string name="youtube_restricted_mode_enabled_title">Piztu YouTube-ren \"Modu Murriztua\"</string>
<string name="settings_category_notification_title">Jakinarazpena</string>
<string name="unsupported_url_dialog_message">Ezin izan da URL-a ezagutu. Beste aplikazio batekin ireki\?</string>
<string name="auto_queue_toggle">Auto-ilara</string>
<string name="clear_queue_confirmation_description">Erreprodukzio ilara aktiboa ordezkatuko da</string>
<string name="clear_queue_confirmation_summary">Erreproduzitzaile batetik beste batera aldatzeak ilara ordezkatu dezake</string>
<string name="clear_queue_confirmation_title">Konfirmazioa eskatu ilaratik ezabatu baino lehenago</string>
<string name="notification_action_nothing">Ezer ez</string>
<string name="notification_action_buffering">Buffering</string>
<string name="notification_action_shuffle">Aleatorio</string>
<string name="notification_actions_at_most_three">Gehienez hiru ekintza aukera ditzakezu jakinarazpenean erakusteko!</string>
<string name="notification_action_repeat">Errepikatu</string>
<string name="notification_action_4_title">Bostgarren ekintzaren botoia</string>
<string name="notification_action_3_title">Laugarren ekintzaren botoia</string>
<string name="notification_action_2_title">Hirugarren ekintzaren botoia</string>
<string name="notification_action_1_title">Bigarren ekintzaren botoia</string>
<string name="notification_action_0_title">Lehenego ekintzaren botoia</string>
<string name="notification_scale_to_square_image_summary">Eskalatu jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era (distortsioak sor ditzake)</string>
<string name="notification_scale_to_square_image_title">Miniatura 1:1 formatu-ratiora eskalatu</string>
<string name="search_showing_result_for">%s bilaketaren erantzunak erakusten</string>
<string name="enqueued">Ilaran jarri da</string>
<string name="enqueue_stream">Jarri ilaran</string>
<string name="show_original_time_ago_summary">Zerbitzuen jatorrizko testuak transmisioko elementuetan ikusgai egongo dira</string>
<string name="show_original_time_ago_title">Erakutsi «orain dela» jatorrizko denbora elementuetan</string>
<string name="notification_actions_summary">Editatu beheko jakinarazpen ekintza bakoitza gainean sakatuz. Hautatu horietako hiru gehienez jakinarazpen trinkoan erakusteko eskuineko kontrol laukiak erabiliz.</string>
</resources>

View file

@ -45,7 +45,6 @@
<string name="background_player_playing_toast">در حال پخش در پس‌زمینه</string>
<string name="content">محتوا</string>
<string name="show_age_restricted_content_title">محتوای محدود شده بر اساس سن</string>
<string name="video_is_age_restricted">نمایش ویدیوهای دارای محدودیت سنی. تغییرات آتی از طریق تنظیمات ممکن است.</string>
<string name="duration_live">زنده</string>
<string name="downloads">بارگیری‌ها</string>
<string name="downloads_title">بارگیری‌ها</string>
@ -288,6 +287,8 @@
<string name="popup_mode_share_menu_title">حالت تصویر در تصویر</string>
<string name="default_popup_resolution_title">اندازه پیش فرض پنجره جداگانه</string>
<string name="controls_popup_title">تصویر در تصویر</string>
<string name="popup_remember_size_pos_title">به یاد نگه داشتن خصوصیات</string>
<string name="popup_remember_size_pos_summary">به یاد داشتن آخرین اندازه و موقعیت قبلی پنجره جداگانه</string>
<string name="use_inexact_seek_title">زمان فعلی پخش کننده را به صورت تقریبی و سریع جلو ببر</string>
<string name="use_inexact_seek_summary">این گزینه باعث می شود هنگام جلو/عقب کردن زمان تصویر، به جای زمان دقیق انتخاب شده، به زمان غیر دقیق و نزدیک به مکان انتخاب شده برود که این کار سریع تر انجام می شود.</string>
<string name="app_ui_crash">کاره یا رابط کاربری با خطا مواجه شد</string>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="main_bg_subtitle">Napauta hakua aloittaaksesi</string>
<string name="main_bg_subtitle">Napauta hakua aloittaaksesi
\n</string>
<string name="view_count_text">%1$s näyttökertaa</string>
<string name="upload_date_text">Julkaistu %1$s</string>
<string name="no_player_found">Ei löytynyt suoratoistosoitinta. Asennetaanko VLC\?</string>
@ -12,7 +13,7 @@
<string name="download">Lataus</string>
<string name="search">Haku</string>
<string name="settings">Asetukset</string>
<string name="did_you_mean">Tarkoititko \"%1$s\"\?</string>
<string name="did_you_mean">Tarkoititko ”%1$s”\?</string>
<string name="share_dialog_title">Jaa</string>
<string name="choose_browser">Valitse selain</string>
<string name="screen_rotation">kierto</string>
@ -44,7 +45,7 @@
<string name="show_higher_resolutions_summary">Vain jotkin laitteet voivat toistaa 2K/4K-videota</string>
<string name="play_with_kodi_title">Toista Kodissa</string>
<string name="kore_not_found">Asennetaanko puuttuva Kore-sovellus\?</string>
<string name="show_play_with_kodi_title">Näytä \"Toista Kodissa\"-vaihtoehto</string>
<string name="show_play_with_kodi_title">Näytä ”Toista Kodissa”-vaihtoehto</string>
<string name="show_play_with_kodi_summary">Näyttää vaihtoehdon videon toistamiseen Kodi-mediasoittimessa</string>
<string name="play_audio">Ääni</string>
<string name="default_audio_format_title">Oletusääniformaatti</string>
@ -53,6 +54,8 @@
<string name="light_theme_title">Kirkas</string>
<string name="dark_theme_title">Tumma</string>
<string name="black_theme_title">Musta</string>
<string name="popup_remember_size_pos_title">Muista ponnahdusikkunan ominaisuudet</string>
<string name="popup_remember_size_pos_summary">Muista ponnahdusikkunan viimeisin koko ja sijainti</string>
<string name="player_gesture_controls_title">Soittimen eleohjaus</string>
<string name="player_gesture_controls_summary">Käytä eleitä ohjataksesi soittimen kirkkautta ja äänentasoa</string>
<string name="show_search_suggestions_title">Hakuehdotukset</string>
@ -76,8 +79,7 @@
<string name="background_player_playing_toast">Toistaa taustalla</string>
<string name="popup_playing_toast">Toistetaan ponnahdusikkunatilassa</string>
<string name="content">Sisältö</string>
<string name="show_age_restricted_content_title">Ikärajoitettu sisältö</string>
<string name="video_is_age_restricted">Ikärajoitettu video. Muuttaminen on mahdollista asetuksissa.</string>
<string name="show_age_restricted_content_title">Näytä ikärajoitettu sisältö</string>
<string name="duration_live">Suora</string>
<string name="downloads">Lataukset</string>
<string name="downloads_title">Lataukset</string>
@ -358,11 +360,14 @@
<string name="previous_export">Edellinen vienti</string>
<string name="subscriptions_import_unsuccessful">Tilauksia ei voitu tuoda</string>
<string name="subscriptions_export_unsuccessful">Tilauksia ei voitu viedä</string>
<string name="import_youtube_instructions">Tuo youtube-tilaukset lataamalla ensin tilauslistatiedostosi:
<string name="import_youtube_instructions">Tuo YouTube-tilaukset Google Takeoutista:
\n
\n1. Mene osoitteeseen: %1$s
\n2. Kirjaudu sisään kun niin vaaditaan
\n3. Latauksen pitäisi alkaa (se on se tiedosto)</string>
\n2. Kirjaudu sisään pyydettäessä
\n3. Klikkaa \"Kaikki Youtube-data valittuna\", sitten \"Poista kaikki valinnat\", sitten ainoastaan \"tilaukset\" ja klikkaa \"OK\"
\n4. Klikkaa \"Seuraava vaihe\" ja \"Luo vienti\"
\n5. Klikkaa \"Lataa\" tämän ilmestyessä
\n6. Ladatusta takeoutin zip-tiedostosta pura json-tiedosto (yleensä sijainnissa \"Youtube ja Youtube Musiikki/tilaukset/tilaukset.json\" ja tuo se tänne</string>
<string name="import_soundcloud_instructions">Tuo SoundCloud-profiili kirjoittamalla joko osoite tai ID:si:
\n
\n1. Laita päälle työpöytämoodi selaimessasi (tai käytä tietokonetta, tämä sivu ei toimi mobiilisivuna)
@ -405,7 +410,7 @@
<string name="selection">Valinta</string>
<string name="main_page_content_summary">Mitkä välilehdet näytetään pääsivulla</string>
<string name="recaptcha_done_button">Valmis</string>
<string name="subtitle_activity_recaptcha">Paina \"Valmis\", kun ratkaistu</string>
<string name="subtitle_activity_recaptcha">Paina ”Valmis”, kun ratkaistu</string>
<string name="infinite_videos">∞ videota</string>
<string name="more_than_100_videos">100+ videota</string>
<plurals name="watching">
@ -438,8 +443,8 @@
<string name="videos_string">Videot</string>
<string name="restricted_video">Tämä video on ikärajoitettu.
\n
\nSalli ikärajoitettu sisältö asetuksissa katsoaksesi.</string>
<string name="youtube_restricted_mode_enabled_title">YouTuben rajoitettu tila</string>
\nSalli ”%1$s” asetuksissa katsoaksesi.</string>
<string name="youtube_restricted_mode_enabled_title">Ota käyttöön YouTuben ”Rajoitettu tila”</string>
<string name="settings_category_updates_title">Päivitykset</string>
<string name="peertube_instance_add_exists">Instanssi on jo olemassa</string>
<string name="peertube_instance_add_https_only">Vain HTTPS-URL:t ovat tuettuja</string>
@ -485,9 +490,9 @@
\n• Koko tilatun kanavan lataaminen, mikä on hidasta, mutta lataa syötteen kokonaisuudessaan.
\n• Erityisen palvelu-endpointin käyttö, mikä on nopeaa, mutta yleensä ei lataa syötettä kokonaisuudessaan.
\n
\nNäiden kahden välinen ero on, että nopean lataamista tiedoista yleensä puuttuu esim. sisällön kesto tai tyyppi (ei voi erotella livevideoita ja tavallisia) tai se ei lataa kaikkea sisältöä.
\nNäiden kahden välinen ero on, että nopean tavan lataamista tiedoista yleensä puuttuu esim. sisällön kesto tai tyyppi (ei voi erotella livevideoita ja tavallisia) tai se ei lataa kaikkea sisältöä.
\n
\nYouTuve on esimerkki palvelusta, joka tarjoaa nopean tavan RSS-syötteen avulla.
\nYouTube on esimerkki palvelusta, joka tarjoaa nopean tavan RSS-syötteen avulla.
\n
\nValinta riippuu siitä, mitä halutaan: nopeutta vai tarkkoja tietoja.</string>
<string name="default_kiosk_page_summary">Oletuskioski</string>
@ -632,4 +637,14 @@
<string name="notification_action_0_title">Ensimmäinen toimintopainike</string>
<string name="notification_scale_to_square_image_summary">Skaalaa ilmoituksessa näytettävä videon esikatselukuva kuvasuhteesta 16:9 kuvasuhteeseen 1:1 (saattaa aiheuttaa vääristymiä)</string>
<string name="notification_scale_to_square_image_title">Skaalaa esikatselukuva 1:1-kuvasuhteeseen</string>
<string name="show_memory_leaks">Näytä muistivuodot</string>
<string name="enqueued">Lisätty jonoon</string>
<string name="enqueue_stream">Lisää jonoon</string>
<string name="clear_cookie_summary">Poista evästeet, jotka NewPipe tallentaa, kun ratkaiset reCAPTCHA:n</string>
<string name="recaptcha_cookies_cleared">reCAPTCHA-evästeet on poistettu</string>
<string name="clear_cookie_title">Poista reCAPTCHA-evästeet</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube tarjoaa ”Rajoitetun tilan”, joka piilottaa aikuisviihdesisällön.</string>
<string name="show_age_restricted_content_summary">Näytä mahdollisesti lapsille sopimaton sisältö, jolla on ikäraja (esim. 18+).</string>
<string name="notification_colorize_summary">Anna Androidin muokata ilmoituksen väriä esikatselukuvan päävärin mukaan (tämä ei ole mahdollista kaikilla laitteilla)</string>
<string name="notification_colorize_title">Käytä värejä ilmoituksessa</string>
</resources>

View file

@ -55,7 +55,7 @@
<string name="content_not_available">Contenu indisponible</string>
<string name="error_snackbar_message">Désolé, quelque chose s\'est mal passé.</string>
<string name="content">Contenu</string>
<string name="show_age_restricted_content_title">Contenu avec limite dâge</string>
<string name="show_age_restricted_content_title">Afficher le contenu avec limite dâge</string>
<string name="duration_live">En direct</string>
<string name="could_not_load_thumbnails">Impossible de charger toutes les miniatures</string>
<string name="youtube_signature_deobfuscation_error">Impossible de déchiffrer la signature URL de la vidéo</string>
@ -73,9 +73,9 @@
<string name="audio">Audio</string>
<string name="retry">Réessayer</string>
<string name="storage_permission_denied">Veuillez dabord accorder laccès au stockage</string>
<string name="main_bg_subtitle">Appuyer sur \"Rechercher\" pour commencer</string>
<string name="main_bg_subtitle">Appuyez sur la loupe pour commencer
\n</string>
<string name="autoplay_by_calling_app_title">Lecture automatique</string>
<string name="video_is_age_restricted">Affiche les vidéos soumises à une limite dâge. Modifier cette option est possible depuis les paramètres.</string>
<string name="user_report">Rapport utilisateur</string>
<string name="error_snackbar_action">Signaler</string>
<string name="could_not_setup_download_menu">Impossible de configurer le menu de téléchargement</string>
@ -125,6 +125,8 @@
<string name="show_higher_resolutions_title">Afficher des définitions plus élevées</string>
<string name="show_higher_resolutions_summary">Seuls certains appareils peuvent lire les vidéos 2K et 4K</string>
<string name="default_video_format_title">Format vidéo par défaut</string>
<string name="popup_remember_size_pos_title">Mémoriser les propriétés de la fenêtre flottante</string>
<string name="popup_remember_size_pos_summary">Mémorise les dernières taille et position de la fenêtre flottante</string>
<string name="settings_category_popup_title">Flottant</string>
<string name="filter">Filtre</string>
<string name="refresh">Rafraîchir</string>
@ -159,7 +161,7 @@
<string name="charset_letters_and_digits">Lettres et chiffres</string>
<string name="title_activity_about">À propos de NewPipe</string>
<string name="copyright" formatted="true">© %1$s par %2$s sous %3$s</string>
<string name="contribution_encouragement">Que ce soit pour des idées de traductions, de changements de design, de nettoyage de code ou de gros changements de code, une aide est toujours la bienvenue. Plus on en fera, meilleur il deviendra!</string>
<string name="contribution_encouragement">Que ce soit pour des idées de traductions, de changements de design, de nettoyage de code ou de gros changements de code, une aide est toujours la bienvenue. Plus on en fera meilleur il sera!</string>
<string name="subscription_change_failed">Impossible de modifier labonnement</string>
<string name="subscription_update_failed">Impossible dactualiser labonnement</string>
<string name="resume_on_audio_focus_gain_summary">Continuer la lecture après les interruptions (ex. : appels téléphoniques)</string>
@ -332,11 +334,14 @@
<string name="previous_export">Exportation précédente</string>
<string name="subscriptions_import_unsuccessful">Impossible dimporter les abonnements</string>
<string name="subscriptions_export_unsuccessful">Impossible dexporter les abonnements</string>
<string name="import_youtube_instructions">Veuillez importer vos abonnements YouTube en téléchargeant le fichier dexportation.
<string name="import_youtube_instructions">Importez vos abonnements YouTube depuis Google Takeout :
\n
\n1. Suivez ce lien : %1$s.
\n2. Connectez-vous à votre compte.
\n3. Un téléchargement va démarrer (celui du fichier dexportation).</string>
\n1. Suivez ce lien : %1$s
\n2. Connectez-vous à votre compte
\n3. Cliquez sur \"Toutes les données Youtube sont incluses\", puis sur \"Tout désélectionner\", puis sélectionnez uniquement \"abonnements\" et cliquez sur \"OK\"
\n4. Cliquez sur \"Étape suivante\" et ensuite sur \"Créer une exportation\"
\n5. Cliquez sur le bouton \"Télécharger\" après qu\'il apparaisse et
\n6. À partir du fichier zip téléchargé, extrayez le fichier .json (généralement sous \"YouTube et YouTube Music/subscriptions/subscriptions.json\") et importez-le ici.</string>
<string name="import_soundcloud_instructions">Veuillez importer un profil SoundCloud en saisissant lURL de votre profil ou votre identifiant.
\n
\n1. Activez le « mode bureau » dans votre navigateur Web (le site nest pas disponible pour les appareils mobiles).
@ -587,9 +592,9 @@
<string name="more_than_100_videos">100+ vidéos</string>
<string name="artists">Artistes</string>
<string name="songs">Chansons</string>
<string name="restricted_video">Cette vidéo est bloquée à cause de la limite d\'âge.
<string name="restricted_video">Cette vidéo dispose d\'une limite d\'âge.
\n
\nActivez « Contenu avec limite d\'âge » dans les paramètres, rubrique « Contenu » si vous voulez la voir.</string>
\nActivez « %1$s » dans les paramètres si vous voulez la voir.</string>
<string name="remove_watched">Supprimer les vidéos vues</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Oui, et des vidéos partiellement regardées</string>
<string name="remove_watched_popup_warning">Les vidéos qui ont été regardées avant et après avoir été ajoutées à la liste de lecture seront supprimées.
@ -600,7 +605,7 @@
<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_title">Afficher la date originelle sur les items</string>
<string name="youtube_restricted_mode_enabled_title">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 les abonnements sans groupes uniquement</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>
@ -632,4 +637,14 @@
<string name="notification_action_0_title">Premier bouton d\'action</string>
<string name="notification_scale_to_square_image_summary">Mettre à l\'échelle la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 (peut provoquer des déformations)</string>
<string name="notification_scale_to_square_image_title">Dimensionner la miniature au format 1:1</string>
<string name="show_memory_leaks">Afficher les fuites de mémoire</string>
<string name="enqueued">Ajouté à la file d\'attente</string>
<string name="enqueue_stream">Ajouter à la file d\'attente</string>
<string name="clear_cookie_summary">Effacer les cookies que NewPipe garde lorsque vous résolvez un reCAPTCHA</string>
<string name="recaptcha_cookies_cleared">Les cookies reCAPTCHA ont été effacés</string>
<string name="clear_cookie_title">Effacer les cookies reCAPTCHA</string>
<string name="youtube_restricted_mode_enabled_summary">YouTube dispose d\'un « Mode restreint » qui cache le contenu potentiellement inapproprié.</string>
<string name="show_age_restricted_content_summary">Le contenu de cette émission n\'est peut-être pas approprié pour les enfants à cause d\'une limite d\'âge (18 +).</string>
<string name="notification_colorize_title">Notification colorée</string>
<string name="notification_colorize_summary">Demander à Android de personnaliser la couleur de la notification en fonction de la couleur principale de la miniature (noter que cela nest pas disponible sur tous les appareils)</string>
</resources>

View file

@ -58,6 +58,8 @@
<string name="light_theme_title">Claro</string>
<string name="dark_theme_title">Escuro</string>
<string name="black_theme_title">Negro</string>
<string name="popup_remember_size_pos_title">Lembrar o tamaño e a posición do «popup»</string>
<string name="popup_remember_size_pos_summary">Lembrar o tamaño e a posición anteriores do «popup»</string>
<string name="use_inexact_seek_title">Usar un salto inexacto mais inexacto</string>
<string name="use_inexact_seek_summary">Busca incorrecta permite ao xogador buscar posicións máis rápidas con precisión reducida. A busca de 5, 15 ou 25 segundos non funciona con isto.</string>
<string name="download_thumbnail_title">Carregar miniaturas</string>
@ -99,7 +101,6 @@
<string name="popup_playing_append">Na cola do reprodutor popup</string>
<string name="content">Contido</string>
<string name="show_age_restricted_content_title">Contido restrinxido para certa idade</string>
<string name="video_is_age_restricted">Mostrar vídeo restrinxido por idade. Os cambios futuros son posibles na configuración.</string>
<string name="duration_live">En directo</string>
<string name="downloads">Descargas</string>
<string name="downloads_title">Descargas</string>

View file

@ -46,6 +46,8 @@
<string name="light_theme_title">בהיר</string>
<string name="dark_theme_title">כהה</string>
<string name="black_theme_title">שחור</string>
<string name="popup_remember_size_pos_title">שמירת מאפייני החלון הצף</string>
<string name="popup_remember_size_pos_summary">שמירת המיקום והגודל האחרונים של החלון הצף</string>
<string name="player_gesture_controls_title">מחוות מגע לשליטה בנגן</string>
<string name="player_gesture_controls_summary">שימוש במחוות כדי לשלוט בבהירות ובעצמת השמע של הנגן</string>
<string name="show_search_suggestions_title">הצעות חיפוש</string>
@ -61,8 +63,7 @@
<string name="background_player_playing_toast">מתנגן ברקע</string>
<string name="popup_playing_toast">מתנגן בחלון צף</string>
<string name="content">תוכן</string>
<string name="show_age_restricted_content_title">תוכן עם הגבלת גיל</string>
<string name="video_is_age_restricted">הצגת סרטונים עם הגבלת גיל. ניתן לשנות את זה בעתיד דרך ההגדרות.</string>
<string name="show_age_restricted_content_title">הצגת תוכן עם הגבלת גיל</string>
<string name="duration_live">חי</string>
<string name="downloads">הורדות</string>
<string name="downloads_title">הורדות</string>
@ -321,7 +322,10 @@
\n
\n1. לעבור לכתובת הזו: %1$s
\n2. להיכנס אם נתבקשת
\n3. ההורדה אמורה להתחיל (זה קובץ הייצוא)</string>
\n3. ללחוץ על „All data included”, ואז על „Deselect all”, לאחר מכן לבחור רק את „subscriptions” וללחוץ על „OK”
\n4. ללחוץ על „Next step” ואז על „Create export”
\n5. ללחוץ על כפתור ה־„Download” כשהוא מופיע ואז
\n6. לחלץ את קובץ ה־‎.json מתוך ה־zip (בדרך כלל תחת „YouTube and YouTube Music/subscriptions/subscriptions.json”) ולייבא אותו כאן.</string>
<string name="playback_tempo">קצב</string>
<string name="use_inexact_seek_title">שימוש בחיפוש מהיר ולא מדויק</string>
<string name="use_inexact_seek_summary">חיפוש גס מאפשר לנגן לחפש נקודת זמן מהר יותר, ברמת דיוק נמוכה יותר. חיפוש של 5, 15 או 25 שניות לא עובד עם ההגדרה הזאת.</string>
@ -610,7 +614,7 @@
<string name="songs">שירים</string>
<string name="restricted_video">סרטון זה מוגבל לצפייה מגיל מסוים.
\n
\nיש להפעיל את „תוכן עם הגבלת גיל” בהגדרות כדי לצפות בו.</string>
\nיש להפעיל את „%1$s” בהגדרות כדי לצפות בו.</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">כן, לרבות סרטונים שהפסקתי באמצע</string>
<string name="remove_watched_popup_warning">סרטונים שלאחר שצפית בהם מופיע לרשימת הנגינה יוסרו.
\nלהמשיך\? זאת פעולה בלתי הפיכה!</string>
@ -618,7 +622,7 @@
<string name="remove_watched_popup_title">להסיר סרטונים שנצפו\?</string>
<string name="show_original_time_ago_summary">הטקסט המקורי משירותים יופיע בפריטי התזרים</string>
<string name="show_original_time_ago_title">הצגת לפני כמה זמן מקורי על פריטים</string>
<string name="youtube_restricted_mode_enabled_title">מצב מוגבל של YouTube</string>
<string name="youtube_restricted_mode_enabled_title">הפעלת „מצב מוגבל של YouTube</string>
<string name="video_detail_by">מאת %s</string>
<string name="channel_created_by">נוצר ע״י %s</string>
<string name="detail_sub_channel_thumbnail_view_description">תמונה ממוזערת של הערוץ</string>
@ -653,4 +657,14 @@
<string name="notification_action_1_title">כפתור פעולה שני</string>
<string name="notification_scale_to_square_image_summary">לשנות את יחס התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 (עשוי לעוות את התמונה)</string>
<string name="notification_scale_to_square_image_title">שינוי גודל התצוגה הממוזערת ליחס תצוגה 1:1</string>
<string name="show_memory_leaks">הצגת דליפות זיכרון</string>
<string name="enqueued">נוסף לתור</string>
<string name="enqueue_stream">הוספה לתור</string>
<string name="clear_cookie_summary">לנקות עוגיות שנשמרות על ידי NewPipe בעת פתרון reCAPTCHA</string>
<string name="recaptcha_cookies_cleared">העוגיות של ה־reCAPTCHA נמחקו</string>
<string name="clear_cookie_title">פינוי עוגיות reCAPTCHA</string>
<string name="youtube_restricted_mode_enabled_summary">פלטפורמת YouTube מספקת „מצב מוגבל” שמסתיר תוכן שעשוי להתאים למבוגרים בלבד.</string>
<string name="show_age_restricted_content_summary">הצגת תוכן שעלול להיות בלתי הולם לילדים עקב מגבלת גיל (כגון 18+).</string>
<string name="notification_colorize_summary">לאפשר ל־Android להתאים את צבע ההתראה בהתאם לצבע העיקרי של התמונה הממוזערת (לא זמין בכל המכשירים)</string>
<string name="notification_colorize_title">צביעת ההתראה</string>
</resources>

View file

@ -51,7 +51,8 @@
<string name="item_deleted">Item हटा दिया गया है</string>
<string name="trending">फ़िलहाल चर्चा में है</string>
<string name="play_queue_audio_settings">ऑडियो सेटिंग</string>
<string name="main_bg_subtitle">शुरू करने के लिए खोज चिह्न दबाएं</string>
<string name="main_bg_subtitle">शुरू करने के लिए \"खोज\" चिह्न दबाएं
\n</string>
<string name="cancel">रद्द करें</string>
<string name="did_you_mean">क्या आप का मतलब %1$s है?</string>
<string name="share_dialog_title">शेयर करें</string>
@ -81,6 +82,8 @@
<string name="default_video_format_title">डिफ़ॉल्ट विडियो का फॉर्मेट</string>
<string name="theme_title">एप्प का नया रूप</string>
<string name="dark_theme_title">काला</string>
<string name="popup_remember_size_pos_title">विडियो पॉपअप की आकर और उसकी स्थति को याद रखे</string>
<string name="popup_remember_size_pos_summary">विडियो पॉपअप के पहले वाली आकर और उसकी स्थिति को याद रखे</string>
<string name="player_gesture_controls_title">प्लेयर इशारा नियंत्रण</string>
<string name="player_gesture_controls_summary">विडियो प्लेयर की ब्राइटनेस और ध्वनी को नियंत्रण के लिए फ़ोन में इशारो का प्रयोग करे</string>
<string name="show_search_suggestions_title">खोज के सुझाव देखे</string>
@ -105,7 +108,6 @@
<string name="popup_playing_append">पॉपअप प्लेयर की कतार पर</string>
<string name="content">विषयवस्तु</string>
<string name="show_age_restricted_content_title">उम्र प्रतिबंधित विषय वस्तु</string>
<string name="video_is_age_restricted">उम्र प्रतिबंदित विडियो है .इस प्रकार की विषयवस्तु को अनुमति देने के लिए सेटिंग से संभव है |</string>
<string name="duration_live">लाइव</string>
<string name="downloads">डाउनलोड</string>
<string name="downloads_title">डाउनलोड</string>

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