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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user