mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-11-14 22:17:11 +00:00
Merge branch 'dev' into LongTapInSubs
This commit is contained in:
@@ -61,6 +61,8 @@ import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.extractor.InfoItem.InfoType.PLAYLIST;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||
@@ -392,31 +394,45 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
try {
|
||||
if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]");
|
||||
|
||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||
case STREAM:
|
||||
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
|
||||
break;
|
||||
case CHANNEL:
|
||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(), serviceId, url, title);
|
||||
break;
|
||||
case PLAYLIST:
|
||||
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(), serviceId, url, title);
|
||||
break;
|
||||
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
|
||||
String url = intent.getStringExtra(Constants.KEY_URL);
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
String title = intent.getStringExtra(Constants.KEY_TITLE);
|
||||
switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
|
||||
case STREAM:
|
||||
boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
|
||||
NavigationHelper.openVideoDetailFragment(getSupportFragmentManager(), serviceId, url, title, autoPlay);
|
||||
break;
|
||||
case CHANNEL:
|
||||
NavigationHelper.openChannelFragment(getSupportFragmentManager(),
|
||||
serviceId,
|
||||
url,
|
||||
title);
|
||||
break;
|
||||
case PLAYLIST:
|
||||
NavigationHelper.openPlaylistFragment(getSupportFragmentManager(),
|
||||
serviceId,
|
||||
url,
|
||||
title);
|
||||
break;
|
||||
}
|
||||
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
|
||||
String searchString = intent.getStringExtra(Constants.KEY_SEARCH_STRING);
|
||||
if (searchString == null) searchString = "";
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
NavigationHelper.openSearchFragment(
|
||||
getSupportFragmentManager(),
|
||||
serviceId,
|
||||
searchString);
|
||||
|
||||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
}
|
||||
} else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
|
||||
String searchQuery = intent.getStringExtra(Constants.KEY_QUERY);
|
||||
if (searchQuery == null) searchQuery = "";
|
||||
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
|
||||
NavigationHelper.openSearchFragment(getSupportFragmentManager(), serviceId, searchQuery);
|
||||
} else {
|
||||
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -127,7 +128,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_search:
|
||||
NavigationHelper.openSearchFragment(getFragmentManager(), ServiceHelper.getSelectedServiceId(activity), "");
|
||||
try {
|
||||
NavigationHelper.openSearchFragment(
|
||||
getFragmentManager(),
|
||||
ServiceHelper.getSelectedServiceId(activity),
|
||||
"");
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -226,7 +234,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
FALLBACK_CHANNEL_URL);
|
||||
String name = preferences.getString(getString(R.string.main_page_selected_channel_name),
|
||||
FALLBACK_CHANNEL_NAME);
|
||||
ChannelFragment fragment = ChannelFragment.getInstance(serviceId, url, name);
|
||||
ChannelFragment fragment = ChannelFragment.getInstance(serviceId,
|
||||
url,
|
||||
name);
|
||||
fragment.useAsFrontPage(true);
|
||||
return fragment;
|
||||
} else {
|
||||
@@ -255,20 +265,13 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
for (final String ks : kl.getAvailableKiosks()) {
|
||||
menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE,
|
||||
KioskTranslator.getTranslatedKioskName(ks, getContext()))
|
||||
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
.setOnMenuItemClickListener(menuItem -> {
|
||||
try {
|
||||
NavigationHelper.openKioskFragment(getFragmentManager(), currentServiceId, ks);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(activity, e,
|
||||
activity.getClass(),
|
||||
null,
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"none", "", R.string.app_ui_crash));
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@@ -54,7 +55,7 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
@@ -64,6 +65,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
@@ -365,11 +367,15 @@ public class VideoDetailFragment
|
||||
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||
Log.w(TAG, "Can't open channel because we got no channel URL");
|
||||
} else {
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.detail_thumbnail_root_layout:
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
@@ -24,6 +25,7 @@ import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
@@ -152,18 +154,35 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
|
||||
@Override
|
||||
public void selected(ChannelInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openChannelFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
try {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openChannelFragment(useAsFrontPage ?
|
||||
getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<PlaylistInfoItem>() {
|
||||
@Override
|
||||
public void selected(PlaylistInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
try {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
useAsFrontPage
|
||||
? getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -178,7 +197,9 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem
|
||||
|
||||
private void onStreamSelected(StreamInfoItem selectedItem) {
|
||||
onItemSelected(selectedItem);
|
||||
NavigationHelper.openVideoDetailFragment(useAsFrontPage ? getParentFragment().getFragmentManager() : getFragmentManager(),
|
||||
NavigationHelper.openVideoDetailFragment(useAsFrontPage
|
||||
? getParentFragment().getFragmentManager()
|
||||
: getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
|
||||
import java.util.Queue;
|
||||
@@ -166,7 +169,6 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void handleResult(@NonNull I result) {
|
||||
super.handleResult(result);
|
||||
|
||||
url = result.getUrl();
|
||||
name = result.getName();
|
||||
setTitle(name);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||
@@ -501,7 +502,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.REQUESTED_CHANNEL,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
url,
|
||||
errorId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,20 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import icepick.State;
|
||||
import io.reactivex.Single;
|
||||
@@ -74,10 +72,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
throws ExtractionException {
|
||||
KioskFragment instance = new KioskFragment();
|
||||
StreamingService service = NewPipe.getService(serviceId);
|
||||
UrlIdHandler kioskTypeUrlIdHandler = service.getKioskList()
|
||||
.getUrlIdHandlerByType(kioskId);
|
||||
ListLinkHandlerFactory kioskLinkHandlerFactory = service.getKioskList()
|
||||
.getListLinkHandlerFactoryByType(kioskId);
|
||||
instance.setInitialData(serviceId,
|
||||
kioskTypeUrlIdHandler.getUrl(kioskId), kioskId);
|
||||
kioskLinkHandlerFactory.fromId(kioskId).getUrl(), kioskId);
|
||||
instance.kioskId = kioskId;
|
||||
return instance;
|
||||
}
|
||||
@@ -136,7 +134,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.content_country_key),
|
||||
getString(R.string.default_country_value));
|
||||
return ExtractorHelper.getKioskInfo(serviceId, url, contentCountry, forceReload);
|
||||
return ExtractorHelper.getKioskInfo(serviceId,
|
||||
url,
|
||||
contentCountry,
|
||||
forceReload);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,7 +146,10 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
.getDefaultSharedPreferences(activity)
|
||||
.getString(getString(R.string.content_country_key),
|
||||
getString(R.string.default_country_value));
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl, contentCountry);
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId,
|
||||
url,
|
||||
currentNextPageUrl,
|
||||
contentCountry);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -19,6 +20,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
@@ -28,12 +30,14 @@ import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
@@ -267,11 +271,16 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
if (!TextUtils.isEmpty(result.getUploaderName())) {
|
||||
headerUploaderName.setText(result.getUploaderName());
|
||||
if (!TextUtils.isEmpty(result.getUploaderUrl())) {
|
||||
headerUploaderLayout.setOnClickListener(v ->
|
||||
headerUploaderLayout.setOnClickListener(v -> {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||
result.getServiceId(), result.getUploaderUrl(),
|
||||
result.getUploaderName())
|
||||
);
|
||||
result.getServiceId(),
|
||||
result.getUploaderUrl(),
|
||||
result.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +348,11 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId), url, errorId);
|
||||
onUnrecoverableError(exception,
|
||||
UserAction.REQUESTED_PLAYLIST,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
url,
|
||||
errorId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,26 +37,30 @@ import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.search.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.search.SearchResult;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.LayoutManagerSmoothScroller;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import icepick.State;
|
||||
@@ -65,14 +69,15 @@ import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class SearchFragment
|
||||
extends BaseListFragment<SearchResult, ListExtractor.InfoItemsPage>
|
||||
extends BaseListFragment<SearchInfo, ListExtractor.InfoItemsPage>
|
||||
implements BackPressable {
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -92,19 +97,29 @@ public class SearchFragment
|
||||
|
||||
@State
|
||||
protected int filterItemCheckedId = -1;
|
||||
private SearchEngine.Filter filter = SearchEngine.Filter.ANY;
|
||||
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
|
||||
// this three represet the current search query
|
||||
@State
|
||||
protected String searchQuery;
|
||||
protected String searchString;
|
||||
@State
|
||||
protected String lastSearchedQuery;
|
||||
protected String[] contentFilter;
|
||||
@State
|
||||
protected String sortFilter;
|
||||
|
||||
// these represtent the last search
|
||||
@State
|
||||
protected String lastSearchedString;
|
||||
|
||||
@State
|
||||
protected boolean wasSearchFocused = false;
|
||||
|
||||
private int currentPage = 0;
|
||||
private int currentNextPage = 0;
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
private StreamingService service;
|
||||
private String currentPageUrl;
|
||||
private String nextPageUrl;
|
||||
private String contentCountry;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
private boolean isSearchHistoryEnabled = true;
|
||||
@@ -130,11 +145,11 @@ public class SearchFragment
|
||||
|
||||
/*////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static SearchFragment getInstance(int serviceId, String query) {
|
||||
public static SearchFragment getInstance(int serviceId, String searchString) {
|
||||
SearchFragment searchFragment = new SearchFragment();
|
||||
searchFragment.setQuery(serviceId, query);
|
||||
searchFragment.setQuery(serviceId, searchString, new String[0], "");
|
||||
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
searchFragment.setSearchOnResume();
|
||||
}
|
||||
|
||||
@@ -202,13 +217,22 @@ public class SearchFragment
|
||||
if (DEBUG) Log.d(TAG, "onResume() called");
|
||||
super.onResume();
|
||||
|
||||
if (!TextUtils.isEmpty(searchQuery)) {
|
||||
try {
|
||||
service = NewPipe.getService(serviceId);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(getActivity(), e, getActivity().getClass(),
|
||||
getActivity().findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
||||
"",
|
||||
"", R.string.general_error));
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
if (wasLoading.getAndSet(false)) {
|
||||
if (currentNextPage > currentPage) loadMoreItems();
|
||||
else search(searchQuery);
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
} else if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (savedState == null) {
|
||||
search(searchQuery);
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
} else if (!isLoading.get() && !wasSearchFocused) {
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
@@ -218,7 +242,7 @@ public class SearchFragment
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
|
||||
|
||||
if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) {
|
||||
if (TextUtils.isEmpty(searchString) || wasSearchFocused) {
|
||||
showKeyboardSearch();
|
||||
showSuggestionsPanel();
|
||||
} else {
|
||||
@@ -247,8 +271,9 @@ public class SearchFragment
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case ReCaptchaActivity.RECAPTCHA_REQUEST:
|
||||
if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(searchQuery)) {
|
||||
search(searchQuery);
|
||||
if (resultCode == Activity.RESULT_OK
|
||||
&& !TextUtils.isEmpty(searchString)) {
|
||||
search(searchString, contentFilter, sortFilter);
|
||||
} else Log.e(TAG, "ReCaptcha failed");
|
||||
break;
|
||||
|
||||
@@ -282,20 +307,22 @@ public class SearchFragment
|
||||
@Override
|
||||
public void writeTo(Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentPage);
|
||||
objectsToSave.add(currentNextPage);
|
||||
objectsToSave.add(currentPageUrl);
|
||||
objectsToSave.add(nextPageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentPage = (int) savedObjects.poll();
|
||||
currentNextPage = (int) savedObjects.poll();
|
||||
currentPageUrl = (String) savedObjects.poll();
|
||||
nextPageUrl = (String) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle bundle) {
|
||||
searchQuery = searchEditText != null ? searchEditText.getText().toString() : searchQuery;
|
||||
searchString = searchEditText != null
|
||||
? searchEditText.getText().toString()
|
||||
: searchString;
|
||||
super.onSaveInstanceState(bundle);
|
||||
}
|
||||
|
||||
@@ -305,8 +332,11 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
public void reloadContent() {
|
||||
if (!TextUtils.isEmpty(searchQuery) || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
|
||||
search(!TextUtils.isEmpty(searchQuery) ? searchQuery : searchEditText.getText().toString());
|
||||
if (!TextUtils.isEmpty(searchString)
|
||||
|| (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
|
||||
search(!TextUtils.isEmpty(searchString)
|
||||
? searchString
|
||||
: searchEditText.getText().toString(), new String[0], "");
|
||||
} else {
|
||||
if (searchEditText != null) {
|
||||
searchEditText.setText("");
|
||||
@@ -330,22 +360,35 @@ public class SearchFragment
|
||||
supportActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
inflater.inflate(R.menu.menu_search, menu);
|
||||
menuItemToFilterName = new HashMap<>();
|
||||
|
||||
int itemId = 0;
|
||||
boolean isFirstItem = true;
|
||||
final Context c = getContext();
|
||||
for(String filter : service.getSearchQIHFactory().getAvailableContentFilter()) {
|
||||
menuItemToFilterName.put(itemId, filter);
|
||||
MenuItem item = menu.add(1,
|
||||
itemId++,
|
||||
0,
|
||||
ServiceHelper.getTranslatedFilterString(filter, c));
|
||||
if(isFirstItem) {
|
||||
item.setChecked(true);
|
||||
isFirstItem = false;
|
||||
}
|
||||
}
|
||||
menu.setGroupCheckable(1, true, true);
|
||||
|
||||
restoreFilterChecked(menu, filterItemCheckedId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_filter_all:
|
||||
case R.id.menu_filter_video:
|
||||
case R.id.menu_filter_channel:
|
||||
case R.id.menu_filter_playlist:
|
||||
changeFilter(item, getFilterFromMenuId(item.getItemId()));
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
List<String> contentFilter = new ArrayList<>(1);
|
||||
contentFilter.add(menuItemToFilterName.get(item.getItemId()));
|
||||
changeContentFilter(item, contentFilter);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void restoreFilterChecked(Menu menu, int itemId) {
|
||||
@@ -354,21 +397,6 @@ public class SearchFragment
|
||||
if (item == null) return;
|
||||
|
||||
item.setChecked(true);
|
||||
filter = getFilterFromMenuId(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
private SearchEngine.Filter getFilterFromMenuId(int itemId) {
|
||||
switch (itemId) {
|
||||
case R.id.menu_filter_video:
|
||||
return SearchEngine.Filter.STREAM;
|
||||
case R.id.menu_filter_channel:
|
||||
return SearchEngine.Filter.CHANNEL;
|
||||
case R.id.menu_filter_playlist:
|
||||
return SearchEngine.Filter.PLAYLIST;
|
||||
case R.id.menu_filter_all:
|
||||
default:
|
||||
return SearchEngine.Filter.ANY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,14 +407,21 @@ public class SearchFragment
|
||||
private TextWatcher textWatcher;
|
||||
|
||||
private void showSearchOnStart() {
|
||||
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → " + searchQuery+", lastSearchedQuery → " + lastSearchedQuery);
|
||||
searchEditText.setText(searchQuery);
|
||||
if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → "
|
||||
+ searchString
|
||||
+ ", lastSearchedQuery → "
|
||||
+ lastSearchedString);
|
||||
searchEditText.setText(searchString);
|
||||
|
||||
if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
if (TextUtils.isEmpty(searchString) || TextUtils.isEmpty(searchEditText.getText())) {
|
||||
searchToolbarContainer.setTranslationX(100);
|
||||
searchToolbarContainer.setAlpha(0f);
|
||||
searchToolbarContainer.setVisibility(View.VISIBLE);
|
||||
searchToolbarContainer.animate().translationX(0).alpha(1f).setDuration(200).setInterpolator(new DecelerateInterpolator()).start();
|
||||
searchToolbarContainer.animate()
|
||||
.translationX(0)
|
||||
.alpha(1f)
|
||||
.setDuration(200)
|
||||
.setInterpolator(new DecelerateInterpolator()).start();
|
||||
} else {
|
||||
searchToolbarContainer.setTranslationX(0);
|
||||
searchToolbarContainer.setAlpha(1f);
|
||||
@@ -396,47 +431,38 @@ public class SearchFragment
|
||||
|
||||
private void initSearchListeners() {
|
||||
if (DEBUG) Log.d(TAG, "initSearchListeners() called");
|
||||
searchClear.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
||||
return;
|
||||
}
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<SuggestionItem>());
|
||||
showKeyboardSearch();
|
||||
searchClear.setOnClickListener(v -> {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
if (TextUtils.isEmpty(searchEditText.getText())) {
|
||||
NavigationHelper.gotoMainFragment(getFragmentManager());
|
||||
return;
|
||||
}
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<>());
|
||||
showKeyboardSearch();
|
||||
});
|
||||
|
||||
TooltipCompat.setTooltipText(searchClear, getString(R.string.clear));
|
||||
|
||||
searchEditText.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
searchEditText.setOnClickListener(v -> {
|
||||
if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
|
||||
if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
searchEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
|
||||
if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]");
|
||||
if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) {
|
||||
showSuggestionsPanel();
|
||||
}
|
||||
});
|
||||
|
||||
suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() {
|
||||
@Override
|
||||
public void onSuggestionItemSelected(SuggestionItem item) {
|
||||
search(item.query);
|
||||
search(item.query, new String[0], "");
|
||||
searchEditText.setText(item.query);
|
||||
}
|
||||
|
||||
@@ -469,21 +495,22 @@ public class SearchFragment
|
||||
}
|
||||
};
|
||||
searchEditText.addTextChangedListener(textWatcher);
|
||||
searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
search(searchEditText.getText().toString());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
searchEditText.setOnEditorActionListener(
|
||||
(TextView v, int actionId, KeyEvent event) -> {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
|
||||
}
|
||||
if (event != null
|
||||
&& (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
|
||||
|| event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
|
||||
search(searchEditText.getText().toString(), new String[0], "");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver();
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed())
|
||||
initSuggestionObserver();
|
||||
}
|
||||
|
||||
private void unsetSearchListeners() {
|
||||
@@ -513,7 +540,8 @@ public class SearchFragment
|
||||
if (searchEditText == null) return;
|
||||
|
||||
if (searchEditText.requestFocus()) {
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
@@ -522,8 +550,10 @@ public class SearchFragment
|
||||
if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called");
|
||||
if (searchEditText == null) return;
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
InputMethodManager imm = (InputMethodManager) activity.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
searchEditText.clearFocus();
|
||||
}
|
||||
@@ -554,10 +584,12 @@ public class SearchFragment
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (suggestionsPanel.getVisibility() == View.VISIBLE && infoListAdapter.getItemsList().size() > 0 && !isLoading.get()) {
|
||||
if (suggestionsPanel.getVisibility() == View.VISIBLE
|
||||
&& infoListAdapter.getItemsList().size() > 0
|
||||
&& !isLoading.get()) {
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
searchEditText.setText(lastSearchedQuery);
|
||||
searchEditText.setText(lastSearchedString);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -573,8 +605,10 @@ public class SearchFragment
|
||||
|
||||
final Observable<String> observable = suggestionPublisher
|
||||
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
|
||||
.startWith(searchQuery != null ? searchQuery : "")
|
||||
.filter(query -> isSuggestionsEnabled);
|
||||
.startWith(searchString != null
|
||||
? searchString
|
||||
: "")
|
||||
.filter(searchString -> isSuggestionsEnabled);
|
||||
|
||||
suggestionDisposable = observable
|
||||
.switchMap(query -> {
|
||||
@@ -645,56 +679,44 @@ public class SearchFragment
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void search(final String query) {
|
||||
if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "]");
|
||||
if (query.isEmpty()) return;
|
||||
private void search(final String searchString, String[] contentFilter, String sortFilter) {
|
||||
if (DEBUG) Log.d(TAG, "search() called with: query = [" + searchString + "]");
|
||||
if (searchString.isEmpty()) return;
|
||||
|
||||
try {
|
||||
final StreamingService service = NewPipe.getServiceByUrl(query);
|
||||
final StreamingService service = NewPipe.getServiceByUrl(searchString);
|
||||
if (service != null) {
|
||||
showLoading();
|
||||
disposables.add(Observable
|
||||
.fromCallable(new Callable<Intent>() {
|
||||
@Override
|
||||
public Intent call() throws Exception {
|
||||
return NavigationHelper.getIntentByLink(activity, service, query);
|
||||
}
|
||||
})
|
||||
.fromCallable(() ->
|
||||
NavigationHelper.getIntentByLink(activity, service, searchString))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Intent>() {
|
||||
@Override
|
||||
public void accept(Intent intent) throws Exception {
|
||||
getFragmentManager().popBackStackImmediate();
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
showError(getString(R.string.url_not_supported_toast), false);
|
||||
}
|
||||
}));
|
||||
.subscribe(intent -> {
|
||||
getFragmentManager().popBackStackImmediate();
|
||||
activity.startActivity(intent);
|
||||
}, throwable ->
|
||||
showError(getString(R.string.url_not_supported_toast), false)));
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Exception occurred, it's not a url
|
||||
}
|
||||
|
||||
lastSearchedQuery = query;
|
||||
searchQuery = query;
|
||||
currentPage = 0;
|
||||
lastSearchedString = this.searchString;
|
||||
this.searchString = searchString;
|
||||
infoListAdapter.clearStreamItemList();
|
||||
hideSuggestionsPanel();
|
||||
hideKeyboardSearch();
|
||||
|
||||
historyRecordManager.onSearched(serviceId, query)
|
||||
historyRecordManager.onSearched(serviceId, searchString)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
ignored -> {},
|
||||
error -> showSnackBarError(error, UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), query, 0)
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0)
|
||||
);
|
||||
suggestionPublisher.onNext(query);
|
||||
suggestionPublisher.onNext(searchString);
|
||||
startLoading(false);
|
||||
}
|
||||
|
||||
@@ -703,11 +725,16 @@ public class SearchFragment
|
||||
super.startLoading(forceLoad);
|
||||
if (disposables != null) disposables.clear();
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
searchDisposable = ExtractorHelper.searchFor(serviceId, searchQuery, currentPage, contentCountry, filter)
|
||||
searchDisposable = ExtractorHelper.searchFor(serviceId,
|
||||
searchString,
|
||||
Arrays.asList(contentFilter),
|
||||
sortFilter,
|
||||
contentCountry)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((searchResult, throwable) -> isLoading.set(false))
|
||||
.subscribe(this::handleResult, this::onError);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -715,8 +742,13 @@ public class SearchFragment
|
||||
isLoading.set(true);
|
||||
showListFooter(true);
|
||||
if (searchDisposable != null) searchDisposable.dispose();
|
||||
currentNextPage = currentPage + 1;
|
||||
searchDisposable = ExtractorHelper.getMoreSearchItems(serviceId, searchQuery, currentNextPage, contentCountry, filter)
|
||||
searchDisposable = ExtractorHelper.getMoreSearchItems(
|
||||
serviceId,
|
||||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl,
|
||||
contentCountry)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
@@ -739,19 +771,22 @@ public class SearchFragment
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void changeFilter(MenuItem item, SearchEngine.Filter filter) {
|
||||
this.filter = filter;
|
||||
private void changeContentFilter(MenuItem item, List<String> contentFilter) {
|
||||
this.filterItemCheckedId = item.getItemId();
|
||||
item.setChecked(true);
|
||||
|
||||
if (!TextUtils.isEmpty(searchQuery)) {
|
||||
search(searchQuery);
|
||||
this.contentFilter = new String[] {contentFilter.get(0)};
|
||||
|
||||
if (!TextUtils.isEmpty(searchString)) {
|
||||
search(searchString, this.contentFilter, sortFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private void setQuery(int serviceId, String searchQuery) {
|
||||
private void setQuery(int serviceId, String searchString, String[] contentfilter, String sortFilter) {
|
||||
this.serviceId = serviceId;
|
||||
this.searchQuery = searchQuery;
|
||||
this.searchString = searchString;
|
||||
this.contentFilter = contentfilter;
|
||||
this.sortFilter = sortFilter;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -772,8 +807,11 @@ public class SearchFragment
|
||||
if (DEBUG) Log.d(TAG, "onSuggestionError() called with: exception = [" + exception + "]");
|
||||
if (super.onError(exception)) return;
|
||||
|
||||
int errorId = exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS, NewPipe.getNameOfService(serviceId), searchQuery, errorId);
|
||||
int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.GET_SUGGESTIONS,
|
||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -798,16 +836,19 @@ public class SearchFragment
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void handleResult(@NonNull SearchResult result) {
|
||||
if (!result.errors.isEmpty()) {
|
||||
showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, 0);
|
||||
public void handleResult(@NonNull SearchInfo result) {
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
|
||||
lastSearchedQuery = searchQuery;
|
||||
lastSearchedString = searchString;
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
currentPageUrl = result.getUrl();
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (!result.getResults().isEmpty()) {
|
||||
infoListAdapter.addInfoItemList(result.getResults());
|
||||
if (!result.getRelatedItems().isEmpty()) {
|
||||
infoListAdapter.addInfoItemList(result.getRelatedItems());
|
||||
} else {
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
@@ -821,12 +862,13 @@ public class SearchFragment
|
||||
@Override
|
||||
public void handleNextItems(ListExtractor.InfoItemsPage result) {
|
||||
showListFooter(false);
|
||||
currentPage = Integer.parseInt(result.getNextPageUrl());
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId)
|
||||
, "\"" + searchQuery + "\" → page " + currentPage, 0);
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId)
|
||||
, "\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
}
|
||||
@@ -835,12 +877,15 @@ public class SearchFragment
|
||||
protected boolean onError(Throwable exception) {
|
||||
if (super.onError(exception)) return true;
|
||||
|
||||
if (exception instanceof SearchEngine.NothingFoundException) {
|
||||
if (exception instanceof SearchExtractor.NothingFoundException) {
|
||||
infoListAdapter.clearStreamItemList();
|
||||
showEmptyState();
|
||||
} else {
|
||||
int errorId = exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, errorId);
|
||||
int errorId = exception instanceof ParsingException
|
||||
? R.string.parsing_error
|
||||
: R.string.general_error;
|
||||
onUnrecoverableError(exception, UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId), searchString, errorId);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -19,9 +20,11 @@ import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.local.BaseLocalListFragment;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
@@ -99,19 +102,26 @@ public final class BookmarkFragment
|
||||
itemListAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||
@Override
|
||||
public void selected(LocalItem selectedItem) {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
if (getParentFragment() == null) return;
|
||||
final FragmentManager fragmentManager = getParentFragment().getFragmentManager();
|
||||
try {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
if (getParentFragment() == null) return;
|
||||
final FragmentManager fragmentManager = getParentFragment().getFragmentManager();
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid,
|
||||
entry.name);
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid,
|
||||
entry.name);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(fragmentManager, entry.getServiceId(),
|
||||
entry.getUrl(), entry.getName());
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
fragmentManager,
|
||||
entry.getServiceId(),
|
||||
entry.getUrl(),
|
||||
entry.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
@@ -43,6 +44,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.fragments.BaseStateFragment;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService;
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
@@ -325,9 +327,15 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt
|
||||
infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() {
|
||||
|
||||
public void selected(ChannelInfoItem selectedItem) {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(),
|
||||
selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName());
|
||||
try {
|
||||
// Requires the parent fragment to find holder for fragment replacement
|
||||
NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(),
|
||||
selectedItem.getServiceId(),
|
||||
selectedItem.getUrl(),
|
||||
selectedItem.getName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void held(ChannelInfoItem selectedItem) {
|
||||
|
||||
@@ -26,9 +26,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
@@ -39,17 +37,16 @@ import android.widget.RemoteViews;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
@@ -94,7 +91,6 @@ public final class BackgroundPlayer extends Service {
|
||||
private NotificationCompat.Builder notBuilder;
|
||||
private RemoteViews notRemoteView;
|
||||
private RemoteViews bigNotRemoteView;
|
||||
private final String setAlphaMethodName = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) ? "setImageAlpha" : "setAlpha";
|
||||
|
||||
private boolean shouldUpdateOnProgress;
|
||||
|
||||
@@ -192,7 +188,9 @@ public final class BackgroundPlayer extends Service {
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCustomContentView(notRemoteView)
|
||||
.setCustomBigContentView(bigNotRemoteView);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) builder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -249,15 +247,6 @@ public final class BackgroundPlayer extends Service {
|
||||
notificationManager.notify(NOTIFICATION_ID, notBuilder.build());
|
||||
}
|
||||
|
||||
private void setControlsOpacity(@IntRange(from = 0, to = 255) int opacity) {
|
||||
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationPlayPause, setAlphaMethodName, opacity);
|
||||
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFForward, setAlphaMethodName, opacity);
|
||||
if (notRemoteView != null) notRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setInt(R.id.notificationFRewind, setAlphaMethodName, opacity);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -279,8 +268,16 @@ public final class BackgroundPlayer extends Service {
|
||||
|
||||
protected class BasePlayerImpl extends BasePlayer {
|
||||
|
||||
@NonNull final private AudioPlaybackResolver resolver;
|
||||
|
||||
BasePlayerImpl(Context context) {
|
||||
super(context);
|
||||
this.resolver = new AudioPlaybackResolver(context, dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPlayer(boolean playOnReady) {
|
||||
super.initPlayer(playOnReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -293,30 +290,41 @@ public final class BackgroundPlayer extends Service {
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initThumbnail(final String url) {
|
||||
resetNotification();
|
||||
if (notRemoteView != null) notRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewResource(R.id.notificationCover, R.drawable.dummy_thumbnail);
|
||||
updateNotification(-1);
|
||||
super.initThumbnail(url);
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Thumbnail Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void updateNotificationThumbnail() {
|
||||
if (basePlayerImpl == null) return;
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover,
|
||||
basePlayerImpl.getThumbnail());
|
||||
}
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setImageViewBitmap(R.id.notificationCover,
|
||||
basePlayerImpl.getThumbnail());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
|
||||
if (loadedImage != null) {
|
||||
// rebuild notification here since remote view does not release bitmaps, causing memory leaks
|
||||
resetNotification();
|
||||
|
||||
if (notRemoteView != null) notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
|
||||
|
||||
updateNotification(-1);
|
||||
}
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
super.onLoadingFailed(imageUri, view, failReason);
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(-1);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// States Implementation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onPrepared(boolean playWhenReady) {
|
||||
super.onPrepared(playWhenReady);
|
||||
@@ -390,29 +398,18 @@ public final class BackgroundPlayer extends Service {
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
if (shouldUpdateOnProgress || hasPlayQueueItemChanged) {
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
updateMetadata();
|
||||
}
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(-1);
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final MediaSource liveSource = super.sourceOf(item, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||
if (index < 0 || index >= info.getAudioStreams().size()) return null;
|
||||
|
||||
final AudioStream audio = info.getAudioStreams().get(index);
|
||||
return buildMediaSource(audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
|
||||
MediaFormat.getSuffixById(audio.getFormatId()));
|
||||
return resolver.resolve(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -439,8 +436,8 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
private void updateMetadata() {
|
||||
if (activityListener != null && currentInfo != null) {
|
||||
activityListener.onMetadataUpdate(currentInfo);
|
||||
if (activityListener != null && getCurrentMetadata() != null) {
|
||||
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,44 +528,36 @@ public final class BackgroundPlayer extends Service {
|
||||
updatePlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
|
||||
setControlsOpacity(77);
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaying() {
|
||||
super.onPlaying();
|
||||
|
||||
setControlsOpacity(255);
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_pause_white);
|
||||
|
||||
lockManager.acquireWifiAndCpu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused() {
|
||||
super.onPaused();
|
||||
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
super.onCompleted();
|
||||
|
||||
setControlsOpacity(255);
|
||||
|
||||
resetNotification();
|
||||
if (bigNotRemoteView != null) bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||
if (notRemoteView != null) notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||
if (bigNotRemoteView != null) {
|
||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||
}
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||
}
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_replay_white);
|
||||
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,14 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
@@ -49,7 +47,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
@@ -57,7 +54,6 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||
import org.schabi.newpipe.player.helper.LoadController;
|
||||
@@ -72,6 +68,8 @@ import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -82,12 +80,12 @@ import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.SerialDisposable;
|
||||
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
||||
|
||||
/**
|
||||
* Base for the players, joining the common properties
|
||||
@@ -108,17 +106,26 @@ public abstract class BasePlayer implements
|
||||
|
||||
@NonNull final protected HistoryRecordManager recordManager;
|
||||
|
||||
@NonNull final protected CustomTrackSelector trackSelector;
|
||||
@NonNull final protected PlayerDataSource dataSource;
|
||||
|
||||
@NonNull final private LoadControl loadControl;
|
||||
@NonNull final private RenderersFactory renderFactory;
|
||||
|
||||
@NonNull final private SerialDisposable progressUpdateReactor;
|
||||
@NonNull final private CompositeDisposable databaseUpdateReactor;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Intent
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final String REPEAT_MODE = "repeat_mode";
|
||||
public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
public static final String APPEND_ONLY = "append_only";
|
||||
public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
@NonNull public static final String REPEAT_MODE = "repeat_mode";
|
||||
@NonNull public static final String PLAYBACK_PITCH = "playback_pitch";
|
||||
@NonNull public static final String PLAYBACK_SPEED = "playback_speed";
|
||||
@NonNull public static final String PLAYBACK_SKIP_SILENCE = "playback_skip_silence";
|
||||
@NonNull public static final String PLAYBACK_QUALITY = "playback_quality";
|
||||
@NonNull public static final String PLAY_QUEUE_KEY = "play_queue_key";
|
||||
@NonNull public static final String APPEND_ONLY = "append_only";
|
||||
@NonNull public static final String SELECT_ON_APPEND = "select_on_append";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
@@ -129,12 +136,13 @@ public abstract class BasePlayer implements
|
||||
protected PlayQueue playQueue;
|
||||
protected PlayQueueAdapter playQueueAdapter;
|
||||
|
||||
protected MediaSourceManager playbackManager;
|
||||
@Nullable protected MediaSourceManager playbackManager;
|
||||
|
||||
protected StreamInfo currentInfo;
|
||||
protected PlayQueueItem currentItem;
|
||||
@Nullable private PlayQueueItem currentItem;
|
||||
@Nullable private MediaSourceTag currentMetadata;
|
||||
@Nullable private Bitmap currentThumbnail;
|
||||
|
||||
protected Toast errorToast;
|
||||
@Nullable protected Toast errorToast;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
@@ -145,18 +153,11 @@ public abstract class BasePlayer implements
|
||||
protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500;
|
||||
protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds
|
||||
|
||||
protected CustomTrackSelector trackSelector;
|
||||
protected PlayerDataSource dataSource;
|
||||
|
||||
protected SimpleExoPlayer simpleExoPlayer;
|
||||
protected AudioReactor audioReactor;
|
||||
protected MediaSessionManager mediaSessionManager;
|
||||
|
||||
private boolean isPrepared = false;
|
||||
private boolean isSynchronizing = false;
|
||||
|
||||
protected Disposable progressUpdateReactor;
|
||||
protected CompositeDisposable databaseUpdateReactor;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@@ -174,29 +175,32 @@ public abstract class BasePlayer implements
|
||||
context.registerReceiver(broadcastReceiver, intentFilter);
|
||||
|
||||
this.recordManager = new HistoryRecordManager(context);
|
||||
|
||||
this.progressUpdateReactor = new SerialDisposable();
|
||||
this.databaseUpdateReactor = new CompositeDisposable();
|
||||
|
||||
final String userAgent = Downloader.USER_AGENT;
|
||||
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
this.dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
|
||||
|
||||
final TrackSelection.Factory trackSelectionFactory =
|
||||
PlayerHelper.getQualitySelector(context, bandwidthMeter);
|
||||
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
|
||||
this.loadControl = new LoadController(context);
|
||||
this.renderFactory = new DefaultRenderersFactory(context);
|
||||
}
|
||||
|
||||
public void setup() {
|
||||
if (simpleExoPlayer == null) initPlayer(/*playOnInit=*/true);
|
||||
if (simpleExoPlayer == null) {
|
||||
initPlayer(/*playOnInit=*/true);
|
||||
}
|
||||
initListeners();
|
||||
}
|
||||
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
|
||||
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||
databaseUpdateReactor = new CompositeDisposable();
|
||||
|
||||
final String userAgent = Downloader.USER_AGENT;
|
||||
final DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
dataSource = new PlayerDataSource(context, userAgent, bandwidthMeter);
|
||||
|
||||
final TrackSelection.Factory trackSelectionFactory =
|
||||
PlayerHelper.getQualitySelector(context, bandwidthMeter);
|
||||
trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
|
||||
final LoadControl loadControl = new LoadController(context);
|
||||
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
|
||||
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
||||
@@ -235,20 +239,24 @@ public abstract class BasePlayer implements
|
||||
final int repeatMode = intent.getIntExtra(REPEAT_MODE, getRepeatMode());
|
||||
final float playbackSpeed = intent.getFloatExtra(PLAYBACK_SPEED, getPlaybackSpeed());
|
||||
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
|
||||
final boolean playbackSkipSilence = intent.getBooleanExtra(PLAYBACK_SKIP_SILENCE,
|
||||
getPlaybackSkipSilence());
|
||||
|
||||
// Good to go...
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, /*playOnInit=*/true);
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence,
|
||||
/*playOnInit=*/true);
|
||||
}
|
||||
|
||||
protected void initPlayback(@NonNull final PlayQueue queue,
|
||||
@Player.RepeatMode final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final float playbackPitch,
|
||||
final boolean playbackSkipSilence,
|
||||
final boolean playOnReady) {
|
||||
destroyPlayer();
|
||||
initPlayer(playOnReady);
|
||||
setRepeatMode(repeatMode);
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch);
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence);
|
||||
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
@@ -270,7 +278,6 @@ public abstract class BasePlayer implements
|
||||
if (playQueue != null) playQueue.dispose();
|
||||
if (audioReactor != null) audioReactor.dispose();
|
||||
if (playbackManager != null) playbackManager.dispose();
|
||||
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||
if (mediaSessionManager != null) mediaSessionManager.dispose();
|
||||
|
||||
if (playQueueAdapter != null) {
|
||||
@@ -284,20 +291,22 @@ public abstract class BasePlayer implements
|
||||
destroyPlayer();
|
||||
unregisterBroadcastReceiver();
|
||||
|
||||
trackSelector = null;
|
||||
databaseUpdateReactor.clear();
|
||||
progressUpdateReactor.set(null);
|
||||
|
||||
simpleExoPlayer = null;
|
||||
mediaSessionManager = null;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Thumbnail Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void initThumbnail(final String url) {
|
||||
private void initThumbnail(final String url) {
|
||||
if (DEBUG) Log.d(TAG, "Thumbnail - initThumbnail() called");
|
||||
if (url == null || url.isEmpty()) return;
|
||||
ImageLoader.getInstance().resume();
|
||||
ImageLoader.getInstance().loadImage(url, this);
|
||||
ImageLoader.getInstance().loadImage(url, ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS,
|
||||
this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -310,6 +319,7 @@ public abstract class BasePlayer implements
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
Log.e(TAG, "Thumbnail - onLoadingFailed() called on imageUri = [" + imageUri + "]",
|
||||
failReason.getCause());
|
||||
currentThumbnail = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -317,64 +327,14 @@ public abstract class BasePlayer implements
|
||||
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingComplete() called with: " +
|
||||
"imageUri = [" + imageUri + "], view = [" + view + "], " +
|
||||
"loadedImage = [" + loadedImage + "]");
|
||||
currentThumbnail = loadedImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String imageUri, View view) {
|
||||
if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " +
|
||||
"imageUri = [" + imageUri + "], view = [" + view + "]");
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// MediaSource Building
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public MediaSource buildLiveMediaSource(@NonNull final String sourceUrl,
|
||||
@C.ContentType final int type) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "buildLiveMediaSource() called with: url = [" + sourceUrl +
|
||||
"], content type = [" + type + "]");
|
||||
}
|
||||
if (dataSource == null) return null;
|
||||
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getLiveDashMediaSourceFactory().createMediaSource(uri);
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getLiveHlsMediaSourceFactory().createMediaSource(uri);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public MediaSource buildMediaSource(@NonNull final String sourceUrl,
|
||||
@NonNull final String cacheKey,
|
||||
@NonNull final String overrideExtension) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "buildMediaSource() called with: url = [" + sourceUrl +
|
||||
"], cacheKey = [" + cacheKey + "]" +
|
||||
"], overrideExtension = [" + overrideExtension + "]");
|
||||
}
|
||||
if (dataSource == null) return null;
|
||||
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
|
||||
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().createMediaSource(uri);
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getDashMediaSourceFactory().createMediaSource(uri);
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getHlsMediaSourceFactory().createMediaSource(uri);
|
||||
case C.TYPE_OTHER:
|
||||
return dataSource.getExtractorMediaSourceFactory(cacheKey).createMediaSource(uri);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
currentThumbnail = null;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -510,13 +470,11 @@ public abstract class BasePlayer implements
|
||||
public abstract void onUpdateProgress(int currentProgress, int duration, int bufferPercent);
|
||||
|
||||
protected void startProgressLoop() {
|
||||
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
|
||||
progressUpdateReactor = getProgressReactor();
|
||||
progressUpdateReactor.set(getProgressReactor());
|
||||
}
|
||||
|
||||
protected void stopProgressLoop() {
|
||||
if (progressUpdateReactor != null) progressUpdateReactor.dispose();
|
||||
progressUpdateReactor = null;
|
||||
progressUpdateReactor.set(null);
|
||||
}
|
||||
|
||||
public void triggerProgressUpdate() {
|
||||
@@ -531,7 +489,8 @@ public abstract class BasePlayer implements
|
||||
private Disposable getProgressReactor() {
|
||||
return Observable.interval(PROGRESS_LOOP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> triggerProgressUpdate());
|
||||
.subscribe(ignored -> triggerProgressUpdate(),
|
||||
error -> Log.e(TAG, "Progress update failure: ", error));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -545,28 +504,16 @@ public abstract class BasePlayer implements
|
||||
(manifest == null ? "no manifest" : "available manifest") + ", " +
|
||||
"timeline size = [" + timeline.getWindowCount() + "], " +
|
||||
"reason = [" + reason + "]");
|
||||
if (playQueue == null) return;
|
||||
|
||||
switch (reason) {
|
||||
case Player.TIMELINE_CHANGE_REASON_RESET: // called after #block
|
||||
case Player.TIMELINE_CHANGE_REASON_PREPARED: // called after #unblock
|
||||
case Player.TIMELINE_CHANGE_REASON_DYNAMIC: // called after playlist changes
|
||||
// Ensures MediaSourceManager#update is complete
|
||||
final boolean isPlaylistStable = timeline.getWindowCount() == playQueue.size();
|
||||
// Ensure dynamic/livestream timeline changes does not cause negative position
|
||||
if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) {
|
||||
if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " +
|
||||
"clamping to default position.");
|
||||
seekToDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
maybeUpdateCurrentMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
if (DEBUG) Log.d(TAG, "ExoPlayer - onTracksChanged(), " +
|
||||
"track group size = " + trackGroups.length);
|
||||
|
||||
maybeUpdateCurrentMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -586,6 +533,8 @@ public abstract class BasePlayer implements
|
||||
} else if (isLoading && !isProgressLoopRunning()) {
|
||||
startProgressLoop();
|
||||
}
|
||||
|
||||
maybeUpdateCurrentMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -609,6 +558,7 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
break;
|
||||
case Player.STATE_READY: //3
|
||||
maybeUpdateCurrentMetadata();
|
||||
maybeCorrectSeekPosition();
|
||||
if (!isPrepared) {
|
||||
isPrepared = true;
|
||||
@@ -625,38 +575,19 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
private void maybeCorrectSeekPosition() {
|
||||
if (playQueue == null || simpleExoPlayer == null || currentInfo == null) return;
|
||||
if (playQueue == null || simpleExoPlayer == null || currentMetadata == null) return;
|
||||
|
||||
final int currentSourceIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentSourceItem = playQueue.getItem();
|
||||
if (currentSourceItem == null) return;
|
||||
|
||||
final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition();
|
||||
final boolean isCurrentWindowCorrect =
|
||||
simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex;
|
||||
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||
final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000;
|
||||
|
||||
if (recoveryPositionMillis != PlayQueueItem.RECOVERY_UNSET && isCurrentWindowCorrect) {
|
||||
// Is recovering previous playback?
|
||||
if (DEBUG) Log.d(TAG, "Playback - Rewinding to recovery time=" +
|
||||
"[" + getTimeString((int)recoveryPositionMillis) + "]");
|
||||
seekTo(recoveryPositionMillis);
|
||||
playQueue.unsetRecovery(currentSourceIndex);
|
||||
|
||||
} else if (isSynchronizing && isLive()) {
|
||||
if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time");
|
||||
// Is still synchronizing?
|
||||
seekToDefault();
|
||||
|
||||
} else if (isSynchronizing && presetStartPositionMillis > 0L) {
|
||||
if (presetStartPositionMillis > 0L) {
|
||||
// Has another start position?
|
||||
if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " +
|
||||
"position=[" + presetStartPositionMillis + "]");
|
||||
// Has another start position?
|
||||
seekTo(presetStartPositionMillis);
|
||||
currentInfo.setStartPosition(0);
|
||||
}
|
||||
|
||||
isSynchronizing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,7 +639,7 @@ public abstract class BasePlayer implements
|
||||
setRecovery();
|
||||
|
||||
final Throwable cause = error.getCause();
|
||||
if (cause instanceof BehindLiveWindowException) {
|
||||
if (error instanceof BehindLiveWindowException) {
|
||||
reload();
|
||||
} else if (cause instanceof UnknownHostException) {
|
||||
playQueue.error(/*isNetworkProblem=*/true);
|
||||
@@ -727,22 +658,29 @@ public abstract class BasePlayer implements
|
||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason final int reason) {
|
||||
if (DEBUG) Log.d(TAG, "ExoPlayer - onPositionDiscontinuity() called with " +
|
||||
"reason = [" + reason + "]");
|
||||
// Refresh the playback if there is a transition to the next video
|
||||
final int newPeriodIndex = simpleExoPlayer.getCurrentPeriodIndex();
|
||||
if (playQueue == null) return;
|
||||
|
||||
/* Discontinuity reasons!! Thank you ExoPlayer lords */
|
||||
// Refresh the playback if there is a transition to the next video
|
||||
final int newWindowIndex = simpleExoPlayer.getCurrentWindowIndex();
|
||||
switch (reason) {
|
||||
case DISCONTINUITY_REASON_PERIOD_TRANSITION:
|
||||
if (newPeriodIndex == playQueue.getIndex()) {
|
||||
// When player is in single repeat mode and a period transition occurs,
|
||||
// we need to register a view count here since no metadata has changed
|
||||
if (getRepeatMode() == Player.REPEAT_MODE_ONE &&
|
||||
newWindowIndex == playQueue.getIndex()) {
|
||||
registerView();
|
||||
} else {
|
||||
playQueue.offsetIndex(+1);
|
||||
break;
|
||||
}
|
||||
case DISCONTINUITY_REASON_SEEK:
|
||||
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
|
||||
case DISCONTINUITY_REASON_INTERNAL:
|
||||
if (playQueue.getIndex() != newWindowIndex) {
|
||||
playQueue.setIndex(newWindowIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
maybeUpdateCurrentMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -788,7 +726,7 @@ public abstract class BasePlayer implements
|
||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
|
||||
|
||||
currentItem = null;
|
||||
currentInfo = null;
|
||||
currentMetadata = null;
|
||||
simpleExoPlayer.stop();
|
||||
isPrepared = false;
|
||||
|
||||
@@ -805,42 +743,21 @@ public abstract class BasePlayer implements
|
||||
simpleExoPlayer.prepare(mediaSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item) {
|
||||
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
|
||||
(info != null ? "available" : "null") + " info, " +
|
||||
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
|
||||
if (simpleExoPlayer == null || playQueue == null) return;
|
||||
|
||||
final boolean onPlaybackInitial = currentItem == null;
|
||||
final boolean hasPlayQueueItemChanged = currentItem != item;
|
||||
final boolean hasStreamInfoChanged = currentInfo != info;
|
||||
|
||||
final int currentPlayQueueIndex = playQueue.indexOf(item);
|
||||
final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex();
|
||||
final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount();
|
||||
|
||||
// when starting playback on the last item when not repeating, maybe auto queue
|
||||
if (info != null && currentPlayQueueIndex == playQueue.size() - 1 &&
|
||||
getRepeatMode() == Player.REPEAT_MODE_OFF &&
|
||||
PlayerHelper.isAutoQueueEnabled(context)) {
|
||||
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams());
|
||||
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
|
||||
}
|
||||
// If nothing to synchronize
|
||||
if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasPlayQueueItemChanged) return;
|
||||
currentItem = item;
|
||||
currentInfo = info;
|
||||
if (hasPlayQueueItemChanged) {
|
||||
// updates only to the stream info should not trigger another view count
|
||||
registerView();
|
||||
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
|
||||
}
|
||||
onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged);
|
||||
|
||||
// Check if on wrong window
|
||||
if (currentPlayQueueIndex != playQueue.getIndex()) {
|
||||
@@ -855,39 +772,29 @@ public abstract class BasePlayer implements
|
||||
"index=[" + currentPlayQueueIndex + "] with " +
|
||||
"playlist length=[" + currentPlaylistSize + "]");
|
||||
|
||||
// If not playing correct stream, change window position and sets flag
|
||||
// for synchronizing once window position is corrected
|
||||
// @see maybeCorrectSeekPosition()
|
||||
} else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial ||
|
||||
!isPlaying()) {
|
||||
if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" +
|
||||
" index=[" + currentPlayQueueIndex + "]," +
|
||||
" from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "].");
|
||||
isSynchronizing = true;
|
||||
simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
|
||||
|
||||
if (item.getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
|
||||
simpleExoPlayer.seekTo(currentPlayQueueIndex, item.getRecoveryPosition());
|
||||
playQueue.unsetRecovery(currentPlayQueueIndex);
|
||||
} else {
|
||||
simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
|
||||
final StreamType streamType = info.getStreamType();
|
||||
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
|
||||
return null;
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
final StreamInfo info = tag.getMetadata();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName());
|
||||
}
|
||||
|
||||
if (!info.getHlsUrl().isEmpty()) {
|
||||
return buildLiveMediaSource(info.getHlsUrl(), C.TYPE_HLS);
|
||||
} else if (!info.getDashMpdUrl().isEmpty()) {
|
||||
return buildLiveMediaSource(info.getDashMpdUrl(), C.TYPE_DASH);
|
||||
}
|
||||
|
||||
return null;
|
||||
initThumbnail(info.getThumbnailUrl());
|
||||
registerView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1020,9 +927,7 @@ public abstract class BasePlayer implements
|
||||
|
||||
public void seekTo(long positionMillis) {
|
||||
if (DEBUG) Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]");
|
||||
if (simpleExoPlayer == null || positionMillis < 0 ||
|
||||
positionMillis > simpleExoPlayer.getDuration()) return;
|
||||
simpleExoPlayer.seekTo(positionMillis);
|
||||
if (simpleExoPlayer != null) simpleExoPlayer.seekTo(positionMillis);
|
||||
}
|
||||
|
||||
public void seekBy(long offsetMillis) {
|
||||
@@ -1046,12 +951,14 @@ public abstract class BasePlayer implements
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void registerView() {
|
||||
if (databaseUpdateReactor == null || currentInfo == null) return;
|
||||
databaseUpdateReactor.add(recordManager.onViewed(currentInfo).onErrorComplete()
|
||||
if (currentMetadata == null) return;
|
||||
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||
final Disposable viewRegister = recordManager.onViewed(currentInfo).onErrorComplete()
|
||||
.subscribe(
|
||||
ignored -> {/* successful */},
|
||||
error -> Log.e(TAG, "Player onViewed() failure: ", error)
|
||||
));
|
||||
);
|
||||
databaseUpdateReactor.add(viewRegister);
|
||||
}
|
||||
|
||||
protected void reload() {
|
||||
@@ -1065,7 +972,7 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
protected void savePlaybackState(final StreamInfo info, final long progress) {
|
||||
if (info == null || databaseUpdateReactor == null) return;
|
||||
if (info == null) return;
|
||||
final Disposable stateSaver = recordManager.saveStreamState(info, progress)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.onErrorComplete()
|
||||
@@ -1077,7 +984,8 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
private void savePlaybackState() {
|
||||
if (simpleExoPlayer == null || currentInfo == null) return;
|
||||
if (simpleExoPlayer == null || currentMetadata == null) return;
|
||||
final StreamInfo currentInfo = currentMetadata.getMetadata();
|
||||
|
||||
if (simpleExoPlayer.getCurrentPosition() > RECOVERY_SKIP_THRESHOLD_MILLIS &&
|
||||
simpleExoPlayer.getCurrentPosition() <
|
||||
@@ -1085,6 +993,34 @@ public abstract class BasePlayer implements
|
||||
savePlaybackState(currentInfo, simpleExoPlayer.getCurrentPosition());
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateCurrentMetadata() {
|
||||
if (simpleExoPlayer == null) return;
|
||||
|
||||
final MediaSourceTag metadata;
|
||||
try {
|
||||
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
||||
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata == null) return;
|
||||
maybeAutoQueueNextStream(metadata);
|
||||
|
||||
if (currentMetadata == metadata) return;
|
||||
currentMetadata = metadata;
|
||||
onMetadataChanged(metadata);
|
||||
}
|
||||
|
||||
private void maybeAutoQueueNextStream(@NonNull final MediaSourceTag currentMetadata) {
|
||||
if (playQueue == null || playQueue.getIndex() != playQueue.size() - 1 ||
|
||||
getRepeatMode() != Player.REPEAT_MODE_OFF ||
|
||||
!PlayerHelper.isAutoQueueEnabled(context)) return;
|
||||
// auto queue when starting playback on the last item when not repeating
|
||||
final PlayQueue autoQueue = PlayerHelper.autoQueueOf(currentMetadata.getMetadata(),
|
||||
playQueue.getStreams());
|
||||
if (autoQueue != null) playQueue.append(autoQueue.getStreams());
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters and Setters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -1101,19 +1037,35 @@ public abstract class BasePlayer implements
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MediaSourceTag getCurrentMetadata() {
|
||||
return currentMetadata;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getVideoUrl() {
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUrl();
|
||||
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUrl();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getVideoTitle() {
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getTitle();
|
||||
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getName();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getUploaderName() {
|
||||
return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader();
|
||||
return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getMetadata().getUploaderName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bitmap getThumbnail() {
|
||||
return currentThumbnail == null ?
|
||||
BitmapFactory.decodeResource(context.getResources(), R.drawable.dummy_thumbnail) :
|
||||
currentThumbnail;
|
||||
}
|
||||
|
||||
/** Checks if the current playback is a livestream AND is playing at or beyond the live edge */
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isLiveEdge() {
|
||||
if (simpleExoPlayer == null || !isLive()) return false;
|
||||
|
||||
@@ -1162,19 +1114,22 @@ public abstract class BasePlayer implements
|
||||
return getPlaybackParameters().pitch;
|
||||
}
|
||||
|
||||
public boolean getPlaybackSkipSilence() {
|
||||
return getPlaybackParameters().skipSilence;
|
||||
}
|
||||
|
||||
public void setPlaybackSpeed(float speed) {
|
||||
setPlaybackParameters(speed, getPlaybackPitch());
|
||||
setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence());
|
||||
}
|
||||
|
||||
public PlaybackParameters getPlaybackParameters() {
|
||||
final PlaybackParameters defaultParameters = new PlaybackParameters(1f, 1f);
|
||||
if (simpleExoPlayer == null) return defaultParameters;
|
||||
if (simpleExoPlayer == null) return PlaybackParameters.DEFAULT;
|
||||
final PlaybackParameters parameters = simpleExoPlayer.getPlaybackParameters();
|
||||
return parameters == null ? defaultParameters : parameters;
|
||||
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
|
||||
}
|
||||
|
||||
public void setPlaybackParameters(float speed, float pitch) {
|
||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch));
|
||||
public void setPlaybackParameters(float speed, float pitch, boolean skipSilence) {
|
||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
|
||||
}
|
||||
|
||||
public PlayQueue getPlayQueue() {
|
||||
@@ -1190,7 +1145,7 @@ public abstract class BasePlayer implements
|
||||
}
|
||||
|
||||
public boolean isProgressLoopRunning() {
|
||||
return progressUpdateReactor != null && !progressUpdateReactor.isDisposed();
|
||||
return progressUpdateReactor.get() != null;
|
||||
}
|
||||
|
||||
public void setRecovery() {
|
||||
|
||||
@@ -58,7 +58,6 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
@@ -67,6 +66,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
@@ -126,7 +127,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
hideSystemUi();
|
||||
setContentView(R.layout.activity_main_player);
|
||||
playerImpl = new VideoPlayerImpl(this);
|
||||
playerImpl = new VideoPlayerImpl(this);
|
||||
playerImpl.setup(findViewById(android.R.id.content));
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.get(KEY_SAVED_STATE) != null) {
|
||||
@@ -181,7 +182,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
playerImpl.setPlaybackQuality(playerState.getPlaybackQuality());
|
||||
playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(),
|
||||
playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(),
|
||||
playerState.wasPlaying());
|
||||
playerState.isPlaybackSkipSilence(), playerState.wasPlaying());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +211,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
playerImpl.setRecovery();
|
||||
playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
|
||||
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
|
||||
playerImpl.getPlaybackQuality(), playerImpl.isPlaying());
|
||||
playerImpl.getPlaybackQuality(), playerImpl.getPlaybackSkipSilence(),
|
||||
playerImpl.isPlaying());
|
||||
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
|
||||
}
|
||||
|
||||
@@ -352,8 +354,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
|
||||
if (playerImpl != null) playerImpl.setPlaybackParameters(playbackTempo, playbackPitch);
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
||||
boolean playbackSkipSilence) {
|
||||
if (playerImpl != null) {
|
||||
playerImpl.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@@ -493,14 +498,11 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
|
||||
titleTextView.setText(getVideoTitle());
|
||||
channelTextView.setText(getUploaderName());
|
||||
titleTextView.setText(tag.getMetadata().getName());
|
||||
channelTextView.setText(tag.getMetadata().getUploaderName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -533,6 +535,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
context.startService(intent);
|
||||
@@ -554,6 +557,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
context.startService(intent);
|
||||
@@ -649,7 +653,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void onPlaybackSpeedClicked() {
|
||||
PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch())
|
||||
PlaybackParameterDialog
|
||||
.newInstance(getPlaybackSpeed(), getPlaybackPitch(), getPlaybackSkipSilence())
|
||||
.show(getSupportFragmentManager(), TAG);
|
||||
}
|
||||
|
||||
@@ -679,14 +684,19 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
|
||||
}
|
||||
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||
return new VideoPlaybackResolver.QualityResolver() {
|
||||
@Override
|
||||
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getDefaultResolutionIndex(context, sortedVideos);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality) {
|
||||
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
||||
@Override
|
||||
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||
String playbackQuality) {
|
||||
return ListHelper.getResolutionIndex(context, sortedVideos, playbackQuality);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -710,7 +720,6 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
super.onBuffering();
|
||||
animatePlayButtons(false, 100);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
@@ -886,7 +895,6 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY());
|
||||
if (!playerImpl.isPlaying()) return false;
|
||||
|
||||
if (e.getX() > playerImpl.getRootView().getWidth() * 2 / 3) {
|
||||
playerImpl.onFastForward();
|
||||
|
||||
@@ -14,21 +14,26 @@ public class PlayerState implements Serializable {
|
||||
private final float playbackSpeed;
|
||||
private final float playbackPitch;
|
||||
@Nullable private final String playbackQuality;
|
||||
private final boolean playbackSkipSilence;
|
||||
private final boolean wasPlaying;
|
||||
|
||||
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
|
||||
final float playbackSpeed, final float playbackPitch, final boolean wasPlaying) {
|
||||
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, wasPlaying);
|
||||
final float playbackSpeed, final float playbackPitch,
|
||||
final boolean playbackSkipSilence, final boolean wasPlaying) {
|
||||
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null,
|
||||
playbackSkipSilence, wasPlaying);
|
||||
}
|
||||
|
||||
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
|
||||
final float playbackSpeed, final float playbackPitch,
|
||||
@Nullable final String playbackQuality, final boolean wasPlaying) {
|
||||
@Nullable final String playbackQuality, final boolean playbackSkipSilence,
|
||||
final boolean wasPlaying) {
|
||||
this.playQueue = playQueue;
|
||||
this.repeatMode = repeatMode;
|
||||
this.playbackSpeed = playbackSpeed;
|
||||
this.playbackPitch = playbackPitch;
|
||||
this.playbackQuality = playbackQuality;
|
||||
this.playbackSkipSilence = playbackSkipSilence;
|
||||
this.wasPlaying = wasPlaying;
|
||||
}
|
||||
|
||||
@@ -62,6 +67,10 @@ public class PlayerState implements Serializable {
|
||||
return playbackQuality;
|
||||
}
|
||||
|
||||
public boolean isPlaybackSkipSilence() {
|
||||
return playbackSkipSilence;
|
||||
}
|
||||
|
||||
public boolean wasPlaying() {
|
||||
return wasPlaying;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
@@ -56,16 +55,17 @@ import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.old.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
@@ -98,6 +98,11 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
private static final int MINIMUM_SHOW_EXTRA_WIDTH_DP = 300;
|
||||
|
||||
private static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
|
||||
private static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS |
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
|
||||
private WindowManager windowManager;
|
||||
private WindowManager.LayoutParams windowLayoutParams;
|
||||
private GestureDetector gestureDetector;
|
||||
@@ -191,14 +196,17 @@ public final class PopupVideoPlayer extends Service {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
popupWidth = popupRememberSizeAndPos ? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize) : defaultSize;
|
||||
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ?
|
||||
WindowManager.LayoutParams.TYPE_PHONE :
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
|
||||
windowLayoutParams = new WindowManager.LayoutParams(
|
||||
(int) popupWidth, (int) getMinimumVideoHeight(popupWidth),
|
||||
layoutParamType,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
IDLE_WINDOW_FLAGS,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
|
||||
windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
|
||||
int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
|
||||
int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
|
||||
@@ -228,6 +236,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
notRemoteView.setTextViewText(R.id.notificationSongName, playerImpl.getVideoTitle());
|
||||
notRemoteView.setTextViewText(R.id.notificationArtist, playerImpl.getUploaderName());
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, playerImpl.getThumbnail());
|
||||
|
||||
notRemoteView.setOnClickPendingIntent(R.id.notificationPlayPause,
|
||||
PendingIntent.getBroadcast(this, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
@@ -243,11 +252,15 @@ public final class PopupVideoPlayer extends Service {
|
||||
|
||||
setRepeatModeRemote(notRemoteView, playerImpl.getRepeatMode());
|
||||
|
||||
return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContent(notRemoteView);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,6 +379,12 @@ public final class PopupVideoPlayer extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWindowFlags(final int flags) {
|
||||
if (windowLayoutParams == null || windowManager == null || playerImpl == null) return;
|
||||
|
||||
windowLayoutParams.flags = flags;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected class VideoPlayerImpl extends VideoPlayer implements View.OnLayoutChangeListener {
|
||||
@@ -428,21 +447,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
if (loadedImage != null) {
|
||||
// rebuild notification here since remote view does not release bitmaps, causing memory leaks
|
||||
notBuilder = createNotification();
|
||||
|
||||
if (notRemoteView != null) {
|
||||
notRemoteView.setImageViewBitmap(R.id.notificationCover, loadedImage);
|
||||
}
|
||||
|
||||
updateNotification(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullScreenButtonClicked() {
|
||||
super.onFullScreenButtonClicked();
|
||||
@@ -459,6 +463,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
this.getRepeatMode(),
|
||||
this.getPlaybackSpeed(),
|
||||
this.getPlaybackPitch(),
|
||||
this.getPlaybackSkipSilence(),
|
||||
this.getPlaybackQuality()
|
||||
);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -510,14 +515,47 @@ public final class PopupVideoPlayer extends Service {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDefaultResolutionIndex(final List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
|
||||
protected VideoPlaybackResolver.QualityResolver getQualityResolver() {
|
||||
return new VideoPlaybackResolver.QualityResolver() {
|
||||
@Override
|
||||
public int getDefaultResolutionIndex(List<VideoStream> sortedVideos) {
|
||||
return ListHelper.getPopupDefaultResolutionIndex(context, sortedVideos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOverrideResolutionIndex(List<VideoStream> sortedVideos,
|
||||
String playbackQuality) {
|
||||
return ListHelper.getPopupResolutionIndex(context, sortedVideos,
|
||||
playbackQuality);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Thumbnail Loading
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
super.onLoadingComplete(imageUri, view, loadedImage);
|
||||
// rebuild notification here since remote view does not release bitmaps,
|
||||
// causing memory leaks
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality) {
|
||||
return ListHelper.getPopupResolutionIndex(context, sortedVideos, playbackQuality);
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
super.onLoadingFailed(imageUri, view, failReason);
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String imageUri, View view) {
|
||||
super.onLoadingCancelled(imageUri, view);
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -538,8 +576,8 @@ public final class PopupVideoPlayer extends Service {
|
||||
}
|
||||
|
||||
private void updateMetadata() {
|
||||
if (activityListener != null && currentInfo != null) {
|
||||
activityListener.onMetadataUpdate(currentInfo);
|
||||
if (activityListener != null && getCurrentMetadata() != null) {
|
||||
activityListener.onMetadataUpdate(getCurrentMetadata().getMetadata());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,8 +609,9 @@ public final class PopupVideoPlayer extends Service {
|
||||
public void onRepeatModeChanged(int i) {
|
||||
super.onRepeatModeChanged(i);
|
||||
setRepeatModeRemote(notRemoteView, i);
|
||||
updateNotification(-1);
|
||||
updatePlayback();
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -585,11 +624,10 @@ public final class PopupVideoPlayer extends Service {
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
resetNotification();
|
||||
updateNotification(-1);
|
||||
updateMetadata();
|
||||
}
|
||||
|
||||
@@ -652,56 +690,70 @@ public final class PopupVideoPlayer extends Service {
|
||||
@Override
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaying() {
|
||||
super.onPlaying();
|
||||
updateNotification(R.drawable.ic_pause_white);
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
lockManager.acquireWifiAndCpu();
|
||||
|
||||
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_pause_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
|
||||
windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
lockManager.acquireWifiAndCpu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
super.onBuffering();
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused() {
|
||||
super.onPaused();
|
||||
|
||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
|
||||
windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
stopForeground(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPausedSeek() {
|
||||
super.onPausedSeek();
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
super.onCompleted();
|
||||
|
||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_replay_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
|
||||
windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams);
|
||||
stopForeground(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -719,16 +771,15 @@ public final class PopupVideoPlayer extends Service {
|
||||
super.hideControlsAndButton(duration, delay, videoPlayPause);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/*package-private*/ void enableVideoRenderer(final boolean enable) {
|
||||
final int videoRendererIndex = getRendererIndex(C.TRACK_TYPE_VIDEO);
|
||||
if (trackSelector != null && videoRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setRendererDisabled(videoRendererIndex, !enable);
|
||||
if (videoRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setRendererDisabled(videoRendererIndex, !enable));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
this.player.getRepeatMode(),
|
||||
this.player.getPlaybackSpeed(),
|
||||
this.player.getPlaybackPitch(),
|
||||
this.player.getPlaybackSkipSilence(),
|
||||
null
|
||||
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
@@ -466,13 +467,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
|
||||
private void openPlaybackParameterDialog() {
|
||||
if (player == null) return;
|
||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(),
|
||||
player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag());
|
||||
PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(),
|
||||
player.getPlaybackSkipSilence()).show(getSupportFragmentManager(), getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) {
|
||||
if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch);
|
||||
public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch,
|
||||
boolean playbackSkipSilence) {
|
||||
if (player != null) {
|
||||
player.setPlaybackParameters(playbackTempo, playbackPitch, playbackSkipSilence);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -47,11 +46,9 @@ import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.CaptionStyleCompat;
|
||||
@@ -62,21 +59,17 @@ import com.google.android.exoplayer2.video.VideoListener;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.Subtitles;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
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;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
@@ -105,13 +98,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis
|
||||
public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds
|
||||
|
||||
private ArrayList<VideoStream> availableStreams;
|
||||
private List<VideoStream> availableStreams;
|
||||
private int selectedStreamIndex;
|
||||
|
||||
protected String playbackQuality;
|
||||
|
||||
protected boolean wasPlaying = false;
|
||||
|
||||
@NonNull final private VideoPlaybackResolver resolver;
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Views
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -162,6 +154,7 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
public VideoPlayer(String debugTag, Context context) {
|
||||
super(context);
|
||||
this.TAG = debugTag;
|
||||
this.resolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
}
|
||||
|
||||
public void setup(View rootView) {
|
||||
@@ -241,7 +234,8 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
// Setup audio session with onboard equalizer
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
trackSelector.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context));
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(context)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +291,9 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
0, Menu.NONE, R.string.caption_none);
|
||||
captionOffItem.setOnMenuItemClickListener(menuItem -> {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setRendererDisabled(textRendererIndex, true);
|
||||
if (textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setRendererDisabled(textRendererIndex, true));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -310,68 +305,61 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
i + 1, Menu.NONE, captionLanguage);
|
||||
captionItem.setOnMenuItemClickListener(menuItem -> {
|
||||
final int textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
if (trackSelector != null && textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
if (textRendererIndex != RENDERER_UNAVAILABLE) {
|
||||
trackSelector.setPreferredTextLanguage(captionLanguage);
|
||||
trackSelector.setRendererDisabled(textRendererIndex, false);
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setRendererDisabled(textRendererIndex, false));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
captionPopupMenu.setOnDismissListener(this);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected abstract int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
||||
|
||||
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
|
||||
private void updateStreamRelatedViews() {
|
||||
if (getCurrentMetadata() == null) return;
|
||||
|
||||
final MediaSourceTag tag = getCurrentMetadata();
|
||||
final StreamInfo metadata = tag.getMetadata();
|
||||
|
||||
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info,
|
||||
final int newPlayQueueIndex,
|
||||
final boolean hasPlayQueueItemChanged) {
|
||||
qualityTextView.setVisibility(View.GONE);
|
||||
playbackSpeedTextView.setVisibility(View.GONE);
|
||||
|
||||
playbackEndTime.setVisibility(View.GONE);
|
||||
playbackLiveSync.setVisibility(View.GONE);
|
||||
|
||||
final StreamType streamType = info == null ? StreamType.NONE : info.getStreamType();
|
||||
|
||||
switch (streamType) {
|
||||
switch (metadata.getStreamType()) {
|
||||
case AUDIO_STREAM:
|
||||
surfaceView.setVisibility(View.GONE);
|
||||
endScreen.setVisibility(View.VISIBLE);
|
||||
playbackEndTime.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
|
||||
case AUDIO_LIVE_STREAM:
|
||||
surfaceView.setVisibility(View.GONE);
|
||||
endScreen.setVisibility(View.VISIBLE);
|
||||
playbackLiveSync.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
|
||||
case LIVE_STREAM:
|
||||
surfaceView.setVisibility(View.VISIBLE);
|
||||
endScreen.setVisibility(View.GONE);
|
||||
playbackLiveSync.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
|
||||
case VIDEO_STREAM:
|
||||
if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break;
|
||||
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
||||
availableStreams = new ArrayList<>(videos);
|
||||
if (playbackQuality == null) {
|
||||
selectedStreamIndex = getDefaultResolutionIndex(videos);
|
||||
} else {
|
||||
selectedStreamIndex = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
}
|
||||
if (metadata.getVideoStreams().size() + metadata.getVideoOnlyStreams().size() == 0)
|
||||
break;
|
||||
|
||||
availableStreams = tag.getSortedAvailableVideoStreams();
|
||||
selectedStreamIndex = tag.getSelectedVideoStreamIndex();
|
||||
buildQualityMenu();
|
||||
qualityTextView.setVisibility(View.VISIBLE);
|
||||
|
||||
qualityTextView.setVisibility(View.VISIBLE);
|
||||
surfaceView.setVisibility(View.VISIBLE);
|
||||
default:
|
||||
endScreen.setVisibility(View.GONE);
|
||||
playbackEndTime.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
@@ -379,69 +367,21 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
buildPlaybackSpeedMenu();
|
||||
playbackSpeedTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback Listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected abstract VideoPlaybackResolver.QualityResolver getQualityResolver();
|
||||
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
updateStreamRelatedViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
final MediaSource liveSource = super.sourceOf(item, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
|
||||
List<MediaSource> mediaSources = new ArrayList<>();
|
||||
|
||||
// Create video stream source
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
||||
final int index;
|
||||
if (videos.isEmpty()) {
|
||||
index = -1;
|
||||
} else if (playbackQuality == null) {
|
||||
index = getDefaultResolutionIndex(videos);
|
||||
} else {
|
||||
index = getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
}
|
||||
final VideoStream video = index >= 0 && index < videos.size() ? videos.get(index) : null;
|
||||
if (video != null) {
|
||||
final MediaSource streamSource = buildMediaSource(video.getUrl(),
|
||||
PlayerHelper.cacheKeyOf(info, video),
|
||||
MediaFormat.getSuffixById(video.getFormatId()));
|
||||
mediaSources.add(streamSource);
|
||||
}
|
||||
|
||||
// Create optional audio stream source
|
||||
final List<AudioStream> audioStreams = info.getAudioStreams();
|
||||
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
||||
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
||||
// Use the audio stream if there is no video stream, or
|
||||
// Merge with audio stream in case if video does not contain audio
|
||||
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
||||
final MediaSource audioSource = buildMediaSource(audio.getUrl(),
|
||||
PlayerHelper.cacheKeyOf(info, audio),
|
||||
MediaFormat.getSuffixById(audio.getFormatId()));
|
||||
mediaSources.add(audioSource);
|
||||
}
|
||||
|
||||
// If there is no audio or video sources, then this media source cannot be played back
|
||||
if (mediaSources.isEmpty()) return null;
|
||||
// Below are auxiliary media sources
|
||||
|
||||
// Create subtitle sources
|
||||
for (final Subtitles subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
||||
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);
|
||||
mediaSources.add(textSource);
|
||||
}
|
||||
|
||||
if (mediaSources.size() == 1) {
|
||||
return mediaSources.get(0);
|
||||
} else {
|
||||
return new MergingMediaSource(mediaSources.toArray(
|
||||
new MediaSource[mediaSources.size()]));
|
||||
}
|
||||
return resolver.resolve(info);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -460,7 +400,6 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||
playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
animateView(endScreen, false, 0);
|
||||
loadingPanel.setBackgroundColor(Color.BLACK);
|
||||
animateView(loadingPanel, true, 0);
|
||||
animateView(surfaceForeground, true, 100);
|
||||
@@ -470,6 +409,8 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
public void onPlaying() {
|
||||
super.onPlaying();
|
||||
|
||||
updateStreamRelatedViews();
|
||||
|
||||
showAndAnimateControl(-1, true);
|
||||
|
||||
playbackSeekBar.setEnabled(true);
|
||||
@@ -480,14 +421,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
loadingPanel.setVisibility(View.GONE);
|
||||
|
||||
animateView(currentDisplaySeek, AnimationUtils.Type.SCALE_AND_ALPHA, false, 200);
|
||||
animateView(endScreen, false, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
if (DEBUG) Log.d(TAG, "onBuffering() called");
|
||||
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
|
||||
animateView(loadingPanel, true, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -552,8 +491,7 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
final int textRenderer = getRendererIndex(C.TRACK_TYPE_TEXT);
|
||||
|
||||
if (captionTextView == null) return;
|
||||
if (trackSelector == null || trackSelector.getCurrentMappedTrackInfo() == null ||
|
||||
textRenderer == RENDERER_UNAVAILABLE) {
|
||||
if (trackSelector.getCurrentMappedTrackInfo() == null || textRenderer == RENDERER_UNAVAILABLE) {
|
||||
captionTextView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
@@ -575,8 +513,8 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
// Build UI
|
||||
buildCaptionMenu(availableLanguages);
|
||||
if (trackSelector.getRendererDisabled(textRenderer) || preferredLanguage == null ||
|
||||
!availableLanguages.contains(preferredLanguage)) {
|
||||
if (trackSelector.getParameters().getRendererDisabled(textRenderer) ||
|
||||
preferredLanguage == null || !availableLanguages.contains(preferredLanguage)) {
|
||||
captionTextView.setText(R.string.caption_none);
|
||||
} else {
|
||||
captionTextView.setText(preferredLanguage);
|
||||
@@ -905,11 +843,12 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void setPlaybackQuality(final String quality) {
|
||||
this.playbackQuality = quality;
|
||||
this.resolver.setPlaybackQuality(quality);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPlaybackQuality() {
|
||||
return playbackQuality;
|
||||
return resolver.getPlaybackQuality();
|
||||
}
|
||||
|
||||
public AspectRatioFrameLayout getAspectRatioFrameLayout() {
|
||||
|
||||
@@ -39,10 +39,13 @@ public class MediaSessionManager {
|
||||
return MediaButtonReceiver.handleIntent(mediaSession, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called on player destruction to prevent leakage.
|
||||
* */
|
||||
public void dispose() {
|
||||
this.sessionConnector.setPlayer(null, null);
|
||||
this.sessionConnector.setQueueNavigator(null);
|
||||
this.mediaSession.setActive(false);
|
||||
this.mediaSession.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,25 +21,34 @@ import static org.schabi.newpipe.player.BasePlayer.DEBUG;
|
||||
public class PlaybackParameterDialog extends DialogFragment {
|
||||
@NonNull private static final String TAG = "PlaybackParameterDialog";
|
||||
|
||||
public static final double MINIMUM_PLAYBACK_VALUE = 0.25f;
|
||||
// Minimum allowable range in ExoPlayer
|
||||
public static final double MINIMUM_PLAYBACK_VALUE = 0.10f;
|
||||
public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f;
|
||||
|
||||
public static final char STEP_UP_SIGN = '+';
|
||||
public static final char STEP_DOWN_SIGN = '-';
|
||||
public static final double PLAYBACK_STEP_VALUE = 0.05f;
|
||||
|
||||
public static final double NIGHTCORE_TEMPO = 1.20f;
|
||||
public static final double NIGHTCORE_PITCH_LOWER = 1.15f;
|
||||
public static final double NIGHTCORE_PITCH_UPPER = 1.25f;
|
||||
public static final double STEP_ONE_PERCENT_VALUE = 0.01f;
|
||||
public static final double STEP_FIVE_PERCENT_VALUE = 0.05f;
|
||||
public static final double STEP_TEN_PERCENT_VALUE = 0.10f;
|
||||
public static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f;
|
||||
public static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f;
|
||||
|
||||
public static final double DEFAULT_TEMPO = 1.00f;
|
||||
public static final double DEFAULT_PITCH = 1.00f;
|
||||
public static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE;
|
||||
public static final boolean DEFAULT_SKIP_SILENCE = false;
|
||||
|
||||
@NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key";
|
||||
@NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key";
|
||||
|
||||
@NonNull private static final String TEMPO_KEY = "tempo_key";
|
||||
@NonNull private static final String PITCH_KEY = "pitch_key";
|
||||
@NonNull private static final String STEP_SIZE_KEY = "step_size_key";
|
||||
|
||||
public interface Callback {
|
||||
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch);
|
||||
void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch,
|
||||
final boolean playbackSkipSilence);
|
||||
}
|
||||
|
||||
@Nullable private Callback callback;
|
||||
@@ -50,6 +59,11 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
|
||||
private double initialTempo = DEFAULT_TEMPO;
|
||||
private double initialPitch = DEFAULT_PITCH;
|
||||
private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE;
|
||||
|
||||
private double tempo = DEFAULT_TEMPO;
|
||||
private double pitch = DEFAULT_PITCH;
|
||||
private double stepSize = DEFAULT_STEP;
|
||||
|
||||
@Nullable private SeekBar tempoSlider;
|
||||
@Nullable private TextView tempoMinimumText;
|
||||
@@ -65,16 +79,26 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
@Nullable private TextView pitchStepDownText;
|
||||
@Nullable private TextView pitchStepUpText;
|
||||
|
||||
@Nullable private CheckBox unhookingCheckbox;
|
||||
@Nullable private TextView stepSizeOnePercentText;
|
||||
@Nullable private TextView stepSizeFivePercentText;
|
||||
@Nullable private TextView stepSizeTenPercentText;
|
||||
@Nullable private TextView stepSizeTwentyFivePercentText;
|
||||
@Nullable private TextView stepSizeOneHundredPercentText;
|
||||
|
||||
@Nullable private TextView nightCorePresetText;
|
||||
@Nullable private TextView resetPresetText;
|
||||
@Nullable private CheckBox unhookingCheckbox;
|
||||
@Nullable private CheckBox skipSilenceCheckbox;
|
||||
|
||||
public static PlaybackParameterDialog newInstance(final double playbackTempo,
|
||||
final double playbackPitch) {
|
||||
final double playbackPitch,
|
||||
final boolean playbackSkipSilence) {
|
||||
PlaybackParameterDialog dialog = new PlaybackParameterDialog();
|
||||
dialog.initialTempo = playbackTempo;
|
||||
dialog.initialPitch = playbackPitch;
|
||||
|
||||
dialog.tempo = playbackTempo;
|
||||
dialog.pitch = playbackPitch;
|
||||
|
||||
dialog.initialSkipSilence = playbackSkipSilence;
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@@ -98,6 +122,10 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
if (savedInstanceState != null) {
|
||||
initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO);
|
||||
initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH);
|
||||
|
||||
tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO);
|
||||
pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH);
|
||||
stepSize = savedInstanceState.getDouble(STEP_SIZE_KEY, DEFAULT_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +134,10 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putDouble(INITIAL_TEMPO_KEY, initialTempo);
|
||||
outState.putDouble(INITIAL_PITCH_KEY, initialPitch);
|
||||
|
||||
outState.putDouble(TEMPO_KEY, getCurrentTempo());
|
||||
outState.putDouble(PITCH_KEY, getCurrentPitch());
|
||||
outState.putDouble(STEP_SIZE_KEY, getCurrentStepSize());
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -123,7 +155,9 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
.setView(view)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) ->
|
||||
setPlaybackParameters(initialTempo, initialPitch))
|
||||
setPlaybackParameters(initialTempo, initialPitch, initialSkipSilence))
|
||||
.setNeutralButton(R.string.playback_reset, (dialogInterface, i) ->
|
||||
setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, DEFAULT_SKIP_SILENCE))
|
||||
.setPositiveButton(R.string.finish, (dialogInterface, i) ->
|
||||
setCurrentPlaybackParameters());
|
||||
|
||||
@@ -136,9 +170,13 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
|
||||
private void setupControlViews(@NonNull View rootView) {
|
||||
setupHookingControl(rootView);
|
||||
setupSkipSilenceControl(rootView);
|
||||
|
||||
setupTempoControl(rootView);
|
||||
setupPitchControl(rootView);
|
||||
setupPresetControl(rootView);
|
||||
|
||||
changeStepSize(stepSize);
|
||||
setupStepSizeSelector(rootView);
|
||||
}
|
||||
|
||||
private void setupTempoControl(@NonNull View rootView) {
|
||||
@@ -150,31 +188,15 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
tempoStepDownText = rootView.findViewById(R.id.tempoStepDown);
|
||||
|
||||
if (tempoCurrentText != null)
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo));
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||
if (tempoMaximumText != null)
|
||||
tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE));
|
||||
if (tempoMinimumText != null)
|
||||
tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE));
|
||||
|
||||
if (tempoStepUpText != null) {
|
||||
tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||
tempoStepUpText.setOnClickListener(view -> {
|
||||
onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (tempoStepDownText != null) {
|
||||
tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||
tempoStepDownText.setOnClickListener(view -> {
|
||||
onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (tempoSlider != null) {
|
||||
tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||
tempoSlider.setProgress(strategy.progressOf(initialTempo));
|
||||
tempoSlider.setProgress(strategy.progressOf(tempo));
|
||||
tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener());
|
||||
}
|
||||
}
|
||||
@@ -188,31 +210,15 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
pitchStepUpText = rootView.findViewById(R.id.pitchStepUp);
|
||||
|
||||
if (pitchCurrentText != null)
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch));
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||
if (pitchMaximumText != null)
|
||||
pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE));
|
||||
if (pitchMinimumText != null)
|
||||
pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE));
|
||||
|
||||
if (pitchStepUpText != null) {
|
||||
pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE));
|
||||
pitchStepUpText.setOnClickListener(view -> {
|
||||
onPitchSliderUpdated(getCurrentPitch() + PLAYBACK_STEP_VALUE);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (pitchStepDownText != null) {
|
||||
pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE));
|
||||
pitchStepDownText.setOnClickListener(view -> {
|
||||
onPitchSliderUpdated(getCurrentPitch() - PLAYBACK_STEP_VALUE);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (pitchSlider != null) {
|
||||
pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE));
|
||||
pitchSlider.setProgress(strategy.progressOf(initialPitch));
|
||||
pitchSlider.setProgress(strategy.progressOf(pitch));
|
||||
pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener());
|
||||
}
|
||||
}
|
||||
@@ -220,7 +226,7 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
private void setupHookingControl(@NonNull View rootView) {
|
||||
unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox);
|
||||
if (unhookingCheckbox != null) {
|
||||
unhookingCheckbox.setChecked(initialPitch != initialTempo);
|
||||
unhookingCheckbox.setChecked(pitch != tempo);
|
||||
unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> {
|
||||
if (isChecked) return;
|
||||
// When unchecked, slide back to the minimum of current tempo or pitch
|
||||
@@ -231,24 +237,84 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPresetControl(@NonNull View rootView) {
|
||||
nightCorePresetText = rootView.findViewById(R.id.presetNightcore);
|
||||
if (nightCorePresetText != null) {
|
||||
nightCorePresetText.setOnClickListener(view -> {
|
||||
final double randomPitch = NIGHTCORE_PITCH_LOWER +
|
||||
Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER);
|
||||
private void setupSkipSilenceControl(@NonNull View rootView) {
|
||||
skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox);
|
||||
if (skipSilenceCheckbox != null) {
|
||||
skipSilenceCheckbox.setChecked(initialSkipSilence);
|
||||
skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) ->
|
||||
setCurrentPlaybackParameters());
|
||||
}
|
||||
}
|
||||
|
||||
setTempoSlider(NIGHTCORE_TEMPO);
|
||||
setPitchSlider(randomPitch);
|
||||
private void setupStepSizeSelector(@NonNull final View rootView) {
|
||||
stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent);
|
||||
stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent);
|
||||
stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent);
|
||||
stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent);
|
||||
stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent);
|
||||
|
||||
if (stepSizeOnePercentText != null) {
|
||||
stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE));
|
||||
stepSizeOnePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_ONE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeFivePercentText != null) {
|
||||
stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE));
|
||||
stepSizeFivePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_FIVE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeTenPercentText != null) {
|
||||
stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE));
|
||||
stepSizeTenPercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_TEN_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeTwentyFivePercentText != null) {
|
||||
stepSizeTwentyFivePercentText.setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
stepSizeTwentyFivePercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE));
|
||||
}
|
||||
|
||||
if (stepSizeOneHundredPercentText != null) {
|
||||
stepSizeOneHundredPercentText.setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
stepSizeOneHundredPercentText.setOnClickListener(view ->
|
||||
changeStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
private void changeStepSize(final double stepSize) {
|
||||
this.stepSize = stepSize;
|
||||
|
||||
if (tempoStepUpText != null) {
|
||||
tempoStepUpText.setText(getStepUpPercentString(stepSize));
|
||||
tempoStepUpText.setOnClickListener(view -> {
|
||||
onTempoSliderUpdated(getCurrentTempo() + stepSize);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
resetPresetText = rootView.findViewById(R.id.presetReset);
|
||||
if (resetPresetText != null) {
|
||||
resetPresetText.setOnClickListener(view -> {
|
||||
setTempoSlider(DEFAULT_TEMPO);
|
||||
setPitchSlider(DEFAULT_PITCH);
|
||||
if (tempoStepDownText != null) {
|
||||
tempoStepDownText.setText(getStepDownPercentString(stepSize));
|
||||
tempoStepDownText.setOnClickListener(view -> {
|
||||
onTempoSliderUpdated(getCurrentTempo() - stepSize);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (pitchStepUpText != null) {
|
||||
pitchStepUpText.setText(getStepUpPercentString(stepSize));
|
||||
pitchStepUpText.setOnClickListener(view -> {
|
||||
onPitchSliderUpdated(getCurrentPitch() + stepSize);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
|
||||
if (pitchStepDownText != null) {
|
||||
pitchStepDownText.setText(getStepDownPercentString(stepSize));
|
||||
pitchStepDownText.setOnClickListener(view -> {
|
||||
onPitchSliderUpdated(getCurrentPitch() - stepSize);
|
||||
setCurrentPlaybackParameters();
|
||||
});
|
||||
}
|
||||
@@ -342,10 +408,11 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void setCurrentPlaybackParameters() {
|
||||
setPlaybackParameters(getCurrentTempo(), getCurrentPitch());
|
||||
setPlaybackParameters(getCurrentTempo(), getCurrentPitch(), getCurrentSkipSilence());
|
||||
}
|
||||
|
||||
private void setPlaybackParameters(final double tempo, final double pitch) {
|
||||
private void setPlaybackParameters(final double tempo, final double pitch,
|
||||
final boolean skipSilence) {
|
||||
if (callback != null && tempoCurrentText != null && pitchCurrentText != null) {
|
||||
if (DEBUG) Log.d(TAG, "Setting playback parameters to " +
|
||||
"tempo=[" + tempo + "], " +
|
||||
@@ -353,27 +420,40 @@ public class PlaybackParameterDialog extends DialogFragment {
|
||||
|
||||
tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo));
|
||||
pitchCurrentText.setText(PlayerHelper.formatPitch(pitch));
|
||||
callback.onPlaybackParameterChanged((float) tempo, (float) pitch);
|
||||
callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence);
|
||||
}
|
||||
}
|
||||
|
||||
private double getCurrentTempo() {
|
||||
return tempoSlider == null ? initialTempo : strategy.valueOf(
|
||||
return tempoSlider == null ? tempo : strategy.valueOf(
|
||||
tempoSlider.getProgress());
|
||||
}
|
||||
|
||||
private double getCurrentPitch() {
|
||||
return pitchSlider == null ? initialPitch : strategy.valueOf(
|
||||
return pitchSlider == null ? pitch : strategy.valueOf(
|
||||
pitchSlider.getProgress());
|
||||
}
|
||||
|
||||
private double getCurrentStepSize() {
|
||||
return stepSize;
|
||||
}
|
||||
|
||||
private boolean getCurrentSkipSilence() {
|
||||
return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStepUpPercentString(final double percent) {
|
||||
return STEP_UP_SIGN + PlayerHelper.formatPitch(percent);
|
||||
return STEP_UP_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStepDownPercentString(final double percent) {
|
||||
return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent);
|
||||
return STEP_DOWN_SIGN + getPercentString(percent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getPercentString(final double percent) {
|
||||
return PlayerHelper.formatPitch(percent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,6 @@ public class PlayerHelper {
|
||||
public static TrackSelection.Factory getQualitySelector(@NonNull final Context context,
|
||||
@NonNull final BandwidthMeter meter) {
|
||||
return new AdaptiveTrackSelection.Factory(meter,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_INITIAL_BITRATE,
|
||||
/*bufferDurationRequiredForQualityIncrease=*/1000,
|
||||
AdaptiveTrackSelection.DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
|
||||
AdaptiveTrackSelection.DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS,
|
||||
@@ -253,7 +252,7 @@ public class PlayerHelper {
|
||||
}
|
||||
|
||||
public static int getShutdownFlingVelocity(@NonNull final Context context) {
|
||||
return 10000;
|
||||
return 6000;
|
||||
}
|
||||
|
||||
public static int getTossFlingVelocity(@NonNull final Context context) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
@@ -11,7 +12,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class FailedMediaSource implements ManagedMediaSource {
|
||||
public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
private final String TAG = "FailedMediaSource@" + Integer.toHexString(hashCode());
|
||||
|
||||
public static class FailedMediaSourceException extends Exception {
|
||||
@@ -72,11 +73,6 @@ public class FailedMediaSource implements ManagedMediaSource {
|
||||
return System.currentTimeMillis() >= retryTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
|
||||
Log.e(TAG, "Loading failed source: ", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
throw new IOException(error);
|
||||
@@ -90,8 +86,14 @@ public class FailedMediaSource implements ManagedMediaSource {
|
||||
@Override
|
||||
public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
|
||||
|
||||
@Override
|
||||
public void releaseSource() {}
|
||||
protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
|
||||
Log.e(TAG, "Loading failed source: ", error);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseSourceInternal() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
@@ -34,7 +36,8 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
|
||||
public void prepareSource(ExoPlayer player, boolean isTopLevelSource,
|
||||
SourceInfoRefreshListener listener) {
|
||||
source.prepareSource(player, isTopLevelSource, listener);
|
||||
}
|
||||
|
||||
@@ -54,8 +57,18 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource() {
|
||||
source.releaseSource();
|
||||
public void releaseSource(SourceInfoRefreshListener listener) {
|
||||
source.releaseSource(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEventListener(Handler handler, MediaSourceEventListener eventListener) {
|
||||
source.addEventListener(handler, eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEventListener(MediaSourceEventListener eventListener) {
|
||||
source.removeEventListener(eventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,14 +3,14 @@ package org.schabi.newpipe.player.mediasource;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||
|
||||
public class ManagedMediaSourcePlaylist {
|
||||
@NonNull private final DynamicConcatenatingMediaSource internalSource;
|
||||
@NonNull private final ConcatenatingMediaSource internalSource;
|
||||
|
||||
public ManagedMediaSourcePlaylist() {
|
||||
internalSource = new DynamicConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
|
||||
internalSource = new ConcatenatingMediaSource(/*isPlaylistAtomic=*/false,
|
||||
new ShuffleOrder.UnshuffledShuffleOrder(0));
|
||||
}
|
||||
|
||||
@@ -32,12 +32,8 @@ public class ManagedMediaSourcePlaylist {
|
||||
null : (ManagedMediaSource) internalSource.getMediaSource(index);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
internalSource.releaseSource();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public DynamicConcatenatingMediaSource getParentMediaSource() {
|
||||
public ConcatenatingMediaSource getParentMediaSource() {
|
||||
return internalSource;
|
||||
}
|
||||
|
||||
@@ -46,7 +42,7 @@ public class ManagedMediaSourcePlaylist {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* Expands the {@link DynamicConcatenatingMediaSource} by appending it with a
|
||||
* Expands the {@link ConcatenatingMediaSource} by appending it with a
|
||||
* {@link PlaceholderMediaSource}.
|
||||
*
|
||||
* @see #append(ManagedMediaSource)
|
||||
@@ -56,17 +52,17 @@ public class ManagedMediaSourcePlaylist {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a {@link ManagedMediaSource} to the end of {@link DynamicConcatenatingMediaSource}.
|
||||
* @see DynamicConcatenatingMediaSource#addMediaSource
|
||||
* Appends a {@link ManagedMediaSource} to the end of {@link ConcatenatingMediaSource}.
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* */
|
||||
public synchronized void append(@NonNull final ManagedMediaSource source) {
|
||||
internalSource.addMediaSource(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link ManagedMediaSource} from {@link DynamicConcatenatingMediaSource}
|
||||
* Removes a {@link ManagedMediaSource} from {@link ConcatenatingMediaSource}
|
||||
* at the given index. If this index is out of bound, then the removal is ignored.
|
||||
* @see DynamicConcatenatingMediaSource#removeMediaSource(int)
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int)
|
||||
* */
|
||||
public synchronized void remove(final int index) {
|
||||
if (index < 0 || index > internalSource.getSize()) return;
|
||||
@@ -75,10 +71,10 @@ public class ManagedMediaSourcePlaylist {
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
|
||||
* Moves a {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* from the given source index to the target index. If either index is out of bound,
|
||||
* then the call is ignored.
|
||||
* @see DynamicConcatenatingMediaSource#moveMediaSource(int, int)
|
||||
* @see ConcatenatingMediaSource#moveMediaSource(int, int)
|
||||
* */
|
||||
public synchronized void move(final int source, final int target) {
|
||||
if (source < 0 || target < 0) return;
|
||||
@@ -99,7 +95,7 @@ public class ManagedMediaSourcePlaylist {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
|
||||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}.
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* */
|
||||
@@ -108,11 +104,11 @@ public class ManagedMediaSourcePlaylist {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link ManagedMediaSource} in {@link DynamicConcatenatingMediaSource}
|
||||
* Updates the {@link ManagedMediaSource} in {@link ConcatenatingMediaSource}
|
||||
* at the given index with a given {@link ManagedMediaSource}. If the index is out of bound,
|
||||
* then the replacement is ignored.
|
||||
* @see DynamicConcatenatingMediaSource#addMediaSource
|
||||
* @see DynamicConcatenatingMediaSource#removeMediaSource(int, Runnable)
|
||||
* @see ConcatenatingMediaSource#addMediaSource
|
||||
* @see ConcatenatingMediaSource#removeMediaSource(int, Runnable)
|
||||
* */
|
||||
public synchronized void update(final int index, @NonNull final ManagedMediaSource source,
|
||||
@Nullable final Runnable finalizingAction) {
|
||||
|
||||
@@ -3,20 +3,19 @@ package org.schabi.newpipe.player.mediasource;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PlaceholderMediaSource implements ManagedMediaSource {
|
||||
public class PlaceholderMediaSource extends BaseMediaSource implements ManagedMediaSource {
|
||||
// Do nothing, so this will stall the playback
|
||||
@Override public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {}
|
||||
@Override public void maybeThrowSourceInfoRefreshError() throws IOException {}
|
||||
@Override public void maybeThrowSourceInfoRefreshError() {}
|
||||
@Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { return null; }
|
||||
@Override public void releasePeriod(MediaPeriod mediaPeriod) {}
|
||||
@Override public void releaseSource() {}
|
||||
@Override protected void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {}
|
||||
@Override protected void releaseSourceInternal() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity,
|
||||
|
||||
@@ -5,12 +5,10 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.source.DynamicConcatenatingMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
||||
import org.schabi.newpipe.player.mediasource.LoadedMediaSource;
|
||||
import org.schabi.newpipe.player.mediasource.ManagedMediaSource;
|
||||
@@ -24,10 +22,8 @@ import org.schabi.newpipe.player.playqueue.events.RemoveEvent;
|
||||
import org.schabi.newpipe.player.playqueue.events.ReorderEvent;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -37,8 +33,6 @@ import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.SerialDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.internal.subscriptions.EmptySubscription;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
@@ -104,7 +98,6 @@ public class MediaSourceManager {
|
||||
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
|
||||
@NonNull private final CompositeDisposable loaderReactor;
|
||||
@NonNull private final Set<PlayQueueItem> loadingItems;
|
||||
@NonNull private final SerialDisposable syncReactor;
|
||||
|
||||
@NonNull private final AtomicBoolean isBlocked;
|
||||
|
||||
@@ -144,7 +137,6 @@ public class MediaSourceManager {
|
||||
|
||||
this.playQueueReactor = EmptySubscription.INSTANCE;
|
||||
this.loaderReactor = new CompositeDisposable();
|
||||
this.syncReactor = new SerialDisposable();
|
||||
|
||||
this.isBlocked = new AtomicBoolean(false);
|
||||
|
||||
@@ -171,8 +163,6 @@ public class MediaSourceManager {
|
||||
|
||||
playQueueReactor.cancel();
|
||||
loaderReactor.dispose();
|
||||
syncReactor.dispose();
|
||||
playlist.dispose();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -311,21 +301,7 @@ public class MediaSourceManager {
|
||||
final PlayQueueItem currentItem = playQueue.getItem();
|
||||
if (isBlocked.get() || currentItem == null) return;
|
||||
|
||||
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
|
||||
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
|
||||
|
||||
final Disposable sync = currentItem.getStream()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError);
|
||||
syncReactor.set(sync);
|
||||
}
|
||||
|
||||
private void syncInternal(@NonNull final PlayQueueItem item,
|
||||
@Nullable final StreamInfo info) {
|
||||
// Ensure the current item is up to date with the play queue
|
||||
if (playQueue.getItem() == item) {
|
||||
playbackListener.onPlaybackSynchronize(item, info);
|
||||
}
|
||||
playbackListener.onPlaybackSynchronize(currentItem);
|
||||
}
|
||||
|
||||
private synchronized void maybeSynchronizePlayer() {
|
||||
@@ -424,7 +400,8 @@ public class MediaSourceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the corresponding MediaSource in {@link DynamicConcatenatingMediaSource}
|
||||
* Checks if the corresponding MediaSource in
|
||||
* {@link com.google.android.exoplayer2.source.ConcatenatingMediaSource}
|
||||
* for a given {@link PlayQueueItem} needs replacement, either due to gapless playback
|
||||
* readiness or playlist desynchronization.
|
||||
* <br><br>
|
||||
@@ -481,8 +458,6 @@ public class MediaSourceManager {
|
||||
|
||||
private void resetSources() {
|
||||
if (DEBUG) Log.d(TAG, "resetSources() called.");
|
||||
|
||||
playlist.dispose();
|
||||
playlist = new ManagedMediaSourcePlaylist();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public interface PlaybackListener {
|
||||
*
|
||||
* May be called anytime at any amount once unblock is called.
|
||||
* */
|
||||
void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
|
||||
void onPlaybackSynchronize(@NonNull final PlayQueueItem item);
|
||||
|
||||
/**
|
||||
* Requests the listener to resolve a stream info into a media source
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
|
||||
public class AudioPlaybackResolver implements PlaybackResolver {
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final PlayerDataSource dataSource;
|
||||
|
||||
public AudioPlaybackResolver(@NonNull final Context context,
|
||||
@NonNull final PlayerDataSource dataSource) {
|
||||
this.context = context;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
|
||||
final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams());
|
||||
if (index < 0 || index >= info.getAudioStreams().size()) return null;
|
||||
|
||||
final AudioStream audio = info.getAudioStreams().get(index);
|
||||
final MediaSourceTag tag = new MediaSourceTag(info);
|
||||
return buildMediaSource(dataSource, audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio),
|
||||
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class MediaSourceTag implements Serializable {
|
||||
@NonNull private final StreamInfo metadata;
|
||||
|
||||
@NonNull private final List<VideoStream> sortedAvailableVideoStreams;
|
||||
private final int selectedVideoStreamIndex;
|
||||
|
||||
public MediaSourceTag(@NonNull final StreamInfo metadata,
|
||||
@NonNull final List<VideoStream> sortedAvailableVideoStreams,
|
||||
final int selectedVideoStreamIndex) {
|
||||
this.metadata = metadata;
|
||||
this.sortedAvailableVideoStreams = sortedAvailableVideoStreams;
|
||||
this.selectedVideoStreamIndex = selectedVideoStreamIndex;
|
||||
}
|
||||
|
||||
public MediaSourceTag(@NonNull final StreamInfo metadata) {
|
||||
this(metadata, Collections.emptyList(), /*indexNotAvailable=*/-1);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StreamInfo getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<VideoStream> getSortedAvailableVideoStreams() {
|
||||
return sortedAvailableVideoStreams;
|
||||
}
|
||||
|
||||
public int getSelectedVideoStreamIndex() {
|
||||
return selectedVideoStreamIndex;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VideoStream getSelectedVideoStream() {
|
||||
return selectedVideoStreamIndex < 0 ||
|
||||
selectedVideoStreamIndex >= sortedAvailableVideoStreams.size() ? null :
|
||||
sortedAvailableVideoStreams.get(selectedVideoStreamIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
|
||||
public interface PlaybackResolver extends Resolver<StreamInfo, MediaSource> {
|
||||
|
||||
@Nullable
|
||||
default MediaSource maybeBuildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||
@NonNull final StreamInfo info) {
|
||||
final StreamType streamType = info.getStreamType();
|
||||
if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final MediaSourceTag tag = new MediaSourceTag(info);
|
||||
if (!info.getHlsUrl().isEmpty()) {
|
||||
return buildLiveMediaSource(dataSource, info.getHlsUrl(), C.TYPE_HLS, tag);
|
||||
} else if (!info.getDashMpdUrl().isEmpty()) {
|
||||
return buildLiveMediaSource(dataSource, info.getDashMpdUrl(), C.TYPE_DASH, tag);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
default MediaSource buildLiveMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||
@NonNull final String sourceUrl,
|
||||
@C.ContentType final int type,
|
||||
@NonNull final MediaSourceTag metadata) {
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getLiveDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getLiveHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
default MediaSource buildMediaSource(@NonNull final PlayerDataSource dataSource,
|
||||
@NonNull final String sourceUrl,
|
||||
@NonNull final String cacheKey,
|
||||
@NonNull final String overrideExtension,
|
||||
@NonNull final MediaSourceTag metadata) {
|
||||
final Uri uri = Uri.parse(sourceUrl);
|
||||
@C.ContentType final int type = TextUtils.isEmpty(overrideExtension) ?
|
||||
Util.inferContentType(uri) : Util.inferContentType("." + overrideExtension);
|
||||
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return dataSource.getLiveSsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_DASH:
|
||||
return dataSource.getDashMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_HLS:
|
||||
return dataSource.getHlsMediaSourceFactory().setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
case C.TYPE_OTHER:
|
||||
return dataSource.getExtractorMediaSourceFactory(cacheKey).setTag(metadata)
|
||||
.createMediaSource(uri);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public interface Resolver<Source, Product> {
|
||||
@Nullable Product resolve(@NonNull Source source);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.schabi.newpipe.player.resolver;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MergingMediaSource;
|
||||
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.Subtitles;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
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 {
|
||||
|
||||
public interface QualityResolver {
|
||||
int getDefaultResolutionIndex(final List<VideoStream> sortedVideos);
|
||||
int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
|
||||
final String playbackQuality);
|
||||
}
|
||||
|
||||
@NonNull private final Context context;
|
||||
@NonNull private final PlayerDataSource dataSource;
|
||||
@NonNull private final QualityResolver qualityResolver;
|
||||
|
||||
@Nullable private String playbackQuality;
|
||||
|
||||
public VideoPlaybackResolver(@NonNull final Context context,
|
||||
@NonNull final PlayerDataSource dataSource,
|
||||
@NonNull final QualityResolver qualityResolver) {
|
||||
this.context = context;
|
||||
this.dataSource = dataSource;
|
||||
this.qualityResolver = qualityResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MediaSource resolve(@NonNull StreamInfo info) {
|
||||
final MediaSource liveSource = maybeBuildLiveMediaSource(dataSource, info);
|
||||
if (liveSource != null) return liveSource;
|
||||
|
||||
List<MediaSource> mediaSources = new ArrayList<>();
|
||||
|
||||
// Create video stream source
|
||||
final List<VideoStream> videos = ListHelper.getSortedStreamVideosList(context,
|
||||
info.getVideoStreams(), info.getVideoOnlyStreams(), false);
|
||||
final int index;
|
||||
if (videos.isEmpty()) {
|
||||
index = -1;
|
||||
} else if (playbackQuality == null) {
|
||||
index = qualityResolver.getDefaultResolutionIndex(videos);
|
||||
} else {
|
||||
index = qualityResolver.getOverrideResolutionIndex(videos, getPlaybackQuality());
|
||||
}
|
||||
final MediaSourceTag tag = new MediaSourceTag(info, videos, index);
|
||||
@Nullable final VideoStream video = tag.getSelectedVideoStream();
|
||||
|
||||
if (video != null) {
|
||||
final MediaSource streamSource = buildMediaSource(dataSource, video.getUrl(),
|
||||
PlayerHelper.cacheKeyOf(info, video),
|
||||
MediaFormat.getSuffixById(video.getFormatId()), tag);
|
||||
mediaSources.add(streamSource);
|
||||
}
|
||||
|
||||
// Create optional audio stream source
|
||||
final List<AudioStream> audioStreams = info.getAudioStreams();
|
||||
final AudioStream audio = audioStreams.isEmpty() ? null : audioStreams.get(
|
||||
ListHelper.getDefaultAudioFormat(context, audioStreams));
|
||||
// Use the audio stream if there is no video stream, or
|
||||
// Merge with audio stream in case if video does not contain audio
|
||||
if (audio != null && ((video != null && video.isVideoOnly) || video == null)) {
|
||||
final MediaSource audioSource = buildMediaSource(dataSource, audio.getUrl(),
|
||||
PlayerHelper.cacheKeyOf(info, audio),
|
||||
MediaFormat.getSuffixById(audio.getFormatId()), tag);
|
||||
mediaSources.add(audioSource);
|
||||
}
|
||||
|
||||
// If there is no audio or video sources, then this media source cannot be played back
|
||||
if (mediaSources.isEmpty()) return null;
|
||||
// Below are auxiliary media sources
|
||||
|
||||
// Create subtitle sources
|
||||
for (final Subtitles subtitle : info.getSubtitles()) {
|
||||
final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType());
|
||||
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);
|
||||
mediaSources.add(textSource);
|
||||
}
|
||||
|
||||
if (mediaSources.size() == 1) {
|
||||
return mediaSources.get(0);
|
||||
} else {
|
||||
return new MergingMediaSource(mediaSources.toArray(
|
||||
new MediaSource[mediaSources.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPlaybackQuality() {
|
||||
return playbackQuality;
|
||||
}
|
||||
|
||||
public void setPlaybackQuality(@Nullable String playbackQuality) {
|
||||
this.playbackQuality = playbackQuality;
|
||||
}
|
||||
}
|
||||
@@ -277,6 +277,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
newpipe_db_shm.delete();
|
||||
|
||||
} else {
|
||||
|
||||
Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ public class Constants {
|
||||
public static final String KEY_TITLE = "key_title";
|
||||
public static final String KEY_LINK_TYPE = "key_link_type";
|
||||
public static final String KEY_OPEN_SEARCH = "key_open_search";
|
||||
public static final String KEY_QUERY = "key_query";
|
||||
public static final String KEY_SEARCH_STRING = "key_search_string";
|
||||
|
||||
public static final String KEY_THEME_CHANGE = "key_theme_change";
|
||||
public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change";
|
||||
|
||||
@@ -38,9 +38,8 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.search.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.search.SearchResult;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
@@ -51,7 +50,6 @@ import java.util.List;
|
||||
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
|
||||
public final class ExtractorHelper {
|
||||
private static final String TAG = ExtractorHelper.class.getSimpleName();
|
||||
@@ -67,29 +65,35 @@ public final class ExtractorHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Single<SearchResult> searchFor(final int serviceId,
|
||||
final String query,
|
||||
final int pageNumber,
|
||||
final String contentCountry,
|
||||
final SearchEngine.Filter filter) {
|
||||
public static Single<SearchInfo> searchFor(final int serviceId,
|
||||
final String searchString,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter,
|
||||
final String contentCountry) {
|
||||
checkServiceId(serviceId);
|
||||
return Single.fromCallable(() ->
|
||||
SearchResult.getSearchResult(NewPipe.getService(serviceId).getSearchEngine(),
|
||||
query, pageNumber, contentCountry, filter)
|
||||
);
|
||||
SearchInfo.getInfo(NewPipe.getService(serviceId),
|
||||
NewPipe.getService(serviceId)
|
||||
.getSearchQIHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter),
|
||||
contentCountry));
|
||||
}
|
||||
|
||||
public static Single<InfoItemsPage> getMoreSearchItems(final int serviceId,
|
||||
final String query,
|
||||
final int nextPageNumber,
|
||||
final String searchLanguage,
|
||||
final SearchEngine.Filter filter) {
|
||||
final String searchString,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter,
|
||||
final String pageUrl,
|
||||
final String contentCountry) {
|
||||
checkServiceId(serviceId);
|
||||
return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter)
|
||||
.map((@NonNull SearchResult searchResult) ->
|
||||
new InfoItemsPage(searchResult.resultList,
|
||||
nextPageNumber + "",
|
||||
searchResult.errors));
|
||||
return Single.fromCallable(() ->
|
||||
SearchInfo.getMoreItems(NewPipe.getService(serviceId),
|
||||
NewPipe.getService(serviceId)
|
||||
.getSearchQIHFactory()
|
||||
.fromQuery(searchString, contentFilter, sortFilter),
|
||||
contentCountry,
|
||||
pageUrl));
|
||||
|
||||
}
|
||||
|
||||
public static Single<List<String>> suggestionsFor(final int serviceId,
|
||||
@@ -234,7 +238,6 @@ public final class ExtractorHelper {
|
||||
serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -443,11 +443,11 @@ public final class ListHelper {
|
||||
/**
|
||||
* Are we connected to wifi?
|
||||
* @param context App context
|
||||
* @return True if connected to wifi
|
||||
* @return {@code true} if connected to wifi
|
||||
*/
|
||||
private static boolean isWifiActive(Context context)
|
||||
{
|
||||
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return manager.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
|
||||
return manager != null && manager.getActiveNetworkInfo() != null && manager.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,13 @@ import org.schabi.newpipe.download.DownloadActivity;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Stream;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.fragments.MainFragment;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
@@ -100,11 +103,13 @@ public class NavigationHelper {
|
||||
final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final float playbackPitch,
|
||||
final boolean playbackSkipSilence,
|
||||
@Nullable final String playbackQuality) {
|
||||
return getPlayerIntent(context, targetClazz, playQueue, playbackQuality)
|
||||
.putExtra(BasePlayer.REPEAT_MODE, repeatMode)
|
||||
.putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed)
|
||||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch);
|
||||
.putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch)
|
||||
.putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence);
|
||||
}
|
||||
|
||||
public static void playOnMainPlayer(final Context context, final PlayQueue queue) {
|
||||
@@ -281,9 +286,11 @@ public class NavigationHelper {
|
||||
return fragmentManager.popBackStackImmediate(SEARCH_FRAGMENT_TAG, 0);
|
||||
}
|
||||
|
||||
public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) {
|
||||
public static void openSearchFragment(FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String searchString) {
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
|
||||
.replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, searchString))
|
||||
.addToBackStack(SEARCH_FRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
@@ -312,7 +319,11 @@ public class NavigationHelper {
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openChannelFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
||||
public static void openChannelFragment(
|
||||
FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
|
||||
@@ -320,7 +331,10 @@ public class NavigationHelper {
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void openPlaylistFragment(FragmentManager fragmentManager, int serviceId, String url, String name) {
|
||||
public static void openPlaylistFragment(FragmentManager fragmentManager,
|
||||
int serviceId,
|
||||
String url,
|
||||
String name) {
|
||||
if (name == null) name = "";
|
||||
defaultTransaction(fragmentManager)
|
||||
.replace(R.id.fragment_holder, PlaylistFragment.getInstance(serviceId, url, name))
|
||||
@@ -368,10 +382,10 @@ public class NavigationHelper {
|
||||
// Through Intents
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void openSearch(Context context, int serviceId, String query) {
|
||||
public static void openSearch(Context context, int serviceId, String searchString) {
|
||||
Intent mIntent = new Intent(context, MainActivity.class);
|
||||
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
|
||||
mIntent.putExtra(Constants.KEY_QUERY, query);
|
||||
mIntent.putExtra(Constants.KEY_SEARCH_STRING, searchString);
|
||||
mIntent.putExtra(Constants.KEY_OPEN_SEARCH, true);
|
||||
context.startActivity(mIntent);
|
||||
}
|
||||
@@ -465,7 +479,8 @@ public class NavigationHelper {
|
||||
|
||||
switch (linkType) {
|
||||
case STREAM:
|
||||
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY, PreferenceManager.getDefaultSharedPreferences(context)
|
||||
rIntent.putExtra(VideoDetailFragment.AUTO_PLAY,
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.autoplay_through_intent_key), false));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.preference.PreferenceManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.StringRes;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
@@ -31,6 +30,18 @@ public class ServiceHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTranslatedFilterString(String filter, Context c) {
|
||||
switch(filter) {
|
||||
case "all": return c.getString(R.string.all);
|
||||
case "videos": return c.getString(R.string.videos);
|
||||
case "channels": return c.getString(R.string.channels);
|
||||
case "playlists": return c.getString(R.string.playlists);
|
||||
case "tracks": return c.getString(R.string.tracks);
|
||||
case "users": return c.getString(R.string.users);
|
||||
default: return filter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource string with instructions for importing subscriptions for each service.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user