Compare commits

...

31 Commits

Author SHA1 Message Date
Aayush Gupta
f4032e5fdd HistoryDao: latestEntry can be null
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 10:29:43 +08:00
Aayush Gupta
e528cbb6ae Bump ktlint to latest stable release and maven coordinate
Disable all new rules to avoid massive file-changes. All new rules should be
enabled one by one as per requirements in separate commit to make review easier.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Aayush Gupta
7c23a55d60 Silence warnings regarding new annotation property behavior
Ref: https://kotlinlang.org/docs/annotations.html#defaults-when-no-use-site-targets-are-specified

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Aayush Gupta
823f1437ca Relocate toml lint task to buildSrc and extend against default task
Fixes build errors after Gradle 9.x upgrade

Ref: https://docs.gradle.org/current/userguide/implementing_custom_tasks.html

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Aayush Gupta
ffd3565477 Bump dependencies to possible stable releases
androidx has bumped minSdk to API 23 which makes us unable to use latest version of:
* room
* workmanager

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Aayush Gupta
c411556b00 Enable Gradle configuration cache
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Aayush Gupta
35244355cd Bump Gradle to latest stable release
Also update the wrapper using the ./gradlew wrapper command

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2025-11-08 09:03:47 +08:00
Tobi
f836f5e75d Merge pull request #12746 from TeamNewPipe/kspMigration
Migrate from KAPT to KSP
2025-11-07 07:41:56 -08:00
Tobi
2fadaffb98 Merge pull request #12765 from TransZAllen/build_error_into_NewPipeExtractor
[Build] Local NewPipeExtractor build inclusion fails in settings.gradle.kts
2025-11-02 02:02:59 -08:00
TransZAllen
1314a21f71 fix: update commented example in settings.gradle.kts for Kotlin DSL
- Resolves build issue related to local NewPipeExtractor inclusion
- Related issue: https://github.com/TeamNewPipe/NewPipe/issues/12763
2025-11-02 16:01:39 +08:00
Tobi
650b51ffec Merge pull request #12694 from mjsir911/m/on.soundcloud
Support on.soundcloud link opening
2025-11-01 15:36:03 -07:00
Tobi
c03f405f8c Merge pull request #12716 from Isaac-75/12194-notification-prompt-after-rotation
Notifications are no longer requested again after rotating the phone
2025-11-01 15:24:31 -07:00
Tobi
0a89276b7a Merge pull request #12575 from TransZAllen/dev
[Bug] Fix missing subtitle text in manually downloaded *.SRT files. (issue #10030)
2025-10-30 14:27:39 -07:00
TransZAllen
300afde83d Update app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2025-10-29 22:34:47 +08:00
TransZAllen
d311faea58 improve comments on TTML → SRT conversion
- update class header with proper technical references and remove author tag.
- update comments of replacing NBSP('\u00A0'), especially adding examples
  of rendering incorrectly.
2025-10-29 19:25:43 +08:00
TransZAllen
71aa6d52d3 Update app/src/main/java/org/schabi/newpipe/streams/SrtFromTtmlWriter.java
Co-authored-by: Tobi <TobiGr@users.noreply.github.com>
2025-10-28 17:39:04 +08:00
Isaac
88eb32be3a moved field as requested 2025-10-26 05:32:12 +11:00
Stypox
2a9c6f0538 Merge pull request #12729 from litetex/workaround-popup-player-ui-elements-being-pushed-out 2025-10-21 18:15:13 +02:00
litetex
c81148ae0a [Popup player] Workaround that UI elements are pushed off screen 2025-10-21 18:09:06 +02:00
Stypox
ecd3e85d49 Merge pull request #12714 from litetex/properly-layout-player-topbar 2025-10-21 18:03:39 +02:00
Isaac
c4e6e4d4c4 Notifications are no longer requested again after rotating the phone 2025-10-19 03:41:52 +11:00
litetex
41981902ab Limit height of wrapper 2025-10-17 20:33:56 +02:00
litetex
56a09220ee Remove not needed viability control
This is done by the parent
2025-10-17 20:15:24 +02:00
litetex
c4bfc119df Improve the alignment of titleTextView and audioTrackTextView
This fulfills the following:
* both should never push content outside of the view
* there should be no wasted space
* `audioTrackTextView` is always aligned to the right
* both should grow equally but also respect their respective contents size first

Caveats:
* Currently the layout weight is distributed using "NestedWeights" which require a widget to be measured twice. According to Android Studio this might cause an exponential performane impact, however there is currently just a single nested component so the effect should be not noticeable
2025-10-17 19:29:26 +02:00
litetex
c49e44443c Correctly name the preview 2025-10-17 19:17:17 +02:00
litetex
1014dd563f Correctly format player.xml
Otherwise it constantly switches the attributes which makes (re) viewing changes next to impossible
2025-10-17 19:08:49 +02:00
TransZAllen
3516667671 refactor(ttml): extract recursion into traverseChildNodesForNestedTags()
- Extracted child-node traversal logic from `extractText()`
  into a helper method `traverseChildNodesForNestedTags()`.
- No functional change.
2025-10-17 12:06:18 +08:00
TransZAllen
22ee01bcfb refactor(ttml): improve extractText() to preserve spaces and special characters
- Replaced `text()` with `getWholeText()`:
  - avoids losing whitespaces at the beginning, end, or within the text;
  - avoids merging two or more consecutive spaces into a single space ' ';
  - avoids converting '\r', '\n', and '\r\n' within the text into a single space ' ';
  For subtitle conversion, the goal is to preserve every character exactly as intended by the subtitle author.
- Normalized tabs, line breaks, and other special characters for SRT-safe output.
- Added comprehensive unit tests in `SrtFromTtmlWriterTest.java`, including cases for simple and nested tags.
2025-10-17 01:57:01 +08:00
m
fd4f4737c2 Support on.soundcloud link opening 2025-10-10 03:22:23 -07:00
TobiGr
e1888ede87 Fix JDoc and apply suggestions 2025-08-27 10:38:13 +02:00
TransZAllen
2c35db7a07 [Bug] Fix missing subtitle text in manually downloaded *.SRT files. (issue #10030)
- Previously, *.SRT files only contained timestamps and sequence numbers, without the actual text content.
- Added recursive text extraction to handle nested tags in TTML
  files.(e.g.: <span> tags)
2025-08-27 14:03:42 +08:00
21 changed files with 804 additions and 154 deletions

40
.editorconfig Normal file
View File

@@ -0,0 +1,40 @@
#
# SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
# SPDX-License-Identifier: GPL-3.0-or-later
#
root = true
[*.{kt,kts}]
ktlint_standard_annotation = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_backing-property-naming = disabled
ktlint_standard_blank-line-before-declaration = disabled
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_enum-wrapping = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_function-literal = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_indent = disabled
ktlint_standard_max-line-length = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_no-blank-line-in-list = disabled
ktlint_standard_no-consecutive-comments = disabled
ktlint_standard_no-empty-first-line-in-class-body = disabled
ktlint_standard_no-empty-first-line-in-method-block = disabled
ktlint_standard_no-line-break-after-else = disabled
ktlint_standard_no-semi = disabled
ktlint_standard_no-single-line-block-comment = disabled
ktlint_standard_package-name = disabled
ktlint_standard_parameter-list-wrapping = disabled
ktlint_standard_property-naming = disabled
ktlint_standard_spacing-between-declarations-with-annotations = disabled
ktlint_standard_spacing-between-declarations-with-comments = disabled
ktlint_standard_statement-wrapping = disabled
ktlint_standard_string-template-indent = disabled
ktlint_standard_trailing-comma-on-call-site = disabled
ktlint_standard_trailing-comma-on-declaration-site = disabled
ktlint_standard_try-catch-finally-spacing = disabled

View File

@@ -12,8 +12,6 @@ plugins {
checkstyle
}
apply(from = "check-dependencies.gradle.kts")
val gitWorkingBranch = providers.exec {
commandLine("git", "rev-parse", "--abbrev-ref", "HEAD")
}.standardOutput.asText.map { it.trim() }
@@ -24,6 +22,14 @@ java {
}
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll(
"-Xannotation-default-target=param-property"
)
}
}
android {
compileSdk = 36
namespace = "org.schabi.newpipe"
@@ -159,7 +165,7 @@ tasks.register<JavaExec>("runKtlint") {
outputs.dir(outputDir)
mainClass.set("com.pinterest.ktlint.Main")
classpath = configurations.getByName("ktlint")
args = listOf("src/**/*.kt")
args = listOf("--editorconfig=../.editorconfig", "src/**/*.kt")
jvmArgs = listOf("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
@@ -168,10 +174,14 @@ tasks.register<JavaExec>("formatKtlint") {
outputs.dir(outputDir)
mainClass.set("com.pinterest.ktlint.Main")
classpath = configurations.getByName("ktlint")
args = listOf("-F", "src/**/*.kt")
args = listOf("--editorconfig=../.editorconfig", "-F", "src/**/*.kt")
jvmArgs = listOf("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
tasks.register<CheckDependenciesOrder>("checkDependenciesOrder") {
tomlFile = layout.projectDirectory.file("../gradle/libs.versions.toml")
}
afterEvaluate {
tasks.named("preDebugBuild").configure {
if (!System.getProperties().containsKey("skipFormatKtlint")) {

View File

@@ -340,6 +340,7 @@
<data android:scheme="https" />
<data android:host="soundcloud.com" />
<data android:host="m.soundcloud.com" />
<data android:host="on.soundcloud.com" />
<data android:host="www.soundcloud.com" />
<data android:pathPrefix="/" />
</intent-filter>

View File

@@ -65,6 +65,8 @@ public class App extends Application {
private static final String TAG = App.class.toString();
private boolean isFirstRun = false;
private boolean notificationsRequested = false;
private static App app;
@NonNull
@@ -72,6 +74,14 @@ public class App extends Application {
return app;
}
public boolean getNotificationsRequested() {
return notificationsRequested;
}
public void setNotificationsRequested() {
notificationsRequested = true;
}
@Override
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);

View File

@@ -9,5 +9,5 @@ package org.schabi.newpipe.database.history.dao
import org.schabi.newpipe.database.BasicDAO
interface HistoryDAO<T> : BasicDAO<T> {
val latestEntry: T
val latestEntry: T?
}

View File

@@ -15,7 +15,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry
interface SearchHistoryDAO : HistoryDAO<SearchHistoryEntry> {
@get:Query("SELECT * FROM search_history WHERE id = (SELECT MAX(id) FROM search_history)")
override val latestEntry: SearchHistoryEntry
override val latestEntry: SearchHistoryEntry?
@Query("DELETE FROM search_history")
override fun deleteAll(): Int

View File

@@ -290,8 +290,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
binding.topControls.setFocusable(true);
binding.metadataView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
// Reset workaround changes from popup player
binding.audioTrackTextView.setMaxWidth(Integer.MAX_VALUE);
}
@Override
@@ -936,8 +937,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
fragmentListener.onFullscreenStateChanged(isFullscreen);
binding.metadataView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE);
setupScreenRotationButton();
}

View File

@@ -40,6 +40,7 @@ import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.gesture.BasePlayerGestureListener;
import org.schabi.newpipe.player.gesture.PopupPlayerGestureListener;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.util.DeviceUtils;
public final class PopupPlayerUi extends VideoPlayerUi {
private static final String TAG = PopupPlayerUi.class.getSimpleName();
@@ -174,6 +175,8 @@ public final class PopupPlayerUi extends VideoPlayerUi {
binding.topControls.setClickable(false);
binding.topControls.setFocusable(false);
binding.bottomControls.bringToFront();
// Workaround that UI elements are pushed off screen
binding.audioTrackTextView.setMaxWidth(DeviceUtils.dpToPx(48, context));
super.setupElementsVisibility();
}

View File

@@ -15,7 +15,11 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author kapodamy
* Converts TTML subtitles to SRT format.
*
* References:
* - TTML 2.0 (W3C): https://www.w3.org/TR/ttml2/
* - SRT format: https://en.wikipedia.org/wiki/SubRip
*/
public class SrtFromTtmlWriter {
private static final String NEW_LINE = "\r\n";
@@ -59,6 +63,226 @@ public class SrtFromTtmlWriter {
out.write(text.getBytes(charset));
}
/**
* Decode XML or HTML entities into their actual (literal) characters.
*
* TTML is XML-based, so text nodes may contain escaped entities
* instead of direct characters. For example:
*
* "&amp;" → "&"
* "&lt;" → "<"
* "&gt;" → ">"
* "&#x9;" → "\t" (TAB)
* "&#xA;" (&#10;) → "\n" (LINE FEED)
*
* XML files cannot contain characters like "<", ">", "&" directly,
* so they must be represented using their entity-encoded forms.
*
* Jsoup sometimes leaves nested or encoded entities unresolved
* (e.g. inside <p> text nodes in TTML files), so this function
* acts as a final “safety net” to ensure all entities are decoded
* before further normalization.
*
* Character representation layers for reference:
* - Literal characters: <, >, &
* → appear in runtime/output text (e.g. final SRT output)
* - Escaped entities: &lt;, &gt;, &amp;
* → appear in XML/HTML/TTML source files
* - Numeric entities: &#xA0;, &#x9;, &#xD;
* → appear mainly in XML/TTML files (also valid in HTML)
* for non-printable or special characters
* - Unicode escapes: \u00A0 (Java/Unicode internal form)
* → appear only in Java source code (NOT valid in XML)
*
* XML entities include both named (&amp;, &lt;) and numeric
* (&#xA0;, &#160;) forms.
*
* @param encodedEntities The raw text fragment possibly containing
* encoded XML entities.
* @return A decoded string where all entities are replaced by their
* actual (literal) characters.
*/
private String decodeXmlEntities(final String encodedEntities) {
return Parser.unescapeEntities(encodedEntities, true);
}
/**
* Handle rare XML entity characters like LF: &#xA;(`\n`),
* CR: &#xD;(`\r`) and CRLF: (`\r\n`).
*
* These are technically valid in TTML (XML allows them)
* but unusual in practice, since most TTML line breaks
* are represented as <br/> tags instead.
* As a defensive approach, we normalize them:
*
* - Windows (\r\n), macOS (\r), and Unix (\n) → unified SRT NEW_LINE (\r\n)
*
* Although well-formed TTML normally encodes line breaks
* as <br/> tags, some auto-generated or malformed TTML files
* may embed literal newline entities (&#xA;, &#xD;). This
* normalization ensures these cases render properly in SRT
* players instead of breaking the subtitle structure.
*
* @param text To be normalized text with actual characters.
* @return Unified SRT NEW_LINE converted from all kinds of line breaks.
*/
private String normalizeLineBreakForSrt(final String text) {
String cleaned = text;
// NOTE:
// The order of newline replacements must NOT change,
// or duplicated line breaks (e.g. \r\n → \n\n) will occur.
cleaned = cleaned.replace("\r\n", "\n")
.replace("\r", "\n");
cleaned = cleaned.replace("\n", NEW_LINE);
return cleaned;
}
private String normalizeForSrt(final String actualText) {
String cleaned = actualText;
// Replace NBSP "non-breaking space" (\u00A0) with regular space ' '(\u0020).
//
// Why:
// - Some viewers render NBSP(\u00A0) incorrectly:
// * MPlayer 1.5: shown as “??”
// * Linux command `cat -A`: displayed as control-like markers
// (M-BM-)
// * Acode (Android editor): displayed as visible replacement
// glyphs (red dots)
// - Other viewers show it as a normal space (e.g., VS Code 1.104.0,
// vlc 3.0.20, mpv 0.37.0, Totem 43.0)
// → Mixed rendering creates inconsistency and may confuse users.
//
// Details:
// - YouTube TTML subtitles use both regular spaces (\u0020)
// and non-breaking spaces (\u00A0).
// - SRT subtitles only support regular spaces (\u0020),
// so \u00A0 may cause display issues.
// - \u00A0 and \u0020 are visually identical (i.e., they both
// appear as spaces ' '), but they differ in Unicode encoding,
// and NBSP (\u00A0) renders differently in different viewers.
// - SRT is a plain-text format and does not interpret
// "non-breaking" behavior.
//
// Conclusion:
// - Ensure uniform behavior, so replace it to a regular space
// without "non-breaking" behavior.
//
// References:
// - Unicode U+00A0 NBSP (Latin-1 Supplement):
// https://unicode.org/charts/PDF/U0080.pdf
cleaned = cleaned.replace('\u00A0', ' ') // Non-breaking space
.replace('\u202F', ' ') // Narrow no-break space
.replace('\u205F', ' ') // Medium mathematical space
.replace('\u3000', ' ') // Ideographic space
// \u2000 ~ \u200A are whitespace characters (e.g.,
// en space, em space), replaced with regular space (\u0020).
.replaceAll("[\\u2000-\\u200A]", " "); // Whitespace characters
// \u200B ~ \u200F are a range of non-spacing characters
// (e.g., zero-width space, zero-width non-joiner, etc.),
// which have no effect in *.SRT files and may cause
// display issues.
// These characters are invisible to the human eye, and
// they still exist in the encoding, so they need to be
// removed.
// After removal, the actual content becomes completely
// empty "", meaning there are no characters left, just
// an empty space, which helps avoid formatting issues
// in subtitles.
cleaned = cleaned.replaceAll("[\\u200B-\\u200F]", ""); // Non-spacing characters
// Remove control characters (\u0000 ~ \u001F, except
// \n, \r, \t).
// - These are ASCII C0 control codes (e.g. \u0001 SOH,
// \u0008 BS, \u001F US), invisible and irrelevant in
// subtitles, may cause square boxes (?) in players.
// - Reference:
// Unicode Basic Latin (https://unicode.org/charts/PDF/U0000.pdf)
// ASCII Control (https://en.wikipedia.org/wiki/ASCII#Control_characters)
cleaned = cleaned.replaceAll("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F]", "");
// Reasoning:
// - subtitle files generally don't require tabs for alignment.
// - Tabs can be displayed with varying widths across different
// editors or platforms, which may cause display issues.
// - Replace it with a single space for consistent display
// across different editors or platforms.
cleaned = cleaned.replace('\t', ' ');
cleaned = normalizeLineBreakForSrt(cleaned);
return cleaned;
}
private String sanitizeFragment(final String raw) {
if (null == raw) {
return "";
}
final String actualCharacters = decodeXmlEntities(raw);
final String srtSafeText = normalizeForSrt(actualCharacters);
return srtSafeText;
}
// Recursively process all child nodes to ensure text inside
// nested tags (e.g., <span>) is also extracted.
private void traverseChildNodesForNestedTags(final Node parent,
final StringBuilder text) {
for (final Node child : parent.childNodes()) {
extractText(child, text);
}
}
// CHECKSTYLE:OFF checkstyle:JavadocStyle
// checkstyle does not understand that span tags are inside a code block
/**
* <p>Recursive method to extract text from all nodes.</p>
* <p>
* This method processes {@link TextNode}s and {@code <br>} tags,
* recursively extracting text from nested tags
* (e.g. extracting text from nested {@code <span>} tags).
* Newlines are added for {@code <br>} tags.
* </p>
* @param node the current node to process
* @param text the {@link StringBuilder} to append the extracted text to
*/
// --------------------------------------------------------------------
// [INTERNAL NOTE] TTML text layer explanation
//
// TTML parsing involves multiple text "layers":
// 1. Raw XML entities (e.g., &lt;, &#xA0;) are decoded by Jsoup.
// 2. extractText() works on DOM TextNodes (already parsed strings).
// 3. sanitizeFragment() decodes remaining entities and fixes
// Unicode quirks.
// 4. normalizeForSrt() ensures literal text is safe for SRT output.
//
// In short:
// Jsoup handles XML-level syntax,
// our code handles text-level normalization for subtitles.
// --------------------------------------------------------------------
private void extractText(final Node node, final StringBuilder text) {
if (node instanceof TextNode textNode) {
String rawTtmlFragment = textNode.getWholeText();
String srtContent = sanitizeFragment(rawTtmlFragment);
text.append(srtContent);
} else if (node instanceof Element element) {
// <br> is a self-closing HTML tag used to insert a line break.
if (element.tagName().equalsIgnoreCase("br")) {
// Add a newline for <br> tags
text.append(NEW_LINE);
}
}
traverseChildNodesForNestedTags(node, text);
}
// CHECKSTYLE:ON
public void build(final SharpStream ttml) throws IOException {
/*
* TTML parser with BASIC support
@@ -79,21 +303,15 @@ public class SrtFromTtmlWriter {
final Elements paragraphList = doc.select("body > div > p");
// check if has frames
if (paragraphList.size() < 1) {
if (paragraphList.isEmpty()) {
return;
}
for (final Element paragraph : paragraphList) {
text.setLength(0);
for (final Node children : paragraph.childNodes()) {
if (children instanceof TextNode) {
text.append(((TextNode) children).text());
} else if (children instanceof Element
&& ((Element) children).tagName().equalsIgnoreCase("br")) {
text.append(NEW_LINE);
}
}
// Recursively extract text from all child nodes
extractText(paragraph, text);
if (ignoreEmptyFrames && text.length() < 1) {
continue;

View File

@@ -17,6 +17,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
@@ -89,9 +90,12 @@ public final class PermissionHelper {
&& ContextCompat.checkSelfPermission(activity,
Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity,
new String[] {Manifest.permission.POST_NOTIFICATIONS}, requestCode);
return false;
if (!App.getApp().getNotificationsRequested()) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.POST_NOTIFICATIONS}, requestCode);
App.getApp().setNotificationsRequested();
return false;
}
}
return true;
}

View File

@@ -109,74 +109,89 @@
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/close"
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
android:src="@drawable/ic_close"
android:visibility="gone"
app:tint="@color/white"
android:contentDescription="@string/close"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:id="@+id/metadataView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="8dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="top"
android:orientation="vertical"
tools:ignore="RtlHardcoded">
android:orientation="horizontal">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
<LinearLayout
android:id="@+id/metadataView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONG" />
android:layout_marginTop="6dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:gravity="top"
android:orientation="vertical"
tools:ignore="NestedWeights,RtlHardcoded">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/channelTextView"
android:layout_width="match_parent"
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold"
tools:ignore="RtlHardcoded"
tools:text="The Video Title LONG very LONG" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/channelTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="12sp"
tools:text="The Video Artist LONG very LONG very Long" />
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="marquee_forever"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="@android:color/white"
android:textSize="12sp"
tools:text="The Video Artist LONG very LONG very Long" />
android:layout_weight="1">
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/audioTrackTextView"
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_gravity="right"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:gravity="center"
android:minWidth="0dp"
android:padding="@dimen/player_main_buttons_padding"
android:singleLine="true"
android:textColor="@android:color/white"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="HardcodedText,RtlHardcoded"
tools:text="English (United States) original"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/audioTrackTextView"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:minWidth="0dp"
android:singleLine="true"
android:ellipsize="end"
android:padding="@dimen/player_main_buttons_padding"
android:textColor="@android:color/white"
android:textStyle="bold"
android:visibility="gone"
tools:ignore="HardcodedText,RtlHardcoded"
tools:visibility="visible"
tools:text="English (Original)" />
<org.schabi.newpipe.views.NewPipeTextView
android:id="@+id/qualityTextView"
android:layout_width="wrap_content"
@@ -212,6 +227,7 @@
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/open_play_queue"
android:focusable="true"
android:paddingStart="3dp"
android:paddingTop="5dp"
@@ -221,7 +237,6 @@
android:src="@drawable/ic_list"
android:visibility="gone"
app:tint="@color/white"
android:contentDescription="@string/open_play_queue"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
@@ -231,16 +246,16 @@
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/chapters"
android:focusable="true"
android:paddingStart="6dp"
android:paddingTop="3dp"
android:paddingEnd="6dp"
android:paddingBottom="3dp"
android:paddingTop="3dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_menu_book"
android:visibility="gone"
app:tint="@color/white"
android:contentDescription="@string/chapters"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
@@ -249,12 +264,12 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/more_options"
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitXY"
android:src="@drawable/ic_expand_more"
app:tint="@color/white"
android:contentDescription="@string/more_options"
tools:ignore="RtlHardcoded" />
</LinearLayout>
@@ -371,11 +386,11 @@
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/toggle_fullscreen"
android:focusable="true"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitCenter"
android:src="@drawable/ic_fullscreen"
android:contentDescription="@string/toggle_fullscreen"
android:visibility="gone"
app:tint="@color/white"
tools:ignore="RtlHardcoded"
@@ -495,13 +510,13 @@
android:layout_marginStart="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/toggle_screen_orientation"
android:focusable="true"
android:nextFocusUp="@id/playbackSeekBar"
android:padding="@dimen/player_main_buttons_padding"
android:scaleType="fitCenter"
android:src="@drawable/ic_fullscreen"
android:visibility="gone"
android:contentDescription="@string/toggle_screen_orientation"
app:tint="@color/white"
tools:ignore="RtlHardcoded"
tools:visibility="visible" />
@@ -523,10 +538,10 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/previous_stream"
android:focusable="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_previous"
android:contentDescription="@string/previous_stream"
app:tint="@color/white" />
@@ -536,9 +551,9 @@
android:layout_height="60dp"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/pause"
android:scaleType="fitCenter"
android:src="@drawable/ic_pause"
android:contentDescription="@string/pause"
app:tint="@color/white" />
<androidx.appcompat.widget.AppCompatImageButton
@@ -549,10 +564,10 @@
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/next_stream"
android:focusable="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_next"
android:contentDescription="@string/next_stream"
app:tint="@color/white" />
</LinearLayout>
@@ -599,12 +614,12 @@
android:layout_marginLeft="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/notification_action_repeat"
android:focusable="true"
android:padding="10dp"
android:scaleType="fitXY"
android:src="@drawable/exo_controls_repeat_off"
android:tint="?attr/colorAccent"
android:contentDescription="@string/notification_action_repeat"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton
@@ -615,12 +630,12 @@
android:layout_toRightOf="@id/repeatButton"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/notification_action_shuffle"
android:focusable="true"
android:padding="10dp"
android:scaleType="fitXY"
android:src="@drawable/ic_shuffle"
android:tint="?attr/colorAccent"
android:contentDescription="@string/notification_action_shuffle"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatTextView
@@ -642,12 +657,12 @@
android:layout_toLeftOf="@+id/itemsListClose"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/add_to_playlist"
android:focusable="true"
android:padding="10dp"
android:scaleType="fitXY"
android:src="@drawable/ic_playlist_add"
android:tint="?attr/colorAccent"
android:contentDescription="@string/add_to_playlist"
tools:ignore="RtlHardcoded" />
<androidx.appcompat.widget.AppCompatImageButton

View File

@@ -0,0 +1,320 @@
package org.schabi.newpipe.streams;
import org.junit.Test;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.parser.Parser;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link SrtFromTtmlWriter}.
*
* Tests focus on {@code extractText()} and its handling of TTML <p> elements.
* Note:
* - Uses reflection to call the private {@code extractText()} method.
* - Update {@code EXTRACT_TEXT_METHOD} if renamed.
*
* ---
* NOTE ABOUT ENTITIES VS UNICODE ESCAPES
*
* - In short:
* * UNICODE ESCAPES → used in Java source (e.g. SrtFromTtmlWriter.java)
* * ENTITIES → used in TTML strings (this test file)
*
* - TTML is an XML-based format. Real TTML subtitles often encode special
* characters as XML entities (named or numeric), e.g.:
* &amp; → '&' (\u0026)
* &lt; → '<' (\u003C)
* &#x9; → tab (\u0009)
* &#xA; → line feed (\u000A)
* &#xD; → carriage return (\u000D)
*
* - Java source code uses **Unicode escapes** (e.g. "\u00A0") which are resolved
* at compile time, so they do not represent real XML entities.
*
* - Purpose of these tests:
* We simulate *real TTML input* as NewPipe receives it — i.e., strings that
* still contain encoded XML entities (&#x9;, &#xA;, &#xD;, etc.).
* The production code (`decodeXmlEntities()`) must convert these into their
* actual Unicode characters before normalization.
*/
public class SrtFromTtmlWriterTest {
private static final String TTML_WRAPPER_START = "<tt><body><div>";
private static final String TTML_WRAPPER_END = "</div></body></tt>";
private static final String EXTRACT_TEXT_METHOD = "extractText";
// Please keep the same definition from `SrtFromTtmlWriter` class.
private static final String NEW_LINE = "\r\n";
/*
* TTML example for simple paragraph <p> without nested tags.
* <p begin="00:00:01.000" end="00:00:03.000" style="s2">Hello World!</p>
*/
private static final String SIMPLE_TTML = "<p begin=\"00:00:01.000\" end=\"00:00:03.000\" "
+ "style=\"s2\">Hello World!</p>";
/**
* TTML example with nested tags with <br>.
* <p begin="00:00:01.000" end="00:00:03.000"><span style="s4">Hello</span><br>World!</p>
*/
private static final String NESTED_TTML = "<p begin=\"00:00:01.000\" end=\"00:00:03.000\">"
+ "<span style=\"s4\">Hello</span><br>World!</p>";
/**
* TTML example with HTML entities.
* &lt; → <, &gt; → >, &amp; → &, &quot; → ", &apos; → '
* &#39; → '
* &#xA0; → ' '
*/
private static final String ENTITY_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&lt;tag&gt; &amp; &quot;text&quot;&apos;&apos;&#39;&#39;"
+ "&#xA0;&#xA0;"
+ "</p>";
/**
* TTML example with special characters:
* - Spaces appear at the beginning and end of the text.
* - Spaces are also present within the text (not just at the edges).
* - The text includes various HTML entities such as &nbsp;,
* &amp;, &lt;, &gt;, etc.
* &nbsp; → non-breaking space (Unicode: '\u00A0', Entity: '&#xA0;')
*/
private static final String SPECIAL_TTML = "<p begin=\"00:00:05.000\" end=\"00:00:07.000\">"
+ " ~-Hello&nbsp;&nbsp;&amp;&amp;&lt;&lt;&gt;&gt;World!! "
+ "</p>";
/**
* TTML example with characters: tab.
* &#x9; → \t
* They are separated by '+' for clarity.
*/
private static final String TAB_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#x9;&#x9;+&#x9;&#x9;+&#x9;&#x9;"
+ "</p>";
/**
* TTML example with line endings.
* &#xD; → \r
*/
private static final String LINE_ENDING_0_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#xD;&#xD;+&#xD;&#xD;+&#xD;&#xD;"
+ "</p>";
// &#xA; → \n
private static final String LINE_ENDING_1_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#xA;&#xA;+&#xA;&#xA;+&#xA;&#xA;"
+ "</p>";
private static final String LINE_ENDING_2_TTML =
"<p begin=\"00:00:05.000\" end=\"00:00:07.000\">"
+ "&#xD;&#xA;+&#xD;&#xA;+&#xD;&#xA;"
+ "</p>";
/**
* TTML example with control characters.
* For example:
* &#x0001; → \u0001
* &#x001F; → \u001F
*
* These control characters, if included as raw Unicode(e.g. '\u0001'),
* are either invalid in XML or rendered as '?' when processed.
* To avoid issues, they should be encoded(e.g. '&#x0001;') in TTML file.
*
* - Reference:
* Unicode Basic Latin (https://unicode.org/charts/PDF/U0000.pdf),
* ASCII Control (https://en.wikipedia.org/wiki/ASCII#Control_characters).
* and the defination of these characters can be known.
*/
private static final String CONTROL_CHAR_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#x0001;+&#x0008;+&#x000B;+&#x000C;+&#x000E;+&#x001F;"
+ "</p>";
private static final String EMPTY_TTML = "<p begin=\"00:00:01.000\" "
+ "end=\"00:00:03.000\">"
+ ""
+ "</p>";
/**
* TTML example with Unicode space characters.
* These characters are encoded using character references
* (&#xXXXX;).
*
* Includes:
* (&#x202F;) '\u202F' → Narrow no-break space
* (&#x205F;) '\u205F' → Medium mathematical space
* (&#x3000;) '\u3000' → Ideographic space
* '\u2000' ~ '\u200A' are whitespace characters:
* (&#x2000;) '\u2000' → En quad
* (&#x2002;) '\u2002' → En space
* (&#x200A;) '\u200A' → Hair space
*
* Each character is separated by '+' for clarity.
*/
private static final String UNICODE_SPACE_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#x202F;+&#x205F;+&#x3000;+&#x2000;+&#x2002;+&#x200A;"
+ "</p>";
/**
* TTML example with non-spacing (invisible) characters.
* These are encoded using character references (&#xXXXX;).
*
* Includes:
* (&#x200B;)'\u200B' → Zero-width space (ZWSP)
* (&#x200E;)'\u200E' → Left-to-right mark (LRM)
* (&#x200F;)'\u200F' → Right-to-left mark (RLM)
*
* They don't display any characters to the human eye.
* '+' is used between them for clarity in test output.
*/
private static final String NON_SPACING_TTML = "<p begin=\"00:00:05.000\" "
+ "end=\"00:00:07.000\">"
+ "&#x200B;+&#x200E;+&#x200F;"
+ "</p>";
/**
* Parses TTML string into a JSoup Document and selects the first <p> element.
*
* @param ttmlContent TTML content (e.g., <p>...</p>)
* @return the first <p> element
* @throws Exception if parsing or reflection fails
*/
private Element parseTtmlParagraph(final String ttmlContent) throws Exception {
final String ttml = TTML_WRAPPER_START + ttmlContent + TTML_WRAPPER_END;
final Document doc = Jsoup.parse(
new ByteArrayInputStream(ttml.getBytes(StandardCharsets.UTF_8)),
"UTF-8", "", Parser.xmlParser());
return doc.select("body > div > p").first();
}
/**
* Invokes private extractText method via reflection.
*
* @param writer SrtFromTtmlWriter instance
* @param paragraph <p> element to extract text from
* @param text StringBuilder to store extracted text
* @throws Exception if reflection fails
*/
private void invokeExtractText(final SrtFromTtmlWriter writer, final Element paragraph,
final StringBuilder text) throws Exception {
final Method method = writer.getClass()
.getDeclaredMethod(EXTRACT_TEXT_METHOD, Node.class, StringBuilder.class);
method.setAccessible(true);
method.invoke(writer, paragraph, text);
}
private String extractTextFromTtml(final String ttmlInput) throws Exception {
final Element paragraph = parseTtmlParagraph(ttmlInput);
final StringBuilder text = new StringBuilder();
final SrtFromTtmlWriter writer = new SrtFromTtmlWriter(null, false);
invokeExtractText(writer, paragraph, text);
final String actualText = text.toString();
return actualText;
}
@Test
public void testExtractTextSimpleParagraph() throws Exception {
final String expected = "Hello World!";
final String actual = extractTextFromTtml(SIMPLE_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextNestedTags() throws Exception {
final String expected = "Hello\r\nWorld!";
final String actual = extractTextFromTtml(NESTED_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithEntity() throws Exception {
final String expected = "<tag> & \"text\"'''' ";
final String actual = extractTextFromTtml(ENTITY_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithSpecialCharacters() throws Exception {
final String expected = " ~-Hello &&<<>>World!! ";
final String actual = extractTextFromTtml(SPECIAL_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithTab() throws Exception {
final String expected = " + + ";
final String actual = extractTextFromTtml(TAB_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithLineEnding0() throws Exception {
final String expected = NEW_LINE + NEW_LINE + "+"
+ NEW_LINE + NEW_LINE + "+"
+ NEW_LINE + NEW_LINE;
final String actual = extractTextFromTtml(LINE_ENDING_0_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithLineEnding1() throws Exception {
final String expected = NEW_LINE + NEW_LINE + "+"
+ NEW_LINE + NEW_LINE + "+"
+ NEW_LINE + NEW_LINE;
final String actual = extractTextFromTtml(LINE_ENDING_1_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithLineEnding2() throws Exception {
final String expected = NEW_LINE + "+"
+ NEW_LINE + "+"
+ NEW_LINE;
final String actual = extractTextFromTtml(LINE_ENDING_2_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithControlCharacters() throws Exception {
final String expected = "+++++";
final String actual = extractTextFromTtml(CONTROL_CHAR_TTML);
assertEquals(expected, actual);
}
/**
* Test case to ensure that extractText() does not throw an exception
* when there are no text in the TTML paragraph (i.e., the paragraph
* is empty).
*
* Note:
* In the NewPipe, *.srt files will contain empty text lines by default.
*/
@Test
public void testExtractTextWithEmpty() throws Exception {
final String expected = "";
final String actual = extractTextFromTtml(EMPTY_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithUnicodeSpaces() throws Exception {
final String expected = " + + + + + ";
final String actual = extractTextFromTtml(UNICODE_SPACE_TTML);
assertEquals(expected, actual);
}
@Test
public void testExtractTextWithNonSpacingCharacters() throws Exception {
final String expected = "++";
final String actual = extractTextFromTtml(NON_SPACING_TTML);
assertEquals(expected, actual);
}
}

12
buildSrc/build.gradle.kts Normal file
View File

@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2025 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}

View File

@@ -4,18 +4,27 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
tasks.register("checkDependenciesOrder") {
group = "verification"
description = "Checks that each section in libs.versions.toml is sorted alphabetically"
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
val tomlFile = file("../gradle/libs.versions.toml")
abstract class CheckDependenciesOrder : DefaultTask() {
doLast {
if (!tomlFile.exists()) {
throw GradleException("TOML file not found")
}
@get:InputFile
abstract val tomlFile: RegularFileProperty
val lines = tomlFile.readLines()
init {
group = "verification"
description = "Checks that each section in libs.versions.toml is sorted alphabetically"
}
@TaskAction
fun run() {
val file = tomlFile.get().asFile
if (!file.exists()) error("TOML file not found")
val lines = file.readLines()
val nonSortedBlocks = mutableListOf<List<String>>()
var currentBlock = mutableListOf<String>()
var prevLine = ""
@@ -50,7 +59,7 @@ tasks.register("checkDependenciesOrder") {
}
if (nonSortedBlocks.isNotEmpty()) {
throw GradleException(
error(
"The following lines were not sorted:\n" +
nonSortedBlocks.joinToString("\n\n") { it.joinToString("\n") }
)

View File

@@ -4,3 +4,6 @@ android.nonTransitiveRClass=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048M --add-opens jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
systemProp.file.encoding=utf-8
# https://docs.gradle.org/current/userguide/configuration_cache.html
org.gradle.configuration-cache=true

View File

@@ -4,47 +4,47 @@
#
[versions]
acra = "5.11.3"
acra = "5.13.1"
agp = "8.13.0"
appcompat = "1.7.1"
assertj = "3.24.2"
assertj = "3.27.6"
autoservice = "1.1.1"
bridge = "v2.0.2"
cardview = "1.0.0"
checkstyle = "10.26.1"
constraintlayout = "2.1.4"
core = "1.12.0"
desugar = "2.0.4"
documentfile = "1.0.1"
exoplayer = "2.18.7"
fragment = "1.6.2"
checkstyle = "12.1.1"
constraintlayout = "2.2.1"
core = "1.17.0"
desugar = "2.1.5"
documentfile = "1.1.0"
exoplayer = "2.19.1"
fragment = "1.8.9"
groupie = "2.10.1"
jsoup = "1.21.2"
junit = "4.13.2"
junit-ext = "1.1.5"
kotlin = "1.9.25"
ksp = "1.9.25-1.0.20"
ktlint = "0.45.2"
leakcanary = "2.12"
lifecycle = "2.6.2"
junit-ext = "1.3.0"
kotlin = "2.2.21"
ksp = "2.3.0"
ktlint = "1.7.1"
leakcanary = "2.14"
lifecycle = "2.9.4"
localbroadcastmanager = "1.1.0"
markwon = "4.6.2"
material = "1.11.0"
material = "1.13.0"
media = "1.7.1"
mockitoCore = "5.6.0"
okhttp = "4.12.0"
phoenix = "2.1.2"
mockitoCore = "5.20.0"
okhttp = "5.3.0"
phoenix = "3.0.0"
#noinspection NewerVersionAvailable,GradleDependency --> 2.8 is the last version, not 2.71828!
picasso = "2.8"
preference = "1.2.1"
prettytime = "5.0.8.Final"
recyclerview = "1.3.2"
room = "2.6.1"
runner = "1.5.2"
recyclerview = "1.4.0"
room = "2.7.2"
runner = "1.7.0"
rxandroid = "3.0.2"
rxbinding = "4.0.0"
rxjava = "3.1.12"
sonarqube = "4.0.0.2929"
sonarqube = "7.0.1.6134"
statesaver = "1.4.1"
stetho = "1.6.0"
swiperefreshlayout = "1.1.0"
@@ -60,8 +60,8 @@ teamnewpipe-nanojson = "e9d656ddb49a412a5a0a5d5ef20ca7ef09549996"
# to cause jitpack to regenerate the artifact.
teamnewpipe-newpipe-extractor = "3af73262cc60cf555fd5f1d691f6c58e2db38ef5"
viewpager2 = "1.1.0"
webkit = "1.9.0"
work = "2.8.1"
webkit = "1.14.0"
work = "2.10.5"
[libraries]
acra-core = { module = "ch.acra:acra-core", version.ref = "acra" }
@@ -87,7 +87,7 @@ androidx-runner = { module = "androidx.test:runner", version.ref = "runner" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
androidx-work-runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "work" }
androidx-work-rxjava3 = { module = "androidx.work:work-rxjava3", version.ref = "work" }
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
evernote-statesaver-compiler = { module = "com.evernote:android-state-processor", version.ref = "statesaver" }
@@ -119,7 +119,7 @@ newpipe-nanojson = { module = "com.github.TeamNewPipe:nanojson", version.ref = "
noties-markwon-core = { module = "io.noties.markwon:core", version.ref = "markwon" }
noties-markwon-linkify = { module = "io.noties.markwon:linkify", version.ref = "markwon" }
ocpsoft-prettytime = { module = "org.ocpsoft.prettytime:prettytime", version.ref = "prettytime" }
pinterest-ktlint = { module = "com.pinterest:ktlint", version.ref = "ktlint" }
pinterest-ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
puppycrawl-checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
reactivex-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
reactivex-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }

Binary file not shown.

View File

@@ -1,7 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionSha256Sum=df67a32e86e3276d011735facb1535f64d0d88df84fa87521e90becc2d735444
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

33
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +85,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -111,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -130,10 +132,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -141,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -149,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -166,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -198,16 +202,15 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

25
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -25,8 +25,9 @@ include (":app")
// We assume, that NewPipe and NewPipe Extractor have the same parent directory.
// If this is not the case, please change the path in includeBuild().
//includeBuild('../NewPipeExtractor') {
//includeBuild("../NewPipeExtractor") {
// dependencySubstitution {
// substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
// substitute(module("com.github.TeamNewPipe:NewPipeExtractor"))
// .using(project(":extractor"))
// }
//}