Merge branch 'dev' into pr2335

pull/2335/head
Stypox 7 months ago
commit d5cfcb28fc
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
  1. 8
      .github/workflows/ci.yml
  2. 2
      README.es.md
  3. 2
      README.ja.md
  4. 2
      README.ko.md
  5. 2
      README.md
  6. 2
      README.pt_BR.md
  7. 2
      README.ro.md
  8. 2
      README.so.md
  9. 2
      README.tr.md
  10. 149
      README.zh_TW.md
  11. 2
      app/build.gradle
  12. 91
      app/src/androidTest/java/org/schabi/newpipe/local/history/HistoryRecordManagerTest.kt
  13. 5
      app/src/androidTest/java/org/schabi/newpipe/local/playlist/LocalPlaylistManagerTest.kt
  14. 79
      app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.java
  15. 40
      app/src/main/java/org/schabi/newpipe/database/history/model/SearchHistoryEntry.kt
  16. 20
      app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
  17. 5
      app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
  18. 2
      app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt
  19. 1
      app/src/main/java/org/schabi/newpipe/player/Player.java
  20. 12
      app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
  21. 57
      app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
  22. 1
      app/src/main/res/layout-large-land/fragment_video_detail.xml
  23. 1
      app/src/main/res/layout/fragment_video_detail.xml
  24. 1
      app/src/main/res/layout/list_channel_item.xml
  25. 1
      app/src/main/res/layout/list_channel_mini_item.xml
  26. 1
      app/src/main/res/layout/list_comments_item.xml
  27. 1
      app/src/main/res/layout/list_comments_mini_item.xml
  28. 1
      app/src/main/res/layout/list_playlist_grid_item.xml
  29. 1
      app/src/main/res/layout/list_playlist_item.xml
  30. 1
      app/src/main/res/layout/list_playlist_mini_item.xml
  31. 1
      app/src/main/res/layout/list_stream_grid_item.xml
  32. 1
      app/src/main/res/layout/list_stream_item.xml
  33. 1
      app/src/main/res/layout/list_stream_mini_item.xml
  34. 1
      app/src/main/res/layout/list_stream_playlist_grid_item.xml
  35. 1
      app/src/main/res/layout/list_stream_playlist_item.xml
  36. 1
      app/src/main/res/layout/picker_subscription_item.xml
  37. 1
      app/src/main/res/layout/play_queue_item.xml
  38. 1
      app/src/main/res/layout/select_channel_item.xml
  39. BIN
      app/src/main/res/mipmap-xhdpi/newpipe_tv_banner.png
  40. 1
      app/src/main/res/values-ar/strings.xml
  41. 1
      app/src/main/res/values-b+ast/strings.xml
  42. 1
      app/src/main/res/values-b+uz+Latn/strings.xml
  43. 1
      app/src/main/res/values-b+zh+HANS+CN/strings.xml
  44. 1
      app/src/main/res/values-be/strings.xml
  45. 1
      app/src/main/res/values-bg/strings.xml
  46. 1
      app/src/main/res/values-bn-rBD/strings.xml
  47. 1
      app/src/main/res/values-bn-rIN/strings.xml
  48. 1
      app/src/main/res/values-bn/strings.xml
  49. 1
      app/src/main/res/values-ca/strings.xml
  50. 1
      app/src/main/res/values-ckb/strings.xml
  51. 1
      app/src/main/res/values-cs/strings.xml
  52. 1
      app/src/main/res/values-da/strings.xml
  53. 1
      app/src/main/res/values-de/strings.xml
  54. 1
      app/src/main/res/values-el/strings.xml
  55. 1
      app/src/main/res/values-eo/strings.xml
  56. 1
      app/src/main/res/values-es/strings.xml
  57. 1
      app/src/main/res/values-et/strings.xml
  58. 1
      app/src/main/res/values-eu/strings.xml
  59. 1
      app/src/main/res/values-fa/strings.xml
  60. 1
      app/src/main/res/values-fi/strings.xml
  61. 1
      app/src/main/res/values-fr/strings.xml
  62. 1
      app/src/main/res/values-gl/strings.xml
  63. 1
      app/src/main/res/values-he/strings.xml
  64. 1
      app/src/main/res/values-hi/strings.xml
  65. 1
      app/src/main/res/values-hr/strings.xml
  66. 1
      app/src/main/res/values-hu/strings.xml
  67. 1
      app/src/main/res/values-in/strings.xml
  68. 1
      app/src/main/res/values-it/strings.xml
  69. 1
      app/src/main/res/values-ja/strings.xml
  70. 1
      app/src/main/res/values-kmr/strings.xml
  71. 1
      app/src/main/res/values-ko/strings.xml
  72. 1
      app/src/main/res/values-ku/strings.xml
  73. 1
      app/src/main/res/values-lt/strings.xml
  74. 1
      app/src/main/res/values-lv/strings.xml
  75. 1
      app/src/main/res/values-mk/strings.xml
  76. 1
      app/src/main/res/values-ml/strings.xml
  77. 1
      app/src/main/res/values-ms/strings.xml
  78. 1
      app/src/main/res/values-nb-rNO/strings.xml
  79. 1
      app/src/main/res/values-ne/strings.xml
  80. 1
      app/src/main/res/values-nl-rBE/strings.xml
  81. 1
      app/src/main/res/values-nl/strings.xml
  82. 1
      app/src/main/res/values-pa/strings.xml
  83. 1
      app/src/main/res/values-pl/strings.xml
  84. 1
      app/src/main/res/values-pt-rBR/strings.xml
  85. 1
      app/src/main/res/values-pt-rPT/strings.xml
  86. 1
      app/src/main/res/values-pt/strings.xml
  87. 1
      app/src/main/res/values-ro/strings.xml
  88. 1
      app/src/main/res/values-ru/strings.xml
  89. 1
      app/src/main/res/values-sc/strings.xml
  90. 1
      app/src/main/res/values-sk/strings.xml
  91. 1
      app/src/main/res/values-sl/strings.xml
  92. 1
      app/src/main/res/values-so/strings.xml
  93. 1
      app/src/main/res/values-sq/strings.xml
  94. 1
      app/src/main/res/values-sr/strings.xml
  95. 1
      app/src/main/res/values-sv/strings.xml
  96. 1
      app/src/main/res/values-ta/strings.xml
  97. 1
      app/src/main/res/values-te/strings.xml
  98. 1
      app/src/main/res/values-th/strings.xml
  99. 1
      app/src/main/res/values-tr/strings.xml
  100. 1
      app/src/main/res/values-uk/strings.xml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -52,6 +52,7 @@ jobs:
test-android:
# macos has hardware acceleration. See android-emulator-runner action
runs-on: macos-latest
timeout-minutes: 20
strategy:
matrix:
# api-level 19 is min sdk, but throws errors related to desugaring
@ -73,6 +74,13 @@ jobs:
# workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
emulator-build: 7425822
script: ./gradlew connectedCheck --stacktrace
- name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553
uses: actions/upload-artifact@v2
if: failure()
with:
name: android-test-report-api${{ matrix.api-level }}
path: app/build/reports/androidTests/connected/**
sonar:
runs-on: ubuntu-latest

@ -18,7 +18,7 @@
<p align="center"><a href="https://newpipe.net">Sitio Web</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">Preguntas Frecuentes</a> &bull; <a href="https://newpipe.net/press/">Prensa</a></p>
<hr>
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Lea esto en otros idiomas: [English](README.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>AVISO: ESTA ES UNA VERSIÓN BETA, POR LO TANTO, PUEDE ENCONTRAR BUGS. SI ENCUENTRA UNO ABRA UN ISSUE A TRAVÉS DE NUESTRO REPOSITORIO DE GITHUB.</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">ウェブサイト</a> &bull; <a href="https://newpipe.net/blog/">ブログ</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">ニュース</a></p>
<hr>
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md)。*
*他の言語で読む: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md)。*
<b>注意: これはベータ版のため、バグが発生する可能性があります。もしバグが発生した場合、GitHub のリポジトリで Issue を開いてください。</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>경고: 이 버전은 베타 버전이므로, 버그가 발생할 수도 있습니다. 만약 버그가 발생하였다면, 우리의 GITHUB 저장소에서 ISSUE를 열람하여 주십시오.</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b>

@ -18,7 +18,7 @@
<p align="center"><a href="https://newpipe.net">Site</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Press</a></p>
<hr>
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Read this in other languages: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>AVISO: ESTA É UMA VERSÃO BETA, PORTANTO, VOCÊ PODE ENCONTRAR BUGS. ENCONTROU ALGUM, ABRA UM ISSUE ATRAVÉS DO NOSSO REPOSITÓRIO GITHUB.</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">Website</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">Presă</a></p>
<hr>
*Citiţi în alte limbi: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Citiţi în alte limbi: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>Atenţionare: ACEASTA ESTE O VERSIUNE BETA, AŞA CĂ S-AR PUTE SĂ ÎNTÂLNIŢI ERORI. DACĂ SE ÎNTÂMPLĂ ACEST LUCRU, DESCHIDEŢI UN ISSUE PRIN REPSITORY-UL NOSTRU GITHUB.</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">Website-ka</a> &bull; <a href="https://newpipe.net/blog/">Maqaalada</a> &bull; <a href="https://newpipe.net/FAQ/">Su'aalaha Aalaa La-iswaydiiyo</a> &bull; <a href="https://newpipe.net/press/">Warbaahinta</a></p>
<hr>
*Ku akhri luuqad kale: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Ku akhri luuqad kale: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>DIGNIIN: MIDKAN, NOOCA APP-KA EE HADDA WALI TIJAABO AYUU KU JIRAA, SIDAA DARTEED CILLADO AYAAD LA KULMI KARTAA. HADAAD LA KULANTO, KA FUR ARIN SHARAXAYA QAYBTANADA ARRIMAHA EE GITHUB-KA.</b>

@ -17,7 +17,7 @@
<p align="center"><a href="https://newpipe.net">Web sitesi</a> &bull; <a href="https://newpipe.net/blog/">Blog</a> &bull; <a href="https://newpipe.net/FAQ/">SSS</a> &bull; <a href="https://newpipe.net/press/">Basın</a></p>
<hr>
*Bu sayfayı diğer dillerde okuyun: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md).*
*Bu sayfayı diğer dillerde okuyun: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md).*
<b>UYARI: BU SÜRÜM BETA SÜRÜMÜDÜR, BU NEDENLE HATALARLA KARŞILAŞABİLİRSİNİZ. HATA BULURSANIZ BU GITHUB DEPOSUNDA BUNU BİLDİRİN.</b>

@ -0,0 +1,149 @@
<p align="center"><a href="https://newpipe.net"><img src="assets/new_pipe_icon_5.png" width="150"></a></p>
<h2 align="center"><b>NewPipe</b></h2>
<h4 align="center">輕巧的 Android 串流前端</h4>
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-zh-tw.svg" alt="Get it on F-Droid" height=80/></a></p>
<p align="center">
<a href="https://github.com/TeamNewPipe/NewPipe/releases" alt="GitHub 發佈"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" ></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="授權條款: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg"></a>
<a href="https://github.com/TeamNewPipe/NewPipe/actions" alt="建置狀態"><img src="https://github.com/TeamNewPipe/NewPipe/workflows/CI/badge.svg?branch=dev&event=push"></a>
<a href="https://hosted.weblate.org/engage/newpipe/" alt="翻譯進度"><img src="https://hosted.weblate.org/widgets/newpipe/-/svg-badge.svg"></a>
<a href="https://web.libera.chat/#newpipe" alt="IRC 頻道: #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">截圖</a> &bull; <a href="#description">說明</a> &bull; <a href="#features">功能</a> &bull; <a href="#installation-and-updates">安裝與更新</a> &bull; <a href="#contribution">貢獻</a> &bull; <a href="#donate">捐款</a> &bull; <a href="#license">授權憑證</a></p>
<p align="center"><a href="https://newpipe.net">網站</a> &bull; <a href="https://newpipe.net/blog/">部落格</a> &bull; <a href="https://newpipe.net/FAQ/">FAQ</a> &bull; <a href="https://newpipe.net/press/">媒體</a></p>
<hr>
*其他語言: [English](README.md), [Español](README.es.md), [한국어](README.ko.md), [Soomaali](README.so.md), [Português Brasil](README.pt_BR.md), [日本語](README.ja.md), [Română](README.ro.md), [Türkçe](README.tr.md), [正體中文](README.zh_TW.md)*
<b>警告:這是測試版本,可能會發生錯誤。如果遇到錯誤,請在我們的 GITHUB REPO 開 ISSUE 回報。</b>
<b>將 NEWPIPE 或其任何分支上傳至 GOOGLE PLAY 商店違反了他們的使用者合約。</b>
<span id="screenshots"></span>
## 截圖
[<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)
<span id="description"></span>
## 說明
NewPipe 不使用任何 Google 架構的函式庫或 Youtube API。因為只解析網站來取得必要資訊,此軟體可以在沒有安裝 Google 服務的裝置上使用。此外,使用 NewPipe 不需要 YouTube 帳號。NewPipe 是個 copyleft 自由軟體。
<span id="features"></span>
### 功能
* 搜索影片
* 無須登入
* 顯示影片的基本資訊
* 播放 Youtube 影片
* 收聽 Youtube 影片
* 彈出模式(懸浮模式)
* 選擇串流播放器來播放影片
* 下載影片
* 只下載音訊
* 在 Kodi 開啟影片
* 顯示上/下一部影片
* 搜尋特定語言的影片
* 播放/屏蔽有年齡限的制內容
* 顯示頻道資訊
* 搜索頻道
* 觀看頻道影片
* 支援 Orbot/Tor (目前未直接實裝)
* 支援 1080p/2K/4K
* 觀看歷史
* 訂閱頻道
* 搜尋歷史
* 搜索/播放播放清單
* 將播放清單加入待播清單
* 將影片加入待播清單
* 末端播放清單
* 字幕
* 支援直播
* 顯示評論
### 支援的網站
NewPipe 支援多種服務。我們的[使用文件](https://teamnewpipe.github.io/documentation/)有如何增加新服務與下載器的說明。想新增服務的話,請聯絡我們。目前支援的服務有:
* YouTube
* SoundCloud \[測試\]
* media.ccc.de \[測試\]
* PeerTube instances \[測試\]
* Bandcamp \[測試\]
<!-- Hidden span to keep old links compatible. -->
<span id="updates"></span>
<span id="installation-and-updates"></span>
## 安裝與更新
你可以用以下的任何方法安裝 NewPipe:
1. 將我們的 repo 加至 F-Droid 再從那邊安裝。詳細的說明在此:https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
2. 從 [Github 發布](https://github.com/TeamNewPipe/NewPipe/releases) 下載 APK 再安裝。
3. 自 F-Droid 更新。這是取得更新最慢的方式,因為 F-Droid 要檢測到更新、建置 APK 、簽署後才會將更新推送給使用者。
4. 自己建置 APK。這是取得更新最快的方法,但因為這也比較複雜,所以我們推薦使用其他方法。
對一般的使用者我們推薦方法一。使用方法一或二安裝的 APK 互相相容,但都不相容於方法三。因為前兩者的簽章使用相同的(我們的)金鑰,與後者(使用 F-Droid 的金鑰)的不同。使用方法四建置除錯 APK 完全避免了金鑰的問題。簽章金鑰能幫助使用者避免安裝惡意的更新。
若你想更換安裝來源(如果 NewPipe 的核心機能壞掉而 F-Droid 又還沒有最新的更新),我們推薦以下的步驟:
1. 在 設定 > 內容 > 匯出資料庫 備份資料以保留歷史、訂閱與播放清單
2. 移除 NewPipe
3. 從新的來源下載 APK 並安裝
4. 在 設定 > 內容 > 匯入資料庫 匯入在步驟 1 備份的資料
<span id="contribution"></span>
## 貢獻
若你有任何想法、翻譯、設計、整理原始碼或大範圍的原始碼改動,我們歡迎任何幫助。
若你想參與,請閱讀[貢獻須知(英文)](.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>
<span id="donate"></span>
## 捐款
若你喜歡 NewPipe 我們歡迎捐款。你可以使用 bitcoin ,也能在 Bountysource 或 Liberapay 上捐款。 更多關於捐款資訊,請見我們的[網站](https://newpipe.net/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>
## 隱私權政策
NewPipe 專案旨在提供私人與匿名的網路媒體使用體驗。
因此,此軟體不在沒有你的同意下收集任何資料。NewPipe 的隱私權政策說明了送出錯誤報告與在我們的部落格上留言時何種資料會被傳輸或儲存。你可以在[這裡](https://newpipe.net/legal/privacy/)找到此文件。
<span id="license"></span>
## 授權條款
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
NewPipe 是自由軟體:可以任意使用、研究、分享或更改。在自由軟體基金會發布的[ GPL 通用公眾授權條款](第三或更新的版本)下可以重新散佈與/或修改。

@ -113,6 +113,7 @@ ext {
leakCanaryVersion = '2.5'
stethoVersion = '1.6.0'
mockitoVersion = '4.0.0'
assertJVersion = '3.22.0'
}
configurations {
@ -293,6 +294,7 @@ dependencies {
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
androidTestImplementation "org.assertj:assertj-core:${assertJVersion}"
}
static String getGitWorkingBranch() {

@ -1,19 +1,19 @@
package org.schabi.newpipe.local.history
import androidx.test.core.app.ApplicationProvider
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.history.model.SearchHistoryEntry
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.util.concurrent.TimeUnit
import java.time.ZoneOffset
class HistoryRecordManagerTest {
@ -23,9 +23,6 @@ class HistoryRecordManagerTest {
@get:Rule
val trampolineScheduler = TrampolineSchedulerRule()
@get:Rule
val timeout = Timeout(1, TimeUnit.SECONDS)
@Before
fun setup() {
database = TestDatabase.createReplacingNewPipeDatabase()
@ -45,59 +42,59 @@ class HistoryRecordManagerTest {
// that the number of Lists it returns is exactly 1, we can only check if the first List is
// correct. Why on earth has a Flowable been used instead of a Single for getAll()?!?
val entities = database.searchHistoryDAO().all.blockingFirst()
assertEquals(1, entities.size)
assertEquals(1, entities[0].id)
assertEquals(0, entities[0].serviceId)
assertEquals("Hello", entities[0].search)
assertThat(entities).hasSize(1)
assertThat(entities[0].id).isEqualTo(1)
assertThat(entities[0].serviceId).isEqualTo(0)
assertThat(entities[0].search).isEqualTo("Hello")
}
@Test
fun deleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(OffsetDateTime.now(), 0, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 2, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 1, "B"),
SearchHistoryEntry(OffsetDateTime.now(), 0, "B"),
SearchHistoryEntry(time.minusSeconds(1), 0, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "A"),
SearchHistoryEntry(time.minusSeconds(3), 1, "B"),
SearchHistoryEntry(time.minusSeconds(4), 0, "B"),
)
// make sure all 4 were inserted
database.searchHistoryDAO().insertAll(entries)
assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size)
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
// try to delete only "A" entries, "B" entries should be untouched
manager.deleteSearchHistory("A").test().await().assertValue(2)
val entities = database.searchHistoryDAO().all.blockingFirst()
assertEquals(2, entities.size)
assertTrue(entries[2].hasEqualValues(entities[0]))
assertTrue(entries[3].hasEqualValues(entities[1]))
assertThat(entities).hasSize(2)
assertThat(entities).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
.containsExactly(*entries.subList(2, 4).toTypedArray())
// assert that nothing happens if we delete a search query that does exist in the db
manager.deleteSearchHistory("A").test().await().assertValue(0)
val entities2 = database.searchHistoryDAO().all.blockingFirst()
assertEquals(2, entities2.size)
assertTrue(entries[2].hasEqualValues(entities2[0]))
assertTrue(entries[3].hasEqualValues(entities2[1]))
assertThat(entities2).hasSize(2)
assertThat(entities2).usingElementComparator { o1, o2 -> if (o1.hasEqualValues(o2)) 0 else 1 }
.containsExactly(*entries.subList(2, 4).toTypedArray())
// delete all remaining entries
manager.deleteSearchHistory("B").test().await().assertValue(2)
assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size)
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
}
@Test
fun deleteCompleteSearchHistory() {
val entries = listOf(
SearchHistoryEntry(OffsetDateTime.now(), 1, "A"),
SearchHistoryEntry(OffsetDateTime.now(), 2, "B"),
SearchHistoryEntry(OffsetDateTime.now(), 0, "C"),
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
SearchHistoryEntry(time.minusSeconds(2), 2, "B"),
SearchHistoryEntry(time.minusSeconds(3), 0, "C"),
)
// make sure all 3 were inserted
database.searchHistoryDAO().insertAll(entries)
assertEquals(entries.size, database.searchHistoryDAO().all.blockingFirst().size)
assertThat(database.searchHistoryDAO().all.blockingFirst()).hasSameSizeAs(entries)
// should remove everything
manager.deleteCompleteSearchHistory().test().await().assertValue(entries.size)
assertEquals(0, database.searchHistoryDAO().all.blockingFirst().size)
assertThat(database.searchHistoryDAO().all.blockingFirst()).isEmpty()
}
@Test
@ -111,11 +108,12 @@ class HistoryRecordManagerTest {
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("", 6, 4).blockingFirst()
assertEquals(4, searches.size)
assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places)
assertEquals(RELATED_SEARCHES_ENTRIES[4].search, searches[1]) // B
assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[2]) // AA
assertEquals(RELATED_SEARCHES_ENTRIES[2].search, searches[3]) // BA
assertThat(searches).containsExactly(
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[4].search, // B
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[2].search, // BA
)
}
@Test
@ -129,25 +127,28 @@ class HistoryRecordManagerTest {
// make sure correct number of searches is returned and in correct order
val searches = manager.getRelatedSearches("A", 3, 5).blockingFirst()
assertEquals(3, searches.size)
assertEquals(RELATED_SEARCHES_ENTRIES[6].search, searches[0]) // A (even if in two places)
assertEquals(RELATED_SEARCHES_ENTRIES[5].search, searches[1]) // AA
assertEquals(RELATED_SEARCHES_ENTRIES[1].search, searches[2]) // BA
assertThat(searches).containsExactly(
RELATED_SEARCHES_ENTRIES[6].search, // A (even if in two places)
RELATED_SEARCHES_ENTRIES[5].search, // AA
RELATED_SEARCHES_ENTRIES[1].search, // BA
)
// also make sure that the string comparison is case insensitive
val searches2 = manager.getRelatedSearches("a", 3, 5).blockingFirst()
assertEquals(searches, searches2)
assertThat(searches).isEqualTo(searches2)
}
companion object {
val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(7), 2, "AC"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(6), 0, "ABC"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(5), 1, "BA"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(4), 3, "A"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(2), 0, "B"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(3), 2, "AA"),
SearchHistoryEntry(OffsetDateTime.now().minusSeconds(1), 1, "A"),
private val time = OffsetDateTime.of(LocalDateTime.of(2000, 1, 1, 1, 1), ZoneOffset.UTC)
private val RELATED_SEARCHES_ENTRIES = listOf(
SearchHistoryEntry(time.minusSeconds(7), 2, "AC"),
SearchHistoryEntry(time.minusSeconds(6), 0, "ABC"),
SearchHistoryEntry(time.minusSeconds(5), 1, "BA"),
SearchHistoryEntry(time.minusSeconds(4), 3, "A"),
SearchHistoryEntry(time.minusSeconds(2), 0, "B"),
SearchHistoryEntry(time.minusSeconds(3), 2, "AA"),
SearchHistoryEntry(time.minusSeconds(1), 1, "A"),
)
}
}

@ -4,13 +4,11 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.Timeout
import org.schabi.newpipe.database.AppDatabase
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.testUtil.TestDatabase
import org.schabi.newpipe.testUtil.TrampolineSchedulerRule
import java.util.concurrent.TimeUnit
class LocalPlaylistManagerTest {
@ -20,9 +18,6 @@ class LocalPlaylistManagerTest {
@get:Rule
val trampolineScheduler = TrampolineSchedulerRule()
@get:Rule
val timeout = Timeout(1, TimeUnit.SECONDS)
@Before
fun setup() {
database = TestDatabase.createReplacingNewPipeDatabase()

@ -1,79 +0,0 @@
package org.schabi.newpipe.database.history.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.time.OffsetDateTime;
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
@Entity(tableName = SearchHistoryEntry.TABLE_NAME,
indices = {@Index(value = SEARCH)})
public class SearchHistoryEntry {
public static final String ID = "id";
public static final String TABLE_NAME = "search_history";
public static final String SERVICE_ID = "service_id";
public static final String CREATION_DATE = "creation_date";
public static final String SEARCH = "search";
@ColumnInfo(name = ID)
@PrimaryKey(autoGenerate = true)
private long id;
@ColumnInfo(name = CREATION_DATE)
private OffsetDateTime creationDate;
@ColumnInfo(name = SERVICE_ID)
private int serviceId;
@ColumnInfo(name = SEARCH)
private String search;
public SearchHistoryEntry(final OffsetDateTime creationDate, final int serviceId,
final String search) {
this.serviceId = serviceId;
this.creationDate = creationDate;
this.search = search;
}
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
public OffsetDateTime getCreationDate() {
return creationDate;
}
public void setCreationDate(final OffsetDateTime creationDate) {
this.creationDate = creationDate;
}
public int getServiceId() {
return serviceId;
}
public void setServiceId(final int serviceId) {
this.serviceId = serviceId;
}
public String getSearch() {
return search;
}
public void setSearch(final String search) {
this.search = search;
}
@Ignore
public boolean hasEqualValues(final SearchHistoryEntry otherEntry) {
return getServiceId() == otherEntry.getServiceId()
&& getSearch().equals(otherEntry.getSearch());
}
}

@ -0,0 +1,40 @@
package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import java.time.OffsetDateTime
@Entity(
tableName = SearchHistoryEntry.TABLE_NAME,
indices = [Index(value = [SearchHistoryEntry.SEARCH])]
)
data class SearchHistoryEntry(
@field:ColumnInfo(name = CREATION_DATE) var creationDate: OffsetDateTime?,
@field:ColumnInfo(
name = SERVICE_ID
) var serviceId: Int,
@field:ColumnInfo(name = SEARCH) var search: String?
) {
@ColumnInfo(name = ID)
@PrimaryKey(autoGenerate = true)
var id: Long = 0
@Ignore
fun hasEqualValues(otherEntry: SearchHistoryEntry): Boolean {
return (
serviceId == otherEntry.serviceId &&
search == otherEntry.search
)
}
companion object {
const val ID = "id"
const val TABLE_NAME = "search_history"
const val SERVICE_ID = "service_id"
const val CREATION_DATE = "creation_date"
const val SEARCH = "search"
}
}

@ -1098,6 +1098,11 @@ public final class VideoDetailFragment
toggleFullscreenIfInFullscreenMode();
if (isPlayerAvailable()) {
// FIXME Workaround #7427
player.setRecovery();
}
if (!useExternalAudioPlayer) {
openNormalBackgroundPlayer(append);
} else {
@ -1114,6 +1119,9 @@ public final class VideoDetailFragment
// See UI changes while remote playQueue changes
if (!isPlayerAvailable()) {
playerHolder.startService(false, this);
} else {
// FIXME Workaround #7427
player.setRecovery();
}
toggleFullscreenIfInFullscreenMode();
@ -2208,12 +2216,20 @@ public final class VideoDetailFragment
mainFragment.setDescendantFocusability(afterDescendants);
toolbar.setDescendantFocusability(afterDescendants);
((ViewGroup) requireView()).setDescendantFocusability(blockDescendants);
mainFragment.requestFocus();
// Only focus the mainFragment if the mainFragment (e.g. search-results)
// or the toolbar (e.g. Textfield for search) don't have focus.
// This was done to fix problems with the keyboard input, see also #7490
if (!mainFragment.hasFocus() && !toolbar.hasFocus()) {
mainFragment.requestFocus();
}
} else {
mainFragment.setDescendantFocusability(blockDescendants);
toolbar.setDescendantFocusability(blockDescendants);
((ViewGroup) requireView()).setDescendantFocusability(afterDescendants);
binding.detailThumbnailRootLayout.requestFocus();
// Only focus the player if it not already has focus
if (!binding.getRoot().hasFocus()) {
binding.detailThumbnailRootLayout.requestFocus();
}
}
}

@ -268,7 +268,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
ShareUtils.openUrlInBrowser(requireContext(), url);
break;
case R.id.menu_item_share:
ShareUtils.shareText(requireContext(), name, url, currentInfo.getThumbnailUrl());
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, url,
currentInfo.getThumbnailUrl());
}
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();

@ -271,7 +271,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
override fun onDestroyView() {
// Ensure that all animations are canceled
feedBinding.newItemsLoadedButton?.clearAnimation()
tryGetNewItemsLoadedButton()?.clearAnimation()
feedBinding.itemsList.adapter = null
_feedBinding = null

@ -635,6 +635,7 @@ public final class Player implements
final boolean isMuted = intent.getBooleanExtra(IS_MUTED, isMuted());
/*
* TODO As seen in #7427 this does not work:
* There are 3 situations when playback shouldn't be started from scratch (zero timestamp):
* 1. User pressed on a timestamp link and the same video should be rewound to the timestamp
* 2. User changed a player from, for example. main to popup, or from audio to main, etc

@ -157,9 +157,8 @@ public final class NavigationHelper {
return;
}
if (PlayerHolder.getInstance().getType() != PlayerType.POPUP) {
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
}
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal());
ContextCompat.startForegroundService(context, intent);
@ -168,12 +167,7 @@ public final class NavigationHelper {
public static void playOnBackgroundPlayer(final Context context,
final PlayQueue queue,
final boolean resumePlayback) {
Toast.makeText(
context,
PlayerHolder.getInstance().getType() == PlayerType.AUDIO
? R.string.background_player_already_playing_toast
: R.string.background_player_playing_toast,
Toast.LENGTH_SHORT)
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
.show();
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);

@ -2,6 +2,7 @@ package org.schabi.newpipe.util;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
@ -21,6 +22,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
@ -63,20 +65,24 @@ public enum StreamDialogEntry {
* Info: Add this entry within showStreamDialog.
*/
enqueue(R.string.enqueue_stream, (fragment, item) -> {
NavigationHelper.enqueueOnPlayer(fragment.getContext(), new SinglePlayQueue(item));
fetchItemInfoIfSparse(fragment, item, fullItem ->
NavigationHelper.enqueueOnPlayer(fragment.getContext(), fullItem));
}),
enqueue_next(R.string.enqueue_next_stream, (fragment, item) -> {
NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), new SinglePlayQueue(item));
fetchItemInfoIfSparse(fragment, item, fullItem ->
NavigationHelper.enqueueNextOnPlayer(fragment.getContext(), fullItem));
}),
start_here_on_background(R.string.start_here_on_background, (fragment, item) ->
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(),
new SinglePlayQueue(item), true)),
start_here_on_background(R.string.start_here_on_background, (fragment, item) -> {
fetchItemInfoIfSparse(fragment, item, fullItem ->
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), fullItem, true));
}),
start_here_on_popup(R.string.start_here_on_popup, (fragment, item) ->
NavigationHelper.playOnPopupPlayer(fragment.getContext(),
new SinglePlayQueue(item), true)),
start_here_on_popup(R.string.start_here_on_popup, (fragment, item) -> {
fetchItemInfoIfSparse(fragment, item, fullItem ->
NavigationHelper.playOnPopupPlayer(fragment.getContext(), fullItem, true));
}),
set_as_playlist_thumbnail(R.string.set_as_playlist_thumbnail, (fragment, item) -> {
}), // has to be set manually
@ -218,4 +224,39 @@ public enum StreamDialogEntry {
fragment.requireActivity().getSupportFragmentManager(),
item.getServiceId(), uploaderUrl, item.getUploaderName());
}
/////////////////////////////////////////////
// helper functions //
/////////////////////////////////////////////
private static void fetchItemInfoIfSparse(final Fragment fragment,
final StreamInfoItem item,
final Consumer<SinglePlayQueue> callback) {
if (!(item.getStreamType() == StreamType.LIVE_STREAM
|| item.getStreamType() == StreamType.AUDIO_LIVE_STREAM)
&& item.getDuration() < 0) {
// Sparse item: fetched by fast fetch
ExtractorHelper.getStreamInfo(
item.getServiceId(),
item.getUrl(),
false
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
final HistoryRecordManager recordManager =
new HistoryRecordManager(fragment.getContext());
recordManager.saveStreamState(result, 0)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> Log.e("StreamDialogEntry",
throwable.toString()))
.subscribe();
callback.accept(new SinglePlayQueue(result));
}, throwable -> Log.e("StreamDialogEntry", throwable.toString()));
} else {
callback.accept(new SinglePlayQueue(item));
}
}
}

@ -651,7 +651,6 @@
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:background="@color/transparent_background_color"
android:contentDescription="@string/list_thumbnail_view_description"
android:gravity="center_vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"

@ -626,7 +626,6 @@
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:background="@color/transparent_background_color"
android:contentDescription="@string/list_thumbnail_view_description"
android:gravity="center_vertical"
android:paddingLeft="@dimen/video_item_search_padding"
android:paddingRight="@dimen/video_item_search_padding"

@ -63,7 +63,6 @@
android:layout_width="@dimen/video_item_search_thumbnail_image_width"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemTitleView"

@ -15,7 +15,6 @@
android:layout_height="42dp"
android:layout_centerVertical="true"
android:layout_marginRight="12dp"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy_channel_item"
tools:ignore="RtlHardcoded" />

@ -18,7 +18,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:focusable="false"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />

@ -16,7 +16,6 @@
android:layout_height="42dp"
android:layout_centerVertical="true"
android:layout_marginRight="12dp"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy_channel_item"
tools:ignore="RtlHardcoded" />

@ -17,7 +17,6 @@
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail_playlist"
tools:ignore="RtlHardcoded" />

@ -18,7 +18,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail_playlist"
tools:ignore="RtlHardcoded" />

@ -18,7 +18,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail_playlist"
tools:ignore="RtlHardcoded" />

@ -14,7 +14,6 @@
android:id="@+id/itemThumbnailView"
android:layout_width="@dimen/video_item_grid_thumbnail_image_width"
android:layout_height="@dimen/video_item_grid_thumbnail_image_height"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
app:layout_constraintEnd_toEndOf="parent"

@ -14,7 +14,6 @@
android:id="@+id/itemThumbnailView"
android:layout_width="@dimen/video_item_search_thumbnail_image_width"
android:layout_height="@dimen/video_item_search_thumbnail_image_height"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
app:layout_constraintBottom_toTopOf="@+id/itemProgressView"

@ -17,7 +17,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="RtlHardcoded" />

@ -17,7 +17,6 @@
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="RtlHardcoded" />

@ -18,7 +18,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
tools:ignore="RtlHardcoded" />

@ -21,7 +21,6 @@
android:id="@+id/thumbnail_view"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/list_thumbnail_view_description"
tools:src="@drawable/buddy_channel_item" />
<org.schabi.newpipe.views.NewPipeTextView

@ -17,7 +17,6 @@
android:layout_marginStart="@dimen/video_item_search_image_right_margin"
android:layout_marginTop="@dimen/video_item_search_image_right_margin"
android:layout_marginBottom="@dimen/video_item_search_image_right_margin"
android:contentDescription="@string/list_thumbnail_view_description"
android:scaleType="centerCrop"
android:src="@drawable/dummy_thumbnail"
app:layout_constraintBottom_toBottomOf="parent"

@ -17,7 +17,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="5dp"
android:contentDescription="@string/list_thumbnail_view_description"
android:src="@drawable/buddy"
tools:ignore="RtlHardcoded" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -21,7 +21,6 @@
<string name="install">تثبيت</string>
<string name="kore_not_found">تطبيق Kore غير موجود. هل تريد تثبيته؟</string>
<string name="light_theme_title">فاتح</string>
<string name="list_thumbnail_view_description">صور معاينة الفيديو</string>
<string name="network_error">خطأ في الشبكة</string>
<string name="no_player_found">لم يتم العثور على مشغل بث. تثبيت VLC؟</string>
<string name="open_in_browser">افتح في المتصفح</string>

@ -458,7 +458,6 @@
<string name="detail_drag_description">Arrastra pa reordenar</string>
<string name="detail_uploader_thumbnail_view_description">Avatar del xubidor</string>
<string name="detail_thumbnail_view_description">Reproducción d\'un videu, duración:</string>
<string name="list_thumbnail_view_description">Miniatura del videu</string>
<string name="your_comment">Un comentariu (n\'inglés):</string>
<string name="what_happened_headline">Qué pasó:</string>
<string name="error_snackbar_action">Informar</string>

@ -288,7 +288,6 @@
<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>

@ -155,7 +155,6 @@
<string name="info_labels">详情:\\n请求:\\n内容语言:\\n内容国家:\\n客户端语言:\\n服务:\\nGMT时间:\\n包名:\\n版本:\\n操作系统版本:</string>
<string name="your_comment">您的附加说明(请用英文):</string>
<string name="error_details_headline">详细信息:</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="short_billion">十亿</string>

@ -153,7 +153,6 @@
<string name="info_labels">Што:\\nЗапыт:\\nМова кантэнту:\\nСэрвіс:\\nЧас па Грынвічы:\\nПакет:\\nВерсія:\\nВерсія АС:</string>
<string name="your_comment">Ваш каментар (English):</string>
<string name="error_details_headline">Падрабязнасці:</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">Спадабалася</string>

@ -213,7 +213,6 @@
<string name="video_streams_empty">Не са намерени видео стриймове</string>
<string name="audio_streams_empty">Не са намерени аудио стриймове</string>
<string name="info_labels">Какво:\\nЗаявка:\\nЕзик на съдържанието:\\nУслуга:\\nВреме по GMT:\\nПакет:\\nВерсия:\\nОС версия:</string>
<string name="list_thumbnail_view_description">Миниатюра на видео</string>
<string name="detail_drag_description">Пренареди чрез плъзгане</string>
<string name="start">Начало</string>
<string name="rename">Преименувай</string>

@ -77,7 +77,6 @@
<string name="your_comment">র মনতবয (ইি):</string>
<string name="error_details_headline">বরণন:</string>
<!-- Content descriptions (for better accessibility) -->
<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">পছনদ হয়</string>

@ -31,7 +31,6 @@
<string name="detail_likes_img_view_description">পছনদ হয়</string>
<string name="detail_uploader_thumbnail_view_description">আপলর ইউজরপিক থবনইল</string>
<string name="detail_thumbnail_view_description">িিও পকদরশন, সময়</string>
<string name="list_thumbnail_view_description">িিও পকদরশন থবনইল</string>
<string name="error_details_headline">বরণন:</string>
<string name="your_comment">আপনর মনতবয (ইি):</string>
<string name="info_labels">ি:\\nঅনধ:\\nকনট ভ:\\nসিস:\\nসময়(GMT এ):\\nপজ:\\nসকরণ:\\nওএস সকরণ:\\nআইপি পরিসর:</string>

@ -116,7 +116,6 @@
<string name="detail_likes_img_view_description">পছন</string>
<string name="detail_uploader_thumbnail_view_description">আপলর অবয়বর পরতিছবি</string>
<string name="detail_thumbnail_view_description">িিও চও, সময়</string>