Render gifs in gif search as MP4s.

This commit is contained in:
Alex Hart
2021-04-14 16:44:03 -03:00
committed by Greyson Parrelli
parent fcc5db2fe6
commit c31146e902
94 changed files with 2062 additions and 273 deletions

View File

@@ -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();

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}