mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 17:29:32 +01:00
Render gifs in gif search as MP4s.
This commit is contained in:
committed by
Greyson Parrelli
parent
fcc5db2fe6
commit
c31146e902
@@ -41,6 +41,7 @@ import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
@@ -60,12 +61,13 @@ public class VideoPlayer extends FrameLayout {
|
||||
private final PlayerView exoView;
|
||||
private final PlayerControlView exoControls;
|
||||
|
||||
private SimpleExoPlayer exoPlayer;
|
||||
private Window window;
|
||||
private PlayerStateCallback playerStateCallback;
|
||||
private PlayerCallback playerCallback;
|
||||
private boolean clipped;
|
||||
private long clippedStartUs;
|
||||
private SimpleExoPlayer exoPlayer;
|
||||
private Window window;
|
||||
private PlayerStateCallback playerStateCallback;
|
||||
private PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback;
|
||||
private PlayerCallback playerCallback;
|
||||
private boolean clipped;
|
||||
private long clippedStartUs;
|
||||
|
||||
public VideoPlayer(Context context) {
|
||||
this(context, null);
|
||||
@@ -94,25 +96,27 @@ public class VideoPlayer extends FrameLayout {
|
||||
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
LoadControl loadControl = new DefaultLoadControl();
|
||||
|
||||
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
|
||||
exoPlayer.addListener(new ExoPlayerListener(window, playerStateCallback));
|
||||
exoPlayer.addListener(new Player.DefaultEventListener() {
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if (playerCallback != null) {
|
||||
switch (playbackState) {
|
||||
case Player.STATE_READY:
|
||||
if (playWhenReady) playerCallback.onPlaying();
|
||||
break;
|
||||
case Player.STATE_ENDED:
|
||||
playerCallback.onStopped();
|
||||
break;
|
||||
if (exoPlayer == null) {
|
||||
exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector, loadControl);
|
||||
exoPlayer.addListener(new ExoPlayerListener(this, window, playerStateCallback, playerPositionDiscontinuityCallback));
|
||||
exoPlayer.addListener(new Player.EventListener() {
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if (playerCallback != null) {
|
||||
switch (playbackState) {
|
||||
case Player.STATE_READY:
|
||||
if (playWhenReady) playerCallback.onPlaying();
|
||||
break;
|
||||
case Player.STATE_ENDED:
|
||||
playerCallback.onStopped();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
exoView.setPlayer(exoPlayer);
|
||||
exoControls.setPlayer(exoPlayer);
|
||||
});
|
||||
exoView.setPlayer(exoPlayer);
|
||||
exoControls.setPlayer(exoPlayer);
|
||||
}
|
||||
|
||||
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(context, "GenericUserAgent", null);
|
||||
AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(context, defaultDataSourceFactory, null);
|
||||
@@ -126,8 +130,18 @@ public class VideoPlayer extends FrameLayout {
|
||||
exoPlayer.setPlayWhenReady(autoplay);
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return exoPlayer != null;
|
||||
}
|
||||
|
||||
public void setResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) {
|
||||
exoView.setResizeMode(resizeMode);
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
this.exoPlayer.setPlayWhenReady(false);
|
||||
if (this.exoPlayer != null) {
|
||||
this.exoPlayer.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideControls() {
|
||||
@@ -146,6 +160,7 @@ public class VideoPlayer extends FrameLayout {
|
||||
public void cleanup() {
|
||||
if (this.exoPlayer != null) {
|
||||
this.exoPlayer.release();
|
||||
this.exoPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +211,7 @@ public class VideoPlayer extends FrameLayout {
|
||||
if (exoPlayer != null && createMediaSource != null) {
|
||||
if (clipped) {
|
||||
exoPlayer.prepare(createMediaSource.create());
|
||||
clipped = false;
|
||||
clipped = false;
|
||||
clippedStartUs = 0;
|
||||
}
|
||||
exoPlayer.setPlayWhenReady(playWhenReady);
|
||||
@@ -215,6 +230,10 @@ public class VideoPlayer extends FrameLayout {
|
||||
this.playerCallback = playerCallback;
|
||||
}
|
||||
|
||||
public void setPlayerPositionDiscontinuityCallback(@NonNull PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback) {
|
||||
this.playerPositionDiscontinuityCallback = playerPositionDiscontinuityCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes a paused video, or restarts if at end of video.
|
||||
*/
|
||||
@@ -227,28 +246,40 @@ public class VideoPlayer extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExoPlayerListener extends Player.DefaultEventListener {
|
||||
private final Window window;
|
||||
private final PlayerStateCallback playerStateCallback;
|
||||
private static class ExoPlayerListener implements Player.EventListener {
|
||||
private final VideoPlayer videoPlayer;
|
||||
private final Window window;
|
||||
private final PlayerStateCallback playerStateCallback;
|
||||
private final PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback;
|
||||
|
||||
ExoPlayerListener(Window window, PlayerStateCallback playerStateCallback) {
|
||||
this.window = window;
|
||||
this.playerStateCallback = playerStateCallback;
|
||||
ExoPlayerListener(@NonNull VideoPlayer videoPlayer,
|
||||
@Nullable Window window,
|
||||
@Nullable PlayerStateCallback playerStateCallback,
|
||||
@Nullable PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback)
|
||||
{
|
||||
this.videoPlayer = videoPlayer;
|
||||
this.window = window;
|
||||
this.playerStateCallback = playerStateCallback;
|
||||
this.playerPositionDiscontinuityCallback = playerPositionDiscontinuityCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
switch(playbackState) {
|
||||
switch (playbackState) {
|
||||
case Player.STATE_IDLE:
|
||||
case Player.STATE_BUFFERING:
|
||||
case Player.STATE_ENDED:
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
if (window != null) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
break;
|
||||
case Player.STATE_READY:
|
||||
if (playWhenReady) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
if (window != null) {
|
||||
if (playWhenReady) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
notifyPlayerReady();
|
||||
break;
|
||||
@@ -257,6 +288,13 @@ public class VideoPlayer extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(int reason) {
|
||||
if (playerPositionDiscontinuityCallback != null) {
|
||||
playerPositionDiscontinuityCallback.onPositionDiscontinuity(videoPlayer, reason);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPlayerReady() {
|
||||
if (playerStateCallback != null) playerStateCallback.onPlayerReady();
|
||||
}
|
||||
@@ -266,6 +304,10 @@ public class VideoPlayer extends FrameLayout {
|
||||
void onPlayerReady();
|
||||
}
|
||||
|
||||
public interface PlayerPositionDiscontinuityCallback {
|
||||
void onPositionDiscontinuity(@NonNull VideoPlayer player, int reason);
|
||||
}
|
||||
|
||||
public interface PlayerCallback {
|
||||
|
||||
void onPlaying();
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.thoughtcrime.securesms.video.exo;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import org.thoughtcrime.securesms.net.ChunkedDataFetcher;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* DataSource which utilizes ChunkedDataFetcher to download video content via Signal content proxy.
|
||||
*/
|
||||
public class ChunkedDataSource implements DataSource {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final TransferListener transferListener;
|
||||
|
||||
private Uri uri;
|
||||
private volatile InputStream inputStream;
|
||||
private volatile Exception exception;
|
||||
|
||||
ChunkedDataSource(@NonNull OkHttpClient okHttpClient, @Nullable TransferListener listener) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.transferListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransferListener(TransferListener transferListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
this.uri = dataSpec.uri;
|
||||
this.exception = null;
|
||||
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
this.inputStream = null;
|
||||
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
ChunkedDataFetcher fetcher = new ChunkedDataFetcher(okHttpClient);
|
||||
|
||||
fetcher.fetch(this.uri.toString(), dataSpec.length, new ChunkedDataFetcher.Callback() {
|
||||
@Override
|
||||
public void onSuccess(InputStream stream) {
|
||||
inputStream = stream;
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
exception = e;
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
countDownLatch.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
throw new IOException(exception);
|
||||
}
|
||||
|
||||
if (inputStream == null) {
|
||||
throw new IOException("Timed out waiting for input stream");
|
||||
}
|
||||
|
||||
if (transferListener != null) {
|
||||
transferListener.onTransferStart(this, dataSpec, false);
|
||||
}
|
||||
|
||||
if ( dataSpec.length != C.LENGTH_UNSET && dataSpec.length - dataSpec.position <= 0) {
|
||||
throw new EOFException("No more data");
|
||||
}
|
||||
|
||||
return dataSpec.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int readLength) throws IOException {
|
||||
int read = inputStream.read(buffer, offset, readLength);
|
||||
|
||||
if (read > 0 && transferListener != null) {
|
||||
transferListener.onBytesTransferred(this, null, false, read);
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Uri getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.video.exo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class ChunkedDataSourceFactory implements DataSource.Factory {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final TransferListener listener;
|
||||
|
||||
public ChunkedDataSourceFactory(@NonNull OkHttpClient okHttpClient, @Nullable TransferListener listener) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DataSource createDataSource() {
|
||||
return new ChunkedDataSource(okHttpClient, listener);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user