Streamable Video.

This commit is contained in:
Nicholas
2023-08-29 16:52:17 -04:00
committed by Nicholas Tinsley
parent 099c94c215
commit 64babe2e42
23 changed files with 290 additions and 125 deletions

View File

@@ -1,24 +1,29 @@
package org.thoughtcrime.securesms.video.exo;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.TransferListener;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.mms.PartUriParser;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@@ -28,14 +33,13 @@ import java.util.Map;
@OptIn(markerClass = UnstableApi.class)
class PartDataSource implements DataSource {
private final @NonNull Context context;
private final String TAG = Log.tag(PartDataSource.class);
private final @Nullable TransferListener listener;
private Uri uri;
private InputStream inputSteam;
private InputStream inputStream;
PartDataSource(@NonNull Context context, @Nullable TransferListener listener) {
this.context = context.getApplicationContext();
PartDataSource(@Nullable TransferListener listener) {
this.listener = listener;
}
@@ -47,13 +51,42 @@ class PartDataSource implements DataSource {
public long open(DataSpec dataSpec) throws IOException {
this.uri = dataSpec.uri;
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
PartUriParser partUri = new PartUriParser(uri);
Attachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
AttachmentTable attachmentDatabase = SignalDatabase.attachments();
PartUriParser partUri = new PartUriParser(uri);
DatabaseAttachment attachment = attachmentDatabase.getAttachment(partUri.getPartId());
if (attachment == null) throw new IOException("Attachment not found");
this.inputSteam = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
final boolean hasIncrementalDigest = attachment.getIncrementalDigest() != null;
final boolean inProgress = attachment.isInProgress();
final String attachmentKey = attachment.getKey();
final boolean hasData = attachment.hasData();
if (inProgress && !hasData && hasIncrementalDigest && attachmentKey != null && FeatureFlags.instantVideoPlayback()) {
final byte[] decode = Base64.decode(attachmentKey);
final File transferFile = attachmentDatabase.getOrCreateTransferFile(attachment.getAttachmentId());
try {
this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.getSize(), decode, attachment.getDigest(), attachment.getIncrementalDigest());
long skipped = 0;
while (skipped < dataSpec.position) {
skipped += this.inputStream.read();
}
Log.d(TAG, "Successfully loaded partial attachment file.");
} catch (InvalidMessageException e) {
throw new IOException("Error decrypting attachment stream!", e);
}
} else if (!inProgress || hasData) {
this.inputStream = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);
Log.d(TAG, "Successfully loaded completed attachment file.");
} else {
throw new IOException("Ineligible " + attachment.getAttachmentId().toString()
+ "\nTransfer state: " + attachment.getTransferState()
+ "\nIncremental Digest Present: " + hasIncrementalDigest
+ "\nAttachment Key Non-Empty: " + (attachmentKey != null && !attachmentKey.isEmpty()));
}
if (listener != null) {
listener.onTransferStart(this, dataSpec, false);
@@ -66,7 +99,7 @@ class PartDataSource implements DataSource {
@Override
public int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException {
int read = inputSteam.read(buffer, offset, readLength);
int read = inputStream.read(buffer, offset, readLength);
if (read > 0 && listener != null) {
listener.onBytesTransferred(this, null, false, read);
@@ -87,6 +120,6 @@ class PartDataSource implements DataSource {
@Override
public void close() throws IOException {
inputSteam.close();
if (inputStream != null) inputStream.close();
}
}

View File

@@ -116,7 +116,7 @@ import okhttp3.OkHttpClient;
@Override
public @NonNull SignalDataSource createDataSource() {
return new SignalDataSource(new DefaultDataSourceFactory(context, "GenericUserAgent", null).createDataSource(),
new PartDataSource(context, listener),
new PartDataSource(listener),
new BlobDataSource(context, listener),
okHttpClient != null ? new ChunkedDataSource(okHttpClient, listener) : null);
}