1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-11-09 11:43:01 +00:00

Merge branch 'TeamNewPipe:dev' into exo182

This commit is contained in:
Robin
2023-01-02 10:35:20 +01:00
committed by GitHub
41 changed files with 663 additions and 667 deletions

View File

@@ -157,9 +157,12 @@ public class MainActivity extends AppCompatActivity {
}
openMiniPlayerUponPlayerStarted();
// Schedule worker for checking for new streams and creating corresponding notifications
// if this is enabled by the user.
NotificationWorker.initialize(this);
if (PermissionHelper.checkPostNotificationsPermission(this,
PermissionHelper.POST_NOTIFICATIONS_REQUEST_CODE)) {
// Schedule worker for checking for new streams and creating corresponding notifications
// if this is enabled by the user.
NotificationWorker.initialize(this);
}
}
@Override
@@ -599,6 +602,9 @@ public class MainActivity extends AppCompatActivity {
((VideoDetailFragment) fragment).openDownloadDialog();
}
break;
case PermissionHelper.POST_NOTIFICATIONS_REQUEST_CODE:
NotificationWorker.initialize(this);
break;
}
}

View File

@@ -1,5 +1,6 @@
package org.schabi.newpipe;
import static org.schabi.newpipe.util.SparseItemUtil.fetchStreamInfoAndSaveToDatabase;
import static org.schabi.newpipe.util.external_communication.ShareUtils.shareText;
import android.content.Context;
@@ -10,6 +11,7 @@ import android.widget.PopupMenu;
import androidx.fragment.app.FragmentManager;
import org.schabi.newpipe.database.stream.model.StreamEntity;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
@@ -75,6 +77,14 @@ public final class QueueItemMenuUtil {
shareText(context, item.getTitle(), item.getUrl(),
item.getThumbnailUrl());
return true;
case R.id.menu_item_download:
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),
info -> {
final DownloadDialog downloadDialog = new DownloadDialog(context,
info);
downloadDialog.show(fragmentManager, "downloadDialog");
});
return true;
}
return false;
});

View File

@@ -17,7 +17,6 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,6 +35,7 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.appcompat.widget.Toolbar;
import androidx.collection.SparseArrayCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.DialogFragment;
import androidx.preference.PreferenceManager;
@@ -211,8 +211,7 @@ public class DownloadDialog extends DialogFragment
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
Icepick.restoreInstanceState(this, savedInstanceState);
final SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams =
new SparseArray<>(4);
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
for (int i = 0; i < videoStreams.size(); i++) {
@@ -236,10 +235,9 @@ public class DownloadDialog extends DialogFragment
}
}
this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams,
secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams);
this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(wrappedAudioStreams);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
final Intent intent = new Intent(context, DownloadManagerService.class);
context.startService(intent);

View File

@@ -10,8 +10,11 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfi
import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams;
import static org.schabi.newpipe.util.NavigationHelper.openPlayQueue;
import static org.schabi.newpipe.util.NavigationHelper.playWithKore;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -119,6 +122,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import icepick.State;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
@@ -129,9 +133,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public final class VideoDetailFragment
extends BaseStateFragment<StreamInfo>
implements BackPressable,
SharedPreferences.OnSharedPreferenceChangeListener,
View.OnClickListener,
View.OnLongClickListener,
PlayerServiceExtendedEventListener,
OnKeyDownListener {
public static final String KEY_SWITCHING_PLAYERS = "switching_players";
@@ -167,6 +168,20 @@ public final class VideoDetailFragment
private boolean tabSettingsChanged = false;
private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates
private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
(sharedPreferences, key) -> {
if (key.equals(getString(R.string.show_comments_key))) {
showComments = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_next_video_key))) {
showRelatedItems = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_description_key))) {
showDescription = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
}
};
@State
protected int serviceId = Constants.NO_SERVICE_ID;
@State
@@ -291,7 +306,7 @@ public final class VideoDetailFragment
showDescription = prefs.getBoolean(getString(R.string.show_description_key), true);
selectedTabTag = prefs.getString(
getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG);
prefs.registerOnSharedPreferenceChangeListener(this);
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
setupBroadcastReceiver();
@@ -378,7 +393,7 @@ public final class VideoDetailFragment
}
PreferenceManager.getDefaultSharedPreferences(activity)
.unregisterOnSharedPreferenceChangeListener(this);
.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
activity.unregisterReceiver(broadcastReceiver);
activity.getContentResolver().unregisterContentObserver(settingsContentObserver);
@@ -424,130 +439,129 @@ public final class VideoDetailFragment
}
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (key.equals(getString(R.string.show_comments_key))) {
showComments = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_next_video_key))) {
showRelatedItems = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
} else if (key.equals(getString(R.string.show_description_key))) {
showDescription = sharedPreferences.getBoolean(key, true);
tabSettingsChanged = true;
}
}
/*//////////////////////////////////////////////////////////////////////////
// OnClick
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onClick(final View v) {
switch (v.getId()) {
case R.id.detail_controls_background:
openBackgroundPlayer(false);
break;
case R.id.detail_controls_popup:
openPopupPlayer(false);
break;
case R.id.detail_controls_playlist_append:
if (getFM() != null && currentInfo != null) {
disposables.add(
PlaylistDialog.createCorrespondingDialog(
getContext(),
List.of(new StreamEntity(currentInfo)),
dialog -> dialog.show(getFM(), TAG)
)
);
}
break;
case R.id.detail_controls_download:
if (PermissionHelper.checkStoragePermissions(activity,
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
this.openDownloadDialog();
}
break;
case R.id.detail_controls_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), currentInfo.getName(),
currentInfo.getUrl(), currentInfo.getThumbnailUrl());
}
break;
case R.id.detail_controls_open_in_browser:
if (currentInfo != null) {
ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getUrl());
}
break;
case R.id.detail_controls_play_with_kodi:
if (currentInfo != null) {
try {
NavigationHelper.playWithKore(
requireContext(), Uri.parse(currentInfo.getUrl()));
} catch (final Exception e) {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
}
KoreUtils.showInstallKoreDialog(requireContext());
}
}
break;
case R.id.detail_uploader_root_layout:
if (isEmpty(currentInfo.getSubChannelUrl())) {
if (!isEmpty(currentInfo.getUploaderUrl())) {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
if (DEBUG) {
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
}
} else {
openChannel(currentInfo.getSubChannelUrl(),
currentInfo.getSubChannelName());
}
break;
case R.id.detail_thumbnail_root_layout:
// make sure not to open any player if there is nothing currently loaded!
// FIXME removing this `if` causes the player service to start correctly, then stop,
// then restart badly without calling `startForeground()`, causing a crash when
// later closing the detail fragment
if (currentInfo != null) {
autoPlayEnabled = true; // forcefully start playing
// FIXME Workaround #7427
if (isPlayerAvailable()) {
player.setRecovery();
}
openVideoPlayerAutoFullscreen();
}
break;
case R.id.detail_title_root_layout:
toggleTitleAndSecondaryControls();
break;
case R.id.overlay_thumbnail:
case R.id.overlay_metadata_layout:
case R.id.overlay_buttons_layout:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
break;
case R.id.overlay_play_queue_button:
NavigationHelper.openPlayQueue(getContext());
break;
case R.id.overlay_play_pause_button:
if (playerIsNotStopped()) {
player.playPause();
player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer(false);
private void setOnClickListeners() {
binding.detailTitleRootLayout.setOnClickListener(v -> toggleTitleAndSecondaryControls());
binding.detailUploaderRootLayout.setOnClickListener(makeOnClickListener(info -> {
if (isEmpty(info.getSubChannelUrl())) {
if (!isEmpty(info.getUploaderUrl())) {
openChannel(info.getUploaderUrl(), info.getUploaderName());
}
setOverlayPlayPauseImage(isPlayerAvailable() && player.isPlaying());
break;
case R.id.overlay_close_button:
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
break;
if (DEBUG) {
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
}
} else {
openChannel(info.getSubChannelUrl(), info.getSubChannelName());
}
}));
binding.detailThumbnailRootLayout.setOnClickListener(v -> {
autoPlayEnabled = true; // forcefully start playing
// FIXME Workaround #7427
if (isPlayerAvailable()) {
player.setRecovery();
}
openVideoPlayerAutoFullscreen();
});
binding.detailControlsBackground.setOnClickListener(v -> openBackgroundPlayer(false));
binding.detailControlsPopup.setOnClickListener(v -> openPopupPlayer(false));
binding.detailControlsPlaylistAppend.setOnClickListener(makeOnClickListener(info ->
disposables.add(PlaylistDialog.createCorrespondingDialog(requireContext(),
List.of(new StreamEntity(info)),
dialog -> dialog.show(getParentFragmentManager(), TAG)))));
binding.detailControlsDownload.setOnClickListener(v -> {
if (PermissionHelper.checkStoragePermissions(activity,
PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) {
openDownloadDialog();
}
});
binding.detailControlsShare.setOnClickListener(makeOnClickListener(info ->
ShareUtils.shareText(requireContext(), info.getName(), info.getUrl(),
info.getThumbnailUrl())));
binding.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener(info ->
ShareUtils.openUrlInBrowser(requireContext(), info.getUrl())));
binding.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener(info -> {
try {
playWithKore(requireContext(), Uri.parse(info.getUrl()));
} catch (final Exception e) {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
}
KoreUtils.showInstallKoreDialog(requireContext());
}
}));
if (DEBUG) {
binding.detailControlsCrashThePlayer.setOnClickListener(v ->
VideoDetailPlayerCrasher.onCrashThePlayer(requireContext(), player));
}
final View.OnClickListener overlayListener = v -> bottomSheetBehavior
.setState(BottomSheetBehavior.STATE_EXPANDED);
binding.overlayThumbnail.setOnClickListener(overlayListener);
binding.overlayMetadataLayout.setOnClickListener(overlayListener);
binding.overlayButtonsLayout.setOnClickListener(overlayListener);
binding.overlayCloseButton.setOnClickListener(v -> bottomSheetBehavior
.setState(BottomSheetBehavior.STATE_HIDDEN));
binding.overlayPlayQueueButton.setOnClickListener(v -> openPlayQueue(requireContext()));
binding.overlayPlayPauseButton.setOnClickListener(v -> {
if (playerIsNotStopped()) {
player.playPause();
player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
showSystemUi();
} else {
autoPlayEnabled = true; // forcefully start playing
openVideoPlayer(false);
}
setOverlayPlayPauseImage(isPlayerAvailable() && player.isPlaying());
});
}
private View.OnClickListener makeOnClickListener(final Consumer<StreamInfo> consumer) {
return v -> {
if (!isLoading.get() && currentInfo != null) {
consumer.accept(currentInfo);
}
};
}
private void setOnLongClickListeners() {
binding.detailTitleRootLayout.setOnLongClickListener(makeOnLongClickListener(info ->
ShareUtils.copyToClipboard(requireContext(),
binding.detailVideoTitleView.getText().toString())));
binding.detailUploaderRootLayout.setOnLongClickListener(makeOnLongClickListener(info -> {
if (isEmpty(info.getSubChannelUrl())) {
Log.w(TAG, "Can't open parent channel because we got no parent channel URL");
} else {
openChannel(info.getUploaderUrl(), info.getUploaderName());
}
}));
binding.detailControlsBackground.setOnLongClickListener(makeOnLongClickListener(info ->
openBackgroundPlayer(true)));
binding.detailControlsPopup.setOnLongClickListener(makeOnLongClickListener(info ->
openPopupPlayer(true)));
binding.detailControlsDownload.setOnLongClickListener(makeOnLongClickListener(info ->
NavigationHelper.openDownloads(activity)));
final View.OnLongClickListener overlayListener = makeOnLongClickListener(info ->
openChannel(info.getUploaderUrl(), info.getUploaderName()));
binding.overlayThumbnail.setOnLongClickListener(overlayListener);
binding.overlayMetadataLayout.setOnLongClickListener(overlayListener);
}
private View.OnLongClickListener makeOnLongClickListener(final Consumer<StreamInfo> consumer) {
return v -> {
if (isLoading.get() || currentInfo == null) {
return false;
}
consumer.accept(currentInfo);
return true;
};
}
private void openChannel(final String subChannelUrl, final String subChannelName) {
@@ -559,43 +573,6 @@ public final class VideoDetailFragment
}
}
@Override
public boolean onLongClick(final View v) {
if (isLoading.get() || currentInfo == null) {
return false;
}
switch (v.getId()) {
case R.id.detail_controls_background:
openBackgroundPlayer(true);
break;
case R.id.detail_controls_popup:
openPopupPlayer(true);
break;
case R.id.detail_controls_download:
NavigationHelper.openDownloads(activity);
break;
case R.id.overlay_thumbnail:
case R.id.overlay_metadata_layout:
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
break;
case R.id.detail_uploader_root_layout:
if (isEmpty(currentInfo.getSubChannelUrl())) {
Log.w(TAG,
"Can't open parent channel because we got no parent channel URL");
} else {
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
}
break;
case R.id.detail_title_root_layout:
ShareUtils.copyToClipboard(requireContext(),
binding.detailVideoTitleView.getText().toString());
break;
}
return true;
}
private void toggleTitleAndSecondaryControls() {
if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) {
binding.detailVideoTitleView.setMaxLines(10);
@@ -616,11 +593,6 @@ public final class VideoDetailFragment
// Init
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
super.onViewCreated(rootView, savedInstanceState);
}
@Override // called from onViewCreated in {@link BaseFragment#onViewCreated}
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
@@ -646,44 +618,25 @@ public final class VideoDetailFragment
}
@Override
@SuppressLint("ClickableViewAccessibility")
protected void initListeners() {
super.initListeners();
binding.detailTitleRootLayout.setOnClickListener(this);
binding.detailTitleRootLayout.setOnLongClickListener(this);
binding.detailUploaderRootLayout.setOnClickListener(this);
binding.detailUploaderRootLayout.setOnLongClickListener(this);
binding.detailThumbnailRootLayout.setOnClickListener(this);
setOnClickListeners();
setOnLongClickListeners();
binding.detailControlsBackground.setOnClickListener(this);
binding.detailControlsBackground.setOnLongClickListener(this);
binding.detailControlsPopup.setOnClickListener(this);
binding.detailControlsPopup.setOnLongClickListener(this);
binding.detailControlsPlaylistAppend.setOnClickListener(this);
binding.detailControlsDownload.setOnClickListener(this);
binding.detailControlsDownload.setOnLongClickListener(this);
binding.detailControlsShare.setOnClickListener(this);
binding.detailControlsOpenInBrowser.setOnClickListener(this);
binding.detailControlsPlayWithKodi.setOnClickListener(this);
if (DEBUG) {
binding.detailControlsCrashThePlayer.setOnClickListener(
v -> VideoDetailPlayerCrasher.onCrashThePlayer(
this.getContext(),
this.player)
);
}
final View.OnTouchListener controlsTouchListener = (view, motionEvent) -> {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN
&& PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_hold_to_append_key), true)) {
binding.overlayThumbnail.setOnClickListener(this);
binding.overlayThumbnail.setOnLongClickListener(this);
binding.overlayMetadataLayout.setOnClickListener(this);
binding.overlayMetadataLayout.setOnLongClickListener(this);
binding.overlayButtonsLayout.setOnClickListener(this);
binding.overlayPlayQueueButton.setOnClickListener(this);
binding.overlayCloseButton.setOnClickListener(this);
binding.overlayPlayPauseButton.setOnClickListener(this);
binding.detailControlsBackground.setOnTouchListener(getOnControlsTouchListener());
binding.detailControlsPopup.setOnTouchListener(getOnControlsTouchListener());
animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA, 0, () ->
animate(binding.touchAppendDetail, false, 1500, AnimationType.ALPHA, 1000));
}
return false;
};
binding.detailControlsBackground.setOnTouchListener(controlsTouchListener);
binding.detailControlsPopup.setOnTouchListener(controlsTouchListener);
binding.appBarLayout.addOnOffsetChangedListener((layout, verticalOffset) -> {
// prevent useless updates to tab layout visibility if nothing changed
@@ -702,23 +655,6 @@ public final class VideoDetailFragment
}
}
private View.OnTouchListener getOnControlsTouchListener() {
return (view, motionEvent) -> {
if (!PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.show_hold_to_append_key), true)) {
return false;
}
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
animate(binding.touchAppendDetail, true, 250, AnimationType.ALPHA,
0, () ->
animate(binding.touchAppendDetail, false, 1500,
AnimationType.ALPHA, 1000));
}
return false;
};
}
private void initThumbnailViews(@NonNull final StreamInfo info) {
PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG)
.into(binding.detailThumbnailImageView, new Callback() {

View File

@@ -230,24 +230,24 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
ShareUtils.openUrlInBrowser(requireContext(), url);
break;
case R.id.menu_item_share:
if (currentInfo != null) {
ShareUtils.shareText(requireContext(), name, url,
currentInfo.getThumbnailUrl());
}
ShareUtils.shareText(requireContext(), name, url,
currentInfo == null ? null : currentInfo.getThumbnailUrl());
break;
case R.id.menu_item_bookmark:
onBookmarkClicked();
break;
case R.id.menu_item_append_playlist:
disposables.add(PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(getFM(), TAG)
));
if (currentInfo != null) {
disposables.add(PlaylistDialog.createCorrespondingDialog(
getContext(),
getPlayQueue()
.getStreams()
.stream()
.map(StreamEntity::new)
.collect(Collectors.toList()),
dialog -> dialog.show(getFM(), TAG)
));
}
break;
default:
return super.onOptionsItemSelected(item);

View File

@@ -33,6 +33,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat;
import androidx.collection.SparseArrayCompat;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper;
@@ -70,9 +71,7 @@ import org.schabi.newpipe.util.ServiceHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -141,7 +140,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@State
boolean wasSearchFocused = false;
@Nullable private Map<Integer, String> menuItemToFilterName = null;
private final SparseArrayCompat<String> menuItemToFilterName = new SparseArrayCompat<>();
private StreamingService service;
private Page nextPage;
private boolean showLocalSuggestions = true;
@@ -426,8 +425,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
menuItemToFilterName = new HashMap<>();
int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
@@ -468,11 +465,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (menuItemToFilterName != null) {
final List<String> cf = new ArrayList<>(1);
cf.add(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, cf);
}
final var filter = Collections.singletonList(menuItemToFilterName.get(item.getItemId()));
changeContentFilter(item, filter);
return true;
}

View File

@@ -216,7 +216,6 @@ public final class Player implements PlaybackListener, Listener {
// minimized to background but will resume automatically to the original player type
private boolean isAudioOnly = false;
private boolean isPrepared = false;
private boolean wasPlaying = false;
/*//////////////////////////////////////////////////////////////////////////
// UIs, listeners and disposables
@@ -918,13 +917,6 @@ public final class Player implements PlaybackListener, Listener {
error -> Log.e(TAG, "Progress update failure: ", error));
}
public void saveWasPlaying() {
this.wasPlaying = getPlayWhenReady();
}
public boolean wasPlaying() {
return wasPlaying;
}
//endregion

View File

@@ -86,8 +86,6 @@ public final class PlayerService extends Service {
}
if (!player.exoPlayerIsNull()) {
player.saveWasPlaying();
// Releases wifi & cpu, disables keepScreenOn, etc.
// We can't just pause the player here because it will make transition
// from one stream to a new stream not smooth

View File

@@ -1,6 +1,7 @@
package org.schabi.newpipe.player.seekbarpreview;
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.SeekbarPreviewThumbnailType;
import static org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper.getSeekbarPreviewThumbnailType;
import android.content.Context;
import android.graphics.Bitmap;
@@ -8,6 +9,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SparseArrayCompat;
import com.google.common.base.Stopwatch;
@@ -15,12 +17,9 @@ import org.schabi.newpipe.extractor.stream.Frameset;
import org.schabi.newpipe.util.PicassoHelper;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
@@ -34,18 +33,15 @@ public class SeekbarPreviewThumbnailHolder {
// Key = Position of the picture in milliseconds
// Supplier = Supplies the bitmap for that position
private final Map<Integer, Supplier<Bitmap>> seekbarPreviewData = new ConcurrentHashMap<>();
private final SparseArrayCompat<Supplier<Bitmap>> seekbarPreviewData =
new SparseArrayCompat<>();
// This ensures that if the reset is still undergoing
// and another reset starts, only the last reset is processed
private UUID currentUpdateRequestIdentifier = UUID.randomUUID();
public synchronized void resetFrom(
@NonNull final Context context,
final List<Frameset> framesets) {
final int seekbarPreviewType =
SeekbarPreviewThumbnailHelper.getSeekbarPreviewThumbnailType(context);
public void resetFrom(@NonNull final Context context, final List<Frameset> framesets) {
final int seekbarPreviewType = getSeekbarPreviewThumbnailType(context);
final UUID updateRequestIdentifier = UUID.randomUUID();
this.currentUpdateRequestIdentifier = updateRequestIdentifier;
@@ -63,13 +59,12 @@ public class SeekbarPreviewThumbnailHolder {
executorService.shutdown();
}
private void resetFromAsync(
final int seekbarPreviewType,
final List<Frameset> framesets,
final UUID updateRequestIdentifier) {
private void resetFromAsync(final int seekbarPreviewType, final List<Frameset> framesets,
final UUID updateRequestIdentifier) {
Log.d(TAG, "Clearing seekbarPreviewData");
seekbarPreviewData.clear();
synchronized (seekbarPreviewData) {
seekbarPreviewData.clear();
}
if (seekbarPreviewType == SeekbarPreviewThumbnailType.NONE) {
Log.d(TAG, "Not processing seekbarPreviewData due to settings");
@@ -94,10 +89,8 @@ public class SeekbarPreviewThumbnailHolder {
generateDataFrom(frameset, updateRequestIdentifier);
}
private Frameset getFrameSetForType(
final List<Frameset> framesets,
final int seekbarPreviewType) {
private Frameset getFrameSetForType(final List<Frameset> framesets,
final int seekbarPreviewType) {
if (seekbarPreviewType == SeekbarPreviewThumbnailType.HIGH_QUALITY) {
Log.d(TAG, "Strategy for seekbarPreviewData: high quality");
return framesets.stream()
@@ -111,17 +104,14 @@ public class SeekbarPreviewThumbnailHolder {
}
}
private void generateDataFrom(
final Frameset frameset,
final UUID updateRequestIdentifier) {
private void generateDataFrom(final Frameset frameset, final UUID updateRequestIdentifier) {
Log.d(TAG, "Starting generation of seekbarPreviewData");
final Stopwatch sw = Log.isLoggable(TAG, Log.DEBUG) ? Stopwatch.createStarted() : null;
int currentPosMs = 0;
int pos = 1;
final int frameCountPerUrl = frameset.getFramesPerPageX() * frameset.getFramesPerPageY();
final int urlFrameCount = frameset.getFramesPerPageX() * frameset.getFramesPerPageY();
// Process each url in the frameset
for (final String url : frameset.getUrls()) {
@@ -130,11 +120,11 @@ public class SeekbarPreviewThumbnailHolder {
// The data is not added directly to "seekbarPreviewData" due to
// concurrency and checks for "updateRequestIdentifier"
final Map<Integer, Supplier<Bitmap>> generatedDataForUrl = new HashMap<>();
final var generatedDataForUrl = new SparseArrayCompat<Supplier<Bitmap>>(urlFrameCount);
// The bitmap consists of several images, which we process here
// foreach frame in the returned bitmap
for (int i = 0; i < frameCountPerUrl; i++) {
for (int i = 0; i < urlFrameCount; i++) {
// Frames outside the video length are skipped
if (pos > frameset.getTotalCount()) {
break;
@@ -161,7 +151,9 @@ public class SeekbarPreviewThumbnailHolder {
// Check if we are still the latest request
// If not abort method execution
if (isRequestIdentifierCurrent(updateRequestIdentifier)) {
seekbarPreviewData.putAll(generatedDataForUrl);
synchronized (seekbarPreviewData) {
seekbarPreviewData.putAll(generatedDataForUrl);
}
} else {
Log.d(TAG, "Aborted of generation of seekbarPreviewData");
break;
@@ -169,7 +161,7 @@ public class SeekbarPreviewThumbnailHolder {
}
if (sw != null) {
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop().toString());
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop());
}
}
@@ -189,17 +181,14 @@ public class SeekbarPreviewThumbnailHolder {
final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
if (sw != null) {
Log.d(TAG,
"Download of bitmap for seekbarPreview from '" + url
+ "' took " + sw.stop().toString());
Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took "
+ sw.stop());
}
return bitmap;
} catch (final Exception ex) {
Log.w(TAG,
"Failed to get bitmap for seekbarPreview from url='" + url
+ "' in time",
ex);
Log.w(TAG, "Failed to get bitmap for seekbarPreview from url='" + url
+ "' in time", ex);
return null;
}
}
@@ -208,32 +197,20 @@ public class SeekbarPreviewThumbnailHolder {
return this.currentUpdateRequestIdentifier.equals(requestIdentifier);
}
public Optional<Bitmap> getBitmapAt(final int positionInMs) {
// Check if the BitmapData is empty
if (seekbarPreviewData.isEmpty()) {
return Optional.empty();
// Get the frame supplier closest to the requested position
Supplier<Bitmap> closestFrame = () -> null;
synchronized (seekbarPreviewData) {
int min = Integer.MAX_VALUE;
for (int i = 0; i < seekbarPreviewData.size(); i++) {
final int pos = Math.abs(seekbarPreviewData.keyAt(i) - positionInMs);
if (pos < min) {
closestFrame = seekbarPreviewData.valueAt(i);
min = pos;
}
}
}
// Get the closest frame to the requested position
final int closestIndexPosition =
seekbarPreviewData.keySet().stream()
.min(Comparator.comparingInt(i -> Math.abs(i - positionInMs)))
.orElse(-1);
// this should never happen, because
// it indicates that "seekbarPreviewData" is empty which was already checked
if (closestIndexPosition == -1) {
return Optional.empty();
}
try {
// Get the bitmap for the position (executes the supplier)
return Optional.ofNullable(seekbarPreviewData.get(closestIndexPosition).get());
} catch (final Exception ex) {
// If there is an error, log it and return Optional.empty
Log.w(TAG, "Unable to get seekbar preview", ex);
return Optional.empty();
}
return Optional.ofNullable(closestFrame.get());
}
}

View File

@@ -154,6 +154,16 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
protected void initListeners() {
super.initListeners();
binding.screenRotationButton.setOnClickListener(makeOnClickListener(() -> {
// Only if it's not a vertical video or vertical video but in landscape with locked
// orientation a screen orientation can be changed automatically
if (!isVerticalVideo || (isLandscape() && globalScreenOrientationLocked(context))) {
player.getFragmentListener()
.ifPresent(PlayerServiceEventListener::onScreenRotationButtonClicked);
} else {
toggleFullscreen();
}
}));
binding.queueButton.setOnClickListener(v -> onQueueClicked());
binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked());
@@ -173,6 +183,14 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
settingsContentObserver);
binding.getRoot().addOnLayoutChangeListener(this);
binding.moreOptionsButton.setOnLongClickListener(v -> {
player.getFragmentListener()
.ifPresent(PlayerServiceEventListener::onMoreOptionsLongClicked);
hideControls(0, 0);
hideSystemUIIfNeeded();
return true;
});
}
@Override
@@ -846,23 +864,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
//////////////////////////////////////////////////////////////////////////*/
//region Click listeners
@Override
public void onClick(final View v) {
if (v.getId() == binding.screenRotationButton.getId()) {
// Only if it's not a vertical video or vertical video but in landscape with locked
// orientation a screen orientation can be changed automatically
if (!isVerticalVideo || (isLandscape() && globalScreenOrientationLocked(context))) {
player.getFragmentListener().ifPresent(
PlayerServiceEventListener::onScreenRotationButtonClicked);
} else {
toggleFullscreen();
}
}
// call it later since it calls manageControlsAfterOnClick at the end
super.onClick(v);
}
@Override
protected void onPlaybackSpeedClicked() {
final AppCompatActivity activity = getParentActivity().orElse(null);
@@ -875,18 +876,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh
.show(activity.getSupportFragmentManager(), null);
}
@Override
public boolean onLongClick(final View v) {
if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) {
player.getFragmentListener().ifPresent(
PlayerServiceEventListener::onMoreOptionsLongClicked);
hideControls(0, 0);
hideSystemUIIfNeeded();
return true;
}
return super.onLongClick(v);
}
@Override
public boolean onKeyDown(final int keyCode) {
if (keyCode == KeyEvent.KEYCODE_SPACE && isFullscreen) {

View File

@@ -84,11 +84,11 @@ import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.player.PlayerFastSeekOverlay;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
public abstract class VideoPlayerUi extends PlayerUi
implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener,
public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBarChangeListener,
PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
private static final String TAG = VideoPlayerUi.class.getSimpleName();
@@ -132,9 +132,11 @@ public abstract class VideoPlayerUi extends PlayerUi
private GestureDetector gestureDetector;
private BasePlayerGestureListener playerGestureListener;
@Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null;
@Nullable
private View.OnLayoutChangeListener onLayoutChangeListener = null;
@NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
@NonNull
private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder =
new SeekbarPreviewThumbnailHolder();
@@ -187,13 +189,13 @@ public abstract class VideoPlayerUi extends PlayerUi
abstract BasePlayerGestureListener buildGestureListener();
protected void initListeners() {
binding.qualityTextView.setOnClickListener(this);
binding.playbackSpeed.setOnClickListener(this);
binding.qualityTextView.setOnClickListener(makeOnClickListener(this::onQualityClicked));
binding.playbackSpeed.setOnClickListener(makeOnClickListener(this::onPlaybackSpeedClicked));
binding.playbackSeekBar.setOnSeekBarChangeListener(this);
binding.captionTextView.setOnClickListener(this);
binding.resizeTextView.setOnClickListener(this);
binding.playbackLiveSync.setOnClickListener(this);
binding.captionTextView.setOnClickListener(makeOnClickListener(this::onCaptionClicked));
binding.resizeTextView.setOnClickListener(makeOnClickListener(this::onResizeClicked));
binding.playbackLiveSync.setOnClickListener(makeOnClickListener(player::seekToDefault));
playerGestureListener = buildGestureListener();
gestureDetector = new GestureDetector(context, playerGestureListener);
@@ -202,20 +204,36 @@ public abstract class VideoPlayerUi extends PlayerUi
binding.repeatButton.setOnClickListener(v -> onRepeatClicked());
binding.shuffleButton.setOnClickListener(v -> onShuffleClicked());
binding.playPauseButton.setOnClickListener(this);
binding.playPreviousButton.setOnClickListener(this);
binding.playNextButton.setOnClickListener(this);
binding.playPauseButton.setOnClickListener(makeOnClickListener(player::playPause));
binding.playPreviousButton.setOnClickListener(makeOnClickListener(player::playPrevious));
binding.playNextButton.setOnClickListener(makeOnClickListener(player::playNext));
binding.moreOptionsButton.setOnClickListener(this);
binding.moreOptionsButton.setOnLongClickListener(this);
binding.share.setOnClickListener(this);
binding.share.setOnLongClickListener(this);
binding.fullScreenButton.setOnClickListener(this);
binding.screenRotationButton.setOnClickListener(this);
binding.playWithKodi.setOnClickListener(this);
binding.openInBrowser.setOnClickListener(this);
binding.playerCloseButton.setOnClickListener(this);
binding.switchMute.setOnClickListener(this);
binding.moreOptionsButton.setOnClickListener(
makeOnClickListener(this::onMoreOptionsClicked));
binding.share.setOnClickListener(makeOnClickListener(() -> {
final PlayQueueItem currentItem = player.getCurrentItem();
if (currentItem != null) {
ShareUtils.shareText(context, currentItem.getTitle(),
player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl());
}
}));
binding.share.setOnLongClickListener(v -> {
ShareUtils.copyToClipboard(context, player.getVideoUrlAtCurrentTime());
return true;
});
binding.fullScreenButton.setOnClickListener(makeOnClickListener(() -> {
player.setRecovery();
NavigationHelper.playOnMainPlayer(context,
Objects.requireNonNull(player.getPlayQueue()), true);
}));
binding.playWithKodi.setOnClickListener(makeOnClickListener(this::onPlayWithKodiClicked));
binding.openInBrowser.setOnClickListener(makeOnClickListener(this::onOpenInBrowserClicked));
binding.playerCloseButton.setOnClickListener(makeOnClickListener(() ->
// set package to this app's package to prevent the intent from being seen outside
context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)
.setPackage(App.PACKAGE_NAME))
));
binding.switchMute.setOnClickListener(makeOnClickListener(player::toggleMute));
ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> {
final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
@@ -229,11 +247,8 @@ public abstract class VideoPlayerUi extends PlayerUi
// player_overlays and fast_seek_overlay too. Without it they will be off-centered.
onLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
binding.playerOverlays.setPadding(
v.getPaddingLeft(),
v.getPaddingTop(),
v.getPaddingRight(),
v.getPaddingBottom());
binding.playerOverlays.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight(), v.getPaddingBottom());
// If we added padding to the fast seek overlay, too, it would not go under the
// system ui. Instead we apply negative margins equal to the window insets of
@@ -603,11 +618,6 @@ public abstract class VideoPlayerUi extends PlayerUi
player.changeState(STATE_PAUSED_SEEK);
}
player.saveWasPlaying();
if (player.isPlaying()) {
player.getExoPlayer().pause();
}
showControls(0);
animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION,
AnimationType.SCALE_AND_ALPHA);
@@ -622,7 +632,7 @@ public abstract class VideoPlayerUi extends PlayerUi
}
player.seekTo(seekBar.getProgress());
if (player.wasPlaying() || player.getExoPlayer().getDuration() == seekBar.getProgress()) {
if (player.getExoPlayer().getDuration() == seekBar.getProgress()) {
player.getExoPlayer().play();
}
@@ -636,9 +646,8 @@ public abstract class VideoPlayerUi extends PlayerUi
if (!player.isProgressLoopRunning()) {
player.startProgressLoop();
}
if (player.wasPlaying()) {
showControlsThenHide();
}
showControlsThenHide();
}
//endregion
@@ -1173,8 +1182,6 @@ public abstract class VideoPlayerUi extends PlayerUi
binding.qualityTextView.setText(MediaFormat.getNameById(videoStream.getFormatId())
+ " " + videoStream.getResolution());
}
player.saveWasPlaying();
}
/**
@@ -1326,86 +1333,39 @@ public abstract class VideoPlayerUi extends PlayerUi
//////////////////////////////////////////////////////////////////////////*/
//region Click listeners
@Override
public void onClick(final View v) {
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (v.getId() == binding.resizeTextView.getId()) {
onResizeClicked();
} else if (v.getId() == binding.captionTextView.getId()) {
onCaptionClicked();
} else if (v.getId() == binding.playbackLiveSync.getId()) {
player.seekToDefault();
} else if (v.getId() == binding.playPauseButton.getId()) {
player.playPause();
} else if (v.getId() == binding.playPreviousButton.getId()) {
player.playPrevious();
} else if (v.getId() == binding.playNextButton.getId()) {
player.playNext();
} else if (v.getId() == binding.moreOptionsButton.getId()) {
onMoreOptionsClicked();
} else if (v.getId() == binding.share.getId()) {
final PlayQueueItem currentItem = player.getCurrentItem();
if (currentItem != null) {
ShareUtils.shareText(context, currentItem.getTitle(),
player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl());
}
} else if (v.getId() == binding.playWithKodi.getId()) {
onPlayWithKodiClicked();
} else if (v.getId() == binding.openInBrowser.getId()) {
onOpenInBrowserClicked();
} else if (v.getId() == binding.fullScreenButton.getId()) {
player.setRecovery();
NavigationHelper.playOnMainPlayer(context, player.getPlayQueue(), true);
return;
} else if (v.getId() == binding.switchMute.getId()) {
player.toggleMute();
} else if (v.getId() == binding.playerCloseButton.getId()) {
// set package to this app's package to prevent the intent from being seen outside
context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)
.setPackage(App.PACKAGE_NAME));
} else if (v.getId() == binding.playbackSpeed.getId()) {
onPlaybackSpeedClicked();
} else if (v.getId() == binding.qualityTextView.getId()) {
onQualityClicked();
}
manageControlsAfterOnClick(v);
}
/**
* Manages the controls after a click occurred on the player UI.
* @param v The view that was clicked
* Create on-click listener which manages the player controls after the view on-click action.
*
* @param runnable The action to be executed.
* @return The view click listener.
*/
public void manageControlsAfterOnClick(@NonNull final View v) {
if (player.getCurrentState() == STATE_COMPLETED) {
return;
}
protected View.OnClickListener makeOnClickListener(@NonNull final Runnable runnable) {
return v -> {
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, DEFAULT_CONTROLS_DURATION);
animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION,
AnimationType.ALPHA, 0, () -> {
if (player.getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible) {
if (v.getId() == binding.playPauseButton.getId()
// Hide controls in fullscreen immediately
|| (v.getId() == binding.screenRotationButton.getId()
&& isFullscreen())) {
hideControls(0, 0);
} else {
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
runnable.run();
// Manages the player controls after handling the view click.
if (player.getCurrentState() == STATE_COMPLETED) {
return;
}
controlsVisibilityHandler.removeCallbacksAndMessages(null);
showHideShadow(true, DEFAULT_CONTROLS_DURATION);
animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION,
AnimationType.ALPHA, 0, () -> {
if (player.getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible) {
if (v == binding.playPauseButton
// Hide controls in fullscreen immediately
|| (v == binding.screenRotationButton && isFullscreen())) {
hideControls(0, 0);
} else {
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
}
}
}
});
}
@Override
public boolean onLongClick(final View v) {
if (v.getId() == binding.share.getId()) {
ShareUtils.copyToClipboard(context, player.getVideoUrlAtCurrentTime());
}
return true;
});
};
}
public boolean onKeyDown(final int keyCode) {

View File

@@ -44,7 +44,13 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
return false;
});
} else {
removePreference(nightThemeKey);
// disable the night theme selection
final Preference preference = findPreference(nightThemeKey);
if (preference != null) {
preference.setEnabled(false);
preference.setSummary(getString(R.string.night_theme_available,
getString(R.string.auto_device_theme_title)));
}
}
}
@@ -61,13 +67,6 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
return super.onPreferenceTreeClick(preference);
}
private void removePreference(final String preferenceKey) {
final Preference preference = findPreference(preferenceKey);
if (preference != null) {
getPreferenceScreen().removePreference(preference);
}
}
private void applyThemeChange(final String beginningThemeKey,
final String themeKey,
final Object newValue) {

View File

@@ -21,6 +21,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.NewPipeSettings;
public final class PermissionHelper {
public static final int POST_NOTIFICATIONS_REQUEST_CODE = 779;
public static final int DOWNLOAD_DIALOG_REQUEST_CODE = 778;
public static final int DOWNLOADS_REQUEST_CODE = 777;
@@ -71,8 +72,7 @@ public final class PermissionHelper {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
requestCode);
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
// PERMISSION_WRITE_STORAGE is an
// app-defined int constant. The callback method gets the
@@ -83,6 +83,18 @@ public final class PermissionHelper {
return true;
}
public static boolean checkPostNotificationsPermission(final Activity activity,
final int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
&& ContextCompat.checkSelfPermission(activity,
Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity,
new String[] {Manifest.permission.POST_NOTIFICATIONS}, requestCode);
return false;
}
return true;
}
/**
* In order to be able to draw over other apps,

View File

@@ -1,7 +1,6 @@
package org.schabi.newpipe.util;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -11,6 +10,8 @@ import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SparseArrayCompat;
import org.schabi.newpipe.DownloaderImpl;
import org.schabi.newpipe.R;
@@ -39,10 +40,10 @@ import us.shandian.giga.util.Utility;
* @param <U> the secondary stream type's class extending {@link Stream}
*/
public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
private final Context context;
@NonNull
private final StreamSizeWrapper<T> streamsWrapper;
private final SparseArray<SecondaryStreamHelper<U>> secondaryStreams;
@NonNull
private final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams;
/**
* Indicates that at least one of the primary streams is an instance of {@link VideoStream},
@@ -51,9 +52,10 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
*/
private final boolean hasAnyVideoOnlyStreamWithNoSecondaryStream;
public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper,
final SparseArray<SecondaryStreamHelper<U>> secondaryStreams) {
this.context = context;
public StreamItemAdapter(
@NonNull final StreamSizeWrapper<T> streamsWrapper,
@NonNull final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams
) {
this.streamsWrapper = streamsWrapper;
this.secondaryStreams = secondaryStreams;
@@ -61,15 +63,15 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
checkHasAnyVideoOnlyStreamWithNoSecondaryStream();
}
public StreamItemAdapter(final Context context, final StreamSizeWrapper<T> streamsWrapper) {
this(context, streamsWrapper, null);
public StreamItemAdapter(final StreamSizeWrapper<T> streamsWrapper) {
this(streamsWrapper, new SparseArrayCompat<>(0));
}
public List<T> getAll() {
return streamsWrapper.getStreamsList();
}
public SparseArray<SecondaryStreamHelper<U>> getAllSecondary() {
public SparseArrayCompat<SecondaryStreamHelper<U>> getAllSecondary() {
return secondaryStreams;
}
@@ -106,6 +108,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
final View view,
final ViewGroup parent,
final boolean isDropdownItem) {
final var context = parent.getContext();
View convertView = view;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(
@@ -129,7 +132,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
if (hasAnyVideoOnlyStreamWithNoSecondaryStream) {
if (videoStream.isVideoOnly()) {
woSoundIconVisibility = hasSecondaryStream(position)
woSoundIconVisibility = secondaryStreams.get(position) != null
// It has a secondary stream associated with it, so check if it's a
// dropdown view so it doesn't look out of place (missing margin)
// compared to those that don't.
@@ -163,8 +166,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
}
if (streamsWrapper.getSizeInBytes(position) > 0) {
final SecondaryStreamHelper<U> secondary = secondaryStreams == null ? null
: secondaryStreams.get(position);
final var secondary = secondaryStreams.get(position);
if (secondary != null) {
final long size = secondary.getSizeInBytes()
+ streamsWrapper.getSizeInBytes(position);
@@ -196,14 +198,6 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
return convertView;
}
/**
* @param position which primary stream to check.
* @return whether the primary stream at position has a secondary stream associated with it.
*/
private boolean hasSecondaryStream(final int position) {
return secondaryStreams != null && secondaryStreams.get(position) != null;
}
/**
* @return if there are any video-only streams with no secondary stream associated with them.
* @see #hasAnyVideoOnlyStreamWithNoSecondaryStream
@@ -213,7 +207,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
final T stream = streamsWrapper.getStreamsList().get(i);
if (stream instanceof VideoStream) {
final boolean videoOnly = ((VideoStream) stream).isVideoOnly();
if (videoOnly && !hasSecondaryStream(i)) {
if (videoOnly && secondaryStreams.get(i) == null) {
return true;
}
}
@@ -228,16 +222,15 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
* @param <T> the stream type's class extending {@link Stream}
*/
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(
Collections.emptyList(), null);
private static final StreamSizeWrapper<Stream> EMPTY =
new StreamSizeWrapper<>(Collections.emptyList(), null);
private final List<T> streamsList;
private final long[] streamSizes;
private final String unknownSize;
public StreamSizeWrapper(final List<T> sL, final Context context) {
this.streamsList = sL != null
? sL
: Collections.emptyList();
public StreamSizeWrapper(@NonNull final List<T> streamList,
@Nullable final Context context) {
this.streamsList = streamList;
this.streamSizes = new long[streamsList.size()];
this.unknownSize = context == null
? "--.-" : context.getString(R.string.unknown_content);
@@ -297,10 +290,6 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
return formatSize(getSizeInBytes(streamIndex));
}
public String getFormattedSize(final T stream) {
return formatSize(getSizeInBytes(stream));
}
private String formatSize(final long size) {
if (size > -1) {
return Utility.formatBytes(size);
@@ -308,10 +297,6 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
return unknownSize;
}
public void setSize(final int streamIndex, final long sizeInBytes) {
streamSizes[streamIndex] = sizeInBytes;
}
public void setSize(final T stream, final long sizeInBytes) {
streamSizes[streamsList.indexOf(stream)] = sizeInBytes;
}

View File

@@ -1,5 +1,8 @@
package us.shandian.giga.service;
import static org.schabi.newpipe.BuildConfig.APPLICATION_ID;
import static org.schabi.newpipe.BuildConfig.DEBUG;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -22,12 +25,12 @@ import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.collection.SparseArrayCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Builder;
import androidx.core.app.ServiceCompat;
@@ -37,24 +40,21 @@ import androidx.preference.PreferenceManager;
import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.player.helper.LockManager;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.MissionRecoveryInfo;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.PendingIntentCompat;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManager.NetworkState;
import static org.schabi.newpipe.BuildConfig.APPLICATION_ID;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service {
private static final String TAG = "DownloadManagerService";
@@ -95,7 +95,7 @@ public class DownloadManagerService extends Service {
private Builder downloadDoneNotification = null;
private StringBuilder downloadDoneList = null;
private final ArrayList<Callback> mEchoObservers = new ArrayList<>(1);
private final List<Callback> mEchoObservers = new ArrayList<>(1);
private ConnectivityManager mConnectivityManager;
private ConnectivityManager.NetworkCallback mNetworkStateListenerL = null;
@@ -108,7 +108,8 @@ public class DownloadManagerService extends Service {
private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1;
private Builder downloadFailedNotification = null;
private final SparseArray<DownloadMission> mFailedDownloads = new SparseArray<>(5);
private final SparseArrayCompat<DownloadMission> mFailedDownloads =
new SparseArrayCompat<>(5);
private Bitmap icLauncher;
private Bitmap icDownloadDone;
@@ -277,7 +278,7 @@ public class DownloadManagerService extends Service {
}
if (msg.what != MESSAGE_ERROR)
mFailedDownloads.delete(mFailedDownloads.indexOfValue(mission));
mFailedDownloads.remove(mFailedDownloads.indexOfValue(mission));
for (Callback observer : mEchoObservers)
observer.handleMessage(msg);
@@ -461,7 +462,7 @@ public class DownloadManagerService extends Service {
}
public void notifyFailedDownload(DownloadMission mission) {
if (!mDownloadNotificationEnable || mFailedDownloads.indexOfValue(mission) >= 0) return;
if (!mDownloadNotificationEnable || mFailedDownloads.containsValue(mission)) return;
int id = downloadFailedNotificationID++;
mFailedDownloads.put(id, mission);