Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple audio tracks #9937

Merged
merged 24 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de7872d
feat: add audio language selector
Theta-Dev Mar 17, 2023
208887d
feat: improve audio track sorting, add prefer_descriptive_audio option
Theta-Dev Mar 18, 2023
dba53d2
fix: remove todo
Theta-Dev Mar 18, 2023
77649d3
fix: reduce complexity
Theta-Dev Mar 18, 2023
366c39d
feat: add language selector to audio player
Theta-Dev Mar 19, 2023
87a88e4
feat: localized audio track names
Theta-Dev Mar 19, 2023
7aed2ee
feat: add prefer original option, improve audio stream ordering
Theta-Dev Mar 19, 2023
ef0a4cf
feat: add external audio playback language selector
Theta-Dev Mar 19, 2023
9b8ffdd
fix: improve track name localization
Theta-Dev Mar 19, 2023
61a1476
fix: ListHelper tests
Theta-Dev Mar 19, 2023
dbd6e4d
fix: sonarcloud lint
Theta-Dev Mar 19, 2023
fdd3b03
fix: audio stream format selection
Theta-Dev Mar 19, 2023
ed06f55
feat: add track selection to downloader
Theta-Dev Mar 20, 2023
694418d
fix: update stream sizes when audio track changed
Theta-Dev Mar 21, 2023
39a5c8b
fix: reset video stream sizes on audio track selection
Theta-Dev Mar 29, 2023
d010384
Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector
Theta-Dev Apr 3, 2023
365bb2d
Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector
Theta-Dev Apr 5, 2023
2edc223
Merge branch 'dev' into alang-selector
Theta-Dev Apr 17, 2023
b567d42
fix: small codestyle fixes
Theta-Dev Apr 21, 2023
c377ffb
Merge branch 'dev' of github.com:TeamNewPipe/NewPipe into alang-selector
Theta-Dev Apr 21, 2023
4e837e8
fix docs in app/src/main/java/org/schabi/newpipe/util/Localization.java
Theta-Dev Apr 30, 2023
22671ca
fix: audio stream cache key, code fmt
Theta-Dev Apr 30, 2023
d89a3c6
Remove "default" from audio track already present message
AudricV May 1, 2023
023f616
Add Open in browser button to audio external players dialog
AudricV May 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 121 additions & 47 deletions app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener;
import org.schabi.newpipe.util.StreamItemAdapter;
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
import org.schabi.newpipe.util.AudioTrackAdapter;
import org.schabi.newpipe.util.AudioTrackAdapter.AudioTracksWrapper;
import org.schabi.newpipe.util.ThemeHelper;

import java.io.File;
Expand Down Expand Up @@ -95,12 +97,14 @@ public class DownloadDialog extends DialogFragment
@State
StreamInfo currentInfo;
@State
StreamSizeWrapper<AudioStream> wrappedAudioStreams;
@State
StreamSizeWrapper<VideoStream> wrappedVideoStreams;
@State
StreamSizeWrapper<SubtitlesStream> wrappedSubtitleStreams;
@State
AudioTracksWrapper wrappedAudioTracks;
@State
int selectedAudioTrackIndex;
@State
int selectedVideoIndex; // set in the constructor
@State
int selectedAudioIndex = 0; // default to the first item
Expand All @@ -117,6 +121,7 @@ public class DownloadDialog extends DialogFragment
private Context context;
private boolean askForSavePath;

private AudioTrackAdapter audioTrackAdapter;
private StreamItemAdapter<AudioStream, Stream> audioStreamsAdapter;
private StreamItemAdapter<VideoStream, AudioStream> videoStreamsAdapter;
private StreamItemAdapter<SubtitlesStream, Stream> subtitleStreamsAdapter;
Expand Down Expand Up @@ -163,18 +168,26 @@ public DownloadDialog() {
public DownloadDialog(@NonNull final Context context, @NonNull final StreamInfo info) {
this.currentInfo = info;

final List<AudioStream> audioStreams =
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP);
final List<List<AudioStream>> groupedAudioStreams =
ListHelper.getGroupedAudioStreams(context, audioStreams);
this.wrappedAudioTracks = new AudioTracksWrapper(groupedAudioStreams, context);
this.selectedAudioTrackIndex =
ListHelper.getDefaultAudioTrackGroup(context, groupedAudioStreams);

// TODO: Adapt this code when the downloader support other types of stream deliveries
final List<VideoStream> videoStreams = ListHelper.getSortedStreamVideosList(
context,
getStreamsOfSpecifiedDelivery(info.getVideoStreams(), PROGRESSIVE_HTTP),
getStreamsOfSpecifiedDelivery(info.getVideoOnlyStreams(), PROGRESSIVE_HTTP),
false,
false
// If there are multiple languages available, prefer streams without audio
// to allow language selection
wrappedAudioTracks.size() > 1
);

this.wrappedVideoStreams = new StreamSizeWrapper<>(videoStreams, context);
this.wrappedAudioStreams = new StreamSizeWrapper<>(
getStreamsOfSpecifiedDelivery(info.getAudioStreams(), PROGRESSIVE_HTTP), context);
this.wrappedSubtitleStreams = new StreamSizeWrapper<>(
getStreamsOfSpecifiedDelivery(info.getSubtitles(), PROGRESSIVE_HTTP), context);

Expand Down Expand Up @@ -212,33 +225,9 @@ public void onCreate(@Nullable final Bundle savedInstanceState) {
setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context));
Icepick.restoreInstanceState(this, savedInstanceState);

final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();

for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
final AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(wrappedAudioStreams.getStreamsList(), videoStreams.get(i));

if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(wrappedAudioStreams,
audioStream));
} else if (DEBUG) {
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
if (mediaFormat != null) {
Log.w(TAG, "No audio stream candidates for video format "
+ mediaFormat.name());
} else {
Log.w(TAG, "No audio stream candidates for unknown video format");
}
}
}

this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(wrappedAudioStreams);
this.audioTrackAdapter = new AudioTrackAdapter(wrappedAudioTracks);
this.subtitleStreamsAdapter = new StreamItemAdapter<>(wrappedSubtitleStreams);
updateSecondaryStreams();

final Intent intent = new Intent(context, DownloadManagerService.class);
context.startService(intent);
Expand All @@ -265,6 +254,39 @@ public void onServiceDisconnected(final ComponentName name) {
}, Context.BIND_AUTO_CREATE);
}

/**
* Update the displayed video streams based on the selected audio track.
*/
private void updateSecondaryStreams() {
final StreamSizeWrapper<AudioStream> audioStreams = getWrappedAudioStreams();
final var secondaryStreams = new SparseArrayCompat<SecondaryStreamHelper<AudioStream>>(4);
final List<VideoStream> videoStreams = wrappedVideoStreams.getStreamsList();
wrappedVideoStreams.resetSizes();

for (int i = 0; i < videoStreams.size(); i++) {
if (!videoStreams.get(i).isVideoOnly()) {
continue;
}
final AudioStream audioStream = SecondaryStreamHelper
.getAudioStreamFor(audioStreams.getStreamsList(), videoStreams.get(i));

if (audioStream != null) {
secondaryStreams.append(i, new SecondaryStreamHelper<>(audioStreams, audioStream));
} else if (DEBUG) {
final MediaFormat mediaFormat = videoStreams.get(i).getFormat();
if (mediaFormat != null) {
Log.w(TAG, "No audio stream candidates for video format "
+ mediaFormat.name());
} else {
Log.w(TAG, "No audio stream candidates for unknown video format");
}
}
}

this.videoStreamsAdapter = new StreamItemAdapter<>(wrappedVideoStreams, secondaryStreams);
this.audioStreamsAdapter = new StreamItemAdapter<>(audioStreams);
}

@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
Expand All @@ -285,13 +307,13 @@ public void onViewCreated(@NonNull final View view,

dialogBinding.fileName.setText(FilenameUtils.createFilename(getContext(),
currentInfo.getName()));
selectedAudioIndex = ListHelper
.getDefaultAudioFormat(getContext(), wrappedAudioStreams.getStreamsList());
selectedAudioIndex = ListHelper.getDefaultAudioFormat(getContext(),
getWrappedAudioStreams().getStreamsList());

selectedSubtitleIndex = getSubtitleIndexBy(subtitleStreamsAdapter.getAll());

dialogBinding.qualitySpinner.setOnItemSelectedListener(this);

dialogBinding.audioTrackSpinner.setOnItemSelectedListener(this);
dialogBinding.videoAudioGroup.setOnCheckedChangeListener(this);

initToolbar(dialogBinding.toolbarLayout.toolbar);
Expand Down Expand Up @@ -383,7 +405,7 @@ private void fetchStreamsSize() {
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size",
currentInfo.getServiceId()))));
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams)
disposables.add(StreamSizeWrapper.fetchSizeForWrapper(getWrappedAudioStreams())
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
== R.id.audio_button) {
Expand All @@ -405,14 +427,28 @@ private void fetchStreamsSize() {
currentInfo.getServiceId()))));
}

private void setupAudioTrackSpinner() {
if (getContext() == null) {
return;
}

dialogBinding.audioTrackSpinner.setAdapter(audioTrackAdapter);
dialogBinding.audioTrackSpinner.setSelection(selectedAudioTrackIndex);
}

private void setupAudioSpinner() {
if (getContext() == null) {
return;
}

dialogBinding.qualitySpinner.setAdapter(audioStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedAudioIndex);
dialogBinding.qualitySpinner.setVisibility(View.GONE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setAdapter(audioStreamsAdapter);
dialogBinding.audioStreamSpinner.setSelection(selectedAudioIndex);
dialogBinding.audioStreamSpinner.setVisibility(View.VISIBLE);
dialogBinding.audioTrackSpinner.setVisibility(
wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE);
}

private void setupVideoSpinner() {
Expand All @@ -422,7 +458,21 @@ private void setupVideoSpinner() {

dialogBinding.qualitySpinner.setAdapter(videoStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedVideoIndex);
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
onVideoStreamSelected();
}

private void onVideoStreamSelected() {
final boolean isVideoOnly = videoStreamsAdapter.getItem(selectedVideoIndex).isVideoOnly();

dialogBinding.audioTrackSpinner.setVisibility(
isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE);
dialogBinding.defaultAudioTrackPresentText.setVisibility(
!isVideoOnly && wrappedAudioTracks.size() > 1 ? View.VISIBLE : View.GONE

);
Theta-Dev marked this conversation as resolved.
Show resolved Hide resolved
}

private void setupSubtitleSpinner() {
Expand All @@ -432,7 +482,11 @@ private void setupSubtitleSpinner() {

dialogBinding.qualitySpinner.setAdapter(subtitleStreamsAdapter);
dialogBinding.qualitySpinner.setSelection(selectedSubtitleIndex);
dialogBinding.qualitySpinner.setVisibility(View.VISIBLE);
setRadioButtonsState(true);
dialogBinding.audioStreamSpinner.setVisibility(View.GONE);
dialogBinding.audioTrackSpinner.setVisibility(View.GONE);
dialogBinding.defaultAudioTrackPresentText.setVisibility(View.GONE);
}


Expand Down Expand Up @@ -550,18 +604,31 @@ public void onItemSelected(final AdapterView<?> parent,
+ "parent = [" + parent + "], view = [" + view + "], "
+ "position = [" + position + "], id = [" + id + "]");
}
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedAudioIndex = position;
break;
case R.id.video_button:
selectedVideoIndex = position;

switch (parent.getId()) {
case R.id.quality_spinner:
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.video_button:
selectedVideoIndex = position;
onVideoStreamSelected();
break;
case R.id.subtitle_button:
selectedSubtitleIndex = position;
break;
}
onItemSelectedSetFileName();
break;
case R.id.subtitle_button:
selectedSubtitleIndex = position;
case R.id.audio_track_spinner:
final boolean trackChanged = selectedAudioTrackIndex != position;
selectedAudioTrackIndex = position;
if (trackChanged) {
updateSecondaryStreams();
fetchStreamsSize();
}
break;
case R.id.audio_stream_spinner:
selectedAudioIndex = position;
}
onItemSelectedSetFileName();
}

private void onItemSelectedSetFileName() {
Expand Down Expand Up @@ -607,6 +674,7 @@ public void onNothingSelected(final AdapterView<?> parent) {

protected void setupDownloadOptions() {
setRadioButtonsState(false);
setupAudioTrackSpinner();

final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0;
final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0;
Expand Down Expand Up @@ -657,6 +725,13 @@ private void setRadioButtonsState(final boolean enabled) {
dialogBinding.subtitleButton.setEnabled(enabled);
}

private StreamSizeWrapper<AudioStream> getWrappedAudioStreams() {
if (selectedAudioTrackIndex < 0 || selectedAudioTrackIndex > wrappedAudioTracks.size()) {
return StreamSizeWrapper.empty();
}
return wrappedAudioTracks.getTracksList().get(selectedAudioTrackIndex);
}

private int getSubtitleIndexBy(@NonNull final List<SubtitlesStream> streams) {
final Localization preferredLocalization = NewPipe.getPreferredLocalization();

Expand Down Expand Up @@ -1013,7 +1088,6 @@ private void continueSelectedDownload(@NonNull final StoredFileHelper storage) {
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
}

psArgs = null;
final long videoSize = wrappedVideoStreams.getSizeInBytes(
(VideoStream) selectedStream);

Expand Down
Loading