Merge remote-tracking branch 'newpipe/dev' into rebase
This commit is contained in:
commit
6a84f433ea
84 changed files with 2915 additions and 711 deletions
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.fragment.app;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// TODO: Replace this deprecated class with its ViewPager2 counterpart
|
||||
|
||||
/**
|
||||
* This is a copy from {@link androidx.fragment.app.FragmentStatePagerAdapter}.
|
||||
* <p>
|
||||
* It includes a workaround to fix the menu visibility when the adapter is restored.
|
||||
* <p>
|
||||
* When restoring the state of this adapter, all the fragments' menu visibility were set to false,
|
||||
* effectively disabling the menu from the user until he switched pages or another event that triggered the
|
||||
* menu to be visible again happened.
|
||||
* <p>
|
||||
* <br><b>Check out the changes in:</b>
|
||||
* <ul>
|
||||
* <li>{@link #saveState()}</li>
|
||||
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
||||
* </ul>
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
|
||||
private static final String TAG = "FragmentStatePagerAdapt";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
|
||||
private @interface Behavior { }
|
||||
|
||||
/**
|
||||
* Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
|
||||
* fragment changes.
|
||||
*
|
||||
* @deprecated This behavior relies on the deprecated
|
||||
* {@link Fragment#setUserVisibleHint(boolean)} API. Use
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
|
||||
* {@link FragmentTransaction#setMaxLifecycle}.
|
||||
* @see #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)
|
||||
*/
|
||||
@Deprecated
|
||||
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
|
||||
|
||||
/**
|
||||
* Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
|
||||
* state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
|
||||
*
|
||||
* @see #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)
|
||||
*/
|
||||
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
|
||||
|
||||
private final FragmentManager mFragmentManager;
|
||||
private final int mBehavior;
|
||||
private FragmentTransaction mCurTransaction = null;
|
||||
|
||||
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
|
||||
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
|
||||
private Fragment mCurrentPrimaryItem = null;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround} that sets the fragment manager for the
|
||||
* adapter. This is the equivalent of calling
|
||||
* {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} and passing in
|
||||
* {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
|
||||
*
|
||||
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
|
||||
* current Fragment changes.</p>
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @deprecated use {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} with
|
||||
* {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
|
||||
*/
|
||||
@Deprecated
|
||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm) {
|
||||
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}.
|
||||
*
|
||||
* If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
|
||||
* Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are
|
||||
* capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is
|
||||
* passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be
|
||||
* callbacks to {@link Fragment#setUserVisibleHint(boolean)}.
|
||||
*
|
||||
* @param fm fragment manager that will interact with this adapter
|
||||
* @param behavior determines if only current fragments are in a resumed state
|
||||
*/
|
||||
public FragmentStatePagerAdapterMenuWorkaround(@NonNull FragmentManager fm,
|
||||
@Behavior int behavior) {
|
||||
mFragmentManager = fm;
|
||||
mBehavior = behavior;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Fragment associated with a specified position.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Fragment getItem(int position);
|
||||
|
||||
@Override
|
||||
public void startUpdate(@NonNull ViewGroup container) {
|
||||
if (container.getId() == View.NO_ID) {
|
||||
throw new IllegalStateException("ViewPager with adapter " + this
|
||||
+ " requires a view id");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
// If we already have this item instantiated, there is nothing
|
||||
// to do. This can happen when we are restoring the entire pager
|
||||
// from its saved state, where the fragment manager has already
|
||||
// taken care of restoring the fragments we previously had instantiated.
|
||||
if (mFragments.size() > position) {
|
||||
Fragment f = mFragments.get(position);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
|
||||
Fragment fragment = getItem(position);
|
||||
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
|
||||
if (mSavedState.size() > position) {
|
||||
Fragment.SavedState fss = mSavedState.get(position);
|
||||
if (fss != null) {
|
||||
fragment.setInitialSavedState(fss);
|
||||
}
|
||||
}
|
||||
while (mFragments.size() <= position) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
fragment.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
|
||||
fragment.setUserVisibleHint(false);
|
||||
}
|
||||
|
||||
mFragments.set(position, fragment);
|
||||
mCurTransaction.add(container.getId(), fragment);
|
||||
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment) object;
|
||||
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
|
||||
+ " v=" + ((Fragment)object).getView());
|
||||
while (mSavedState.size() <= position) {
|
||||
mSavedState.add(null);
|
||||
}
|
||||
mSavedState.set(position, fragment.isAdded()
|
||||
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
|
||||
mFragments.set(position, null);
|
||||
|
||||
mCurTransaction.remove(fragment);
|
||||
if (fragment == mCurrentPrimaryItem) {
|
||||
mCurrentPrimaryItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"ReferenceEquality", "deprecation"})
|
||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
Fragment fragment = (Fragment)object;
|
||||
if (fragment != mCurrentPrimaryItem) {
|
||||
if (mCurrentPrimaryItem != null) {
|
||||
mCurrentPrimaryItem.setMenuVisibility(false);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
|
||||
} else {
|
||||
mCurrentPrimaryItem.setUserVisibleHint(false);
|
||||
}
|
||||
}
|
||||
fragment.setMenuVisibility(true);
|
||||
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
if (mCurTransaction == null) {
|
||||
mCurTransaction = mFragmentManager.beginTransaction();
|
||||
}
|
||||
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
|
||||
} else {
|
||||
fragment.setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
mCurrentPrimaryItem = fragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishUpdate(@NonNull ViewGroup container) {
|
||||
if (mCurTransaction != null) {
|
||||
mCurTransaction.commitNowAllowingStateLoss();
|
||||
mCurTransaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return ((Fragment)object).getView() == view;
|
||||
}
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
private final String SELECTED_FRAGMENT = "selected_fragment";
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Parcelable saveState() {
|
||||
Bundle state = null;
|
||||
if (mSavedState.size() > 0) {
|
||||
state = new Bundle();
|
||||
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
|
||||
mSavedState.toArray(fss);
|
||||
state.putParcelableArray("states", fss);
|
||||
}
|
||||
for (int i=0; i<mFragments.size(); i++) {
|
||||
Fragment f = mFragments.get(i);
|
||||
if (f != null && f.isAdded()) {
|
||||
if (state == null) {
|
||||
state = new Bundle();
|
||||
}
|
||||
String key = "f" + i;
|
||||
mFragmentManager.putFragment(state, key, f);
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// Check if it's the same fragment instance
|
||||
if (f == mCurrentPrimaryItem) {
|
||||
state.putString(SELECTED_FRAGMENT, key);
|
||||
}
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
|
||||
if (state != null) {
|
||||
Bundle bundle = (Bundle)state;
|
||||
bundle.setClassLoader(loader);
|
||||
Parcelable[] fss = bundle.getParcelableArray("states");
|
||||
mSavedState.clear();
|
||||
mFragments.clear();
|
||||
if (fss != null) {
|
||||
for (int i=0; i<fss.length; i++) {
|
||||
mSavedState.add((Fragment.SavedState)fss[i]);
|
||||
}
|
||||
}
|
||||
Iterable<String> keys = bundle.keySet();
|
||||
for (String key: keys) {
|
||||
if (key.startsWith("f")) {
|
||||
int index = Integer.parseInt(key.substring(1));
|
||||
Fragment f = mFragmentManager.getFragment(bundle, key);
|
||||
if (f != null) {
|
||||
while (mFragments.size() <= index) {
|
||||
mFragments.add(null);
|
||||
}
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
final boolean wasSelected = bundle.getString(SELECTED_FRAGMENT, "").equals(key);
|
||||
f.setMenuVisibility(wasSelected);
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
mFragments.set(index, f);
|
||||
} else {
|
||||
Log.w(TAG, "Bad fragment at key " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,6 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -26,6 +20,12 @@ import android.widget.RadioButton;
|
|||
import android.widget.RadioGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
|
|
@ -51,12 +51,11 @@ import org.schabi.newpipe.util.NavigationHelper;
|
|||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.Icepick;
|
||||
|
|
@ -631,78 +630,18 @@ public class RouterActivity extends AppCompatActivity {
|
|||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Removes invisible separators (\p{Z}) and punctuation characters including
|
||||
* brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for
|
||||
* more details.
|
||||
*/
|
||||
private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]";
|
||||
|
||||
@Nullable
|
||||
private String getUrl(Intent intent) {
|
||||
// first gather data and find service
|
||||
String videoUrl = null;
|
||||
String foundUrl = null;
|
||||
if (intent.getData() != null) {
|
||||
// this means the video was called though another app
|
||||
videoUrl = intent.getData().toString();
|
||||
// Called from another app
|
||||
foundUrl = intent.getData().toString();
|
||||
} else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
|
||||
//this means that vidoe was called through share menu
|
||||
String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
final String[] uris = getUris(extraText);
|
||||
videoUrl = uris.length > 0 ? uris[0] : null;
|
||||
// Called from the share menu
|
||||
final String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
foundUrl = UrlFinder.firstUrlFromInput(extraText);
|
||||
}
|
||||
|
||||
return videoUrl;
|
||||
}
|
||||
|
||||
private String removeHeadingGibberish(final String input) {
|
||||
int start = 0;
|
||||
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
|
||||
if (!input.substring(i, i + 1).matches("\\p{L}")) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return input.substring(start, input.length());
|
||||
}
|
||||
|
||||
private String trim(final String input) {
|
||||
if (input == null || input.length() < 1) {
|
||||
return input;
|
||||
} else {
|
||||
String output = input;
|
||||
while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(1);
|
||||
}
|
||||
while (output.length() > 0
|
||||
&& output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) {
|
||||
output = output.substring(0, output.length() - 1);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all Strings which look remotely like URLs from a text.
|
||||
* Used if NewPipe was called through share menu.
|
||||
*
|
||||
* @param sharedText text to scan for URLs.
|
||||
* @return potential URLs
|
||||
*/
|
||||
protected String[] getUris(final String sharedText) {
|
||||
final Collection<String> result = new HashSet<>();
|
||||
if (sharedText != null) {
|
||||
final String[] array = sharedText.split("\\p{Space}");
|
||||
for (String s : array) {
|
||||
s = trim(s);
|
||||
if (s.length() != 0) {
|
||||
if (s.matches(".+://.+")) {
|
||||
result.add(removeHeadingGibberish(s));
|
||||
} else if (s.matches(".+\\..+")) {
|
||||
result.add("http://" + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
return foundUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import org.schabi.newpipe.BaseFragment;
|
|||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.ReCaptchaActivity;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
|
@ -181,6 +182,9 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
|||
if (exception instanceof ReCaptchaException) {
|
||||
onReCaptchaException((ReCaptchaException) exception);
|
||||
return true;
|
||||
} else if (exception instanceof ContentNotAvailableException) {
|
||||
showError(getString(R.string.content_not_available), false);
|
||||
return true;
|
||||
} else if (exception instanceof IOException) {
|
||||
showError(getString(R.string.network_error), true);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import androidx.appcompat.app.ActionBar;
|
|||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
|
@ -185,7 +185,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||
updateTitleForTab(tab.getPosition());
|
||||
}
|
||||
|
||||
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private static class SelectedTabsPagerAdapter extends FragmentStatePagerAdapterMenuWorkaround {
|
||||
private final Context context;
|
||||
private final List<Tab> internalTabsList;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ import org.schabi.newpipe.download.DownloadDialog;
|
|||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
||||
|
|
@ -1223,20 +1222,12 @@ public class VideoDetailFragment
|
|||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
else if (exception instanceof ContentNotAvailableException) {
|
||||
showError(getString(R.string.content_not_available), false);
|
||||
} else {
|
||||
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException
|
||||
? R.string.youtube_signature_decryption_error
|
||||
: exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.REQUESTED_STREAM,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
url,
|
||||
errorId);
|
||||
}
|
||||
int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error
|
||||
: exception instanceof ExtractionException ? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_STREAM,
|
||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,16 +440,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
if (exception instanceof ContentNotAvailableException) {
|
||||
showError(getString(R.string.content_not_available), false);
|
||||
} else {
|
||||
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.REQUESTED_CHANNEL,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
url,
|
||||
errorId);
|
||||
}
|
||||
int errorId = exception instanceof ExtractionException
|
||||
? R.string.parsing_error : R.string.general_error;
|
||||
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL,
|
||||
NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.view.ViewGroup;
|
|||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
|
||||
public class StreamGridInfoItemHolder extends StreamMiniInfoItemHolder {
|
||||
public class StreamGridInfoItemHolder extends StreamInfoItemHolder {
|
||||
|
||||
public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_grid_item, parent);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder {
|
|||
public final TextView itemAdditionalDetails;
|
||||
|
||||
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) {
|
||||
super(infoItemBuilder, R.layout.list_stream_item, parent);
|
||||
this(infoItemBuilder, R.layout.list_stream_item, parent);
|
||||
}
|
||||
|
||||
public StreamInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||
super(infoItemBuilder, layoutId, parent);
|
||||
itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
|||
.putBoolean(getString(R.string.feed_use_dedicated_fetch_method_key), !usingDedicatedMethod)
|
||||
.apply()
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setPositiveButton(resources.getString(R.string.finish), null)
|
||||
.create()
|
||||
.show()
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ class FeedGroupDialog : DialogFragment() {
|
|||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
||||
|
||||
val selectedCountText = getString(R.string.feed_group_dialog_selection_count, this.selectedSubscriptions.size)
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_selector_header_info.text = selectedCountText
|
||||
|
||||
|
|
@ -234,7 +235,8 @@ class FeedGroupDialog : DialogFragment() {
|
|||
item.isSelected = isSelected
|
||||
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
|
||||
|
||||
val updateSelectedCountText = getString(R.string.feed_group_dialog_selection_count, this.selectedSubscriptions.size)
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = updateSelectedCountText
|
||||
subscriptions_selector_header_info.text = updateSelectedCountText
|
||||
}
|
||||
|
|
|
|||
|
|
@ -448,7 +448,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
|
||||
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
|
||||
muteButton.setColorFilter(ContextCompat.getColor(getApplicationContext(), isMuted ? R.color.white : R.color.gray));
|
||||
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(),
|
||||
isMuted ? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@ package org.schabi.newpipe.player;
|
|||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
|
@ -700,11 +698,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
|||
item.setTitle(player.isMuted() ? R.string.unmute : R.string.mute);
|
||||
|
||||
//2) Icon change accordingly to current App Theme
|
||||
TypedArray a = getTheme().obtainStyledAttributes(R.style.Theme_AppCompat, new int[]{R.attr.volume_off});
|
||||
int attributeResourceId = a.getResourceId(0, 0);
|
||||
Drawable drawableMuted = getResources().getDrawable(attributeResourceId);
|
||||
Drawable drawableUnmuted = getResources().getDrawable(R.drawable.ic_volume_off_gray_24dp);
|
||||
item.setIcon(player.isMuted() ? drawableMuted : drawableUnmuted);
|
||||
item.setIcon(player.isMuted() ? getThemedDrawable(R.attr.volume_off) : getThemedDrawable(R.attr.volume_on));
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable getThemedDrawable(int attribute) {
|
||||
return getResources().getDrawable(
|
||||
getTheme().obtainStyledAttributes(R.style.Theme_AppCompat, new int[]{attribute})
|
||||
.getResourceId(0, 0));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
|
|||
displayedDurationValues.add(durationsValue);
|
||||
try {
|
||||
displayedDescriptionValues.add(String.format(
|
||||
res.getQuantityString(R.plurals.dynamic_seek_duration_description,
|
||||
res.getQuantityString(R.plurals.seconds,
|
||||
currentDurationValue),
|
||||
currentDurationValue));
|
||||
} catch (Resources.NotFoundException ignored) {
|
||||
|
|
@ -88,7 +88,7 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
|
|||
durations.setEntryValues(displayedDurationValues.toArray(new CharSequence[0]));
|
||||
durations.setEntries(displayedDescriptionValues.toArray(new CharSequence[0]));
|
||||
final int selectedDuration = Integer.parseInt(durations.getValue());
|
||||
if (selectedDuration / (int) DateUtils.SECOND_IN_MILLIS % 10 == 5) {
|
||||
if (inexactSeek && selectedDuration / (int) DateUtils.SECOND_IN_MILLIS % 10 == 5) {
|
||||
final int newDuration = selectedDuration / (int) DateUtils.SECOND_IN_MILLIS + 5;
|
||||
durations.setValue(Integer.toString(newDuration * (int) DateUtils.SECOND_IN_MILLIS));
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ public class WebMReader {
|
|||
private final static int ID_DefaultDuration = 0x3E383;
|
||||
private final static int ID_FlagLacing = 0x1C;
|
||||
private final static int ID_CodecDelay = 0x16AA;
|
||||
private final static int ID_SeekPreRoll = 0x16BB;
|
||||
|
||||
private final static int ID_Cluster = 0x0F43B675;
|
||||
private final static int ID_Timecode = 0x67;
|
||||
|
|
@ -332,6 +333,10 @@ public class WebMReader {
|
|||
break;
|
||||
case ID_CodecDelay:
|
||||
entry.codecDelay = readNumber(elem);
|
||||
break;
|
||||
case ID_SeekPreRoll:
|
||||
entry.seekPreRoll = readNumber(elem);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -414,8 +419,9 @@ public class WebMReader {
|
|||
public byte[] codecPrivate;
|
||||
public byte[] bMetadata;
|
||||
public TrackKind kind;
|
||||
public long defaultDuration;
|
||||
public long codecDelay;
|
||||
public long defaultDuration = -1;
|
||||
public long codecDelay = -1;
|
||||
public long seekPreRoll = -1;
|
||||
}
|
||||
|
||||
public class Segment {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ public class WebMWriter implements Closeable {
|
|||
private final static int BUFFER_SIZE = 8 * 1024;
|
||||
private final static int DEFAULT_TIMECODE_SCALE = 1000000;
|
||||
private final static int INTERV = 100;// 100ms on 1000000us timecode scale
|
||||
private final static int DEFAULT_CUES_EACH_MS = 5000;// 100ms on 1000000us timecode scale
|
||||
private final static int DEFAULT_CUES_EACH_MS = 5000;// 5000ms on 1000000us timecode scale
|
||||
private final static byte CLUSTER_HEADER_SIZE = 8;
|
||||
private final static int CUE_RESERVE_SIZE = 65535;
|
||||
private final static byte MINIMUM_EBML_VOID_SIZE = 4;
|
||||
|
||||
private WebMReader.WebMTrack[] infoTracks;
|
||||
private SharpStream[] sourceTracks;
|
||||
|
|
@ -38,15 +41,18 @@ public class WebMWriter implements Closeable {
|
|||
private Segment[] readersSegment;
|
||||
private Cluster[] readersCluster;
|
||||
|
||||
private int[] predefinedDurations;
|
||||
private ArrayList<ClusterInfo> clustersOffsetsSizes;
|
||||
|
||||
private byte[] outBuffer;
|
||||
private ByteBuffer outByteBuffer;
|
||||
|
||||
public WebMWriter(SharpStream... source) {
|
||||
sourceTracks = source;
|
||||
readers = new WebMReader[sourceTracks.length];
|
||||
infoTracks = new WebMTrack[sourceTracks.length];
|
||||
outBuffer = new byte[BUFFER_SIZE];
|
||||
outByteBuffer = ByteBuffer.wrap(outBuffer);
|
||||
clustersOffsetsSizes = new ArrayList<>(256);
|
||||
}
|
||||
|
||||
public WebMTrack[] getTracksFromSource(int sourceIndex) throws IllegalStateException {
|
||||
|
|
@ -83,11 +89,9 @@ public class WebMWriter implements Closeable {
|
|||
try {
|
||||
readersSegment = new Segment[readers.length];
|
||||
readersCluster = new Cluster[readers.length];
|
||||
predefinedDurations = new int[readers.length];
|
||||
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
infoTracks[i] = readers[i].selectTrack(trackIndex[i]);
|
||||
predefinedDurations[i] = -1;
|
||||
readersSegment[i] = readers[i].getNextSegment();
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -118,6 +122,8 @@ public class WebMWriter implements Closeable {
|
|||
readersSegment = null;
|
||||
readersCluster = null;
|
||||
outBuffer = null;
|
||||
outByteBuffer = null;
|
||||
clustersOffsetsSizes = null;
|
||||
}
|
||||
|
||||
public void build(SharpStream out) throws IOException, RuntimeException {
|
||||
|
|
@ -140,7 +146,7 @@ public class WebMWriter implements Closeable {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00// segment content size
|
||||
});
|
||||
|
||||
long baseSegmentOffset = written + listBuffer.get(0).length;
|
||||
long segmentOffset = written + listBuffer.get(0).length;
|
||||
|
||||
/* seek head */
|
||||
listBuffer.add(new byte[]{
|
||||
|
|
@ -177,20 +183,22 @@ public class WebMWriter implements Closeable {
|
|||
/* tracks */
|
||||
listBuffer.addAll(makeTracks());
|
||||
|
||||
for (byte[] buff : listBuffer) {
|
||||
dump(buff, out);
|
||||
}
|
||||
dump(listBuffer, out);
|
||||
|
||||
// reserve space for Cues element, but is a waste of space (actually is 64 KiB)
|
||||
// TODO: better Cue maker
|
||||
long cueReservedOffset = written;
|
||||
dump(new byte[]{(byte) 0xec, 0x20, (byte) 0xff, (byte) 0xfb}, out);
|
||||
int reserved = (1024 * 63) - 4;
|
||||
while (reserved > 0) {
|
||||
int write = Math.min(reserved, outBuffer.length);
|
||||
out.write(outBuffer, 0, write);
|
||||
reserved -= write;
|
||||
written += write;
|
||||
// reserve space for Cues element
|
||||
long cueOffset = written;
|
||||
make_EBML_void(out, CUE_RESERVE_SIZE, true);
|
||||
|
||||
int[] defaultSampleDuration = new int[infoTracks.length];
|
||||
long[] duration = new long[infoTracks.length];
|
||||
|
||||
for (int i = 0; i < infoTracks.length; i++) {
|
||||
if (infoTracks[i].defaultDuration < 0) {
|
||||
defaultSampleDuration[i] = -1;// not available
|
||||
} else {
|
||||
defaultSampleDuration[i] = (int) Math.ceil(infoTracks[i].defaultDuration / (float) DEFAULT_TIMECODE_SCALE);
|
||||
}
|
||||
duration[i] = -1;
|
||||
}
|
||||
|
||||
// Select a track for the cue
|
||||
|
|
@ -198,16 +206,8 @@ public class WebMWriter implements Closeable {
|
|||
long nextCueTime = infoTracks[cuesForTrackId].trackType == 1 ? -1 : 0;
|
||||
ArrayList<KeyFrame> keyFrames = new ArrayList<>(32);
|
||||
|
||||
ArrayList<Long> clusterOffsets = new ArrayList<>(32);
|
||||
ArrayList<Integer> clusterSizes = new ArrayList<>(32);
|
||||
|
||||
long duration = 0;
|
||||
int durationFromTrackId = 0;
|
||||
|
||||
byte[] bTimecode = makeTimecode(0);
|
||||
|
||||
int firstClusterOffset = (int) written;
|
||||
long currentClusterOffset = makeCluster(out, bTimecode, 0, clusterOffsets, clusterSizes);
|
||||
long currentClusterOffset = makeCluster(out, 0, 0, true);
|
||||
|
||||
long baseTimecode = 0;
|
||||
long limitTimecode = -1;
|
||||
|
|
@ -239,8 +239,7 @@ public class WebMWriter implements Closeable {
|
|||
newClusterByTrackId = -1;
|
||||
baseTimecode = bloq.absoluteTimecode;
|
||||
limitTimecode = baseTimecode + INTERV;
|
||||
bTimecode = makeTimecode(baseTimecode);
|
||||
currentClusterOffset = makeCluster(out, bTimecode, currentClusterOffset, clusterOffsets, clusterSizes);
|
||||
currentClusterOffset = makeCluster(out, baseTimecode, currentClusterOffset, true);
|
||||
}
|
||||
|
||||
if (cuesForTrackId == i) {
|
||||
|
|
@ -248,19 +247,18 @@ public class WebMWriter implements Closeable {
|
|||
if (nextCueTime > -1) {
|
||||
nextCueTime += DEFAULT_CUES_EACH_MS;
|
||||
}
|
||||
keyFrames.add(
|
||||
new KeyFrame(baseSegmentOffset, currentClusterOffset - 8, written, bTimecode.length, bloq.absoluteTimecode)
|
||||
);
|
||||
keyFrames.add(new KeyFrame(segmentOffset, currentClusterOffset, written, bloq.absoluteTimecode));
|
||||
}
|
||||
}
|
||||
|
||||
writeBlock(out, bloq, baseTimecode);
|
||||
blockWritten++;
|
||||
|
||||
if (bloq.absoluteTimecode > duration) {
|
||||
duration = bloq.absoluteTimecode;
|
||||
durationFromTrackId = bloq.trackNumber;
|
||||
if (defaultSampleDuration[i] < 0 && duration[i] >= 0) {
|
||||
// if the sample duration in unknown, calculate using current_duration - previous_duration
|
||||
defaultSampleDuration[i] = (int) (bloq.absoluteTimecode - duration[i]);
|
||||
}
|
||||
duration[i] = bloq.absoluteTimecode;
|
||||
|
||||
if (limitTimecode < 0) {
|
||||
limitTimecode = bloq.absoluteTimecode + INTERV;
|
||||
|
|
@ -276,55 +274,61 @@ public class WebMWriter implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
makeCluster(out, null, currentClusterOffset, null, clusterSizes);
|
||||
makeCluster(out, -1, currentClusterOffset, false);
|
||||
|
||||
long segmentSize = written - offsetSegmentSizeSet - 7;
|
||||
|
||||
/* ---- final step write offsets and sizes ---- */
|
||||
/* Segment size */
|
||||
seekTo(out, offsetSegmentSizeSet);
|
||||
writeLong(out, segmentSize);
|
||||
outByteBuffer.putLong(0, segmentSize);
|
||||
out.write(outBuffer, 1, DataReader.LONG_SIZE - 1);
|
||||
|
||||
if (predefinedDurations[durationFromTrackId] > -1) {
|
||||
duration += predefinedDurations[durationFromTrackId];// this value is full-filled in makeTrackEntry() method
|
||||
}
|
||||
seekTo(out, offsetInfoDurationSet);
|
||||
writeFloat(out, duration);
|
||||
|
||||
firstClusterOffset -= baseSegmentOffset;
|
||||
seekTo(out, offsetClusterSet);
|
||||
writeInt(out, firstClusterOffset);
|
||||
|
||||
seekTo(out, cueReservedOffset);
|
||||
|
||||
/* Cue */
|
||||
dump(new byte[]{0x1c, 0x53, (byte) 0xbb, 0x6b, 0x20, 0x00, 0x00}, out);
|
||||
|
||||
for (KeyFrame keyFrame : keyFrames) {
|
||||
for (byte[] buffer : makeCuePoint(cuesForTrackId, keyFrame)) {
|
||||
dump(buffer, out);
|
||||
if (written >= (cueReservedOffset + 65535 - 16)) {
|
||||
throw new IOException("Too many Cues");
|
||||
}
|
||||
/* Segment duration */
|
||||
long longestDuration = 0;
|
||||
for (int i = 0; i < duration.length; i++) {
|
||||
if (defaultSampleDuration[i] > 0) {
|
||||
duration[i] += defaultSampleDuration[i];
|
||||
}
|
||||
if (duration[i] > longestDuration) {
|
||||
longestDuration = duration[i];
|
||||
}
|
||||
}
|
||||
short cueSize = (short) (written - cueReservedOffset - 7);
|
||||
seekTo(out, offsetInfoDurationSet);
|
||||
outByteBuffer.putFloat(0, longestDuration);
|
||||
dump(outBuffer, DataReader.FLOAT_SIZE, out);
|
||||
|
||||
/* EBML Void */
|
||||
ByteBuffer voidBuffer = ByteBuffer.allocate(4);
|
||||
voidBuffer.putShort((short) 0xec20);
|
||||
voidBuffer.putShort((short) (firstClusterOffset - written - 4));
|
||||
dump(voidBuffer.array(), out);
|
||||
/* first Cluster offset */
|
||||
firstClusterOffset -= segmentOffset;
|
||||
writeInt(out, offsetClusterSet, firstClusterOffset);
|
||||
|
||||
seekTo(out, offsetCuesSet);
|
||||
writeInt(out, (int) (cueReservedOffset - baseSegmentOffset));
|
||||
seekTo(out, cueOffset);
|
||||
|
||||
seekTo(out, cueReservedOffset + 5);
|
||||
writeShort(out, cueSize);
|
||||
/* Cue */
|
||||
short cueSize = 0;
|
||||
dump(new byte[]{0x1c, 0x53, (byte) 0xbb, 0x6b, 0x20, 0x00, 0x00}, out);// header size is 7
|
||||
|
||||
for (int i = 0; i < clusterSizes.size(); i++) {
|
||||
seekTo(out, clusterOffsets.get(i));
|
||||
byte[] buffer = ByteBuffer.allocate(4).putInt(clusterSizes.get(i) | 0x10000000).array();
|
||||
dump(buffer, out);
|
||||
for (KeyFrame keyFrame : keyFrames) {
|
||||
int size = makeCuePoint(cuesForTrackId, keyFrame, outBuffer);
|
||||
|
||||
if ((cueSize + size + 7 + MINIMUM_EBML_VOID_SIZE) > CUE_RESERVE_SIZE) {
|
||||
break;// no space left
|
||||
}
|
||||
|
||||
cueSize += size;
|
||||
dump(outBuffer, size, out);
|
||||
}
|
||||
|
||||
make_EBML_void(out, CUE_RESERVE_SIZE - cueSize - 7, false);
|
||||
|
||||
seekTo(out, cueOffset + 5);
|
||||
outByteBuffer.putShort(0, cueSize);
|
||||
dump(outBuffer, DataReader.SHORT_SIZE, out);
|
||||
|
||||
/* seek head, seek for cues element */
|
||||
writeInt(out, offsetCuesSet, (int) (cueOffset - segmentOffset));
|
||||
|
||||
for (ClusterInfo cluster : clustersOffsetsSizes) {
|
||||
writeInt(out, cluster.offset, cluster.size | 0x10000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,25 +379,10 @@ public class WebMWriter implements Closeable {
|
|||
written = offset;
|
||||
}
|
||||
|
||||
private void writeLong(SharpStream stream, long number) throws IOException {
|
||||
byte[] buffer = ByteBuffer.allocate(DataReader.LONG_SIZE).putLong(number).array();
|
||||
stream.write(buffer, 1, buffer.length - 1);
|
||||
written += buffer.length - 1;
|
||||
}
|
||||
|
||||
private void writeFloat(SharpStream stream, float number) throws IOException {
|
||||
byte[] buffer = ByteBuffer.allocate(DataReader.FLOAT_SIZE).putFloat(number).array();
|
||||
dump(buffer, stream);
|
||||
}
|
||||
|
||||
private void writeShort(SharpStream stream, short number) throws IOException {
|
||||
byte[] buffer = ByteBuffer.allocate(DataReader.SHORT_SIZE).putShort(number).array();
|
||||
dump(buffer, stream);
|
||||
}
|
||||
|
||||
private void writeInt(SharpStream stream, int number) throws IOException {
|
||||
byte[] buffer = ByteBuffer.allocate(DataReader.INTEGER_SIZE).putInt(number).array();
|
||||
dump(buffer, stream);
|
||||
private void writeInt(SharpStream stream, long offset, int number) throws IOException {
|
||||
seekTo(stream, offset);
|
||||
outByteBuffer.putInt(0, number);
|
||||
dump(outBuffer, DataReader.INTEGER_SIZE, stream);
|
||||
}
|
||||
|
||||
private void writeBlock(SharpStream stream, Block bloq, long clusterTimecode) throws IOException {
|
||||
|
|
@ -416,47 +405,43 @@ public class WebMWriter implements Closeable {
|
|||
}
|
||||
listBuffer.set(1, encode(blockSize, false));
|
||||
|
||||
for (byte[] buff : listBuffer) {
|
||||
dump(buff, stream);
|
||||
}
|
||||
dump(listBuffer, stream);
|
||||
|
||||
int read;
|
||||
while ((read = bloq.data.read(outBuffer)) > 0) {
|
||||
stream.write(outBuffer, 0, read);
|
||||
written += read;
|
||||
dump(outBuffer, read, stream);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] makeTimecode(long timecode) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(9);
|
||||
buffer.put((byte) 0xe7);
|
||||
buffer.put(encode(timecode, true));
|
||||
private long makeCluster(SharpStream stream, long timecode, long offset, boolean create) throws IOException {
|
||||
ClusterInfo cluster;
|
||||
|
||||
byte[] res = new byte[buffer.position()];
|
||||
System.arraycopy(buffer.array(), 0, res, 0, res.length);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private long makeCluster(SharpStream stream, byte[] bTimecode, long startOffset, ArrayList<Long> clusterOffsets, ArrayList<Integer> clusterSizes) throws IOException {
|
||||
if (startOffset > 0) {
|
||||
clusterSizes.add((int) (written - startOffset));// size for last offset
|
||||
if (offset > 0) {
|
||||
// save the size of the previous cluster (maximum 256 MiB)
|
||||
cluster = clustersOffsetsSizes.get(clustersOffsetsSizes.size() - 1);
|
||||
cluster.size = (int) (written - offset - CLUSTER_HEADER_SIZE);
|
||||
}
|
||||
|
||||
if (clusterOffsets != null) {
|
||||
offset = written;
|
||||
|
||||
if (create) {
|
||||
/* cluster */
|
||||
dump(new byte[]{0x1f, 0x43, (byte) 0xb6, 0x75}, stream);
|
||||
clusterOffsets.add(written);// warning: max cluster size is 256 MiB
|
||||
dump(new byte[]{0x10, 0x00, 0x00, 0x00}, stream);
|
||||
|
||||
startOffset = written;// size for the this cluster
|
||||
cluster = new ClusterInfo();
|
||||
cluster.offset = written;
|
||||
clustersOffsetsSizes.add(cluster);
|
||||
|
||||
dump(bTimecode, stream);
|
||||
dump(new byte[]{
|
||||
0x10, 0x00, 0x00, 0x00,
|
||||
/* timestamp */
|
||||
(byte) 0xe7
|
||||
}, stream);
|
||||
|
||||
return startOffset;
|
||||
dump(encode(timecode, true), stream);
|
||||
}
|
||||
|
||||
return -1;
|
||||
return offset;
|
||||
}
|
||||
|
||||
private void makeEBML(SharpStream stream) throws IOException {
|
||||
|
|
@ -509,13 +494,24 @@ public class WebMWriter implements Closeable {
|
|||
buffer.add(new byte[]{(byte) 0x86});
|
||||
buffer.addAll(encode(track.codecId));
|
||||
|
||||
/* codec delay*/
|
||||
if (track.codecDelay >= 0) {
|
||||
buffer.add(new byte[]{0x56, (byte) 0xAA});
|
||||
buffer.add(encode(track.codecDelay, true));
|
||||
}
|
||||
|
||||
/* codec seek pre-roll*/
|
||||
if (track.seekPreRoll >= 0) {
|
||||
buffer.add(new byte[]{0x56, (byte) 0xBB});
|
||||
buffer.add(encode(track.seekPreRoll, true));
|
||||
}
|
||||
|
||||
/* type */
|
||||
buffer.add(new byte[]{(byte) 0x83});
|
||||
buffer.add(encode(track.trackType, true));
|
||||
|
||||
/* default duration */
|
||||
if (track.defaultDuration != 0) {
|
||||
predefinedDurations[internalTrackId] = (int) Math.ceil(track.defaultDuration / (float) DEFAULT_TIMECODE_SCALE);
|
||||
if (track.defaultDuration >= 0) {
|
||||
buffer.add(new byte[]{0x23, (byte) 0xe3, (byte) 0x83});
|
||||
buffer.add(encode(track.defaultDuration, true));
|
||||
}
|
||||
|
|
@ -538,21 +534,29 @@ public class WebMWriter implements Closeable {
|
|||
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> makeCuePoint(int internalTrackId, KeyFrame keyFrame) {
|
||||
ArrayList<byte[]> buffer = new ArrayList<>(5);
|
||||
private int makeCuePoint(int internalTrackId, KeyFrame keyFrame, byte[] buffer) {
|
||||
ArrayList<byte[]> cue = new ArrayList<>(5);
|
||||
|
||||
/* CuePoint */
|
||||
buffer.add(new byte[]{(byte) 0xbb});
|
||||
buffer.add(null);
|
||||
cue.add(new byte[]{(byte) 0xbb});
|
||||
cue.add(null);
|
||||
|
||||
/* CueTime */
|
||||
buffer.add(new byte[]{(byte) 0xb3});
|
||||
buffer.add(encode(keyFrame.atTimecode, true));
|
||||
cue.add(new byte[]{(byte) 0xb3});
|
||||
cue.add(encode(keyFrame.duration, true));
|
||||
|
||||
/* CueTrackPosition */
|
||||
buffer.addAll(makeCueTrackPosition(internalTrackId, keyFrame));
|
||||
cue.addAll(makeCueTrackPosition(internalTrackId, keyFrame));
|
||||
|
||||
return lengthFor(buffer);
|
||||
int size = 0;
|
||||
lengthFor(cue);
|
||||
|
||||
for (byte[] buff : cue) {
|
||||
System.arraycopy(buff, 0, buffer, size, buff.length);
|
||||
size += buff.length;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> makeCueTrackPosition(int internalTrackId, KeyFrame keyFrame) {
|
||||
|
|
@ -568,20 +572,48 @@ public class WebMWriter implements Closeable {
|
|||
|
||||
/* CueClusterPosition */
|
||||
buffer.add(new byte[]{(byte) 0xf1});
|
||||
buffer.add(encode(keyFrame.atCluster, true));
|
||||
buffer.add(encode(keyFrame.clusterPosition, true));
|
||||
|
||||
/* CueRelativePosition */
|
||||
if (keyFrame.atBlock > 0) {
|
||||
if (keyFrame.relativePosition > 0) {
|
||||
buffer.add(new byte[]{(byte) 0xf0});
|
||||
buffer.add(encode(keyFrame.atBlock, true));
|
||||
buffer.add(encode(keyFrame.relativePosition, true));
|
||||
}
|
||||
|
||||
return lengthFor(buffer);
|
||||
}
|
||||
|
||||
private void make_EBML_void(SharpStream out, int size, boolean wipe) throws IOException {
|
||||
/* ebml void */
|
||||
outByteBuffer.putShort(0, (short) 0xec20);
|
||||
outByteBuffer.putShort(2, (short) (size - 4));
|
||||
|
||||
dump(outBuffer, 4, out);
|
||||
|
||||
if (wipe) {
|
||||
size -= 4;
|
||||
while (size > 0) {
|
||||
int write = Math.min(size, outBuffer.length);
|
||||
dump(outBuffer, write, out);
|
||||
size -= write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dump(byte[] buffer, SharpStream stream) throws IOException {
|
||||
stream.write(buffer);
|
||||
written += buffer.length;
|
||||
dump(buffer, buffer.length, stream);
|
||||
}
|
||||
|
||||
private void dump(byte[] buffer, int count, SharpStream stream) throws IOException {
|
||||
stream.write(buffer, 0, count);
|
||||
written += count;
|
||||
}
|
||||
|
||||
private void dump(ArrayList<byte[]> buffers, SharpStream stream) throws IOException {
|
||||
for (byte[] buffer : buffers) {
|
||||
stream.write(buffer);
|
||||
written += buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<byte[]> lengthFor(ArrayList<byte[]> buffer) {
|
||||
|
|
@ -614,11 +646,11 @@ public class WebMWriter implements Closeable {
|
|||
byte[] buffer = new byte[offset + length];
|
||||
long marker = (long) Math.floor((length - 1f) / 8f);
|
||||
|
||||
float mul = 1;
|
||||
for (int i = length - 1; i >= 0; i--, mul *= 0x100) {
|
||||
long b = (long) Math.floor(number / mul);
|
||||
int shift = 0;
|
||||
for (int i = length - 1; i >= 0; i--, shift += 8) {
|
||||
long b = number >>> shift;
|
||||
if (!withLength && i == marker) {
|
||||
b = b | (0x80 >> (length - 1));
|
||||
b = b | (0x80 >>> (length - 1));
|
||||
}
|
||||
buffer[offset + i] = (byte) b;
|
||||
}
|
||||
|
|
@ -686,17 +718,15 @@ public class WebMWriter implements Closeable {
|
|||
|
||||
class KeyFrame {
|
||||
|
||||
KeyFrame(long segment, long cluster, long block, int bTimecodeLength, long timecode) {
|
||||
atCluster = cluster - segment;
|
||||
if ((block - bTimecodeLength) > cluster) {
|
||||
atBlock = (int) (block - cluster);
|
||||
}
|
||||
atTimecode = timecode;
|
||||
KeyFrame(long segment, long cluster, long block, long timecode) {
|
||||
clusterPosition = cluster - segment;
|
||||
relativePosition = (int) (block - cluster - CLUSTER_HEADER_SIZE);
|
||||
duration = timecode;
|
||||
}
|
||||
|
||||
long atCluster;
|
||||
int atBlock;
|
||||
long atTimecode;
|
||||
final long clusterPosition;
|
||||
final int relativePosition;
|
||||
final long duration;
|
||||
}
|
||||
|
||||
class Block {
|
||||
|
|
@ -717,4 +747,11 @@ public class WebMWriter implements Closeable {
|
|||
return String.format("trackNumber=%s isKeyFrame=%S absoluteTimecode=%s", trackNumber, isKeyframe(), absoluteTimecode);
|
||||
}
|
||||
}
|
||||
|
||||
class ClusterInfo {
|
||||
|
||||
long offset;
|
||||
int size;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,360 @@
|
|||
/* THIS FILE WAS MODIFIED, CHANGES ARE DOCUMENTED. */
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.util.urlfinder;
|
||||
|
||||
import androidx.annotation.RestrictTo;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
|
||||
|
||||
/**
|
||||
* Commonly used regular expression patterns.
|
||||
*/
|
||||
public final class PatternsCompat {
|
||||
/**
|
||||
* Regular expression to match all IANA top-level domains.
|
||||
*
|
||||
* List accurate as of 2015/11/24. List taken from:
|
||||
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
* This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
|
||||
*/
|
||||
static final String IANA_TOP_LEVEL_DOMAINS =
|
||||
"(?:"
|
||||
+ "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active"
|
||||
+ "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam"
|
||||
+ "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates"
|
||||
+ "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])"
|
||||
+ "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva"
|
||||
+ "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black"
|
||||
+ "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique"
|
||||
+ "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business"
|
||||
+ "|buzz|bzh|b[abdefghijmnorstvwyz])"
|
||||
+ "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards"
|
||||
+ "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo"
|
||||
+ "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco"
|
||||
+ "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach"
|
||||
+ "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos"
|
||||
+ "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses"
|
||||
+ "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
|
||||
+ "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta"
|
||||
+ "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount"
|
||||
+ "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])"
|
||||
+ "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises"
|
||||
+ "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed"
|
||||
+ "|express|e[cegrstu])"
|
||||
+ "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film"
|
||||
+ "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth"
|
||||
+ "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi"
|
||||
+ "|f[ijkmor])"
|
||||
+ "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving"
|
||||
+ "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger"
|
||||
+ "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
|
||||
+ "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings"
|
||||
+ "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai"
|
||||
+ "|h[kmnrtu])"
|
||||
+ "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute"
|
||||
+ "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])"
|
||||
+ "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])"
|
||||
+ "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])"
|
||||
+ "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc"
|
||||
+ "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live"
|
||||
+ "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])"
|
||||
+ "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba"
|
||||
+ "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda"
|
||||
+ "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar"
|
||||
+ "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])"
|
||||
+ "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk"
|
||||
+ "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])"
|
||||
+ "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka"
|
||||
+ "|otsuka|ovh|om)"
|
||||
+ "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography"
|
||||
+ "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing"
|
||||
+ "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property"
|
||||
+ "|protection|pub|p[aefghklmnrstwy])"
|
||||
+ "|(?:qpon|quebec|qa)"
|
||||
+ "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals"
|
||||
+ "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks"
|
||||
+ "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])"
|
||||
+ "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo"
|
||||
+ "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security"
|
||||
+ "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski"
|
||||
+ "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting"
|
||||
+ "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies"
|
||||
+ "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])"
|
||||
+ "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica"
|
||||
+ "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools"
|
||||
+ "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])"
|
||||
+ "|(?:ubs|university|uno|uol|u[agksyz])"
|
||||
+ "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin"
|
||||
+ "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])"
|
||||
+ "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill"
|
||||
+ "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])"
|
||||
+ "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434"
|
||||
+ "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d"
|
||||
+ "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431"
|
||||
+ "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648"
|
||||
+ "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
|
||||
+ "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646"
|
||||
+ "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633"
|
||||
+ "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629"
|
||||
+ "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646"
|
||||
+ "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627"
|
||||
+ "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
|
||||
+ "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4"
|
||||
+ "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
|
||||
+ "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22"
|
||||
+ "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c"
|
||||
+ "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71"
|
||||
+ "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063"
|
||||
+ "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c"
|
||||
+ "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c"
|
||||
+ "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f"
|
||||
+ "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc"
|
||||
+ "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137"
|
||||
+ "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox"
|
||||
+ "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g"
|
||||
+ "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim"
|
||||
+ "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks"
|
||||
+ "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a"
|
||||
+ "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd"
|
||||
+ "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h"
|
||||
+ "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s"
|
||||
+ "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c"
|
||||
+ "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i"
|
||||
+ "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d"
|
||||
+ "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt"
|
||||
+ "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e"
|
||||
+ "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab"
|
||||
+ "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema"
|
||||
+ "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh"
|
||||
+ "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c"
|
||||
+ "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb"
|
||||
+ "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a"
|
||||
+ "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o"
|
||||
+ "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)"
|
||||
+ "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])"
|
||||
+ "|(?:zara|zip|zone|zuerich|z[amw]))";
|
||||
|
||||
public static final Pattern IP_ADDRESS
|
||||
= Pattern.compile(
|
||||
"((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
|
||||
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
|
||||
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
|
||||
+ "|[1-9][0-9]|[0-9]))");
|
||||
|
||||
/**
|
||||
* Valid UCS characters defined in RFC 3987. Excludes space characters.
|
||||
*/
|
||||
private static final String UCS_CHAR = "[" +
|
||||
"\u00A0-\uD7FF" +
|
||||
"\uF900-\uFDCF" +
|
||||
"\uFDF0-\uFFEF" +
|
||||
"\uD800\uDC00-\uD83F\uDFFD" +
|
||||
"\uD840\uDC00-\uD87F\uDFFD" +
|
||||
"\uD880\uDC00-\uD8BF\uDFFD" +
|
||||
"\uD8C0\uDC00-\uD8FF\uDFFD" +
|
||||
"\uD900\uDC00-\uD93F\uDFFD" +
|
||||
"\uD940\uDC00-\uD97F\uDFFD" +
|
||||
"\uD980\uDC00-\uD9BF\uDFFD" +
|
||||
"\uD9C0\uDC00-\uD9FF\uDFFD" +
|
||||
"\uDA00\uDC00-\uDA3F\uDFFD" +
|
||||
"\uDA40\uDC00-\uDA7F\uDFFD" +
|
||||
"\uDA80\uDC00-\uDABF\uDFFD" +
|
||||
"\uDAC0\uDC00-\uDAFF\uDFFD" +
|
||||
"\uDB00\uDC00-\uDB3F\uDFFD" +
|
||||
"\uDB44\uDC00-\uDB7F\uDFFD" +
|
||||
"&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
|
||||
|
||||
/**
|
||||
* Valid characters for IRI label defined in RFC 3987.
|
||||
*/
|
||||
private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR;
|
||||
|
||||
/**
|
||||
* Valid characters for IRI TLD defined in RFC 3987.
|
||||
*/
|
||||
private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR;
|
||||
|
||||
/**
|
||||
* RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
|
||||
*/
|
||||
private static final String IRI_LABEL =
|
||||
"[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
|
||||
|
||||
/**
|
||||
* RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters.
|
||||
*/
|
||||
private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w";
|
||||
|
||||
private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")";
|
||||
|
||||
private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
|
||||
|
||||
public static final Pattern DOMAIN_NAME
|
||||
= Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// CHANGED: Removed rtsp from supported protocols //
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
private static final String PROTOCOL = "(?i:http|https)://";
|
||||
|
||||
/* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */
|
||||
private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
|
||||
|
||||
private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
|
||||
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
|
||||
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@";
|
||||
|
||||
private static final String PORT_NUMBER = "\\:\\d{1,5}";
|
||||
|
||||
private static final String PATH_AND_QUERY = "[/\\?](?:(?:[" + LABEL_CHAR
|
||||
+ ";/\\?:@&=#~" // plus optional query params
|
||||
+ "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*";
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match most part of RFC 3987
|
||||
* Internationalized URLs, aka IRIs.
|
||||
*/
|
||||
public static final Pattern WEB_URL = Pattern.compile("("
|
||||
+ "("
|
||||
+ "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?"
|
||||
+ "(?:" + DOMAIN_NAME + ")"
|
||||
+ "(?:" + PORT_NUMBER + ")?"
|
||||
+ ")"
|
||||
+ "(" + PATH_AND_QUERY + ")?"
|
||||
+ WORD_BOUNDARY
|
||||
+ ")");
|
||||
|
||||
/**
|
||||
* Regular expression that matches known TLDs and punycode TLDs
|
||||
*/
|
||||
private static final String STRICT_TLD = "(?:" +
|
||||
IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")";
|
||||
|
||||
/**
|
||||
* Regular expression that matches host names using {@link #STRICT_TLD}
|
||||
*/
|
||||
private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+"
|
||||
+ STRICT_TLD + ")";
|
||||
|
||||
/**
|
||||
* Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or
|
||||
* {@link #IP_ADDRESS}
|
||||
*/
|
||||
private static final Pattern STRICT_DOMAIN_NAME
|
||||
= Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")");
|
||||
|
||||
/**
|
||||
* Regular expression that matches domain names without a TLD
|
||||
*/
|
||||
private static final String RELAXED_DOMAIN_NAME =
|
||||
"(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")";
|
||||
|
||||
/**
|
||||
* Regular expression to match strings that do not start with a supported protocol. The TLDs
|
||||
* are expected to be one of the known TLDs.
|
||||
*/
|
||||
private static final String WEB_URL_WITHOUT_PROTOCOL = "("
|
||||
+ WORD_BOUNDARY
|
||||
+ "(?<!:\\/\\/)"
|
||||
+ "("
|
||||
+ "(?:" + STRICT_DOMAIN_NAME + ")"
|
||||
+ "(?:" + PORT_NUMBER + ")?"
|
||||
+ ")"
|
||||
+ "(?:" + PATH_AND_QUERY + ")?"
|
||||
+ WORD_BOUNDARY
|
||||
+ ")";
|
||||
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// CHANGED: Field visibility was modified //
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
/**
|
||||
* Regular expression to match strings that start with a supported protocol. Rules for domain
|
||||
* names and TLDs are more relaxed. TLDs are optional.
|
||||
*/
|
||||
/*package*/ static final String WEB_URL_WITH_PROTOCOL = "("
|
||||
+ WORD_BOUNDARY
|
||||
+ "(?:"
|
||||
+ "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")"
|
||||
+ "(?:" + RELAXED_DOMAIN_NAME + ")?"
|
||||
+ "(?:" + PORT_NUMBER + ")?"
|
||||
+ ")"
|
||||
+ "(?:" + PATH_AND_QUERY + ")?"
|
||||
+ WORD_BOUNDARY
|
||||
+ ")";
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match IRIs. If a string starts with http(s):// the expression
|
||||
* tries to match the URL structure with a relaxed rule for TLDs. If the string does not start
|
||||
* with http(s):// the TLDs are expected to be one of the known TLDs.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
public static final Pattern AUTOLINK_WEB_URL = Pattern.compile(
|
||||
"(" + WEB_URL_WITH_PROTOCOL + "|" + WEB_URL_WITHOUT_PROTOCOL + ")");
|
||||
|
||||
/**
|
||||
* Regular expression for valid email characters. Does not include some of the valid characters
|
||||
* defined in RFC5321: #&~!^`{}/=$*?|
|
||||
*/
|
||||
private static final String EMAIL_CHAR = LABEL_CHAR + "\\+\\-_%'";
|
||||
|
||||
/**
|
||||
* Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits
|
||||
* the local part to be at most 64 octets.
|
||||
*/
|
||||
private static final String EMAIL_ADDRESS_LOCAL_PART =
|
||||
"[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{0,62}[" + EMAIL_CHAR + "])?";
|
||||
|
||||
/**
|
||||
* Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits
|
||||
* the domain to be at most 255 octets.
|
||||
*/
|
||||
private static final String EMAIL_ADDRESS_DOMAIN =
|
||||
"(?=.{1,255}(?:\\s|$|^))" + HOST_NAME;
|
||||
|
||||
/**
|
||||
* Regular expression pattern to match email addresses. It excludes double quoted local parts
|
||||
* and the special characters #&~!^`{}/=$*?| that are included in RFC5321.
|
||||
* @hide
|
||||
*/
|
||||
@RestrictTo(LIBRARY_GROUP_PREFIX)
|
||||
public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY +
|
||||
"(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
|
||||
WORD_BOUNDARY + ")"
|
||||
);
|
||||
|
||||
public static final Pattern EMAIL_ADDRESS
|
||||
= Pattern.compile(
|
||||
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
|
||||
"\\@" +
|
||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
|
||||
"(" +
|
||||
"\\." +
|
||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
|
||||
")+"
|
||||
);
|
||||
|
||||
/**
|
||||
* Do not create this static utility class.
|
||||
*/
|
||||
private PatternsCompat() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package org.schabi.newpipe.util.urlfinder
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class UrlFinder {
|
||||
companion object {
|
||||
private val WEB_URL_WITH_PROTOCOL = Pattern.compile(PatternsCompat.WEB_URL_WITH_PROTOCOL)
|
||||
|
||||
/**
|
||||
* @return the first url found in the input, null otherwise.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun firstUrlFromInput(input: String?): String? {
|
||||
if (input.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val matcher = WEB_URL_WITH_PROTOCOL.matcher(input)
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ public class DownloadRunnable extends Thread {
|
|||
Log.d(TAG, mId + ":acquired block at position=" + block.position + " done=" + block.done);
|
||||
}
|
||||
|
||||
long start = block.position * DownloadMission.BLOCK_SIZE;
|
||||
long start = (long)block.position * DownloadMission.BLOCK_SIZE;
|
||||
long end = start + DownloadMission.BLOCK_SIZE - 1;
|
||||
|
||||
start += block.done;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue