mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-22 11:02:35 +01:00
Merge pull request #10165 from TeamNewPipe/fix/media-format
Fix downloads of streams with missing MediaFormat
This commit is contained in:
commit
725c18eada
@ -12,15 +12,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.MediaFormat
|
import org.schabi.newpipe.extractor.MediaFormat
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream
|
import org.schabi.newpipe.extractor.stream.AudioStream
|
||||||
import org.schabi.newpipe.extractor.stream.Stream
|
import org.schabi.newpipe.extractor.stream.Stream
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream
|
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||||
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@ -84,7 +90,7 @@ class StreamItemAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun subtitleStreams_noIcon() {
|
fun subtitleStreams_noIcon() {
|
||||||
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
|
val adapter = StreamItemAdapter<SubtitlesStream, Stream>(
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
(0 until 5).map {
|
(0 until 5).map {
|
||||||
SubtitlesStream.Builder()
|
SubtitlesStream.Builder()
|
||||||
.setContent("https://example.com", true)
|
.setContent("https://example.com", true)
|
||||||
@ -105,7 +111,7 @@ class StreamItemAdapterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun audioStreams_noIcon() {
|
fun audioStreams_noIcon() {
|
||||||
val adapter = StreamItemAdapter<AudioStream, Stream>(
|
val adapter = StreamItemAdapter<AudioStream, Stream>(
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
(0 until 5).map {
|
(0 until 5).map {
|
||||||
AudioStream.Builder()
|
AudioStream.Builder()
|
||||||
.setId(Stream.ID_UNKNOWN)
|
.setId(Stream.ID_UNKNOWN)
|
||||||
@ -123,12 +129,109 @@ class StreamItemAdapterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromFileTypeHeaders() {
|
||||||
|
val streams = getIncompleteAudioStreams(5)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromFileTypeHeaders(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("file-type", "mp0"))), 1)
|
||||||
|
|
||||||
|
helper.assertValidResponse(getResponse(mapOf(Pair("x-amz-meta-file-type", "aiff"))), 2, MediaFormat.AIFF)
|
||||||
|
helper.assertValidResponse(getResponse(mapOf(Pair("file-type", "mp3"))), 3, MediaFormat.MP3)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromContentDispositionHeader() {
|
||||||
|
val streams = getIncompleteAudioStreams(11)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromContentDispositionHeader(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "mp3"))), 0)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.png\""))), 1
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"data.csv\""))), 2
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; filename=\"data.csv\""))), 3
|
||||||
|
)
|
||||||
|
helper.assertInvalidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"fieldName\"; filename*=\"filename.jpg\""))), 4
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "filename=\"train.ogg\""))),
|
||||||
|
5, MediaFormat.OGG
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "some-form-data; filename=\"audio.flac\""))),
|
||||||
|
6, MediaFormat.FLAC
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.aiff\"; filename=\"audio.aiff\""))),
|
||||||
|
7, MediaFormat.AIFF
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"alien?\"; filename*=UTF-8''%CE%B1%CE%BB%CE%B9%CF%B5%CE%BD.m4a"))),
|
||||||
|
8, MediaFormat.M4A
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=UTF-8''alien.opus"))),
|
||||||
|
9, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Disposition", "form-data; name=\"audio.mp3\"; filename=\"audio.opus\"; filename*=\"UTF-8''alien.opus\""))),
|
||||||
|
10, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveMediaFormatFromContentTypeHeader() {
|
||||||
|
val streams = getIncompleteAudioStreams(12)
|
||||||
|
val wrapper = StreamInfoWrapper(streams, context)
|
||||||
|
val retrieveMediaFormat = { stream: AudioStream, response: Response ->
|
||||||
|
StreamInfoWrapper.retrieveMediaFormatFromContentTypeHeader(stream, wrapper, response)
|
||||||
|
}
|
||||||
|
val helper = AssertionHelper(streams, wrapper, retrieveMediaFormat)
|
||||||
|
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("content-length", "984501"))), 0)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/xyz"))), 1)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 2)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "mp3"))), 3)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/mpeg"))), 4)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "audio/aif"))), 5)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf(Pair("Content-Type", "whatever"))), 6)
|
||||||
|
helper.assertInvalidResponse(getResponse(mapOf()), 7)
|
||||||
|
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/flac"))), 8, MediaFormat.FLAC
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/wav"))), 9, MediaFormat.WAV
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/opus"))), 10, MediaFormat.OPUS
|
||||||
|
)
|
||||||
|
helper.assertValidResponse(
|
||||||
|
getResponse(mapOf(Pair("Content-Type", "audio/aiff"))), 11, MediaFormat.AIFF
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a list of video streams, in which their video only property mirrors the provided
|
* @return a list of video streams, in which their video only property mirrors the provided
|
||||||
* [videoOnly] vararg.
|
* [videoOnly] vararg.
|
||||||
*/
|
*/
|
||||||
private fun getVideoStreams(vararg videoOnly: Boolean) =
|
private fun getVideoStreams(vararg videoOnly: Boolean) =
|
||||||
StreamItemAdapter.StreamSizeWrapper(
|
StreamItemAdapter.StreamInfoWrapper(
|
||||||
videoOnly.map {
|
videoOnly.map {
|
||||||
VideoStream.Builder()
|
VideoStream.Builder()
|
||||||
.setId(Stream.ID_UNKNOWN)
|
.setId(Stream.ID_UNKNOWN)
|
||||||
@ -161,6 +264,19 @@ class StreamItemAdapterTest {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getIncompleteAudioStreams(size: Int): List<AudioStream> {
|
||||||
|
val list = ArrayList<AudioStream>(size)
|
||||||
|
for (i in 1..size) {
|
||||||
|
list.add(
|
||||||
|
AudioStream.Builder()
|
||||||
|
.setId(Stream.ID_UNKNOWN)
|
||||||
|
.setContent("https://example.com/$i", true)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the item at [position] in the [spinner] has the correct icon visibility when
|
* Checks whether the item at [position] in the [spinner] has the correct icon visibility when
|
||||||
* it is shown in normal mode (selected) and in dropdown mode (user is choosing one of a list).
|
* it is shown in normal mode (selected) and in dropdown mode (user is choosing one of a list).
|
||||||
@ -196,11 +312,56 @@ class StreamItemAdapterTest {
|
|||||||
streams.forEachIndexed { index, stream ->
|
streams.forEachIndexed { index, stream ->
|
||||||
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
|
val secondaryStreamHelper: SecondaryStreamHelper<T>? = stream?.let {
|
||||||
SecondaryStreamHelper(
|
SecondaryStreamHelper(
|
||||||
StreamItemAdapter.StreamSizeWrapper(streams, context),
|
StreamItemAdapter.StreamInfoWrapper(streams, context),
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
put(index, secondaryStreamHelper)
|
put(index, secondaryStreamHelper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getResponse(headers: Map<String, String>): Response {
|
||||||
|
val listHeaders = HashMap<String, List<String>>()
|
||||||
|
headers.forEach { entry ->
|
||||||
|
listHeaders[entry.key] = listOf(entry.value)
|
||||||
|
}
|
||||||
|
return Response(200, null, listHeaders, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for assertion related to extractions of [MediaFormat]s.
|
||||||
|
*/
|
||||||
|
class AssertionHelper<T : Stream>(
|
||||||
|
private val streams: List<T>,
|
||||||
|
private val wrapper: StreamInfoWrapper<T>,
|
||||||
|
private val retrieveMediaFormat: (stream: T, response: Response) -> Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that an invalid response does not result in wrongly extracted [MediaFormat].
|
||||||
|
*/
|
||||||
|
fun assertInvalidResponse(
|
||||||
|
response: Response,
|
||||||
|
index: Int
|
||||||
|
) {
|
||||||
|
assertFalse(
|
||||||
|
"invalid header returns valid value", retrieveMediaFormat(streams[index], response)
|
||||||
|
)
|
||||||
|
assertNull("Media format extracted although stated otherwise", wrapper.getFormat(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that a valid response results in correctly extracted and handled [MediaFormat].
|
||||||
|
*/
|
||||||
|
fun assertValidResponse(
|
||||||
|
response: Response,
|
||||||
|
index: Int,
|
||||||
|
format: MediaFormat
|
||||||
|
) {
|
||||||
|
assertTrue(
|
||||||
|
"header was not recognized", retrieveMediaFormat(streams[index], response)
|
||||||
|
)
|
||||||
|
assertEquals("Wrong media format extracted", format, wrapper.getFormat(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ import org.schabi.newpipe.util.PermissionHelper;
|
|||||||
import org.schabi.newpipe.util.SecondaryStreamHelper;
|
import org.schabi.newpipe.util.SecondaryStreamHelper;
|
||||||
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
|
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
|
||||||
import org.schabi.newpipe.util.AudioTrackAdapter;
|
import org.schabi.newpipe.util.AudioTrackAdapter;
|
||||||
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
|
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
@ -97,9 +97,9 @@ public class DownloadDialog extends DialogFragment
|
|||||||
@State
|
@State
|
||||||
StreamInfo currentInfo;
|
StreamInfo currentInfo;
|
||||||
@State
|
@State
|
||||||
StreamSizeWrapper<VideoStream> wrappedVideoStreams;
|
StreamInfoWrapper<VideoStream> wrappedVideoStreams;
|
||||||
@State
|
@State
|
||||||
StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams;
|
StreamInfoWrapper<SubtitlesStream> wrappedSubtitleStreams;
|
||||||
@State
|
@State
|
||||||
AudioTracksWrapper wrappedAudioTracks;
|
AudioTracksWrapper wrappedAudioTracks;
|
||||||
@State
|
@State
|
||||||
@ -187,8 +187,8 @@ public class DownloadDialog extends DialogFragment
|
|||||||
wrappedAudioTracks.size() > 1
|
wrappedAudioTracks.size() > 1
|
||||||
);
|
);
|
||||||
|
|
||||||
this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context);
|
this.wrappedVideoStreams = new StreamInfoWrapper<>(videoStreams, context);
|
||||||
this.wrappedSubtitleStreams = new StreamSizeWrapper<>(
|
this.wrappedSubtitleStreams = new StreamInfoWrapper<>(
|
||||||
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);
|
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);
|
||||||
|
|
||||||
this.selectedVideoIndex = ListHelper.getDefaultResolutionIndex(context, videoStreams);
|
this.selectedVideoIndex = ListHelper.getDefaultResolutionIndex(context, videoStreams);
|
||||||
@ -258,10 +258,10 @@ public class DownloadDialog extends DialogFragment
|
|||||||
* Update the displayed video streams based on the selected audio track.
|
* Update the displayed video streams based on the selected audio track.
|
||||||
*/
|
*/
|
||||||
private void updateSecondaryStreams() {
|
private void updateSecondaryStreams() {
|
||||||
final StreamSizeWrapper<AudioStream> audioStreams = getWrappedAudioStreams();
|
final StreamInfoWrapper<AudioStream> audioStreams = getWrappedAudioStreams();
|
||||||
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
|
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
|
||||||
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
|
||||||
wrappedVideoStreams.resetSizes();
|
wrappedVideoStreams.resetInfo();
|
||||||
|
|
||||||
for (int i = 0; i < videoStreams.size(); i++) {
|
for (int i = 0; i < videoStreams.size(); i++) {
|
||||||
if (!videoStreams.get(i).isVideoOnly()) {
|
if (!videoStreams.get(i).isVideoOnly()) {
|
||||||
@ -396,7 +396,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
|
|
||||||
private void fetchStreamsSize() {
|
private void fetchStreamsSize() {
|
||||||
disposables.clear();
|
disposables.clear();
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams)
|
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedVideoStreams)
|
||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||||
== R.id.video_button) {
|
== R.id.video_button) {
|
||||||
@ -406,7 +406,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||||
"Downloading video stream size",
|
"Downloading video stream size",
|
||||||
currentInfo.getServiceId()))));
|
currentInfo.getServiceId()))));
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(getWrappedAudioStreams())
|
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(getWrappedAudioStreams())
|
||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||||
== R.id.audio_button) {
|
== R.id.audio_button) {
|
||||||
@ -416,7 +416,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
|
||||||
"Downloading audio stream size",
|
"Downloading audio stream size",
|
||||||
currentInfo.getServiceId()))));
|
currentInfo.getServiceId()))));
|
||||||
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams)
|
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedSubtitleStreams)
|
||||||
.subscribe(result -> {
|
.subscribe(result -> {
|
||||||
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
|
||||||
== R.id.subtitle_button) {
|
== R.id.subtitle_button) {
|
||||||
@ -724,9 +724,9 @@ public class DownloadDialog extends DialogFragment
|
|||||||
dialogBinding.subtitleButton.setEnabled(enabled);
|
dialogBinding.subtitleButton.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamSizeWrapper<AudioStream> getWrappedAudioStreams() {
|
private StreamInfoWrapper<AudioStream> getWrappedAudioStreams() {
|
||||||
if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) {
|
if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) {
|
||||||
return StreamSizeWrapper.empty();
|
return StreamInfoWrapper.empty();
|
||||||
}
|
}
|
||||||
return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex);
|
return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex);
|
||||||
}
|
}
|
||||||
@ -766,7 +766,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showFailedDialog(@StringRes final int msg) {
|
private void showFailedDialog(@StringRes final int msg) {
|
||||||
assureCorrectAppLanguage(getContext());
|
assureCorrectAppLanguage(requireContext());
|
||||||
new AlertDialog.Builder(context)
|
new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.general_error)
|
.setTitle(R.string.general_error)
|
||||||
.setMessage(msg)
|
.setMessage(msg)
|
||||||
@ -799,7 +799,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
filenameTmp += "opus";
|
filenameTmp += "opus";
|
||||||
} else if (format != null) {
|
} else if (format != null) {
|
||||||
mimeTmp = format.mimeType;
|
mimeTmp = format.mimeType;
|
||||||
filenameTmp += format.suffix;
|
filenameTmp += format.getSuffix();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.video_button:
|
case R.id.video_button:
|
||||||
@ -808,7 +808,7 @@ public class DownloadDialog extends DialogFragment
|
|||||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
mimeTmp = format.mimeType;
|
mimeTmp = format.mimeType;
|
||||||
filenameTmp += format.suffix;
|
filenameTmp += format.getSuffix();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.subtitle_button:
|
case R.id.subtitle_button:
|
||||||
@ -820,9 +820,9 @@ public class DownloadDialog extends DialogFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (format == MediaFormat.TTML) {
|
if (format == MediaFormat.TTML) {
|
||||||
filenameTmp += MediaFormat.SRT.suffix;
|
filenameTmp += MediaFormat.SRT.getSuffix();
|
||||||
} else if (format != null) {
|
} else if (format != null) {
|
||||||
filenameTmp += format.suffix;
|
filenameTmp += format.getSuffix();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -13,7 +13,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -75,15 +75,15 @@ public class AudioTrackAdapter extends BaseAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class AudioTracksWrapper implements Serializable {
|
public static class AudioTracksWrapper implements Serializable {
|
||||||
private final List<StreamSizeWrapper<AudioStream>> tracksList;
|
private final List<StreamInfoWrapper<AudioStream>> tracksList;
|
||||||
|
|
||||||
public AudioTracksWrapper(@NonNull final List<List<AudioStream>> groupedAudioStreams,
|
public AudioTracksWrapper(@NonNull final List<List<AudioStream>> groupedAudioStreams,
|
||||||
@Nullable final Context context) {
|
@Nullable final Context context) {
|
||||||
this.tracksList = groupedAudioStreams.stream().map(streams ->
|
this.tracksList = groupedAudioStreams.stream().map(streams ->
|
||||||
new StreamSizeWrapper<>(streams, context)).collect(Collectors.toList());
|
new StreamInfoWrapper<>(streams, context)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<StreamSizeWrapper<AudioStream>> getTracksList() {
|
public List<StreamInfoWrapper<AudioStream>> getTracksList() {
|
||||||
return tracksList;
|
return tracksList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,15 +7,15 @@ import org.schabi.newpipe.extractor.MediaFormat;
|
|||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamInfoWrapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SecondaryStreamHelper<T extends Stream> {
|
public class SecondaryStreamHelper<T extends Stream> {
|
||||||
private final int position;
|
private final int position;
|
||||||
private final StreamSizeWrapper<T> streams;
|
private final StreamInfoWrapper<T> streams;
|
||||||
|
|
||||||
public SecondaryStreamHelper(@NonNull final StreamSizeWrapper<T> streams,
|
public SecondaryStreamHelper(@NonNull final StreamInfoWrapper<T> streams,
|
||||||
final T selectedStream) {
|
final T selectedStream) {
|
||||||
this.streams = streams;
|
this.streams = streams;
|
||||||
this.position = streams.getStreamsList().indexOf(selectedStream);
|
this.position = streams.getStreamsList().indexOf(selectedStream);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.schabi.newpipe.util;
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -11,21 +13,25 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.collection.SparseArrayCompat;
|
import androidx.collection.SparseArrayCompat;
|
||||||
|
|
||||||
import org.schabi.newpipe.DownloaderImpl;
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.MediaFormat;
|
import org.schabi.newpipe.extractor.MediaFormat;
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
|
import org.schabi.newpipe.extractor.utils.Utils;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.core.Single;
|
import io.reactivex.rxjava3.core.Single;
|
||||||
@ -41,7 +47,7 @@ import us.shandian.giga.util.Utility;
|
|||||||
*/
|
*/
|
||||||
public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
|
public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseAdapter {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final StreamSizeWrapper<T> streamsWrapper;
|
private final StreamInfoWrapper<T> streamsWrapper;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams;
|
private final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams;
|
||||||
|
|
||||||
@ -53,7 +59,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
private final boolean hasAnyVideoOnlyStreamWithNoSecondaryStream;
|
private final boolean hasAnyVideoOnlyStreamWithNoSecondaryStream;
|
||||||
|
|
||||||
public StreamItemAdapter(
|
public StreamItemAdapter(
|
||||||
@NonNull final StreamSizeWrapper<T> streamsWrapper,
|
@NonNull final StreamInfoWrapper<T> streamsWrapper,
|
||||||
@NonNull final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams
|
@NonNull final SparseArrayCompat<SecondaryStreamHelper<U>> secondaryStreams
|
||||||
) {
|
) {
|
||||||
this.streamsWrapper = streamsWrapper;
|
this.streamsWrapper = streamsWrapper;
|
||||||
@ -63,7 +69,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
checkHasAnyVideoOnlyStreamWithNoSecondaryStream();
|
checkHasAnyVideoOnlyStreamWithNoSecondaryStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamItemAdapter(final StreamSizeWrapper<T> streamsWrapper) {
|
public StreamItemAdapter(final StreamInfoWrapper<T> streamsWrapper) {
|
||||||
this(streamsWrapper, new SparseArrayCompat<>(0));
|
this(streamsWrapper, new SparseArrayCompat<>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +127,7 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
final TextView sizeView = convertView.findViewById(R.id.stream_size);
|
final TextView sizeView = convertView.findViewById(R.id.stream_size);
|
||||||
|
|
||||||
final T stream = getItem(position);
|
final T stream = getItem(position);
|
||||||
final MediaFormat mediaFormat = stream.getFormat();
|
final MediaFormat mediaFormat = streamsWrapper.getFormat(position);
|
||||||
|
|
||||||
int woSoundIconVisibility = View.GONE;
|
int woSoundIconVisibility = View.GONE;
|
||||||
String qualityString;
|
String qualityString;
|
||||||
@ -147,8 +153,6 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
final AudioStream audioStream = ((AudioStream) stream);
|
final AudioStream audioStream = ((AudioStream) stream);
|
||||||
if (audioStream.getAverageBitrate() > 0) {
|
if (audioStream.getAverageBitrate() > 0) {
|
||||||
qualityString = audioStream.getAverageBitrate() + "kbps";
|
qualityString = audioStream.getAverageBitrate() + "kbps";
|
||||||
} else if (mediaFormat != null) {
|
|
||||||
qualityString = mediaFormat.getName();
|
|
||||||
} else {
|
} else {
|
||||||
qualityString = context.getString(R.string.unknown_quality);
|
qualityString = context.getString(R.string.unknown_quality);
|
||||||
}
|
}
|
||||||
@ -221,46 +225,58 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
*
|
*
|
||||||
* @param <T> the stream type's class extending {@link Stream}
|
* @param <T> the stream type's class extending {@link Stream}
|
||||||
*/
|
*/
|
||||||
public static class StreamSizeWrapper<T extends Stream> implements Serializable {
|
public static class StreamInfoWrapper<T extends Stream> implements Serializable {
|
||||||
private static final StreamSizeWrapper<Stream> EMPTY =
|
private static final StreamInfoWrapper<Stream> EMPTY =
|
||||||
new StreamSizeWrapper<>(Collections.emptyList(), null);
|
new StreamInfoWrapper<>(Collections.emptyList(), null);
|
||||||
private static final int SIZE_UNSET = -2;
|
private static final int SIZE_UNSET = -2;
|
||||||
|
|
||||||
private final List<T> streamsList;
|
private final List<T> streamsList;
|
||||||
private final long[] streamSizes;
|
private final long[] streamSizes;
|
||||||
|
private final MediaFormat[] streamFormats;
|
||||||
private final String unknownSize;
|
private final String unknownSize;
|
||||||
|
|
||||||
public StreamSizeWrapper(@NonNull final List<T> streamList,
|
public StreamInfoWrapper(@NonNull final List<T> streamList,
|
||||||
@Nullable final Context context) {
|
@Nullable final Context context) {
|
||||||
this.streamsList = streamList;
|
this.streamsList = streamList;
|
||||||
this.streamSizes = new long[streamsList.size()];
|
this.streamSizes = new long[streamsList.size()];
|
||||||
this.unknownSize = context == null
|
this.unknownSize = context == null
|
||||||
? "--.-" : context.getString(R.string.unknown_content);
|
? "--.-" : context.getString(R.string.unknown_content);
|
||||||
|
this.streamFormats = new MediaFormat[streamsList.size()];
|
||||||
resetSizes();
|
resetInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to fetch the sizes of all the streams in a wrapper.
|
* Helper method to fetch the sizes and missing media formats
|
||||||
|
* of all the streams in a wrapper.
|
||||||
*
|
*
|
||||||
* @param <X> the stream type's class extending {@link Stream}
|
* @param <X> the stream type's class extending {@link Stream}
|
||||||
* @param streamsWrapper the wrapper
|
* @param streamsWrapper the wrapper
|
||||||
* @return a {@link Single} that returns a boolean indicating if any elements were changed
|
* @return a {@link Single} that returns a boolean indicating if any elements were changed
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static <X extends Stream> Single<Boolean> fetchSizeForWrapper(
|
public static <X extends Stream> Single<Boolean> fetchMoreInfoForWrapper(
|
||||||
final StreamSizeWrapper<X> streamsWrapper) {
|
final StreamInfoWrapper<X> streamsWrapper) {
|
||||||
final Callable<Boolean> fetchAndSet = () -> {
|
final Callable<Boolean> fetchAndSet = () -> {
|
||||||
boolean hasChanged = false;
|
boolean hasChanged = false;
|
||||||
for (final X stream : streamsWrapper.getStreamsList()) {
|
for (final X stream : streamsWrapper.getStreamsList()) {
|
||||||
if (streamsWrapper.getSizeInBytes(stream) > SIZE_UNSET) {
|
final boolean changeSize = streamsWrapper.getSizeInBytes(stream) <= SIZE_UNSET;
|
||||||
|
final boolean changeFormat = stream.getFormat() == null;
|
||||||
|
if (!changeSize && !changeFormat) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
final Response response = DownloaderImpl.getInstance()
|
||||||
final long contentLength = DownloaderImpl.getInstance().getContentLength(
|
.head(stream.getContent());
|
||||||
stream.getContent());
|
if (changeSize) {
|
||||||
streamsWrapper.setSize(stream, contentLength);
|
final String contentLength = response.getHeader("Content-Length");
|
||||||
hasChanged = true;
|
if (!isNullOrEmpty(contentLength)) {
|
||||||
|
streamsWrapper.setSize(stream, Long.parseLong(contentLength));
|
||||||
|
hasChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changeFormat) {
|
||||||
|
hasChanged = retrieveMediaFormat(stream, streamsWrapper, response)
|
||||||
|
|| hasChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return hasChanged;
|
return hasChanged;
|
||||||
};
|
};
|
||||||
@ -271,13 +287,149 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
.onErrorReturnItem(true);
|
.onErrorReturnItem(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetSizes() {
|
/**
|
||||||
Arrays.fill(streamSizes, SIZE_UNSET);
|
* Try to retrieve the {@link MediaFormat} for a stream from the request headers.
|
||||||
|
*
|
||||||
|
* @param <X> the stream type to get the {@link MediaFormat} for
|
||||||
|
* @param stream the stream to find the {@link MediaFormat} for
|
||||||
|
* @param streamsWrapper the wrapper to store the found {@link MediaFormat} in
|
||||||
|
* @param response the response of the head request for the given stream
|
||||||
|
* @return {@code true} if the media format could be retrieved; {@code false} otherwise
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static <X extends Stream> boolean retrieveMediaFormat(
|
||||||
|
@NonNull final X stream,
|
||||||
|
@NonNull final StreamInfoWrapper<X> streamsWrapper,
|
||||||
|
@NonNull final Response response) {
|
||||||
|
return retrieveMediaFormatFromFileTypeHeaders(stream, streamsWrapper, response)
|
||||||
|
|| retrieveMediaFormatFromContentDispositionHeader(
|
||||||
|
stream, streamsWrapper, response)
|
||||||
|
|| retrieveMediaFormatFromContentTypeHeader(stream, streamsWrapper, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <X extends Stream> StreamSizeWrapper<X> empty() {
|
@VisibleForTesting
|
||||||
|
public static <X extends Stream> boolean retrieveMediaFormatFromFileTypeHeaders(
|
||||||
|
@NonNull final X stream,
|
||||||
|
@NonNull final StreamInfoWrapper<X> streamsWrapper,
|
||||||
|
@NonNull final Response response) {
|
||||||
|
// try to use additional headers from CDNs or servers,
|
||||||
|
// e.g. x-amz-meta-file-type (e.g. for SoundCloud)
|
||||||
|
final List<String> keys = response.responseHeaders().keySet().stream()
|
||||||
|
.filter(k -> k.endsWith("file-type")).collect(Collectors.toList());
|
||||||
|
if (!keys.isEmpty()) {
|
||||||
|
for (final String key : keys) {
|
||||||
|
final String suffix = response.getHeader(key);
|
||||||
|
final MediaFormat format = MediaFormat.getFromSuffix(suffix);
|
||||||
|
if (format != null) {
|
||||||
|
streamsWrapper.setFormat(stream, format);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Retrieve a {@link MediaFormat} from a HTTP Content-Disposition header
|
||||||
|
* for a stream and store the info in a wrapper.</p>
|
||||||
|
* @see
|
||||||
|
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">
|
||||||
|
* mdn Web Docs for the HTTP Content-Disposition Header</a>
|
||||||
|
* @param stream the stream to get the {@link MediaFormat} for
|
||||||
|
* @param streamsWrapper the wrapper to store the {@link MediaFormat} in
|
||||||
|
* @param response the response to get the Content-Disposition header from
|
||||||
|
* @return {@code true} if the {@link MediaFormat} could be retrieved from the response;
|
||||||
|
* otherwise {@code false}
|
||||||
|
* @param <X>
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static <X extends Stream> boolean retrieveMediaFormatFromContentDispositionHeader(
|
||||||
|
@NonNull final X stream,
|
||||||
|
@NonNull final StreamInfoWrapper<X> streamsWrapper,
|
||||||
|
@NonNull final Response response) {
|
||||||
|
// parse the Content-Disposition header,
|
||||||
|
// see
|
||||||
|
// there can be two filename directives
|
||||||
|
String contentDisposition = response.getHeader("Content-Disposition");
|
||||||
|
if (contentDisposition == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
contentDisposition = Utils.decodeUrlUtf8(contentDisposition);
|
||||||
|
final String[] parts = contentDisposition.split(";");
|
||||||
|
for (String part : parts) {
|
||||||
|
final String fileName;
|
||||||
|
part = part.trim();
|
||||||
|
|
||||||
|
// extract the filename
|
||||||
|
if (part.startsWith("filename=")) {
|
||||||
|
// remove directive and decode
|
||||||
|
fileName = Utils.decodeUrlUtf8(part.substring(9));
|
||||||
|
} else if (part.startsWith("filename*=")) {
|
||||||
|
fileName = Utils.decodeUrlUtf8(part.substring(10));
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the file extension / suffix
|
||||||
|
final String[] p = fileName.split("\\.");
|
||||||
|
String suffix = p[p.length - 1];
|
||||||
|
if (suffix.endsWith("\"") || suffix.endsWith("'")) {
|
||||||
|
// remove trailing quotes if present, end index is exclusive
|
||||||
|
suffix = suffix.substring(0, suffix.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the corresponding media format
|
||||||
|
final MediaFormat format = MediaFormat.getFromSuffix(suffix);
|
||||||
|
if (format != null) {
|
||||||
|
streamsWrapper.setFormat(stream, format);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final Exception ignored) {
|
||||||
|
// fail silently
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static <X extends Stream> boolean retrieveMediaFormatFromContentTypeHeader(
|
||||||
|
@NonNull final X stream,
|
||||||
|
@NonNull final StreamInfoWrapper<X> streamsWrapper,
|
||||||
|
@NonNull final Response response) {
|
||||||
|
// try to get the format by content type
|
||||||
|
// some mime types are not unique for every format, those are omitted
|
||||||
|
final String contentTypeHeader = response.getHeader("Content-Type");
|
||||||
|
if (contentTypeHeader == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable MediaFormat foundFormat = null;
|
||||||
|
for (final MediaFormat format : MediaFormat.getAllFromMimeType(contentTypeHeader)) {
|
||||||
|
if (foundFormat == null) {
|
||||||
|
foundFormat = format;
|
||||||
|
} else if (foundFormat.id != format.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundFormat != null) {
|
||||||
|
streamsWrapper.setFormat(stream, foundFormat);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetInfo() {
|
||||||
|
Arrays.fill(streamSizes, SIZE_UNSET);
|
||||||
|
for (int i = 0; i < streamsList.size(); i++) {
|
||||||
|
streamFormats[i] = streamsList.get(i) == null // test for invalid streams
|
||||||
|
? null : streamsList.get(i).getFormat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <X extends Stream> StreamInfoWrapper<X> empty() {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (StreamSizeWrapper<X>) EMPTY;
|
return (StreamInfoWrapper<X>) EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<T> getStreamsList() {
|
public List<T> getStreamsList() {
|
||||||
@ -306,5 +458,13 @@ public class StreamItemAdapter<T extends Stream, U extends Stream> extends BaseA
|
|||||||
public void setSize(final T stream, final long sizeInBytes) {
|
public void setSize(final T stream, final long sizeInBytes) {
|
||||||
streamSizes[streamsList.indexOf(stream)] = sizeInBytes;
|
streamSizes[streamsList.indexOf(stream)] = sizeInBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MediaFormat getFormat(final int streamIndex) {
|
||||||
|
return streamFormats[streamIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFormat(final T stream, final MediaFormat format) {
|
||||||
|
streamFormats[streamsList.indexOf(stream)] = format;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user