mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-20 11:20:52 +02:00
Compare commits
183 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d6d8c7830c | ||
![]() |
f53a0f0d07 | ||
![]() |
17edd1c3d4 | ||
![]() |
b0685c153a | ||
![]() |
ade5e38fa5 | ||
![]() |
7385aa09a8 | ||
![]() |
185a5fad88 | ||
![]() |
53ffc82fe2 | ||
![]() |
801267df18 | ||
![]() |
6e73e0b395 | ||
![]() |
88c86e88b0 | ||
![]() |
19e152a54b | ||
![]() |
2898bead66 | ||
![]() |
381c329441 | ||
![]() |
e028a63f30 | ||
![]() |
4274827dbe | ||
![]() |
7a30f4a7d2 | ||
![]() |
d0c03a0211 | ||
![]() |
c900ef036c | ||
![]() |
d088d432c5 | ||
![]() |
c9fafbe198 | ||
![]() |
2d909b0514 | ||
![]() |
c2a012553d | ||
![]() |
085f0266ac | ||
![]() |
7ede2daa3c | ||
![]() |
713c53d170 | ||
![]() |
110b3a6a8f | ||
![]() |
e12e6dd7a7 | ||
![]() |
e183fc6118 | ||
![]() |
dd57e246b8 | ||
![]() |
f4a4172369 | ||
![]() |
b96d1714b5 | ||
![]() |
dbd809b040 | ||
![]() |
ff4e6b139d | ||
![]() |
af098aaac8 | ||
![]() |
5d7e62c736 | ||
![]() |
e2ead011f5 | ||
![]() |
a067c950e1 | ||
![]() |
3369618cfa | ||
![]() |
4e9b6520e5 | ||
![]() |
a074203fae | ||
![]() |
42092e3f28 | ||
![]() |
c07b34e298 | ||
![]() |
ef5f181328 | ||
![]() |
a7ea2fcf92 | ||
![]() |
e81730715c | ||
![]() |
c09f2ad482 | ||
![]() |
84aef8b5b5 | ||
![]() |
8fac3e8221 | ||
![]() |
5874ed781d | ||
![]() |
d8f29bd7a7 | ||
![]() |
8120b6aaaa | ||
![]() |
13a0d1de70 | ||
![]() |
20e828be51 | ||
![]() |
ccd82fb8b8 | ||
![]() |
0711650ff8 | ||
![]() |
4194ac2226 | ||
![]() |
248e2d7ee0 | ||
![]() |
1daa654051 | ||
![]() |
d751434979 | ||
![]() |
8cc21920b7 | ||
![]() |
248212588d | ||
![]() |
13c0fdef08 | ||
![]() |
dfc27b2480 | ||
![]() |
b2d78d380b | ||
![]() |
faa6cb5c7d | ||
![]() |
452977abdf | ||
![]() |
93570b2f59 | ||
![]() |
073f5c2c8c | ||
![]() |
1b4313f847 | ||
![]() |
07cead7e99 | ||
![]() |
f128751aba | ||
![]() |
9516d9da17 | ||
![]() |
e9aafc2a56 | ||
![]() |
a91a6575e0 | ||
![]() |
5bd4093dfb | ||
![]() |
f9890e2016 | ||
![]() |
00529fe134 | ||
![]() |
5e9dce7d39 | ||
![]() |
734680b9f0 | ||
![]() |
d0b5345252 | ||
![]() |
c2b4a44a59 | ||
![]() |
38c79bbc11 | ||
![]() |
0d7028a36c | ||
![]() |
b63f687491 | ||
![]() |
952b636569 | ||
![]() |
e14ba48244 | ||
![]() |
720c8c31ec | ||
![]() |
4e8407ed8f | ||
![]() |
26c5f69161 | ||
![]() |
078ae15794 | ||
![]() |
16ae90dc9f | ||
![]() |
3de5afc68e | ||
![]() |
a7e8f5087e | ||
![]() |
2e6e75cd4e | ||
![]() |
9f3b35634a | ||
![]() |
c24dfc63dc | ||
![]() |
c029929850 | ||
![]() |
d9100913d5 | ||
![]() |
e03d970bc2 | ||
![]() |
fe4516ea23 | ||
![]() |
039b47b872 | ||
![]() |
0a57a8a7f3 | ||
![]() |
8c823f3a2d | ||
![]() |
33f3a4f455 | ||
![]() |
2ecf8044d7 | ||
![]() |
c9a1eb55b5 | ||
![]() |
76bb1fd61e | ||
![]() |
1275b26ba0 | ||
![]() |
a470a4af9b | ||
![]() |
487c9ebbd4 | ||
![]() |
746cab92f0 | ||
![]() |
33266a96ff | ||
![]() |
2816889d8d | ||
![]() |
58e177b3e4 | ||
![]() |
5cfd8bbb56 | ||
![]() |
e6fe6fd645 | ||
![]() |
63e167b38e | ||
![]() |
abe77c4783 | ||
![]() |
72af51fe9d | ||
![]() |
36b4134838 | ||
![]() |
cf6ee26fdb | ||
![]() |
858111e623 | ||
![]() |
367e625804 | ||
![]() |
ae4d9c7f80 | ||
![]() |
6c6ee41346 | ||
![]() |
9ef7688f9e | ||
![]() |
7d6e226c2b | ||
![]() |
17d1346a8a | ||
![]() |
59e0c10c42 | ||
![]() |
0d29e66092 | ||
![]() |
caa000f447 | ||
![]() |
267e114354 | ||
![]() |
b5375396d2 | ||
![]() |
e34f666b70 | ||
![]() |
19334b4f96 | ||
![]() |
1fa609e539 | ||
![]() |
d9ce25a721 | ||
![]() |
5b8fc25da6 | ||
![]() |
c70968dcf1 | ||
![]() |
77147510fb | ||
![]() |
7092577482 | ||
![]() |
3e70050056 | ||
![]() |
1f23c814e5 | ||
![]() |
145e0a0b7b | ||
![]() |
e68e787e7a | ||
![]() |
903308d285 | ||
![]() |
a4d8388b2e | ||
![]() |
3e83f9f956 | ||
![]() |
4063221313 | ||
![]() |
c3c8d80919 | ||
![]() |
3ae71c73c4 | ||
![]() |
b3db8c9549 | ||
![]() |
596eb4a0f9 | ||
![]() |
6b0381b903 | ||
![]() |
049c8f70cd | ||
![]() |
353bf69550 | ||
![]() |
cdb989ede3 | ||
![]() |
74079e4238 | ||
![]() |
c89746214c | ||
![]() |
1d97db3ef9 | ||
![]() |
e43fdf5ef9 | ||
![]() |
3984fc075f | ||
![]() |
ee2a159374 | ||
![]() |
57c9b29ba3 | ||
![]() |
927ea72337 | ||
![]() |
123bdbd13b | ||
![]() |
0baa5a2f04 | ||
![]() |
7d98a70028 | ||
![]() |
73b72ab01c | ||
![]() |
e8cf71f41c | ||
![]() |
5f2d2a64d2 | ||
![]() |
27bf3901de | ||
![]() |
16d4fa03a5 | ||
![]() |
bef579ec26 | ||
![]() |
0cff10e02e | ||
![]() |
4c6f7238dd | ||
![]() |
55027a9b2b | ||
![]() |
0a984ca8c8 | ||
![]() |
929d13bfea | ||
![]() |
1975973ff2 | ||
![]() |
63afacc067 | ||
![]() |
3175199787 |
@@ -13,8 +13,8 @@ android {
|
||||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 29
|
||||
versionCode 940
|
||||
versionName "0.19.4"
|
||||
versionCode 952
|
||||
versionName "0.19.7"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -50,7 +50,7 @@ android {
|
||||
// TODO: update Gradle version
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
shrinkResources false // disabled to fix F-Droid's reproducible build
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
archivesBaseName = 'app'
|
||||
}
|
||||
@@ -84,13 +84,18 @@ ext {
|
||||
checkstyleVersion = '8.32'
|
||||
stethoVersion = '1.5.1'
|
||||
leakCanaryVersion = '2.2'
|
||||
exoPlayerVersion = '2.11.4'
|
||||
exoPlayerVersion = '2.11.6'
|
||||
androidxLifecycleVersion = '2.2.0'
|
||||
androidxRoomVersion = '2.2.5'
|
||||
groupieVersion = '2.8.0'
|
||||
markwonVersion = '4.3.1'
|
||||
}
|
||||
|
||||
configurations {
|
||||
checkstyle
|
||||
ktlint
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
configFile rootProject.file('checkstyle.xml')
|
||||
ignoreFailures false
|
||||
@@ -106,8 +111,7 @@ task runCheckstyle(type: Checkstyle) {
|
||||
exclude '**/BuildConfig.java'
|
||||
exclude 'main/java/us/shandian/giga/**'
|
||||
|
||||
// empty classpath
|
||||
classpath = files()
|
||||
classpath = configurations.checkstyle
|
||||
|
||||
showViolations true
|
||||
|
||||
@@ -117,10 +121,6 @@ task runCheckstyle(type: Checkstyle) {
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
ktlint
|
||||
}
|
||||
|
||||
task runKtlint(type: JavaExec) {
|
||||
main = "com.pinterest.ktlint.Main"
|
||||
classpath = configurations.ktlint
|
||||
@@ -143,7 +143,7 @@ dependencies {
|
||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
ktlint "com.pinterest:ktlint:0.35.0"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
@@ -163,7 +163,7 @@ dependencies {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:98055a3c3c17f2543a63d375a44c1d1f557fa76e'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:b4481dfec21cf39aabbb791290d30130235aeabd'
|
||||
|
||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||
@@ -37,7 +38,6 @@ import java.net.SocketException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.exceptions.CompositeException;
|
||||
import io.reactivex.exceptions.MissingBackpressureException;
|
||||
import io.reactivex.exceptions.OnErrorNotImplementedException;
|
||||
|
@@ -34,8 +34,6 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
@@ -127,12 +125,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Window w = getWindow();
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager() != null
|
||||
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
@@ -166,6 +158,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
||||
.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
drawerItems.getMenu()
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -26,6 +24,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
/**
|
||||
@@ -143,28 +142,23 @@ public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.github_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
||||
|
||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
donationLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.donation_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
||||
|
||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
websiteLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.website_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
||||
|
||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||
privacyPolicyLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.privacy_policy_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void openWebsite(final String url, final Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,33 @@
|
||||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaylistLocalItem extends LocalItem {
|
||||
String getOrderingName();
|
||||
|
||||
static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
final List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
final String on1 = left.getOrderingName();
|
||||
final String on2 = right.getOrderingName();
|
||||
if (on1 == null) {
|
||||
return on2 == null ? 0 : 1;
|
||||
} else {
|
||||
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions
|
||||
|
||||
WHERE name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngrouped(
|
||||
currentGroupId: Long
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
AND s.name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId: Long,
|
||||
filter: String
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@@ -52,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
entity.uid = uidFromInsert
|
||||
} else {
|
||||
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
entity.uid = subscriptionIdFromDb
|
||||
|
||||
update(entity)
|
||||
|
@@ -130,4 +130,55 @@ public class SubscriptionEntity {
|
||||
item.setDescription(getDescription());
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
|
||||
@Override
|
||||
@SuppressWarnings("EqualsReplaceableByObjectsCall")
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SubscriptionEntity that = (SubscriptionEntity) o;
|
||||
|
||||
if (uid != that.uid) {
|
||||
return false;
|
||||
}
|
||||
if (serviceId != that.serviceId) {
|
||||
return false;
|
||||
}
|
||||
if (!url.equals(that.url)) {
|
||||
return false;
|
||||
}
|
||||
if (name != null ? !name.equals(that.name) : that.name != null) {
|
||||
return false;
|
||||
}
|
||||
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
|
||||
return false;
|
||||
}
|
||||
if (subscriberCount != null
|
||||
? !subscriberCount.equals(that.subscriberCount)
|
||||
: that.subscriberCount != null) {
|
||||
return false;
|
||||
}
|
||||
return description != null
|
||||
? description.equals(that.description)
|
||||
: that.description == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (uid ^ (uid >>> 32));
|
||||
result = 31 * result + serviceId;
|
||||
result = 31 * result + url.hashCode();
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
|
||||
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
|
||||
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -365,10 +365,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
public void onSaveInstanceState(final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
// Check if the next video label and video is visible,
|
||||
// if it is, include the two elements in the next check
|
||||
int nextCount = currentInfo != null && currentInfo.getNextVideo() != null ? 2 : 0;
|
||||
|
||||
if (!isLoading.get() && currentInfo != null && isVisible()) {
|
||||
outState.putSerializable(INFO_KEY, currentInfo);
|
||||
}
|
||||
@@ -479,7 +475,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
case R.id.detail_controls_download:
|
||||
NavigationHelper.openDownloads(getActivity());
|
||||
break;
|
||||
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
Log.w(TAG,
|
||||
@@ -488,6 +483,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||
}
|
||||
break;
|
||||
case R.id.detail_title_root_layout:
|
||||
ShareUtils.copyToClipboard(getContext(), videoTitleTextView.getText().toString());
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -583,6 +581,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
videoTitleRoot.setOnLongClickListener(this);
|
||||
uploaderRootLayout.setOnClickListener(this);
|
||||
uploaderRootLayout.setOnLongClickListener(this);
|
||||
videoTitleRoot.setOnClickListener(this);
|
||||
@@ -1015,14 +1014,14 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
}
|
||||
|
||||
private void prepareDescription(final Description description) {
|
||||
if (TextUtils.isEmpty(description.getContent())
|
||||
if (description == null || TextUtils.isEmpty(description.getContent())
|
||||
|| description == Description.emptyDescription) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (description.getType() == Description.HTML) {
|
||||
disposables.add(Single.just(description.getContent())
|
||||
.map((@io.reactivex.annotations.NonNull String descriptionText) -> {
|
||||
.map((@NonNull String descriptionText) -> {
|
||||
Spanned parsedDescription;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
parsedDescription = Html.fromHtml(descriptionText, 0);
|
||||
@@ -1034,7 +1033,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
})
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe((@io.reactivex.annotations.NonNull Spanned spanned) -> {
|
||||
.subscribe((@NonNull Spanned spanned) -> {
|
||||
videoDescriptionView.setText(spanned);
|
||||
videoDescriptionView.setVisibility(View.VISIBLE);
|
||||
}));
|
||||
|
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||
|
||||
@@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
protected String url;
|
||||
|
||||
protected I currentInfo;
|
||||
protected String currentNextPageUrl;
|
||||
protected Page currentNextPage;
|
||||
protected Disposable currentWorker;
|
||||
|
||||
@Override
|
||||
@@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentInfo);
|
||||
objectsToSave.add(currentNextPageUrl);
|
||||
objectsToSave.add(currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentInfo = (I) savedObjects.poll();
|
||||
currentNextPageUrl = (String) savedObjects.poll();
|
||||
currentNextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -130,7 +131,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
.subscribe((@NonNull I result) -> {
|
||||
isLoading.set(false);
|
||||
currentInfo = result;
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
handleResult(result);
|
||||
}, (@NonNull Throwable throwable) -> onError(throwable));
|
||||
}
|
||||
@@ -157,11 +158,10 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doFinally(this::allowDownwardFocusScroll)
|
||||
.subscribe((@io.reactivex.annotations.NonNull
|
||||
ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
.subscribe((@NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> {
|
||||
isLoading.set(false);
|
||||
handleNextItems(InfoItemsPage);
|
||||
}, (@io.reactivex.annotations.NonNull Throwable throwable) -> {
|
||||
}, (@NonNull Throwable throwable) -> {
|
||||
isLoading.set(false);
|
||||
onError(throwable);
|
||||
});
|
||||
@@ -182,7 +182,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
|
||||
showListFooter(hasMoreItems());
|
||||
@@ -190,7 +190,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
return !TextUtils.isEmpty(currentNextPageUrl);
|
||||
return Page.isValid(currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -403,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -555,7 +555,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
}
|
||||
}
|
||||
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(), streamItems, index);
|
||||
currentInfo.getNextPage(), streamItems, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -71,7 +71,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -44,7 +44,7 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||
name = kioskTranslatedName;
|
||||
|
||||
currentInfo = null;
|
||||
currentNextPageUrl = null;
|
||||
currentNextPage = null;
|
||||
} catch (ExtractionException e) {
|
||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||
"Loading default kiosk from selected service", 0);
|
||||
|
@@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
@Override
|
||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,7 +349,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
return new PlaylistPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(),
|
||||
currentInfo.getNextPage(),
|
||||
infoItems,
|
||||
index
|
||||
);
|
||||
|
@@ -7,6 +7,7 @@ import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
@@ -37,6 +38,7 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
@@ -71,6 +73,7 @@ import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static android.text.Html.escapeHtml;
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
@@ -118,13 +121,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@State
|
||||
String lastSearchedString;
|
||||
|
||||
@State
|
||||
String searchSuggestion;
|
||||
|
||||
@State
|
||||
boolean isCorrectedSearch;
|
||||
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
private StreamingService service;
|
||||
private String currentPageUrl;
|
||||
private String nextPageUrl;
|
||||
private Page nextPage;
|
||||
private String contentCountry;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
|
||||
@@ -143,6 +151,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
private EditText searchEditText;
|
||||
private View searchClear;
|
||||
|
||||
private TextView correctSuggestion;
|
||||
|
||||
private View suggestionsPanel;
|
||||
private RecyclerView suggestionsRecyclerView;
|
||||
|
||||
@@ -257,6 +267,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
}
|
||||
@@ -345,6 +357,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
|
||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -354,15 +368,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@Override
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentPageUrl);
|
||||
objectsToSave.add(nextPageUrl);
|
||||
objectsToSave.add(nextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentPageUrl = (String) savedObjects.poll();
|
||||
nextPageUrl = (String) savedObjects.poll();
|
||||
nextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -497,6 +509,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return;
|
||||
}
|
||||
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<>());
|
||||
showKeyboardSearch();
|
||||
@@ -554,11 +568,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start,
|
||||
final int count, final int after) { }
|
||||
final int count, final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start,
|
||||
final int before, final int count) { }
|
||||
final int before, final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
@@ -688,10 +704,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return false;
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initSuggestionObserver() called");
|
||||
@@ -845,7 +857,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
||||
if (!Page.isValid(nextPage)) {
|
||||
return;
|
||||
}
|
||||
isLoading.set(true);
|
||||
@@ -858,7 +870,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl)
|
||||
nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
@@ -961,9 +973,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
|
||||
searchSuggestion = result.getSearchSuggestion();
|
||||
isCorrectedSearch = result.isCorrectedSearch();
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
lastSearchedString = searchString;
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
currentPageUrl = result.getUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (!result.getRelatedItems().isEmpty()) {
|
||||
@@ -978,17 +994,49 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
super.handleResult(result);
|
||||
}
|
||||
|
||||
private void handleSearchSuggestion() {
|
||||
if (TextUtils.isEmpty(searchSuggestion)) {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
} else {
|
||||
final String helperText = getString(isCorrectedSearch
|
||||
? R.string.search_showing_result_for
|
||||
: R.string.did_you_mean);
|
||||
|
||||
final String highlightedSearchSuggestion =
|
||||
"<b><i>" + escapeHtml(searchSuggestion) + "</i></b>";
|
||||
correctSuggestion.setText(
|
||||
Html.fromHtml(String.format(helperText, highlightedSearchSuggestion)));
|
||||
|
||||
|
||||
correctSuggestion.setOnClickListener(v -> {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
search(searchSuggestion, contentFilter, sortFilter);
|
||||
searchEditText.setText(searchSuggestion);
|
||||
});
|
||||
|
||||
correctSuggestion.setOnLongClickListener(v -> {
|
||||
searchEditText.setText(searchSuggestion);
|
||||
searchEditText.setSelection(searchSuggestion.length());
|
||||
showKeyboardSearch();
|
||||
return true;
|
||||
});
|
||||
|
||||
correctSuggestion.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
showListFooter(false);
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||
+ "pageIds: " + nextPage.getIds() + ", "
|
||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
}
|
||||
@@ -1020,6 +1068,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0,
|
||||
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
|
@@ -9,7 +9,6 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -40,9 +39,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private View headerRootLayout;
|
||||
private Switch aSwitch;
|
||||
|
||||
private boolean mIsVisibleToUser = false;
|
||||
private Switch autoplaySwitch;
|
||||
|
||||
public static RelatedVideosFragment getInstance(final StreamInfo info) {
|
||||
RelatedVideosFragment instance = new RelatedVideosFragment();
|
||||
@@ -50,12 +47,6 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
mIsVisibleToUser = isVisibleToUser;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -81,22 +72,18 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
}
|
||||
|
||||
protected View getListHeader() {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getNextStream() != null) {
|
||||
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
|
||||
headerRootLayout = activity.getLayoutInflater()
|
||||
.inflate(R.layout.related_streams_header, itemsList, false);
|
||||
aSwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
autoplaySwitch = headerRootLayout.findViewById(R.id.autoplay_switch);
|
||||
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
Boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
aSwitch.setChecked(autoplay);
|
||||
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton compoundButton,
|
||||
final boolean b) {
|
||||
final SharedPreferences pref = PreferenceManager
|
||||
.getDefaultSharedPreferences(getContext());
|
||||
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext()).edit()
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply();
|
||||
}
|
||||
});
|
||||
.putBoolean(getString(R.string.auto_queue_key), b).apply());
|
||||
return headerRootLayout;
|
||||
} else {
|
||||
return null;
|
||||
@@ -105,7 +92,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return Single.fromCallable(() -> ListExtractor.InfoItemsPage.emptyPage());
|
||||
return Single.fromCallable(ListExtractor.InfoItemsPage::emptyPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -216,8 +203,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
|
||||
final String s) {
|
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
|
||||
if (null != aSwitch) {
|
||||
aSwitch.setChecked(autoplay);
|
||||
if (autoplaySwitch != null) {
|
||||
autoplaySwitch.setChecked(autoplay);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,15 +1,11 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
@@ -24,6 +20,7 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -129,14 +126,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
|
||||
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (!AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
|
||||
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
if (AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
openCommentAuthor(item);
|
||||
} else {
|
||||
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
@@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
// Fragment LifeCycle - Creation
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
String on1 = left.getOrderingName();
|
||||
String on2 = right.getOrderingName();
|
||||
if (on1 == null && on2 == null) {
|
||||
return 0;
|
||||
} else if (on1 != null && on2 == null) {
|
||||
return -1;
|
||||
} else if (on1 == null && on2 != null) {
|
||||
return 1;
|
||||
} else {
|
||||
return on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
super.startLoading(forceLoad);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistsSubscriber());
|
||||
|
@@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.extractor.ListInfo
|
||||
@@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
|
||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
fun subscriptions() = subscriptionTable.all
|
||||
|
||||
fun getSubscriptions(
|
||||
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
filterQuery: String = "",
|
||||
showOnlyUngrouped: Boolean = false
|
||||
): Flowable<List<SubscriptionEntity>> {
|
||||
return when {
|
||||
filterQuery.isNotEmpty() -> {
|
||||
return if (showOnlyUngrouped) {
|
||||
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId, filterQuery)
|
||||
} else {
|
||||
subscriptionTable.getSubscriptionsFiltered(filterQuery)
|
||||
}
|
||||
}
|
||||
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
||||
else -> subscriptionTable.all
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
||||
val listEntities = subscriptionTable.upsertAll(
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
|
||||
database.runInTransaction {
|
||||
infoList.forEachIndexed { index, info ->
|
||||
@@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
|
||||
}
|
||||
|
||||
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
||||
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
||||
@@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
|
||||
|
||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -9,42 +9,56 @@ import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
|
||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
||||
}
|
||||
}
|
||||
class FeedGroupDialogViewModel(
|
||||
applicationContext: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialQuery: String = "",
|
||||
initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||
private var subscriptionManager = SubscriptionManager(applicationContext)
|
||||
|
||||
private var filterSubscriptions = BehaviorProcessor.create<String>()
|
||||
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
|
||||
|
||||
private var subscriptionsFlowable = Flowable
|
||||
.combineLatest(
|
||||
filterSubscriptions.startWith(initialQuery),
|
||||
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
|
||||
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.switchMap { filter ->
|
||||
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
|
||||
}.map { list -> list.map { PickerSubscriptionItem(it) } }
|
||||
|
||||
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
||||
|
||||
private var actionProcessingDisposable: Disposable? = null
|
||||
|
||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
|
||||
private var subscriptionsDisposable = Flowable
|
||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
@@ -55,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
|
||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
}
|
||||
|
||||
fun deleteGroup() {
|
||||
@@ -74,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||
|
||||
actionProcessingDisposable = completable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
}
|
||||
}
|
||||
|
||||
fun filterSubscriptionsBy(query: String) {
|
||||
filterSubscriptions.onNext(query)
|
||||
}
|
||||
|
||||
fun clearSubscriptionsFilter() {
|
||||
filterSubscriptions.onNext("")
|
||||
}
|
||||
|
||||
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
|
||||
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
|
||||
}
|
||||
|
||||
sealed class DialogEvent {
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
|
||||
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
private val initialQuery: String = "",
|
||||
private val initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext,
|
||||
groupId, initialQuery, initialShowOnlyUngrouped) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
||||
class EmptyPlaceholderItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||
}
|
||||
|
@@ -1,39 +1,28 @@
|
||||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.View
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.util.AnimationUtils
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
|
||||
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
|
||||
companion object {
|
||||
const val UPDATE_SELECTED = 123
|
||||
|
||||
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
|
||||
}
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
var isSelected: Boolean = false
|
||||
) : Item() {
|
||||
override fun getId(): Long = subscriptionEntity.uid
|
||||
override fun getLayout(): Int = R.layout.picker_subscription_item
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.contains(UPDATE_SELECTED)) {
|
||||
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
return
|
||||
}
|
||||
|
||||
super.bind(viewHolder, position, payloads)
|
||||
}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
|
||||
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
|
||||
|
||||
viewHolder.title_view.text = subscriptionEntity.name
|
||||
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||
@@ -47,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
|
||||
viewHolder.selected_highlight.alpha = 1F
|
||||
}
|
||||
|
||||
override fun getId(): Long {
|
||||
return subscriptionEntity.uid
|
||||
fun updateSelected(containerView: View, isSelected: Boolean) {
|
||||
this.isSelected = isSelected
|
||||
animateView(containerView.selected_highlight,
|
||||
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
}
|
||||
}
|
||||
|
@@ -64,7 +64,6 @@ import org.schabi.newpipe.player.helper.LoadController;
|
||||
import org.schabi.newpipe.player.helper.MediaSessionManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
||||
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
|
||||
import org.schabi.newpipe.player.playback.CustomTrackSelector;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
@@ -77,7 +76,6 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
@@ -217,7 +215,7 @@ public abstract class BasePlayer implements
|
||||
|
||||
final TrackSelection.Factory trackSelectionFactory = PlayerHelper
|
||||
.getQualitySelector(context);
|
||||
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
|
||||
|
||||
this.loadControl = new LoadController();
|
||||
this.renderFactory = new DefaultRenderersFactory(context);
|
||||
@@ -333,13 +331,12 @@ public abstract class BasePlayer implements
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final float speed = preferences
|
||||
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
|
||||
getPlaybackPitch());
|
||||
final boolean skipSilence = preferences
|
||||
.getBoolean(context.getString(R.string.playback_skip_silence_key),
|
||||
getPlaybackSkipSilence());
|
||||
final float speed = preferences.getFloat(
|
||||
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(
|
||||
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
|
||||
final boolean skipSilence = preferences.getBoolean(
|
||||
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||
return new PlaybackParameters(speed, pitch, skipSilence);
|
||||
}
|
||||
|
||||
@@ -835,16 +832,8 @@ public abstract class BasePlayer implements
|
||||
final Throwable cause = error.getCause();
|
||||
if (error instanceof BehindLiveWindowException) {
|
||||
reload();
|
||||
} else if (cause instanceof UnknownHostException) {
|
||||
playQueue.error(/*isNetworkProblem=*/true);
|
||||
} else if (isCurrentWindowValid()) {
|
||||
playQueue.error(/*isTransitioningToBadStream=*/true);
|
||||
} else if (cause instanceof FailedMediaSource.MediaSourceResolutionException) {
|
||||
playQueue.error(/*recoverableWithNoAvailableStream=*/false);
|
||||
} else if (cause instanceof FailedMediaSource.StreamInfoLoadException) {
|
||||
playQueue.error(/*recoverableIfLoadFailsWhenNetworkIsFine=*/false);
|
||||
} else {
|
||||
playQueue.error(/*noIdeaWhatHappenedAndLetUserChooseWhatToDo=*/true);
|
||||
playQueue.error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1131,6 +1120,7 @@ public abstract class BasePlayer implements
|
||||
Log.d(TAG, "onFastRewind() called");
|
||||
}
|
||||
seekBy(-getSeekDuration());
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
public void onFastForward() {
|
||||
@@ -1138,6 +1128,7 @@ public abstract class BasePlayer implements
|
||||
Log.d(TAG, "onFastForward() called");
|
||||
}
|
||||
seekBy(getSeekDuration());
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
private int getSeekDuration() {
|
||||
@@ -1479,10 +1470,21 @@ public abstract class BasePlayer implements
|
||||
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback parameters of the player, and also saves them to shared preferences.
|
||||
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
|
||||
* @param speed the playback speed, will be rounded to up to 2 decimal places
|
||||
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
|
||||
* @param skipSilence skip silence during playback
|
||||
*/
|
||||
public void setPlaybackParameters(final float speed, final float pitch,
|
||||
final boolean skipSilence) {
|
||||
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
|
||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
|
||||
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
|
||||
final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
|
||||
|
||||
savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
|
||||
simpleExoPlayer.setPlaybackParameters(
|
||||
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
|
||||
}
|
||||
|
||||
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
|
||||
|
@@ -166,9 +166,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
initPopup();
|
||||
initPopupCloseOverlay();
|
||||
}
|
||||
if (!playerImpl.isPlaying()) {
|
||||
playerImpl.getPlayer().setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
playerImpl.handleIntent(intent);
|
||||
|
||||
|
@@ -45,7 +45,6 @@ import org.schabi.newpipe.util.ThemeHelper;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
@@ -84,14 +83,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
|
||||
private ImageButton repeatButton;
|
||||
private ImageButton backwardButton;
|
||||
private ImageButton fastRewindButton;
|
||||
private ImageButton playPauseButton;
|
||||
private ImageButton fastForwardButton;
|
||||
private ImageButton forwardButton;
|
||||
private ImageButton shuffleButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private TextView playbackSpeedButton;
|
||||
private TextView playbackPitchButton;
|
||||
|
||||
private Menu menu;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -166,6 +164,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
case R.id.action_append_playlist:
|
||||
appendAllToPlaylist();
|
||||
return true;
|
||||
case R.id.action_playback_speed:
|
||||
openPlaybackParameterDialog();
|
||||
return true;
|
||||
case R.id.action_mute:
|
||||
player.onMuteUnmuteButtonClicked();
|
||||
return true;
|
||||
@@ -310,20 +311,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
private void buildControls() {
|
||||
repeatButton = rootView.findViewById(R.id.control_repeat);
|
||||
backwardButton = rootView.findViewById(R.id.control_backward);
|
||||
fastRewindButton = rootView.findViewById(R.id.control_fast_rewind);
|
||||
playPauseButton = rootView.findViewById(R.id.control_play_pause);
|
||||
fastForwardButton = rootView.findViewById(R.id.control_fast_forward);
|
||||
forwardButton = rootView.findViewById(R.id.control_forward);
|
||||
shuffleButton = rootView.findViewById(R.id.control_shuffle);
|
||||
playbackSpeedButton = rootView.findViewById(R.id.control_playback_speed);
|
||||
playbackPitchButton = rootView.findViewById(R.id.control_playback_pitch);
|
||||
progressBar = rootView.findViewById(R.id.control_progress_bar);
|
||||
|
||||
repeatButton.setOnClickListener(this);
|
||||
backwardButton.setOnClickListener(this);
|
||||
fastRewindButton.setOnClickListener(this);
|
||||
playPauseButton.setOnClickListener(this);
|
||||
fastForwardButton.setOnClickListener(this);
|
||||
forwardButton.setOnClickListener(this);
|
||||
shuffleButton.setOnClickListener(this);
|
||||
playbackSpeedButton.setOnClickListener(this);
|
||||
playbackPitchButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
|
||||
@@ -473,16 +474,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
player.onRepeatClicked();
|
||||
} else if (view.getId() == backwardButton.getId()) {
|
||||
player.onPlayPrevious();
|
||||
} else if (view.getId() == fastRewindButton.getId()) {
|
||||
player.onFastRewind();
|
||||
} else if (view.getId() == playPauseButton.getId()) {
|
||||
player.onPlayPause();
|
||||
} else if (view.getId() == fastForwardButton.getId()) {
|
||||
player.onFastForward();
|
||||
} else if (view.getId() == forwardButton.getId()) {
|
||||
player.onPlayNext();
|
||||
} else if (view.getId() == shuffleButton.getId()) {
|
||||
player.onShuffleClicked();
|
||||
} else if (view.getId() == playbackSpeedButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
} else if (view.getId() == playbackPitchButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
} else if (view.getId() == metadata.getId()) {
|
||||
scrollToSelected();
|
||||
} else if (view.getId() == progressLiveSync.getId()) {
|
||||
@@ -690,8 +691,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
|
||||
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
|
||||
if (parameters != null) {
|
||||
playbackSpeedButton.setText(formatSpeed(parameters.speed));
|
||||
playbackPitchButton.setText(formatPitch(parameters.pitch));
|
||||
if (menu != null && player != null) {
|
||||
final MenuItem item = menu.findItem(R.id.action_playback_speed);
|
||||
item.setTitle(formatSpeed(parameters.speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -132,17 +132,17 @@ public final class PlayerHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a {@link StreamInfo} and the existing queue items, provide the
|
||||
* {@link SinglePlayQueue} consisting of the next video for auto queuing.
|
||||
* Given a {@link StreamInfo} and the existing queue items,
|
||||
* provide the {@link SinglePlayQueue} consisting of the next video for auto queueing.
|
||||
* <p>
|
||||
* This method detects and prevents cycle by naively checking if a
|
||||
* candidate next video's url already exists in the existing items.
|
||||
* This method detects and prevents cycles by naively checking
|
||||
* if a candidate next video's url already exists in the existing items.
|
||||
* </p>
|
||||
* <p>
|
||||
* To select the next video, {@link StreamInfo#getNextVideo()} is first
|
||||
* checked. If it is nonnull and is not part of the existing items, then
|
||||
* it will be used as the next video. Otherwise, an random item with
|
||||
* non-repeating url will be selected from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* The first item in {@link StreamInfo#getRelatedStreams()} is checked first.
|
||||
* If it is non-null and is not part of the existing items, it will be used as the next stream.
|
||||
* Otherwise, a random item with non-repeating url will be selected
|
||||
* from the {@link StreamInfo#getRelatedStreams()}.
|
||||
* </p>
|
||||
*
|
||||
* @param info currently playing stream
|
||||
@@ -152,27 +152,28 @@ public final class PlayerHelper {
|
||||
@Nullable
|
||||
public static PlayQueue autoQueueOf(@NonNull final StreamInfo info,
|
||||
@NonNull final List<PlayQueueItem> existingItems) {
|
||||
Set<String> urls = new HashSet<>(existingItems.size());
|
||||
final Set<String> urls = new HashSet<>(existingItems.size());
|
||||
for (final PlayQueueItem item : existingItems) {
|
||||
urls.add(item.getUrl());
|
||||
}
|
||||
|
||||
final StreamInfoItem nextVideo = info.getNextVideo();
|
||||
if (nextVideo != null && !urls.contains(nextVideo.getUrl())) {
|
||||
return getAutoQueuedSinglePlayQueue(nextVideo);
|
||||
}
|
||||
|
||||
final List<InfoItem> relatedItems = info.getRelatedStreams();
|
||||
if (relatedItems == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
||||
for (final InfoItem item : info.getRelatedStreams()) {
|
||||
if (relatedItems.get(0) != null && relatedItems.get(0) instanceof StreamInfoItem
|
||||
&& !urls.contains(relatedItems.get(0).getUrl())) {
|
||||
return getAutoQueuedSinglePlayQueue((StreamInfoItem) relatedItems.get(0));
|
||||
}
|
||||
|
||||
final List<StreamInfoItem> autoQueueItems = new ArrayList<>();
|
||||
for (final InfoItem item : relatedItems) {
|
||||
if (item instanceof StreamInfoItem && !urls.contains(item.getUrl())) {
|
||||
autoQueueItems.add((StreamInfoItem) item);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.shuffle(autoQueueItems);
|
||||
return autoQueueItems.isEmpty()
|
||||
? null : getAutoQueuedSinglePlayQueue(autoQueueItems.get(0));
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
@@ -26,8 +27,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
private String preferredTextLanguage;
|
||||
|
||||
public CustomTrackSelector(final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(adaptiveTrackSelectionFactory);
|
||||
public CustomTrackSelector(final Context context,
|
||||
final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(context, adaptiveTrackSelectionFactory);
|
||||
}
|
||||
|
||||
private static boolean formatHasLanguage(final Format format, final String language) {
|
||||
|
@@ -2,9 +2,12 @@ package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -12,7 +15,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> extends PlayQueue {
|
||||
@@ -21,7 +23,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
|
||||
final int serviceId;
|
||||
final String baseUrl;
|
||||
String nextUrl;
|
||||
Page nextPage;
|
||||
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
@@ -29,16 +31,16 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl,
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final Page nextPage,
|
||||
final List<StreamInfoItem> streams, final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.nextPage = nextPage;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
this.isComplete = !isInitial && !Page.isValid(nextPage);
|
||||
}
|
||||
|
||||
protected abstract String getTag();
|
||||
@@ -66,7 +68,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
@@ -100,7 +102,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
@@ -17,15 +18,15 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final ChannelInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final Page nextPage,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
super(serviceId, url, nextPage, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,7 +42,7 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
|
@@ -305,25 +305,16 @@ public abstract class PlayQueue implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an exception for the item at the current index in order and the course of action:
|
||||
* if the error can be skipped or the current item should be removed.
|
||||
* Report an exception for the item at the current index in order and skip to the next one
|
||||
* <p>
|
||||
* This is done as a separate event as the underlying manager may have
|
||||
* different implementation regarding exceptions.
|
||||
* </p>
|
||||
*
|
||||
* @param skippable whether the error could be skipped
|
||||
*/
|
||||
public synchronized void error(final boolean skippable) {
|
||||
final int index = getIndex();
|
||||
|
||||
if (skippable) {
|
||||
queueIndex.incrementAndGet();
|
||||
} else {
|
||||
removeInternal(index);
|
||||
}
|
||||
|
||||
broadcast(new ErrorEvent(index, getIndex(), skippable));
|
||||
public synchronized void error() {
|
||||
final int oldIndex = getIndex();
|
||||
queueIndex.incrementAndGet();
|
||||
broadcast(new ErrorEvent(oldIndex, getIndex()));
|
||||
}
|
||||
|
||||
private synchronized void removeInternal(final int removeIndex) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user