1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-11-25 19:44:50 +00:00

Merge branch 'dev' into daynight

This commit is contained in:
krlvm
2021-04-03 00:08:26 +03:00
committed by GitHub
105 changed files with 1017 additions and 942 deletions

View File

@@ -1,192 +0,0 @@
package org.schabi.newpipe.about;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.tabs.TabLayoutMediator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityAboutBinding;
import org.schabi.newpipe.databinding.FragmentAboutBinding;
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 {
/**
* List of all software components.
*/
private static final SoftwareComponent[] SOFTWARE_COMPONENTS = {
new SoftwareComponent("ACRA", "2013", "Kevin Gaudin",
"https://github.com/ACRA/acra", StandardLicenses.APACHE2),
new SoftwareComponent("AndroidX", "2005 - 2011", "The Android Open Source Project",
"https://developer.android.com/jetpack", StandardLicenses.APACHE2),
new SoftwareComponent("CircleImageView", "2014 - 2020", "Henning Dodenhof",
"https://github.com/hdodenhof/CircleImageView",
StandardLicenses.APACHE2),
new SoftwareComponent("ExoPlayer", "2014 - 2020", "Google, Inc.",
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2),
new SoftwareComponent("GigaGet", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3),
new SoftwareComponent("Groupie", "2016", "Lisa Wray",
"https://github.com/lisawray/groupie", StandardLicenses.MIT),
new SoftwareComponent("Icepick", "2015", "Frankie Sardo",
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1),
new SoftwareComponent("Jsoup", "2009 - 2020", "Jonathan Hedley",
"https://github.com/jhy/jsoup", StandardLicenses.MIT),
new SoftwareComponent("Markwon", "2019", "Dimitry Ivanov",
"https://github.com/noties/Markwon", StandardLicenses.APACHE2),
new SoftwareComponent("Material Components for Android", "2016 - 2020", "Google, Inc.",
"https://github.com/material-components/material-components-android",
StandardLicenses.APACHE2),
new SoftwareComponent("NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3),
new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
"https://github.com/spacecowboy/NoNonsense-FilePicker",
StandardLicenses.MPL2),
new SoftwareComponent("OkHttp", "2019", "Square, Inc.",
"https://square.github.io/okhttp/", StandardLicenses.APACHE2),
new SoftwareComponent("PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2),
new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2),
new SoftwareComponent("RxBinding", "2015", "Jake Wharton",
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2),
new SoftwareComponent("RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2),
new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
"https://github.com/nostra13/Android-Universal-Image-Loader",
StandardLicenses.APACHE2),
};
private static final int POS_ABOUT = 0;
private static final int POS_LICENSE = 1;
private static final int TOTAL_COUNT = 2;
@Override
protected void onCreate(final Bundle savedInstanceState) {
assureCorrectAppLanguage(this);
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
setTitle(getString(R.string.title_activity_about));
final ActivityAboutBinding aboutBinding = ActivityAboutBinding.inflate(getLayoutInflater());
setContentView(aboutBinding.getRoot());
setSupportActionBar(aboutBinding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
final SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(this);
// Set up the ViewPager with the sections adapter.
aboutBinding.container.setAdapter(mSectionsPagerAdapter);
new TabLayoutMediator(aboutBinding.tabs, aboutBinding.container, (tab, position) -> {
switch (position) {
default:
case POS_ABOUT:
tab.setText(R.string.tab_about);
break;
case POS_LICENSE:
tab.setText(R.string.tab_licenses);
break;
}
}).attach();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
final int id = item.getItemId();
switch (id) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class AboutFragment extends Fragment {
public AboutFragment() {
}
/**
* Created a new instance of this fragment for the given section number.
*
* @return New instance of {@link AboutFragment}
*/
public static AboutFragment newInstance() {
return new AboutFragment();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
final FragmentAboutBinding aboutBinding =
FragmentAboutBinding.inflate(inflater, container, false);
final Context context = getContext();
aboutBinding.appVersion.setText(BuildConfig.VERSION_NAME);
aboutBinding.githubLink.setOnClickListener(nv ->
openUrlInBrowser(context, context.getString(R.string.github_url), false));
aboutBinding.donationLink.setOnClickListener(v ->
openUrlInBrowser(context, context.getString(R.string.donation_url), false));
aboutBinding.websiteLink.setOnClickListener(nv ->
openUrlInBrowser(context, context.getString(R.string.website_url), false));
aboutBinding.privacyPolicyLink.setOnClickListener(v ->
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url),
false));
return aboutBinding.getRoot();
}
}
/**
* A {@link FragmentStateAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public static class SectionsPagerAdapter extends FragmentStateAdapter {
public SectionsPagerAdapter(final FragmentActivity fa) {
super(fa);
}
@NonNull
@Override
public Fragment createFragment(final int position) {
switch (position) {
default:
case POS_ABOUT:
return AboutFragment.newInstance();
case POS_LICENSE:
return LicenseFragment.newInstance(SOFTWARE_COMPONENTS);
}
}
@Override
public int getItemCount() {
// Show 2 total pages.
return TOTAL_COUNT;
}
}
}

View File

@@ -0,0 +1,191 @@
package org.schabi.newpipe.about
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.ActivityAboutBinding
import org.schabi.newpipe.databinding.FragmentAboutBinding
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Localization.assureCorrectAppLanguage(this)
super.onCreate(savedInstanceState)
ThemeHelper.setTheme(this)
title = getString(R.string.title_activity_about)
val aboutBinding = ActivityAboutBinding.inflate(layoutInflater)
setContentView(aboutBinding.root)
setSupportActionBar(aboutBinding.aboutToolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
val mAboutStateAdapter = AboutStateAdapter(this)
// Set up the ViewPager with the sections adapter.
aboutBinding.aboutViewPager2.adapter = mAboutStateAdapter
TabLayoutMediator(
aboutBinding.aboutTabLayout,
aboutBinding.aboutViewPager2
) { tab: TabLayout.Tab, position: Int ->
when (position) {
POS_ABOUT -> tab.setText(R.string.tab_about)
POS_LICENSE -> tab.setText(R.string.tab_licenses)
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
}
}.attach()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
/**
* A placeholder fragment containing a simple view.
*/
class AboutFragment : Fragment() {
private fun Button.openLink(url: Int) {
setOnClickListener {
ShareUtils.openUrlInBrowser(
context,
requireContext().getString(url),
false
)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val aboutBinding = FragmentAboutBinding.inflate(inflater, container, false)
aboutBinding.aboutAppVersion.text = BuildConfig.VERSION_NAME
aboutBinding.aboutGithubLink.openLink(R.string.github_url)
aboutBinding.aboutDonationLink.openLink(R.string.donation_url)
aboutBinding.aboutWebsiteLink.openLink(R.string.website_url)
aboutBinding.aboutPrivacyPolicyLink.openLink(R.string.privacy_policy_url)
return aboutBinding.root
}
}
/**
* A [FragmentStateAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
private class AboutStateAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun createFragment(position: Int): Fragment {
return when (position) {
POS_ABOUT -> AboutFragment()
POS_LICENSE -> LicenseFragment.newInstance(SOFTWARE_COMPONENTS)
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
}
}
override fun getItemCount(): Int {
// Show 2 total pages.
return TOTAL_COUNT
}
}
companion object {
/**
* List of all software components.
*/
private val SOFTWARE_COMPONENTS = arrayOf(
SoftwareComponent(
"ACRA", "2013", "Kevin Gaudin",
"https://github.com/ACRA/acra", StandardLicenses.APACHE2
),
SoftwareComponent(
"AndroidX", "2005 - 2011", "The Android Open Source Project",
"https://developer.android.com/jetpack", StandardLicenses.APACHE2
),
SoftwareComponent(
"CircleImageView", "2014 - 2020", "Henning Dodenhof",
"https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2
),
SoftwareComponent(
"ExoPlayer", "2014 - 2020", "Google, Inc.",
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2
),
SoftwareComponent(
"GigaGet", "2014 - 2015", "Peter Cai",
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3
),
SoftwareComponent(
"Groupie", "2016", "Lisa Wray",
"https://github.com/lisawray/groupie", StandardLicenses.MIT
),
SoftwareComponent(
"Icepick", "2015", "Frankie Sardo",
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
),
SoftwareComponent(
"Jsoup", "2009 - 2020", "Jonathan Hedley",
"https://github.com/jhy/jsoup", StandardLicenses.MIT
),
SoftwareComponent(
"Markwon", "2019", "Dimitry Ivanov",
"https://github.com/noties/Markwon", StandardLicenses.APACHE2
),
SoftwareComponent(
"Material Components for Android", "2016 - 2020", "Google, Inc.",
"https://github.com/material-components/material-components-android",
StandardLicenses.APACHE2
),
SoftwareComponent(
"NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3
),
SoftwareComponent(
"NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2
),
SoftwareComponent(
"OkHttp", "2019", "Square, Inc.",
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
),
SoftwareComponent(
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxAndroid", "2015", "The RxAndroid authors",
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxBinding", "2015", "Jake Wharton",
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2
),
SoftwareComponent(
"RxJava", "2016 - 2020", "RxJava Contributors",
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
),
SoftwareComponent(
"Universal Image Loader", "2011 - 2015", "Sergey Tarasevich",
"https://github.com/nostra13/Android-Universal-Image-Loader",
StandardLicenses.APACHE2
)
)
private const val POS_ABOUT = 0
private const val POS_LICENSE = 1
private const val TOTAL_COUNT = 2
}
}

View File

@@ -1,145 +0,0 @@
package org.schabi.newpipe.about;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.FragmentLicensesBinding;
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding;
import org.schabi.newpipe.util.ShareUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
/**
* Fragment containing the software licenses.
*/
public class LicenseFragment extends Fragment {
private static final String ARG_COMPONENTS = "components";
private static final String LICENSE_KEY = "ACTIVE_LICENSE";
private SoftwareComponent[] softwareComponents;
private SoftwareComponent componentForContextMenu;
private License activeLicense;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
final Bundle bundle = new Bundle();
bundle.putParcelableArray(ARG_COMPONENTS, Objects.requireNonNull(softwareComponents));
final LicenseFragment fragment = new LicenseFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
softwareComponents = (SoftwareComponent[]) getArguments()
.getParcelableArray(ARG_COMPONENTS);
if (savedInstanceState != null) {
final Serializable license = savedInstanceState.getSerializable(LICENSE_KEY);
if (license != null) {
activeLicense = (License) license;
}
}
// Sort components by name
Arrays.sort(softwareComponents, Comparator.comparing(SoftwareComponent::getName));
}
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
final FragmentLicensesBinding binding = FragmentLicensesBinding
.inflate(inflater, container, false);
binding.appReadLicense.setOnClickListener(v -> {
activeLicense = StandardLicenses.GPL3;
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
StandardLicenses.GPL3));
});
for (final SoftwareComponent component : softwareComponents) {
final ItemSoftwareComponentBinding componentBinding = ItemSoftwareComponentBinding
.inflate(inflater, container, false);
componentBinding.name.setText(component.getName());
componentBinding.copyright.setText(getString(R.string.copyright,
component.getYears(),
component.getCopyrightOwner(),
component.getLicense().getAbbreviation()));
final View root = componentBinding.getRoot();
root.setTag(component);
root.setOnClickListener(v -> {
activeLicense = component.getLicense();
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
component.getLicense()));
});
binding.softwareComponents.addView(root);
registerForContextMenu(root);
}
if (activeLicense != null) {
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
activeLicense));
}
return binding.getRoot();
}
@Override
public void onCreateContextMenu(final ContextMenu menu, final View v,
final ContextMenu.ContextMenuInfo menuInfo) {
final MenuInflater inflater = getActivity().getMenuInflater();
final SoftwareComponent component = (SoftwareComponent) v.getTag();
menu.setHeaderTitle(component.getName());
inflater.inflate(R.menu.software_component, menu);
super.onCreateContextMenu(menu, v, menuInfo);
componentForContextMenu = (SoftwareComponent) v.getTag();
}
@Override
public boolean onContextItemSelected(@NonNull final MenuItem item) {
// item.getMenuInfo() is null so we use the tag of the view
final SoftwareComponent component = componentForContextMenu;
if (component == null) {
return false;
}
switch (item.getItemId()) {
case R.id.action_website:
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
return true;
case R.id.action_show_license:
compositeDisposable.add(LicenseFragmentHelper.showLicense(getActivity(),
component.getLicense()));
}
return false;
}
@Override
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
if (activeLicense != null) {
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense);
}
}
}

View File

@@ -0,0 +1,131 @@
package org.schabi.newpipe.about
import android.os.Bundle
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.R
import org.schabi.newpipe.about.LicenseFragmentHelper.showLicense
import org.schabi.newpipe.databinding.FragmentLicensesBinding
import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding
import org.schabi.newpipe.util.ShareUtils
import java.util.Arrays
import java.util.Objects
/**
* Fragment containing the software licenses.
*/
class LicenseFragment : Fragment() {
private lateinit var softwareComponents: Array<SoftwareComponent>
private var componentForContextMenu: SoftwareComponent? = null
private var activeLicense: License? = null
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
softwareComponents =
arguments?.getParcelableArray(ARG_COMPONENTS) as Array<SoftwareComponent>
if (savedInstanceState != null) {
val license = savedInstanceState.getSerializable(LICENSE_KEY)
if (license != null) {
activeLicense = license as License?
}
}
// Sort components by name
Arrays.sort(softwareComponents, Comparator.comparing(SoftwareComponent::name))
}
override fun onDestroy() {
compositeDisposable.dispose()
super.onDestroy()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentLicensesBinding.inflate(inflater, container, false)
binding.licensesAppReadLicense.setOnClickListener {
activeLicense = StandardLicenses.GPL3
compositeDisposable.add(
showLicense(activity, StandardLicenses.GPL3)
)
}
for (component in softwareComponents) {
val componentBinding = ItemSoftwareComponentBinding
.inflate(inflater, container, false)
componentBinding.name.text = component.name
componentBinding.copyright.text = getString(
R.string.copyright,
component.years,
component.copyrightOwner,
component.license.abbreviation
)
val root: View = componentBinding.root
root.tag = component
root.setOnClickListener {
activeLicense = component.license
compositeDisposable.add(
showLicense(activity, component.license)
)
}
binding.licensesSoftwareComponents.addView(root)
registerForContextMenu(root)
}
if (activeLicense != null) {
compositeDisposable.add(
showLicense(activity, activeLicense!!)
)
}
return binding.root
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
val inflater = requireActivity().menuInflater
val component = v.tag as SoftwareComponent
menu.setHeaderTitle(component.name)
inflater.inflate(R.menu.software_component, menu)
super.onCreateContextMenu(menu, v, menuInfo)
componentForContextMenu = component
}
override fun onContextItemSelected(item: MenuItem): Boolean {
// item.getMenuInfo() is null so we use the tag of the view
val component = componentForContextMenu ?: return false
when (item.itemId) {
R.id.menu_software_website -> {
ShareUtils.openUrlInBrowser(activity, component.link)
return true
}
R.id.menu_software_show_license -> compositeDisposable.add(
showLicense(activity, component.license)
)
}
return false
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
if (activeLicense != null) {
savedInstanceState.putSerializable(LICENSE_KEY, activeLicense)
}
}
companion object {
private const val ARG_COMPONENTS = "components"
private const val LICENSE_KEY = "ACTIVE_LICENSE"
fun newInstance(softwareComponents: Array<SoftwareComponent>): LicenseFragment {
val fragment = LicenseFragment()
fragment.arguments =
bundleOf(ARG_COMPONENTS to Objects.requireNonNull(softwareComponents))
return fragment
}
}
}

View File

@@ -1,109 +0,0 @@
package org.schabi.newpipe.about;
import android.content.Context;
import android.util.Base64;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public final class LicenseFragmentHelper {
private LicenseFragmentHelper() { }
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
private static String getFormattedLicense(@NonNull final Context context,
@NonNull final License license) {
final StringBuilder licenseContent = new StringBuilder();
final String webViewData;
try (BufferedReader in = new BufferedReader(new InputStreamReader(
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8))) {
String str;
while ((str = in.readLine()) != null) {
licenseContent.append(str);
}
// split the HTML file and insert the stylesheet into the HEAD of the file
webViewData = licenseContent.toString().replace("</head>",
"<style>" + getLicenseStylesheet(context) + "</style></head>");
} catch (final IOException e) {
throw new IllegalArgumentException(
"Could not get license file: " + license.getFilename(), e);
}
return webViewData;
}
/**
* @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme
*/
private static String getLicenseStylesheet(@NonNull final Context context) {
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
return "body{padding:12px 15px;margin:0;"
+ "background:#" + getHexRGBColor(context, isLightTheme
? R.color.light_license_background_color
: R.color.dark_license_background_color) + ";"
+ "color:#" + getHexRGBColor(context, isLightTheme
? R.color.light_license_text_color
: R.color.dark_license_text_color) + "}"
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
? R.color.light_youtube_primary_color
: R.color.dark_youtube_primary_color) + "}"
+ "pre{white-space:pre-wrap}";
}
/**
* Cast R.color to a hexadecimal color value.
*
* @param context the context to use
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
private static String getHexRGBColor(@NonNull final Context context, final int color) {
return context.getResources().getString(color).substring(3);
}
static Disposable showLicense(@Nullable final Context context, @NonNull final License license) {
if (context == null) {
return Disposable.empty();
}
return Observable.fromCallable(() -> getFormattedLicense(context, license))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(formattedLicense -> {
final String webViewData = Base64.encodeToString(formattedLicense
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
final WebView webView = new WebView(context);
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
final AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(license.getName());
alert.setView(webView);
assureCorrectAppLanguage(context);
alert.setNegativeButton(context.getString(R.string.finish),
(dialog, which) -> dialog.dismiss());
alert.show();
});
}
}

View File

@@ -0,0 +1,116 @@
package org.schabi.newpipe.about
import android.content.Context
import android.util.Base64
import android.webkit.WebView
import androidx.appcompat.app.AlertDialog
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.R
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ThemeHelper
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
object LicenseFragmentHelper {
/**
* @param context the context to use
* @param license the license
* @return String which contains a HTML formatted license page
* styled according to the context's theme
*/
private fun getFormattedLicense(context: Context, license: License): String {
val licenseContent = StringBuilder()
val webViewData: String
try {
BufferedReader(
InputStreamReader(
context.assets.open(license.filename),
StandardCharsets.UTF_8
)
).use { `in` ->
var str: String?
while (`in`.readLine().also { str = it } != null) {
licenseContent.append(str)
}
// split the HTML file and insert the stylesheet into the HEAD of the file
webViewData = "$licenseContent".replace(
"</head>",
"<style>" + getLicenseStylesheet(context) + "</style></head>"
)
}
} catch (e: IOException) {
throw IllegalArgumentException(
"Could not get license file: " + license.filename, e
)
}
return webViewData
}
/**
* @param context the Android context
* @return String which is a CSS stylesheet according to the context's theme
*/
private fun getLicenseStylesheet(context: Context): String {
val isLightTheme = ThemeHelper.isLightThemeSelected(context)
return (
"body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor(
context,
if (isLightTheme) R.color.light_license_background_color
else R.color.dark_license_background_color
) + ";" + "color:#" + getHexRGBColor(
context,
if (isLightTheme) R.color.light_license_text_color
else R.color.dark_license_text_color
) + "}" + "a[href]{color:#" + getHexRGBColor(
context,
if (isLightTheme) R.color.light_youtube_primary_color
else R.color.dark_youtube_primary_color
) + "}" + "pre{white-space:pre-wrap}"
)
}
/**
* Cast R.color to a hexadecimal color value.
*
* @param context the context to use
* @param color the color number from R.color
* @return a six characters long String with hexadecimal RGB values
*/
private fun getHexRGBColor(context: Context, color: Int): String {
return context.getString(color).substring(3)
}
@JvmStatic
fun showLicense(context: Context?, license: License): Disposable {
return if (context == null) {
Disposable.empty()
} else {
Observable.fromCallable { getFormattedLicense(context, license) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { formattedLicense: String ->
val webViewData = Base64.encodeToString(
formattedLicense
.toByteArray(StandardCharsets.UTF_8),
Base64.NO_PADDING
)
val webView = WebView(context)
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64")
val alert = AlertDialog.Builder(context)
alert.setTitle(license.name)
alert.setView(webView)
Localization.assureCorrectAppLanguage(context)
alert.setNegativeButton(
context.getString(R.string.finish)
) { dialog, _ -> dialog.dismiss() }
alert.show()
}
}
}
}

View File

@@ -1,19 +0,0 @@
package org.schabi.newpipe.about;
/**
* Class containing information about standard software licenses.
*/
public final class StandardLicenses {
public static final License GPL3
= new License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html");
public static final License APACHE2
= new License("Apache License, Version 2.0", "ALv2", "apache2.html");
public static final License MPL2
= new License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html");
public static final License MIT
= new License("MIT License", "MIT", "mit.html");
public static final License EPL1
= new License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html");
private StandardLicenses() { }
}

View File

@@ -0,0 +1,21 @@
package org.schabi.newpipe.about
/**
* Class containing information about standard software licenses.
*/
object StandardLicenses {
@JvmField
val GPL3 = License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html")
@JvmField
val APACHE2 = License("Apache License, Version 2.0", "ALv2", "apache2.html")
@JvmField
val MPL2 = License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html")
@JvmField
val MIT = License("MIT License", "MIT", "mit.html")
@JvmField
val EPL1 = License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html")
}

View File

@@ -73,7 +73,7 @@ import org.schabi.newpipe.fragments.BackPressable;
import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
@@ -153,7 +153,7 @@ public final class VideoDetailFragment
// tabs
private boolean showComments;
private boolean showRelatedStreams;
private boolean showRelatedItems;
private boolean showDescription;
private String selectedTabTag;
@AttrRes @NonNull final List<Integer> tabIcons = new ArrayList<>();
@@ -280,7 +280,7 @@ public final class VideoDetailFragment
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
showComments = prefs.getBoolean(getString(R.string.show_comments_key), true);
showRelatedStreams = prefs.getBoolean(getString(R.string.show_next_video_key), true);
showRelatedItems = prefs.getBoolean(getString(R.string.show_next_video_key), true);
showDescription = prefs.getBoolean(getString(R.string.show_description_key), true);
selectedTabTag = prefs.getString(
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
@@ -413,7 +413,7 @@ public final class VideoDetailFragment
showComments = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_next_video_key))) {
showRelatedStreams = sharedPreferences.getBoolean(key, true);
showRelatedItems = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_description_key))) {
showComments = sharedPreferences.getBoolean(key, true);
@@ -927,11 +927,11 @@ public final class VideoDetailFragment
tabContentDescriptions.add(R.string.comments_tab_description);
}
if (showRelatedStreams && binding.relatedStreamsLayout == null) {
if (showRelatedItems && binding.relatedItemsLayout == null) {
// temp empty fragment. will be updated in handleResult
pageAdapter.addFragment(new EmptyFragment(false), RELATED_TAB_TAG);
tabIcons.add(R.drawable.ic_art_track);
tabContentDescriptions.add(R.string.related_streams_tab_description);
tabContentDescriptions.add(R.string.related_items_tab_description);
}
if (showDescription) {
@@ -974,14 +974,14 @@ public final class VideoDetailFragment
}
private void updateTabs(@NonNull final StreamInfo info) {
if (showRelatedStreams) {
if (binding.relatedStreamsLayout == null) { // phone
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info));
if (showRelatedItems) {
if (binding.relatedItemsLayout == null) { // phone
pageAdapter.updateItem(RELATED_TAB_TAG, RelatedItemsFragment.getInstance(info));
} else { // tablet + TV
getChildFragmentManager().beginTransaction()
.replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(info))
.replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info))
.commitAllowingStateLoss();
binding.relatedStreamsLayout.setVisibility(
binding.relatedItemsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.VISIBLE);
}
}
@@ -1009,6 +1009,12 @@ public final class VideoDetailFragment
}
public void updateTabLayoutVisibility() {
if (binding == null) {
//If binding is null we do not need to and should not do anything with its object(s)
return;
}
if (pageAdapter.getCount() < 2 || binding.viewPager.getVisibility() != View.VISIBLE) {
// hide tab layout if there is only one tab or if the view pager is also hidden
binding.tabLayout.setVisibility(View.GONE);
@@ -1331,8 +1337,8 @@ public final class VideoDetailFragment
super.handleError();
setErrorImage(R.drawable.not_available_monkey);
if (binding.relatedStreamsLayout != null) { // hide related streams for tablets
binding.relatedStreamsLayout.setVisibility(View.INVISIBLE);
if (binding.relatedItemsLayout != null) { // hide related streams for tablets
binding.relatedItemsLayout.setVisibility(View.INVISIBLE);
}
// hide comments / related streams / description tabs
@@ -1426,12 +1432,12 @@ public final class VideoDetailFragment
binding.detailTitleRootLayout.setClickable(false);
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
if (binding.relatedStreamsLayout != null) {
if (showRelatedStreams) {
binding.relatedStreamsLayout.setVisibility(
if (binding.relatedItemsLayout != null) {
if (showRelatedItems) {
binding.relatedItemsLayout.setVisibility(
player != null && player.isFullscreen() ? View.GONE : View.INVISIBLE);
} else {
binding.relatedStreamsLayout.setVisibility(View.GONE);
binding.relatedItemsLayout.setVisibility(View.GONE);
}
}
@@ -1843,8 +1849,8 @@ public final class VideoDetailFragment
showSystemUi();
}
if (binding.relatedStreamsLayout != null) {
binding.relatedStreamsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
if (binding.relatedItemsLayout != null) {
binding.relatedItemsLayout.setVisibility(fullscreen ? View.GONE : View.VISIBLE);
}
scrollToTop();

View File

@@ -45,6 +45,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingScrolling;
@@ -124,8 +125,8 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
/**
* If the default implementation of {@link StateSaver.WriteRead} should be used.
*
* @see StateSaver
* @param useDefaultStateSaving Whether the default implementation should be used
* @see StateSaver
*/
public void setUseDefaultStateSaving(final boolean useDefaultStateSaving) {
this.useDefaultStateSaving = useDefaultStateSaving;
@@ -350,7 +351,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
return;
}
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
final List<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue);
@@ -361,7 +362,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
StreamDialogEntry.append_playlist,
StreamDialogEntry.share
));
} else {
} else {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup,
@@ -372,6 +373,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
if (!isNullOrEmpty(item.getUploaderUrl())) {
entries.add(StreamDialogEntry.show_channel_details);
}
StreamDialogEntry.setEnabledEntries(entries);
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),

View File

@@ -422,7 +422,9 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
@Override
public void setTitle(final String title) {
super.setTitle(title);
headerBinding.playlistTitleView.setText(title);
if (headerBinding != null) {
headerBinding.playlistTitleView.setText(title);
}
}
private void onBookmarkClicked() {

View File

@@ -139,7 +139,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State
boolean wasSearchFocused = false;
private Map<Integer, String> menuItemToFilterName;
@Nullable private Map<Integer, String> menuItemToFilterName = null;
private StreamingService service;
private Page nextPage;
private boolean isSuggestionsEnabled = true;
@@ -455,11 +455,12 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (menuItemToFilterName != null) {
final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
}
return true;
}

View File

@@ -15,38 +15,38 @@ import androidx.preference.PreferenceManager;
import androidx.viewbinding.ViewBinding;
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.RelatedStreamsHeaderBinding;
import org.schabi.newpipe.databinding.RelatedItemsHeaderBinding;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.ktx.ViewUtils;
import org.schabi.newpipe.util.RelatedStreamInfo;
import org.schabi.newpipe.util.RelatedItemInfo;
import java.io.Serializable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInfo>
public class RelatedItemsFragment extends BaseListInfoFragment<RelatedItemInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key";
private final CompositeDisposable disposables = new CompositeDisposable();
private RelatedStreamInfo relatedStreamInfo;
private RelatedItemInfo relatedItemInfo;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private RelatedStreamsHeaderBinding headerBinding;
private RelatedItemsHeaderBinding headerBinding;
public static RelatedVideosFragment getInstance(final StreamInfo info) {
final RelatedVideosFragment instance = new RelatedVideosFragment();
public static RelatedItemsFragment getInstance(final StreamInfo info) {
final RelatedItemsFragment instance = new RelatedItemsFragment();
instance.setInitialData(info);
return instance;
}
public RelatedVideosFragment() {
public RelatedItemsFragment() {
super(UserAction.REQUESTED_STREAM);
}
@@ -63,7 +63,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_related_streams, container, false);
return inflater.inflate(R.layout.fragment_related_items, container, false);
}
@Override
@@ -80,8 +80,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
@Override
protected ViewBinding getListHeader() {
if (relatedStreamInfo != null && relatedStreamInfo.getRelatedItems() != null) {
headerBinding = RelatedStreamsHeaderBinding
if (relatedItemInfo != null && relatedItemInfo.getRelatedItems() != null) {
headerBinding = RelatedItemsHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
final SharedPreferences pref = PreferenceManager
@@ -107,8 +107,8 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<RelatedStreamInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedStreamInfo);
protected Single<RelatedItemInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedItemInfo);
}
@Override
@@ -120,7 +120,7 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
}
@Override
public void handleResult(@NonNull final RelatedStreamInfo result) {
public void handleResult(@NonNull final RelatedItemInfo result) {
super.handleResult(result);
if (headerBinding != null) {
@@ -145,23 +145,23 @@ public class RelatedVideosFragment extends BaseListInfoFragment<RelatedStreamInf
private void setInitialData(final StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if (this.relatedStreamInfo == null) {
this.relatedStreamInfo = RelatedStreamInfo.getInfo(info);
if (this.relatedItemInfo == null) {
this.relatedItemInfo = RelatedItemInfo.getInfo(info);
}
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedStreamInfo);
outState.putSerializable(INFO_KEY, relatedItemInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
final Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof RelatedStreamInfo) {
this.relatedStreamInfo = (RelatedStreamInfo) serializable;
if (serializable instanceof RelatedItemInfo) {
this.relatedItemInfo = (RelatedItemInfo) serializable;
}
}

View File

@@ -4,8 +4,6 @@ import android.text.TextUtils;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
@@ -14,6 +12,8 @@ import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.Localization;
import androidx.preference.PreferenceManager;
import static org.schabi.newpipe.MainActivity.DEBUG;
/*

View File

@@ -72,7 +72,6 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
// Save the list 10 seconds after the last change occurred
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
@State
protected Long playlistId;
@State
@@ -340,7 +339,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
}
@Override
public void onComplete() { }
public void onComplete() {
}
};
}
@@ -361,6 +361,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
.create()
.show();
}
} else if (item.getItemId() == R.id.menu_item_rename_playlist) {
createRenameDialog();
} else {
return super.onOptionsItemSelected(item);
}
@@ -420,7 +422,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
playlistItem.getStreamId());
final boolean hasState = streamStatesIter.next() != null;
if (indexInHistory < 0 || hasState) {
if (indexInHistory < 0 || hasState) {
notWatchedItems.add(playlistItem);
} else if (!thumbnailVideoRemoved
&& playlistManager.getPlaylistThumbnail(playlistId)
@@ -722,7 +724,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
@Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder,
final int swipeDir) { }
final int swipeDir) {
}
};
}
@@ -755,7 +758,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
StreamDialogEntry.append_playlist,
StreamDialogEntry.share
));
} else {
} else {
entries.addAll(Arrays.asList(
StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup,

View File

@@ -76,6 +76,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -493,10 +494,12 @@ public final class Player implements
// Setup subtitle view
simpleExoPlayer.addTextOutput(binding.subtitleView);
// Setup audio session with onboard equalizer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
// enable media tunneling
if (DeviceUtils.shouldSupportMediaTunneling()) {
trackSelector.setParameters(
trackSelector.buildUponParameters().setTunnelingEnabled(true));
} else if (DEBUG) {
Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling");
}
}
@@ -629,10 +632,10 @@ public final class Player implements
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
// and we should retry in this case
if (simpleExoPlayer.getPlaybackState()
== com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.retry();
simpleExoPlayer.prepare();
}
simpleExoPlayer.seekTo(playQueue.getIndex(), newQueue.getItem().getRecoveryPosition());
simpleExoPlayer.setPlayWhenReady(playWhenReady);
@@ -643,10 +646,10 @@ public final class Player implements
&& !playQueue.isDisposed()) {
// Do not re-init the same PlayQueue. Save time
// Player can have state = IDLE when playback is stopped or failed
// and we should retry() in this case
// and we should retry in this case
if (simpleExoPlayer.getPlaybackState()
== com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.retry();
simpleExoPlayer.prepare();
}
simpleExoPlayer.setPlayWhenReady(playWhenReady);
@@ -711,11 +714,7 @@ public final class Player implements
// Android TV: without it focus will frame the whole player
binding.playPauseButton.requestFocus();
if (simpleExoPlayer.getPlayWhenReady()) {
play();
} else {
pause();
}
playPause();
}
NavigationHelper.sendPlayerStartedEvent(context);
}
@@ -1658,7 +1657,7 @@ public final class Player implements
saveWasPlaying();
if (isPlaying()) {
simpleExoPlayer.setPlayWhenReady(false);
simpleExoPlayer.pause();
}
showControls(0);
@@ -1674,7 +1673,7 @@ public final class Player implements
seekTo(seekBar.getProgress());
if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) {
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.play();
}
binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress()));
@@ -1692,7 +1691,7 @@ public final class Player implements
}
public void saveWasPlaying() {
this.wasPlaying = simpleExoPlayer.getPlayWhenReady();
this.wasPlaying = getPlayWhenReady();
}
//endregion
@@ -1917,7 +1916,7 @@ public final class Player implements
}
@Override // exoplayer listener
public void onLoadingChanged(final boolean isLoading) {
public void onIsLoadingChanged(final boolean isLoading) {
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onLoadingChanged() called with: "
+ "isLoading = [" + isLoading + "]");
@@ -1961,7 +1960,8 @@ public final class Player implements
if (currentState == STATE_BLOCKED) {
changeState(STATE_BUFFERING);
}
simpleExoPlayer.prepare(mediaSource);
simpleExoPlayer.setMediaSource(mediaSource);
simpleExoPlayer.prepare();
}
public void changeState(final int state) {
@@ -2360,6 +2360,12 @@ public final class Player implements
break;
}
case DISCONTINUITY_REASON_SEEK:
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
}
if (isPrepared) {
saveStreamProgressState();
}
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
case DISCONTINUITY_REASON_INTERNAL:
if (playQueue.getIndex() != newWindowIndex) {
@@ -2424,10 +2430,8 @@ public final class Player implements
setRecovery();
reloadPlayQueueManager();
break;
case ExoPlaybackException.TYPE_OUT_OF_MEMORY:
case ExoPlaybackException.TYPE_REMOTE:
case ExoPlaybackException.TYPE_RENDERER:
case ExoPlaybackException.TYPE_TIMEOUT:
default:
showUnrecoverableError(error);
onPlaybackShutdown();
@@ -2632,16 +2636,6 @@ public final class Player implements
simpleExoPlayer.seekToDefaultPosition();
}
}
@Override // exoplayer override
public void onSeekProcessed() {
if (DEBUG) {
Log.d(TAG, "ExoPlayer - onSeekProcessed() called");
}
if (isPrepared) {
saveStreamProgressState();
}
}
//endregion
@@ -2669,7 +2663,7 @@ public final class Player implements
}
}
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.play();
saveStreamProgressState();
}
@@ -2682,7 +2676,7 @@ public final class Player implements
}
audioReactor.abandonAudioFocus();
simpleExoPlayer.setPlayWhenReady(false);
simpleExoPlayer.pause();
saveStreamProgressState();
}
@@ -2691,7 +2685,7 @@ public final class Player implements
Log.d(TAG, "onPlayPause() called");
}
if (isPlaying()) {
if (getPlayWhenReady()) {
pause();
} else {
play();
@@ -4017,6 +4011,10 @@ public final class Player implements
return !exoPlayerIsNull() && simpleExoPlayer.isPlaying();
}
public boolean getPlayWhenReady() {
return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady();
}
private boolean isLoading() {
return !exoPlayerIsNull() && simpleExoPlayer.isLoading();
}

View File

@@ -229,8 +229,10 @@ abstract class BasePlayerGestureListener(
// because the soft input is visible (the draggable area is currently resized).
player.updateScreenSize()
player.checkPopupPositionBounds()
initialPopupX = player.popupLayoutParams!!.x
initialPopupY = player.popupLayoutParams!!.y
player.popupLayoutParams?.let {
initialPopupX = it.x
initialPopupY = it.y
}
return super.onDown(e)
}
@@ -466,7 +468,7 @@ abstract class BasePlayerGestureListener(
// ///////////////////////////////////////////////////////////////////
private fun getDisplayPortion(e: MotionEvent): DisplayPortion {
return if (player.playerType == MainPlayer.PlayerType.POPUP) {
return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) {
when {
e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT
e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT

View File

@@ -26,7 +26,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout>
Rect globalRect = new Rect();
private boolean skippingInterception = false;
private final List<Integer> skipInterceptionOfElements = Arrays.asList(
R.id.detail_content_root_layout, R.id.relatedStreamsLayout,
R.id.detail_content_root_layout, R.id.relatedItemsLayout,
R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls,
R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton);

View File

@@ -103,13 +103,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
animateAudio(DUCK_AUDIO_TO, 1.0f);
if (PlayerHelper.isResumeAfterAudioFocusGain(context)) {
player.setPlayWhenReady(true);
player.play();
}
}
private void onAudioFocusLoss() {
Log.d(TAG, "onAudioFocusLoss() called");
player.setPlayWhenReady(false);
player.pause();
}
private void onAudioFocusLossCanDuck() {
@@ -148,7 +148,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAudioSessionId(final EventTime eventTime, final int audioSessionId) {
public void onAudioSessionIdChanged(final EventTime eventTime, final int audioSessionId) {
if (!PlayerHelper.isUsingDSP()) {
return;
}

View File

@@ -4,7 +4,7 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
public class LoadController implements LoadControl {
@@ -33,7 +33,7 @@ public class LoadController implements LoadControl {
final DefaultLoadControl.Builder builder = new DefaultLoadControl.Builder();
builder.setBufferDurationsMs(minimumPlaybackBufferMs, optimalPlaybackBufferMs,
initialPlaybackBufferMs, initialPlaybackBufferMs);
internalLoadControl = builder.createDefaultLoadControl();
internalLoadControl = builder.build();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -47,9 +47,9 @@ public class LoadController implements LoadControl {
}
@Override
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroupArray,
final TrackSelectionArray trackSelectionArray) {
internalLoadControl.onTracksSelected(renderers, trackGroupArray, trackSelectionArray);
public void onTracksSelected(final Renderer[] renderers, final TrackGroupArray trackGroups,
final ExoTrackSelection[] trackSelections) {
internalLoadControl.onTracksSelected(renderers, trackGroups, trackSelections);
}
@Override
@@ -92,11 +92,12 @@ public class LoadController implements LoadControl {
@Override
public boolean shouldStartPlayback(final long bufferedDurationUs, final float playbackSpeed,
final boolean rebuffering) {
final boolean rebuffering, final long targetLiveOffsetUs) {
final boolean isInitialPlaybackBufferFilled
= bufferedDurationUs >= this.initialPlaybackBufferUs * playbackSpeed;
final boolean isInternalStartingPlayback = internalLoadControl
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering);
.shouldStartPlayback(bufferedDurationUs, playbackSpeed, rebuffering,
targetLiveOffsetUs);
return isInitialPlaybackBufferFilled || isInternalStartingPlayback;
}

View File

@@ -36,8 +36,6 @@ public class MediaSessionManager {
@NonNull final Player player,
@NonNull final MediaSessionCallback callback) {
mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setActive(true);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()

View File

@@ -23,7 +23,7 @@ import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout.ResizeMode;
import com.google.android.exoplayer2.util.MimeTypes;
@@ -180,10 +180,10 @@ public final class PlayerHelper {
* if a candidate next video's url already exists in the existing items.
* </p>
* <p>
* The first item in {@link StreamInfo#getRelatedStreams()} is checked first.
* The first item in {@link StreamInfo#getRelatedItems()} 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()}.
* Otherwise, a random stream with non-repeating url will be selected
* from the {@link StreamInfo#getRelatedItems()}. Non-stream items are ignored.
* </p>
*
* @param info currently playing stream
@@ -198,7 +198,7 @@ public final class PlayerHelper {
urls.add(item.getUrl());
}
final List<InfoItem> relatedItems = info.getRelatedStreams();
final List<InfoItem> relatedItems = info.getRelatedItems();
if (Utils.isNullOrEmpty(relatedItems)) {
return null;
}
@@ -323,7 +323,7 @@ public final class PlayerHelper {
return 60000;
}
public static TrackSelection.Factory getQualitySelector() {
public static ExoTrackSelection.Factory getQualitySelector() {
return new AdaptiveTrackSelection.Factory(
1000,
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,

View File

@@ -13,7 +13,7 @@ import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.util.Assertions;
/**
@@ -28,7 +28,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
private String preferredTextLanguage;
public CustomTrackSelector(final Context context,
final TrackSelection.Factory adaptiveTrackSelectionFactory) {
final ExoTrackSelection.Factory adaptiveTrackSelectionFactory) {
super(context, adaptiveTrackSelectionFactory);
}
@@ -50,7 +50,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
@Override
@Nullable
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
protected Pair<ExoTrackSelection.Definition, TextTrackScore> selectTextTrack(
final TrackGroupArray groups,
@NonNull final int[][] formatSupport,
@NonNull final Parameters params,
@@ -86,7 +86,7 @@ public class CustomTrackSelector extends DefaultTrackSelector {
}
}
return selectedGroup == null ? null
: Pair.create(new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
: Pair.create(new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex),
Assertions.checkNotNull(selectedTrackScore));
}
}

View File

@@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.util.Util;
@@ -43,13 +44,13 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
switch (type) {
case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_DASH:
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_HLS:
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
default:
throw new IllegalStateException("Unsupported type: " + type);
}
@@ -68,16 +69,16 @@ public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
switch (type) {
case C.TYPE_SS:
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_DASH:
return dataSource.getDashMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_HLS:
return dataSource.getHlsMediaSourceFactory().setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
case C.TYPE_OTHER:
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
.createMediaSource(uri);
.createMediaSource(MediaItem.fromUri(uri));
default:
throw new IllegalStateException("Unsupported type: " + type);
}

View File

@@ -6,7 +6,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
@@ -22,7 +22,6 @@ import org.schabi.newpipe.util.ListHelper;
import java.util.ArrayList;
import java.util.List;
import static com.google.android.exoplayer2.C.SELECTION_FLAG_AUTOSELECT;
import static com.google.android.exoplayer2.C.TIME_UNSET;
public class VideoPlaybackResolver implements PlaybackResolver {
@@ -101,12 +100,12 @@ public class VideoPlaybackResolver implements PlaybackResolver {
if (mimeType == null) {
continue;
}
final Format textFormat = Format.createTextSampleFormat(null, mimeType,
SELECTION_FLAG_AUTOSELECT,
PlayerHelper.captionLanguageOf(context, subtitle));
final MediaSource textSource = dataSource.getSampleMediaSourceFactory()
.createMediaSource(Uri.parse(subtitle.getUrl()), textFormat, TIME_UNSET);
.createMediaSource(
new MediaItem.Subtitle(Uri.parse(subtitle.getUrl()),
mimeType,
PlayerHelper.captionLanguageOf(context, subtitle)),
TIME_UNSET);
mediaSources.add(textSource);
}
}

View File

@@ -7,7 +7,6 @@ import android.os.Bundle;
import android.provider.Settings;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.preference.Preference;
@@ -22,8 +21,8 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
private String captionSettingsKey;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.appearance_settings);
final String themeKey = getString(R.string.theme_key);
// the key of the active theme when settings were opened (or recreated after theme change)
@@ -59,11 +58,6 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
}
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.appearance_settings);
}
@Override
public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) {

View File

@@ -10,7 +10,6 @@ import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -50,8 +49,35 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
private String initialLanguage;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final File homeDir = ContextCompat.getDataDir(requireContext());
manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
manager.deleteSettingsFile();
addPreferencesFromResource(R.xml.content_settings);
final Preference importDataPreference = findPreference(getString(R.string.import_data));
importDataPreference.setOnPreferenceClickListener(p -> {
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
FilePickerActivityHelper.MODE_FILE);
startActivityForResult(i, REQUEST_IMPORT_PATH);
return true;
});
final Preference exportDataPreference = findPreference(getString(R.string.export_data));
exportDataPreference.setOnPreferenceClickListener(p -> {
final 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);
startActivityForResult(i, REQUEST_EXPORT_PATH);
return true;
});
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
@@ -103,37 +129,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
return super.onPreferenceTreeClick(preference);
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final File homeDir = ContextCompat.getDataDir(requireContext());
manager = new ContentSettingsManager(new NewPipeFileLocator(homeDir));
manager.deleteSettingsFile();
addPreferencesFromResource(R.xml.content_settings);
final Preference importDataPreference = findPreference(getString(R.string.import_data));
importDataPreference.setOnPreferenceClickListener(p -> {
final Intent i = new Intent(getActivity(), FilePickerActivityHelper.class)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false)
.putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false)
.putExtra(FilePickerActivityHelper.EXTRA_MODE,
FilePickerActivityHelper.MODE_FILE);
startActivityForResult(i, REQUEST_IMPORT_PATH);
return true;
});
final Preference exportDataPreference = findPreference(getString(R.string.export_data));
exportDataPreference.setOnPreferenceClickListener(p -> {
final 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);
startActivityForResult(i, REQUEST_EXPORT_PATH);
return true;
});
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@@ -11,7 +11,6 @@ import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.preference.Preference;
@@ -46,8 +45,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
private Context ctx;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.download_settings);
downloadPathVideoPreference = getString(R.string.download_path_video_key);
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
@@ -76,11 +75,6 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
});
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.download_settings);
}
@Override
public void onAttach(final Context context) {
super.onAttach(context);

View File

@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
@@ -29,8 +28,9 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
private CompositeDisposable disposables;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.history_settings);
cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
viewsHistoryClearKey = getString(R.string.clear_views_history_key);
playbackStatesClearKey = getString(R.string.clear_playback_states_key);
@@ -39,11 +39,6 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
disposables = new CompositeDisposable();
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.history_settings);
}
@Override
public boolean onPreferenceTreeClick(final Preference preference) {
if (preference.getKey().equals(cacheWipeKey)) {

View File

@@ -56,11 +56,11 @@ public class SettingsActivity extends AppCompatActivity
SettingsLayoutBinding.inflate(getLayoutInflater());
setContentView(settingsLayoutBinding.getRoot());
setSupportActionBar(settingsLayoutBinding.toolbarLayout.toolbar);
setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
if (savedInstanceBundle == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, new MainSettingsFragment())
.replace(R.id.settings_fragment_holder, new MainSettingsFragment())
.commit();
}
@@ -102,7 +102,7 @@ public class SettingsActivity extends AppCompatActivity
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
R.animator.custom_fade_in, R.animator.custom_fade_out)
.replace(R.id.fragment_holder, fragment)
.replace(R.id.settings_fragment_holder, fragment)
.addToBackStack(null)
.commit();
return true;

View File

@@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import org.schabi.newpipe.R;
@@ -16,15 +15,10 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
final String updateToggleKey = getString(R.string.update_app_key);
findPreference(updateToggleKey).setOnPreferenceChangeListener(updatePreferenceChange);
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
}
}

View File

@@ -8,7 +8,6 @@ import android.provider.Settings;
import android.text.format.DateUtils;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.preference.ListPreference;
import com.google.android.material.snackbar.Snackbar;
@@ -23,8 +22,8 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
private SharedPreferences.OnSharedPreferenceChangeListener listener;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.video_audio_settings);
updateSeekOptions();
@@ -104,11 +103,6 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
}
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.video_audio_settings);
}
@Override
public void onResume() {
super.onResume();

View File

@@ -17,6 +17,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
@@ -41,9 +42,8 @@ public class NotificationActionsPreference extends Preference {
}
private NotificationSlot[] notificationSlots;
private List<Integer> compactSlots;
@Nullable private NotificationSlot[] notificationSlots = null;
@Nullable private List<Integer> compactSlots = null;
////////////////////////////////////////////////////////////////////////////
// Lifecycle
@@ -85,19 +85,22 @@ public class NotificationActionsPreference extends Preference {
////////////////////////////////////////////////////////////////////////////
private void saveChanges() {
final SharedPreferences.Editor editor = getSharedPreferences().edit();
if (compactSlots != null && notificationSlots != null) {
final SharedPreferences.Editor editor = getSharedPreferences().edit();
for (int i = 0; i < 3; i++) {
editor.putInt(getContext().getString(NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
(i < compactSlots.size() ? compactSlots.get(i) : -1));
for (int i = 0; i < 3; i++) {
editor.putInt(getContext().getString(
NotificationConstants.SLOT_COMPACT_PREF_KEYS[i]),
(i < compactSlots.size() ? compactSlots.get(i) : -1));
}
for (int i = 0; i < 5; i++) {
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
notificationSlots[i].selectedAction);
}
editor.apply();
}
for (int i = 0; i < 5; i++) {
editor.putInt(getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]),
notificationSlots[i].selectedAction);
}
editor.apply();
}

View File

@@ -20,6 +20,13 @@ public final class DeviceUtils {
private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv";
private static Boolean isTV = null;
/*
* Devices that do not support media tunneling
*/
// Formuler Z8 Pro, Z8, CC, Z Alpha, Z+ Neo
private static final boolean HI3798MV200 = Build.VERSION.SDK_INT == 24
&& Build.DEVICE.equals("Hi3798MV200");
private DeviceUtils() {
}
@@ -88,4 +95,15 @@ public final class DeviceUtils {
sp,
context.getResources().getDisplayMetrics());
}
/**
* Some devices have broken tunneled video playback but claim to support it.
* See https://github.com/TeamNewPipe/NewPipe/issues/5911
* @return false if Kitkat (does not support tunneling) or affected device
*/
public static boolean shouldSupportMediaTunneling() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& !HI3798MV200;
}
}

View File

@@ -62,7 +62,8 @@ public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
private NavigationHelper() { }
private NavigationHelper() {
}
/*//////////////////////////////////////////////////////////////////////////
// Players
@@ -111,18 +112,22 @@ public final class NavigationHelper {
public static void playOnMainPlayer(final AppCompatActivity activity,
@NonNull final PlayQueue playQueue) {
final PlayQueueItem item = playQueue.getItem();
assert item != null;
openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, false);
if (item != null) {
openVideoDetailFragment(activity, activity.getSupportFragmentManager(),
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
false);
}
}
public static void playOnMainPlayer(final Context context,
@NonNull final PlayQueue playQueue,
final boolean switchingPlayers) {
final PlayQueueItem item = playQueue.getItem();
assert item != null;
openVideoDetail(context,
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue, switchingPlayers);
if (item != null) {
openVideoDetail(context,
item.getServiceId(), item.getUrl(), item.getTitle(), playQueue,
switchingPlayers);
}
}
public static void playOnPopupPlayer(final Context context,

View File

@@ -9,19 +9,19 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RelatedStreamInfo extends ListInfo<InfoItem> {
public RelatedStreamInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
final String name) {
public class RelatedItemInfo extends ListInfo<InfoItem> {
public RelatedItemInfo(final int serviceId, final ListLinkHandler listUrlIdHandler,
final String name) {
super(serviceId, listUrlIdHandler, name);
}
public static RelatedStreamInfo getInfo(final StreamInfo info) {
public static RelatedItemInfo getInfo(final StreamInfo info) {
final ListLinkHandler handler = new ListLinkHandler(
info.getOriginalUrl(), info.getUrl(), info.getId(), Collections.emptyList(), null);
final RelatedStreamInfo relatedStreamInfo = new RelatedStreamInfo(
final RelatedItemInfo relatedItemInfo = new RelatedItemInfo(
info.getServiceId(), handler, info.getName());
final List<InfoItem> streams = new ArrayList<>(info.getRelatedStreams());
relatedStreamInfo.setRelatedItems(streams);
return relatedStreamInfo;
final List<InfoItem> relatedItems = new ArrayList<>(info.getRelatedItems());
relatedItemInfo.setRelatedItems(relatedItems);
return relatedItemInfo;
}
}

View File

@@ -24,6 +24,12 @@ public enum StreamDialogEntry {
// enum values with DEFAULT actions //
//////////////////////////////////////
show_channel_details(R.string.show_channel_details, (fragment, item) ->
// For some reason `getParentFragmentManager()` doesn't work, but this does.
NavigationHelper.openChannelFragment(fragment.getActivity().getSupportFragmentManager(),
item.getServiceId(), item.getUploaderUrl(), item.getUploaderName())
),
/**
* Enqueues the stream automatically to the current PlayerType.<br>
* <br>