mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-21 23:47:30 +00:00
Handle voice note media playback with ExoPlayer.
There are several (popular) phone models out there that have bugs in their MediaPlayer implementation that cause them to be unable to play voice notes. By moving to ExoPlayer, an application-level media player, we should avoid most of these headaches and stardardize playback. Fixes #7748
This commit is contained in:
parent
053e6fc223
commit
3f25fb7d5f
@ -76,8 +76,8 @@ dependencies {
|
||||
compile 'com.google.android.gms:play-services-maps:9.6.1'
|
||||
compile 'com.google.android.gms:play-services-places:9.6.1'
|
||||
|
||||
compile 'com.google.android.exoplayer:exoplayer-core:2.8.4'
|
||||
compile 'com.google.android.exoplayer:exoplayer-ui:2.8.4'
|
||||
compile 'com.google.android.exoplayer:exoplayer-core:2.9.1'
|
||||
compile 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
|
||||
|
||||
compile 'org.whispersystems:signal-service-android:2.12.2'
|
||||
compile 'org.whispersystems:webrtc-android:M69'
|
||||
@ -172,8 +172,8 @@ dependencyVerification {
|
||||
'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
|
||||
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
|
||||
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
|
||||
'com.google.android.exoplayer:exoplayer-ui:027557b2d69b15e1852a2530b36971f0dcc177abae240ee35e05f63502cdb0a7',
|
||||
'com.google.android.exoplayer:exoplayer-core:e69b409e11887c955deb373357c30eeabf183395db0092b4817e0f80bb467d5b',
|
||||
'com.google.android.exoplayer:exoplayer-ui:7a942afcc402ff01e9bf48e8d3942850986710f06562d50a1408aaf04a683151',
|
||||
'com.google.android.exoplayer:exoplayer-core:b6ab34abac36bc2bc6934b7a50008162feca2c0fde91aaf1e8c1c22f2c16e2c0',
|
||||
'org.whispersystems:signal-service-android:26639df2a9c31b6f31f82034091a4ea3002ca6b1088e7fe6d30428a8290dcf2a',
|
||||
'org.whispersystems:webrtc-android:5493c92141ce884fc5ce8240d783232f4fe14bd17a8d0d7d1bd4944d0bd1682f',
|
||||
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
|
||||
|
@ -4,7 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -6,7 +6,7 @@ import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
@ -17,6 +17,21 @@ import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentServer;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
@ -43,7 +58,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
private final @Nullable WakeLock wakeLock;
|
||||
|
||||
private @NonNull WeakReference<Listener> listener;
|
||||
private @Nullable MediaPlayerWrapper mediaPlayer;
|
||||
private @Nullable SimpleExoPlayer mediaPlayer;
|
||||
private @Nullable AttachmentServer audioAttachmentServer;
|
||||
private long startTime;
|
||||
|
||||
@ -85,64 +100,64 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
private void play(final double progress, boolean earpiece) throws IOException {
|
||||
if (this.mediaPlayer != null) return;
|
||||
|
||||
this.mediaPlayer = new MediaPlayerWrapper();
|
||||
LoadControl loadControl = new DefaultLoadControl.Builder().setBufferDurationsMs(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE).createDefaultLoadControl();
|
||||
this.mediaPlayer = ExoPlayerFactory.newSimpleInstance(context, new DefaultRenderersFactory(context), new DefaultTrackSelector(), loadControl);
|
||||
this.audioAttachmentServer = new AttachmentServer(context, slide.asAttachment());
|
||||
this.startTime = System.currentTimeMillis();
|
||||
|
||||
audioAttachmentServer.start();
|
||||
|
||||
mediaPlayer.setDataSource(context, audioAttachmentServer.getUri());
|
||||
mediaPlayer.prepare(createMediaSource(audioAttachmentServer.getUri()));
|
||||
mediaPlayer.setPlayWhenReady(true);
|
||||
mediaPlayer.setAudioStreamType(earpiece ? AudioManager.STREAM_VOICE_CALL : AudioManager.STREAM_MUSIC);
|
||||
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
mediaPlayer.addListener(new Player.DefaultEventListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
Log.i(TAG, "onPrepared");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
if (mediaPlayer == null) return;
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
switch (playbackState) {
|
||||
case Player.STATE_READY:
|
||||
Log.w(TAG, "onPrepared");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
if (mediaPlayer == null) return;
|
||||
|
||||
if (progress > 0) {
|
||||
mediaPlayer.seekTo((int) (mediaPlayer.getDuration() * progress));
|
||||
}
|
||||
if (progress > 0) {
|
||||
mediaPlayer.seekTo((long) (mediaPlayer.getDuration() * progress));
|
||||
}
|
||||
|
||||
sensorManager.registerListener(AudioSlidePlayer.this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
mediaPlayer.start();
|
||||
sensorManager.registerListener(AudioSlidePlayer.this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
|
||||
setPlaying(AudioSlidePlayer.this);
|
||||
setPlaying(AudioSlidePlayer.this);
|
||||
}
|
||||
|
||||
notifyOnStart();
|
||||
progressEventHandler.sendEmptyMessage(0);
|
||||
break;
|
||||
|
||||
case Player.STATE_ENDED:
|
||||
Log.w(TAG, "onComplete");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
mediaPlayer = null;
|
||||
|
||||
if (audioAttachmentServer != null) {
|
||||
audioAttachmentServer.stop();
|
||||
audioAttachmentServer = null;
|
||||
}
|
||||
|
||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
}
|
||||
|
||||
notifyOnStart();
|
||||
progressEventHandler.sendEmptyMessage(0);
|
||||
}
|
||||
});
|
||||
|
||||
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
Log.i(TAG, "onComplete");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
mediaPlayer = null;
|
||||
|
||||
if (audioAttachmentServer != null) {
|
||||
audioAttachmentServer.stop();
|
||||
audioAttachmentServer = null;
|
||||
}
|
||||
|
||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
}
|
||||
});
|
||||
|
||||
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
Log.w(TAG, "MediaPlayer Error: " + what + " , " + extra);
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
Log.w(TAG, "MediaPlayer Error: " + error);
|
||||
|
||||
Toast.makeText(context, R.string.AudioSlidePlayer_error_playing_audio, Toast.LENGTH_SHORT).show();
|
||||
|
||||
@ -163,11 +178,14 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mediaPlayer.prepareAsync();
|
||||
private MediaSource createMediaSource(@NonNull Uri uri) {
|
||||
return new ExtractorMediaSource.Factory(new DefaultDataSourceFactory(context, BuildConfig.USER_AGENT))
|
||||
.setExtractorsFactory(new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true))
|
||||
.createMediaSource(uri);
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
@ -199,7 +217,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
public void setListener(@NonNull Listener listener) {
|
||||
this.listener = new WeakReference<>(listener);
|
||||
|
||||
if (this.mediaPlayer != null && this.mediaPlayer.isPlaying()) {
|
||||
if (this.mediaPlayer != null && this.mediaPlayer.getPlaybackState() == Player.STATE_READY) {
|
||||
notifyOnStart();
|
||||
}
|
||||
}
|
||||
@ -214,7 +232,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
return new Pair<>(0D, 0);
|
||||
} else {
|
||||
return new Pair<>((double) mediaPlayer.getCurrentPosition() / (double) mediaPlayer.getDuration(),
|
||||
mediaPlayer.getCurrentPosition());
|
||||
(int) mediaPlayer.getCurrentPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,7 +295,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() != Sensor.TYPE_PROXIMITY) return;
|
||||
if (mediaPlayer == null || !mediaPlayer.isPlaying()) return;
|
||||
if (mediaPlayer == null || mediaPlayer.getPlaybackState() != Player.STATE_READY) return;
|
||||
|
||||
int streamType;
|
||||
|
||||
@ -335,7 +353,7 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
public void handleMessage(Message msg) {
|
||||
AudioSlidePlayer player = playerReference.get();
|
||||
|
||||
if (player == null || player.mediaPlayer == null || !player.mediaPlayer.isPlaying()) {
|
||||
if (player == null || player.mediaPlayer == null || player.mediaPlayer.getPlaybackState() != ExoPlayer.STATE_READY) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -344,19 +362,4 @@ public class AudioSlidePlayer implements SensorEventListener {
|
||||
sendEmptyMessageDelayed(0, 50);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MediaPlayerWrapper extends MediaPlayer {
|
||||
|
||||
private int streamType;
|
||||
|
||||
@Override
|
||||
public void setAudioStreamType(int streamType) {
|
||||
this.streamType = streamType;
|
||||
super.setAudioStreamType(streamType);
|
||||
}
|
||||
|
||||
public int getAudioStreamType() {
|
||||
return streamType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
@ -66,7 +67,7 @@ public class VideoPlayer extends FrameLayout {
|
||||
private static final String TAG = VideoPlayer.class.getName();
|
||||
|
||||
@Nullable private final VideoView videoView;
|
||||
@Nullable private final SimpleExoPlayerView exoView;
|
||||
@Nullable private final PlayerView exoView;
|
||||
|
||||
@Nullable private SimpleExoPlayer exoPlayer;
|
||||
@Nullable private AttachmentServer attachmentServer;
|
||||
|
@ -6,10 +6,14 @@ import android.net.Uri;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSource;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AttachmentDataSource implements DataSource {
|
||||
|
||||
@ -23,6 +27,10 @@ public class AttachmentDataSource implements DataSource {
|
||||
this.partDataSource = partDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransferListener(TransferListener transferListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
if (PartAuthority.isLocalUri(dataSpec.uri)) dataSource = partDataSource;
|
||||
@ -41,6 +49,11 @@ public class AttachmentDataSource implements DataSource {
|
||||
return dataSource.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
dataSource.close();
|
||||
|
@ -13,12 +13,12 @@ public class AttachmentDataSourceFactory implements DataSource.Factory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final DefaultDataSourceFactory defaultDataSourceFactory;
|
||||
private final TransferListener<? super DataSource> listener;
|
||||
private final DefaultDataSourceFactory defaultDataSourceFactory;
|
||||
private final TransferListener listener;
|
||||
|
||||
public AttachmentDataSourceFactory(@NonNull Context context,
|
||||
@NonNull DefaultDataSourceFactory defaultDataSourceFactory,
|
||||
@Nullable TransferListener<? super DataSource> listener)
|
||||
@Nullable TransferListener listener)
|
||||
{
|
||||
this.context = context;
|
||||
this.defaultDataSourceFactory = defaultDataSourceFactory;
|
||||
|
@ -18,20 +18,27 @@ import org.thoughtcrime.securesms.mms.PartUriParser;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PartDataSource implements DataSource {
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @Nullable TransferListener<? super PartDataSource> listener;
|
||||
private final @NonNull Context context;
|
||||
private final @Nullable TransferListener listener;
|
||||
|
||||
private Uri uri;
|
||||
private InputStream inputSteam;
|
||||
|
||||
PartDataSource(@NonNull Context context, @Nullable TransferListener<? super PartDataSource> listener) {
|
||||
PartDataSource(@NonNull Context context, @Nullable TransferListener listener) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransferListener(TransferListener transferListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
this.uri = dataSpec.uri;
|
||||
@ -45,7 +52,7 @@ public class PartDataSource implements DataSource {
|
||||
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
|
||||
|
||||
if (listener != null) {
|
||||
listener.onTransferStart(this, dataSpec);
|
||||
listener.onTransferStart(this, dataSpec, false);
|
||||
}
|
||||
|
||||
if (attachment.getSize() - dataSpec.position <= 0) throw new EOFException("No more data");
|
||||
@ -58,7 +65,7 @@ public class PartDataSource implements DataSource {
|
||||
int read = inputSteam.read(buffer, offset, readLength);
|
||||
|
||||
if (read > 0 && listener != null) {
|
||||
listener.onBytesTransferred(this, read);
|
||||
listener.onBytesTransferred(this, null, false, read);
|
||||
}
|
||||
|
||||
return read;
|
||||
@ -69,6 +76,11 @@ public class PartDataSource implements DataSource {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
inputSteam.close();
|
||||
|
Loading…
x
Reference in New Issue
Block a user