mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Move all files to natural position.
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
package org.thoughtcrime.securesms.audio;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class AudioCodec {
|
||||
|
||||
private static final String TAG = AudioCodec.class.getSimpleName();
|
||||
|
||||
private static final int SAMPLE_RATE = 44100;
|
||||
private static final int SAMPLE_RATE_INDEX = 4;
|
||||
private static final int CHANNELS = 1;
|
||||
private static final int BIT_RATE = 32000;
|
||||
|
||||
private final int bufferSize;
|
||||
private final MediaCodec mediaCodec;
|
||||
private final AudioRecord audioRecord;
|
||||
|
||||
private boolean running = true;
|
||||
private boolean finished = false;
|
||||
|
||||
public AudioCodec() throws IOException {
|
||||
this.bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
|
||||
this.audioRecord = createAudioRecord(this.bufferSize);
|
||||
this.mediaCodec = createMediaCodec(this.bufferSize);
|
||||
|
||||
this.mediaCodec.start();
|
||||
|
||||
try {
|
||||
audioRecord.startRecording();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
mediaCodec.release();
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
running = false;
|
||||
while (!finished) Util.wait(this, 0);
|
||||
}
|
||||
|
||||
public void start(final OutputStream outputStream) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
byte[] audioRecordData = new byte[bufferSize];
|
||||
ByteBuffer[] codecInputBuffers = mediaCodec.getInputBuffers();
|
||||
ByteBuffer[] codecOutputBuffers = mediaCodec.getOutputBuffers();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
boolean running = isRunning();
|
||||
|
||||
handleCodecInput(audioRecord, audioRecordData, mediaCodec, codecInputBuffers, running);
|
||||
handleCodecOutput(mediaCodec, codecOutputBuffers, bufferInfo, outputStream);
|
||||
|
||||
if (!running) break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
} finally {
|
||||
mediaCodec.stop();
|
||||
audioRecord.stop();
|
||||
|
||||
mediaCodec.release();
|
||||
audioRecord.release();
|
||||
|
||||
Util.close(outputStream);
|
||||
setFinished();
|
||||
}
|
||||
}
|
||||
}, "signal-AudioCodec").start();
|
||||
}
|
||||
|
||||
private synchronized boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
private synchronized void setFinished() {
|
||||
finished = true;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private void handleCodecInput(AudioRecord audioRecord, byte[] audioRecordData,
|
||||
MediaCodec mediaCodec, ByteBuffer[] codecInputBuffers,
|
||||
boolean running)
|
||||
{
|
||||
int length = audioRecord.read(audioRecordData, 0, audioRecordData.length);
|
||||
int codecInputBufferIndex = mediaCodec.dequeueInputBuffer(10 * 1000);
|
||||
|
||||
if (codecInputBufferIndex >= 0) {
|
||||
ByteBuffer codecBuffer = codecInputBuffers[codecInputBufferIndex];
|
||||
codecBuffer.clear();
|
||||
codecBuffer.put(audioRecordData);
|
||||
mediaCodec.queueInputBuffer(codecInputBufferIndex, 0, length, 0, running ? 0 : MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCodecOutput(MediaCodec mediaCodec,
|
||||
ByteBuffer[] codecOutputBuffers,
|
||||
MediaCodec.BufferInfo bufferInfo,
|
||||
OutputStream outputStream)
|
||||
throws IOException
|
||||
{
|
||||
int codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
|
||||
|
||||
while (codecOutputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
if (codecOutputBufferIndex >= 0) {
|
||||
ByteBuffer encoderOutputBuffer = codecOutputBuffers[codecOutputBufferIndex];
|
||||
|
||||
encoderOutputBuffer.position(bufferInfo.offset);
|
||||
encoderOutputBuffer.limit(bufferInfo.offset + bufferInfo.size);
|
||||
|
||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
|
||||
byte[] header = createAdtsHeader(bufferInfo.size - bufferInfo.offset);
|
||||
|
||||
|
||||
outputStream.write(header);
|
||||
|
||||
byte[] data = new byte[encoderOutputBuffer.remaining()];
|
||||
encoderOutputBuffer.get(data);
|
||||
outputStream.write(data);
|
||||
}
|
||||
|
||||
encoderOutputBuffer.clear();
|
||||
|
||||
mediaCodec.releaseOutputBuffer(codecOutputBufferIndex, false);
|
||||
} else if (codecOutputBufferIndex== MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
||||
codecOutputBuffers = mediaCodec.getOutputBuffers();
|
||||
}
|
||||
|
||||
codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private byte[] createAdtsHeader(int length) {
|
||||
int frameLength = length + 7;
|
||||
byte[] adtsHeader = new byte[7];
|
||||
|
||||
adtsHeader[0] = (byte) 0xFF; // Sync Word
|
||||
adtsHeader[1] = (byte) 0xF1; // MPEG-4, Layer (0), No CRC
|
||||
adtsHeader[2] = (byte) ((MediaCodecInfo.CodecProfileLevel.AACObjectLC - 1) << 6);
|
||||
adtsHeader[2] |= (((byte) SAMPLE_RATE_INDEX) << 2);
|
||||
adtsHeader[2] |= (((byte) CHANNELS) >> 2);
|
||||
adtsHeader[3] = (byte) (((CHANNELS & 3) << 6) | ((frameLength >> 11) & 0x03));
|
||||
adtsHeader[4] = (byte) ((frameLength >> 3) & 0xFF);
|
||||
adtsHeader[5] = (byte) (((frameLength & 0x07) << 5) | 0x1f);
|
||||
adtsHeader[6] = (byte) 0xFC;
|
||||
|
||||
return adtsHeader;
|
||||
}
|
||||
|
||||
private AudioRecord createAudioRecord(int bufferSize) {
|
||||
return new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, bufferSize * 10);
|
||||
}
|
||||
|
||||
private MediaCodec createMediaCodec(int bufferSize) throws IOException {
|
||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
|
||||
MediaFormat mediaFormat = new MediaFormat();
|
||||
|
||||
mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
|
||||
mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
|
||||
mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
||||
|
||||
try {
|
||||
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
mediaCodec.release();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
return mediaCodec;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.thoughtcrime.securesms.audio;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import androidx.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class AudioRecorder {
|
||||
|
||||
private static final String TAG = AudioRecorder.class.getSimpleName();
|
||||
|
||||
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
|
||||
|
||||
private final Context context;
|
||||
|
||||
private AudioCodec audioCodec;
|
||||
private Uri captureUri;
|
||||
|
||||
public AudioRecorder(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
Log.i(TAG, "startRecording()");
|
||||
|
||||
executor.execute(() -> {
|
||||
Log.i(TAG, "Running startRecording() + " + Thread.currentThread().getId());
|
||||
try {
|
||||
if (audioCodec != null) {
|
||||
throw new AssertionError("We can only record once at a time.");
|
||||
}
|
||||
|
||||
ParcelFileDescriptor fds[] = ParcelFileDescriptor.createPipe();
|
||||
|
||||
captureUri = BlobProvider.getInstance()
|
||||
.forData(new ParcelFileDescriptor.AutoCloseInputStream(fds[0]), 0)
|
||||
.withMimeType(MediaUtil.AUDIO_AAC)
|
||||
.createForSingleSessionOnDiskAsync(context, () -> Log.i(TAG, "Write successful."), e -> Log.w(TAG, "Error during recording", e));
|
||||
audioCodec = new AudioCodec();
|
||||
|
||||
audioCodec.start(new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public @NonNull ListenableFuture<Pair<Uri, Long>> stopRecording() {
|
||||
Log.i(TAG, "stopRecording()");
|
||||
|
||||
final SettableFuture<Pair<Uri, Long>> future = new SettableFuture<>();
|
||||
|
||||
executor.execute(() -> {
|
||||
if (audioCodec == null) {
|
||||
sendToFuture(future, new IOException("MediaRecorder was never initialized successfully!"));
|
||||
return;
|
||||
}
|
||||
|
||||
audioCodec.stop();
|
||||
|
||||
try {
|
||||
long size = MediaUtil.getMediaSize(context, captureUri);
|
||||
sendToFuture(future, new Pair<>(captureUri, size));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
sendToFuture(future, ioe);
|
||||
}
|
||||
|
||||
audioCodec = null;
|
||||
captureUri = null;
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final Exception exception) {
|
||||
Util.runOnMain(() -> future.setException(exception));
|
||||
}
|
||||
|
||||
private <T> void sendToFuture(final SettableFuture<T> future, final T result) {
|
||||
Util.runOnMain(() -> future.set(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package org.thoughtcrime.securesms.audio;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
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.LoadControl;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
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.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentDataSourceFactory;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class AudioSlidePlayer implements SensorEventListener {
|
||||
|
||||
private static final String TAG = AudioSlidePlayer.class.getSimpleName();
|
||||
|
||||
private static @NonNull Optional<AudioSlidePlayer> playing = Optional.absent();
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final @NonNull AudioSlide slide;
|
||||
private final @NonNull Handler progressEventHandler;
|
||||
private final @NonNull AudioManager audioManager;
|
||||
private final @NonNull SensorManager sensorManager;
|
||||
private final @NonNull Sensor proximitySensor;
|
||||
private final @Nullable WakeLock wakeLock;
|
||||
|
||||
private @NonNull WeakReference<Listener> listener;
|
||||
private @Nullable SimpleExoPlayer mediaPlayer;
|
||||
private long startTime;
|
||||
|
||||
public synchronized static AudioSlidePlayer createFor(@NonNull Context context,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
if (playing.isPresent() && playing.get().getAudioSlide().equals(slide)) {
|
||||
playing.get().setListener(listener);
|
||||
return playing.get();
|
||||
} else {
|
||||
return new AudioSlidePlayer(context, slide, listener);
|
||||
}
|
||||
}
|
||||
|
||||
private AudioSlidePlayer(@NonNull Context context,
|
||||
@NonNull AudioSlide slide,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
this.context = context;
|
||||
this.slide = slide;
|
||||
this.listener = new WeakReference<>(listener);
|
||||
this.progressEventHandler = new ProgressEventHandler(this);
|
||||
this.audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
|
||||
this.sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
|
||||
this.proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
this.wakeLock = ServiceUtil.getPowerManager(context).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
|
||||
} else {
|
||||
this.wakeLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void play(final double progress) throws IOException {
|
||||
play(progress, false);
|
||||
}
|
||||
|
||||
private void play(final double progress, boolean earpiece) throws IOException {
|
||||
if (this.mediaPlayer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (slide.getUri() == null) {
|
||||
throw new IOException("Slide has no URI!");
|
||||
}
|
||||
|
||||
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.startTime = System.currentTimeMillis();
|
||||
|
||||
mediaPlayer.prepare(createMediaSource(slide.getUri()));
|
||||
mediaPlayer.setPlayWhenReady(true);
|
||||
mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
|
||||
.setContentType(earpiece ? C.CONTENT_TYPE_SPEECH : C.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(earpiece ? C.USAGE_VOICE_COMMUNICATION : C.USAGE_MEDIA)
|
||||
.build());
|
||||
mediaPlayer.addListener(new Player.EventListener() {
|
||||
|
||||
boolean started = false;
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
Log.d(TAG, "onPlayerStateChanged(" + playWhenReady + ", " + playbackState + ")");
|
||||
switch (playbackState) {
|
||||
case Player.STATE_READY:
|
||||
Log.i(TAG, "onPrepared() " + mediaPlayer.getBufferedPercentage() + "% buffered");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
if (mediaPlayer == null) return;
|
||||
|
||||
if (started) {
|
||||
Log.d(TAG, "Already started. Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
started = true;
|
||||
|
||||
if (progress > 0) {
|
||||
mediaPlayer.seekTo((long) (mediaPlayer.getDuration() * progress));
|
||||
}
|
||||
|
||||
sensorManager.registerListener(AudioSlidePlayer.this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
|
||||
setPlaying(AudioSlidePlayer.this);
|
||||
}
|
||||
|
||||
notifyOnStart();
|
||||
progressEventHandler.sendEmptyMessage(0);
|
||||
break;
|
||||
|
||||
case Player.STATE_ENDED:
|
||||
Log.i(TAG, "onComplete");
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
mediaPlayer = null;
|
||||
|
||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
Log.w(TAG, "MediaPlayer Error: " + error);
|
||||
|
||||
Toast.makeText(context, R.string.AudioSlidePlayer_error_playing_audio, Toast.LENGTH_SHORT).show();
|
||||
|
||||
synchronized (AudioSlidePlayer.this) {
|
||||
mediaPlayer = null;
|
||||
|
||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
wakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnStop();
|
||||
progressEventHandler.removeMessages(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private MediaSource createMediaSource(@NonNull Uri uri) {
|
||||
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
|
||||
AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null);
|
||||
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);
|
||||
|
||||
return new ExtractorMediaSource.Factory(attachmentDataSourceFactory)
|
||||
.setExtractorsFactory(extractorsFactory)
|
||||
.createMediaSource(uri);
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
Log.i(TAG, "Stop called!");
|
||||
|
||||
removePlaying(this);
|
||||
|
||||
if (this.mediaPlayer != null) {
|
||||
this.mediaPlayer.stop();
|
||||
this.mediaPlayer.release();
|
||||
}
|
||||
|
||||
sensorManager.unregisterListener(AudioSlidePlayer.this);
|
||||
|
||||
this.mediaPlayer = null;
|
||||
}
|
||||
|
||||
public synchronized static void stopAll() {
|
||||
if (playing.isPresent()) {
|
||||
playing.get().stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener) {
|
||||
this.listener = new WeakReference<>(listener);
|
||||
|
||||
if (this.mediaPlayer != null && this.mediaPlayer.getPlaybackState() == Player.STATE_READY) {
|
||||
notifyOnStart();
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull AudioSlide getAudioSlide() {
|
||||
return slide;
|
||||
}
|
||||
|
||||
|
||||
private Pair<Double, Integer> getProgress() {
|
||||
if (mediaPlayer == null || mediaPlayer.getCurrentPosition() <= 0 || mediaPlayer.getDuration() <= 0) {
|
||||
return new Pair<>(0D, 0);
|
||||
} else {
|
||||
return new Pair<>((double) mediaPlayer.getCurrentPosition() / (double) mediaPlayer.getDuration(),
|
||||
(int) mediaPlayer.getCurrentPosition());
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOnStart() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onStart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyOnStop() {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onStop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyOnProgress(final double progress, final long millis) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getListener().onProgress(progress, millis);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NonNull Listener getListener() {
|
||||
Listener listener = this.listener.get();
|
||||
|
||||
if (listener != null) return listener;
|
||||
else return new Listener() {
|
||||
@Override
|
||||
public void onStart() {}
|
||||
@Override
|
||||
public void onStop() {}
|
||||
@Override
|
||||
public void onProgress(double progress, long millis) {}
|
||||
};
|
||||
}
|
||||
|
||||
private synchronized static void setPlaying(@NonNull AudioSlidePlayer player) {
|
||||
if (playing.isPresent() && playing.get() != player) {
|
||||
playing.get().notifyOnStop();
|
||||
playing.get().stop();
|
||||
}
|
||||
|
||||
playing = Optional.of(player);
|
||||
}
|
||||
|
||||
private synchronized static void removePlaying(@NonNull AudioSlidePlayer player) {
|
||||
if (playing.isPresent() && playing.get() == player) {
|
||||
playing = Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() != Sensor.TYPE_PROXIMITY) return;
|
||||
if (mediaPlayer == null || mediaPlayer.getPlaybackState() != Player.STATE_READY) return;
|
||||
|
||||
int streamType;
|
||||
|
||||
if (event.values[0] < 5f && event.values[0] != proximitySensor.getMaximumRange()) {
|
||||
streamType = AudioManager.STREAM_VOICE_CALL;
|
||||
} else {
|
||||
streamType = AudioManager.STREAM_MUSIC;
|
||||
}
|
||||
|
||||
if (streamType == AudioManager.STREAM_VOICE_CALL &&
|
||||
mediaPlayer.getAudioStreamType() != streamType &&
|
||||
!audioManager.isWiredHeadsetOn())
|
||||
{
|
||||
double position = mediaPlayer.getCurrentPosition();
|
||||
double duration = mediaPlayer.getDuration();
|
||||
double progress = position / duration;
|
||||
|
||||
if (wakeLock != null) wakeLock.acquire();
|
||||
stop();
|
||||
try {
|
||||
play(progress, true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
} else if (streamType == AudioManager.STREAM_MUSIC &&
|
||||
mediaPlayer.getAudioStreamType() != streamType &&
|
||||
System.currentTimeMillis() - startTime > 500)
|
||||
{
|
||||
if (wakeLock != null) wakeLock.release();
|
||||
stop();
|
||||
notifyOnStop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onStart();
|
||||
void onStop();
|
||||
void onProgress(double progress, long millis);
|
||||
}
|
||||
|
||||
private static class ProgressEventHandler extends Handler {
|
||||
|
||||
private final WeakReference<AudioSlidePlayer> playerReference;
|
||||
|
||||
private ProgressEventHandler(@NonNull AudioSlidePlayer player) {
|
||||
this.playerReference = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
AudioSlidePlayer player = playerReference.get();
|
||||
|
||||
if (player == null || player.mediaPlayer == null || !isPlayerActive(player.mediaPlayer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Pair<Double, Integer> progress = player.getProgress();
|
||||
player.notifyOnProgress(progress.first, progress.second);
|
||||
sendEmptyMessageDelayed(0, 50);
|
||||
}
|
||||
|
||||
private boolean isPlayerActive(@NonNull SimpleExoPlayer player) {
|
||||
return player.getPlaybackState() == Player.STATE_READY || player.getPlaybackState() == Player.STATE_BUFFERING;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user