diff --git a/app/jni/voice.c b/app/jni/voice.c
index b14bac1673..53a55de485 100644
--- a/app/jni/voice.c
+++ b/app/jni/voice.c
@@ -80,6 +80,20 @@ typedef struct {
int copy_comments;
} oe_enc_opt;
+typedef struct {
+ ogg_int32_t _packetId;
+ opus_int64 bytes_written;
+ opus_int64 pages_out;
+ opus_int64 total_samples;
+ ogg_int64_t enc_granulepos;
+ int size_segments;
+ int last_segments;
+ ogg_int64_t last_granulepos;
+ opus_int32 min_bytes;
+ int max_frame_bytes;
+ int serialno;
+} resume_data;
+
static int write_uint32(Packet *p, ogg_uint32_t val) {
if (p->pos > p->maxlen - 4) {
return 0;
@@ -248,18 +262,19 @@ static int writeOggPage(ogg_page *page, FILE *os) {
return written;
}
-const opus_int32 bitrate = 32000;
-const opus_int32 rate = 48000;
+const opus_int32 bitrate = OPUS_BITRATE_MAX;
const opus_int32 frame_size = 960;
const int with_cvbr = 1;
const int max_ogg_delay = 0;
const int comment_padding = 512;
+opus_int32 rate = 48000;
opus_int32 coding_rate = 48000;
ogg_int32_t _packetId;
OpusEncoder *_encoder = 0;
uint8_t *_packet = 0;
ogg_stream_state os;
+char *_filePath = NULL;
FILE *_fileOs = 0;
oe_enc_opt inopt;
OpusHeader header;
@@ -274,6 +289,7 @@ ogg_int64_t enc_granulepos;
ogg_int64_t last_granulepos;
int size_segments;
int last_segments;
+int serialno;
void cleanupRecorder() {
@@ -304,6 +320,10 @@ void cleanupRecorder() {
size_segments = 0;
last_segments = 0;
last_granulepos = 0;
+ if (_filePath) {
+ free(_filePath);
+ _filePath = NULL;
+ }
memset(&os, 0, sizeof(ogg_stream_state));
memset(&inopt, 0, sizeof(oe_enc_opt));
memset(&header, 0, sizeof(OpusHeader));
@@ -311,15 +331,24 @@ void cleanupRecorder() {
memset(&og, 0, sizeof(ogg_page));
}
-int initRecorder(const char *path) {
+int initRecorder(const char *path, opus_int32 sampleRate) {
cleanupRecorder();
+ coding_rate = sampleRate;
+ rate = sampleRate;
+
if (!path) {
+ loge(TAG_VOICE, "path is null");
return 0;
}
- _fileOs = fopen(path, "wb");
+ int length = strlen(path);
+ _filePath = (char*) malloc(length + 1);
+ strcpy(_filePath, path);
+
+ _fileOs = fopen(path, "w");
if (!_fileOs) {
+ loge(TAG_VOICE, "error cannot open file: %s", path);
return 0;
}
@@ -327,26 +356,14 @@ int initRecorder(const char *path) {
inopt.gain = 0;
inopt.endianness = 0;
inopt.copy_comments = 0;
- inopt.rawmode = 1;
- inopt.ignorelength = 1;
+ inopt.rawmode = 0;
+ inopt.ignorelength = 0;
inopt.samplesize = 16;
inopt.channels = 1;
inopt.skip = 0;
comment_init(&inopt.comments, &inopt.comments_length, opus_get_version_string());
- if (rate > 24000) {
- coding_rate = 48000;
- } else if (rate > 16000) {
- coding_rate = 24000;
- } else if (rate > 12000) {
- coding_rate = 16000;
- } else if (rate > 8000) {
- coding_rate = 12000;
- } else {
- coding_rate = 8000;
- }
-
if (rate != coding_rate) {
loge(TAG_VOICE, "Invalid rate");
return 0;
@@ -369,6 +386,7 @@ int initRecorder(const char *path) {
_packet = malloc(max_frame_bytes);
result = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
+ //result = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(10));
if (result != OPUS_OK) {
loge(TAG_VOICE, "Error OPUS_SET_BITRATE returned: %s", opus_strerror(result));
return 0;
@@ -392,7 +410,7 @@ int initRecorder(const char *path) {
header.preskip = (int)(inopt.skip * (48000.0 / coding_rate));
inopt.extraout = (int)(header.preskip * (rate / 48000.0));
- if (ogg_stream_init(&os, rand()) == -1) {
+ if (ogg_stream_init(&os, serialno = rand()) == -1) {
loge(TAG_VOICE, "Error: stream init failed");
return 0;
}
@@ -450,6 +468,135 @@ int initRecorder(const char *path) {
return 1;
}
+void saveResumeData() {
+ if (_filePath == NULL) {
+ return;
+ }
+ const char* ext = ".resume";
+ char* _resumeFilePath = (char*) malloc(strlen(_filePath) + strlen(ext) + 1);
+ strcpy(_resumeFilePath, _filePath);
+ strcat(_resumeFilePath, ext);
+
+ FILE* resumeFile = fopen(_resumeFilePath, "wb");
+ if (!resumeFile) {
+ loge(TAG_VOICE, "error cannot open resume file to write: %s", _resumeFilePath);
+ free(_resumeFilePath);
+ return;
+ }
+ resume_data data;
+ data._packetId = _packetId;
+ data.bytes_written = bytes_written;
+ data.pages_out = pages_out;
+ data.total_samples = total_samples;
+ data.enc_granulepos = enc_granulepos;
+ data.size_segments = size_segments;
+ data.last_segments = last_segments;
+ data.last_granulepos = last_granulepos;
+ data.min_bytes = min_bytes;
+ data.max_frame_bytes = max_frame_bytes;
+ data.serialno = serialno;
+
+ if (fwrite(&data, sizeof(resume_data), 1, resumeFile) != 1) {
+ loge(TAG_VOICE, "error writing resume data to file: %s", _resumeFilePath);
+ }
+ fclose(resumeFile);
+
+ free(_resumeFilePath);
+}
+
+resume_data readResumeData(const char* filePath) {
+
+ const char* ext = ".resume";
+ char* _resumeFilePath = (char*) malloc(strlen(filePath) + strlen(ext) + 1);
+ strcpy(_resumeFilePath, filePath);
+ strcat(_resumeFilePath, ext);
+
+ resume_data data;
+
+ FILE* resumeFile = fopen(_resumeFilePath, "rb");
+ if (!resumeFile) {
+ loge(TAG_VOICE, "error cannot open resume file to read: %s", _resumeFilePath);
+ memset(&data, 0, sizeof(resume_data));
+ free(_resumeFilePath);
+ return data;
+ }
+
+ if (fread(&data, sizeof(resume_data), 1, resumeFile) != 1) {
+ loge(TAG_VOICE, "error cannot read resume file: %s", _resumeFilePath);
+ memset(&data, 0, sizeof(resume_data));
+ }
+
+ fclose(resumeFile);
+ free(_resumeFilePath);
+
+ return data;
+}
+
+int resumeRecorder(const char *path, opus_int32 sampleRate) {
+ cleanupRecorder();
+
+ coding_rate = sampleRate;
+ rate = sampleRate;
+
+ if (!path) {
+ loge("path is null");
+ return 0;
+ }
+
+ int length = strlen(path);
+ _filePath = (char*) malloc(length + 1);
+ strcpy(_filePath, path);
+
+ resume_data resumeData = readResumeData(path);
+ _packetId = resumeData._packetId;
+ bytes_written = resumeData.bytes_written;
+ pages_out = resumeData.pages_out;
+ total_samples = resumeData.total_samples;
+ enc_granulepos = resumeData.enc_granulepos;
+ size_segments = resumeData.size_segments;
+ last_segments = resumeData.last_segments;
+ last_granulepos = resumeData.last_granulepos;
+ min_bytes = resumeData.min_bytes;
+ max_frame_bytes = resumeData.max_frame_bytes;
+ serialno = resumeData.serialno;
+
+ _fileOs = fopen(path, "a");
+ if (!_fileOs) {
+ loge(TAG_VOICE, "error cannot open resume file: %s", path);
+ return 0;
+ }
+
+ int result = OPUS_OK;
+ _encoder = opus_encoder_create(coding_rate, 1, OPUS_APPLICATION_VOIP, &result);
+ if (result != OPUS_OK) {
+ loge(TAG_VOICE, "Error cannot create encoder: %s", opus_strerror(result));
+ return 0;
+ }
+
+ _packet = malloc(max_frame_bytes);
+
+ result = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
+ //result = opus_encoder_ctl(_encoder, OPUS_SET_COMPLEXITY(10));
+ if (result != OPUS_OK) {
+ loge(TAG_VOICE, "Error OPUS_SET_BITRATE returned: %s", opus_strerror(result));
+ return 0;
+ }
+
+#ifdef OPUS_SET_LSB_DEPTH
+ result = opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(max(8, min(24, 16))));
+ if (result != OPUS_OK) {
+ loge(TAG_VOICE, "Warning OPUS_SET_LSB_DEPTH returned: %s", opus_strerror(result));
+ }
+#endif
+
+ if (ogg_stream_init(&os, serialno) == -1) {
+ loge(TAG_VOICE, "Error: stream init failed");
+ return 0;
+ }
+
+ return 1;
+}
+
int writeFrame(uint8_t *framePcmBytes, unsigned int frameByteCount) {
int cur_frame_size = frame_size;
_packetId++;
@@ -535,10 +682,10 @@ int writeFrame(uint8_t *framePcmBytes, unsigned int frameByteCount) {
return 1;
}
-JNIEXPORT int Java_org_thunderdog_challegram_N_startRecord(JNIEnv *env, jclass class, jstring path) {
+JNIEXPORT jint Java_org_thunderdog_challegram_N_startRecord(JNIEnv *env, jclass class, jstring path, jint sampleRate) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
- int result = initRecorder(pathStr);
+ int32_t result = initRecorder(pathStr, sampleRate);
if (pathStr != 0) {
(*env)->ReleaseStringUTFChars(env, path, pathStr);
@@ -547,12 +694,27 @@ JNIEXPORT int Java_org_thunderdog_challegram_N_startRecord(JNIEnv *env, jclass c
return result;
}
-JNIEXPORT int Java_org_thunderdog_challegram_N_writeFrame(JNIEnv *env, jclass class, jobject frame, jint len) {
+JNIEXPORT jint Java_org_thunderdog_challegram_N_resumeRecord(JNIEnv *env, jclass class, jstring path, jint sampleRate) {
+ const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
+
+ int32_t result = resumeRecorder(pathStr, sampleRate);
+
+ if (pathStr != 0) {
+ (*env)->ReleaseStringUTFChars(env, path, pathStr);
+ }
+
+ return result;
+}
+
+JNIEXPORT jint Java_org_thunderdog_challegram_N_writeFrame(JNIEnv *env, jclass class, jobject frame, jint len) {
jbyte *frameBytes = (*env)->GetDirectBufferAddress(env, frame);
return writeFrame((uint8_t *) frameBytes, (size_t) len);
}
-JNIEXPORT void Java_org_thunderdog_challegram_N_stopRecord(JNIEnv *env, jclass class) {
+JNIEXPORT void Java_org_thunderdog_challegram_N_stopRecord(JNIEnv *env, jclass class, jboolean allowResuming) {
+ if (allowResuming && _filePath != NULL) {
+ saveResumeData();
+ }
cleanupRecorder();
}
@@ -663,11 +825,11 @@ JNIEXPORT void Java_org_thunderdog_challegram_N_readOpusFile(JNIEnv *env, jclass
(*env)->ReleaseIntArrayElements(env, args, argsArr, 0);
}
-JNIEXPORT int Java_org_thunderdog_challegram_N_seekOpusFile(JNIEnv *env, jclass class, jfloat position) {
+JNIEXPORT jint Java_org_thunderdog_challegram_N_seekOpusFile(JNIEnv *env, jclass class, jfloat position) {
return seekPlayer(position);
}
-JNIEXPORT int Java_org_thunderdog_challegram_N_openOpusFile(JNIEnv *env, jclass class, jstring path) {
+JNIEXPORT jint Java_org_thunderdog_challegram_N_openOpusFile(JNIEnv *env, jclass class, jstring path) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
int result = initPlayer(pathStr);
@@ -679,7 +841,7 @@ JNIEXPORT int Java_org_thunderdog_challegram_N_openOpusFile(JNIEnv *env, jclass
return result;
}
-JNIEXPORT int Java_org_thunderdog_challegram_N_isOpusFile(JNIEnv *env, jclass class, jstring path) {
+JNIEXPORT jint Java_org_thunderdog_challegram_N_isOpusFile(JNIEnv *env, jclass class, jstring path) {
const char *pathStr = (*env)->GetStringUTFChars(env, path, 0);
int result = 0;
diff --git a/app/src/main/java/org/thunderdog/challegram/N.java b/app/src/main/java/org/thunderdog/challegram/N.java
index 8503e9651c..07b4b85f89 100644
--- a/app/src/main/java/org/thunderdog/challegram/N.java
+++ b/app/src/main/java/org/thunderdog/challegram/N.java
@@ -81,9 +81,22 @@ public static int pinBitmapIfNeeded (Bitmap bitmap) {
// TODO remove rendering, because it is no longer used
// audio.c
- public static native int startRecord (String path);
+ public static int startRecord (String path) {
+ return startRecord(path, 48000);
+ }
+
+ public static int resumeRecord (String path) {
+ return resumeRecord(path, 48000);
+ }
+
+ public static void stopRecord () {
+ stopRecord(false);
+ }
+
+ public static native int startRecord (String path, int sampleRate);
+ public static native int resumeRecord (String path, int sampleRate);
public static native int writeFrame (ByteBuffer frame, int len);
- public static native void stopRecord ();
+ public static native void stopRecord (boolean allowResuming);
public static native int openOpusFile (String path);
public static native int seekOpusFile (float position);
public static native int isOpusFile (String path);
diff --git a/app/src/main/java/org/thunderdog/challegram/U.java b/app/src/main/java/org/thunderdog/challegram/U.java
index 26221712ec..e9341bd41c 100644
--- a/app/src/main/java/org/thunderdog/challegram/U.java
+++ b/app/src/main/java/org/thunderdog/challegram/U.java
@@ -112,6 +112,7 @@
import org.thunderdog.challegram.loader.ImageReader;
import org.thunderdog.challegram.loader.ImageStrictCache;
import org.thunderdog.challegram.mediaview.data.MediaItem;
+import org.thunderdog.challegram.telegram.RandomAccessDataSource;
import org.thunderdog.challegram.telegram.Tdlib;
import org.thunderdog.challegram.telegram.TdlibDataSource;
import org.thunderdog.challegram.telegram.TdlibDelegate;
@@ -756,6 +757,10 @@ public static MediaSource newMediaSource (int accountId, @Nullable TdApi.File fi
}
}
+ public static MediaSource newMediaSource (RandomAccessFile file) {
+ return new ProgressiveMediaSource.Factory(new RandomAccessDataSource.Factory(file)).createMediaSource(newMediaItem(Uri.EMPTY));
+ }
+
public static MediaSource newMediaSource (int accountId, int fileId) {
return new ProgressiveMediaSource.Factory(new TdlibDataSource.Factory()).createMediaSource(newMediaItem(TdlibDataSource.UriFactory.create(accountId, fileId)));
}
@@ -1848,7 +1853,7 @@ private static boolean moveDir (File fromDir, File toDir) {
return successCount == innerFiles.length && fromDir.delete();
}
- private static boolean moveFile (File fromFile, File toFile) {
+ public static boolean moveFile (File fromFile, File toFile) {
if (fromFile.renameTo(toFile)) {
return true;
}
@@ -3490,6 +3495,16 @@ public static long getTotalExternalMemorySize () {
}
}
+ public static int getStreamVolume (int stream) {
+ final AudioManager audioManager = (AudioManager) UI.getContext().getSystemService(Context.AUDIO_SERVICE);
+ return audioManager.getStreamVolume(stream);
+ }
+
+ public static void adjustStreamVolume (int stream, int volume, int flags) {
+ final AudioManager audioManager = (AudioManager) UI.getContext().getSystemService(Context.AUDIO_SERVICE);
+ audioManager.adjustStreamVolume(stream, volume, flags);
+ }
+
// ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION opened, but the permission is still not granted. Ignore until the app restarts.
public static boolean canReadFile (String url) {
diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java
index 03fd36d087..74d9816629 100644
--- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java
+++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageView.java
@@ -94,6 +94,7 @@ public class MessageView extends SparseDrawableView implements Destroyable, Draw
private static final int FLAG_LONG_PRESSED = 1 << 4;
private static final int FLAG_DISABLE_MEASURE = 1 << 6;
private static final int FLAG_USE_COMPLEX_RECEIVER = 1 << 7;
+ private static final int FLAG_IGNORE_PARENT_ON_MEASURE = 1 << 8;
private @Nullable TGMessage msg;
@@ -167,6 +168,10 @@ public void setCustomMeasureDisabled (boolean disabled) {
this.flags = BitwiseUtils.setFlag(this.flags, FLAG_DISABLE_MEASURE, disabled);
}
+ public void setParentOnMeasureDisabled (boolean disabled) {
+ this.flags = BitwiseUtils.setFlag(this.flags, FLAG_IGNORE_PARENT_ON_MEASURE, disabled);
+ }
+
@Override
public void performDestroy () {
avatarReceiver.destroy();
@@ -383,7 +388,9 @@ protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
if ((flags & FLAG_DISABLE_MEASURE) != 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
- int width = ((View) getParent()).getMeasuredWidth();
+ int width = BitwiseUtils.hasFlag(flags, FLAG_IGNORE_PARENT_ON_MEASURE) ?
+ MeasureSpec.getSize(widthMeasureSpec) :
+ ((View) getParent()).getMeasuredWidth();
if (msg != null) {
msg.buildLayout(width);
}
diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageViewGroup.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageViewGroup.java
index be3daa4224..b727cfc3d2 100644
--- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessageViewGroup.java
+++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessageViewGroup.java
@@ -30,6 +30,7 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import org.thunderdog.challegram.data.TD;
import org.thunderdog.challegram.data.TGMessage;
import org.thunderdog.challegram.loader.gif.GifReceiver;
import org.thunderdog.challegram.navigation.ViewController;
@@ -271,6 +272,7 @@ public void detach () {
public void setMessage (TGMessage message) {
messageView.setMessage(message);
overlayView.setMessage(message);
+ videoPlayerView.setVisibility(TD.isSelfDestructTypeImmediately(message.getMessage()) ? GONE: VISIBLE);
requestVideo(message);
if (getMeasuredHeight() != messageView.getCurrentHeight()) {
diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java
index 82f7f444e3..0d64518bf5 100644
--- a/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java
+++ b/app/src/main/java/org/thunderdog/challegram/component/chat/MessagesManager.java
@@ -1065,7 +1065,7 @@ public boolean wouldReusePlayList (TdApi.Message fromMessage, boolean isReverse,
}
private boolean filterMedia (TdApi.Message message, int contentType) {
- return !tdlib.messageSending(message) && contentType == message.content.getConstructor();
+ return !TD.isSelfDestructTypeImmediately(message) && !tdlib.messageSending(message) && contentType == message.content.getConstructor();
}
@Override
diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/VoiceInputView.java b/app/src/main/java/org/thunderdog/challegram/component/chat/VoiceInputView.java
deleted file mode 100644
index d36593a777..0000000000
--- a/app/src/main/java/org/thunderdog/challegram/component/chat/VoiceInputView.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * This file is a part of Telegram X
- * Copyright © 2014 (tgx-android@pm.me)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * File created on 04/03/2016 at 13:50
- */
-package org.thunderdog.challegram.component.chat;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnticipateOvershootInterpolator;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-
-import org.thunderdog.challegram.N;
-import org.thunderdog.challegram.R;
-import org.thunderdog.challegram.core.Background;
-import org.thunderdog.challegram.core.Lang;
-import org.thunderdog.challegram.data.TGRecord;
-import org.thunderdog.challegram.helper.Recorder;
-import org.thunderdog.challegram.navigation.ViewController;
-import org.thunderdog.challegram.support.RippleSupport;
-import org.thunderdog.challegram.support.ViewSupport;
-import org.thunderdog.challegram.telegram.TGLegacyAudioManager;
-import org.thunderdog.challegram.theme.ColorId;
-import org.thunderdog.challegram.theme.Theme;
-import org.thunderdog.challegram.tool.Fonts;
-import org.thunderdog.challegram.tool.Screen;
-import org.thunderdog.challegram.tool.Strings;
-import org.thunderdog.challegram.tool.UI;
-import org.thunderdog.challegram.tool.Views;
-
-import me.vkryl.android.AnimatorUtils;
-import me.vkryl.android.animator.FactorAnimator;
-import me.vkryl.android.util.ClickHelper;
-import me.vkryl.android.widget.FrameLayoutFix;
-
-public class VoiceInputView extends FrameLayoutFix implements View.OnClickListener, TGLegacyAudioManager.PlayListener, ClickHelper.Delegate, FactorAnimator.Target {
- private static final long VOICE_START_DELAY = 500l;
- private static final long ANIMATION_START_DELAY = 80l;
-
- public interface Callback {
- void onDiscardVoiceRecord ();
- }
-
- private Paint textPaint;
- private int textOffset, textRight;
- private int waveLeft;
-
- private Callback callback;
- private Waveform waveform;
- private ImageView iconView;
-
- private ClickHelper clickHelper;
-
- public VoiceInputView (Context context) {
- super(context);
-
- textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- textPaint.setTypeface(Fonts.getRobotoRegular());
- textPaint.setTextSize(Screen.dp(15f));
- textOffset = Screen.dp(5f);
- textRight = Screen.dp(39f);
- waveLeft = Screen.dp(66f);
-
- this.clickHelper = new ClickHelper(this);
-
- this.waveform = new Waveform(null, Waveform.MODE_RECT, false);
-
- iconView = new ImageView(context);
- iconView.setId(R.id.btn_discard_record);
- iconView.setScaleType(ImageView.ScaleType.CENTER);
- iconView.setImageResource(R.drawable.baseline_delete_24);
- iconView.setColorFilter(Theme.iconColor());
- iconView.setOnClickListener(this);
- iconView.setLayoutParams(FrameLayoutFix.newParams(Screen.dp(58f), ViewGroup.LayoutParams.MATCH_PARENT, Lang.rtl() ? Gravity.RIGHT : Gravity.LEFT));
- Views.setClickable(iconView);
- RippleSupport.setTransparentSelector(iconView);
-
- addView(iconView);
-
- RelativeLayout.LayoutParams params;
-
- params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Screen.dp(48f));
- params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
- if (Lang.rtl()) {
- params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- params.leftMargin = Screen.dp(55f);
- } else {
- params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
- params.rightMargin = Screen.dp(55f);
- }
-
- setWillNotDraw(false);
- setLayoutParams(params);
- }
-
- public void addThemeListeners (ViewController> c) {
- c.addThemeFilterListener(iconView, ColorId.icon);
- c.addThemeInvalidateListener(this);
- ViewSupport.setThemedBackground(this, ColorId.filling);
- }
-
- private int calculateWaveformWidth () {
- return getMeasuredWidth() - waveLeft - Screen.dp(110f) + Screen.dp(55f);
- }
-
- @Override
- protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (getMeasuredWidth() != 0) {
- waveform.layout(calculateWaveformWidth());
- }
- }
-
- public void setCallback (Callback callback) {
- this.callback = callback;
- }
-
- @Override
- public void onClick (View v) {
- if (v.getId() == R.id.btn_discard_record) {
- if (callback != null) {
- callback.onDiscardVoiceRecord();
- }
- }
- }
-
- @Override
- public void onPlayPause (int fileId, boolean isPlaying, boolean isUpdate) {
- if (record != null && record.getFileId() == fileId && !isPlaying && !isCaught && !ignoreStop) {
- progress = 1f;
- updateProgress();
- invalidate();
- }
- }
-
- @Override
- public boolean needPlayProgress (int fileId) {
- return true;
- }
-
- @Override
- public void onPlayProgress (int fileId, float progress, boolean isUpdate) {
- if (record != null && record.getFileId() == fileId && progress > 0f) {
- this.progress = progress;
- if (!updateProgress()) {
- invalidate();
- }
- }
- }
-
- // Waveform and record stuff
-
- private boolean ignoreStop;
-
- public void ignoreStop () {
- ignoreStop = true;
- }
-
- public void clearData () {
- discardRecord();
- ignoreStop = false;
- progress = 0f;
- hasStartedPlaying = false;
- waveform.setData(null);
- invalidate();
- }
-
- private TGRecord record;
-
- private void setRecord (final TGRecord record) {
- if (this.record != record) {
- if (this.record != null) {
- TGLegacyAudioManager.instance().unsubscribe(this.record.getAudio().getId(), this);
- }
- this.record = record;
- if (record != null) {
- TGLegacyAudioManager.instance().subscribe(record.getAudio().getId(), this);
- }
- }
- }
-
- public void processRecord (final TGRecord record) {
- setRecord(record);
- setDuration(record.getDuration());
- Background.instance().post(() -> {
- final byte[] waveform = record.getWaveform() != null ? record.getWaveform() : N.getWaveform(record.getPath());
- if (waveform != null) {
- UI.post(() -> setWaveform(record, waveform));
- }
- });
- }
-
- public TGRecord getRecord () {
- TGRecord record = this.record;
- setRecord(null);
- return record;
- }
-
- // Waveform animation
-
- private boolean hasStartedPlaying;
-
- private final FactorAnimator waveformAnimator = new FactorAnimator(0, this, overshoot, OPEN_DURATION);
-
- private void setWaveform (final TGRecord record, byte[] waveform) {
- if (this.record == null || !this.record.equals(record)) {
- return;
- }
- record.setWaveform(waveform);
- this.waveform.setData(waveform);
- waveformAnimator.forceFactor(0f);
- waveformAnimator.setStartDelay(ANIMATION_START_DELAY);
- waveformAnimator.animateTo(1f);
- hasStartedPlaying = false;
- invalidate();
- }
-
- @Override
- public void onFactorChanged (int id, float factor, float fraction, FactorAnimator callee) {
- setExpand(factor);
- }
-
- @Override
- public void onFactorChangeFinished (int id, float finalFactor, FactorAnimator callee) {
-
- }
-
- private void playPause () {
- if (!hasStartedPlaying) {
- hasStartedPlaying = true;
- updateProgress();
- invalidate();
- playVoice(record);
- } else {
- /*if (Config.USE_NEW_PLAYER) {
- // TODO
- } else {
- Player.instance().playPause(record.getAudio(), true);
- }*/
- }
- }
-
- private void playVoice (TGRecord record) {
- if (this.record == null || !this.record.equals(record)) {
- return;
- }
-
- /*if (Config.USE_NEW_PLAYER) {
- // TODO
- } else {
- Player.instance().destroy();
- Player.instance().playPause(record.getAudio(), true);
- }*/
- }
-
- public float getExpand () {
- return waveform.getExpand();
- }
-
- public void setExpand (float expand) {
- waveform.setExpand(expand);
- invalidateWave();
- }
-
- private void invalidateWave () {
- invalidate(waveLeft, 0, waveLeft + waveform.getWidth(), getMeasuredHeight());
- }
-
- // Duration shit
-
- private float progress;
- private int duration = -1;
- private int seek = -1;
- private String seekStr;
-
- public void setDuration (int duration) {
- if (this.duration != duration) {
- this.duration = duration;
- updateProgress();
- }
- }
-
- private boolean updateProgress () {
- int seek = (int) ((float) duration * (hasStartedPlaying ? progress : 1f));
- if (this.seek != seek) {
- this.seek = seek;
- seekStr = Strings.buildDuration(seek);
- invalidate();
- return true;
- }
- return false;
- }
-
- @Override
- protected void onDraw (Canvas c) {
- int width = getMeasuredWidth();
- int height = getMeasuredHeight();
- int centerY = (int) ((float) height * .5f);
-
- if (seekStr != null) {
- textPaint.setColor(Theme.textAccentColor());
- c.drawText(seekStr, width - textRight, centerY + textOffset, textPaint);
- }
-
- waveform.draw(c, !hasStartedPlaying ? 1f : progress, waveLeft, centerY);
- }
-
- // Record utils
-
- public void discardRecord () {
- if (record != null) {
- Recorder.instance().delete(record);
- setRecord(null);
- }
- }
-
- @Override
- public boolean needClickAt (View view, float x, float y) {
- return waveform != null && record != null && x >= waveLeft && x < waveLeft + waveform.getWidth();
- }
-
- @Override
- public void onClickAt (View view, float x, float y) {
- if (waveform != null && record != null && x >= waveLeft && x < waveLeft + waveform.getWidth()) {
- playPause();
- }
- }
-
- // Touch events
-
- private boolean isCaught;
-
- @Override
- public boolean onTouchEvent (MotionEvent e) {
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- isCaught = false;
-
- if (Lang.rtl()) {
- if (e.getX() > getMeasuredWidth()) {
- return false;
- }
- } else {
- if (e.getX() < ((RelativeLayout.LayoutParams) getLayoutParams()).leftMargin) {
- return false;
- }
- }
- }
- }
-
- return clickHelper.onTouchEvent(this, e);
- }
-
- // Close Animation
-
- private static final AnticipateOvershootInterpolator overshoot = new AnticipateOvershootInterpolator(3.0f);
-
- private float collapse;
-
- public float getCollapse () {
- return collapse;
- }
-
- public void setCollapse (float collapse) {
- if (this.collapse != collapse) {
- this.collapse = collapse;
- if (waveform != null) {
- waveform.setExpand(1f - overshoot.getInterpolation(collapse));
- invalidateWave();
- }
- }
- }
-
- private ValueAnimator cancel;
- private static final long CLOSE_DURATION = 350l;
- private static final long FADE_DURATION = 150l;
- private static final long OPEN_DURATION = 350l;
-
- public void animateClose () {
- collapse = 0f;
-
- final long startDelay;
-
- if (waveform != null && waveform.getMaxSample() != 0) {
- final float startFactor = getCollapse();
- final float diffFactor = 1f - startFactor;
- cancel = AnimatorUtils.simpleValueAnimator();
- cancel.addUpdateListener(animation -> setCollapse(startFactor + diffFactor * AnimatorUtils.getFraction(animation)));
- cancel.setDuration(CLOSE_DURATION);
- cancel.setInterpolator(AnimatorUtils.LINEAR_INTERPOLATOR);
- cancel.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd (Animator animation) {
- cancel = null;
- }
- });
- startDelay = CLOSE_DURATION - FADE_DURATION;
- } else {
- cancel = null;
- startDelay = 0l;
- }
- Views.animateAlpha(this, 0f, FADE_DURATION, startDelay, AnimatorUtils.DECELERATE_INTERPOLATOR, new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd (Animator animation) {
- setVisibility(View.GONE);
- }
- });
- if (cancel != null) {
- cancel.start();
- }
- }
-
- public void cancelCloseAnimation () {
- if (cancel != null) {
- cancel.cancel();
- cancel = null;
- }
- Views.clearAnimations(this);
- }
-}
diff --git a/app/src/main/java/org/thunderdog/challegram/component/chat/Waveform.java b/app/src/main/java/org/thunderdog/challegram/component/chat/Waveform.java
index 6697322b29..19adf789fe 100644
--- a/app/src/main/java/org/thunderdog/challegram/component/chat/Waveform.java
+++ b/app/src/main/java/org/thunderdog/challegram/component/chat/Waveform.java
@@ -210,6 +210,10 @@ public void destroy () {
}
public void draw (Canvas c, float progress, int startX, int centerY) {
+ draw(c, progress, startX, centerY, false);
+ }
+
+ public void draw (Canvas c, float progress, int startX, int centerY, boolean hideActive) {
switch (mode) {
case MODE_BITMAP: {
if (chunks == null || bitmap == null || bitmap.isRecycled()) {
@@ -226,16 +230,20 @@ public void draw (Canvas c, float progress, int startX, int centerY) {
break;
}
if (progress == 1f) {
- int colorId = isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive;
- c.drawBitmap(bitmap, startX, topY, PorterDuffPaint.get(colorId));
+ if (!hideActive) {
+ int colorId = isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive;
+ c.drawBitmap(bitmap, startX, topY, PorterDuffPaint.get(colorId));
+ }
break;
}
float endX = progress * (float) currentWidth;
- c.save();
- c.clipRect(startX, topY, startX + endX, topY + bitmap.getHeight());
- int colorId = isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive;
- c.drawBitmap(bitmap, startX, topY, PorterDuffPaint.get(colorId));
- c.restore();
+ if (!hideActive) {
+ c.save();
+ c.clipRect(startX, topY, startX + endX, topY + bitmap.getHeight());
+ int colorId = isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive;
+ c.drawBitmap(bitmap, startX, topY, PorterDuffPaint.get(colorId));
+ c.restore();
+ }
c.save();
c.clipRect(startX + endX, topY, startX + bitmap.getWidth(), topY + bitmap.getHeight());
c.drawBitmap(bitmap, startX, topY, paint);
@@ -248,6 +256,9 @@ public void draw (Canvas c, float progress, int startX, int centerY) {
}
int cx = startX;
if (progress == 0f || progress == 1f) {
+ if (hideActive && progress == 1f ) {
+ break;
+ }
paint.setColor(Theme.getColor(progress == 0f ? (isOutBubble ? ColorId.bubbleOut_waveformInactive : ColorId.waveformInactive) : (isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive)));
for (Chunk chunk : chunks) {
chunk.draw(c, cx, centerY, expandFactor, paint);
@@ -259,20 +270,22 @@ public void draw (Canvas c, float progress, int startX, int centerY) {
int topY = centerY - bound;
int bottomY = centerY + bound;
float endX = startX + progress * (float) currentWidth;
- c.save();
- c.clipRect(startX, topY, endX, bottomY);
- paint.setColor(Theme.getColor(isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive));
int i = 0;
- for (Chunk chunk : chunks) {
- chunk.draw(c, cx, centerY, expandFactor, paint);
- cx += width + spacing;
- if (cx > endX) {
- cx -= width + spacing;
- break;
+ if (!hideActive) {
+ c.save();
+ c.clipRect(startX, topY, endX, bottomY);
+ paint.setColor(Theme.getColor(isOutBubble ? ColorId.bubbleOut_waveformActive : ColorId.waveformActive));
+ for (Chunk chunk : chunks) {
+ chunk.draw(c, cx, centerY, expandFactor, paint);
+ cx += width + spacing;
+ if (cx > endX) {
+ cx -= width + spacing;
+ break;
+ }
+ i++;
}
- i++;
+ c.restore();
}
- c.restore();
c.save();
c.clipRect(endX - 1, topY, startX + currentWidth, bottomY);
paint.setColor(Theme.getColor(isOutBubble ? ColorId.bubbleOut_waveformInactive : ColorId.waveformInactive));
diff --git a/app/src/main/java/org/thunderdog/challegram/data/FileComponent.java b/app/src/main/java/org/thunderdog/challegram/data/FileComponent.java
index 5c96829d96..c572a9a4f0 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/FileComponent.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/FileComponent.java
@@ -40,6 +40,7 @@
import org.thunderdog.challegram.loader.ImageVideoThumbFile;
import org.thunderdog.challegram.loader.Receiver;
import org.thunderdog.challegram.mediaview.MediaViewThumbLocation;
+import org.thunderdog.challegram.mediaview.disposable.DisposableMediaViewController;
import org.thunderdog.challegram.player.TGPlayerController;
import org.thunderdog.challegram.telegram.TGLegacyAudioManager;
import org.thunderdog.challegram.telegram.Tdlib;
@@ -257,7 +258,11 @@ public void setVoice (TdApi.VoiceNote voice, TdApi.Message playPauseFile, TGPlay
this.progress.setViewProvider(viewProvider);
}
- if (context.getChatId() == 0) { // Preview mode
+ if (TD.isSelfDestructTypeImmediately(message)) {
+ this.progress.setDownloadedIconRes(R.drawable.baseline_hot_once_24);
+ this.progress.setIgnorePlayPauseClicks(true);
+ this.progress.setNoCloud();
+ } else if (context.getChatId() == 0) { // Preview mode
this.progress.setCurrentState(TdlibFilesManager.STATE_DOWNLOADED_OR_UPLOADED, false);
this.progress.setDownloadedIconRes(R.drawable.baseline_pause_24);
}
@@ -747,7 +752,7 @@ public void draw (T view, Canvas c, int star
}
int waveformLeft = startX + Screen.dp(FileProgressComponent.DEFAULT_FILE_RADIUS) * 2 + getPreviewOffset();
int cy = startY + Screen.dp(FileProgressComponent.DEFAULT_FILE_RADIUS);
- waveform.draw(c, seek, waveformLeft, cy);
+ waveform.draw(c, seek, waveformLeft, cy, isPlaying && TD.isSelfDestructTypeImmediately(message));
boolean align = context.isOutgoingBubble();
if (unreadFactor != 0f) {
int cx = startX + Screen.dp(FileProgressComponent.DEFAULT_FILE_RADIUS);
@@ -792,6 +797,15 @@ public void open () {
}
}
+ @Override
+ public boolean onPlayPauseClick (FileProgressComponent context, View view, TdApi.File file, long messageId) {
+ if (TD.isSelfDestructTypeImmediately(message)) {
+ return DisposableMediaViewController.openMediaOrShowTooltip(view, this.context, (targetView, outRect) -> progress.toRect(outRect));
+ }
+
+ return false;
+ }
+
@Override
public boolean onClick (FileProgressComponent context, View view, TdApi.File file, long messageId) {
if (doc != null) {
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TD.java b/app/src/main/java/org/thunderdog/challegram/data/TD.java
index b11fdd2959..a11087a474 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TD.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TD.java
@@ -5480,6 +5480,10 @@ public static void updateExcludedChatTypes (TdApi.ChatFolder chatFolder, Filter<
chatFolder.excludeArchived = filter.accept(R.id.chatType_archived);
}
+ public static boolean isSelfDestructTypeImmediately (TdApi.Message message) {
+ return message != null && message.selfDestructType != null && message.selfDestructType.getConstructor() == TdApi.MessageSelfDestructTypeImmediately.CONSTRUCTOR;
+ }
+
public static final int[] CHAT_TYPES = {
R.id.chatType_contact,
R.id.chatType_nonContact,
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGAudio.java b/app/src/main/java/org/thunderdog/challegram/data/TGAudio.java
index 064d24f3fa..241c99d9ca 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TGAudio.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TGAudio.java
@@ -55,12 +55,6 @@ public TGAudio (Tdlib tdlib, TdApi.Message msg, TdApi.Audio audio) {
this.audio = audio;
}
- public TGAudio (Tdlib tdlib, TGRecord record) {
- this.tdlib = tdlib;
- this.msg = null;
- this.voice = new TdApi.VoiceNote(record.getDuration(), null, "audio/ogg", null, TD.newFile(record.getFile()));
- }
-
public Tdlib tdlib () {
return tdlib;
}
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java
index c9b9245e4d..62a1d4872a 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessage.java
@@ -2372,7 +2372,7 @@ public final void draw (MessageView view, Canvas c, @NonNull AvatarReceiver avat
RectF rectF = Paints.getRectF();
rectF.set(lineLeft, lineTop, lineRight, lineBottom);
- final int lineColor = APPLY_ACCENT_TO_FORWARDS && !isOutgoingBubble() && fAuthorNameAccentColor != null ? fAuthorNameAccentColor.getVerticalLineColor() : getVerticalLineColor();
+ final int lineColor = getForwardLineColor();
c.drawRoundRect(rectF, lineWidth / 2f, lineWidth / 2f, Paints.fillingPaint(lineColor));
if (mergeTop) {
@@ -3532,7 +3532,11 @@ private void buildForward () {
}
}
- private int getForwardAuthorNameLeft () {
+ public int getForwardLineColor () {
+ return APPLY_ACCENT_TO_FORWARDS && !isOutgoingBubble() && fAuthorNameAccentColor != null ? fAuthorNameAccentColor.getVerticalLineColor() : getVerticalLineColor();
+ }
+
+ public int getForwardAuthorNameLeft () {
return useBubbles() ? getInternalBubbleStartX() + Screen.dp(11f) : xfContentLeft;
}
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGMessageFile.java b/app/src/main/java/org/thunderdog/challegram/data/TGMessageFile.java
index 804080d4ad..7de2945fc7 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TGMessageFile.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessageFile.java
@@ -375,6 +375,12 @@ private CaptionedFile findCaptionedFile (long messageId) {
return null;
}
+ @Nullable
+ public FileComponent findFileComponent (long messageId) {
+ CaptionedFile file = findCaptionedFile(messageId);
+ return file != null ? file.component : null;
+ }
+
private static final int FLAG_CHANGED_LAYOUT = 1;
private static final int FLAG_CHANGED_TEXT_RECEIVERS = 1 << 1;
private static final int FLAG_CHANGED_CONTENT_RECEIVERS = 1 << 2;
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGMessageVideo.java b/app/src/main/java/org/thunderdog/challegram/data/TGMessageVideo.java
index 4e3985ce85..5508a00b1f 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TGMessageVideo.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TGMessageVideo.java
@@ -16,6 +16,7 @@
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
@@ -39,6 +40,7 @@
import org.thunderdog.challegram.loader.Receiver;
import org.thunderdog.challegram.loader.gif.GifFile;
import org.thunderdog.challegram.loader.gif.GifReceiver;
+import org.thunderdog.challegram.mediaview.disposable.DisposableMediaViewController;
import org.thunderdog.challegram.navigation.ViewController;
import org.thunderdog.challegram.player.RoundVideoController;
import org.thunderdog.challegram.player.TGPlayerController;
@@ -98,6 +100,10 @@ private void setVideoNote (TdApi.VideoNote videoNote) {
this.fileProgress.setSimpleListener(this);
this.fileProgress.setViewProvider(overlayViews);
this.fileProgress.setFile(videoNote.video, getMessage());
+ if (TD.isSelfDestructTypeImmediately(getMessage())) {
+ this.fileProgress.setIgnorePlayPauseClicks(true);
+ this.fileProgress.setDownloadedIconRes(R.drawable.baseline_hot_once_24);
+ }
if (videoNote.minithumbnail != null) {
this.miniThumbnail = new ImageFileLocal(videoNote.minithumbnail);
@@ -155,6 +161,16 @@ private void setTrackListenerAttached (boolean attach) {
@Override
protected boolean updateMessageContent (TdApi.Message message, TdApi.MessageContent newContent, boolean isBottomMessage) {
setNotViewed(!((TdApi.MessageVideoNote) newContent).isViewed, true);
+ if (TD.isSelfDestructTypeImmediately(message)) {
+ if (newContent != null && newContent.getConstructor() == TdApi.MessageVideoNote.CONSTRUCTOR) {
+ final var videoNote = ((TdApi.MessageVideoNote) newContent).videoNote;
+ if (videoNote.minithumbnail != null) {
+ this.miniThumbnail = new ImageFileLocal(videoNote.minithumbnail);
+ invalidatePreviewReceiver();
+ }
+ }
+ }
+
return false;
}
@@ -231,6 +247,16 @@ protected final void onChildFactorChanged (int id, float factor, float fraction)
}
}
+ @Override
+ public int getForwardLineColor () {
+ return ColorUtils.alphaColor(1f - isFullSizeAnimator.getFloatValue(), super.getForwardLineColor());
+ }
+
+ @Override
+ public int getForwardAuthorNameLeft () {
+ return (int) (super.getForwardAuthorNameLeft() - Screen.dp(11f) * isFullSizeAnimator.getFloatValue());
+ }
+
public void setFullSizeAnimatorDuration (long fullSizeAnimatorDuration) {
this.fullSizeAnimatorDuration = fullSizeAnimatorDuration;
}
@@ -249,11 +275,20 @@ public void autoDownloadContent (TdApi.ChatType type) {
fileProgress.downloadAutomatically(type);
}
+ @Override
+ public boolean onPlayPauseClick (FileProgressComponent context, View view, TdApi.File file, long messageId) {
+ return onClick(context, view, file, messageId);
+ }
+
@Override
public boolean onClick (FileProgressComponent context, View view, TdApi.File file, long messageId) {
if (Config.ROUND_VIDEOS_PLAYBACK_SUPPORTED) {
if (view.getParent() instanceof MessageViewGroup) {
- tdlib.context().player().playPauseMessage(tdlib, msg, manager);
+ if (TD.isSelfDestructTypeImmediately(getMessage())) {
+ DisposableMediaViewController.openMediaOrShowTooltip(view, this, (targetView, outRect) -> fileProgress.toRect(outRect));
+ } else {
+ tdlib.context().player().playPauseMessage(tdlib, msg, manager);
+ }
}
} else {
U.openFile(manager.controller(), "video.mp4", new File(file.local.path), "video/mp4", 0);
@@ -487,7 +522,27 @@ protected void drawContent (MessageView view, Canvas c, int startX, int startY,
if (preview.needPlaceholder()) {
preview.drawPlaceholderRounded(c, videoSize / 2f);
}
- preview.draw(c);
+
+ final boolean drawSpoiler = TD.isSelfDestructTypeImmediately(getMessage());
+ final float radius = videoSize / 2f;
+ final float cx = preview.centerX(), cy = preview.centerY();
+
+ if (drawSpoiler) {
+ Receiver spoilerReceiver;
+ if (preview instanceof DoubleImageReceiver) {
+ spoilerReceiver = ((DoubleImageReceiver) preview).getPreview();
+ } else {
+ spoilerReceiver = preview;
+ }
+ if (spoilerReceiver.isEmpty()) {
+ spoilerReceiver = preview;
+ }
+ spoilerReceiver.draw(c);
+
+ c.drawCircle(cx, cy, radius, Paints.fillingPaint(Theme.getColor(ColorId.spoilerMediaOverlay)));
+ } else {
+ preview.draw(c);
+ }
}
@Override
@@ -523,8 +578,9 @@ protected void drawOverlay (MessageView view, Canvas c, int startX, int startY,
c.drawCircle(circleX, textY + Screen.dp(11.5f), Screen.dp(1.5f), Paints.fillingPaint(ColorUtils.alphaColor(viewFactor, useBubbles ? 0xffffffff : Theme.getColor(ColorId.online))));
}
+ final boolean drawSpoiler = TD.isSelfDestructTypeImmediately(getMessage());
float alpha = (1f - unmuteFactor) * (1f - fileProgress.getBackgroundAlpha());
- if (alpha > 0f) {
+ if (alpha > 0f && !drawSpoiler) {
int radius = Screen.dp(12f);
int centerY = receiver.getBottom() - radius - Screen.dp(10f);
diff --git a/app/src/main/java/org/thunderdog/challegram/data/TGRecord.java b/app/src/main/java/org/thunderdog/challegram/data/TGRecord.java
index aaa3ba9e37..c687881809 100644
--- a/app/src/main/java/org/thunderdog/challegram/data/TGRecord.java
+++ b/app/src/main/java/org/thunderdog/challegram/data/TGRecord.java
@@ -17,20 +17,14 @@
import org.drinkless.tdlib.TdApi;
import org.thunderdog.challegram.telegram.Tdlib;
-import java.io.File;
-
public class TGRecord {
private final Tdlib.Generation generation;
- private File file;
private final int duration;
private byte[] waveform;
- private final TGAudio audio;
public TGRecord (Tdlib tdlib, Tdlib.Generation generation, int duration, byte[] waveform) {
this.generation = generation;
- this.file = new File(generation.destinationPath);
this.duration = duration;
- this.audio = new TGAudio(tdlib, this);
this.waveform = waveform;
}
@@ -43,21 +37,16 @@ public int getDuration () {
return duration;
}
- public File getFile () {
- return file;
- }
-
public String getPath () {
- return file.getPath();
- }
+ if (generation.file.local != null && generation.file.local.isDownloadingCompleted) {
+ return generation.file.local.path;
+ }
- public TGAudio getAudio () {
- return audio;
+ return generation.destinationPath;
}
public void setWaveform (byte[] waveform) {
this.waveform = waveform;
- this.audio.setWaveform(waveform);
}
public byte[] getWaveform () {
@@ -71,10 +60,4 @@ public int getFileId () {
public TdApi.InputFile toInputFile () {
return new TdApi.InputFileId(generation.file.id);
}
-
- public void delete () {
- if (file != null && file.delete()) {
- file = null;
- }
- }
}
diff --git a/app/src/main/java/org/thunderdog/challegram/filegen/VideoGen.java b/app/src/main/java/org/thunderdog/challegram/filegen/VideoGen.java
index 33985e9a19..f35cca1b1f 100644
--- a/app/src/main/java/org/thunderdog/challegram/filegen/VideoGen.java
+++ b/app/src/main/java/org/thunderdog/challegram/filegen/VideoGen.java
@@ -40,6 +40,13 @@
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.VideoEncoderSettings;
+import com.googlecode.mp4parser.BasicContainer;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
+import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
+import com.googlecode.mp4parser.authoring.tracks.AppendTrack;
+import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import com.otaliastudios.transcoder.Transcoder;
import com.otaliastudios.transcoder.TranscoderListener;
import com.otaliastudios.transcoder.common.TrackType;
@@ -71,9 +78,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -661,4 +670,79 @@ private boolean convertVideoSimple (String sourcePath, String destinationPath, V
videoData.editMovie(destinationPath, info.needMute(), info.getRotate(), (double) info.getStartTimeUs() / 1_000_000.0, info.getEndTimeUs() == -1 ? -1 : (double) info.getEndTimeUs() / 1_000_000.0, onProgress, isCancelled) :
videoData.editMovie(destinationPath, info.needMute(), info.getRotate(), onProgress, isCancelled);
}
+
+ public static void appendTwoVideos(String firstVideoPath, String secondVideoPath, String output, boolean needTrimFirstVideo, double startTime, double endTime) {
+ try {
+ Movie[] inMovies = new Movie[2];
+
+ inMovies[0] = MovieCreator.build(firstVideoPath);
+ inMovies[1] = MovieCreator.build(secondVideoPath);
+
+ List