Skip to content

Commit

Permalink
Merge pull request #8895 from Isira-Seneviratne/SparseArrayCompat
Browse files Browse the repository at this point in the history
Use SparseArrayCompat.
  • Loading branch information
Stypox authored Dec 28, 2022
2 parents fcac53c + d661700 commit 231e677
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.schabi.newpipe.util

import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.Spinner
import androidx.collection.SparseArrayCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
Expand Down Expand Up @@ -39,9 +39,7 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_noSecondaryStream() {
val adapter = StreamItemAdapter<VideoStream, AudioStream>(
context,
getVideoStreams(true, true, true, true),
null
getVideoStreams(true, true, true, true)
)

spinner.adapter = adapter
Expand All @@ -54,7 +52,6 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_hasSecondaryStream() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(false, true, false, true),
getAudioStreams(false, true, false, true)
)
Expand All @@ -69,7 +66,6 @@ class StreamItemAdapterTest {
@Test
fun videoStreams_Mixed() {
val adapter = StreamItemAdapter(
context,
getVideoStreams(true, true, true, true, true, false, true, true),
getAudioStreams(false, true, false, false, false, true, true, true)
)
Expand All @@ -88,7 +84,6 @@ class StreamItemAdapterTest {
@Test
fun subtitleStreams_noIcon() {
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map {
SubtitlesStream.Builder()
Expand All @@ -99,8 +94,7 @@ class StreamItemAdapterTest {
.build()
},
context
),
null
)
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
Expand All @@ -111,7 +105,6 @@ class StreamItemAdapterTest {
@Test
fun audioStreams_noIcon() {
val adapter = StreamItemAdapter<AudioStream, Stream>(
context,
StreamItemAdapter.StreamSizeWrapper(
(0 until 5).map {
AudioStream.Builder()
Expand All @@ -122,8 +115,7 @@ class StreamItemAdapterTest {
.build()
},
context
),
null
)
)
spinner.adapter = adapter
for (i in 0 until spinner.count) {
Expand Down Expand Up @@ -200,7 +192,7 @@ class StreamItemAdapterTest {
* Helper function that builds a secondary stream list.
*/
private fun <T : Stream> getSecondaryStreamsFromList(streams: List<T?>) =
SparseArray<SecondaryStreamHelper<T>?>(streams.size).apply {
SparseArrayCompat<SecondaryStreamHelper<T>?>(streams.size).apply {
streams.forEachIndexed { index, stream ->
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
SecondaryStreamHelper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
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;
Expand All @@ -36,6 +35,7 @@
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;
Expand Down Expand Up @@ -211,8 +211,7 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
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++) {
Expand All @@ -236,10 +235,9 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
}
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;
Expand Down Expand Up @@ -70,9 +71,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -426,8 +425,6 @@ public void onCreateOptionsMenu(@NonNull final Menu menu,
supportActionBar.setDisplayHomeAsUpEnabled(true);
}

menuItemToFilterName = new HashMap<>();

int itemId = 0;
boolean isFirstItem = true;
final Context c = getContext();
Expand Down Expand Up @@ -468,11 +465,8 @@ public void onCreateOptionsMenu(@NonNull final Menu menu,

@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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
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;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SparseArrayCompat;

import com.google.common.base.Stopwatch;

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;
Expand All @@ -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;
Expand All @@ -63,13 +59,12 @@ public synchronized void resetFrom(
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");
Expand All @@ -94,10 +89,8 @@ private void resetFromAsync(
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()
Expand All @@ -111,17 +104,14 @@ private Frameset getFrameSetForType(
}
}

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()) {
Expand All @@ -130,11 +120,11 @@ private void generateDataFrom(

// 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;
Expand All @@ -161,15 +151,17 @@ private void generateDataFrom(
// 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;
}
}

if (sw != null) {
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop().toString());
Log.d(TAG, "Generation of seekbarPreviewData took " + sw.stop());
}
}

Expand All @@ -189,17 +181,14 @@ private Bitmap getBitMapFrom(final String url) {
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;
}
}
Expand All @@ -208,32 +197,20 @@ private boolean isRequestIdentifierCurrent(final UUID requestIdentifier) {
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 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();
// 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;
}
}
}

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());
}
}
Loading

0 comments on commit 231e677

Please sign in to comment.