Merge pull request #4001 from ByteHamster/select-audio-tracks

Allow to select audio track
This commit is contained in:
H. Lehmann 2020-04-04 11:56:47 +02:00 committed by GitHub
commit 77efea895a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 300 additions and 85 deletions

View File

@ -2,6 +2,8 @@ package de.danoeh.antennapod.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
@ -17,6 +19,8 @@ import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import java.util.List;
public class PlaybackControlsDialog extends DialogFragment {
private static final String ARGUMENT_IS_PLAYING_VIDEO = "isPlayingVideo";
@ -43,6 +47,7 @@ public class PlaybackControlsDialog extends DialogFragment {
@Override
public void setupGUI() {
setupUi();
setupAudioTracks();
}
};
controller.init();
@ -198,6 +203,23 @@ public class PlaybackControlsDialog extends DialogFragment {
});
}
private void setupAudioTracks() {
List<String> audioTracks = controller.getAudioTracks();
int selectedAudioTrack = controller.getSelectedAudioTrack();
final Button butAudioTracks = dialog.findViewById(R.id.audio_tracks);
if (audioTracks.size() < 2 || selectedAudioTrack < 0) {
butAudioTracks.setVisibility(View.GONE);
return;
}
butAudioTracks.setVisibility(View.VISIBLE);
butAudioTracks.setText(audioTracks.get(selectedAudioTrack));
butAudioTracks.setOnClickListener(v -> {
controller.setAudioTrack((selectedAudioTrack + 1) % audioTracks.size());
new Handler().postDelayed(this::setupAudioTracks, 500);
});
}
private float getCurrentSpeed() {
Playable media = null;
if (controller != null) {

View File

@ -11,6 +11,13 @@
android:minWidth="300dp"
android:orientation="vertical">
<Button
android:id="@+id/audio_tracks"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -9,6 +9,7 @@ import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SeekParameters;
@ -17,7 +18,13 @@ import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.DefaultTrackNameProvider;
import com.google.android.exoplayer2.ui.TrackNameProvider;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
@ -30,6 +37,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import org.antennapod.audio.MediaPlayer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class ExoPlayerWrapper implements IPlayer {
@ -45,11 +55,11 @@ public class ExoPlayerWrapper implements IPlayer {
private MediaPlayer.OnBufferingUpdateListener bufferingUpdateListener;
private PlaybackParameters playbackParameters;
private MediaPlayer.OnInfoListener infoListener;
private DefaultTrackSelector trackSelector;
ExoPlayerWrapper(Context context) {
this.context = context;
exoPlayer = createPlayer();
createPlayer();
playbackParameters = exoPlayer.getPlaybackParameters();
bufferingUpdateDisposable = Observable.interval(2, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
@ -60,16 +70,17 @@ public class ExoPlayerWrapper implements IPlayer {
});
}
private SimpleExoPlayer createPlayer() {
private void createPlayer() {
DefaultLoadControl.Builder loadControl = new DefaultLoadControl.Builder();
loadControl.setBufferDurationsMs(30000, 120000,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS);
loadControl.setBackBuffer(UserPreferences.getRewindSecs() * 1000 + 500, true);
SimpleExoPlayer p = ExoPlayerFactory.newSimpleInstance(context, new DefaultRenderersFactory(context),
new DefaultTrackSelector(), loadControl.createDefaultLoadControl());
p.setSeekParameters(SeekParameters.EXACT);
p.addListener(new Player.EventListener() {
trackSelector = new DefaultTrackSelector();
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultRenderersFactory(context),
trackSelector, loadControl.createDefaultLoadControl());
exoPlayer.setSeekParameters(SeekParameters.EXACT);
exoPlayer.addListener(new Player.EventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (audioCompletionListener != null && playbackState == Player.STATE_ENDED) {
@ -93,7 +104,6 @@ public class ExoPlayerWrapper implements IPlayer {
audioSeekCompleteListener.onSeekComplete(null);
}
});
return p;
}
@Override
@ -154,7 +164,7 @@ public class ExoPlayerWrapper implements IPlayer {
@Override
public void reset() {
exoPlayer.release();
exoPlayer = createPlayer();
createPlayer();
}
@Override
@ -226,6 +236,67 @@ public class ExoPlayerWrapper implements IPlayer {
exoPlayer.stop();
}
@Override
public List<String> getAudioTracks() {
List<String> trackNames = new ArrayList<>();
TrackNameProvider trackNameProvider = new DefaultTrackNameProvider(context.getResources());
for (Format format : getFormats()) {
trackNames.add(trackNameProvider.getTrackName(format));
}
return trackNames;
}
private List<Format> getFormats() {
List<Format> formats = new ArrayList<>();
MappingTrackSelector.MappedTrackInfo trackInfo = trackSelector.getCurrentMappedTrackInfo();
if (trackInfo == null) {
return Collections.emptyList();
}
TrackGroupArray trackGroups = trackInfo.getTrackGroups(getAudioRendererIndex());
for (int i = 0; i < trackGroups.length; i++) {
formats.add(trackGroups.get(i).getFormat(0));
}
return formats;
}
@Override
public void setAudioTrack(int track) {
MappingTrackSelector.MappedTrackInfo trackInfo = trackSelector.getCurrentMappedTrackInfo();
if (trackInfo == null) {
return;
}
TrackGroupArray trackGroups = trackInfo.getTrackGroups(getAudioRendererIndex());
DefaultTrackSelector.SelectionOverride override = new DefaultTrackSelector.SelectionOverride(track, 0);
DefaultTrackSelector.ParametersBuilder params = trackSelector.buildUponParameters()
.setSelectionOverride(getAudioRendererIndex(), trackGroups, override);
trackSelector.setParameters(params);
}
private int getAudioRendererIndex() {
for (int i = 0; i < exoPlayer.getRendererCount(); i++) {
if (exoPlayer.getRendererType(i) == C.TRACK_TYPE_AUDIO) {
return i;
}
}
return -1;
}
@Override
public int getSelectedAudioTrack() {
TrackSelectionArray trackSelections = exoPlayer.getCurrentTrackSelections();
List<Format> availableFormats = getFormats();
for (int i = 0; i < trackSelections.length; i++) {
TrackSelection track = trackSelections.get(i);
if (track == null) {
continue;
}
if (availableFormats.contains(track.getSelectedFormat())) {
return availableFormats.indexOf(track.getSelectedFormat());
}
}
return -1;
}
void setOnCompletionListener(MediaPlayer.OnCompletionListener audioCompletionListener) {
this.audioCompletionListener = audioCompletionListener;
}

View File

@ -17,6 +17,7 @@ import org.antennapod.audio.MediaPlayer;
import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@ -828,6 +829,18 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
media = playable;
}
public List<String> getAudioTracks() {
return mediaPlayer.getAudioTracks();
}
public void setAudioTrack(int track) {
mediaPlayer.setAudioTrack(track);
}
public int getSelectedAudioTrack() {
return mediaPlayer.getSelectedAudioTrack();
}
private void createMediaPlayer() {
if (mediaPlayer != null) {
mediaPlayer.release();

View File

@ -42,6 +42,7 @@ import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -1582,6 +1583,26 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return mediaPlayer.getPosition();
}
public List<String> getAudioTracks() {
if (mediaPlayer == null) {
return Collections.emptyList();
}
return mediaPlayer.getAudioTracks();
}
public int getSelectedAudioTrack() {
if (mediaPlayer == null) {
return -1;
}
return mediaPlayer.getSelectedAudioTrack();
}
public void setAudioTrack(int track) {
if (mediaPlayer != null) {
mediaPlayer.setAudioTrack(track);
}
}
public boolean isStreaming() {
return mediaPlayer.isStreaming();
}

View File

@ -8,6 +8,7 @@ import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
import java.util.List;
import java.util.concurrent.Future;
import de.danoeh.antennapod.core.feed.MediaType;
@ -230,6 +231,12 @@ public abstract class PlaybackServiceMediaPlayer {
protected abstract void setPlayable(Playable playable);
public abstract List<String> getAudioTracks();
public abstract void setAudioTrack(int track);
public abstract int getSelectedAudioTrack();
public void skip() {
endPlayback(false, true, true, true);
}

View File

@ -10,45 +10,58 @@ import org.antennapod.audio.MediaPlayer;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import java.util.Collections;
import java.util.List;
public class AudioPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "AudioPlayer";
private static final String TAG = "AudioPlayer";
public AudioPlayer(Context context) {
super(context);
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(sonicListener);
}
public AudioPlayer(Context context) {
super(context);
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
if (key.equals(UserPreferences.PREF_MEDIA_PLAYER)) {
checkMpi();
}
});
}
private final SharedPreferences.OnSharedPreferenceChangeListener sonicListener =
(sharedPreferences, key) -> {
if (key.equals(UserPreferences.PREF_MEDIA_PLAYER)) {
checkMpi();
}
};
@Override
public void setDisplay(SurfaceHolder sh) {
if (sh != null) {
Log.e(TAG, "Setting display not supported in Audio Player");
throw new UnsupportedOperationException("Setting display not supported in Audio Player");
}
}
@Override
public void setDisplay(SurfaceHolder sh) {
if (sh != null) {
Log.e(TAG, "Setting display not supported in Audio Player");
throw new UnsupportedOperationException("Setting display not supported in Audio Player");
}
}
@Override
public void setPlaybackParams(float speed, boolean skipSilence) {
if (canSetSpeed()) {
setPlaybackSpeed(speed);
}
//Default player does not support silence skipping
}
@Override
public void setPlaybackParams(float speed, boolean skipSilence) {
if(canSetSpeed()) {
setPlaybackSpeed(speed);
}
//Default player does not support silence skipping
}
@Override
protected boolean useSonic() {
return UserPreferences.useSonic();
}
@Override
protected boolean useSonic() {
return UserPreferences.useSonic();
}
@Override
protected boolean downmix() {
return UserPreferences.stereoToMono();
}
@Override
protected boolean downmix() {
return UserPreferences.stereoToMono();
}
public List<String> getAudioTracks() {
return Collections.emptyList();
}
@Override
public void setAudioTrack(int track) {
}
@Override
public int getSelectedAudioTrack() {
return -1;
}
}

View File

@ -4,48 +4,54 @@ import android.content.Context;
import android.view.SurfaceHolder;
import java.io.IOException;
import java.util.List;
public interface IPlayer {
boolean canSetSpeed();
boolean canSetSpeed();
boolean canDownmix();
boolean canDownmix();
int getCurrentPosition();
int getCurrentPosition();
float getCurrentSpeedMultiplier();
float getCurrentSpeedMultiplier();
int getDuration();
int getDuration();
boolean isPlaying();
boolean isPlaying();
void pause();
void pause();
void prepare() throws IllegalStateException, IOException;
void prepare() throws IllegalStateException, IOException;
void release();
void release();
void reset();
void reset();
void seekTo(int msec);
void seekTo(int msec);
void setAudioStreamType(int streamtype);
void setAudioStreamType(int streamtype);
void setDataSource(String path) throws IllegalStateException, IOException,
void setDataSource(String path) throws IllegalStateException, IOException,
IllegalArgumentException, SecurityException;
void setDisplay(SurfaceHolder sh);
void setDisplay(SurfaceHolder sh);
void setPlaybackParams(float speed, boolean skipSilence);
void setPlaybackParams(float speed, boolean skipSilence);
void setDownmix(boolean enable);
void setDownmix(boolean enable);
void setVolume(float left, float right);
void setVolume(float left, float right);
void start();
void start();
void stop();
void stop();
void setWakeMode(Context context, int mode);
List<String> getAudioTracks();
void setAudioTrack(int track);
int getSelectedAudioTrack();
}

View File

@ -44,6 +44,9 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.Collections;
import java.util.List;
/**
* Communicates with the playback service. GUI classes should use this class to
* control playback instead of communicating with the PlaybackService directly.
@ -627,6 +630,26 @@ public class PlaybackController {
}
}
public List<String> getAudioTracks() {
if (playbackService == null) {
return Collections.emptyList();
}
return playbackService.getAudioTracks();
}
public int getSelectedAudioTrack() {
if (playbackService == null) {
return -1;
}
return playbackService.getSelectedAudioTrack();
}
public void setAudioTrack(int track) {
if (playbackService != null) {
playbackService.setAudioTrack(track);
}
}
public boolean isPlayingVideoLocally() {
if (PlaybackService.isCasting()) {
return false;

View File

@ -3,37 +3,53 @@ package de.danoeh.antennapod.core.util.playback;
import android.media.MediaPlayer;
import android.util.Log;
import java.util.Collections;
import java.util.List;
public class VideoPlayer extends MediaPlayer implements IPlayer {
private static final String TAG = "VideoPlayer";
private static final String TAG = "VideoPlayer";
@Override
public boolean canSetSpeed() {
return false;
}
@Override
public boolean canSetSpeed() {
return false;
}
@Override
public boolean canDownmix() {
return false;
}
@Override
public boolean canDownmix() {
return false;
}
@Override
public float getCurrentSpeedMultiplier() {
return 1;
}
@Override
public float getCurrentSpeedMultiplier() {
return 1;
}
@Override
public void setPlaybackParams(float speed, boolean skipSilence) {
//Ignore this for non ExoPlayer implementations
}
@Override
public void setPlaybackParams(float speed, boolean skipSilence) {
//Ignore this for non ExoPlayer implementations
}
@Override
public void setDownmix(boolean b) {
Log.e(TAG, "Setting downmix unsupported in video player");
throw new UnsupportedOperationException("Setting downmix unsupported in video player");
}
@Override
public void setDownmix(boolean b) {
Log.e(TAG, "Setting downmix unsupported in video player");
throw new UnsupportedOperationException("Setting downmix unsupported in video player");
}
@Override
public void setVideoScalingMode(int mode) {
super.setVideoScalingMode(mode);
}
public List<String> getAudioTracks() {
return Collections.emptyList();
}
@Override
public void setAudioTrack(int track) {
}
@Override
public int getSelectedAudioTrack() {
return -1;
}
}

View File

@ -16,6 +16,9 @@ import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConn
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
import de.danoeh.antennapod.core.cast.MediaInfoCreator;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
@ -602,6 +605,19 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
}
}
@Override
public List<String> getAudioTracks() {
return Collections.emptyList();
}
public void setAudioTrack(int track) {
}
public int getSelectedAudioTrack() {
return -1;
}
@Override
protected Future<?> endPlayback(boolean hasEnded, boolean wasSkipped, boolean shouldContinue,
boolean toStoppedState) {