mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-19 23:40:51 +02:00
Compare commits
97 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3d1cc348c8 | ||
![]() |
2756db6601 | ||
![]() |
7b56aaad53 | ||
![]() |
b73677fa5e | ||
![]() |
f391574113 | ||
![]() |
ea863b0c24 | ||
![]() |
4506c90e59 | ||
![]() |
894a63ed82 | ||
![]() |
0155454526 | ||
![]() |
f7aafb87a8 | ||
![]() |
227001ec32 | ||
![]() |
d765364915 | ||
![]() |
3d47e63d6f | ||
![]() |
79c5c3cc57 | ||
![]() |
8babbddcf9 | ||
![]() |
13756508d3 | ||
![]() |
b7fe001b13 | ||
![]() |
35b4110a7a | ||
![]() |
cdca0c6325 | ||
![]() |
094851cc7d | ||
![]() |
f55612be40 | ||
![]() |
b70fd826e7 | ||
![]() |
994559b39b | ||
![]() |
907842c672 | ||
![]() |
c890ab44d6 | ||
![]() |
89b11ff71c | ||
![]() |
3e34eeeed7 | ||
![]() |
69302fcbd0 | ||
![]() |
60879351a9 | ||
![]() |
f4433ac508 | ||
![]() |
7b7d0d6171 | ||
![]() |
a6e0ed09a8 | ||
![]() |
19ed6ebbaf | ||
![]() |
dfeee17d39 | ||
![]() |
4c429c869c | ||
![]() |
825de1b6ee | ||
![]() |
124461f587 | ||
![]() |
a570fa6110 | ||
![]() |
0e8df83bbd | ||
![]() |
952c8428d8 | ||
![]() |
5fe2c10aa1 | ||
![]() |
1f4aa2506b | ||
![]() |
9f8844fa5f | ||
![]() |
990aa88e00 | ||
![]() |
9e335c1894 | ||
![]() |
8f2b9a6bb7 | ||
![]() |
448f3e8918 | ||
![]() |
62b2ab7571 | ||
![]() |
78cbfa20d9 | ||
![]() |
ccd42d42ab | ||
![]() |
abe03ef9a3 | ||
![]() |
9813ffb83b | ||
![]() |
b4eefa3eed | ||
![]() |
6a166b798a | ||
![]() |
0686aec606 | ||
![]() |
54b3c62803 | ||
![]() |
40d83ba7e7 | ||
![]() |
ea3c379d88 | ||
![]() |
a6ee3e99ea | ||
![]() |
b2cab4aea0 | ||
![]() |
2a2e532acc | ||
![]() |
0954a494e7 | ||
![]() |
bc32c946ff | ||
![]() |
95debf66e5 | ||
![]() |
3b166f82a8 | ||
![]() |
7d19250565 | ||
![]() |
c510a4149d | ||
![]() |
33e473c509 | ||
![]() |
e3dfdb0b06 | ||
![]() |
efa262480a | ||
![]() |
2c8dd9ce2a | ||
![]() |
ead1399e7b | ||
![]() |
4a4b1e0e49 | ||
![]() |
cb5e37184a | ||
![]() |
4b78a9366d | ||
![]() |
094a3af8ae | ||
![]() |
d6fffc7e55 | ||
![]() |
283d33aa27 | ||
![]() |
6c445c0833 | ||
![]() |
dd10c1756f | ||
![]() |
ced75a9b60 | ||
![]() |
5ecad47d6e | ||
![]() |
6e11ca1ac4 | ||
![]() |
edfdabb691 | ||
![]() |
4653e94c57 | ||
![]() |
54df50f84d | ||
![]() |
7588c8de5a | ||
![]() |
d564199e88 | ||
![]() |
e458adb342 | ||
![]() |
b43a7354cf | ||
![]() |
6341ad88e8 | ||
![]() |
314b2fb14f | ||
![]() |
795ba89dc4 | ||
![]() |
5b8ff28556 | ||
![]() |
5e66a66111 | ||
![]() |
3e6bed538a | ||
![]() |
020322df0b |
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
@@ -13,6 +13,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
|
||||
* Check whether your issue/feature is already fixed/implemented
|
||||
* If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome!
|
||||
* We use English for development. Issues in other languages will be closed and ignored.
|
||||
* Please only add *one* issue at a time. Do not put multiple issues into one thread.
|
||||
|
||||
## Bug Fixing
|
||||
* If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information.
|
||||
@@ -26,7 +27,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras
|
||||
* Stick to NewPipe's style conventions (well, just look the other code and then do it the same way :))
|
||||
* Do not bring non-free software (e.g., binary blobs) into the project. Also, make sure you do not introduce Google libraries.
|
||||
* Stick to [F-Droid contribution guidelines](https://f-droid.org/wiki/page/Inclusion_Policy)
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your
|
||||
* Make changes on a separate branch, not on the master branch. This is commonly known as *feature branch workflow*. You may then send your changes as a pull request on GitHub. Patches to the email address mentioned in this document might not be considered, GitHub is the primary platform.
|
||||
* When submitting changes, you confirm that your code is licensed under the terms of the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html).
|
||||
* Please test (compile and run) your code before you submit changes! Ideally, provide test feedback in the PR description. Untested code will **not** be merged!
|
||||
* Try to figure out yourself why builds on our CI fail.
|
||||
|
@@ -10,9 +10,6 @@ android:
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-26
|
||||
|
||||
# Additional components
|
||||
- extra-android-m2repository
|
||||
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
|
84
README.md
84
README.md
@@ -1,34 +1,32 @@
|
||||
<p align="center"><a href="https://newpipe.schabi.org"><img src="assets/new_pipe_icon_5.png" width="150"/></a></p>
|
||||
<h2 align="center"><b>NewPipe</b></h2>
|
||||
<h4 align="center">A free lightweight YouTube frontend for Android.</h4>
|
||||
<p align="center"><a href="https://f-droid.org/packages/org.schabi.newpipe/"><img src="https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png"/></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/TeamNewPipe/NewPipe" alt="GitHub release"><img src="https://img.shields.io/github/release/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPL v3"><img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" /></a>
|
||||
<a href="https://travis-ci.org/TeamNewPipe/NewPipe" alt="Build Status"><img src="https://travis-ci.org/TeamNewPipe/NewPipe.svg" /></a>
|
||||
<a href="https://hosted.weblate.org/engage/NewPipe/" alt="Translation Status"><img src="https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg" /></a>
|
||||
<a href="http://webchat.freenode.net/?channels=%23newpipe" alt="IRC channel: #newpipe"><img src="https://img.shields.io/badge/IRC%20chat-%23newpipe-brightgreen.svg" /></a>
|
||||
</p>
|
||||
<hr />
|
||||
<p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p>
|
||||
<hr />
|
||||
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.
|
||||
|
||||
# NewPipe
|
||||
NewPipe: A free lightweight YouTube frontend for Android.
|
||||
|
||||
[](https://newpipe.schabi.org)
|
||||
[](https://f-droid.org/packages/org.schabi.newpipe/)
|
||||
|
||||
|
||||
Project status:
|
||||
[](https://hosted.weblate.org/engage/NewPipe/)
|
||||
[](https://travis-ci.org/TeamNewPipe/NewPipe)
|
||||
|
||||
## Donate
|
||||

|
||||

|
||||
|
||||
`16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh`
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img src="screenshots/screenshot_1.png" width=160>](screenshots/screenshot_1.png)
|
||||
[<img src="screenshots/screenshot_2.png" width=160>](screenshots/screenshot_2.png)
|
||||
[<img src="screenshots/screenshot_3.png" width=160>](screenshots/screenshot_3.png)
|
||||
[<img src="screenshots/screenshot_4.png" width=160>](screenshots/screenshot_4.png)
|
||||
[<img src="screenshots/screenshot_5.png" width=160>](screenshots/screenshot_5.png)
|
||||
[<img src="screenshots/screenshot_6.png" width=160>](screenshots/screenshot_6.png)
|
||||
[<img src="screenshots/screenshot_7.png" width=160>](screenshots/screenshot_7.png)
|
||||
[<img src="screenshots/screenshot_8.png" width=160>](screenshots/screenshot_8.png)
|
||||
[<img src="screenshots/screenshot_9.png" width=160>](screenshots/screenshot_9.png)
|
||||
|
||||
[<img src="screenshots/shot_1.png" width=160>](screenshots/shot_1.png)
|
||||
[<img src="screenshots/shot_2.png" width=160>](screenshots/shot_2.png)
|
||||
[<img src="screenshots/shot_3.png" width=160>](screenshots/shot_3.png)
|
||||
[<img src="screenshots/shot_4.png" width=160>](screenshots/shot_4.png)
|
||||
[<img src="screenshots/shot_5.png" width=160>](screenshots/shot_5.png)
|
||||
[<img src="screenshots/shot_6.png" width=160>](screenshots/shot_6.png)
|
||||
[<img src="screenshots/shot_7.png" width=160>](screenshots/shot_7.png)
|
||||
[<img src="screenshots/shot_8.png" width=160>](screenshots/shot_8.png)
|
||||
[<img src="screenshots/shot_9.png" width=160>](screenshots/shot_9.png)
|
||||
[<img src="screenshots/shot_10.png" width=160>](screenshots/shot_10.png)
|
||||
|
||||
## Description
|
||||
|
||||
@@ -39,7 +37,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
||||
* Search videos
|
||||
* Display general information about a video
|
||||
* Watch YouTube videos
|
||||
* Listen to YouTube videos (experimental)
|
||||
* Listen to YouTube videos
|
||||
* Popup mode (floating player)
|
||||
* Select the streaming player to watch the video with
|
||||
* Download videos
|
||||
@@ -47,21 +45,23 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only
|
||||
* Open a video in Kodi
|
||||
* Show Next/Related videos
|
||||
* Search YouTube in a specific language
|
||||
* Watch age restricted material
|
||||
* Watch/Block age restricted material
|
||||
* Display general information about channels
|
||||
* Search channels
|
||||
* Watch videos from a channel
|
||||
* Orbot/Tor support (not yet directly)
|
||||
* 1080p/2k/4k support
|
||||
* View history
|
||||
* Subscribe to channels
|
||||
* Search history
|
||||
* Search/Watch Playlists
|
||||
|
||||
### Coming Features
|
||||
|
||||
* Multiservice support (eg. SoundCloud)
|
||||
* Bookmarks
|
||||
* View history
|
||||
* Search history
|
||||
* Subscribe to channels
|
||||
* Search/Watch Playlists
|
||||
* Queeing videos
|
||||
* Watch as queues Playlists
|
||||
* Queuing videos
|
||||
* Subtitles support
|
||||
* livestream support
|
||||
* ... and many more
|
||||
@@ -75,6 +75,22 @@ The more is done the better it gets!
|
||||
|
||||
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
|
||||
|
||||
## Donate
|
||||
If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin or BountySource. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate/).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://bitcoin.org/img/icons/logotop.svg" alt="Bitcoin" /></td>
|
||||
<td><img src="assets/bitcoin_qr_code.png" alt="Bitcoin QR Code" width="100px"/></td>
|
||||
<td><samp>16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://www.bountysource.com/teams/newpipe"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Bountysource.png/320px-Bountysource.png" alz="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>
|
||||
|
||||
## License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "org.schabi.newpipe"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 26
|
||||
versionCode 38
|
||||
versionName "0.10.0"
|
||||
versionCode 40
|
||||
versionName "0.10.2"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -26,6 +26,9 @@ android {
|
||||
debuggable true
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
beta {
|
||||
applicationIdSuffix ".beta"
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
@@ -45,7 +48,8 @@ dependencies {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
compile 'com.github.TeamNewPipe:NewPipeExtractor:7ae274b'
|
||||
compile 'com.github.TeamNewPipe:NewPipeExtractor:b9d0941'
|
||||
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
@@ -62,7 +66,7 @@ dependencies {
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||
compile 'com.nononsenseapps:filepicker:3.0.1'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.5.1'
|
||||
|
||||
debugCompile 'com.facebook.stetho:stetho:1.5.0'
|
||||
|
12
app/proguard-rules.pro
vendored
12
app/proguard-rules.pro
vendored
@@ -24,4 +24,14 @@
|
||||
|
||||
-dontwarn org.mozilla.javascript.tools.**
|
||||
-dontwarn android.arch.util.paging.CountedDataSource
|
||||
-dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource
|
||||
-dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource
|
||||
|
||||
|
||||
# Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick
|
||||
-dontwarn icepick.**
|
||||
-keep class icepick.** { *; }
|
||||
-keep class **$$Icepick { *; }
|
||||
-keepclasseswithmembernames class * {
|
||||
@icepick.* <fields>;
|
||||
}
|
||||
-keepnames class * { @icepick.State *;}
|
||||
|
10
app/src/beta/AndroidManifest.xml
Normal file
10
app/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:label="NewPipe Beta"
|
||||
tools:replace="android:label">
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
app/src/beta/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
app/src/beta/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/beta/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
app/src/beta/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/beta/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
app/src/beta/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
app/src/beta/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
app/src/beta/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
@@ -88,10 +88,14 @@
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
|
||||
|
||||
<activity
|
||||
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
|
||||
android:name=".util.FilePickerActivityHelper"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/FilePickerTheme"/>
|
||||
android:theme="@style/FilePickerThemeDark">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
|
@@ -12,7 +12,6 @@ import java.io.InterruptedIOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
@@ -135,11 +134,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
||||
}
|
||||
|
||||
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
|
||||
for (Map.Entry<String, List<String>> entry : con.getHeaderFields().entrySet()) {
|
||||
System.err.println(entry.getKey() + ": " + entry.getValue());
|
||||
}
|
||||
String inputLine;
|
||||
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
|
@@ -43,9 +43,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
protected void handleUrl(String url) {
|
||||
try {
|
||||
NavigationHelper.openByLink(this, url);
|
||||
} catch (Exception e) {
|
||||
boolean success = NavigationHelper.openByLink(this, url);
|
||||
if (!success) {
|
||||
Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import io.reactivex.Flowable;
|
||||
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE;
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID;
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH;
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID;
|
||||
import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME;
|
||||
|
||||
@@ -27,11 +28,20 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> {
|
||||
@Override
|
||||
int deleteAll();
|
||||
|
||||
@Query("DELETE FROM " + TABLE_NAME + " WHERE " + SEARCH + " = :query")
|
||||
int deleteAllWhereQuery(String query);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<SearchHistoryEntry>> getAll();
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE)
|
||||
@Override
|
||||
Flowable<List<SearchHistoryEntry>> listByService(int serviceId);
|
||||
|
||||
@Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit")
|
||||
Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit);
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import android.arch.persistence.room.Index;
|
||||
import android.arch.persistence.room.PrimaryKey;
|
||||
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_SERVICE_ID;
|
||||
import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_TABLE;
|
||||
@@ -28,7 +29,7 @@ public class SubscriptionEntity {
|
||||
private long uid = 0;
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_SERVICE_ID)
|
||||
private int serviceId = -1;
|
||||
private int serviceId = Constants.NO_SERVICE_ID;
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_URL)
|
||||
private String url;
|
||||
|
@@ -65,6 +65,7 @@ import org.schabi.newpipe.player.PopupVideoPlayer;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
@@ -110,7 +111,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo> implement
|
||||
private boolean wasRelatedStreamsExpanded = false;
|
||||
|
||||
@State
|
||||
protected int serviceId = -1;
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
|
@@ -8,6 +8,7 @@ import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
import java.util.Queue;
|
||||
|
||||
@@ -21,7 +22,7 @@ import io.reactivex.schedulers.Schedulers;
|
||||
public abstract class BaseListInfoFragment<I extends ListInfo> extends BaseListFragment<I, ListExtractor.NextItemsResult> {
|
||||
|
||||
@State
|
||||
protected int serviceId = -1;
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@State
|
||||
protected String name;
|
||||
@State
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
public class SuggestionItem {
|
||||
public final boolean fromHistory;
|
||||
public final String query;
|
||||
|
||||
public SuggestionItem(boolean fromHistory, String query) {
|
||||
this.fromHistory = fromHistory;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + fromHistory + "→" + query + "]";
|
||||
}
|
||||
}
|
@@ -1,89 +1,108 @@
|
||||
package org.schabi.newpipe.fragments.list.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.support.v4.widget.ResourceCursorAdapter;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* SuggestionListAdapter.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@link ResourceCursorAdapter} to display suggestions.
|
||||
*/
|
||||
public class SuggestionListAdapter extends ResourceCursorAdapter {
|
||||
|
||||
private static final String[] columns = new String[]{"_id", "title"};
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_TITLE = 1;
|
||||
public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> {
|
||||
private final ArrayList<SuggestionItem> items = new ArrayList<>();
|
||||
private final Context context;
|
||||
private OnSuggestionItemSelected listener;
|
||||
|
||||
public interface OnSuggestionItemSelected {
|
||||
void onSuggestionItemSelected(SuggestionItem item);
|
||||
void onSuggestionItemLongClick(SuggestionItem item);
|
||||
}
|
||||
|
||||
public SuggestionListAdapter(Context context) {
|
||||
super(context, android.R.layout.simple_list_item_1, null, 0);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setItems(List<SuggestionItem> items) {
|
||||
this.items.clear();
|
||||
this.items.addAll(items);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setListener(OnSuggestionItemSelected listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
ViewHolder viewHolder = new ViewHolder(view);
|
||||
viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the suggestion list
|
||||
* @param suggestions the list of suggestions
|
||||
*/
|
||||
public void updateAdapter(List<String> suggestions) {
|
||||
MatrixCursor cursor = new MatrixCursor(columns, suggestions.size());
|
||||
int i = 0;
|
||||
for (String suggestion : suggestions) {
|
||||
String[] columnValues = new String[columns.length];
|
||||
columnValues[INDEX_TITLE] = suggestion;
|
||||
columnValues[INDEX_ID] = Integer.toString(i);
|
||||
cursor.addRow(columnValues);
|
||||
i++;
|
||||
}
|
||||
changeCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the suggestion for a position
|
||||
* @param position the position of the suggestion
|
||||
* @return the suggestion
|
||||
*/
|
||||
public String getSuggestion(int position) {
|
||||
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
|
||||
public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence convertToString(Cursor cursor) {
|
||||
return cursor.getString(INDEX_TITLE);
|
||||
public void onBindViewHolder(SuggestionItemHolder holder, int position) {
|
||||
final SuggestionItem currentItem = getItem(position);
|
||||
holder.updateFrom(currentItem);
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (listener != null) listener.onSuggestionItemSelected(currentItem);
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (listener != null) listener.onSuggestionItemLongClick(currentItem);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
private final TextView suggestionTitle;
|
||||
private ViewHolder(View view) {
|
||||
this.suggestionTitle = view.findViewById(android.R.id.text1);
|
||||
private SuggestionItem getItem(int position) {
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return getItemCount() == 0;
|
||||
}
|
||||
|
||||
public static class SuggestionItemHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView itemSuggestionQuery;
|
||||
private final ImageView suggestionIcon;
|
||||
|
||||
// Cache some ids, as they can potentially be constantly updated/recycled
|
||||
private final int historyResId;
|
||||
private final int searchResId;
|
||||
|
||||
private SuggestionItemHolder(View rootView) {
|
||||
super(rootView);
|
||||
suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon);
|
||||
itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query);
|
||||
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
||||
}
|
||||
|
||||
private void updateFrom(SuggestionItem item) {
|
||||
suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId);
|
||||
itemSuggestionQuery.setText(item.query);
|
||||
}
|
||||
|
||||
private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
int attributeResourceId = a.getResourceId(0, 0);
|
||||
a.recycle();
|
||||
return attributeResourceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -178,6 +178,10 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O
|
||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
initExoPlayerCache();
|
||||
|
||||
if (audioManager == null) {
|
||||
this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
|
||||
}
|
||||
|
||||
AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
||||
DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory);
|
||||
DefaultLoadControl loadControl = new DefaultLoadControl();
|
||||
|
@@ -7,9 +7,8 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.util.Log;
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper;
|
||||
|
||||
public class DownloadSettingsFragment extends BasePreferenceFragment {
|
||||
private static final int REQUEST_DOWNLOAD_PATH = 0x1235;
|
||||
@@ -48,10 +47,10 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
|
||||
Intent i = new Intent(getActivity(), FilePickerActivity.class)
|
||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
|
||||
Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true)
|
||||
.putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR);
|
||||
if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE)) {
|
||||
startActivityForResult(i, REQUEST_DOWNLOAD_PATH);
|
||||
} else if (preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
|
||||
|
@@ -19,7 +19,7 @@ public class AnimationUtils {
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
||||
public enum Type {
|
||||
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA
|
||||
ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA
|
||||
}
|
||||
|
||||
public static void animateView(View view, boolean enterOrExit, long duration) {
|
||||
@@ -95,9 +95,16 @@ public class AnimationUtils {
|
||||
case LIGHT_SCALE_AND_ALPHA:
|
||||
animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
|
||||
break;
|
||||
case SLIDE_AND_ALPHA:
|
||||
animateSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
|
||||
break;
|
||||
case LIGHT_SLIDE_AND_ALPHA:
|
||||
animateLightSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Animate the background color of a view
|
||||
*/
|
||||
@@ -237,4 +244,50 @@ public class AnimationUtils {
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setTranslationY(-view.getHeight());
|
||||
view.setAlpha(0f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight())
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void animateLightSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
|
||||
if (enterOrExit) {
|
||||
view.setTranslationY(-view.getHeight() / 2);
|
||||
view.setAlpha(0f);
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight() / 2)
|
||||
.setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
view.setVisibility(View.GONE);
|
||||
if (execOnEnd != null) execOnEnd.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,4 +9,6 @@ public class Constants {
|
||||
public static final String KEY_QUERY = "key_query";
|
||||
|
||||
public static final String KEY_THEME_CHANGE = "key_theme_change";
|
||||
|
||||
public static final int NO_SERVICE_ID = -1;
|
||||
}
|
||||
|
@@ -50,7 +50,14 @@ public final class ExtractorHelper {
|
||||
//no instance
|
||||
}
|
||||
|
||||
private static void checkServiceId(int serviceId) {
|
||||
if(serviceId == Constants.NO_SERVICE_ID) {
|
||||
throw new IllegalArgumentException("serviceId is NO_SERVICE_ID");
|
||||
}
|
||||
}
|
||||
|
||||
public static Single<SearchResult> searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<SearchResult>() {
|
||||
@Override
|
||||
public SearchResult call() throws Exception {
|
||||
@@ -61,6 +68,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, final SearchEngine.Filter filter) {
|
||||
checkServiceId(serviceId);
|
||||
return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter)
|
||||
.map(new Function<SearchResult, NextItemsResult>() {
|
||||
@Override
|
||||
@@ -71,6 +79,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId, final String query, final String searchLanguage) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<List<String>>() {
|
||||
@Override
|
||||
public List<String> call() throws Exception {
|
||||
@@ -80,6 +89,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<StreamInfo> getStreamInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<StreamInfo>() {
|
||||
@Override
|
||||
public StreamInfo call() throws Exception {
|
||||
@@ -89,6 +99,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<ChannelInfo> getChannelInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<ChannelInfo>() {
|
||||
@Override
|
||||
public ChannelInfo call() throws Exception {
|
||||
@@ -98,6 +109,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
@@ -107,6 +119,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) {
|
||||
checkServiceId(serviceId);
|
||||
return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable<PlaylistInfo>() {
|
||||
@Override
|
||||
public PlaylistInfo call() throws Exception {
|
||||
@@ -116,6 +129,7 @@ public final class ExtractorHelper {
|
||||
}
|
||||
|
||||
public static Single<NextItemsResult> getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(new Callable<NextItemsResult>() {
|
||||
@Override
|
||||
public NextItemsResult call() throws Exception {
|
||||
@@ -133,6 +147,7 @@ public final class ExtractorHelper {
|
||||
* and put the results in the cache.
|
||||
*/
|
||||
private static <I extends Info> Single<I> checkCache(boolean forceLoad, int serviceId, String url, Single<I> loadFromNetwork) {
|
||||
checkServiceId(serviceId);
|
||||
loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer<I>() {
|
||||
@Override
|
||||
public void accept(@NonNull I i) throws Exception {
|
||||
@@ -157,6 +172,7 @@ public final class ExtractorHelper {
|
||||
* Default implementation uses the {@link InfoCache} to get cached results
|
||||
*/
|
||||
public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) {
|
||||
checkServiceId(serviceId);
|
||||
return Maybe.defer(new Callable<MaybeSource<? extends I>>() {
|
||||
@Override
|
||||
public MaybeSource<? extends I> call() throws Exception {
|
||||
|
@@ -0,0 +1,17 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.FilePickerActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if(ThemeHelper.isLightThemeSelected(this)) {
|
||||
this.setTheme(R.style.FilePickerThemeLight);
|
||||
} else {
|
||||
this.setTheme(R.style.FilePickerThemeDark);
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package org.schabi.newpipe.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.LinearSmoothScroller;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
public class LayoutManagerSmoothScroller extends LinearLayoutManager {
|
||||
|
||||
public LayoutManagerSmoothScroller(Context context) {
|
||||
super(context, VERTICAL, false);
|
||||
}
|
||||
|
||||
public LayoutManagerSmoothScroller(Context context, int orientation, boolean reverseLayout) {
|
||||
super(context, orientation, reverseLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
|
||||
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
|
||||
smoothScroller.setTargetPosition(position);
|
||||
startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
|
||||
public TopSnappedSmoothScroller(Context context) {
|
||||
super(context);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
return LayoutManagerSmoothScroller.this
|
||||
.computeScrollVectorForPosition(targetPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVerticalSnapPreference() {
|
||||
return SNAP_TO_START;
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,7 +14,9 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.about.AboutActivity;
|
||||
import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
@@ -228,13 +230,17 @@ public class NavigationHelper {
|
||||
// Link handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void openByLink(Context context, String url) throws Exception {
|
||||
Intent intentByLink = getIntentByLink(context, url);
|
||||
if (intentByLink == null)
|
||||
throw new NullPointerException("getIntentByLink(context = [" + context + "], url = [" + url + "]) returned null");
|
||||
public static boolean openByLink(Context context, String url) {
|
||||
Intent intentByLink;
|
||||
try {
|
||||
intentByLink = getIntentByLink(context, url);
|
||||
} catch (ExtractionException e) {
|
||||
return false;
|
||||
}
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intentByLink);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
|
||||
@@ -245,14 +251,20 @@ public class NavigationHelper {
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
private static Intent getIntentByLink(Context context, String url) throws Exception {
|
||||
StreamingService service = NewPipe.getServiceByUrl(url);
|
||||
public static Intent getIntentByLink(Context context, String url) throws ExtractionException {
|
||||
return getIntentByLink(context, NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static Intent getIntentByLink(Context context, StreamingService service, String url) throws ExtractionException {
|
||||
if (service != ServiceList.YouTube.getService()) {
|
||||
throw new ExtractionException("Service not supported at the moment");
|
||||
}
|
||||
|
||||
int serviceId = service.getServiceId();
|
||||
StreamingService.LinkType linkType = service.getLinkTypeByUrl(url);
|
||||
|
||||
if (linkType == StreamingService.LinkType.NONE) {
|
||||
throw new Exception("Url not known to service. service=" + serviceId + " url=" + url);
|
||||
throw new ExtractionException("Url not known to service. service=" + serviceId + " url=" + url);
|
||||
}
|
||||
|
||||
url = getCleanUrl(service, url, linkType);
|
||||
@@ -268,7 +280,7 @@ public class NavigationHelper {
|
||||
return rIntent;
|
||||
}
|
||||
|
||||
private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws Exception {
|
||||
private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException {
|
||||
switch (linkType) {
|
||||
case STREAM:
|
||||
return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl);
|
||||
|
@@ -21,6 +21,7 @@ package org.schabi.newpipe.util;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@@ -29,6 +30,7 @@ import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
|
||||
import java.io.File;
|
||||
@@ -110,6 +112,7 @@ public class StateSaver {
|
||||
/**
|
||||
* Try to restore the state from memory and disk, using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead.
|
||||
*/
|
||||
@Nullable
|
||||
private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], writeRead = [" + writeRead + "]");
|
||||
@@ -117,7 +120,7 @@ public class StateSaver {
|
||||
|
||||
FileInputStream fileInputStream = null;
|
||||
try {
|
||||
Queue<Object> savedObjects = stateObjectsHolder.remove(savedState.prefixFileSaved);
|
||||
Queue<Object> savedObjects = stateObjectsHolder.remove(savedState.getPrefixFileSaved());
|
||||
if (savedObjects != null) {
|
||||
writeRead.readFrom(savedObjects);
|
||||
if (MainActivity.DEBUG) {
|
||||
@@ -126,8 +129,13 @@ public class StateSaver {
|
||||
return savedState;
|
||||
}
|
||||
|
||||
File file = new File(savedState.pathFileSaved);
|
||||
if (!file.exists()) return null;
|
||||
File file = new File(savedState.getPathFileSaved());
|
||||
if (!file.exists()) {
|
||||
if(MainActivity.DEBUG) {
|
||||
Log.d(TAG, "Cache file doesn't exist: " + file.getAbsolutePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fileInputStream = new FileInputStream(file);
|
||||
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
|
||||
@@ -139,7 +147,7 @@ public class StateSaver {
|
||||
|
||||
return savedState;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Failed to restore state", e);
|
||||
} finally {
|
||||
if (fileInputStream != null) {
|
||||
try {
|
||||
@@ -154,10 +162,17 @@ public class StateSaver {
|
||||
/**
|
||||
* @see #tryToSave(boolean, String, String, WriteRead)
|
||||
*/
|
||||
@Nullable
|
||||
public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedState savedState, Bundle outState, WriteRead writeRead) {
|
||||
String currentSavedPrefix = savedState == null || TextUtils.isEmpty(savedState.prefixFileSaved)
|
||||
? System.nanoTime() - writeRead.hashCode() + ""
|
||||
: savedState.prefixFileSaved;
|
||||
@NonNull
|
||||
String currentSavedPrefix;
|
||||
if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) {
|
||||
// Generate unique prefix
|
||||
currentSavedPrefix = System.nanoTime() - writeRead.hashCode() + "";
|
||||
} else {
|
||||
// Reuse prefix
|
||||
currentSavedPrefix = savedState.getPrefixFileSaved();
|
||||
}
|
||||
|
||||
savedState = tryToSave(isChangingConfig, currentSavedPrefix, writeRead.generateSuffix(), writeRead);
|
||||
if (savedState != null) {
|
||||
@@ -173,22 +188,33 @@ public class StateSaver {
|
||||
* to the file with the name of prefixFileName + suffixFileName, in a cache folder got from the {@link #init(Context)}.
|
||||
* <p>
|
||||
* It checks if the file already exists and if it does, just return the path, so a good way to save is:
|
||||
* <li> A fixed prefix for the file
|
||||
* <li> A changing suffix
|
||||
* <ul>
|
||||
* <li> A fixed prefix for the file</li>
|
||||
* <li> A changing suffix</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param isChangingConfig
|
||||
* @param prefixFileName
|
||||
* @param suffixFileName
|
||||
* @param writeRead
|
||||
*/
|
||||
@Nullable
|
||||
private static SavedState tryToSave(boolean isChangingConfig, final String prefixFileName, String suffixFileName, WriteRead writeRead) {
|
||||
if (MainActivity.DEBUG) {
|
||||
Log.d(TAG, "tryToSave() called with: isChangingConfig = [" + isChangingConfig + "], prefixFileName = [" + prefixFileName + "], suffixFileName = [" + suffixFileName + "], writeRead = [" + writeRead + "]");
|
||||
}
|
||||
|
||||
Queue<Object> savedObjects = new LinkedList<>();
|
||||
LinkedList<Object> savedObjects = new LinkedList<>();
|
||||
writeRead.writeTo(savedObjects);
|
||||
|
||||
if (isChangingConfig) {
|
||||
if (savedObjects.size() > 0) {
|
||||
stateObjectsHolder.put(prefixFileName, savedObjects);
|
||||
return new SavedState(prefixFileName, "");
|
||||
} else return null;
|
||||
} else {
|
||||
if(MainActivity.DEBUG) Log.d(TAG, "Nothing to save");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FileOutputStream fileOutputStream = null;
|
||||
@@ -197,8 +223,12 @@ public class StateSaver {
|
||||
if (!cacheDir.exists()) throw new RuntimeException("Cache dir does not exist > " + cacheDirPath);
|
||||
cacheDir = new File(cacheDir, CACHE_DIR_NAME);
|
||||
if (!cacheDir.exists()) {
|
||||
boolean mkdirResult = cacheDir.mkdir();
|
||||
if (!mkdirResult) return null;
|
||||
if(!cacheDir.mkdir()) {
|
||||
if(BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Failed to create cache directory " + cacheDir.getAbsolutePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(suffixFileName)) suffixFileName = ".cache";
|
||||
@@ -214,7 +244,9 @@ public class StateSaver {
|
||||
return name.contains(prefixFileName);
|
||||
}
|
||||
});
|
||||
for (File file1 : files) file1.delete();
|
||||
for (File fileToDelete : files) {
|
||||
fileToDelete.delete();
|
||||
}
|
||||
}
|
||||
|
||||
fileOutputStream = new FileOutputStream(file);
|
||||
@@ -223,7 +255,7 @@ public class StateSaver {
|
||||
|
||||
return new SavedState(prefixFileName, file.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Failed to save state", e);
|
||||
} finally {
|
||||
if (fileOutputStream != null) {
|
||||
try {
|
||||
@@ -241,11 +273,11 @@ public class StateSaver {
|
||||
public static void onDestroy(SavedState savedState) {
|
||||
if (MainActivity.DEBUG) Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]");
|
||||
|
||||
if (savedState != null && !TextUtils.isEmpty(savedState.pathFileSaved)) {
|
||||
stateObjectsHolder.remove(savedState.prefixFileSaved);
|
||||
if (savedState != null && !TextUtils.isEmpty(savedState.getPathFileSaved())) {
|
||||
stateObjectsHolder.remove(savedState.getPrefixFileSaved());
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(savedState.pathFileSaved).delete();
|
||||
new File(savedState.getPathFileSaved()).delete();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
@@ -271,9 +303,12 @@ public class StateSaver {
|
||||
// Inner
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Information about the saved state on the disk
|
||||
*/
|
||||
public static class SavedState implements Parcelable {
|
||||
public String prefixFileSaved;
|
||||
public String pathFileSaved;
|
||||
private final String prefixFileSaved;
|
||||
private final String pathFileSaved;
|
||||
|
||||
public SavedState(String prefixFileSaved, String pathFileSaved) {
|
||||
this.prefixFileSaved = prefixFileSaved;
|
||||
@@ -287,7 +322,7 @@ public class StateSaver {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return prefixFileSaved + " > " + pathFileSaved;
|
||||
return getPrefixFileSaved() + " > " + getPathFileSaved();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,6 +348,22 @@ public class StateSaver {
|
||||
return new SavedState[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the prefix of the saved file
|
||||
* @return the file prefix
|
||||
*/
|
||||
public String getPrefixFileSaved() {
|
||||
return prefixFileSaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the saved file
|
||||
* @return the path to the saved file
|
||||
*/
|
||||
public String getPathFileSaved() {
|
||||
return pathFileSaved;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -101,6 +101,18 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of mission by its timestamp. Oldest first
|
||||
* @param missions the missions to sort
|
||||
*/
|
||||
static void sortByTimestamp(List<DownloadMission> missions) {
|
||||
Collections.sort(missions, new Comparator<DownloadMission>() {
|
||||
@Override
|
||||
public int compare(DownloadMission o1, DownloadMission o2) {
|
||||
return Long.valueOf(o1.timestamp).compareTo(o2.timestamp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads finished missions from the data source
|
||||
@@ -111,12 +123,8 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||
finishedMissions = new ArrayList<>();
|
||||
}
|
||||
// Ensure its sorted
|
||||
Collections.sort(finishedMissions, new Comparator<DownloadMission>() {
|
||||
@Override
|
||||
public int compare(DownloadMission o1, DownloadMission o2) {
|
||||
return (int) (o1.timestamp - o2.timestamp);
|
||||
}
|
||||
});
|
||||
sortByTimestamp(finishedMissions);
|
||||
|
||||
mMissions.ensureCapacity(mMissions.size() + finishedMissions.size());
|
||||
for (DownloadMission mission : finishedMissions) {
|
||||
File downloadedFile = mission.getDownloadedFile();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user