Update to 4.1.1

This commit is contained in:
DrKLO 2017-07-08 19:32:04 +03:00
parent 6a1cf64f6f
commit dd679bd7d1
649 changed files with 47670 additions and 11248 deletions

View File

@ -9,15 +9,17 @@ configurations {
}
dependencies {
compile 'com.google.android.gms:play-services-gcm:10.2.0'
compile 'com.google.android.gms:play-services-maps:10.2.0'
compile 'com.google.android.gms:play-services-vision:10.2.0'
compile 'com.android.support:support-core-ui:25.3.0'
compile 'com.android.support:support-compat:25.3.0'
compile 'com.android.support:support-core-utils:25.3.0'
compile 'com.android.support:support-v13:25.3.0'
compile 'com.android.support:palette-v7:25.3.0'
compile 'net.hockeyapp.android:HockeySDK:4.1.2'
compile 'com.google.android.gms:play-services-gcm:11.0.1'
compile 'com.google.android.gms:play-services-maps:11.0.1'
compile 'com.google.android.gms:play-services-vision:11.0.1'
compile 'com.google.android.gms:play-services-wallet:11.0.1'
compile 'com.google.android.gms:play-services-wearable:11.0.1'
compile 'com.android.support:support-core-ui:25.3.1'
compile 'com.android.support:support-compat:25.3.1'
compile 'com.android.support:support-core-utils:25.3.1'
compile 'com.android.support:support-v13:25.3.1'
compile 'com.android.support:palette-v7:25.3.1'
compile 'net.hockeyapp.android:HockeySDK:4.1.3'
compile 'com.googlecode.mp4parser:isoparser:1.0.6'
compile 'com.stripe:stripe-android:2.0.2'
}
@ -88,7 +90,7 @@ android {
}
}
defaultConfig.versionCode = 957
defaultConfig.versionCode = 1030
sourceSets.debug {
manifest.srcFile 'config/debug/AndroidManifest.xml'
@ -160,7 +162,7 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 25
versionName "3.18.0"
versionName "4.1.1"
externalNativeBuild {
ndkBuild {

View File

@ -22,6 +22,7 @@
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/AppNameBeta"
android:theme="@style/Theme.TMessages.Start"
android:name=".ApplicationLoader"

View File

@ -25,6 +25,7 @@
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/AppNameBeta"
android:theme="@style/Theme.TMessages.Start"
android:name=".ApplicationLoader"

View File

@ -6,6 +6,7 @@
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/AppName"
android:theme="@style/Theme.TMessages.Start"
android:name=".ApplicationLoader"

View File

@ -22,6 +22,7 @@
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/AppName"
android:theme="@style/Theme.TMessages.Start"
android:name=".ApplicationLoader"

View File

@ -25,6 +25,7 @@
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:label="@string/AppName"
android:theme="@style/Theme.TMessages.Start"
android:name=".ApplicationLoader"

View File

@ -108,17 +108,9 @@ include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := WebRtcAec
LOCAL_SRC_FILES := ./libtgvoip/external/libWebRtcAec_android_$(TARGET_ARCH_ABI).a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := voip
LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing -O3
LOCAL_CFLAGS := -O3 -DUSE_KISS_FFT -fexceptions
LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing -O3 -frtti -D__STDC_LIMIT_MACROS
LOCAL_CFLAGS := -O3 -DUSE_KISS_FFT -fexceptions -DWEBRTC_APM_DEBUG_DUMP=0 -DWEBRTC_POSIX -D__STDC_LIMIT_MACROS
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
# LOCAL_CPPFLAGS += -mfloat-abi=softfp -mfpu=neon
@ -138,7 +130,7 @@ endif
MY_DIR := libtgvoip
LOCAL_C_INCLUDES := jni/opus/include jni/boringssl/include/
LOCAL_C_INCLUDES := jni/opus/include jni/boringssl/include/ jni/libtgvoip/webrtc_dsp/
LOCAL_SRC_FILES := \
./libtgvoip/logging.cpp \
@ -160,7 +152,113 @@ LOCAL_SRC_FILES := \
./libtgvoip/os/android/AudioOutputAndroid.cpp \
./libtgvoip/EchoCanceller.cpp \
./libtgvoip/CongestionControl.cpp \
./libtgvoip/VoIPServerConfig.cpp
./libtgvoip/VoIPServerConfig.cpp \
./libtgvoip/audio/Resampler.cpp \
./libtgvoip/NetworkSocket.cpp \
./libtgvoip/os/posix/NetworkSocketPosix.cpp
# WebRTC signal processing
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/common_audio/ring_buffer.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/auto_corr_to_refl_coef.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/auto_correlation.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/complex_bit_reverse.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/complex_fft.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/copy_set_operations.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/cross_correlation.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/division_operations.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/dot_product_with_scale.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/downsample_fast.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/energy.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/filter_ar.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/filter_ar_fast_q12.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/filter_ma_fast_q12.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/get_hanning_window.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/get_scaling_square.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/ilbc_specific_functions.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/levinson_durbin.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/lpc_to_refl_coef.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/min_max_operations.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/randomization_functions.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/real_fft.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/refl_coef_to_lpc.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/resample.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/resample_48khz.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/resample_by_2.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/resample_by_2_internal.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/resample_fractional.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/spl_init.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/spl_inl.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/spl_sqrt.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/spl_sqrt_floor.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/splitting_filter_impl.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/sqrt_of_one_minus_x_squared.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/vector_scaling_operations.c
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/base/checks.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core_c.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aecm/echo_control_mobile.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/delay_estimator.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/delay_estimator_wrapper.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/three_band_filter_bank.cc \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/splitting_filter.cc \
./libtgvoip/webrtc_dsp/webrtc/system_wrappers/source/cpu_features.cc \
./libtgvoip/webrtc_dsp/webrtc/common_audio/sparse_fir_filter.cc \
./libtgvoip/webrtc_dsp/webrtc/common_audio/channel_buffer.cc \
./libtgvoip/webrtc_dsp/webrtc/common_audio/audio_util.cc
#LOCAL_SRC_FILES += \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/block_mean_calculator.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/logging/apm_data_dumper.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aec/aec_resampler.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aec/echo_cancellation.cc \
#./libtgvoip/webrtc_dsp/webrtc/common_audio/wav_header.cc \
#./libtgvoip/webrtc_dsp/webrtc/common_audio/wav_file.cc \
#./libtgvoip/webrtc_dsp/webrtc/base/stringutils.cc
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/noise_suppression_x.c \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/noise_suppression.c \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/ns_core.c \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core_c.c \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core.c \
./libtgvoip/webrtc_dsp/webrtc/common_audio/fft4g.c
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/agc/legacy/analog_agc.c \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/agc/legacy/digital_agc.c
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aecm/aecm_core_neon.cc.neon \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/min_max_operations_neon.c.neon \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/downsample_fast_neon.c.neon \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/cross_correlation_neon.c.neon \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/filter_ar_fast_q12_armv7.S.neon
#LOCAL_SRC_FILES += \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core_neon.cc.neon
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft_neon.cc.neon
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/ns/nsx_core_neon.c.neon
#LOCAL_ARM_NEON := true
endif
ifeq ($(TARGET_ARCH_ABI),armeabi)
LOCAL_SRC_FILES += \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/complex_bit_reverse_arm.S \
./libtgvoip/webrtc_dsp/webrtc/common_audio/signal_processing/spl_sqrt_floor_arm.S
endif
#ifeq ($(TARGET_ARCH_ABI),x86)
#LOCAL_SRC_FILES += \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/aec/aec_core_sse2.cc \
#./libtgvoip/webrtc_dsp/webrtc/modules/audio_processing/utility/ooura_fft_sse2.cc
#endif
include $(BUILD_STATIC_LIBRARY)
@ -295,13 +393,13 @@ include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE := tmessages.26
LOCAL_MODULE := tmessages.27
LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno
LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS
LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11
LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -lOpenSLES
LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad avformat avcodec avutil voip WebRtcAec
LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -lOpenSLES -lEGL -lGLESv2
LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad avformat avcodec avutil voip
LOCAL_SRC_FILES := \
./opus/src/opus.c \

View File

@ -13,6 +13,9 @@ jmethodID jclass_RequestDelegateInternal_run;
jclass jclass_QuickAckDelegate;
jmethodID jclass_QuickAckDelegate_run;
jclass jclass_WriteToSocketDelegate;
jmethodID jclass_WriteToSocketDelegate_run;
jclass jclass_FileLoadOperationDelegate;
jmethodID jclass_FileLoadOperationDelegate_onFinished;
jmethodID jclass_FileLoadOperationDelegate_onFailed;
@ -28,6 +31,7 @@ jmethodID jclass_ConnectionsManager_onInternalPushReceived;
jmethodID jclass_ConnectionsManager_onUpdateConfig;
jmethodID jclass_ConnectionsManager_onBytesSent;
jmethodID jclass_ConnectionsManager_onBytesReceived;
jmethodID jclass_ConnectionsManager_onRequestNewServerIpAndPort;
jint createLoadOpetation(JNIEnv *env, jclass c, jint dc_id, jlong id, jlong volume_id, jlong access_hash, jint local_id, jbyteArray encKey, jbyteArray encIv, jstring extension, jint version, jint size, jstring dest, jstring temp, jobject delegate) {
if (encKey != nullptr && encIv == nullptr || encKey == nullptr && encIv != nullptr || extension == nullptr || dest == nullptr || temp == nullptr) {
@ -157,11 +161,15 @@ jint getCurrentTime(JNIEnv *env, jclass c) {
return ConnectionsManager::getInstance().getCurrentTime();
}
jint isTestBackend(JNIEnv *env, jclass c) {
return ConnectionsManager::getInstance().isTestBackend() ? 1 : 0;
}
jint getTimeDifference(JNIEnv *env, jclass c) {
return ConnectionsManager::getInstance().getTimeDifference();
}
void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject onQuickAck, jint flags, jint datacenterId, jint connetionType, jboolean immediate, jint token) {
void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject onQuickAck, jobject onWriteToSocket, jint flags, jint datacenterId, jint connetionType, jboolean immediate, jint token) {
TL_api_request *request = new TL_api_request();
request->request = (NativeByteBuffer *) object;
if (onComplete != nullptr) {
@ -170,6 +178,9 @@ void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject
if (onQuickAck != nullptr) {
onQuickAck = env->NewGlobalRef(onQuickAck);
}
if (onWriteToSocket != nullptr) {
onWriteToSocket = env->NewGlobalRef(onWriteToSocket);
}
ConnectionsManager::getInstance().sendRequest(request, ([onComplete](TLObject *response, TL_error *error, int32_t networkType) {
TL_api_response *resp = (TL_api_response *) response;
jint ptr = 0;
@ -191,7 +202,11 @@ void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject
if (onQuickAck != nullptr) {
jniEnv->CallVoidMethod(onQuickAck, jclass_QuickAckDelegate_run);
}
}), flags, datacenterId, (ConnectionType) connetionType, immediate, token, onComplete, onQuickAck);
}), ([onWriteToSocket] {
if (onWriteToSocket != nullptr) {
jniEnv->CallVoidMethod(onWriteToSocket, jclass_WriteToSocketDelegate_run);
}
}), flags, datacenterId, (ConnectionType) connetionType, immediate, token, onComplete, onQuickAck, onWriteToSocket);
}
void cancelRequest(JNIEnv *env, jclass c, jint token, jboolean notifyServer) {
@ -220,6 +235,24 @@ void applyDatacenterAddress(JNIEnv *env, jclass c, jint datacenterId, jstring ip
}
}
void setProxySettings(JNIEnv *env, jclass c, jstring address, jint port, jstring username, jstring password) {
const char *addressStr = env->GetStringUTFChars(address, 0);
const char *usernameStr = env->GetStringUTFChars(username, 0);
const char *passwordStr = env->GetStringUTFChars(password, 0);
ConnectionsManager::getInstance().setProxySettings(addressStr, (uint16_t) port, usernameStr, passwordStr);
if (addressStr != 0) {
env->ReleaseStringUTFChars(address, addressStr);
}
if (usernameStr != 0) {
env->ReleaseStringUTFChars(username, usernameStr);
}
if (passwordStr != 0) {
env->ReleaseStringUTFChars(password, passwordStr);
}
}
jint getConnectionState(JNIEnv *env, jclass c) {
return ConnectionsManager::getInstance().getConnectionState();
}
@ -241,7 +274,7 @@ void resumeNetwork(JNIEnv *env, jclass c, jboolean partial) {
}
void updateDcSettings(JNIEnv *env, jclass c) {
ConnectionsManager::getInstance().updateDcSettings(0);
ConnectionsManager::getInstance().updateDcSettings(0, false);
}
void setUseIpv6(JNIEnv *env, jclass c, bool value) {
@ -256,6 +289,10 @@ void setPushConnectionEnabled(JNIEnv *env, jclass c, jboolean value) {
ConnectionsManager::getInstance().setPushConnectionEnabled(value);
}
void applyDnsConfig(JNIEnv *env, jclass c, jint address) {
ConnectionsManager::getInstance().applyDnsConfig((NativeByteBuffer *) address);
}
class Delegate : public ConnectiosManagerDelegate {
void onUpdate() {
@ -299,17 +336,32 @@ class Delegate : public ConnectiosManagerDelegate {
void onBytesSent(int32_t amount, int32_t networkType) {
jniEnv->CallStaticVoidMethod(jclass_ConnectionsManager, jclass_ConnectionsManager_onBytesSent, amount, networkType);
}
void onRequestNewServerIpAndPort(int32_t second) {
jniEnv->CallStaticVoidMethod(jclass_ConnectionsManager, jclass_ConnectionsManager_onRequestNewServerIpAndPort, second);
}
};
void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring configPath, jstring logPath, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) {
void setLangCode(JNIEnv *env, jclass c, jstring langCode) {
const char *langCodeStr = env->GetStringUTFChars(langCode, 0);
ConnectionsManager::getInstance().setLangCode(std::string(langCodeStr));
if (langCodeStr != 0) {
env->ReleaseStringUTFChars(langCode, langCodeStr);
}
}
void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring systemLangCode, jstring configPath, jstring logPath, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) {
const char *deviceModelStr = env->GetStringUTFChars(deviceModel, 0);
const char *systemVersionStr = env->GetStringUTFChars(systemVersion, 0);
const char *appVersionStr = env->GetStringUTFChars(appVersion, 0);
const char *langCodeStr = env->GetStringUTFChars(langCode, 0);
const char *systemLangCodeStr = env->GetStringUTFChars(systemLangCode, 0);
const char *configPathStr = env->GetStringUTFChars(configPath, 0);
const char *logPathStr = env->GetStringUTFChars(logPath, 0);
ConnectionsManager::getInstance().init(version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(configPathStr), std::string(logPathStr), userId, true, enablePushConnection, hasNetwork, networkType);
ConnectionsManager::getInstance().init(version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(systemLangCodeStr), std::string(configPathStr), std::string(logPathStr), userId, true, enablePushConnection, hasNetwork, networkType);
if (deviceModelStr != 0) {
env->ReleaseStringUTFChars(deviceModel, deviceModelStr);
@ -323,6 +375,9 @@ void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring d
if (langCodeStr != 0) {
env->ReleaseStringUTFChars(langCode, langCodeStr);
}
if (systemLangCodeStr != 0) {
env->ReleaseStringUTFChars(systemLangCode, systemLangCodeStr);
}
if (configPathStr != 0) {
env->ReleaseStringUTFChars(configPath, configPathStr);
}
@ -340,16 +395,19 @@ static const char *ConnectionsManagerClassPathName = "org/telegram/tgnet/Connect
static JNINativeMethod ConnectionsManagerMethods[] = {
{"native_getCurrentTimeMillis", "()J", (void *) getCurrentTimeMillis},
{"native_getCurrentTime", "()I", (void *) getCurrentTime},
{"native_isTestBackend", "()I", (void *) isTestBackend},
{"native_getTimeDifference", "()I", (void *) getTimeDifference},
{"native_sendRequest", "(ILorg/telegram/tgnet/RequestDelegateInternal;Lorg/telegram/tgnet/QuickAckDelegate;IIIZI)V", (void *) sendRequest},
{"native_sendRequest", "(ILorg/telegram/tgnet/RequestDelegateInternal;Lorg/telegram/tgnet/QuickAckDelegate;Lorg/telegram/tgnet/WriteToSocketDelegate;IIIZI)V", (void *) sendRequest},
{"native_cancelRequest", "(IZ)V", (void *) cancelRequest},
{"native_cleanUp", "()V", (void *) cleanUp},
{"native_cancelRequestsForGuid", "(I)V", (void *) cancelRequestsForGuid},
{"native_bindRequestToGuid", "(II)V", (void *) bindRequestToGuid},
{"native_applyDatacenterAddress", "(ILjava/lang/String;I)V", (void *) applyDatacenterAddress},
{"native_setProxySettings", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V", (void *) setProxySettings},
{"native_getConnectionState", "()I", (void *) getConnectionState},
{"native_setUserId", "(I)V", (void *) setUserId},
{"native_init", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init},
{"native_init", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init},
{"native_setLangCode", "(Ljava/lang/String;)V", (void *) setLangCode},
{"native_switchBackend", "()V", (void *) switchBackend},
{"native_pauseNetwork", "()V", (void *) pauseNetwork},
{"native_resumeNetwork", "(Z)V", (void *) resumeNetwork},
@ -357,7 +415,8 @@ static JNINativeMethod ConnectionsManagerMethods[] = {
{"native_setUseIpv6", "(Z)V", (void *) setUseIpv6},
{"native_setNetworkAvailable", "(ZI)V", (void *) setNetworkAvailable},
{"native_setPushConnectionEnabled", "(Z)V", (void *) setPushConnectionEnabled},
{"native_setJava", "(Z)V", (void *) setJava}
{"native_setJava", "(Z)V", (void *) setJava},
{"native_applyDnsConfig", "(I)V", (void *) applyDnsConfig}
};
inline int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodsCount) {
@ -405,6 +464,15 @@ extern "C" int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env) {
return JNI_FALSE;
}
jclass_WriteToSocketDelegate = (jclass) env->NewGlobalRef(env->FindClass("org/telegram/tgnet/WriteToSocketDelegate"));
if (jclass_WriteToSocketDelegate == 0) {
return JNI_FALSE;
}
jclass_WriteToSocketDelegate_run = env->GetMethodID(jclass_WriteToSocketDelegate, "run", "()V");
if (jclass_WriteToSocketDelegate_run == 0) {
return JNI_FALSE;
}
jclass_FileLoadOperationDelegate = (jclass) env->NewGlobalRef(env->FindClass("org/telegram/tgnet/FileLoadOperationDelegate"));
if (jclass_FileLoadOperationDelegate == 0) {
return JNI_FALSE;
@ -465,6 +533,10 @@ extern "C" int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env) {
if (jclass_ConnectionsManager_onBytesReceived == 0) {
return JNI_FALSE;
}
jclass_ConnectionsManager_onRequestNewServerIpAndPort = env->GetStaticMethodID(jclass_ConnectionsManager, "onRequestNewServerIpAndPort", "(I)V");
if (jclass_ConnectionsManager_onRequestNewServerIpAndPort == 0) {
return JNI_FALSE;
}
ConnectionsManager::getInstance().setDelegate(new Delegate());
return JNI_TRUE;

View File

@ -252,16 +252,27 @@ jint Java_org_telegram_ui_Components_AnimatedFileDrawable_getVideoFrame(JNIEnv *
//LOGD("decoded frame with w = %d, h = %d, format = %d", info->frame->width, info->frame->height, info->frame->format);
if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_BGRA || info->frame->format == AV_PIX_FMT_YUVJ420P) {
jint *dataArr = env->GetIntArrayElements(data, 0);
int wantedWidth;
int wantedHeight;
if (dataArr != nullptr) {
wantedWidth = dataArr[0];
wantedHeight = dataArr[1];
dataArr[3] = (int) (1000 * info->frame->pkt_pts * av_q2d(info->video_stream->time_base));
env->ReleaseIntArrayElements(data, dataArr, 0);
} else {
AndroidBitmapInfo bitmapInfo;
AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);
wantedWidth = bitmapInfo.width;
wantedHeight = bitmapInfo.height;
}
void *pixels;
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) {
if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_YUVJ420P) {
//LOGD("y %d, u %d, v %d, width %d, height %d", info->frame->linesize[0], info->frame->linesize[2], info->frame->linesize[1], info->frame->width, info->frame->height);
libyuv::I420ToARGB(info->frame->data[0], info->frame->linesize[0], info->frame->data[2], info->frame->linesize[2], info->frame->data[1], info->frame->linesize[1], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height);
if (wantedWidth == info->frame->width && wantedHeight == info->frame->height || wantedWidth == info->frame->height && wantedHeight == info->frame->width) {
libyuv::I420ToARGB(info->frame->data[0], info->frame->linesize[0], info->frame->data[2], info->frame->linesize[2], info->frame->data[1], info->frame->linesize[1], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height);
}
} else if (info->frame->format == AV_PIX_FMT_BGRA) {
libyuv::ABGRToARGB(info->frame->data[0], info->frame->linesize[0], (uint8_t *) pixels, info->frame->width * 4, info->frame->width, info->frame->height);
}

View File

@ -56,6 +56,22 @@ JNIEXPORT void Java_org_telegram_messenger_Utilities_aesIgeEncryption(JNIEnv *en
(*env)->ReleaseByteArrayElements(env, iv, ivBuff, 0);
}
JNIEXPORT jint Java_org_telegram_messenger_Utilities_aesCtrDecryption(JNIEnv *env, jclass class, jobject buffer, jbyteArray key, jbyteArray iv, int offset, int length) {
jbyte *what = (*env)->GetDirectBufferAddress(env, buffer) + offset;
unsigned char *keyBuff = (unsigned char *)(*env)->GetByteArrayElements(env, key, NULL);
unsigned char *ivBuff = (unsigned char *)(*env)->GetByteArrayElements(env, iv, NULL);
AES_KEY akey;
unsigned int num = 0;
uint8_t count[16];
memset(count, 0, 16);
AES_set_encrypt_key(keyBuff, 32 * 8, &akey);
AES_ctr128_encrypt(what, what, length, &akey, ivBuff, count, &num);
(*env)->ReleaseByteArrayElements(env, key, keyBuff, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, iv, ivBuff, JNI_ABORT);
return num;
}
JNIEXPORT jstring Java_org_telegram_messenger_Utilities_readlink(JNIEnv *env, jclass class, jstring path) {
static char buf[1000];
char *fileName = (*env)->GetStringUTFChars(env, path, NULL);

View File

@ -50,6 +50,11 @@ TL_dcOption *TL_dcOption::TLdeserialize(NativeByteBuffer *stream, uint32_t const
void TL_dcOption::readParams(NativeByteBuffer *stream, bool &error) {
flags = stream->readInt32(&error);
ipv6 = (flags & 1) != 0;
media_only = (flags & 2) != 0;
tcpo_only = (flags & 4) != 0;
cdn = (flags & 8) != 0;
isStatic = (flags & 16) != 0;
id = stream->readInt32(&error);
ip_address = stream->readString(&error);
port = stream->readInt32(&error);
@ -57,12 +62,89 @@ void TL_dcOption::readParams(NativeByteBuffer *stream, bool &error) {
void TL_dcOption::serializeToStream(NativeByteBuffer *stream) {
stream->writeInt32(constructor);
flags = ipv6 ? (flags | 1) : (flags &~ 1);
flags = media_only ? (flags | 2) : (flags &~ 2);
flags = tcpo_only ? (flags | 4) : (flags &~ 4);
flags = cdn ? (flags | 8) : (flags &~ 8);
flags = isStatic ? (flags | 16) : (flags &~ 16);
stream->writeInt32(flags);
stream->writeInt32(id);
stream->writeString(ip_address);
stream->writeInt32(port);
}
TL_cdnPublicKey *TL_cdnPublicKey::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
if (TL_cdnPublicKey::constructor != constructor) {
error = true;
DEBUG_E("can't parse magic %x in TL_cdnPublicKey", constructor);
return nullptr;
}
TL_cdnPublicKey *result = new TL_cdnPublicKey();
result->readParams(stream, error);
return result;
}
void TL_cdnPublicKey::readParams(NativeByteBuffer *stream, bool &error) {
dc_id = stream->readInt32(&error);
public_key = stream->readString(&error);
}
void TL_cdnPublicKey::serializeToStream(NativeByteBuffer *stream) {
stream->writeInt32(constructor);
stream->writeInt32(dc_id);
stream->writeString(public_key);
}
TL_cdnConfig *TL_cdnConfig::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
if (TL_cdnConfig::constructor != constructor) {
error = true;
DEBUG_E("can't parse magic %x in TL_cdnConfig", constructor);
return nullptr;
}
TL_cdnConfig *result = new TL_cdnConfig();
result->readParams(stream, error);
return result;
}
void TL_cdnConfig::readParams(NativeByteBuffer *stream, bool &error) {
int magic = stream->readInt32(&error);
if (magic != 0x1cb5c415) {
error = true;
DEBUG_E("wrong Vector magic, got %x", magic);
return;
}
int count = stream->readInt32(&error);
for (int a = 0; a < count; a++) {
TL_cdnPublicKey *object = TL_cdnPublicKey::TLdeserialize(stream, stream->readUint32(&error), error);
if (object == nullptr) {
return;
}
public_keys.push_back(std::unique_ptr<TL_cdnPublicKey>(object));
}
}
void TL_cdnConfig::serializeToStream(NativeByteBuffer *stream) {
stream->writeInt32(constructor);
stream->writeInt32(0x1cb5c415);
int count = public_keys.size();
stream->writeInt32(count);
for (int a = 0; a < count; a++) {
public_keys[a]->serializeToStream(stream);
}
}
bool TL_help_getCdnConfig::isNeedLayer() {
return true;
}
TLObject *TL_help_getCdnConfig::deserializeResponse(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
return TL_cdnConfig::TLdeserialize(stream, constructor, error);
}
void TL_help_getCdnConfig::serializeToStream(NativeByteBuffer *stream) {
stream->writeInt32(constructor);
}
TL_disabledFeature *TL_disabledFeature::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
if (TL_disabledFeature::constructor != constructor) {
error = true;
@ -141,6 +223,12 @@ void TL_config::readParams(NativeByteBuffer *stream, bool &error) {
call_connect_timeout_ms = stream->readInt32(&error);
call_packet_timeout_ms = stream->readInt32(&error);
me_url_prefix = stream->readString(&error);
if ((flags & 4) != 0) {
suggested_lang_code = stream->readString(&error);
}
if ((flags & 4) != 0) {
lang_pack_version = stream->readInt32(&error);
}
magic = stream->readUint32(&error);
if (magic != 0x1cb5c415) {
error = true;
@ -195,6 +283,12 @@ void TL_config::serializeToStream(NativeByteBuffer *stream) {
stream->writeInt32(call_connect_timeout_ms);
stream->writeInt32(call_packet_timeout_ms);
stream->writeString(me_url_prefix);
if ((flags & 4) != 0) {
stream->writeString(suggested_lang_code);
}
if ((flags & 4) != 0) {
stream->writeInt32(lang_pack_version);
}
stream->writeInt32(0x1cb5c415);
count = (uint32_t) disabled_features.size();
stream->writeInt32(count);
@ -235,7 +329,7 @@ User *User::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &
case 0x200250ba:
result = new TL_userEmpty();
break;
case 0xd10d979a:
case 0x2e13f4c3:
result = new TL_user();
break;
default:
@ -289,6 +383,9 @@ void TL_user::readParams(NativeByteBuffer *stream, bool &error) {
if ((flags & 524288) != 0) {
bot_inline_placeholder = stream->readString(&error);
}
if ((flags & 4194304) != 0) {
lang_code = stream->readString(&error);
}
}
void TL_user::serializeToStream(NativeByteBuffer *stream) {
@ -325,6 +422,9 @@ void TL_user::serializeToStream(NativeByteBuffer *stream) {
if ((flags & 524288) != 0) {
stream->writeString(bot_inline_placeholder);
}
if ((flags & 4194304) != 0) {
stream->writeString(lang_code);
}
}
TL_auth_authorization *TL_auth_authorization::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
@ -640,7 +740,7 @@ TL_upload_file::~TL_upload_file() {
}
void TL_upload_file::readParams(NativeByteBuffer *stream, bool &error) {
type = std::unique_ptr<storage_FileType>(storage_FileType::TLdeserialize(stream, stream->readInt32(&error), error));
type = std::unique_ptr<storage_FileType>(storage_FileType::TLdeserialize(stream, stream->readUint32(&error), error));
mtime = stream->readInt32(&error);
bytes = stream->readByteBuffer(true, &error);
}

View File

@ -45,6 +45,11 @@ public:
static const uint32_t constructor = 0x5d8c6cc;
int32_t flags;
bool ipv6;
bool media_only;
bool tcpo_only;
bool cdn;
bool isStatic;
int32_t id;
std::string ip_address;
int32_t port;
@ -54,6 +59,41 @@ public:
void serializeToStream(NativeByteBuffer *stream);
};
class TL_cdnPublicKey : public TLObject {
public:
static const uint32_t constructor = 0xc982eaba;
int32_t dc_id;
std::string public_key;
static TL_cdnPublicKey *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error);
void readParams(NativeByteBuffer *stream, bool &error);
void serializeToStream(NativeByteBuffer *stream);
};
class TL_cdnConfig : public TLObject {
public:
static const uint32_t constructor = 0x5725e40a;
std::vector<std::unique_ptr<TL_cdnPublicKey>> public_keys;
static TL_cdnConfig *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error);
void readParams(NativeByteBuffer *stream, bool &error);
void serializeToStream(NativeByteBuffer *stream);
};
class TL_help_getCdnConfig : public TLObject {
public:
static const uint32_t constructor = 0x52029342;
bool isNeedLayer();
TLObject *deserializeResponse(NativeByteBuffer *stream, uint32_t constructor, bool &error);
void serializeToStream(NativeByteBuffer *stream);
};
class TL_disabledFeature : public TLObject {
public:
@ -70,7 +110,7 @@ public:
class TL_config : public TLObject {
public:
static const uint32_t constructor = 0xcb601684;
static const uint32_t constructor = 0x7feec888;
int32_t flags;
int32_t date;
@ -101,6 +141,8 @@ public:
int32_t call_connect_timeout_ms;
int32_t call_packet_timeout_ms;
std::string me_url_prefix;
std::string suggested_lang_code;
int32_t lang_pack_version;
std::vector<std::unique_ptr<TL_disabledFeature>> disabled_features;
static TL_config *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error);
@ -271,6 +313,7 @@ public:
int32_t bot_info_version;
std::string restriction_reason;
std::string bot_inline_placeholder;
std::string lang_code;
static User *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error);
};
@ -287,7 +330,7 @@ public:
class TL_user : public User {
public:
static const uint32_t constructor = 0xd10d979a;
static const uint32_t constructor = 0x2e13f4c3;
void readParams(NativeByteBuffer *stream, bool &error);
void serializeToStream(NativeByteBuffer *stream);

View File

@ -20,6 +20,7 @@ public:
ByteStream();
~ByteStream();
void append(NativeByteBuffer *buffer);
void append(uint8_t *buffer, uint32_t size);
bool hasData();
void get(NativeByteBuffer *dst);
void discard(uint32_t count);

View File

@ -45,7 +45,7 @@ void Connection::suspendConnection() {
DEBUG_D("connection(%p, dc%u, type %d) suspend", this, currentDatacenter->getDatacenterId(), connectionType);
connectionState = TcpConnectionStageSuspended;
dropConnection();
ConnectionsManager::getInstance().onConnectionClosed(this);
ConnectionsManager::getInstance().onConnectionClosed(this, 0);
firstPacketSent = false;
if (restOfTheData != nullptr) {
restOfTheData->reuse();
@ -216,33 +216,36 @@ void Connection::onReceivedData(NativeByteBuffer *buffer) {
void Connection::connect() {
if (!ConnectionsManager::getInstance().isNetworkAvailable()) {
ConnectionsManager::getInstance().onConnectionClosed(this);
ConnectionsManager::getInstance().onConnectionClosed(this, 0);
return;
}
if ((connectionState == TcpConnectionStageConnected || connectionState == TcpConnectionStageConnecting)) {
return;
}
connectionState = TcpConnectionStageConnecting;
bool ipv6 = ConnectionsManager::getInstance().isIpv6Enabled();
uint32_t ipv6 = ConnectionsManager::getInstance().isIpv6Enabled() ? TcpAddressFlagIpv6 : 0;
uint32_t isStatic = !ConnectionsManager::getInstance().proxyAddress.empty() ? TcpAddressFlagStatic : 0;
if (connectionType == ConnectionTypeDownload) {
currentAddressFlags = 2;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | (ipv6 ? 1 : 0));
currentAddressFlags = TcpAddressFlagDownload | isStatic;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | ipv6);
if (hostAddress.empty()) {
currentAddressFlags = 0;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | (ipv6 ? 1 : 0));
currentAddressFlags = isStatic;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | ipv6);
}
if (hostAddress.empty() && ipv6) {
currentAddressFlags = 2;
ipv6 = 0;
currentAddressFlags = TcpAddressFlagDownload | isStatic;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags);
if (hostAddress.empty()) {
currentAddressFlags = 0;
currentAddressFlags = isStatic;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags);
}
}
} else {
currentAddressFlags = 0;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | (ipv6 ? 1 : 0));
if (ipv6 && hostAddress.empty()) {
currentAddressFlags = isStatic;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags | ipv6);
if (hostAddress.empty() && ipv6) {
ipv6 = 0;
hostAddress = currentDatacenter->getCurrentAddress(currentAddressFlags);
}
}
@ -259,7 +262,7 @@ void Connection::connect() {
lastPacketLength = 0;
wasConnected = false;
hasSomeDataSinceLastConnect = false;
openConnection(hostAddress, hostPort, ipv6, ConnectionsManager::getInstance().currentNetworkType);
openConnection(hostAddress, hostPort, ipv6 != 0, ConnectionsManager::getInstance().currentNetworkType);
if (connectionType == ConnectionTypePush) {
if (isTryingNextPort) {
setTimeout(20);
@ -273,7 +276,7 @@ void Connection::connect() {
if (connectionType == ConnectionTypeUpload) {
setTimeout(25);
} else {
setTimeout(15);
setTimeout(12);
}
}
}
@ -285,6 +288,14 @@ void Connection::reconnect() {
connect();
}
bool Connection::hasUsefullData() {
return usefullData;
}
void Connection::setHasUsefullData() {
usefullData = true;
}
void Connection::sendData(NativeByteBuffer *buff, bool reportAck) {
if (buff == nullptr) {
return;
@ -391,7 +402,8 @@ void Connection::onDisconnected(int reason) {
if (connectionState != TcpConnectionStageSuspended && connectionState != TcpConnectionStageIdle) {
connectionState = TcpConnectionStageIdle;
}
ConnectionsManager::getInstance().onConnectionClosed(this);
ConnectionsManager::getInstance().onConnectionClosed(this, reason);
usefullData = false;
uint32_t datacenterId = currentDatacenter->getDatacenterId();
if (connectionState == TcpConnectionStageIdle && connectionType == ConnectionTypeGeneric && (currentDatacenter->isHandshaking() || datacenterId == ConnectionsManager::getInstance().currentDatacenterId || datacenterId == ConnectionsManager::getInstance().movingToDatacenterId)) {

View File

@ -31,6 +31,8 @@ public:
void connect();
void suspendConnection();
void sendData(NativeByteBuffer *buffer, bool reportAck);
bool hasUsefullData();
void setHasUsefullData();
uint32_t getConnectionToken();
ConnectionType getConnectionType();
Datacenter *getDatacenter();
@ -68,6 +70,7 @@ private:
bool wasConnected = false;
uint32_t willRetryConnectCount = 5;
Timer *reconnectTimer;
bool usefullData = false;
AES_KEY encryptKey;
uint8_t encryptIv[16];

View File

@ -13,6 +13,7 @@
#include <memory.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "ByteStream.h"
#include "ConnectionSocket.h"
#include "FileLog.h"
@ -21,6 +22,7 @@
#include "EventObject.h"
#include "Timer.h"
#include "NativeByteBuffer.h"
#include "BuffersStorage.h"
#ifndef EPOLLRDHUP
#define EPOLLRDHUP 0x2000
@ -45,34 +47,81 @@ ConnectionSocket::~ConnectionSocket() {
void ConnectionSocket::openConnection(std::string address, uint16_t port, bool ipv6, int32_t networkType) {
currentNetworkType = networkType;
isIpv6 = ipv6;
currentAddress = address;
currentPort = port;
int epolFd = ConnectionsManager::getInstance().epolFd;
ConnectionsManager::getInstance().attachConnection(this);
if ((socketFd = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0)) < 0) {
DEBUG_E("connection(%p) can't create socket", this);
closeSocket(1);
return;
}
memset(&socketAddress, 0, sizeof(sockaddr_in));
memset(&socketAddress6, 0, sizeof(sockaddr_in6));
if (ipv6) {
socketAddress6.sin6_family = AF_INET6;
socketAddress6.sin6_port = htons(port);
if (inet_pton(AF_INET6, address.c_str(), &socketAddress6.sin6_addr.s6_addr) != 1) {
DEBUG_E("connection(%p) bad ipv6 %s", this, address.c_str());
if (!ConnectionsManager::getInstance().proxyAddress.empty()) {
std::string &proxyAddress = ConnectionsManager::getInstance().proxyAddress;
if ((socketFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
DEBUG_E("connection(%p) can't create proxy socket", this);
closeSocket(1);
return;
}
} else {
proxyAuthState = 1;
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(port);
if (inet_pton(AF_INET, address.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
DEBUG_E("connection(%p) bad ipv4 %s", this, address.c_str());
socketAddress.sin_port = htons(ConnectionsManager::getInstance().proxyPort);
bool continueCheckAddress;
if (inet_pton(AF_INET, proxyAddress.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
continueCheckAddress = true;
DEBUG_D("connection(%p) not ipv4 address %s", this, proxyAddress.c_str());
} else {
continueCheckAddress = false;
}
if (continueCheckAddress) {
if (inet_pton(AF_INET6, proxyAddress.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
continueCheckAddress = true;
DEBUG_D("connection(%p) not ipv6 address %s", this, proxyAddress.c_str());
} else {
continueCheckAddress = false;
}
if (continueCheckAddress) {
struct hostent *he;
if ((he = gethostbyname(proxyAddress.c_str())) == nullptr) {
DEBUG_E("connection(%p) can't resolve host %s address", this, proxyAddress.c_str());
closeSocket(1);
return;
}
struct in_addr **addr_list = (struct in_addr **) he->h_addr_list;
if (addr_list[0] != nullptr) {
socketAddress.sin_addr.s_addr = addr_list[0]->s_addr;
DEBUG_D("connection(%p) resolved host %s address %x", this, proxyAddress.c_str(), addr_list[0]->s_addr);
} else {
DEBUG_E("connection(%p) can't resolve host %s address", this, proxyAddress.c_str());
closeSocket(1);
return;
}
}
}
} else {
proxyAuthState = 0;
if ((socketFd = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0)) < 0) {
DEBUG_E("connection(%p) can't create socket", this);
closeSocket(1);
return;
}
if (ipv6) {
socketAddress6.sin6_family = AF_INET6;
socketAddress6.sin6_port = htons(port);
if (inet_pton(AF_INET6, address.c_str(), &socketAddress6.sin6_addr.s6_addr) != 1) {
DEBUG_E("connection(%p) bad ipv6 %s", this, address.c_str());
closeSocket(1);
return;
}
} else {
socketAddress.sin_family = AF_INET;
socketAddress.sin_port = htons(port);
if (inet_pton(AF_INET, address.c_str(), &socketAddress.sin_addr.s_addr) != 1) {
DEBUG_E("connection(%p) bad ipv4 %s", this, address.c_str());
closeSocket(1);
return;
}
}
}
int yes = 1;
@ -119,6 +168,7 @@ void ConnectionSocket::closeSocket(int reason) {
}
socketFd = -1;
}
proxyAuthState = 0;
onConnectedSent = false;
outgoingByteStream->clean();
onDisconnected(reason);
@ -143,10 +193,58 @@ void ConnectionSocket::onEvent(uint32_t events) {
if (readCount > 0) {
buffer->limit((uint32_t) readCount);
lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis();
if (ConnectionsManager::getInstance().delegate != nullptr) {
ConnectionsManager::getInstance().delegate->onBytesReceived(readCount, currentNetworkType);
if (proxyAuthState == 2) {
if (readCount == 2) {
uint8_t auth_method = buffer->bytes()[1];
if (auth_method == 0xff) {
closeSocket(1);
DEBUG_E("connection(%p) unsupported proxy auth method", this);
} else if (auth_method == 0x02) {
DEBUG_D("connection(%p) proxy auth required", this);
proxyAuthState = 3;
} else if (auth_method == 0x00) {
proxyAuthState = 5;
}
adjustWriteOp();
} else {
closeSocket(1);
DEBUG_E("connection(%p) invalid proxy response on state 2", this);
}
} else if (proxyAuthState == 4) {
if (readCount == 2) {
uint8_t auth_method = buffer->bytes()[1];
if (auth_method != 0x00) {
closeSocket(1);
DEBUG_E("connection(%p) auth invalid", this);
} else {
proxyAuthState = 5;
}
adjustWriteOp();
} else {
closeSocket(1);
DEBUG_E("connection(%p) invalid proxy response on state 4", this);
}
} else if (proxyAuthState == 6) {
if (readCount > 2) {
uint8_t status = buffer->bytes()[1];
if (status == 0x00) {
DEBUG_D("connection(%p) connected via proxy", this);
proxyAuthState = 0;
adjustWriteOp();
} else {
closeSocket(1);
DEBUG_E("connection(%p) invalid proxy status on state 6, 0x%x", this, status);
}
} else {
closeSocket(1);
DEBUG_E("connection(%p) invalid proxy response on state 6", this);
}
} else if (proxyAuthState == 0) {
if (ConnectionsManager::getInstance().delegate != nullptr) {
ConnectionsManager::getInstance().delegate->onBytesReceived(readCount, currentNetworkType);
}
onReceivedData(buffer);
}
onReceivedData(buffer);
}
if (readCount != READ_BUFFER_SIZE) {
break;
@ -159,29 +257,77 @@ void ConnectionSocket::onEvent(uint32_t events) {
closeSocket(1);
return;
} else {
if (!onConnectedSent) {
lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis();
onConnected();
onConnectedSent = true;
}
NativeByteBuffer *buffer = ConnectionsManager::getInstance().networkBuffer;
buffer->clear();
outgoingByteStream->get(buffer);
buffer->flip();
uint32_t remaining = buffer->remaining();
if (remaining) {
ssize_t sentLength;
if ((sentLength = send(socketFd, buffer->bytes(), remaining, 0)) < 0) {
DEBUG_E("connection(%p) send failed", this);
closeSocket(1);
return;
} else {
if (ConnectionsManager::getInstance().delegate != nullptr) {
ConnectionsManager::getInstance().delegate->onBytesSent(sentLength, currentNetworkType);
if (proxyAuthState != 0) {
static uint8_t buffer[1024];
if (proxyAuthState == 1) {
lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis();
proxyAuthState = 2;
buffer[0] = 0x05;
buffer[1] = 0x02;
buffer[2] = 0x00;
buffer[3] = 0x02;
if (send(socketFd, buffer, 4, 0) < 0) {
DEBUG_E("connection(%p) send failed", this);
closeSocket(1);
return;
}
outgoingByteStream->discard((uint32_t) sentLength);
adjustWriteOp();
} else if (proxyAuthState == 3) {
buffer[0] = 0x01;
uint8_t len1 = (uint8_t) ConnectionsManager::getInstance().proxyUser.length();
uint8_t len2 = (uint8_t) ConnectionsManager::getInstance().proxyPassword.length();
buffer[1] = len1;
memcpy(&buffer[2], ConnectionsManager::getInstance().proxyUser.c_str(), len1);
buffer[2 + len1] = len2;
memcpy(&buffer[3 + len1], ConnectionsManager::getInstance().proxyPassword.c_str(), len2);
proxyAuthState = 4;
if (send(socketFd, buffer, 3 + len1 + len2, 0) < 0) {
DEBUG_E("connection(%p) send failed", this);
closeSocket(1);
return;
}
adjustWriteOp();
} else if (proxyAuthState == 5) {
buffer[0] = 0x05;
buffer[1] = 0x01;
buffer[2] = 0x00;
buffer[3] = (uint8_t) (isIpv6 ? 0x04 : 0x01);
uint16_t networkPort = ntohs(currentPort);
inet_pton(isIpv6 ? AF_INET6 : AF_INET, currentAddress.c_str(), &buffer[4]);
memcpy(&buffer[4 + (isIpv6 ? 16 : 4)], &networkPort, sizeof(uint16_t));
proxyAuthState = 6;
if (send(socketFd, buffer, 4 + (isIpv6 ? 16 : 4) + 2, 0) < 0) {
DEBUG_E("connection(%p) send failed", this);
closeSocket(1);
return;
}
adjustWriteOp();
}
} else {
if (!onConnectedSent) {
lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis();
onConnected();
onConnectedSent = true;
}
NativeByteBuffer *buffer = ConnectionsManager::getInstance().networkBuffer;
buffer->clear();
outgoingByteStream->get(buffer);
buffer->flip();
uint32_t remaining = buffer->remaining();
if (remaining) {
ssize_t sentLength;
if ((sentLength = send(socketFd, buffer->bytes(), remaining, 0)) < 0) {
DEBUG_E("connection(%p) send failed", this);
closeSocket(1);
return;
} else {
if (ConnectionsManager::getInstance().delegate != nullptr) {
ConnectionsManager::getInstance().delegate->onBytesSent(sentLength, currentNetworkType);
}
outgoingByteStream->discard((uint32_t) sentLength);
adjustWriteOp();
}
}
}
}
@ -196,6 +342,13 @@ void ConnectionSocket::onEvent(uint32_t events) {
}
}
void ConnectionSocket::writeBuffer(uint8_t *data, uint32_t size) {
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(size);
buffer->writeBytes(data, size);
outgoingByteStream->append(buffer);
adjustWriteOp();
}
void ConnectionSocket::writeBuffer(NativeByteBuffer *buffer) {
outgoingByteStream->append(buffer);
adjustWriteOp();
@ -203,7 +356,7 @@ void ConnectionSocket::writeBuffer(NativeByteBuffer *buffer) {
void ConnectionSocket::adjustWriteOp() {
eventMask.events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET;
if (outgoingByteStream->hasData()) {
if (proxyAuthState == 0 && (outgoingByteStream->hasData() || !onConnectedSent) || proxyAuthState == 1 || proxyAuthState == 3 || proxyAuthState == 5) {
eventMask.events |= EPOLLOUT;
}
eventMask.data.ptr = eventObject;
@ -218,6 +371,10 @@ void ConnectionSocket::setTimeout(time_t time) {
lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis();
}
time_t ConnectionSocket::getTimeout() {
return timeout;
}
void ConnectionSocket::checkTimeout(int64_t now) {
if (timeout != 0 && (now - lastEventTime) > (int64_t) timeout * 1000) {
closeSocket(2);

View File

@ -24,9 +24,11 @@ public:
ConnectionSocket();
virtual ~ConnectionSocket();
void writeBuffer(uint8_t *data, uint32_t size);
void writeBuffer(NativeByteBuffer *buffer);
void openConnection(std::string address, uint16_t port, bool ipv6, int32_t networkType);
void setTimeout(time_t timeout);
time_t getTimeout();
bool isDisconnected();
void dropConnection();
@ -43,11 +45,16 @@ private:
struct sockaddr_in socketAddress;
struct sockaddr_in6 socketAddress6;
int socketFd = -1;
time_t timeout = 15;
time_t timeout = 12;
bool onConnectedSent = false;
int64_t lastEventTime = 0;
EventObject *eventObject;
int32_t currentNetworkType;
bool isIpv6;
std::string currentAddress;
uint16_t currentPort;
uint8_t proxyAuthState;
bool checkSocketError();
void closeSocket(int reason);

View File

@ -144,7 +144,7 @@ int ConnectionsManager::callEvents(int64_t now) {
if (!networkPaused) {
return 1000;
}
int32_t timeToPushPing = (sendingPushPing ? 30000 : 60000 * 3) - abs(now - lastPushPingTime);
int32_t timeToPushPing = (sendingPushPing ? 30000 : 60000 * 3) - llabs(now - lastPushPingTime);
if (timeToPushPing <= 0) {
return 1000;
}
@ -253,7 +253,7 @@ void ConnectionsManager::select() {
sendPing(datacenter, false);
}
if (abs((int32_t) (now / 1000) - lastDcUpdateTime) >= DC_UPDATE_TIME) {
updateDcSettings(0);
updateDcSettings(0, false);
}
processRequestQueue(0, 0);
} else if (!datacenter->isHandshaking()) {
@ -384,11 +384,7 @@ void ConnectionsManager::loadConfig() {
movingToDatacenterId = DEFAULT_DATACENTER_ID;
}
void ConnectionsManager::saveConfig() {
if (config == nullptr) {
config = new Config("tgnet.dat");
}
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(32 * 1024);
void ConnectionsManager::saveConfigInternal(NativeByteBuffer *buffer) {
buffer->writeInt32(configVersion);
buffer->writeBool(testBackend);
Datacenter *currentDatacenter = getDatacenterWithId(currentDatacenterId);
@ -414,6 +410,17 @@ void ConnectionsManager::saveConfig() {
iter->second->serializeToStream(buffer);
}
}
}
void ConnectionsManager::saveConfig() {
if (config == nullptr) {
config = new Config("tgnet.dat");
}
static NativeByteBuffer *sizeCalculator = new NativeByteBuffer(true);
sizeCalculator->clearCapacity();
saveConfigInternal(sizeCalculator);
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(sizeCalculator->capacity());
saveConfigInternal(buffer);
config->writeConfig(buffer);
buffer->reuse();
}
@ -507,6 +514,10 @@ int32_t ConnectionsManager::getCurrentTime() {
return (int32_t) (getCurrentTimeMillis() / 1000) + timeDifference;
}
bool ConnectionsManager::isTestBackend() {
return testBackend;
}
int32_t ConnectionsManager::getTimeDifference() {
return timeDifference;
}
@ -572,18 +583,41 @@ void ConnectionsManager::cleanUp() {
});
}
void ConnectionsManager::onConnectionClosed(Connection *connection) {
void ConnectionsManager::onConnectionClosed(Connection *connection, int reason) {
Datacenter *datacenter = connection->getDatacenter();
if (connection->getConnectionType() == ConnectionTypeGeneric) {
if (proxyAddress.empty()) {
if (reason == 2) {
disconnectTimeoutAmount += connection->getTimeout();
} else {
disconnectTimeoutAmount += 4;
}
if (disconnectTimeoutAmount >= 20) {
if (!connection->hasUsefullData()) {
requestingSecondAddress = 0;
delegate->onRequestNewServerIpAndPort(requestingSecondAddress);
}
disconnectTimeoutAmount = 0;
}
}
if (datacenter->isHandshaking()) {
datacenter->onHandshakeConnectionClosed(connection);
}
if (datacenter->getDatacenterId() == currentDatacenterId) {
if (networkAvailable) {
if (connectionState != ConnectionStateConnecting) {
connectionState = ConnectionStateConnecting;
if (delegate != nullptr) {
delegate->onConnectionStateChanged(connectionState);
if (proxyAddress.empty()) {
if (connectionState != ConnectionStateConnecting) {
connectionState = ConnectionStateConnecting;
if (delegate != nullptr) {
delegate->onConnectionStateChanged(connectionState);
}
}
} else {
if (connectionState != ConnectionStateConnectingViaProxy) {
connectionState = ConnectionStateConnectingViaProxy;
if (delegate != nullptr) {
delegate->onConnectionStateChanged(connectionState);
}
}
}
} else {
@ -643,7 +677,13 @@ void ConnectionsManager::onConnectionDataReceived(Connection *connection, Native
bool error = false;
if (length == 4) {
int32_t code = data->readInt32(&error);
DEBUG_E("mtproto error = %d", code);
Datacenter *datacenter = connection->getDatacenter();
if (code == -404 && datacenter->isCdnDatacenter) {
datacenter->clear();
DEBUG_D("connection(%p, dc%u, type %d) reset auth key duo to -404 error", connection, datacenter->getDatacenterId(), connection->getConnectionType());
} else {
DEBUG_E("mtproto error = %d", code);
}
connection->reconnect();
return;
}
@ -703,6 +743,10 @@ void ConnectionsManager::onConnectionDataReceived(Connection *connection, Native
processServerResponse(object, messageId, 0, 0, connection, 0, 0);
connection->addProcessedMessageId(messageId);
}
connection->setHasUsefullData();
if (connection->getConnectionType() == ConnectionTypeGeneric) {
disconnectTimeoutAmount = 0;
}
delete object;
}
} else {
@ -740,6 +784,10 @@ void ConnectionsManager::onConnectionDataReceived(Connection *connection, Native
if (!doNotProcess) {
TLObject *object = TLdeserialize(nullptr, messageLength, data);
if (object != nullptr) {
connection->setHasUsefullData();
if (connection->getConnectionType() == ConnectionTypeGeneric) {
disconnectTimeoutAmount = 0;
}
DEBUG_D("connection(%p, dc%u, type %d) received object %s", connection, datacenter->getDatacenterId(), connection->getConnectionType(), typeid(*object).name());
processServerResponse(object, messageId, messageSeqNo, messageServerSalt, connection, 0, 0);
connection->addProcessedMessageId(messageId);
@ -980,9 +1028,11 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag
TL_error *error2 = hasResult ? dynamic_cast<TL_error *>(response->result.get()) : nullptr;
if (error != nullptr) {
allowInitConnection = false;
static std::string authRestart = "AUTH_RESTART";
bool processEvenFailed = error->error_code == 500 && error->error_message.find(authRestart) != std::string::npos;
DEBUG_E("request %p rpc error %d: %s", request, error->error_code, error->error_message.c_str());
if ((request->requestFlags & RequestFlagFailOnServerErrors) == 0) {
if ((request->requestFlags & RequestFlagFailOnServerErrors) == 0 || processEvenFailed) {
if (error->error_code == 500 || error->error_code < 0) {
discardResponse = true;
request->minStartTime = request->startTime + (request->serverFailureCount > 10 ? 10 : request->serverFailureCount);
@ -1378,7 +1428,7 @@ int32_t ConnectionsManager::sendRequestInternal(TLObject *object, onCompleteFunc
delete object;
return 0;
}
Request *request = new Request(lastRequestToken++, connetionType, flags, datacenterId, onComplete, onQuickAck);
Request *request = new Request(lastRequestToken++, connetionType, flags, datacenterId, onComplete, onQuickAck, nullptr);
request->rawRequest = object;
request->rpcRequest = wrapInLayer(object, getDatacenterWithId(datacenterId), request);
requestsQueue.push_back(std::unique_ptr<Request>(request));
@ -1401,7 +1451,7 @@ void ConnectionsManager::sendRequest(TLObject *object, onCompleteFunc onComplete
return;
}
scheduleTask([&, requestToken, object, onComplete, onQuickAck, flags, datacenterId, connetionType, immediate] {
Request *request = new Request(requestToken, connetionType, flags, datacenterId, onComplete, onQuickAck);
Request *request = new Request(requestToken, connetionType, flags, datacenterId, onComplete, onQuickAck, nullptr);
request->rawRequest = object;
request->rpcRequest = wrapInLayer(object, getDatacenterWithId(datacenterId), request);
requestsQueue.push_back(std::unique_ptr<Request>(request));
@ -1412,7 +1462,7 @@ void ConnectionsManager::sendRequest(TLObject *object, onCompleteFunc onComplete
}
#ifdef ANDROID
void ConnectionsManager::sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken, jobject ptr1, jobject ptr2) {
void ConnectionsManager::sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, onWriteToSocketFunc onWriteToSocket, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken, jobject ptr1, jobject ptr2, jobject ptr3) {
if (!currentUserId && !(flags & RequestFlagWithoutLogin)) {
DEBUG_D("can't do request without login %s", typeid(*object).name());
delete object;
@ -1429,14 +1479,19 @@ void ConnectionsManager::sendRequest(TLObject *object, onCompleteFunc onComplete
env->DeleteGlobalRef(ptr2);
ptr2 = nullptr;
}
if (ptr3 != nullptr) {
env->DeleteGlobalRef(ptr3);
ptr3 = nullptr;
}
return;
}
scheduleTask([&, requestToken, object, onComplete, onQuickAck, flags, datacenterId, connetionType, immediate, ptr1, ptr2] {
scheduleTask([&, requestToken, object, onComplete, onQuickAck, onWriteToSocket, flags, datacenterId, connetionType, immediate, ptr1, ptr2, ptr3] {
DEBUG_D("send request %p - %s", object, typeid(*object).name());
Request *request = new Request(requestToken, connetionType, flags, datacenterId, onComplete, onQuickAck);
Request *request = new Request(requestToken, connetionType, flags, datacenterId, onComplete, onQuickAck, onWriteToSocket);
request->rawRequest = object;
request->ptr1 = ptr1;
request->ptr2 = ptr2;
request->ptr3 = ptr3;
request->rpcRequest = wrapInLayer(object, getDatacenterWithId(datacenterId), request);
DEBUG_D("send request wrapped %p - %s", request->rpcRequest.get(), typeid(*(request->rpcRequest.get())).name());
requestsQueue.push_back(std::unique_ptr<Request>(request));
@ -1487,7 +1542,7 @@ void ConnectionsManager::setUserId(int32_t userId) {
registerForInternalPushUpdates();
}
if (currentUserId != userId && userId != 0) {
updateDcSettings(0);
updateDcSettings(0, false);
}
if (currentUserId != 0 && pushConnectionEnabled) {
Datacenter *datacenter = getDatacenterWithId(currentDatacenterId);
@ -1501,6 +1556,7 @@ void ConnectionsManager::setUserId(int32_t userId) {
void ConnectionsManager::switchBackend() {
scheduleTask([&] {
currentDatacenterId = 1;
testBackend = !testBackend;
datacenters.clear();
initDatacenters();
@ -1699,10 +1755,10 @@ void ConnectionsManager::registerForInternalPushUpdates() {
}
inline void addMessageToDatacenter(uint32_t datacenterId, NetworkMessage *networkMessage, std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>> &genericMessagesToDatacenters) {
std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>>::iterator iter = genericMessagesToDatacenters.find(datacenterId);
if (iter == genericMessagesToDatacenters.end()) {
std::vector<std::unique_ptr<NetworkMessage>> &array = genericMessagesToDatacenters[datacenterId] = std::vector<std::unique_ptr<NetworkMessage>>();
inline void addMessageToDatacenter(uint32_t datacenterId, NetworkMessage *networkMessage, std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>> &messagesToDatacenters) {
std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>>::iterator iter = messagesToDatacenters.find(datacenterId);
if (iter == messagesToDatacenters.end()) {
std::vector<std::unique_ptr<NetworkMessage>> &array = messagesToDatacenters[datacenterId] = std::vector<std::unique_ptr<NetworkMessage>>();
array.push_back(std::unique_ptr<NetworkMessage>(networkMessage));
} else {
iter->second.push_back(std::unique_ptr<NetworkMessage>(networkMessage));
@ -1711,11 +1767,13 @@ inline void addMessageToDatacenter(uint32_t datacenterId, NetworkMessage *networ
void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t dc) {
static std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>> genericMessagesToDatacenters;
static std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>> tempMessagesToDatacenters;
static std::vector<uint32_t> unknownDatacenterIds;
static std::vector<Datacenter *> neededDatacenters;
static std::vector<Datacenter *> unauthorizedDatacenters;
genericMessagesToDatacenters.clear();
tempMessagesToDatacenters.clear();
unknownDatacenterIds.clear();
neededDatacenters.clear();
unauthorizedDatacenters.clear();
@ -1775,30 +1833,35 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
}
Datacenter *requestDatacenter = getDatacenterWithId(datacenterId);
if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest) {
request->rpcRequest.release();
request->rpcRequest = wrapInLayer(request->rawRequest, requestDatacenter, request);
request->serializedLength = request->getRpcRequest()->getObjectSize();
}
if (requestDatacenter == nullptr) {
if (std::find(unknownDatacenterIds.begin(), unknownDatacenterIds.end(), datacenterId) == unknownDatacenterIds.end()) {
unknownDatacenterIds.push_back(datacenterId);
}
iter++;
continue;
} else if (!requestDatacenter->hasAuthKey()) {
if (std::find(neededDatacenters.begin(), neededDatacenters.end(), requestDatacenter) == neededDatacenters.end()) {
neededDatacenters.push_back(requestDatacenter);
} else {
if (requestDatacenter->isCdnDatacenter) {
request->requestFlags |= RequestFlagEnableUnauthorized;
}
iter++;
continue;
} else if (!(request->requestFlags & RequestFlagEnableUnauthorized) && !requestDatacenter->authorized && request->datacenterId != DEFAULT_DATACENTER_ID && request->datacenterId != currentDatacenterId) {
if (std::find(unauthorizedDatacenters.begin(), unauthorizedDatacenters.end(), requestDatacenter) == unauthorizedDatacenters.end()) {
unauthorizedDatacenters.push_back(requestDatacenter);
if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest) {
request->rpcRequest.release();
request->rpcRequest = wrapInLayer(request->rawRequest, requestDatacenter, request);
request->serializedLength = request->getRpcRequest()->getObjectSize();
}
if (!requestDatacenter->hasAuthKey()) {
if (std::find(neededDatacenters.begin(), neededDatacenters.end(), requestDatacenter) == neededDatacenters.end()) {
neededDatacenters.push_back(requestDatacenter);
}
iter++;
continue;
} else if (!(request->requestFlags & RequestFlagEnableUnauthorized) && !requestDatacenter->authorized && request->datacenterId != DEFAULT_DATACENTER_ID && request->datacenterId != currentDatacenterId) {
if (std::find(unauthorizedDatacenters.begin(), unauthorizedDatacenters.end(), requestDatacenter) == unauthorizedDatacenters.end()) {
unauthorizedDatacenters.push_back(requestDatacenter);
}
iter++;
continue;
}
iter++;
continue;
}
Connection *connection = requestDatacenter->getConnectionByType(request->connectionType, true);
@ -1827,7 +1890,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
)
) {
if (!forceThisRequest && request->connectionToken > 0) {
if (request->connectionType & ConnectionTypeGeneric && request->connectionToken == connection->getConnectionToken()) {
if ((request->connectionType & ConnectionTypeGeneric || request->connectionType & ConnectionTypeTemp) && request->connectionToken == connection->getConnectionToken()) {
DEBUG_D("request token is valid, not retrying %s", typeInfo.name());
iter++;
continue;
@ -1892,11 +1955,15 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
case ConnectionTypeGeneric:
addMessageToDatacenter(requestDatacenter->getDatacenterId(), networkMessage, genericMessagesToDatacenters);
break;
case ConnectionTypeTemp:
addMessageToDatacenter(requestDatacenter->getDatacenterId(), networkMessage, tempMessagesToDatacenters);
break;
case ConnectionTypeDownload:
case ConnectionTypeUpload: {
std::vector<std::unique_ptr<NetworkMessage>> array;
array.push_back(std::unique_ptr<NetworkMessage>(networkMessage));
sendMessagesToConnectionWithConfirmation(array, connection, false);
request->onWriteToSocket();
break;
}
default:
@ -1954,7 +2021,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
if (requestStartTime != 0 && abs(currentTime - requestStartTime) >= timeout) {
std::vector<uint32_t> allDc;
for (std::map<uint32_t, Datacenter *>::iterator iter2 = datacenters.begin(); iter2 != datacenters.end(); iter2++) {
if (iter2->first == datacenterId) {
if (iter2->first == datacenterId || iter2->second->isCdnDatacenter) {
continue;
}
allDc.push_back(iter2->first);
@ -1972,29 +2039,31 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
}
Datacenter *requestDatacenter = getDatacenterWithId(datacenterId);
if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest) {
request->rpcRequest.release();
request->rpcRequest = wrapInLayer(request->rawRequest, requestDatacenter, request);
}
if (requestDatacenter == nullptr) {
if (std::find(unknownDatacenterIds.begin(), unknownDatacenterIds.end(), datacenterId) == unknownDatacenterIds.end()) {
unknownDatacenterIds.push_back(datacenterId);
}
iter++;
continue;
} else if (!requestDatacenter->hasAuthKey()) {
if (std::find(neededDatacenters.begin(), neededDatacenters.end(), requestDatacenter) == neededDatacenters.end()) {
neededDatacenters.push_back(requestDatacenter);
} else {
if (requestDatacenter->lastInitVersion != currentVersion && !request->isInitRequest) {
request->rpcRequest.release();
request->rpcRequest = wrapInLayer(request->rawRequest, requestDatacenter, request);
}
iter++;
continue;
} else if (!(request->requestFlags & RequestFlagEnableUnauthorized) && !requestDatacenter->authorized && request->datacenterId != DEFAULT_DATACENTER_ID && request->datacenterId != currentDatacenterId) {
if (std::find(unauthorizedDatacenters.begin(), unauthorizedDatacenters.end(), requestDatacenter) == unauthorizedDatacenters.end()) {
unauthorizedDatacenters.push_back(requestDatacenter);
if (!requestDatacenter->hasAuthKey()) {
if (std::find(neededDatacenters.begin(), neededDatacenters.end(), requestDatacenter) == neededDatacenters.end()) {
neededDatacenters.push_back(requestDatacenter);
}
iter++;
continue;
} else if (!(request->requestFlags & RequestFlagEnableUnauthorized) && !requestDatacenter->authorized && request->datacenterId != DEFAULT_DATACENTER_ID && request->datacenterId != currentDatacenterId) {
if (std::find(unauthorizedDatacenters.begin(), unauthorizedDatacenters.end(), requestDatacenter) == unauthorizedDatacenters.end()) {
unauthorizedDatacenters.push_back(requestDatacenter);
}
iter++;
continue;
}
iter++;
continue;
}
Connection *connection = requestDatacenter->getConnectionByType(request->connectionType, true);
@ -2067,6 +2136,9 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
case ConnectionTypeGeneric:
addMessageToDatacenter(requestDatacenter->getDatacenterId(), networkMessage, genericMessagesToDatacenters);
break;
case ConnectionTypeTemp:
addMessageToDatacenter(requestDatacenter->getDatacenterId(), networkMessage, tempMessagesToDatacenters);
break;
case ConnectionTypeDownload:
case ConnectionTypeUpload: {
std::vector<std::unique_ptr<NetworkMessage>> array;
@ -2090,6 +2162,14 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
genericMessagesToDatacenters[datacenter->getDatacenterId()] = std::vector<std::unique_ptr<NetworkMessage>>();
}
}
iter2 = tempMessagesToDatacenters.find(datacenter->getDatacenterId());
if (iter2 == tempMessagesToDatacenters.end()) {
Connection *connection = datacenter->getTempConnection(false);
if (connection != nullptr && connection->getConnectionToken() != 0 && connection->hasMessagesToConfirm()) {
tempMessagesToDatacenters[datacenter->getDatacenterId()] = std::vector<std::unique_ptr<NetworkMessage>>();
}
}
}
for (std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>>::iterator iter = genericMessagesToDatacenters.begin(); iter != genericMessagesToDatacenters.end(); iter++) {
@ -2156,6 +2236,14 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
}
}
for (std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>>::iterator iter = tempMessagesToDatacenters.begin(); iter != tempMessagesToDatacenters.end(); iter++) {
Datacenter *datacenter = getDatacenterWithId(iter->first);
if (datacenter != nullptr) {
std::vector<std::unique_ptr<NetworkMessage>> &array = iter->second;
sendMessagesToConnectionWithConfirmation(array, datacenter->getTempConnection(true), false);
}
}
if (connectionTypes == ConnectionTypeGeneric && dc == currentDatacenterId) {
std::map<uint32_t, std::vector<std::unique_ptr<NetworkMessage>>>::iterator iter2 = genericMessagesToDatacenters.find(currentDatacenterId);
if (iter2 == genericMessagesToDatacenters.end()) {
@ -2164,7 +2252,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t
}
if (!unknownDatacenterIds.empty()) {
updateDcSettings(0);
updateDcSettings(0, false);
}
size_t count = neededDatacenters.size();
@ -2198,7 +2286,7 @@ Datacenter *ConnectionsManager::getDatacenterWithId(uint32_t datacenterId) {
std::unique_ptr<TLObject> ConnectionsManager::wrapInLayer(TLObject *object, Datacenter *datacenter, Request *baseRequest) {
if (object->isNeedLayer()) {
if (datacenter == nullptr || datacenter->lastInitVersion != currentVersion) {
if (datacenter->getDatacenterId() == currentDatacenterId) {
if (datacenter != nullptr && datacenter->getDatacenterId() == currentDatacenterId) {
registerForInternalPushUpdates();
}
baseRequest->isInitRequest = true;
@ -2206,20 +2294,29 @@ std::unique_ptr<TLObject> ConnectionsManager::wrapInLayer(TLObject *object, Data
request->query = std::unique_ptr<TLObject>(object);
request->api_id = currentApiId;
request->app_version = currentAppVersion;
request->device_model = currentDeviceModel;
request->lang_code = currentLangCode;
request->system_version = currentSystemVersion;
request->system_lang_code = currentLangCode;
request->lang_pack = "android";
request->system_lang_code = currentSystemLangCode;
if (datacenter == nullptr || datacenter->isCdnDatacenter) {
request->device_model = "n/a";
request->system_version = "n/a";
} else {
request->device_model = currentDeviceModel;
request->system_version = currentSystemVersion;
}
if (request->lang_code.empty()) {
request->lang_code = "en";
}
if (request->device_model.empty()) {
request->device_model = "device model unknown";
request->device_model = "n/a";
}
if (request->app_version.empty()) {
request->app_version = "app version unknown";
request->app_version = "n/a";
}
if (request->system_version.empty()) {
request->system_version = "system version unknown";
request->system_version = "n/a";
}
invokeWithLayer *request2 = new invokeWithLayer();
request2->layer = currentLayer;
@ -2231,54 +2328,68 @@ std::unique_ptr<TLObject> ConnectionsManager::wrapInLayer(TLObject *object, Data
return std::unique_ptr<TLObject>(object);
}
void ConnectionsManager::updateDcSettings(uint32_t dcNum) {
if (updatingDcSettings) {
return;
void ConnectionsManager::updateDcSettings(uint32_t dcNum, bool workaround) {
if (workaround) {
if (updatingDcSettingsWorkaround) {
return;
}
updatingDcSettingsWorkaround = true;
} else {
if (updatingDcSettings) {
return;
}
updatingDcSettings = true;
updatingDcStartTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000);
}
updatingDcStartTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000);
updatingDcSettings = true;
TL_help_getConfig *request = new TL_help_getConfig();
sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) {
if (!updatingDcSettings) {
TL_help_getConfig *request = new TL_help_getConfig();
sendRequest(request, [&, workaround](TLObject *response, TL_error *error, int32_t networkType) {
if (!workaround && !updatingDcSettings || workaround && !updatingDcSettingsWorkaround) {
return;
}
if (response != nullptr) {
TL_config *config = (TL_config *) response;
int32_t updateIn = config->expires - getCurrentTime();
if (updateIn <= 0) {
updateIn = 120;
if (!workaround) {
int32_t updateIn = config->expires - getCurrentTime();
if (updateIn <= 0) {
updateIn = 120;
}
lastDcUpdateTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000) - DC_UPDATE_TIME + updateIn;
}
lastDcUpdateTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000) - DC_UPDATE_TIME + updateIn;
struct DatacenterInfo {
std::vector<std::string> addressesIpv4;
std::vector<std::string> addressesIpv6;
std::vector<std::string> addressesIpv4Download;
std::vector<std::string> addressesIpv6Download;
std::map<std::string, uint32_t> ports;
std::vector<TcpAddress> addressesIpv4;
std::vector<TcpAddress> addressesIpv6;
std::vector<TcpAddress> addressesIpv4Download;
std::vector<TcpAddress> addressesIpv6Download;
bool isCdn = false;
void addAddressAndPort(std::string address, uint32_t port, uint32_t flags) {
std::vector<std::string> *addresses;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
void addAddressAndPort(TL_dcOption *dcOption) {
std::vector<TcpAddress> *addresses;
if (!isCdn) {
isCdn = dcOption->cdn;
}
if (dcOption->media_only) {
if (dcOption->ipv6) {
addresses = &addressesIpv6Download;
} else {
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
if (dcOption->ipv6) {
addresses = &addressesIpv6;
} else {
addresses = &addressesIpv4;
}
}
if (std::find(addresses->begin(), addresses->end(), address) != addresses->end()) {
return;
for (std::vector<TcpAddress>::iterator iter = addresses->begin(); iter != addresses->end(); iter++) {
if (iter->address == dcOption->ip_address && iter->port == dcOption->port) {
return;
}
}
addresses->push_back(address);
ports[address] = port;
DEBUG_D("getConfig add %s:%d to dc%d", dcOption->ip_address.c_str(), dcOption->port, dcOption->id);
addresses->push_back(TcpAddress(dcOption->ip_address, dcOption->port, dcOption->flags));
}
};
@ -2293,7 +2404,7 @@ void ConnectionsManager::updateDcSettings(uint32_t dcNum) {
} else {
info = iter->second.get();
}
info->addAddressAndPort(dcOption->ip_address, (uint32_t) dcOption->port, (uint32_t) dcOption->flags);
info->addAddressAndPort(dcOption);
}
if (!map.empty()) {
@ -2304,10 +2415,10 @@ void ConnectionsManager::updateDcSettings(uint32_t dcNum) {
datacenter = new Datacenter(iter->first);
datacenters[iter->first] = datacenter;
}
datacenter->replaceAddressesAndPorts(info->addressesIpv4, info->ports, 0);
datacenter->replaceAddressesAndPorts(info->addressesIpv6, info->ports, 1);
datacenter->replaceAddressesAndPorts(info->addressesIpv4Download, info->ports, 2);
datacenter->replaceAddressesAndPorts(info->addressesIpv6Download, info->ports, 3);
datacenter->replaceAddresses(info->addressesIpv4, info->isCdn ? 8 : 0);
datacenter->replaceAddresses(info->addressesIpv6, info->isCdn ? 9 : 1);
datacenter->replaceAddresses(info->addressesIpv4Download, info->isCdn ? 10 : 2);
datacenter->replaceAddresses(info->addressesIpv6Download, info->isCdn ? 11 : 3);
if (iter->first == movingToDatacenterId) {
movingToDatacenterId = DEFAULT_DATACENTER_ID;
moveToDatacenter(iter->first);
@ -2320,8 +2431,12 @@ void ConnectionsManager::updateDcSettings(uint32_t dcNum) {
delegate->onUpdateConfig(config);
}
}
updatingDcSettings = false;
}, nullptr, RequestFlagEnableUnauthorized | RequestFlagWithoutLogin | RequestFlagTryDifferentDc, dcNum == 0 ? currentDatacenterId : dcNum, ConnectionTypeGeneric, true);
if (workaround) {
updatingDcSettingsWorkaround = false;
} else {
updatingDcSettings = false;
}
}, nullptr, RequestFlagEnableUnauthorized | RequestFlagWithoutLogin | (workaround ? 0 : RequestFlagTryDifferentDc), dcNum == 0 ? currentDatacenterId : dcNum, workaround ? ConnectionTypeTemp : ConnectionTypeGeneric, true);
}
void ConnectionsManager::moveToDatacenter(uint32_t datacenterId) {
@ -2352,7 +2467,7 @@ void ConnectionsManager::moveToDatacenter(uint32_t datacenterId) {
void ConnectionsManager::authorizeOnMovingDatacenter() {
Datacenter *datacenter = getDatacenterWithId(movingToDatacenterId);
if (datacenter == nullptr) {
updateDcSettings(0);
updateDcSettings(0, false);
return;
}
datacenter->recreateSessions();
@ -2391,15 +2506,16 @@ void ConnectionsManager::applyDatacenterAddress(uint32_t datacenterId, std::stri
scheduleTask([&, datacenterId, ipAddress, port] {
Datacenter *datacenter = getDatacenterWithId(datacenterId);
if (datacenter != nullptr) {
std::vector<std::string> addresses;
std::map<std::string, uint32_t> ports;
addresses.push_back(ipAddress);
ports[ipAddress] = port;
std::vector<TcpAddress> addresses;
addresses.push_back(TcpAddress(ipAddress, port, 0));
datacenter->suspendConnections();
datacenter->replaceAddressesAndPorts(addresses, ports, 0);
datacenter->replaceAddresses(addresses, 0);
datacenter->resetAddressAndPortNum();
saveConfig();
updateDcSettings(datacenterId);
if (datacenter->isHandshaking()) {
datacenter->beginHandshake(true);
}
updateDcSettings(datacenterId, false);
}
});
}
@ -2428,7 +2544,41 @@ void ConnectionsManager::setPushConnectionEnabled(bool value) {
}
}
void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType) {
void ConnectionsManager::applyDnsConfig(NativeByteBuffer *buffer) {
scheduleTask([&, buffer] {
TL_help_configSimple *config = Datacenter::decodeSimpleConfig(buffer);
int currentDate = getCurrentTime();
if (config != nullptr && config->date <= currentDate && currentDate <= config->expires) {
Datacenter *datacenter = getDatacenterWithId(config->dc_id);
if (datacenter != nullptr) {
std::vector<TcpAddress> addresses;
for (std::vector<std::unique_ptr<TL_ipPort>>::iterator iter = config->ip_port_list.begin(); iter != config->ip_port_list.end(); iter++) {
TL_ipPort *ipPort = iter->get();
addresses.push_back(TcpAddress(ipPort->ipv4, ipPort->port, 0));
DEBUG_D("got address %s and port %d for dc%d", ipPort->ipv4.c_str(), ipPort->port, config->dc_id);
}
if (!addresses.empty()) {
datacenter->replaceAddresses(addresses, TcpAddressFlagTemp);
Connection *connection = datacenter->getTempConnection(false);
if (connection != nullptr) {
connection->suspendConnection();
}
if (datacenter->isHandshaking()) {
datacenter->beginHandshake(true);
}
updateDcSettings(config->dc_id, true);
}
}
delete config;
} else if (requestingSecondAddress == 0) {
requestingSecondAddress = 1;
delegate->onRequestNewServerIpAndPort(requestingSecondAddress);
}
buffer->reuse();
});
}
void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType) {
currentVersion = version;
currentLayer = layer;
currentApiId = apiId;
@ -2437,6 +2587,7 @@ void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, st
currentSystemVersion = systemVersion;
currentAppVersion = appVersion;
currentLangCode = langCode;
currentSystemLangCode = systemLangCode;
currentUserId = userId;
currentLogPath = logPath;
pushConnectionEnabled = enablePushConnection;
@ -2459,6 +2610,32 @@ void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, st
pthread_create(&networkThread, NULL, (ConnectionsManager::ThreadProc), this);
}
void ConnectionsManager::setProxySettings(std::string address, uint16_t port, std::string username, std::string password) {
scheduleTask([&, address, port, username, password] {
bool reconnect = proxyAddress != address;
proxyAddress = address;
proxyPort = port;
proxyUser = username;
proxyPassword = password;
if (reconnect) {
for (std::map<uint32_t, Datacenter *>::iterator iter = datacenters.begin(); iter != datacenters.end(); iter++) {
iter->second->suspendConnections();
}
processRequestQueue(0, 0);
}
});
}
void ConnectionsManager::setLangCode(std::string langCode) {
scheduleTask([&, langCode] {
currentLangCode = langCode;
for (std::map<uint32_t, Datacenter *>::iterator iter = datacenters.begin(); iter != datacenters.end(); iter++) {
iter->second->lastInitVersion = 0;
}
saveConfig();
});
}
void ConnectionsManager::resumeNetwork(bool partial) {
scheduleTask([&, partial] {
if (partial) {

View File

@ -45,6 +45,7 @@ public:
int64_t getCurrentTimeMillis();
int64_t getCurrentTimeMonotonicMillis();
int32_t getCurrentTime();
bool isTestBackend();
int32_t getTimeDifference();
int32_t sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate);
void sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken);
@ -61,14 +62,17 @@ public:
void pauseNetwork();
void setNetworkAvailable(bool value, int32_t type);
void setUseIpv6(bool value);
void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType);
void updateDcSettings(uint32_t datacenterId);
void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string systemLangCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType);
void setProxySettings(std::string address, uint16_t port, std::string username, std::string password);
void setLangCode(std::string langCode);
void updateDcSettings(uint32_t datacenterId, bool workaround);
void setPushConnectionEnabled(bool value);
void applyDnsConfig(NativeByteBuffer *buffer);
void setMtProtoVersion(int version);
int32_t getMtProtoVersion();
#ifdef ANDROID
void sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken, jobject ptr1, jobject ptr2);
void sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, onWriteToSocketFunc onWriteToSocket, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken, jobject ptr1, jobject ptr2, jobject ptr3);
static void useJavaVM(JavaVM *vm, bool useJavaByteBuffers);
#endif
@ -78,6 +82,7 @@ private:
void initDatacenters();
void loadConfig();
void saveConfig();
void saveConfigInternal(NativeByteBuffer *buffer);
void select();
void wakeup();
void processServerResponse(TLObject *message, int64_t messageId, int32_t messageSeqNo, int64_t messageSalt, Connection *connection, int64_t innerMsgId, int64_t containerMessageId);
@ -102,7 +107,7 @@ private:
void scheduleTask(std::function<void()> task);
void scheduleEvent(EventObject *eventObject, uint32_t time);
void removeEvent(EventObject *eventObject);
void onConnectionClosed(Connection *connection);
void onConnectionClosed(Connection *connection, int reason);
void onConnectionConnected(Connection *connection);
void onConnectionQuickAckReceived(Connection *connection, int32_t ack);
void onConnectionDataReceived(Connection *connection, NativeByteBuffer *data, uint32_t length);
@ -134,6 +139,9 @@ private:
int64_t lastPushPingTime = 0;
bool sendingPushPing = false;
bool updatingDcSettings = false;
bool updatingDcSettingsWorkaround = false;
int32_t disconnectTimeoutAmount = 0;
int32_t requestingSecondAddress = 0;
int32_t updatingDcStartTime = 0;
int32_t lastDcUpdateTime = 0;
int64_t lastPingTime = getCurrentTimeMonotonicMillis();
@ -147,6 +155,11 @@ private:
std::map<int32_t, std::vector<int32_t>> requestsByGuids;
std::map<int32_t, int32_t> guidsByRequests;
std::string proxyUser = "";
std::string proxyPassword = "";
std::string proxyAddress = "";
std::uint16_t proxyPort = 1080;
pthread_t networkThread;
pthread_mutex_t mutex;
std::queue<std::function<void()>> pendingTasks;
@ -176,6 +189,7 @@ private:
std::string currentSystemVersion;
std::string currentAppVersion;
std::string currentLangCode;
std::string currentSystemLangCode;
std::string currentConfigPath;
std::string currentLogPath;
int32_t currentUserId = 0;

View File

@ -27,28 +27,33 @@
static std::vector<std::string> serverPublicKeys;
static std::vector<uint64_t> serverPublicKeysFingerprints;
static BN_CTX *bnContext;
static std::map<int32_t, std::string> cdnPublicKeys;
static std::map<int32_t, uint64_t> cdnPublicKeysFingerprints;
static std::vector<Datacenter *> cdnWaitingDatacenters;
static bool loadingCdnKeys = false;
static BN_CTX *bnContext = nullptr;
static SHA256_CTX sha256Ctx;
static Config *cdnConfig = nullptr;
Datacenter::Datacenter(uint32_t id) {
datacenterId = id;
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
uploadConnection[a] = nullptr;
}
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
downloadConnections[a] = nullptr;
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
downloadConnection[a] = nullptr;
}
}
Datacenter::Datacenter(NativeByteBuffer *data) {
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
uploadConnection[a] = nullptr;
}
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
downloadConnections[a] = nullptr;
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
downloadConnection[a] = nullptr;
}
uint32_t currentVersion = data->readUint32(nullptr);
if (currentVersion >= 2 && currentVersion <= 5) {
if (currentVersion >= 2 && currentVersion <= 7) {
datacenterId = data->readUint32(nullptr);
if (currentVersion >= 3) {
lastInitVersion = data->readUint32(nullptr);
@ -56,29 +61,56 @@ Datacenter::Datacenter(NativeByteBuffer *data) {
uint32_t len = data->readUint32(nullptr);
for (uint32_t a = 0; a < len; a++) {
std::string address = data->readString(nullptr);
addressesIpv4.push_back(address);
ports[address] = data->readUint32(nullptr);
uint32_t port = data->readUint32(nullptr);
int32_t flags;
if (currentVersion >= 7) {
flags = data->readInt32(nullptr);
} else {
flags = 0;
}
addressesIpv4.push_back(TcpAddress(address, port, flags));
}
if (currentVersion >= 5) {
len = data->readUint32(nullptr);
for (uint32_t a = 0; a < len; a++) {
std::string address = data->readString(nullptr);
addressesIpv6.push_back(address);
ports[address] = data->readUint32(nullptr);
uint32_t port = data->readUint32(nullptr);
int32_t flags;
if (currentVersion >= 7) {
flags = data->readInt32(nullptr);
} else {
flags = 0;
}
addressesIpv6.push_back(TcpAddress(address, port, flags));
}
len = data->readUint32(nullptr);
for (uint32_t a = 0; a < len; a++) {
std::string address = data->readString(nullptr);
addressesIpv4Download.push_back(address);
ports[address] = data->readUint32(nullptr);
uint32_t port = data->readUint32(nullptr);
int32_t flags;
if (currentVersion >= 7) {
flags = data->readInt32(nullptr);
} else {
flags = 0;
}
addressesIpv4Download.push_back(TcpAddress(address, port, flags));
}
len = data->readUint32(nullptr);
for (uint32_t a = 0; a < len; a++) {
std::string address = data->readString(nullptr);
addressesIpv6Download.push_back(address);
ports[address] = data->readUint32(nullptr);
uint32_t port = data->readUint32(nullptr);
int32_t flags;
if (currentVersion >= 7) {
flags = data->readInt32(nullptr);
} else {
flags = 0;
}
addressesIpv6Download.push_back(TcpAddress(address, port, flags));
}
}
if (currentVersion >= 6) {
isCdnDatacenter = data->readBool(nullptr);
}
len = data->readUint32(nullptr);
if (len != 0) {
authKey = data->readBytes(len, nullptr);
@ -108,7 +140,7 @@ Datacenter::Datacenter(NativeByteBuffer *data) {
NativeByteBuffer *buffer = config->readConfig();
if (buffer != nullptr) {
uint32_t version = buffer->readUint32(nullptr);
if (version <= paramsConfigVersion) {
if (version >= 1) {
currentPortNumIpv4 = buffer->readUint32(nullptr);
currentAddressNumIpv4 = buffer->readUint32(nullptr);
currentPortNumIpv6 = buffer->readUint32(nullptr);
@ -133,28 +165,28 @@ Datacenter::Datacenter(NativeByteBuffer *data) {
void Datacenter::switchTo443Port() {
for (uint32_t a = 0; a < addressesIpv4.size(); a++) {
if (ports[addressesIpv4[a]] == 443) {
if (addressesIpv4[a].port == 443) {
currentAddressNumIpv4 = a;
currentPortNumIpv4 = 0;
break;
}
}
for (uint32_t a = 0; a < addressesIpv6.size(); a++) {
if (ports[addressesIpv6[a]] == 443) {
if (addressesIpv6[a].port == 443) {
currentAddressNumIpv6 = a;
currentPortNumIpv6 = 0;
break;
}
}
for (uint32_t a = 0; a < addressesIpv4Download.size(); a++) {
if (ports[addressesIpv4Download[a]] == 443) {
if (addressesIpv4Download[a].port == 443) {
currentAddressNumIpv4Download = a;
currentPortNumIpv4Download = 0;
break;
}
}
for (uint32_t a = 0; a < addressesIpv6Download.size(); a++) {
if (ports[addressesIpv6Download[a]] == 443) {
if (addressesIpv6Download[a].port == 443) {
currentAddressNumIpv6Download = a;
currentPortNumIpv6Download = 0;
break;
@ -164,9 +196,15 @@ void Datacenter::switchTo443Port() {
std::string Datacenter::getCurrentAddress(uint32_t flags) {
uint32_t currentAddressNum;
std::vector<std::string> *addresses;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
std::vector<TcpAddress> *addresses;
if (flags == 0 && !hasAuthKey() && !addressesIpv4Temp.empty()) {
flags = TcpAddressFlagTemp;
}
if ((flags & TcpAddressFlagTemp) != 0) {
currentAddressNum = currentAddressNumIpv4Temp;
addresses = &addressesIpv4Temp;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNum = currentAddressNumIpv6Download;
addresses = &addressesIpv6Download;
} else {
@ -174,7 +212,7 @@ std::string Datacenter::getCurrentAddress(uint32_t flags) {
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNum = currentAddressNumIpv6;
addresses = &addressesIpv6;
} else {
@ -185,57 +223,111 @@ std::string Datacenter::getCurrentAddress(uint32_t flags) {
if (addresses->empty()) {
return std::string("");
}
if ((flags & TcpAddressFlagStatic) != 0) {
for (std::vector<TcpAddress>::iterator iter = addresses->begin(); iter != addresses->end(); iter++) {
if ((iter->flags & TcpAddressFlagStatic) != 0) {
return iter->address;
}
}
}
if (currentAddressNum >= addresses->size()) {
currentAddressNum = 0;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagTemp) != 0) {
currentAddressNumIpv4Temp = currentAddressNum;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNumIpv6Download = currentAddressNum;
} else {
currentAddressNumIpv4Download = currentAddressNum;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNumIpv6 = currentAddressNum;
} else {
currentAddressNumIpv4 = currentAddressNum;
}
}
}
return (*addresses)[currentAddressNum];
return (*addresses)[currentAddressNum].address;
}
int32_t Datacenter::getCurrentPort(uint32_t flags) {
if (ports.empty()) {
return overridePort == -1 ? 443 : overridePort;
}
const int32_t *portsArray = overridePort == 8888 ? defaultPorts8888 : defaultPorts;
uint32_t currentAddressNum;
uint32_t currentPortNum;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
std::vector<TcpAddress> *addresses;
if (flags == 0 && !hasAuthKey() && !addressesIpv4Temp.empty()) {
flags = TcpAddressFlagTemp;
}
if ((flags & TcpAddressFlagTemp) != 0) {
currentAddressNum = currentAddressNumIpv4Temp;
currentPortNum = currentPortNumIpv4Temp;
addresses = &addressesIpv4Temp;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNum = currentAddressNumIpv6Download;
currentPortNum = currentPortNumIpv6Download;
addresses = &addressesIpv6Download;
} else {
currentAddressNum = currentAddressNumIpv4Download;
currentPortNum = currentPortNumIpv4Download;
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNum = currentAddressNumIpv6;
currentPortNum = currentPortNumIpv6;
addresses = &addressesIpv6;
} else {
currentAddressNum = currentAddressNumIpv4;
currentPortNum = currentPortNumIpv4;
addresses = &addressesIpv4;
}
}
if (addresses->empty()) {
return overridePort == -1 ? 443 : overridePort;
}
const int32_t *portsArray = overridePort == 8888 ? defaultPorts8888 : defaultPorts;
if ((flags & TcpAddressFlagStatic) != 0) {
uint32_t num = 0;
for (std::vector<TcpAddress>::iterator iter = addresses->begin(); iter != addresses->end(); iter++) {
if ((iter->flags & TcpAddressFlagStatic) != 0) {
currentAddressNum = num;
break;
}
num++;
}
}
if (currentAddressNum >= addresses->size()) {
currentAddressNum = 0;
if ((flags & TcpAddressFlagTemp) != 0) {
currentAddressNumIpv4Temp = currentAddressNum;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNumIpv6Download = currentAddressNum;
} else {
currentAddressNumIpv4Download = currentAddressNum;
}
} else {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentAddressNumIpv6 = currentAddressNum;
} else {
currentAddressNumIpv4 = currentAddressNum;
}
}
}
if (currentPortNum >= 11) {
currentPortNum = 0;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagTemp) != 0) {
currentPortNumIpv4Temp = currentAddressNum;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNumIpv6Download = currentPortNum;
} else {
currentPortNumIpv4Download = currentPortNum;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNumIpv6 = currentPortNum;
} else {
currentPortNumIpv4 = currentPortNum;
@ -247,40 +339,49 @@ int32_t Datacenter::getCurrentPort(uint32_t flags) {
if (overridePort != -1) {
return overridePort;
}
std::string address = getCurrentAddress(flags);
return ports[address];
return ((*addresses) [currentAddressNum]).port;
}
return port;
}
void Datacenter::addAddressAndPort(std::string address, uint32_t port, uint32_t flags) {
std::vector<std::string> *addresses;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
std::vector<TcpAddress> *addresses;
if ((flags & TcpAddressFlagTemp) != 0) {
addresses = &addressesIpv4Temp;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
addresses = &addressesIpv6Download;
} else {
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
addresses = &addressesIpv6;
} else {
addresses = &addressesIpv4;
}
}
if (std::find(addresses->begin(), addresses->end(), address) != addresses->end()) {
return;
for (std::vector<TcpAddress>::iterator iter = addresses->begin(); iter != addresses->end(); iter++) {
if (iter->address == address && iter->port == port) {
return;
}
}
addresses->push_back(address);
ports[address] = port;
addresses->push_back(TcpAddress(address, port, flags));
}
void Datacenter::nextAddressOrPort(uint32_t flags) {
uint32_t currentPortNum;
uint32_t currentAddressNum;
std::vector<std::string> *addresses;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
std::vector<TcpAddress> *addresses;
if (flags == 0 && !hasAuthKey() && !addressesIpv4Temp.empty()) {
flags = TcpAddressFlagTemp;
}
if ((flags & TcpAddressFlagTemp) != 0) {
currentPortNum = currentPortNumIpv4Temp;
currentAddressNum = currentAddressNumIpv4Temp;
addresses = &addressesIpv4Temp;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNum = currentPortNumIpv6Download;
currentAddressNum = currentAddressNumIpv6Download;
addresses = &addressesIpv6Download;
@ -290,7 +391,7 @@ void Datacenter::nextAddressOrPort(uint32_t flags) {
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNum = currentPortNumIpv6;
currentAddressNum = currentAddressNumIpv6;
addresses = &addressesIpv6;
@ -310,8 +411,11 @@ void Datacenter::nextAddressOrPort(uint32_t flags) {
}
currentPortNum = 0;
}
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagTemp) != 0) {
currentPortNumIpv4Temp = currentPortNum;
currentAddressNumIpv4Temp = currentAddressNum;
} else if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNumIpv6Download = currentPortNum;
currentAddressNumIpv6Download = currentAddressNum;
} else {
@ -319,7 +423,7 @@ void Datacenter::nextAddressOrPort(uint32_t flags) {
currentAddressNumIpv4Download = currentAddressNum;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
currentPortNumIpv6 = currentPortNum;
currentAddressNumIpv6 = currentAddressNum;
} else {
@ -359,42 +463,21 @@ void Datacenter::resetAddressAndPortNum() {
storeCurrentAddressAndPortNum();
}
void Datacenter::replaceAddressesAndPorts(std::vector<std::string> &newAddresses, std::map<std::string, uint32_t> &newPorts, uint32_t flags) {
std::vector<std::string> *addresses;
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
addresses = &addressesIpv6Download;
} else {
addresses = &addressesIpv4Download;
}
} else {
if ((flags & 1) != 0) {
addresses = &addressesIpv6;
} else {
addresses = &addressesIpv4;
}
}
size_t size = addresses->size();
for (uint32_t a = 0; a < size; a++) {
std::map<std::string, uint32_t>::iterator iter = ports.find((*addresses)[a]);
if (iter != ports.end()) {
ports.erase(iter);
}
}
if ((flags & 2) != 0) {
if ((flags & 1) != 0) {
void Datacenter::replaceAddresses(std::vector<TcpAddress> &newAddresses, uint32_t flags) {
isCdnDatacenter = (flags & 8) != 0;
if ((flags & TcpAddressFlagDownload) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
addressesIpv6Download = newAddresses;
} else {
addressesIpv4Download = newAddresses;
}
} else {
if ((flags & 1) != 0) {
if ((flags & TcpAddressFlagIpv6) != 0) {
addressesIpv6 = newAddresses;
} else {
addressesIpv4 = newAddresses;
}
}
ports.insert(newPorts.begin(), newPorts.end());
}
void Datacenter::serializeToStream(NativeByteBuffer *stream) {
@ -404,24 +487,29 @@ void Datacenter::serializeToStream(NativeByteBuffer *stream) {
size_t size;
stream->writeInt32((int32_t) (size = addressesIpv4.size()));
for (uint32_t a = 0; a < size; a++) {
stream->writeString(addressesIpv4[a]);
stream->writeInt32(ports[addressesIpv4[a]]);
stream->writeString(addressesIpv4[a].address);
stream->writeInt32(addressesIpv4[a].port);
stream->writeInt32(addressesIpv4[a].flags);
}
stream->writeInt32((int32_t) (size = addressesIpv6.size()));
for (uint32_t a = 0; a < size; a++) {
stream->writeString(addressesIpv6[a]);
stream->writeInt32(ports[addressesIpv6[a]]);
stream->writeString(addressesIpv6[a].address);
stream->writeInt32(addressesIpv6[a].port);
stream->writeInt32(addressesIpv6[a].flags);
}
stream->writeInt32((int32_t) (size = addressesIpv4Download.size()));
for (uint32_t a = 0; a < size; a++) {
stream->writeString(addressesIpv4Download[a]);
stream->writeInt32(ports[addressesIpv4Download[a]]);
stream->writeString(addressesIpv4Download[a].address);
stream->writeInt32(addressesIpv4Download[a].port);
stream->writeInt32(addressesIpv4Download[a].flags);
}
stream->writeInt32((int32_t) (size = addressesIpv6Download.size()));
for (uint32_t a = 0; a < size; a++) {
stream->writeString(addressesIpv6Download[a]);
stream->writeInt32(ports[addressesIpv6Download[a]]);
stream->writeString(addressesIpv6Download[a].address);
stream->writeInt32(addressesIpv6Download[a].port);
stream->writeInt32(addressesIpv6Download[a].flags);
}
stream->writeBool(isCdnDatacenter);
if (authKey != nullptr) {
stream->writeInt32(authKey->length);
stream->writeBytes(authKey);
@ -439,7 +527,10 @@ void Datacenter::serializeToStream(NativeByteBuffer *stream) {
}
void Datacenter::clear() {
authKey = nullptr;
if (authKey != nullptr) {
delete authKey;
authKey = nullptr;
}
authKeyId = 0;
authorized = false;
serverSalts.clear();
@ -537,14 +628,17 @@ void Datacenter::suspendConnections() {
if (genericConnection != nullptr) {
genericConnection->suspendConnection();
}
if (tempConnection != nullptr) {
tempConnection->suspendConnection();
}
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
if (uploadConnection[a] != nullptr) {
uploadConnection[a]->suspendConnection();
}
}
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
if (downloadConnections[a] != nullptr) {
downloadConnections[a]->suspendConnection();
if (downloadConnection[a] != nullptr) {
downloadConnection[a]->suspendConnection();
}
}
}
@ -553,14 +647,17 @@ void Datacenter::getSessions(std::vector<int64_t> &sessions) {
if (genericConnection != nullptr) {
sessions.push_back(genericConnection->getSissionId());
}
if (tempConnection != nullptr) {
sessions.push_back(tempConnection->getSissionId());
}
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
if (uploadConnection[a] != nullptr) {
sessions.push_back(uploadConnection[a]->getSissionId());
}
}
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
if (downloadConnections[a] != nullptr) {
sessions.push_back(downloadConnections[a]->getSissionId());
if (downloadConnection[a] != nullptr) {
sessions.push_back(downloadConnection[a]->getSissionId());
}
}
}
@ -569,23 +666,26 @@ void Datacenter::recreateSessions() {
if (genericConnection != nullptr) {
genericConnection->recreateSession();
}
if (tempConnection != nullptr) {
tempConnection->recreateSession();
}
for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) {
if (uploadConnection[a] != nullptr) {
uploadConnection[a]->recreateSession();
}
}
for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) {
if (downloadConnections[a] != nullptr) {
downloadConnections[a]->recreateSession();
if (downloadConnection[a] != nullptr) {
downloadConnection[a]->recreateSession();
}
}
}
Connection *Datacenter::createDownloadConnection(uint8_t num) {
if (downloadConnections[num] == nullptr) {
downloadConnections[num] = new Connection(this, ConnectionTypeDownload);
if (downloadConnection[num] == nullptr) {
downloadConnection[num] = new Connection(this, ConnectionTypeDownload);
}
return downloadConnections[num];
return downloadConnection[num];
}
Connection *Datacenter::createUploadConnection(uint8_t num) {
@ -609,6 +709,13 @@ Connection *Datacenter::createPushConnection() {
return pushConnection;
}
Connection *Datacenter::createTempConnection() {
if (tempConnection == nullptr) {
tempConnection = new Connection(this, ConnectionTypeTemp);
}
return tempConnection;
}
uint32_t Datacenter::getDatacenterId() {
return datacenterId;
}
@ -908,61 +1015,6 @@ void Datacenter::aesIgeEncryption(uint8_t *buffer, uint8_t *key, uint8_t *iv, bo
}
}
int32_t Datacenter::selectPublicKey(std::vector<int64_t> &fingerprints) {
if (serverPublicKeys.empty()) {
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n"
"lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n"
"an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n"
"Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n"
"8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n"
"Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0xc3b42b026ce86b21LL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt\n"
"ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru\n"
"vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L\n"
"xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi\n"
"XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp\n"
"NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0x9a996a1db11c729bLL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+\n"
"DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB\n"
"1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s\n"
"g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z\n"
"hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f\n"
"x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0xb05b2a6f70cdea78LL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa\n"
"xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i\n"
"qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc\n"
"/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks\n"
"WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t\n"
"UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0x71e025b6c76033e3LL);
}
size_t count1 = fingerprints.size();
size_t count2 = serverPublicKeysFingerprints.size();
for (uint32_t a = 0; a < count1; a++) {
for (uint32_t b = 0; b < count2; b++) {
if ((uint64_t) fingerprints[a] == serverPublicKeysFingerprints[b]) {
return b;
}
}
}
return -1;
}
void Datacenter::processHandshakeResponse(TLObject *message, int64_t messageId) {
if (handshakeState == 0) {
return;
@ -977,10 +1029,86 @@ void Datacenter::processHandshakeResponse(TLObject *message, int64_t messageId)
handshakeState = 2;
TL_resPQ *result = (TL_resPQ *) message;
if (authNonce->isEqualTo(result->nonce.get())) {
int32_t keyIndex = selectPublicKey(result->server_public_key_fingerprints);
if (keyIndex < 0) {
DEBUG_E("dc%u handshake: can't find valid server public key", datacenterId);
beginHandshake(false);
std::string key;
int64_t keyFingerprint = 0;
size_t count1 = result->server_public_key_fingerprints.size();
if (isCdnDatacenter) {
std::map<int32_t, uint64_t>::iterator iter = cdnPublicKeysFingerprints.find(datacenterId);
if (iter != cdnPublicKeysFingerprints.end()) {
for (uint32_t a = 0; a < count1; a++) {
if ((uint64_t) result->server_public_key_fingerprints[a] == iter->second) {
keyFingerprint = iter->second;
key = cdnPublicKeys[datacenterId];
}
}
}
} else {
if (serverPublicKeys.empty()) {
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n"
"lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n"
"an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n"
"Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n"
"8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n"
"Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0xc3b42b026ce86b21LL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt\n"
"ksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru\n"
"vCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L\n"
"xI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi\n"
"XvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp\n"
"NTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0x9a996a1db11c729bLL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+\n"
"DyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB\n"
"1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s\n"
"g1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z\n"
"hRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f\n"
"x5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0xb05b2a6f70cdea78LL);
serverPublicKeys.push_back("-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa\n"
"xDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i\n"
"qAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc\n"
"/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks\n"
"WV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t\n"
"UiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n"
"-----END RSA PUBLIC KEY-----");
serverPublicKeysFingerprints.push_back(0x71e025b6c76033e3LL);
}
size_t count2 = serverPublicKeysFingerprints.size();
for (uint32_t a = 0; a < count1; a++) {
for (uint32_t b = 0; b < count2; b++) {
if ((uint64_t) result->server_public_key_fingerprints[a] == serverPublicKeysFingerprints[b]) {
keyFingerprint = result->server_public_key_fingerprints[a];
key = serverPublicKeys[a];
break;
}
}
if (keyFingerprint != 0) {
break;
}
}
}
if (keyFingerprint == 0) {
if (isCdnDatacenter) {
DEBUG_D("dc%u handshake: can't find valid cdn server public key", datacenterId);
loadCdnConfig(this);
} else {
DEBUG_E("dc%u handshake: can't find valid server public key", datacenterId);
beginHandshake(false);
}
return;
}
@ -1014,7 +1142,7 @@ void Datacenter::processHandshakeResponse(TLObject *message, int64_t messageId)
request->q->bytes[2] = (uint8_t) (q >> 8);
request->q->bytes[1] = (uint8_t) (q >> 16);
request->q->bytes[0] = (uint8_t) (q >> 24);
request->public_key_fingerprint = (int64_t) serverPublicKeysFingerprints[keyIndex];
request->public_key_fingerprint = keyFingerprint;
TL_p_q_inner_data *innerData = new TL_p_q_inner_data();
innerData->nonce = std::unique_ptr<ByteArray>(new ByteArray(authNonce));
@ -1038,8 +1166,6 @@ void Datacenter::processHandshakeResponse(TLObject *message, int64_t messageId)
RAND_bytes(innerDataBuffer->bytes() + SHA_DIGEST_LENGTH + innerDataSize, additionalSize);
}
std::string &key = serverPublicKeys[keyIndex];
BIO *keyBio = BIO_new(BIO_s_mem());
BIO_write(keyBio, key.c_str(), (int) key.length());
RSA *rsaKey = PEM_read_bio_RSAPublicKey(keyBio, NULL, NULL, NULL);
@ -1572,6 +1698,8 @@ Connection *Datacenter::createConnectionByType(uint32_t connectionType) {
return createUploadConnection(connectionNum);
case ConnectionTypePush:
return createPushConnection();
case ConnectionTypeTemp:
return createTempConnection();
default:
return nullptr;
}
@ -1584,7 +1712,7 @@ Connection *Datacenter::getDownloadConnection(uint8_t num, bool create) {
if (create) {
createDownloadConnection(num)->connect();
}
return downloadConnections[num];
return downloadConnection[num];
}
Connection *Datacenter::getUploadConnection(uint8_t num, bool create) {
@ -1617,6 +1745,16 @@ Connection *Datacenter::getPushConnection(bool create) {
return pushConnection;
}
Connection *Datacenter::getTempConnection(bool create) {
if (authKey == nullptr) {
return nullptr;
}
if (create) {
createTempConnection()->connect();
}
return tempConnection;
}
Connection *Datacenter::getConnectionByType(uint32_t connectionType, bool create) {
uint8_t connectionNum = (uint8_t) (connectionType >> 16);
connectionType = connectionType & 0x0000ffff;
@ -1629,13 +1767,15 @@ Connection *Datacenter::getConnectionByType(uint32_t connectionType, bool create
return getUploadConnection(connectionNum, create);
case ConnectionTypePush:
return getPushConnection(create);
case ConnectionTypeTemp:
return getTempConnection(create);
default:
return nullptr;
}
}
void Datacenter::exportAuthorization() {
if (exportingAuthorization) {
if (exportingAuthorization || isCdnDatacenter) {
return;
}
exportingAuthorization = true;
@ -1668,3 +1808,201 @@ void Datacenter::exportAuthorization() {
bool Datacenter::isExportingAuthorization() {
return exportingAuthorization;
}
void Datacenter::saveCdnConfigInternal(NativeByteBuffer *buffer) {
buffer->writeInt32(1);
buffer->writeInt32(cdnPublicKeys.size());
for (std::map<int32_t, std::string>::iterator iter = cdnPublicKeys.begin(); iter != cdnPublicKeys.end(); iter++) {
buffer->writeInt32(iter->first);
buffer->writeString(iter->second);
buffer->writeInt64(cdnPublicKeysFingerprints[iter->first]);
}
}
void Datacenter::saveCdnConfig() {
if (cdnConfig == nullptr) {
cdnConfig = new Config("cdnkeys.dat");
}
static NativeByteBuffer *sizeCalculator = new NativeByteBuffer(true);
sizeCalculator->clearCapacity();
saveCdnConfigInternal(sizeCalculator);
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(sizeCalculator->capacity());
saveCdnConfigInternal(buffer);
cdnConfig->writeConfig(buffer);
buffer->reuse();
}
void Datacenter::loadCdnConfig(Datacenter *datacenter) {
if (std::find(cdnWaitingDatacenters.begin(), cdnWaitingDatacenters.end(), datacenter) != cdnWaitingDatacenters.end()) {
return;
}
cdnWaitingDatacenters.push_back(datacenter);
if (loadingCdnKeys) {
return;
}
if (cdnPublicKeysFingerprints.empty()) {
if (cdnConfig == nullptr) {
cdnConfig = new Config("cdnkeys.dat");
}
NativeByteBuffer *buffer = cdnConfig->readConfig();
if (buffer != nullptr) {
uint32_t version = buffer->readUint32(nullptr);
if (version >= 1) {
size_t count = buffer->readUint32(nullptr);
for (uint32_t a = 0; a < count; a++) {
int dcId = buffer->readInt32(nullptr);
cdnPublicKeys[dcId] = buffer->readString(nullptr);
cdnPublicKeysFingerprints[dcId] = buffer->readUint64(nullptr);
}
}
buffer->reuse();
if (!cdnPublicKeysFingerprints.empty()) {
size_t count = cdnWaitingDatacenters.size();
for (uint32_t a = 0; a < count; a++) {
cdnWaitingDatacenters[a]->beginHandshake(false);
}
cdnWaitingDatacenters.clear();
return;
}
}
}
loadingCdnKeys = true;
TL_help_getCdnConfig *request = new TL_help_getCdnConfig();
ConnectionsManager::getInstance().sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) {
if (response != nullptr) {
TL_cdnConfig *config = (TL_cdnConfig *) response;
size_t count = config->public_keys.size();
BIO *keyBio = BIO_new(BIO_s_mem());
NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(1024);
static uint8_t sha1Buffer[20];
for (uint32_t a = 0; a < count; a++) {
TL_cdnPublicKey *publicKey = config->public_keys[a].get();
cdnPublicKeys[publicKey->dc_id] = publicKey->public_key;
BIO_write(keyBio, publicKey->public_key.c_str(), (int) publicKey->public_key.length());
RSA *rsaKey = PEM_read_bio_RSAPublicKey(keyBio, NULL, NULL, NULL);
int nBytes = BN_num_bytes(rsaKey->n);
int eBytes = BN_num_bytes(rsaKey->e);
std::string nStr(nBytes, 0), eStr(eBytes, 0);
BN_bn2bin(rsaKey->n, (uint8_t *)&nStr[0]);
BN_bn2bin(rsaKey->e, (uint8_t *)&eStr[0]);
buffer->writeString(nStr);
buffer->writeString(eStr);
SHA1(buffer->bytes(), buffer->position(), sha1Buffer);
cdnPublicKeysFingerprints[publicKey->dc_id] = ((uint64_t) sha1Buffer[19]) << 56 |
((uint64_t) sha1Buffer[18]) << 48 |
((uint64_t) sha1Buffer[17]) << 40 |
((uint64_t) sha1Buffer[16]) << 32 |
((uint64_t) sha1Buffer[15]) << 24 |
((uint64_t) sha1Buffer[14]) << 16 |
((uint64_t) sha1Buffer[13]) << 8 |
((uint64_t) sha1Buffer[12]);
RSA_free(rsaKey);
if (a != count - 1) {
buffer->position(0);
BIO_reset(keyBio);
}
}
buffer->reuse();
BIO_free(keyBio);
count = cdnWaitingDatacenters.size();
for (uint32_t a = 0; a < count; a++) {
cdnWaitingDatacenters[a]->beginHandshake(false);
}
cdnWaitingDatacenters.clear();
saveCdnConfig();
}
loadingCdnKeys = false;
}, nullptr, RequestFlagEnableUnauthorized | RequestFlagWithoutLogin, DEFAULT_DATACENTER_ID, ConnectionTypeGeneric, true);
}
TL_help_configSimple *Datacenter::decodeSimpleConfig(NativeByteBuffer *buffer) {
TL_help_configSimple *result = nullptr;
static std::string public_key =
"-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n"
"fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n"
"192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n"
"9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n"
"fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n"
"Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n"
"-----END RSA PUBLIC KEY-----";
BIO *keyBio = BIO_new(BIO_s_mem());
BIO_write(keyBio, public_key.c_str(), (int) public_key.length());
RSA *rsaKey = PEM_read_bio_RSAPublicKey(keyBio, NULL, NULL, NULL);
if (rsaKey == nullptr) {
if (rsaKey == nullptr) {
DEBUG_E("Invalid rsa public key");
return nullptr;
}
}
BIGNUM x, y;
uint8_t *bytes = buffer->bytes();
if (bnContext == nullptr) {
bnContext = BN_CTX_new();
}
BN_init(&x);
BN_init(&y);
BN_bin2bn(bytes, 256, &x);
if (BN_mod_exp(&y, &x, rsaKey->e, rsaKey->n, bnContext) == 1) {
uint8_t temp[256];
/*BN_bn2bin(&y, temp);
std::string res = "";
for (int a = 0; a < 256; a++) {
char buf[20];
sprintf(buf, "%x", temp[a]);
res += buf;
}
DEBUG_D("hex = %s", res.c_str());*/
unsigned l = 256 - BN_num_bytes(&y);
memset(bytes, 0, l);
if (BN_bn2bin(&y, bytes + l) == 256 - l) {
AES_KEY aeskey;
unsigned char iv[16];
memcpy(iv, bytes + 16, 16);
AES_set_decrypt_key(bytes, 256, &aeskey);
AES_cbc_encrypt(bytes + 32, bytes + 32, 256 - 32, &aeskey, iv, AES_DECRYPT);
EVP_MD_CTX ctx;
unsigned char sha256_out[32];
unsigned olen = 0;
EVP_MD_CTX_init(&ctx);
EVP_DigestInit_ex(&ctx, EVP_sha256(), NULL);
EVP_DigestUpdate(&ctx, bytes + 32, 256 - 32 - 16);
EVP_DigestFinal_ex(&ctx, sha256_out, &olen);
EVP_MD_CTX_cleanup(&ctx);
if (olen == 32) {
if (memcmp(bytes + 256 - 16, sha256_out, 16) == 0) {
unsigned data_len = *(unsigned *) (bytes + 32);
if (data_len && data_len <= 256 - 32 - 16 && !(data_len & 3)) {
buffer->position(32 + 4);
bool error = false;
result = TL_help_configSimple::TLdeserialize(buffer, buffer->readUint32(&error), error);
if (error) {
if (result != nullptr) {
delete result;
result = nullptr;
}
}
} else {
DEBUG_E("TL data length field invalid - %d", data_len);
}
} else {
DEBUG_E("RSA signature check FAILED (SHA256 mismatch)");
}
}
}
}
BN_free(&x);
BN_free(&y);
RSA_free(rsaKey);
BIO_free(keyBio);
return result;
}

View File

@ -19,6 +19,7 @@ class TL_future_salt;
class Connection;
class NativeByteBuffer;
class TL_future_salt;
class TL_help_configSimple;
class ByteArray;
class TLObject;
class Config;
@ -35,7 +36,7 @@ public:
void addAddressAndPort(std::string address, uint32_t port, uint32_t flags);
void nextAddressOrPort(uint32_t flags);
void storeCurrentAddressAndPortNum();
void replaceAddressesAndPorts(std::vector<std::string> &newAddresses, std::map<std::string, uint32_t> &newPorts, uint32_t flags);
void replaceAddresses(std::vector<TcpAddress> &newAddresses, uint32_t flags);
void serializeToStream(NativeByteBuffer *stream);
void clear();
void clearServerSalts();
@ -55,6 +56,7 @@ public:
Connection *getUploadConnection(uint8_t num, bool create);
Connection *getGenericConnection(bool create);
Connection *getPushConnection(bool create);
Connection *getTempConnection(bool create);
Connection *getConnectionByType(uint32_t connectionType, bool create);
static void aesIgeEncryption(uint8_t *buffer, uint8_t *key, uint8_t *iv, bool encrypt, bool changeIv, uint32_t length);
@ -72,21 +74,24 @@ private:
uint32_t datacenterId;
Connection *genericConnection = nullptr;
Connection *downloadConnections[DOWNLOAD_CONNECTIONS_COUNT];
Connection *tempConnection = nullptr;
Connection *downloadConnection[DOWNLOAD_CONNECTIONS_COUNT];
Connection *uploadConnection[UPLOAD_CONNECTIONS_COUNT];
Connection *pushConnection = nullptr;
uint32_t lastInitVersion = 0;
bool authorized = false;
std::vector<std::string> addressesIpv4;
std::vector<std::string> addressesIpv6;
std::vector<std::string> addressesIpv4Download;
std::vector<std::string> addressesIpv6Download;
std::map<std::string, uint32_t> ports;
std::vector<TcpAddress> addressesIpv4;
std::vector<TcpAddress> addressesIpv6;
std::vector<TcpAddress> addressesIpv4Download;
std::vector<TcpAddress> addressesIpv6Download;
std::vector<TcpAddress> addressesIpv4Temp;
std::vector<std::unique_ptr<TL_future_salt>> serverSalts;
uint32_t currentPortNumIpv4 = 0;
uint32_t currentAddressNumIpv4 = 0;
uint32_t currentPortNumIpv4Temp = 0;
uint32_t currentAddressNumIpv4Temp = 0;
uint32_t currentPortNumIpv6 = 0;
uint32_t currentAddressNumIpv6 = 0;
uint32_t currentPortNumIpv4Download = 0;
@ -97,13 +102,15 @@ private:
int64_t authKeyId = 0;
int32_t overridePort = -1;
Config *config = nullptr;
bool isCdnDatacenter = false;
const uint32_t configVersion = 5;
const uint32_t configVersion = 7;
const uint32_t paramsConfigVersion = 1;
Connection *createDownloadConnection(uint8_t num);
Connection *createUploadConnection(uint8_t num);
Connection *createGenericConnection();
Connection *createTempConnection();
Connection *createPushConnection();
Connection *createConnectionByType(uint32_t connectionType);
@ -120,11 +127,16 @@ private:
void sendRequestData(TLObject *object, bool important);
void cleanupHandshake();
void sendAckRequest(int64_t messageId);
int32_t selectPublicKey(std::vector<int64_t> &fingerprints);
bool exportingAuthorization = false;
void exportAuthorization();
static void saveCdnConfig();
static void saveCdnConfigInternal(NativeByteBuffer *buffer);
static void loadCdnConfig(Datacenter *datacenter);
static TL_help_configSimple *decodeSimpleConfig(NativeByteBuffer *buffer);
friend class ConnectionsManager;
};

View File

@ -21,7 +21,7 @@
#define DEFAULT_DATACENTER_ID INT_MAX
#define DC_UPDATE_TIME 60 * 60
#define DOWNLOAD_CONNECTIONS_COUNT 2
#define UPLOAD_CONNECTIONS_COUNT 2
#define UPLOAD_CONNECTIONS_COUNT 4
#define CONNECTION_BACKGROUND_KEEP_TIME 10000
#define DOWNLOAD_CHUNK_SIZE 1024 * 32
@ -44,6 +44,7 @@ class FileLoadOperation;
typedef std::function<void(TLObject *response, TL_error *error, int32_t networkType)> onCompleteFunc;
typedef std::function<void()> onQuickAckFunc;
typedef std::function<void()> onWriteToSocketFunc;
typedef std::list<std::unique_ptr<Request>> requestsList;
typedef requestsList::iterator requestsIter;
@ -59,12 +60,23 @@ enum ConnectionType {
ConnectionTypeDownload = 2,
ConnectionTypeUpload = 4,
ConnectionTypePush = 8,
ConnectionTypeTemp = 16
};
enum TcpAddressFlag {
TcpAddressFlagIpv6 = 1,
TcpAddressFlagDownload = 2,
TcpAddressFlagO = 4,
TcpAddressFlagCdn = 8,
TcpAddressFlagStatic = 16,
TcpAddressFlagTemp = 2048
};
enum ConnectionState {
ConnectionStateConnecting = 1,
ConnectionStateWaitingForNetwork = 2,
ConnectionStateConnected = 3
ConnectionStateConnected = 3,
ConnectionStateConnectingViaProxy = 4
};
enum EventObjectType {
@ -87,6 +99,20 @@ enum FileLoadFailReason {
FileLoadFailReasonRetryLimit
};
class TcpAddress {
public:
std::string address;
int32_t flags;
int32_t port;
TcpAddress(std::string addr, int32_t p, int32_t f) {
address = addr;
port = p;
flags = f;
}
};
typedef std::function<void(std::string path)> onFinishedFunc;
typedef std::function<void(FileLoadFailReason reason)> onFailedFunc;
typedef std::function<void(float progress)> onProgressChangedFunc;
@ -101,6 +127,7 @@ typedef struct ConnectiosManagerDelegate {
virtual void onInternalPushReceived() = 0;
virtual void onBytesSent(int32_t amount, int32_t networkType) = 0;
virtual void onBytesReceived(int32_t amount, int32_t networkType) = 0;
virtual void onRequestNewServerIpAndPort(int32_t second) = 0;
} ConnectiosManagerDelegate;
#define AllConnectionTypes ConnectionTypeGeneric | ConnectionTypeDownload | ConnectionTypeUpload

View File

@ -7,6 +7,7 @@
*/
#include <memory.h>
#include <arpa/inet.h>
#include "MTProtoScheme.h"
#include "ApiScheme.h"
#include "FileLog.h"
@ -852,6 +853,47 @@ void initConnection::serializeToStream(NativeByteBuffer *stream) {
stream->writeString(device_model);
stream->writeString(system_version);
stream->writeString(app_version);
stream->writeString(system_lang_code);
stream->writeString(lang_pack);
stream->writeString(lang_code);
query->serializeToStream(stream);
}
void TL_ipPort::readParams(NativeByteBuffer *stream, bool &error) {
struct in_addr ip_addr;
ip_addr.s_addr = htonl(stream->readUint32(&error));
ipv4 = inet_ntoa(ip_addr);
port = stream->readUint32(&error);
}
TL_help_configSimple *TL_help_configSimple::TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error) {
if (TL_help_configSimple::constructor != constructor) {
error = true;
DEBUG_E("can't parse magic %x in TL_help_configSimple", constructor);
return nullptr;
}
TL_help_configSimple *result = new TL_help_configSimple();
result->readParams(stream, error);
return result;
}
void TL_help_configSimple::readParams(NativeByteBuffer *stream, bool &error) {
date = stream->readInt32(&error);
expires = stream->readInt32(&error);
dc_id = stream->readUint32(&error);
int32_t magic = stream->readInt32(&error);
if (magic != 0x1cb5c415) {
error = true;
DEBUG_E("wrong Vector magic, got %x", magic);
return;
}
uint32_t count = stream->readUint32(&error);
for (uint32_t a = 0; a < count; a++) {
TL_ipPort *object = new TL_ipPort();
object->readParams(stream, error);
if (error) {
return;
}
ip_port_list.push_back(std::unique_ptr<TL_ipPort>(object));
}
}

View File

@ -614,16 +614,41 @@ public:
class initConnection : public TLObject {
public:
static const uint32_t constructor = 0x69796de9;
static const uint32_t constructor = 0xc7481da6;
int32_t api_id;
std::string device_model;
std::string system_version;
std::string app_version;
std::string system_lang_code;
std::string lang_pack;
std::string lang_code;
std::unique_ptr<TLObject> query;
void serializeToStream(NativeByteBuffer *stream);
};
class TL_ipPort : public TLObject {
public:
std::string ipv4;
uint32_t port;
void readParams(NativeByteBuffer *stream, bool &error);
};
class TL_help_configSimple : public TLObject {
public:
static const uint32_t constructor = 0xd997c3c5;
int32_t date;
int32_t expires;
uint32_t dc_id;
std::vector<std::unique_ptr<TL_ipPort>> ip_port_list;
static TL_help_configSimple *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error);
void readParams(NativeByteBuffer *stream, bool &error);
};
#endif

View File

@ -12,13 +12,14 @@
#include "MTProtoScheme.h"
#include "ConnectionsManager.h"
Request::Request(int32_t token, ConnectionType type, uint32_t flags, uint32_t datacenter, onCompleteFunc completeFunc, onQuickAckFunc quickAckFunc) {
Request::Request(int32_t token, ConnectionType type, uint32_t flags, uint32_t datacenter, onCompleteFunc completeFunc, onQuickAckFunc quickAckFunc, onWriteToSocketFunc writeToSocketFunc) {
requestToken = token;
connectionType = type;
requestFlags = flags;
datacenterId = datacenter;
onCompleteRequestCallback = completeFunc;
onQuickAckCallback = quickAckFunc;
onWriteToSocketCallback = writeToSocketFunc;
dataType = (uint8_t) (requestFlags >> 24);
}
@ -32,6 +33,10 @@ Request::~Request() {
jniEnv->DeleteGlobalRef(ptr2);
ptr2 = nullptr;
}
if (ptr3 != nullptr) {
jniEnv->DeleteGlobalRef(ptr3);
ptr3 = nullptr;
}
#endif
}
@ -59,6 +64,12 @@ void Request::onComplete(TLObject *result, TL_error *error, int32_t networkType)
}
}
void Request::onWriteToSocket() {
if (onWriteToSocketCallback != nullptr) {
onWriteToSocketCallback();
}
}
void Request::onQuickAck() {
if (onQuickAckCallback != nullptr) {
onQuickAckCallback();

View File

@ -24,7 +24,7 @@ class TL_error;
class Request {
public:
Request(int32_t token, ConnectionType type, uint32_t flags, uint32_t datacenter, onCompleteFunc completeFunc, onQuickAckFunc quickAckFunc);
Request(int32_t token, ConnectionType type, uint32_t flags, uint32_t datacenter, onCompleteFunc completeFunc, onQuickAckFunc quickAckFunc, onWriteToSocketFunc writeToSocketFunc);
~Request();
int64_t messageId = 0;
@ -50,17 +50,20 @@ public:
std::unique_ptr<TLObject> rpcRequest;
onCompleteFunc onCompleteRequestCallback;
onQuickAckFunc onQuickAckCallback;
onWriteToSocketFunc onWriteToSocketCallback;
void addRespondMessageId(int64_t id);
bool respondsToMessageId(int64_t id);
void clear(bool time);
void onComplete(TLObject *result, TL_error *error, int32_t networkType);
void onQuickAck();
void onWriteToSocket();
TLObject *getRpcRequest();
#ifdef ANDROID
jobject ptr1 = nullptr;
jobject ptr2 = nullptr;
jobject ptr3 = nullptr;
#endif
private:

View File

@ -66,6 +66,7 @@
android:allowBackup="false"
android:hardwareAccelerated="@bool/useHardwareAcceleration"
android:icon="@drawable/ic_launcher"
android:roundIcon="@drawable/ic_launcher"
android:largeHeap="true"
android:theme="@style/Theme.TMessages.Start"
android:manageSpaceActivity="org.telegram.ui.ManageSpaceActivity">
@ -124,6 +125,8 @@
<data android:host="telegram.me" android:scheme="https" />
<data android:host="telegram.dog" android:scheme="http" />
<data android:host="telegram.dog" android:scheme="https" />
<data android:host="telesco.pe" android:scheme="http" />
<data android:host="telesco.pe" android:scheme="https" />
<data android:host="t.me" android:scheme="http" />
<data android:host="t.me" android:scheme="https" />
</intent-filter>
@ -176,6 +179,15 @@
android:resizeableActivity="false"
android:windowSoftInputMode="adjustResize|stateHidden">
</activity>
<activity
android:name=".GoogleVoiceClientActivity"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.voicesearch.SEND_MESSAGE_TO_CONTACTS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name="org.telegram.ui.VoIPActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
@ -254,12 +266,23 @@
<service android:name=".ClearCacheService" android:exported="false"/>
<service android:name=".VideoEncodingService" android:enabled="true"/>
<service android:name=".voip.VoIPService" android:enabled="true"/>
<service android:name=".GoogleVoiceClientService"/>
<service android:name=".MusicPlayerService" android:exported="true" android:enabled="true"/>
<service android:name=".MusicBrowserService" android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<service android:name=".WearDataLayerListenerService">
<intent-filter>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
<data android:scheme="wear" android:host="*" />
</intent-filter>
</service>
<receiver android:name=".MusicPlayerReceiver" >
<intent-filter>
@ -315,7 +338,11 @@
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon" android:resource="@drawable/ic_player" />
<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc" />
<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="face" />
<meta-data android:name="com.google.android.gms.vision.DEPENDENCIES" android:value="face" />
<meta-data android:name="com.samsung.android.icon_container.has_icon_container" android:value="true"/>
<meta-data android:name="android.max_aspect" android:value="2.5" />
</application>
</manifest>

View File

@ -18,6 +18,7 @@ import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@ -111,6 +112,7 @@ public class AndroidUtilities {
public static int statusBarHeight = 0;
public static float density = 1;
public static Point displaySize = new Point();
public static int roundMessageSize;
public static boolean incorrectDisplaySizeFix;
public static Integer photoSize = null;
public static DisplayMetrics displayMetrics = new DisplayMetrics();
@ -549,6 +551,13 @@ public class AndroidUtilities {
displaySize.y = newSize;
}
}
if (roundMessageSize == 0) {
if (AndroidUtilities.isTablet()) {
roundMessageSize = (int) (AndroidUtilities.getMinTabletSide() * 0.6f);
} else {
roundMessageSize = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.6f);
}
}
FileLog.e("display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi);
} catch (Exception e) {
FileLog.e(e);
@ -1606,6 +1615,10 @@ public class AndroidUtilities {
}
}
public static boolean isBannedForever(int time) {
return Math.abs(time - System.currentTimeMillis() / 1000) > 5 * 365 * 24 * 60 * 60;
}
public static void setRectToRect(Matrix matrix, RectF src, RectF dst, int rotation, Matrix.ScaleToFit align) {
float tx, sx;
float ty, sy;
@ -1641,4 +1654,104 @@ public class AndroidUtilities {
matrix.preScale(sx, sy);
matrix.preTranslate(tx, ty);
}
public static boolean handleProxyIntent(Activity activity, Intent intent) {
if (intent == null) {
return false;
}
try {
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
return false;
}
Uri data = intent.getData();
if (data != null) {
String user = null;
String password = null;
String port = null;
String address = null;
String scheme = data.getScheme();
if (scheme != null) {
if ((scheme.equals("http") || scheme.equals("https"))) {
String host = data.getHost().toLowerCase();
if (host.equals("telegram.me") || host.equals("t.me") || host.equals("telegram.dog") || host.equals("telesco.pe")) {
String path = data.getPath();
if (path != null) {
if (path.startsWith("/socks")) {
address = data.getQueryParameter("server");
port = data.getQueryParameter("port");
user = data.getQueryParameter("user");
password = data.getQueryParameter("pass");
}
}
}
} else if (scheme.equals("tg")) {
String url = data.toString();
if (url.startsWith("tg:socks") || url.startsWith("tg://socks")) {
url = url.replace("tg:proxy", "tg://telegram.org").replace("tg://proxy", "tg://telegram.org");
data = Uri.parse(url);
address = data.getQueryParameter("server");
port = data.getQueryParameter("port");
user = data.getQueryParameter("user");
password = data.getQueryParameter("pass");
}
}
}
if (!TextUtils.isEmpty(address) && !TextUtils.isEmpty(port)) {
if (user == null) {
user = "";
}
if (password == null) {
password = "";
}
showProxyAlert(activity, address, port, user, password);
return true;
}
}
} catch (Exception ignore) {
}
return false;
}
public static void showProxyAlert(Activity activity, final String address, final String port, final String user, final String password) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(LocaleController.getString("Proxy", R.string.Proxy));
StringBuilder stringBuilder = new StringBuilder(LocaleController.getString("EnableProxyAlert", R.string.EnableProxyAlert));
stringBuilder.append("\n\n");
stringBuilder.append(LocaleController.getString("UseProxyAddress", R.string.UseProxyAddress)).append(": ").append(address).append("\n");
stringBuilder.append(LocaleController.getString("UseProxyPort", R.string.UseProxyPort)).append(": ").append(port).append("\n");
if (!TextUtils.isEmpty(user)) {
stringBuilder.append(LocaleController.getString("UseProxyUsername", R.string.UseProxyUsername)).append(": ").append(user).append("\n");
}
if (!TextUtils.isEmpty(password)) {
stringBuilder.append(LocaleController.getString("UseProxyPassword", R.string.UseProxyPassword)).append(": ").append(password).append("\n");
}
stringBuilder.append("\n").append(LocaleController.getString("EnableProxyAlert2", R.string.EnableProxyAlert2));
builder.setMessage(stringBuilder.toString());
builder.setPositiveButton(LocaleController.getString("ConnectingToProxyEnable", R.string.ConnectingToProxyEnable), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit();
editor.putBoolean("proxy_enabled", true);
editor.putString("proxy_ip", address);
int p = Utilities.parseInt(port);
editor.putInt("proxy_port", p);
if (TextUtils.isEmpty(password)) {
editor.remove("proxy_pass");
} else {
editor.putString("proxy_pass", password);
}
if (TextUtils.isEmpty(user)) {
editor.remove("proxy_user");
} else {
editor.putString("proxy_user", user);
}
editor.commit();
ConnectionsManager.native_setProxySettings(address, p, user, password);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.proxySettingsChanged);
}
});
builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null);
builder.show().setCanceledOnTouchOutside(true);
}
}

View File

@ -143,24 +143,27 @@ public class ApplicationLoader extends Application {
UserConfig.loadConfig();
String deviceModel;
String systemLangCode;
String langCode;
String appVersion;
String systemVersion;
String configPath = getFilesDirFixed().toString();
try {
systemLangCode = LocaleController.getSystemLocaleStringIso639();
langCode = LocaleController.getLocaleStringIso639();
deviceModel = Build.MANUFACTURER + Build.MODEL;
PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0);
appVersion = pInfo.versionName + " (" + pInfo.versionCode + ")";
systemVersion = "SDK " + Build.VERSION.SDK_INT;
} catch (Exception e) {
langCode = "en";
systemLangCode = "en";
langCode = "";
deviceModel = "Android unknown";
appVersion = "App version unknown";
systemVersion = "SDK " + Build.VERSION.SDK_INT;
}
if (langCode.trim().length() == 0) {
if (systemLangCode.trim().length() == 0) {
langCode = "en";
}
if (deviceModel.trim().length() == 0) {
@ -177,7 +180,7 @@ public class ApplicationLoader extends Application {
boolean enablePushConnection = preferences.getBoolean("pushConnection", true);
MessagesController.getInstance();
ConnectionsManager.getInstance().init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getClientUserId(), enablePushConnection);
ConnectionsManager.getInstance().init(BuildVars.BUILD_VERSION, TLRPC.LAYER, BuildVars.APP_ID, deviceModel, systemVersion, appVersion, langCode, systemLangCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getClientUserId(), enablePushConnection);
if (UserConfig.getCurrentUser() != null) {
MessagesController.getInstance().putUser(UserConfig.getCurrentUser(), true);
ConnectionsManager.getInstance().applyCountryPortNumber(UserConfig.getCurrentUser().phone);

View File

@ -180,7 +180,7 @@ public class Bitmaps {
if (config != null) {
switch (config) {
case RGB_565:
newConfig = Bitmap.Config.RGB_565;
newConfig = Bitmap.Config.ARGB_8888;
break;
case ALPHA_8:
newConfig = Bitmap.Config.ALPHA_8;

View File

@ -11,8 +11,8 @@ package org.telegram.messenger;
public class BuildVars {
public static boolean DEBUG_VERSION = false;
public static boolean DEBUG_PRIVATE_VERSION = false;
public static int BUILD_VERSION = 957;
public static String BUILD_VERSION_STRING = "3.18";
public static int BUILD_VERSION = 1030;
public static String BUILD_VERSION_STRING = "4.1";
public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id
public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id
public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here";

View File

@ -23,7 +23,7 @@ public class ChatObject {
}
public static boolean isKickedFromChat(TLRPC.Chat chat) {
return chat == null || chat instanceof TLRPC.TL_chatEmpty || chat instanceof TLRPC.TL_chatForbidden || chat instanceof TLRPC.TL_channelForbidden || chat.kicked || chat.deactivated;
return chat == null || chat instanceof TLRPC.TL_chatEmpty || chat instanceof TLRPC.TL_chatForbidden || chat instanceof TLRPC.TL_channelForbidden || chat.kicked || chat.deactivated || chat.banned_rights != null && chat.banned_rights.view_messages;
}
public static boolean isNotInChat(TLRPC.Chat chat) {
@ -34,6 +34,50 @@ public class ChatObject {
return chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden;
}
public static boolean hasAdminRights(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.flags != 0);
}
public static boolean canChangeChatInfo(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.change_info);
}
public static boolean canAddAdmins(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.add_admins);
}
public static boolean canBlockUsers(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.ban_users);
}
public static boolean canSendStickers(TLRPC.Chat chat) {
return chat == null || chat != null && (chat.banned_rights == null || !chat.banned_rights.send_media && !chat.banned_rights.send_stickers);
}
public static boolean canSendEmbed(TLRPC.Chat chat) {
return chat == null || chat != null && (chat.banned_rights == null || !chat.banned_rights.send_media && !chat.banned_rights.embed_links);
}
public static boolean canSendMessages(TLRPC.Chat chat) {
return chat == null || chat != null && (chat.banned_rights == null || !chat.banned_rights.send_messages);
}
public static boolean canPost(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.post_messages);
}
public static boolean canAddViaLink(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.invite_link);
}
public static boolean canAddUsers(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.invite_users);
}
public static boolean canEditInfo(TLRPC.Chat chat) {
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.change_info);
}
public static boolean isChannel(int chatId) {
TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId);
return chat instanceof TLRPC.TL_channel || chat instanceof TLRPC.TL_channelForbidden;
@ -41,11 +85,11 @@ public class ChatObject {
public static boolean isCanWriteToChannel(int chatId) {
TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId);
return chat != null && (chat.creator || chat.editor || chat.megagroup);
return chat != null && (chat.creator || chat.admin_rights != null && chat.admin_rights.post_messages || chat.megagroup);
}
public static boolean canWriteToChat(TLRPC.Chat chat) {
return !isChannel(chat) || chat.creator || chat.editor || !chat.broadcast;
return !isChannel(chat) || chat.creator || chat.admin_rights != null && chat.admin_rights.post_messages || !chat.broadcast;
}
public static TLRPC.Chat getChatByDialog(long did) {

View File

@ -38,7 +38,15 @@ public class ClearCacheService extends IntentService {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
long diff = 60 * 60 * 1000 * 24 * (keepMedia == 0 ? 7 : 30);
int days;
if (keepMedia == 0) {
days = 7;
} else if (keepMedia == 1) {
days = 30;
} else {
days = 3;
}
long diff = 60 * 60 * 1000 * 24 * days;
final HashMap<Integer, File> paths = ImageLoader.getInstance().createMediaPaths();
for (HashMap.Entry<Integer, File> entry : paths.entrySet()) {
if (entry.getKey() == FileLoader.MEDIA_DIR_CACHE) {

View File

@ -24,7 +24,7 @@ public class DispatchQueue extends Thread {
start();
}
private void sendMessage(Message msg, int delay) {
public void sendMessage(Message msg, int delay) {
try {
syncLatch.await();
if (delay <= 0) {
@ -72,10 +72,19 @@ public class DispatchQueue extends Thread {
}
}
public void handleMessage(Message inputMessage) {
}
@Override
public void run() {
Looper.prepare();
handler = new Handler();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
DispatchQueue.this.handleMessage(msg);
}
};
syncLatch.countDown();
Looper.loop();
}

View File

@ -27,6 +27,7 @@ public class FileLoadOperation {
private int offset;
private TLRPC.TL_upload_file response;
private TLRPC.TL_upload_webFile responseWeb;
private TLRPC.TL_upload_cdnFile responseCdn;
}
private final static int stateIdle = 0;
@ -56,6 +57,13 @@ public class FileLoadOperation {
private int requestsCount;
private int renameRetryCount;
private boolean isCdn;
private byte[] cdnIv;
private byte[] cdnKey;
private byte[] cdnToken;
private int cdnDatacenterId;
private boolean reuploadingCdn;
private int nextDownloadOffset;
private ArrayList<RequestInfo> requestInfos;
private ArrayList<RequestInfo> delayedRequestInfos;
@ -383,6 +391,9 @@ public class FileLoadOperation {
} else if (requestInfo.responseWeb != null) {
requestInfo.responseWeb.disableFree = false;
requestInfo.responseWeb.freeResources();
} else if (requestInfo.responseCdn != null) {
requestInfo.responseCdn.disableFree = false;
requestInfo.responseCdn.freeResources();
}
}
delayedRequestInfos.clear();
@ -449,8 +460,10 @@ public class FileLoadOperation {
delayedRequestInfos.add(requestInfo);
if (requestInfo.response != null) {
requestInfo.response.disableFree = true;
} else {
} else if (requestInfo.responseWeb != null) {
requestInfo.responseWeb.disableFree = true;
} else if (requestInfo.responseCdn != null) {
requestInfo.responseCdn.disableFree = true;
}
}
return;
@ -459,13 +472,25 @@ public class FileLoadOperation {
NativeByteBuffer bytes;
if (requestInfo.response != null) {
bytes = requestInfo.response.bytes;
} else {
} else if (requestInfo.responseWeb != null) {
bytes = requestInfo.responseWeb.bytes;
} else if (requestInfo.responseCdn != null) {
bytes = requestInfo.responseCdn.bytes;
} else {
bytes = null;
}
if (bytes == null || bytes.limit() == 0) {
onFinishLoadingFile(true);
return;
}
if (requestInfo.responseCdn != null) {
int offset = requestInfo.offset / 16;
cdnIv[15] = (byte) (offset & 0xff);
cdnIv[14] = (byte) ((offset >> 8) & 0xff);
cdnIv[13] = (byte) ((offset >> 16) & 0xff);
cdnIv[12] = (byte) ((offset >> 24) & 0xff);
Utilities.aesCtrDecryption(bytes.buffer, cdnKey, cdnIv, 0, bytes.limit());
}
int currentBytesSize = bytes.limit();
downloadedBytes += currentBytesSize;
boolean finishedDownloading = currentBytesSize != currentDownloadChunkSize || (totalBytesCount == downloadedBytes || downloadedBytes % currentDownloadChunkSize != 0) && (totalBytesCount <= 0 || totalBytesCount <= downloadedBytes);
@ -496,9 +521,12 @@ public class FileLoadOperation {
if (delayedRequestInfo.response != null) {
delayedRequestInfo.response.disableFree = false;
delayedRequestInfo.response.freeResources();
} else {
} else if (delayedRequestInfo.responseWeb != null) {
delayedRequestInfo.responseWeb.disableFree = false;
delayedRequestInfo.responseWeb.freeResources();
} else if (delayedRequestInfo.responseCdn != null) {
delayedRequestInfo.responseCdn.disableFree = false;
delayedRequestInfo.responseCdn.freeResources();
}
break;
}
@ -570,13 +598,45 @@ public class FileLoadOperation {
}
}
private void clearOperaion(RequestInfo currentInfo) {
int minOffset = Integer.MAX_VALUE;
for (int a = 0; a < requestInfos.size(); a++) {
RequestInfo info = requestInfos.get(a);
minOffset = Math.min(info.offset, minOffset);
if (currentInfo == info) {
continue;
}
if (info.requestToken != 0) {
ConnectionsManager.getInstance().cancelRequest(info.requestToken, true);
}
}
requestInfos.clear();
for (int a = 0; a < delayedRequestInfos.size(); a++) {
RequestInfo info = delayedRequestInfos.get(a);
if (info.response != null) {
info.response.disableFree = false;
info.response.freeResources();
} else if (info.responseWeb != null) {
info.responseWeb.disableFree = false;
info.responseWeb.freeResources();
} else if (info.responseCdn != null) {
info.responseCdn.disableFree = false;
info.responseCdn.freeResources();
}
minOffset = Math.min(info.offset, minOffset);
}
delayedRequestInfos.clear();
requestsCount = 0;
nextDownloadOffset = minOffset;
}
private void startDownloadRequest() {
if (state != stateDownloading || totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount || requestInfos.size() + delayedRequestInfos.size() >= currentMaxDownloadRequests) {
return;
}
int count = 1;
if (totalBytesCount > 0) {
count = Math.max(0, currentMaxDownloadRequests - requestInfos.size()/* - delayedRequestInfos.size()*/);
count = Math.max(0, currentMaxDownloadRequests - requestInfos.size());
}
for (int a = 0; a < count; a++) {
@ -584,24 +644,31 @@ public class FileLoadOperation {
break;
}
boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + currentDownloadChunkSize >= totalBytesCount;
TLObject request;
final TLObject request;
int offset;
int flags;
if (webLocation != null) {
TLRPC.TL_upload_getWebFile req = new TLRPC.TL_upload_getWebFile();
req.location = webLocation;
int connectionType = requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2;
int flags = (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors;
if (isCdn) {
TLRPC.TL_upload_getCdnFile req = new TLRPC.TL_upload_getCdnFile();
req.file_token = cdnToken;
req.offset = offset = nextDownloadOffset;
req.limit = currentDownloadChunkSize;
request = req;
//flags = ConnectionsManager.ConnectionTypeGeneric;
flags = requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2;
flags |= ConnectionsManager.RequestFlagEnableUnauthorized;
} else {
TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
req.location = location;
req.offset = offset = nextDownloadOffset;
req.limit = currentDownloadChunkSize;
request = req;
flags = requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2;
if (webLocation != null) {
TLRPC.TL_upload_getWebFile req = new TLRPC.TL_upload_getWebFile();
req.location = webLocation;
req.offset = offset = nextDownloadOffset;
req.limit = currentDownloadChunkSize;
request = req;
} else {
TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
req.location = location;
req.offset = offset = nextDownloadOffset;
req.limit = currentDownloadChunkSize;
request = req;
}
}
nextDownloadOffset += currentDownloadChunkSize;
final RequestInfo requestInfo = new RequestInfo();
@ -610,25 +677,85 @@ public class FileLoadOperation {
requestInfo.requestToken = ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
if (response instanceof TLRPC.TL_upload_file) {
requestInfo.response = (TLRPC.TL_upload_file) response;
}else {
requestInfo.responseWeb = (TLRPC.TL_upload_webFile) response;
if (!requestInfos.contains(requestInfo)) {
return;
}
if (response != null) {
if (currentType == ConnectionsManager.FileTypeAudio) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_AUDIOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypeVideo) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_VIDEOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypePhoto) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_PHOTOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypeFile) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_FILES, response.getObjectSize() + 4);
if (error != null) {
if (request instanceof TLRPC.TL_upload_getCdnFile) {
if (error.text.equals("FILE_TOKEN_INVALID")) {
isCdn = false;
clearOperaion(requestInfo);
startDownloadRequest();
return;
}
}
}
processRequestResult(requestInfo, error);
if (response instanceof TLRPC.TL_upload_fileCdnRedirect) {
TLRPC.TL_upload_fileCdnRedirect res = (TLRPC.TL_upload_fileCdnRedirect) response;
if (res.encryption_iv == null || res.encryption_key == null || res.encryption_iv.length != 16 || res.encryption_key.length != 32) {
error = new TLRPC.TL_error();
error.text = "bad redirect response";
error.code = 400;
processRequestResult(requestInfo, error);
} else {
isCdn = true;
cdnDatacenterId = res.dc_id;
cdnIv = res.encryption_iv;
cdnKey = res.encryption_key;
cdnToken = res.file_token;
clearOperaion(requestInfo);
startDownloadRequest();
}
} else if (response instanceof TLRPC.TL_upload_cdnFileReuploadNeeded) {
if (!reuploadingCdn) {
clearOperaion(requestInfo);
reuploadingCdn = true;
TLRPC.TL_upload_cdnFileReuploadNeeded res = (TLRPC.TL_upload_cdnFileReuploadNeeded) response;
TLRPC.TL_upload_reuploadCdnFile req = new TLRPC.TL_upload_reuploadCdnFile();
req.file_token = cdnToken;
req.request_token = res.request_token;
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
reuploadingCdn = false;
if (error == null) {
startDownloadRequest();
} else {
if (error.text.equals("FILE_TOKEN_INVALID") || error.text.equals("REQUEST_TOKEN_INVALID")) {
isCdn = false;
clearOperaion(requestInfo);
startDownloadRequest();
} else {
onFail(false, 0);
}
}
}
}, null, null, 0, datacenter_id, ConnectionsManager.ConnectionTypeGeneric, true);
}
} else {
if (response instanceof TLRPC.TL_upload_file) {
requestInfo.response = (TLRPC.TL_upload_file) response;
} else if (response instanceof TLRPC.TL_upload_webFile) {
requestInfo.responseWeb = (TLRPC.TL_upload_webFile) response;
} else {
requestInfo.responseCdn = (TLRPC.TL_upload_cdnFile) response;
}
if (response != null) {
if (currentType == ConnectionsManager.FileTypeAudio) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_AUDIOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypeVideo) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_VIDEOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypePhoto) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_PHOTOS, response.getObjectSize() + 4);
} else if (currentType == ConnectionsManager.FileTypeFile) {
StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_FILES, response.getObjectSize() + 4);
}
}
processRequestResult(requestInfo, error);
}
}
}, null, (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors, datacenter_id, flags, isLast);
}, null, null, flags, isCdn ? cdnDatacenterId : datacenter_id, connectionType, isLast);
requestsCount++;
}
}

View File

@ -16,6 +16,7 @@ import org.telegram.tgnet.NativeByteBuffer;
import org.telegram.tgnet.RequestDelegate;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.telegram.tgnet.WriteToSocketDelegate;
import java.io.File;
import java.io.FileInputStream;
@ -34,8 +35,12 @@ public class FileUploadOperation {
}
private boolean isLastPart = false;
private final int maxRequestsCount = 8;
private int uploadChunkSize = 1024 * 128;
private static final int minUploadChunkSize = 64;
private static final int initialRequestsCount = 8;
private static final int maxUploadingKBytes = 1024 * 2;
private int maxRequestsCount;
private int currentUploadingBytes;
private int uploadChunkSize = 64 * 1024;
private ArrayList<byte[]> freeRequestIvs;
private int requestNum;
private String uploadingFilePath;
@ -98,7 +103,7 @@ public class FileUploadOperation {
@Override
public void run() {
preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE);
for (int a = 0; a < maxRequestsCount; a++) {
for (int a = 0; a < initialRequestsCount; a++) {
startUploadRequest();
}
}
@ -110,9 +115,14 @@ public class FileUploadOperation {
return;
}
state = 2;
for (Integer num : requestTokens.values()) {
ConnectionsManager.getInstance().cancelRequest(num, true);
}
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
for (Integer num : requestTokens.values()) {
ConnectionsManager.getInstance().cancelRequest(num, true);
}
}
});
delegate.didFailedUploadingFile(this);
cleanup();
}
@ -183,12 +193,6 @@ public class FileUploadOperation {
try {
started = true;
if (stream == null) {
if (isEncrypted) {
freeRequestIvs = new ArrayList<>(maxRequestsCount);
for (int a = 0; a < maxRequestsCount; a++) {
freeRequestIvs.add(new byte[32]);
}
}
File cacheFile = new File(uploadingFilePath);
stream = new FileInputStream(cacheFile);
if (estimatedSize != 0) {
@ -206,7 +210,7 @@ public class FileUploadOperation {
}
}
uploadChunkSize = (int) Math.max(128, (totalFileSize + 1024 * 3000 - 1) / (1024 * 3000));
uploadChunkSize = (int) Math.max(minUploadChunkSize, (totalFileSize + 1024 * 3000 - 1) / (1024 * 3000));
if (1024 % uploadChunkSize != 0) {
int chunkSize = 64;
while (uploadChunkSize > chunkSize) {
@ -214,6 +218,14 @@ public class FileUploadOperation {
}
uploadChunkSize = chunkSize;
}
maxRequestsCount = maxUploadingKBytes / uploadChunkSize;
if (isEncrypted) {
freeRequestIvs = new ArrayList<>(maxRequestsCount);
for (int a = 0; a < maxRequestsCount; a++) {
freeRequestIvs.add(new byte[32]);
}
}
uploadChunkSize *= 1024;
totalPartsCount = (int) (totalFileSize + uploadChunkSize - 1) / uploadChunkSize;
@ -401,6 +413,9 @@ public class FileUploadOperation {
final long currentRequestBytesOffset = readBytesCount;
final int currentRequestPartNum = currentPartNum++;
final int requestSize = finalRequest.getObjectSize() + 4;
int connectionType = ConnectionsManager.ConnectionTypeUpload | ((requestNumFinal % 4) << 16);
int requestToken = ConnectionsManager.getInstance().sendRequest(finalRequest, new RequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
@ -451,6 +466,15 @@ public class FileUploadOperation {
delegate.didFinishUploadingFile(FileUploadOperation.this, null, result, key, iv);
cleanup();
}
if (currentType == ConnectionsManager.FileTypeAudio) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_AUDIOS, 1);
} else if (currentType == ConnectionsManager.FileTypeVideo) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_VIDEOS, 1);
} else if (currentType == ConnectionsManager.FileTypePhoto) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_PHOTOS, 1);
} else if (currentType == ConnectionsManager.FileTypeFile) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_FILES, 1);
}
} else if (currentUploadRequetsCount < maxRequestsCount) {
if (estimatedSize == 0) {
if (saveInfoTimes >= 4) {
@ -486,25 +510,27 @@ public class FileUploadOperation {
}
saveInfoTimes++;
}
startUploadRequest();
}
if (currentType == ConnectionsManager.FileTypeAudio) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_AUDIOS, 1);
} else if (currentType == ConnectionsManager.FileTypeVideo) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_VIDEOS, 1);
} else if (currentType == ConnectionsManager.FileTypePhoto) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_PHOTOS, 1);
} else if (currentType == ConnectionsManager.FileTypeFile) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_FILES, 1);
}
} else {
delegate.didFailedUploadingFile(FileUploadOperation.this);
cleanup();
}
}
}, 0, currentUploadRequetsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeUpload : ConnectionsManager.ConnectionTypeUpload2);
}, null, new WriteToSocketDelegate() {
@Override
public void run() {
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
if (currentUploadRequetsCount < maxRequestsCount) {
startUploadRequest();
}
}
});
}
}, 0, ConnectionsManager.DEFAULT_DATACENTER_ID, connectionType, true);
requestTokens.put(requestNumFinal, requestToken);
}
}

View File

@ -11,12 +11,16 @@ package org.telegram.messenger;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import com.google.android.gms.gcm.GcmListenerService;
import org.json.JSONObject;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import static android.support.v4.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED;
public class GcmPushListenerService extends GcmListenerService {
@ -44,13 +48,52 @@ public class GcmPushListenerService extends GcmListenerService {
String ip = parts[0];
int port = Integer.parseInt(parts[1]);
ConnectionsManager.getInstance().applyDatacenterAddress(dc, ip, port);
} else {
if (ApplicationLoader.mainInterfacePaused) {
int value = bundle.getInt("badge", -1);
if (value == -1) {
} else if ("MESSAGE_ANNOUNCEMENT".equals(key)) {
Object obj = bundle.get("google.sent_time");
long time;
try {
if (obj instanceof String) {
time = Utilities.parseLong((String) obj);
} else if (obj instanceof Long) {
time = (Long) obj;
} else {
time = System.currentTimeMillis();
}
} catch (Exception ignore) {
time = System.currentTimeMillis();
}
TLRPC.TL_updateServiceNotification update = new TLRPC.TL_updateServiceNotification();
update.popup = false;
update.flags = 2;
update.inbox_date = (int) (time / 1000);
update.message = bundle.getString("message");
update.type = "announcement";
update.media = new TLRPC.TL_messageMediaEmpty();
final TLRPC.TL_updates updates = new TLRPC.TL_updates();
updates.updates.add(update);
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().processUpdates(updates, false);
}
});
} else if (Build.VERSION.SDK_INT >= 24 && ApplicationLoader.mainInterfacePaused && UserConfig.isClientActivated()) {
Object value = bundle.get("badge");
if (value == null) {
Object obj = bundle.get("google.sent_time");
long time;
if (obj instanceof String) {
time = Utilities.parseLong((String) obj);
} else if (obj instanceof Long) {
time = (Long) obj;
} else {
time = -1;
}
if (time == -1 || UserConfig.lastAppPauseTime < time) {
ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo == null || !netInfo.isConnected()) {
if (connectivityManager.getRestrictBackgroundStatus() == RESTRICT_BACKGROUND_STATUS_ENABLED && netInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
NotificationsController.getInstance().showSingleBackgroundNotification();
}
}
@ -59,7 +102,6 @@ public class GcmPushListenerService extends GcmListenerService {
} catch (Exception e) {
FileLog.e(e);
}
ConnectionsManager.onInternalPushReceived();
ConnectionsManager.getInstance().resumeNetworkMaybe();
}

View File

@ -136,8 +136,8 @@ public class ImageLoader {
try {
URL downloadUrl = new URL(url);
httpConnection = downloadUrl.openConnection();
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
httpConnection.addRequestProperty("Referer", "google.com");
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1");
//httpConnection.addRequestProperty("Referer", "google.com");
httpConnection.setConnectTimeout(5000);
httpConnection.setReadTimeout(5000);
if (httpConnection instanceof HttpURLConnection) {
@ -150,8 +150,8 @@ public class ImageLoader {
downloadUrl = new URL(newUrl);
httpConnection = downloadUrl.openConnection();
httpConnection.setRequestProperty("Cookie", cookies);
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
httpConnection.addRequestProperty("Referer", "google.com");
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1");
//httpConnection.addRequestProperty("Referer", "google.com");
}
}
httpConnection.connect();
@ -311,8 +311,8 @@ public class ImageLoader {
try {
URL downloadUrl = new URL(cacheImage.httpUrl);
httpConnection = downloadUrl.openConnection();
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
httpConnection.addRequestProperty("Referer", "google.com");
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1");
//httpConnection.addRequestProperty("Referer", "google.com");
httpConnection.setConnectTimeout(5000);
httpConnection.setReadTimeout(5000);
if (httpConnection instanceof HttpURLConnection) {
@ -1714,15 +1714,16 @@ public class ImageLoader {
}
if (thumb != 2) {
boolean isEncrypted = imageLocation instanceof TLRPC.TL_documentEncrypted || imageLocation instanceof TLRPC.TL_fileEncryptedLocation;
CacheImage img = new CacheImage();
if (httpLocation != null && !httpLocation.startsWith("vthumb") && !httpLocation.startsWith("thumb") && (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) ||
imageLocation instanceof TLRPC.TL_webDocument && ((TLRPC.TL_webDocument) imageLocation).mime_type.equals("image/gif") ||
imageLocation instanceof TLRPC.Document && MessageObject.isGifDocument((TLRPC.Document) imageLocation)) {
imageLocation instanceof TLRPC.Document && (MessageObject.isGifDocument((TLRPC.Document) imageLocation) || MessageObject.isRoundVideoDocument((TLRPC.Document) imageLocation))) {
img.animatedFile = true;
}
if (cacheFile == null) {
if (cacheOnly || size == 0 || httpLocation != null) {
if (cacheOnly || size == 0 || httpLocation != null || isEncrypted) {
cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url);
} else if (imageLocation instanceof TLRPC.Document) {
if (MessageObject.isVideoDocument((TLRPC.Document) imageLocation)) {
@ -1867,7 +1868,7 @@ public class ImageLoader {
if (thumbKey != null) {
thumbUrl = thumbKey + "." + ext;
}
saveImageToCache = !MessageObject.isGifDocument(document);
saveImageToCache = !MessageObject.isGifDocument(document) && !MessageObject.isRoundVideoDocument((TLRPC.Document) imageLocation);
}
if (imageLocation == thumbLocation) {
imageLocation = null;
@ -2099,6 +2100,13 @@ public class ImageLoader {
}
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = (int) scaleFactor;
if (bmOptions.inSampleSize % 2 != 0) {
int sample = 1;
while (sample * 2 < bmOptions.inSampleSize) {
sample *= 2;
}
bmOptions.inSampleSize = sample;
}
bmOptions.inPurgeable = Build.VERSION.SDK_INT < 21;
String exifPath = null;

View File

@ -68,6 +68,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
private Drawable currentThumb;
private Drawable staticThumb;
private boolean allowStartAnimation = true;
private boolean allowDecodeSingleFrame;
private boolean needsQualityThumb;
private boolean shouldGenerateQualityThumb;
@ -378,6 +379,9 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
public boolean onAttachedToWindow() {
NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
if (needsQualityThumb) {
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated);
}
if (setImageBackup != null && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) {
setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.size, setImageBackup.ext, setImageBackup.cacheOnly);
return true;
@ -676,6 +680,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
return false;
}
public float getCurrentAlpha() {
return currentAlpha;
}
public Bitmap getBitmap() {
if (currentImage instanceof AnimatedFileDrawable) {
return ((AnimatedFileDrawable) currentImage).getAnimatedBitmap();
@ -698,6 +706,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicWidth() : staticThumb.getIntrinsicHeight();
}
Bitmap bitmap = getBitmap();
if (bitmap == null) {
if (staticThumb != null) {
return staticThumb.getIntrinsicWidth();
}
return 1;
}
return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getWidth() : bitmap.getHeight();
}
@ -708,6 +722,12 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicHeight() : staticThumb.getIntrinsicWidth();
}
Bitmap bitmap = getBitmap();
if (bitmap == null) {
if (staticThumb != null) {
return staticThumb.getIntrinsicHeight();
}
return 1;
}
return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getHeight() : bitmap.getWidth();
}
@ -768,6 +788,14 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
imageH = height;
}
public float getCenterX() {
return imageX + imageW / 2.0f;
}
public float getCenterY() {
return imageY + imageH / 2.0f;
}
public int getImageX() {
return imageX;
}
@ -889,6 +917,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
allowStartAnimation = value;
}
public void setAllowDecodeSingleFrame(boolean value) {
allowDecodeSingleFrame = value;
}
public boolean isAllowStartAnimation() {
return allowStartAnimation;
}
@ -966,6 +998,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
fileDrawable.setParentView(parentView);
if (allowStartAnimation) {
fileDrawable.start();
} else {
fileDrawable.setAllowDecodeSingleFrame(allowDecodeSingleFrame);
}
}
@ -984,7 +1018,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
currentThumb = bitmap;
if (roundRadius != 0 && currentImage == null && bitmap instanceof BitmapDrawable) {
if (roundRadius != 0 && bitmap instanceof BitmapDrawable) {
if (bitmap instanceof AnimatedFileDrawable) {
((AnimatedFileDrawable) bitmap).setRoundRadius(roundRadius);
} else {
@ -996,9 +1030,13 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
}
if (!memCache && crossfadeAlpha != 2) {
currentAlpha = 0.0f;
lastUpdateAlphaTime = System.currentTimeMillis();
crossfadeWithThumb = staticThumb != null && currentKey == null;
if (parentMessageObject != null && parentMessageObject.isRoundVideo() && parentMessageObject.isSending()) {
currentAlpha = 1.0f;
} else {
currentAlpha = 0.0f;
lastUpdateAlphaTime = System.currentTimeMillis();
crossfadeWithThumb = staticThumb != null && currentKey == null;
}
} else {
currentAlpha = 1.0f;
}

View File

@ -0,0 +1,26 @@
/*
* This is the source code of Telegram for Android v. 3.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2017.
*/
package org.telegram.messenger;
public class Intro {
public static native void on_draw_frame();
public static native void set_scroll_offset(float a_offset);
public static native void set_page(int page);
public static native void set_date(float a);
public static native void set_date0(float a);
public static native void set_pages_textures(int a1, int a2, int a3, int a4, int a5, int a6);
public static native void set_ic_textures(int a_ic_bubble_dot, int a_ic_bubble, int a_ic_cam_lens, int a_ic_cam, int a_ic_pencil, int a_ic_pin, int a_ic_smile_eye, int a_ic_smile, int a_ic_videocam);
public static native void set_telegram_textures(int a_telegram_sphere, int a_telegram_plane);
public static native void set_fast_textures(int a_fast_body, int a_fast_spiral, int a_fast_arrow, int a_fast_arrow_shadow);
public static native void set_free_textures(int a_knot_up, int a_knot_down);
public static native void set_powerful_textures(int a_powerful_mask, int a_powerful_star, int a_powerful_infinity, int a_powerful_infinity_white);
public static native void set_private_textures(int a_private_door, int a_private_screw);
public static native void on_surface_created();
public static native void on_surface_changed(int a_width_px, int a_height_px, float a_scale_factor, int a1, int a2, int a3, int a4, int a5);
}

View File

@ -15,21 +15,24 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Xml;
import org.telegram.messenger.time.FastDateFormat;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.RequestDelegate;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
import org.xmlpull.v1.XmlPullParser;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
@ -55,6 +58,8 @@ public class LocaleController {
public FastDateFormat formatterMonthYear;
public FastDateFormat formatterYearMax;
public FastDateFormat formatterStats;
public FastDateFormat formatterBannedUntil;
public FastDateFormat formatterBannedUntilThisYear;
public FastDateFormat chatDate;
public FastDateFormat chatFullDate;
@ -64,10 +69,10 @@ public class LocaleController {
private Locale systemDefaultLocale;
private PluralRules currentPluralRules;
private LocaleInfo currentLocaleInfo;
private LocaleInfo defaultLocalInfo;
private HashMap<String, String> localeValues = new HashMap<>();
private String languageOverride;
private boolean changingConfiguration = false;
private boolean reloadLastFile;
private HashMap<String, String> currencyValues;
private HashMap<String, String> translitChars;
@ -87,13 +92,16 @@ public class LocaleController {
}
public static class LocaleInfo {
public String name;
public String nameEnglish;
public String shortName;
public String pathToFile;
public int version;
public boolean builtIn;
public String getSaveString() {
return name + "|" + nameEnglish + "|" + shortName + "|" + pathToFile;
return name + "|" + nameEnglish + "|" + shortName + "|" + pathToFile + "|" + version;
}
public static LocaleInfo createWithString(String string) {
@ -101,19 +109,51 @@ public class LocaleController {
return null;
}
String[] args = string.split("\\|");
if (args.length != 4) {
return null;
LocaleInfo localeInfo = null;
if (args.length >= 4) {
localeInfo = new LocaleInfo();
localeInfo.name = args[0];
localeInfo.nameEnglish = args[1];
localeInfo.shortName = args[2];
localeInfo.pathToFile = args[3];
if (args.length >= 5) {
localeInfo.version = Utilities.parseInt(args[4]);
}
}
LocaleInfo localeInfo = new LocaleInfo();
localeInfo.name = args[0];
localeInfo.nameEnglish = args[1];
localeInfo.shortName = args[2];
localeInfo.pathToFile = args[3];
return localeInfo;
}
public File getPathToFile() {
if (isRemote()) {
return new File(ApplicationLoader.getFilesDirFixed(), "remote_" + shortName + ".xml");
}
return !TextUtils.isEmpty(pathToFile) ? new File(pathToFile) : null;
}
public String getKey() {
if (pathToFile != null && !"remote".equals(pathToFile)) {
return "local_" + shortName;
}
return shortName;
}
public boolean isRemote() {
return "remote".equals(pathToFile);
}
public boolean isLocal() {
return !TextUtils.isEmpty(pathToFile) && !isRemote();
}
public boolean isBuiltIn() {
return builtIn;
}
}
public ArrayList<LocaleInfo> sortedLanguages = new ArrayList<>();
private boolean loadingRemoteLanguages;
public ArrayList<LocaleInfo> languages = new ArrayList<>();
public ArrayList<LocaleInfo> remoteLanguages = new ArrayList<>();
public HashMap<String, LocaleInfo> languagesDict = new HashMap<>();
private ArrayList<LocaleInfo> otherLanguages = new ArrayList<>();
@ -162,7 +202,8 @@ public class LocaleController {
localeInfo.nameEnglish = "English";
localeInfo.shortName = "en";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
@ -170,14 +211,16 @@ public class LocaleController {
localeInfo.nameEnglish = "Italian";
localeInfo.shortName = "it";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
localeInfo.name = "Español";
localeInfo.nameEnglish = "Spanish";
localeInfo.shortName = "es";
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
@ -185,7 +228,8 @@ public class LocaleController {
localeInfo.nameEnglish = "German";
localeInfo.shortName = "de";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
@ -193,7 +237,8 @@ public class LocaleController {
localeInfo.nameEnglish = "Dutch";
localeInfo.shortName = "nl";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
@ -201,15 +246,17 @@ public class LocaleController {
localeInfo.nameEnglish = "Arabic";
localeInfo.shortName = "ar";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
localeInfo.name = "Português (Brasil)";
localeInfo.nameEnglish = "Portuguese (Brazil)";
localeInfo.shortName = "pt_BR";
localeInfo.shortName = "pt_br";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
localeInfo = new LocaleInfo();
@ -217,29 +264,32 @@ public class LocaleController {
localeInfo.nameEnglish = "Korean";
localeInfo.shortName = "ko";
localeInfo.pathToFile = null;
sortedLanguages.add(localeInfo);
localeInfo.builtIn = true;
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
loadOtherLanguages();
for (LocaleInfo locale : otherLanguages) {
sortedLanguages.add(locale);
languagesDict.put(locale.shortName, locale);
if (remoteLanguages.isEmpty()) {
loadRemoteLanguages();
}
Collections.sort(sortedLanguages, new Comparator<LocaleInfo>() {
@Override
public int compare(LocaleController.LocaleInfo o, LocaleController.LocaleInfo o2) {
return o.name.compareTo(o2.name);
}
});
for (int a = 0; a < otherLanguages.size(); a++) {
LocaleInfo locale = otherLanguages.get(a);
languages.add(locale);
languagesDict.put(locale.getKey(), locale);
}
defaultLocalInfo = localeInfo = new LocaleController.LocaleInfo();
localeInfo.name = "System default";
localeInfo.nameEnglish = "System default";
localeInfo.shortName = null;
localeInfo.pathToFile = null;
sortedLanguages.add(0, localeInfo);
for (int a = 0; a < remoteLanguages.size(); a++) {
LocaleInfo locale = remoteLanguages.get(a);
LocaleInfo existingLocale = getLanguageFromDict(locale.getKey());
if (existingLocale != null) {
existingLocale.pathToFile = locale.pathToFile;
existingLocale.version = locale.version;
} else {
languages.add(locale);
languagesDict.put(locale.getKey(), locale);
}
}
systemDefaultLocale = Locale.getDefault();
is24HourFormat = DateFormat.is24HourFormat(ApplicationLoader.applicationContext);
@ -250,21 +300,22 @@ public class LocaleController {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
String lang = preferences.getString("language", null);
if (lang != null) {
currentInfo = languagesDict.get(lang);
currentInfo = getLanguageFromDict(lang);
if (currentInfo != null) {
override = true;
}
}
if (currentInfo == null && systemDefaultLocale.getLanguage() != null) {
currentInfo = languagesDict.get(systemDefaultLocale.getLanguage());
currentInfo = getLanguageFromDict(systemDefaultLocale.getLanguage());
}
if (currentInfo == null) {
currentInfo = languagesDict.get(getLocaleString(systemDefaultLocale));
}
if (currentInfo == null) {
currentInfo = languagesDict.get("en");
currentInfo = getLanguageFromDict(getLocaleString(systemDefaultLocale));
if (currentInfo == null) {
currentInfo = getLanguageFromDict("en");
}
}
applyLanguage(currentInfo, override);
} catch (Exception e) {
FileLog.e(e);
@ -278,6 +329,13 @@ public class LocaleController {
}
}
private LocaleInfo getLanguageFromDict(String key) {
if (key == null) {
return null;
}
return languagesDict.get(key.toLowerCase().replace("-", "_"));
}
private void addRules(String[] languages, PluralRules rules) {
for (String language : languages) {
allRules.put(language, rules);
@ -305,6 +363,14 @@ public class LocaleController {
return systemDefaultLocale;
}
public boolean isCurrentLocalLocale() {
return currentLocaleInfo.isLocal();
}
public void reloadCurrentRemoteLocale() {
applyRemoteLanguage(currentLocaleInfo, null, true);
}
private String getLocaleString(Locale locale) {
if (locale == null) {
return "en";
@ -328,7 +394,7 @@ public class LocaleController {
return result.toString();
}
public static String getLocaleStringIso639() {
public static String getSystemLocaleStringIso639() {
Locale locale = getInstance().getSystemDefaultLocale();
if (locale == null) {
return "en";
@ -352,6 +418,30 @@ public class LocaleController {
return result.toString();
}
public static String getLocaleStringIso639() {
Locale locale = getInstance().currentLocale;
if (locale == null) {
return "en";
}
String languageCode = locale.getLanguage();
String countryCode = locale.getCountry();
String variantCode = locale.getVariant();
if (languageCode.length() == 0 && countryCode.length() == 0) {
return "en";
}
StringBuilder result = new StringBuilder(11);
result.append(languageCode);
if (countryCode.length() > 0 || variantCode.length() > 0) {
result.append('-');
}
result.append(countryCode);
if (variantCode.length() > 0) {
result.append('_');
}
result.append(variantCode);
return result.toString();
}
public boolean applyLanguageFile(File file) {
try {
HashMap<String, String> stringMap = getLocaleFileStrings(file);
@ -379,7 +469,7 @@ public class LocaleController {
return false;
}
LocaleInfo localeInfo = languagesDict.get(languageCode);
LocaleInfo localeInfo = getLanguageFromDict(languageCode);
if (localeInfo == null) {
localeInfo = new LocaleInfo();
localeInfo.name = languageName;
@ -387,21 +477,10 @@ public class LocaleController {
localeInfo.shortName = languageCode;
localeInfo.pathToFile = finalFile.getAbsolutePath();
sortedLanguages.add(localeInfo);
languages.add(localeInfo);
languagesDict.put(localeInfo.shortName, localeInfo);
otherLanguages.add(localeInfo);
Collections.sort(sortedLanguages, new Comparator<LocaleInfo>() {
@Override
public int compare(LocaleController.LocaleInfo o, LocaleController.LocaleInfo o2) {
if (o.shortName == null) {
return -1;
} else if (o2.shortName == null) {
return 1;
}
return o.name.compareTo(o2.name);
}
});
saveOtherLanguages();
}
localeValues = stringMap;
@ -417,30 +496,53 @@ public class LocaleController {
private void saveOtherLanguages() {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("langconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
String locales = "";
for (LocaleInfo localeInfo : otherLanguages) {
StringBuilder stringBuilder = new StringBuilder();
for (int a = 0; a < otherLanguages.size(); a++) {
LocaleInfo localeInfo = otherLanguages.get(a);
String loc = localeInfo.getSaveString();
if (loc != null) {
if (locales.length() != 0) {
locales += "&";
if (stringBuilder.length() != 0) {
stringBuilder.append("&");
}
locales += loc;
stringBuilder.append(loc);
}
}
editor.putString("locales", locales);
editor.putString("locales", stringBuilder.toString());
stringBuilder.setLength(0);
for (int a = 0; a < remoteLanguages.size(); a++) {
LocaleInfo localeInfo = remoteLanguages.get(a);
String loc = localeInfo.getSaveString();
if (loc != null) {
if (stringBuilder.length() != 0) {
stringBuilder.append("&");
}
stringBuilder.append(loc);
}
}
editor.putString("remote", stringBuilder.toString());
editor.commit();
}
public boolean deleteLanguage(LocaleInfo localeInfo) {
if (localeInfo.pathToFile == null) {
if (localeInfo.pathToFile == null || localeInfo.isRemote()) {
return false;
}
if (currentLocaleInfo == localeInfo) {
applyLanguage(defaultLocalInfo, true);
LocaleInfo info = null;
if (systemDefaultLocale.getLanguage() != null) {
info = getLanguageFromDict(systemDefaultLocale.getLanguage());
}
if (info == null) {
info = getLanguageFromDict(getLocaleString(systemDefaultLocale));
}
if (info == null) {
info = getLanguageFromDict("en");
}
applyLanguage(info, true);
}
otherLanguages.remove(localeInfo);
sortedLanguages.remove(localeInfo);
languages.remove(localeInfo);
languagesDict.remove(localeInfo.shortName);
File file = new File(localeInfo.pathToFile);
file.delete();
@ -451,21 +553,39 @@ public class LocaleController {
private void loadOtherLanguages() {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("langconfig", Activity.MODE_PRIVATE);
String locales = preferences.getString("locales", null);
if (locales == null || locales.length() == 0) {
return;
if (!TextUtils.isEmpty(locales)) {
String[] localesArr = locales.split("&");
for (String locale : localesArr) {
LocaleInfo localeInfo = LocaleInfo.createWithString(locale);
if (localeInfo != null) {
otherLanguages.add(localeInfo);
}
}
}
String[] localesArr = locales.split("&");
for (String locale : localesArr) {
LocaleInfo localeInfo = LocaleInfo.createWithString(locale);
if (localeInfo != null) {
otherLanguages.add(localeInfo);
locales = preferences.getString("remote", null);
if (!TextUtils.isEmpty(locales)) {
String[] localesArr = locales.split("&");
for (String locale : localesArr) {
LocaleInfo localeInfo = LocaleInfo.createWithString(locale);
localeInfo.shortName = localeInfo.shortName.replace("-", "_");
if (localeInfo != null) {
remoteLanguages.add(localeInfo);
}
}
}
}
private HashMap<String, String> getLocaleFileStrings(File file) {
return getLocaleFileStrings(file, false);
}
private HashMap<String, String> getLocaleFileStrings(File file, boolean preserveEscapes) {
FileInputStream stream = null;
reloadLastFile = false;
try {
if (!file.exists()) {
return new HashMap<>();
}
HashMap<String, String> stringMap = new HashMap<>();
XmlPullParser parser = Xml.newPullParser();
stream = new FileInputStream(file);
@ -475,19 +595,28 @@ public class LocaleController {
String value = null;
String attrName = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_TAG) {
if (eventType == XmlPullParser.START_TAG) {
name = parser.getName();
int c = parser.getAttributeCount();
if (c > 0) {
attrName = parser.getAttributeValue(0);
}
} else if(eventType == XmlPullParser.TEXT) {
} else if (eventType == XmlPullParser.TEXT) {
if (attrName != null) {
value = parser.getText();
if (value != null) {
value = value.trim();
value = value.replace("\\n", "\n");
value = value.replace("\\", "");
if (preserveEscapes) {
value = value.replace("<", "&lt;").replace(">", "&gt;").replace("'", "\\'").replace("& ", "&amp; ");
} else {
value = value.replace("\\n", "\n");
value = value.replace("\\", "");
String old = value;
value = value.replace("&lt;", "<");
if (!reloadLastFile && !value.equals(old)) {
reloadLastFile = true;
}
}
}
}
} else if (eventType == XmlPullParser.END_TAG) {
@ -506,6 +635,7 @@ public class LocaleController {
return stringMap;
} catch (Exception e) {
FileLog.e(e);
reloadLastFile = true;
} finally {
try {
if (stream != null) {
@ -526,64 +656,48 @@ public class LocaleController {
if (localeInfo == null) {
return;
}
File pathToFile = localeInfo.getPathToFile();
String shortName = localeInfo.shortName;
ConnectionsManager.getInstance().setLangCode(shortName.replace("_", "-"));
if (localeInfo.isRemote() && !pathToFile.exists()) {
applyRemoteLanguage(localeInfo, null, false);
}
try {
Locale newLocale;
if (localeInfo.shortName != null) {
String[] args = localeInfo.shortName.split("_");
if (args.length == 1) {
newLocale = new Locale(localeInfo.shortName);
} else {
newLocale = new Locale(args[0], args[1]);
}
if (newLocale != null) {
if (override) {
languageOverride = localeInfo.shortName;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("language", localeInfo.shortName);
editor.commit();
}
}
String[] args = localeInfo.shortName.split("_");
if (args.length == 1) {
newLocale = new Locale(localeInfo.shortName);
} else {
newLocale = systemDefaultLocale;
languageOverride = null;
newLocale = new Locale(args[0], args[1]);
}
if (override) {
languageOverride = localeInfo.shortName;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.remove("language");
editor.putString("language", localeInfo.getKey());
editor.commit();
if (newLocale != null) {
LocaleInfo info = null;
if (newLocale.getLanguage() != null) {
info = languagesDict.get(newLocale.getLanguage());
}
if (info == null) {
info = languagesDict.get(getLocaleString(newLocale));
}
if (info == null) {
newLocale = Locale.US;
}
}
}
if (newLocale != null) {
if (localeInfo.pathToFile == null) {
localeValues.clear();
} else if (!fromFile) {
localeValues = getLocaleFileStrings(new File(localeInfo.pathToFile));
}
currentLocale = newLocale;
currentLocaleInfo = localeInfo;
currentPluralRules = allRules.get(currentLocale.getLanguage());
if (currentPluralRules == null) {
currentPluralRules = allRules.get("en");
}
changingConfiguration = true;
Locale.setDefault(currentLocale);
android.content.res.Configuration config = new android.content.res.Configuration();
config.locale = currentLocale;
ApplicationLoader.applicationContext.getResources().updateConfiguration(config, ApplicationLoader.applicationContext.getResources().getDisplayMetrics());
changingConfiguration = false;
if (pathToFile == null) {
localeValues.clear();
} else if (!fromFile) {
localeValues = getLocaleFileStrings(pathToFile);
}
currentLocale = newLocale;
currentLocaleInfo = localeInfo;
currentPluralRules = allRules.get(currentLocale.getLanguage());
if (currentPluralRules == null) {
currentPluralRules = allRules.get("en");
}
changingConfiguration = true;
Locale.setDefault(currentLocale);
android.content.res.Configuration config = new android.content.res.Configuration();
config.locale = currentLocale;
ApplicationLoader.applicationContext.getResources().updateConfiguration(config, ApplicationLoader.applicationContext.getResources().getDisplayMetrics());
changingConfiguration = false;
if (reloadLastFile) {
reloadCurrentRemoteLocale();
reloadLastFile = false;
}
} catch (Exception e) {
FileLog.e(e);
@ -592,6 +706,10 @@ public class LocaleController {
recreateFormatters();
}
public LocaleInfo getCurrentLocaleInfo() {
return currentLocaleInfo;
}
public static String getCurrentLanguageName() {
return getString("LanguageName", R.string.LanguageName);
}
@ -728,7 +846,7 @@ public class LocaleController {
return (discount ? "-" : "") + String.format(type + customFormat, doubleAmount);
}
public String formatCurrencyDecimalString(long amount, String type) {
public String formatCurrencyDecimalString(long amount, String type, boolean inludeType) {
type = type.toUpperCase();
String customFormat;
double doubleAmount;
@ -784,7 +902,7 @@ public class LocaleController {
doubleAmount = amount / 100.0;
break;
}
return String.format(type + customFormat, doubleAmount);
return String.format(inludeType ? type : "" + customFormat, doubleAmount).trim();
}
public static String formatStringSimple(String string, Object... args) {
@ -1004,29 +1122,48 @@ public class LocaleController {
formatterMonthYear = createFormatter(locale, getStringInternal("formatterMonthYear", R.string.formatterMonthYear), "MMMM yyyy");
formatterDay = createFormatter(lang.toLowerCase().equals("ar") || lang.toLowerCase().equals("ko") ? locale : Locale.US, is24HourFormat ? getStringInternal("formatterDay24H", R.string.formatterDay24H) : getStringInternal("formatterDay12H", R.string.formatterDay12H), is24HourFormat ? "HH:mm" : "h:mm a");
formatterStats = createFormatter(locale, is24HourFormat ? getStringInternal("formatterStats24H", R.string.formatterStats24H) : getStringInternal("formatterStats12H", R.string.formatterStats12H), is24HourFormat ? "MMM dd yyyy, HH:mm" : "MMM dd yyyy, h:mm a");
formatterBannedUntil = createFormatter(locale, is24HourFormat ? getStringInternal("formatterBannedUntil24H", R.string.formatterBannedUntil24H) : getStringInternal("formatterBannedUntil12H", R.string.formatterBannedUntil12H), is24HourFormat ? "MMM dd yyyy, HH:mm" : "MMM dd yyyy, h:mm a");
formatterBannedUntilThisYear = createFormatter(locale, is24HourFormat ? getStringInternal("formatterBannedUntilThisYear24H", R.string.formatterBannedUntilThisYear24H) : getStringInternal("formatterBannedUntilThisYear12H", R.string.formatterBannedUntilThisYear12H), is24HourFormat ? "MMM dd, HH:mm" : "MMM dd, h:mm a");
}
public static boolean isRTLCharacter(char ch) {
return Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE;
}
public static String formatDateForBan(long date) {
try {
date *= 1000;
Calendar rightNow = Calendar.getInstance();
int year = rightNow.get(Calendar.YEAR);
rightNow.setTimeInMillis(date);
int dateYear = rightNow.get(Calendar.YEAR);
if (year == dateYear) {
return getInstance().formatterBannedUntilThisYear.format(new Date(date));
} else {
return getInstance().formatterBannedUntil.format(new Date(date));
}
} catch (Exception e) {
FileLog.e(e);
}
return "LOC_ERR";
}
public static String stringForMessageListDate(long date) {
try {
date *= 1000;
Calendar rightNow = Calendar.getInstance();
int day = rightNow.get(Calendar.DAY_OF_YEAR);
int year = rightNow.get(Calendar.YEAR);
rightNow.setTimeInMillis(date);
int dateDay = rightNow.get(Calendar.DAY_OF_YEAR);
int dateYear = rightNow.get(Calendar.YEAR);
if (Math.abs(System.currentTimeMillis() - date) >= 31536000000L) {
return getInstance().formatterYear.format(new Date(date));
} else {
int dayDiff = dateDay - day;
if(dayDiff == 0 || dayDiff == -1 && System.currentTimeMillis() - date < 60 * 60 * 8 * 1000) {
if (dayDiff == 0 || dayDiff == -1 && System.currentTimeMillis() - date < 60 * 60 * 8 * 1000) {
return getInstance().formatterDay.format(new Date(date));
} else if(dayDiff > -7 && dayDiff <= -1) {
} else if (dayDiff > -7 && dayDiff <= -1) {
return getInstance().formatterWeek.format(new Date(date));
} else {
return getInstance().formatterMonth.format(new Date(date));
@ -1105,6 +1242,208 @@ public class LocaleController {
}
}
public void saveRemoteLocaleStrings(final TLRPC.TL_langPackDifference difference) {
File finalFile = new File(ApplicationLoader.getFilesDirFixed(), "remote_" + difference.lang_code + ".xml");
try {
final HashMap<String, String> values;
if (difference.from_version == 0) {
values = new HashMap<>();
} else {
values = getLocaleFileStrings(finalFile, true);
}
for (int a = 0; a < difference.strings.size(); a++) {
TLRPC.LangPackString string = difference.strings.get(a);
if (string instanceof TLRPC.TL_langPackString) {
values.put(string.key, string.value);
} else if (string instanceof TLRPC.TL_langPackStringPluralized) {
values.put(string.key + "_zero", string.zero_value != null ? string.zero_value : "");
values.put(string.key + "_one", string.one_value != null ? string.one_value : "");
values.put(string.key + "_two", string.two_value != null ? string.two_value : "");
values.put(string.key + "_few", string.few_value != null ? string.few_value : "");
values.put(string.key + "_many", string.many_value != null ? string.many_value : "");
values.put(string.key + "_other", string.other_value != null ? string.other_value : "");
} else if (string instanceof TLRPC.TL_langPackStringDeleted) {
values.remove(string.key);
}
}
BufferedWriter writer = new BufferedWriter(new FileWriter(finalFile));
writer.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
writer.write("<resources>\n");
for (HashMap.Entry<String, String> entry : values.entrySet()) {
writer.write(String.format("<string name=\"%1$s\">%2$s</string>\n", entry.getKey(), entry.getValue()));
}
writer.write("</resources>");
writer.close();
final HashMap<String, String> valuesToSet = getLocaleFileStrings(finalFile);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
LocaleInfo localeInfo = getLanguageFromDict(difference.lang_code);
if (localeInfo != null) {
localeInfo.version = difference.version;
}
saveOtherLanguages();
try {
Locale newLocale;
String[] args = localeInfo.shortName.split("_");
if (args.length == 1) {
newLocale = new Locale(localeInfo.shortName);
} else {
newLocale = new Locale(args[0], args[1]);
}
if (newLocale != null) {
languageOverride = localeInfo.shortName;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("language", localeInfo.getKey());
editor.commit();
}
if (newLocale != null) {
localeValues = valuesToSet;
currentLocale = newLocale;
currentLocaleInfo = localeInfo;
currentPluralRules = allRules.get(currentLocale.getLanguage());
if (currentPluralRules == null) {
currentPluralRules = allRules.get("en");
}
changingConfiguration = true;
Locale.setDefault(currentLocale);
android.content.res.Configuration config = new android.content.res.Configuration();
config.locale = currentLocale;
ApplicationLoader.applicationContext.getResources().updateConfiguration(config, ApplicationLoader.applicationContext.getResources().getDisplayMetrics());
changingConfiguration = false;
}
} catch (Exception e) {
FileLog.e(e);
changingConfiguration = false;
}
recreateFormatters();
NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInterface);
}
});
} catch (Exception ignore) {
}
}
public void loadRemoteLanguages() {
if (loadingRemoteLanguages) {
return;
}
loadingRemoteLanguages = true;
TLRPC.TL_langpack_getLanguages req = new TLRPC.TL_langpack_getLanguages();
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(final TLObject response, TLRPC.TL_error error) {
if (response != null) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
loadingRemoteLanguages = false;
TLRPC.Vector res = (TLRPC.Vector) response;
HashMap<String, LocaleInfo> remoteLoaded = new HashMap<>();
remoteLanguages.clear();
for (int a = 0; a < res.objects.size(); a++) {
TLRPC.TL_langPackLanguage language = (TLRPC.TL_langPackLanguage) res.objects.get(a);
LocaleInfo localeInfo = new LocaleInfo();
localeInfo.nameEnglish = language.name;
localeInfo.name = language.native_name;
localeInfo.shortName = language.lang_code.replace('-', '_').toLowerCase();
localeInfo.pathToFile = "remote";
LocaleInfo existing = getLanguageFromDict(localeInfo.getKey());
if (existing == null) {
languages.add(localeInfo);
languagesDict.put(localeInfo.getKey(), localeInfo);
} else {
existing.nameEnglish = localeInfo.nameEnglish;
existing.name = localeInfo.name;
existing.pathToFile = localeInfo.pathToFile;
}
remoteLanguages.add(localeInfo);
remoteLoaded.put(localeInfo.getKey(), existing);
}
for (int a = 0; a < languages.size(); a++) {
LocaleInfo info = languages.get(a);
if (info.isBuiltIn() || !info.isRemote()) {
continue;
}
LocaleInfo existing = remoteLoaded.get(info.getKey());
if (existing == null) {
languages.remove(a);
languagesDict.remove(info.getKey());
a--;
if (info == currentLocaleInfo) {
if (systemDefaultLocale.getLanguage() != null) {
info = getLanguageFromDict(systemDefaultLocale.getLanguage());
}
if (info == null) {
info = getLanguageFromDict(getLocaleString(systemDefaultLocale));
}
if (info == null) {
info = getLanguageFromDict("en");
}
applyLanguage(info, true);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInterface);
}
}
}
saveOtherLanguages();
NotificationCenter.getInstance().postNotificationName(NotificationCenter.suggestedLangpack);
applyLanguage(currentLocaleInfo, true);
}
});
}
}
}, ConnectionsManager.RequestFlagWithoutLogin);
}
public void applyRemoteLanguage(LocaleInfo localeInfo, TLRPC.TL_langPackLanguage language, boolean force) {
if (localeInfo == null && language == null || localeInfo != null && !localeInfo.isRemote()) {
return;
}
if (localeInfo.version != 0 && !BuildVars.DEBUG_VERSION && !force) {
TLRPC.TL_langpack_getDifference req = new TLRPC.TL_langpack_getDifference();
req.from_version = localeInfo.version;
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(final TLObject response, TLRPC.TL_error error) {
if (response != null) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
saveRemoteLocaleStrings((TLRPC.TL_langPackDifference) response);
}
});
}
}
}, ConnectionsManager.RequestFlagWithoutLogin);
} else {
ConnectionsManager.getInstance().setLangCode(localeInfo != null ? localeInfo.shortName : language.lang_code);
TLRPC.TL_langpack_getLangPack req = new TLRPC.TL_langpack_getLangPack();
if (language == null) {
req.lang_code = localeInfo.shortName;
} else {
req.lang_code = language.lang_code;
}
req.lang_code = req.lang_code.replace("_", "-");
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(final TLObject response, TLRPC.TL_error error) {
if (response != null) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
saveRemoteLocaleStrings((TLRPC.TL_langPackDifference) response);
}
});
}
}
}, ConnectionsManager.RequestFlagWithoutLogin);
}
}
public String getTranslitString(String src) {
if (translitChars == null) {
translitChars = new HashMap<>(520);

View File

@ -9,6 +9,7 @@
package org.telegram.messenger;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
@ -37,6 +38,7 @@ import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -52,11 +54,14 @@ public class MessageObject {
public CharSequence caption;
public MessageObject replyMessageObject;
public int type = 1000;
private int isRoundVideoCached;
public long eventId;
public int contentType;
public String dateKey;
public String monthKey;
public boolean deleted;
public float audioProgress;
public float gifState;
public int audioProgressSec;
public boolean isDateObject;
public ArrayList<TLRPC.PhotoSize> photoThumbs;
@ -106,9 +111,14 @@ public class MessageObject {
}
public MessageObject(TLRPC.Message message, AbstractMap<Integer, TLRPC.User> users, AbstractMap<Integer, TLRPC.Chat> chats, boolean generateLayout) {
this(message, users, chats, generateLayout, 0);
}
public MessageObject(TLRPC.Message message, AbstractMap<Integer, TLRPC.User> users, AbstractMap<Integer, TLRPC.Chat> chats, boolean generateLayout, long eid) {
Theme.createChatResources(null, true);
messageOwner = message;
eventId = eid;
if (message.replyMessage != null) {
replyMessageObject = new MessageObject(message.replyMessage, users, chats, false);
@ -380,6 +390,8 @@ public class MessageObject {
messageText = LocaleController.getString("AttachVideo", R.string.AttachVideo);
} else if (isVoice()) {
messageText = LocaleController.getString("AttachAudio", R.string.AttachAudio);
} else if (isRoundVideo()) {
messageText = LocaleController.getString("AttachRound", R.string.AttachRound);
} else if (message.media instanceof TLRPC.TL_messageMediaGeo || message.media instanceof TLRPC.TL_messageMediaVenue) {
messageText = LocaleController.getString("AttachLocation", R.string.AttachLocation);
} else if (message.media instanceof TLRPC.TL_messageMediaContact) {
@ -429,10 +441,12 @@ public class MessageObject {
dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay);
monthKey = String.format("%d_%02d", dateYear, dateMonth);
if (messageOwner.message != null && messageOwner.id < 0 && messageOwner.message.length() > 6 && (isVideo() || isNewGif())) {
if (messageOwner.message != null && messageOwner.id < 0 && messageOwner.message.length() > 6 && (isVideo() || isNewGif() || isRoundVideo())) {
videoEditedInfo = new VideoEditedInfo();
if (!videoEditedInfo.parseString(messageOwner.message)) {
videoEditedInfo = null;
} else {
videoEditedInfo.roundVideo = isRoundVideo();
}
}
@ -478,6 +492,496 @@ public class MessageObject {
checkMediaExistance();
}
public MessageObject(TLRPC.TL_channelAdminLogEvent event, ArrayList<MessageObject> messageObjects, HashMap<String, ArrayList<MessageObject>> messagesByDays, TLRPC.Chat chat, int[] mid) {
TLRPC.User fromUser = null;
if (event.user_id > 0) {
if (fromUser == null) {
fromUser = MessagesController.getInstance().getUser(event.user_id);
}
}
Calendar rightNow = new GregorianCalendar();
rightNow.setTimeInMillis((long) (event.date) * 1000);
int dateDay = rightNow.get(Calendar.DAY_OF_YEAR);
int dateYear = rightNow.get(Calendar.YEAR);
int dateMonth = rightNow.get(Calendar.MONTH);
dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay);
monthKey = String.format("%d_%02d", dateYear, dateMonth);
ArrayList<MessageObject> dayArray = messagesByDays.get(dateKey);
TLRPC.Peer to_id = new TLRPC.TL_peerChannel();
to_id.channel_id = chat.id;
if (dayArray == null) {
dayArray = new ArrayList<>();
messagesByDays.put(dateKey, dayArray);
TLRPC.Message dateMsg = new TLRPC.Message();
dateMsg.message = LocaleController.formatDateChat(event.date);
dateMsg.id = 0;
dateMsg.date = event.date;
MessageObject dateObj = new MessageObject(dateMsg, null, false);
dateObj.type = 10;
dateObj.contentType = 1;
dateObj.isDateObject = true;
messageObjects.add(dateObj);
}
TLRPC.Message message = null;
if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeTitle) {
String title = ((TLRPC.TL_channelAdminLogEventActionChangeTitle) event.action).new_value;
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.formatString("EventLogEditedGroupTitle", R.string.EventLogEditedGroupTitle, title), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.formatString("EventLogEditedChannelTitle", R.string.EventLogEditedChannelTitle, title), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangePhoto) {
messageOwner = new TLRPC.TL_messageService();
if (event.action.new_photo instanceof TLRPC.TL_chatPhotoEmpty) {
messageOwner.action = new TLRPC.TL_messageActionChatDeletePhoto();
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.getString("EventLogRemovedWGroupPhoto", R.string.EventLogRemovedWGroupPhoto), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogRemovedChannelPhoto", R.string.EventLogRemovedChannelPhoto), "un1", fromUser);
}
} else {
messageOwner.action = new TLRPC.TL_messageActionChatEditPhoto();
messageOwner.action.photo = new TLRPC.TL_photo();
TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize();
photoSize.location = event.action.new_photo.photo_small;
photoSize.type = "s";
photoSize.w = photoSize.h = 80;
messageOwner.action.photo.sizes.add(photoSize);
photoSize = new TLRPC.TL_photoSize();
photoSize.location = event.action.new_photo.photo_big;
photoSize.type = "m";
photoSize.w = photoSize.h = 640;
messageOwner.action.photo.sizes.add(photoSize);
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.getString("EventLogEditedGroupPhoto", R.string.EventLogEditedGroupPhoto), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogEditedChannelPhoto", R.string.EventLogEditedChannelPhoto), "un1", fromUser);
}
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantJoin) {
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.getString("EventLogGroupJoined", R.string.EventLogGroupJoined), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogChannelJoined", R.string.EventLogChannelJoined), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantLeave) {
messageOwner = new TLRPC.TL_messageService();
messageOwner.action = new TLRPC.TL_messageActionChatDeleteUser();
messageOwner.action.user_id = event.user_id;
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.getString("EventLogLeftGroup", R.string.EventLogLeftGroup), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogLeftChannel", R.string.EventLogLeftChannel), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantInvite) {
messageOwner = new TLRPC.TL_messageService();
messageOwner.action = new TLRPC.TL_messageActionChatAddUser();
TLRPC.User whoUser = MessagesController.getInstance().getUser(event.action.participant.user_id);
if (event.action.participant.user_id == messageOwner.from_id) {
if (chat.megagroup) {
messageText = replaceWithLink(LocaleController.getString("EventLogGroupJoined", R.string.EventLogGroupJoined), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogChannelJoined", R.string.EventLogChannelJoined), "un1", fromUser);
}
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogAdded", R.string.EventLogAdded), "un2", whoUser);
messageText = replaceWithLink(messageText, "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantToggleAdmin) {
messageOwner = new TLRPC.TL_message();
TLRPC.User whoUser = MessagesController.getInstance().getUser(event.action.prev_participant.user_id);
String str = LocaleController.getString("EventLogPromoted", R.string.EventLogPromoted);
int offset = str.indexOf("%1$s");
StringBuilder rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset)));
rights.append("\n");
TLRPC.TL_channelAdminRights o = event.action.prev_participant.admin_rights;
TLRPC.TL_channelAdminRights n = event.action.new_participant.admin_rights;
if (o == null) {
o = new TLRPC.TL_channelAdminRights();
}
if (n == null) {
n = new TLRPC.TL_channelAdminRights();
}
if (o.change_info != n.change_info) {
rights.append('\n').append(n.change_info ? '+' : '-').append(' ');
rights.append(chat.megagroup ? LocaleController.getString("EventLogPromotedChangeGroupInfo", R.string.EventLogPromotedChangeGroupInfo) : LocaleController.getString("EventLogPromotedChangeChannelInfo", R.string.EventLogPromotedChangeChannelInfo));
}
if (!chat.megagroup) {
if (o.post_messages != n.post_messages) {
rights.append('\n').append(n.post_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedPostMessages", R.string.EventLogPromotedPostMessages));
}
if (o.edit_messages != n.edit_messages) {
rights.append('\n').append(n.edit_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedEditMessages", R.string.EventLogPromotedEditMessages));
}
}
if (o.delete_messages != n.delete_messages) {
rights.append('\n').append(n.delete_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedDeleteMessages", R.string.EventLogPromotedDeleteMessages));
}
if (o.add_admins != n.add_admins) {
rights.append('\n').append(n.add_admins ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedAddAdmins", R.string.EventLogPromotedAddAdmins));
}
if (chat.megagroup) {
if (o.ban_users != n.ban_users) {
rights.append('\n').append(n.ban_users ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedBanUsers", R.string.EventLogPromotedBanUsers));
}
}
if (o.invite_users != n.invite_users) {
rights.append('\n').append(n.invite_users ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedAddUsers", R.string.EventLogPromotedAddUsers));
}
if (chat.megagroup) {
if (o.pin_messages != n.pin_messages) {
rights.append('\n').append(n.pin_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogPromotedPinMessages", R.string.EventLogPromotedPinMessages));
}
}
messageText = rights.toString();
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionParticipantToggleBan) {
messageOwner = new TLRPC.TL_message();
TLRPC.User whoUser = MessagesController.getInstance().getUser(event.action.prev_participant.user_id);
TLRPC.TL_channelBannedRights o = event.action.prev_participant.banned_rights;
TLRPC.TL_channelBannedRights n = event.action.new_participant.banned_rights;
if (chat.megagroup && (n == null || !n.view_messages || n != null && o != null && n.until_date != o.until_date)) {
StringBuilder rights;
String bannedDuration;
if (n != null && !AndroidUtilities.isBannedForever(n.until_date)) {
bannedDuration = "";
int duration = n.until_date - event.date;
int days = duration / 60 / 60 / 24;
duration -= days * 60 * 60 * 24;
int hours = duration / 60 / 60;
duration -= hours * 60 * 60;
int minutes = duration / 60;
int count = 0;
for (int a = 0; a < 3; a++) {
String addStr = null;
if (a == 0) {
if (days != 0) {
addStr = LocaleController.formatPluralString("Days", days);
count++;
}
} else if (a == 1) {
if (hours != 0) {
addStr = LocaleController.formatPluralString("Hours", hours);
count++;
}
} else {
if (minutes != 0) {
addStr = LocaleController.formatPluralString("Minutes", minutes);
count++;
}
}
if (addStr != null) {
if (bannedDuration.length() > 0) {
bannedDuration += ", ";
}
bannedDuration += addStr;
}
if (count == 2) {
break;
}
}
} else {
bannedDuration = LocaleController.getString("UserRestrictionsUntilForever", R.string.UserRestrictionsUntilForever);
}
String str = LocaleController.getString("EventLogRestrictedUntil", R.string.EventLogRestrictedUntil);
int offset = str.indexOf("%1$s");
rights = new StringBuilder(String.format(str, getUserName(whoUser, messageOwner.entities, offset), bannedDuration));
boolean added = false;
if (o == null) {
o = new TLRPC.TL_channelBannedRights();
}
if (n == null) {
n = new TLRPC.TL_channelBannedRights();
}
if (o.view_messages != n.view_messages) {
if (!added) {
rights.append('\n');
added = true;
}
rights.append('\n').append(!n.view_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogRestrictedReadMessages", R.string.EventLogRestrictedReadMessages));
}
if (o.send_messages != n.send_messages) {
if (!added) {
rights.append('\n');
added = true;
}
rights.append('\n').append(!n.send_messages ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogRestrictedSendMessages", R.string.EventLogRestrictedSendMessages));
}
if (o.send_stickers != n.send_stickers || o.send_inline != n.send_inline || o.send_gifs != n.send_gifs || o.send_games != n.send_games) {
if (!added) {
rights.append('\n');
added = true;
}
rights.append('\n').append(!n.send_stickers ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogRestrictedSendStickers", R.string.EventLogRestrictedSendStickers));
}
if (o.send_media != n.send_media) {
if (!added) {
rights.append('\n');
added = true;
}
rights.append('\n').append(!n.send_media ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogRestrictedSendMedia", R.string.EventLogRestrictedSendMedia));
}
if (o.embed_links != n.embed_links) {
if (!added) {
rights.append('\n');
}
rights.append('\n').append(!n.embed_links ? '+' : '-').append(' ');
rights.append(LocaleController.getString("EventLogRestrictedSendEmbed", R.string.EventLogRestrictedSendEmbed));
}
messageText = rights.toString();
} else {
String str;
if (n != null && (o == null || n.view_messages)) {
str = LocaleController.getString("EventLogChannelRestricted", R.string.EventLogChannelRestricted);
} else {
str = LocaleController.getString("EventLogChannelUnrestricted", R.string.EventLogChannelUnrestricted);
}
int offset = str.indexOf("%1$s");
messageText = String.format(str, getUserName(whoUser, messageOwner.entities, offset));
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionUpdatePinned) {
if (event.action.message instanceof TLRPC.TL_messageEmpty) {
messageText = replaceWithLink(LocaleController.getString("EventLogUnpinnedMessages", R.string.EventLogUnpinnedMessages), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogPinnedMessages", R.string.EventLogPinnedMessages), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionToggleSignatures) {
if (((TLRPC.TL_channelAdminLogEventActionToggleSignatures) event.action).new_value) {
messageText = replaceWithLink(LocaleController.getString("EventLogToggledSignaturesOn", R.string.EventLogToggledSignaturesOn), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogToggledSignaturesOff", R.string.EventLogToggledSignaturesOff), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionToggleInvites) {
if (((TLRPC.TL_channelAdminLogEventActionToggleInvites) event.action).new_value) {
messageText = replaceWithLink(LocaleController.getString("EventLogToggledInvitesOn", R.string.EventLogToggledInvitesOn), "un1", fromUser);
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogToggledInvitesOff", R.string.EventLogToggledInvitesOff), "un1", fromUser);
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionDeleteMessage) {
messageText = replaceWithLink(LocaleController.getString("EventLogDeletedMessages", R.string.EventLogDeletedMessages), "un1", fromUser);
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeAbout) {
messageText = replaceWithLink(chat.megagroup ? LocaleController.getString("EventLogEditedGroupDescription", R.string.EventLogEditedGroupDescription) : LocaleController.getString("EventLogEditedChannelDescription", R.string.EventLogEditedChannelDescription), "un1", fromUser);
message = new TLRPC.TL_message();
message.out = false;
message.unread = false;
message.from_id = event.user_id;
message.to_id = to_id;
message.date = event.date;
message.message = ((TLRPC.TL_channelAdminLogEventActionChangeAbout) event.action).new_value;
if (!TextUtils.isEmpty(((TLRPC.TL_channelAdminLogEventActionChangeAbout) event.action).prev_value)) {
message.media = new TLRPC.TL_messageMediaWebPage();
message.media.webpage = new TLRPC.TL_webPage();
message.media.webpage.flags = 10;
message.media.webpage.display_url = "";
message.media.webpage.url = "";
message.media.webpage.site_name = LocaleController.getString("EventLogPreviousGroupDescription", R.string.EventLogPreviousGroupDescription);
message.media.webpage.description = ((TLRPC.TL_channelAdminLogEventActionChangeAbout) event.action).prev_value;
} else {
message.media = new TLRPC.TL_messageMediaEmpty();
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionChangeUsername) {
String newLink = ((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).new_value;
if (!TextUtils.isEmpty(newLink)) {
messageText = replaceWithLink(chat.megagroup ? LocaleController.getString("EventLogChangedGroupLink", R.string.EventLogChangedGroupLink) : LocaleController.getString("EventLogChangedChannelLink", R.string.EventLogChangedChannelLink), "un1", fromUser);
} else {
messageText = replaceWithLink(chat.megagroup ? LocaleController.getString("EventLogRemovedGroupLink", R.string.EventLogRemovedGroupLink) : LocaleController.getString("EventLogRemovedChannelLink", R.string.EventLogRemovedChannelLink), "un1", fromUser);
}
message = new TLRPC.TL_message();
message.out = false;
message.unread = false;
message.from_id = event.user_id;
message.to_id = to_id;
message.date = event.date;
if (!TextUtils.isEmpty(newLink)) {
message.message = "https://" + MessagesController.getInstance().linkPrefix + "/" + newLink;
} else {
message.message = "";
}
TLRPC.TL_messageEntityUrl url = new TLRPC.TL_messageEntityUrl();
url.offset = 0;
url.length = message.message.length();
message.entities.add(url);
if (!TextUtils.isEmpty(((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).prev_value)) {
message.media = new TLRPC.TL_messageMediaWebPage();
message.media.webpage = new TLRPC.TL_webPage();
message.media.webpage.flags = 10;
message.media.webpage.display_url = "";
message.media.webpage.url = "";
message.media.webpage.site_name = LocaleController.getString("EventLogPreviousLink", R.string.EventLogPreviousLink);
message.media.webpage.description = "https://" + MessagesController.getInstance().linkPrefix + "/" + ((TLRPC.TL_channelAdminLogEventActionChangeUsername) event.action).prev_value;
} else {
message.media = new TLRPC.TL_messageMediaEmpty();
}
} else if (event.action instanceof TLRPC.TL_channelAdminLogEventActionEditMessage) {
message = new TLRPC.TL_message();
message.out = false;
message.unread = false;
message.from_id = event.user_id;
message.to_id = to_id;
message.date = event.date;
TLRPC.Message newMessage = ((TLRPC.TL_channelAdminLogEventActionEditMessage) event.action).new_message;
TLRPC.Message oldMessage = ((TLRPC.TL_channelAdminLogEventActionEditMessage) event.action).prev_message;
if (newMessage.media != null && !(newMessage.media instanceof TLRPC.TL_messageMediaEmpty) && !(newMessage.media instanceof TLRPC.TL_messageMediaWebPage) && TextUtils.isEmpty(newMessage.message)) {
messageText = replaceWithLink(LocaleController.getString("EventLogEditedCaption", R.string.EventLogEditedCaption), "un1", fromUser);
message.media = newMessage.media;
message.media.webpage = new TLRPC.TL_webPage();
message.media.webpage.site_name = LocaleController.getString("EventLogOriginalCaption", R.string.EventLogOriginalCaption);
if (TextUtils.isEmpty(oldMessage.media.caption)) {
message.media.webpage.description = LocaleController.getString("EventLogOriginalCaptionEmpty", R.string.EventLogOriginalCaptionEmpty);
} else {
message.media.webpage.description = oldMessage.media.caption;
}
} else {
messageText = replaceWithLink(LocaleController.getString("EventLogEditedMessages", R.string.EventLogEditedMessages), "un1", fromUser);
message.message = newMessage.message;
message.media = new TLRPC.TL_messageMediaWebPage();
message.media.webpage = new TLRPC.TL_webPage();
message.media.webpage.site_name = LocaleController.getString("EventLogOriginalMessages", R.string.EventLogOriginalMessages);
message.media.webpage.description = oldMessage.message;
}
message.reply_markup = newMessage.reply_markup;
message.media.webpage.flags = 10;
message.media.webpage.display_url = "";
message.media.webpage.url = "";
} else {
messageText = "unsupported " + event.action;
}
if (messageOwner == null) {
messageOwner = new TLRPC.TL_messageService();
}
messageOwner.message = messageText.toString();
messageOwner.from_id = event.user_id;
messageOwner.date = event.date;
messageOwner.id = mid[0]++;
eventId = event.id;
//messageOwner.out = event.user_id == UserConfig.getClientUserId();
messageOwner.out = false;
messageOwner.to_id = new TLRPC.TL_peerChannel();
messageOwner.to_id.channel_id = chat.id;
messageOwner.unread = false;
if (chat.megagroup) {
messageOwner.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP;
}
if (event.action.message != null && !(event.action.message instanceof TLRPC.TL_messageEmpty)) {
message = event.action.message;
}
if (message != null) {
message.out = false;
message.id = mid[0]++;
message.reply_to_msg_id = 0;
message.flags = message.flags &~ TLRPC.MESSAGE_FLAG_EDITED;
if (chat.megagroup) {
message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP;
}
MessageObject messageObject = new MessageObject(message, null, null, true, eventId);
messageObjects.add(messageObjects.size() - 1, messageObject);
}
messageObjects.add(messageObjects.size() - 1, this);
if (messageText == null) {
messageText = "";
}
setType();
measureInlineBotButtons();
if (messageOwner.message != null && messageOwner.id < 0 && messageOwner.message.length() > 6 && (isVideo() || isNewGif() || isRoundVideo())) {
videoEditedInfo = new VideoEditedInfo();
if (!videoEditedInfo.parseString(messageOwner.message)) {
videoEditedInfo = null;
} else {
videoEditedInfo.roundVideo = isRoundVideo();
}
}
generateCaption();
TextPaint paint;
if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) {
paint = Theme.chat_msgGameTextPaint;
} else {
paint = Theme.chat_msgTextPaint;
}
int[] emojiOnly = MessagesController.getInstance().allowBigEmoji ? new int[1] : null;
messageText = Emoji.replaceEmoji(messageText, paint.getFontMetricsInt(), AndroidUtilities.dp(20), false, emojiOnly);
if (emojiOnly != null && emojiOnly[0] >= 1 && emojiOnly[0] <= 3) {
TextPaint emojiPaint;
int size;
switch (emojiOnly[0]) {
case 1:
emojiPaint = Theme.chat_msgTextPaintOneEmoji;
size = AndroidUtilities.dp(32);
break;
case 2:
emojiPaint = Theme.chat_msgTextPaintTwoEmoji;
size = AndroidUtilities.dp(28);
break;
case 3:
default:
emojiPaint = Theme.chat_msgTextPaintThreeEmoji;
size = AndroidUtilities.dp(24);
break;
}
Emoji.EmojiSpan[] spans = ((Spannable) messageText).getSpans(0, messageText.length(), Emoji.EmojiSpan.class);
if (spans != null && spans.length > 0) {
for (int a = 0; a < spans.length; a++) {
spans[a].replaceFontMetrics(emojiPaint.getFontMetricsInt(), size);
}
}
}
generateLayout(fromUser);
layoutCreated = true;
generateThumbs(false);
checkMediaExistance();
}
private String getUserName(TLRPC.User user, ArrayList<TLRPC.MessageEntity> entities, int offset) {
String name;
if (user == null) {
name = "";
} else {
name = ContactsController.formatName(user.first_name, user.last_name);
}
if (offset >= 0) {
TLRPC.TL_messageEntityMentionName entity = new TLRPC.TL_messageEntityMentionName();
entity.user_id = user.id;
entity.offset = offset;
entity.length = name.length();
entities.add(entity);
}
if (!TextUtils.isEmpty(user.username)) {
if (offset >= 0) {
TLRPC.TL_messageEntityMentionName entity = new TLRPC.TL_messageEntityMentionName();
entity.user_id = user.id;
entity.offset = offset + name.length() + 2;
entity.length = user.username.length() + 1;
entities.add(entity);
}
return String.format("%1$s (@%2$s)", name, user.username);
}
return name;
}
public void applyNewText() {
if (TextUtils.isEmpty(messageOwner.message)) {
return;
@ -560,6 +1064,8 @@ public class MessageObject {
messageText = replaceWithLink(LocaleController.getString("ActionPinnedGif", R.string.ActionPinnedGif), "un1", fromUser != null ? fromUser : chat);
} else if (replyMessageObject.isVoice()) {
messageText = replaceWithLink(LocaleController.getString("ActionPinnedVoice", R.string.ActionPinnedVoice), "un1", fromUser != null ? fromUser : chat);
} else if (replyMessageObject.isRoundVideo()) {
messageText = replaceWithLink(LocaleController.getString("ActionPinnedRound", R.string.ActionPinnedRound), "un1", fromUser != null ? fromUser : chat);
} else if (replyMessageObject.isSticker()) {
messageText = replaceWithLink(LocaleController.getString("ActionPinnedSticker", R.string.ActionPinnedSticker), "un1", fromUser != null ? fromUser : chat);
} else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) {
@ -612,7 +1118,12 @@ public class MessageObject {
}
StaticLayout staticLayout = new StaticLayout(text, Theme.chat_msgBotButtonPaint, AndroidUtilities.dp(2000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (staticLayout.getLineCount() > 0) {
maxButtonSize = Math.max(maxButtonSize, (int) Math.ceil(staticLayout.getLineWidth(0) - staticLayout.getLineLeft(0)) + AndroidUtilities.dp(4));
float width = staticLayout.getLineWidth(0);
float left = staticLayout.getLineLeft(0);
if (left < width) {
width -= left;
}
maxButtonSize = Math.max(maxButtonSize, (int) Math.ceil(width) + AndroidUtilities.dp(4));
}
}
wantedBotKeyboardWidth = Math.max(wantedBotKeyboardWidth, (maxButtonSize + AndroidUtilities.dp(12)) * size + AndroidUtilities.dp(5) * (size - 1));
@ -624,13 +1135,15 @@ public class MessageObject {
if (messageOwner instanceof TLRPC.TL_message || messageOwner instanceof TLRPC.TL_messageForwarded_old2) {
if (isMediaEmpty()) {
type = 0;
if (messageText == null || messageText.length() == 0) {
if (TextUtils.isEmpty(messageText) && eventId == 0) {
messageText = "Empty message";
}
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) {
type = 1;
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageOwner.media instanceof TLRPC.TL_messageMediaVenue) {
type = 4;
} else if (isRoundVideo()) {
type = 5;
} else if (isVideo()) {
type = 3;
} else if (isVoice()) {
@ -726,6 +1239,12 @@ public class MessageObject {
}
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) {
return "image/jpeg";
} else if (messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
if (messageOwner.media.webpage.document != null) {
return messageOwner.media.document.mime_type;
} else if (messageOwner.media.webpage.photo != null) {
return "image/jpeg";
}
}
return "";
}
@ -734,21 +1253,23 @@ public class MessageObject {
return document != null && document.thumb != null && document.mime_type != null && (document.mime_type.equals("image/gif") || isNewGifDocument(document));
}
public static boolean isVoiceVideoDocument(TLRPC.Document document) {
public static boolean isRoundVideoDocument(TLRPC.Document document) {
if (Build.VERSION.SDK_INT < 16) {
return false;
}
if (document != null && document.mime_type != null && document.mime_type.equals("video/mp4")) {
int width = 0;
int height = 0;
boolean animated = false;
boolean round = false;
for (int a = 0; a < document.attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeAnimated) {
animated = true;
} else if (attribute instanceof TLRPC.TL_documentAttributeVideo) {
if (attribute instanceof TLRPC.TL_documentAttributeVideo) {
width = attribute.w;
height = attribute.w;
round = attribute.round_message;
}
}
if (animated && width <= 1280 && height <= 1280) {
if (round && width <= 1280 && height <= 1280) {
return true;
}
}
@ -943,6 +1464,7 @@ public class MessageObject {
name = "";
id = "0";
}
name = name.replace('\n', ' ');
SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name}));
builder.setSpan(new URLSpanNoUnderlineBold("" + id), start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return builder;
@ -1173,6 +1695,7 @@ public class MessageObject {
}
boolean useManualParse = !hasEntities && (
eventId != 0 ||
messageOwner instanceof TLRPC.TL_message_old ||
messageOwner instanceof TLRPC.TL_message_old2 ||
messageOwner instanceof TLRPC.TL_message_old3 ||
@ -1207,16 +1730,23 @@ public class MessageObject {
} else if (entity.offset + entity.length > messageOwner.message.length()) {
entity.length = messageOwner.message.length() - entity.offset;
}
if (spans != null && spans.length > 0) {
for (int b = 0; b < spans.length; b++) {
if (spans[b] == null) {
continue;
}
int start = spannable.getSpanStart(spans[b]);
int end = spannable.getSpanEnd(spans[b]);
if (entity.offset <= start && entity.offset + entity.length >= start || entity.offset <= end && entity.offset + entity.length >= end) {
spannable.removeSpan(spans[b]);
spans[b] = null;
if (entity instanceof TLRPC.TL_messageEntityBold ||
entity instanceof TLRPC.TL_messageEntityItalic ||
entity instanceof TLRPC.TL_messageEntityCode ||
entity instanceof TLRPC.TL_messageEntityPre ||
entity instanceof TLRPC.TL_messageEntityMentionName ||
entity instanceof TLRPC.TL_inputMessageEntityMentionName) {
if (spans != null && spans.length > 0) {
for (int b = 0; b < spans.length; b++) {
if (spans[b] == null) {
continue;
}
int start = spannable.getSpanStart(spans[b]);
int end = spannable.getSpanEnd(spans[b]);
if (entity.offset <= start && entity.offset + entity.length >= start || entity.offset <= end && entity.offset + entity.length >= end) {
spannable.removeSpan(spans[b]);
spans[b] = null;
}
}
}
}
@ -1239,7 +1769,7 @@ public class MessageObject {
} else if (entity instanceof TLRPC.TL_messageEntityEmail) {
spannable.setSpan(new URLSpanReplacement("mailto:" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (entity instanceof TLRPC.TL_messageEntityUrl) {
if (!url.toLowerCase().startsWith("http")) {
if (!url.toLowerCase().startsWith("http") && !url.toLowerCase().startsWith("tg://")) {
spannable.setSpan(new URLSpan("http://" + url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
spannable.setSpan(new URLSpan(url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -1252,9 +1782,9 @@ public class MessageObject {
}
int maxWidth;
boolean needShare = messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !isOut();
boolean needShare = eventId == 0 && messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !isOut();
generatedWithMinSize = AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : AndroidUtilities.displaySize.x;
maxWidth = generatedWithMinSize - AndroidUtilities.dp(needShare ? 122 : 80);
maxWidth = generatedWithMinSize - AndroidUtilities.dp(needShare || eventId != 0 ? 122 : 80);
if (fromUser != null && fromUser.bot || (isMegagroup() || messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) && !isOut()) {
maxWidth -= AndroidUtilities.dp(20);
}
@ -1272,7 +1802,15 @@ public class MessageObject {
}
try {
textLayout = new StaticLayout(messageText, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textLayout = StaticLayout.Builder.obtain(messageText, 0, messageText.length(), paint, maxWidth)
.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY)
.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.build();
} else {
textLayout = new StaticLayout(messageText, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
} catch (Exception e) {
FileLog.e(e);
return;
@ -1303,7 +1841,15 @@ public class MessageObject {
block.charactersOffset = startCharacter;
block.charactersEnd = endCharacter;
try {
block.textLayout = new StaticLayout(messageText, startCharacter, endCharacter, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
block.textLayout = StaticLayout.Builder.obtain(messageText, startCharacter, endCharacter, paint, maxWidth)
.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY)
.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_NONE)
.setAlignment(Layout.Alignment.ALIGN_NORMAL)
.build();
} else {
block.textLayout = new StaticLayout(messageText, startCharacter, endCharacter, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
}
block.textYOffset = textLayout.getLineTop(linesOffset);
if (a != 0) {
block.height = (int) (block.textYOffset - prevOffset);
@ -1434,6 +1980,10 @@ public class MessageObject {
return messageOwner.out && messageOwner.from_id > 0 && !messageOwner.post;
}
public boolean needDrawAvatar() {
return isFromUser() || eventId != 0;
}
public boolean isFromUser() {
return messageOwner.from_id > 0 && !messageOwner.post;
}
@ -1474,12 +2024,12 @@ public class MessageObject {
}
public boolean isSecretPhoto() {
return messageOwner instanceof TLRPC.TL_message_secret && messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && messageOwner.ttl > 0 && messageOwner.ttl <= 60;
return messageOwner instanceof TLRPC.TL_message_secret && (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || isRoundVideo()) && messageOwner.ttl > 0 && messageOwner.ttl <= 60;
}
public boolean isSecretMedia() {
return messageOwner instanceof TLRPC.TL_message_secret &&
(messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && messageOwner.ttl > 0 && messageOwner.ttl <= 60 || isVoice() || isVideo());
((messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || isRoundVideo()) && messageOwner.ttl > 0 && messageOwner.ttl <= 60 || isVoice() || isVideo());
}
public static void setUnreadFlags(TLRPC.Message message, int flag) {
@ -1635,10 +2185,12 @@ public class MessageObject {
boolean isVideo = false;
int width = 0;
int height = 0;
for (int a = 0; a < document.attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeVideo) {
if (Build.VERSION.SDK_INT >= 16 && attribute.round_message) {
return false;
}
isVideo = true;
width = attribute.w;
height = attribute.h;
@ -1676,8 +2228,14 @@ public class MessageObject {
return message.media != null && message.media.document != null && isMusicDocument(message.media.document);
}
public static boolean isVideoVoiceMessage(TLRPC.Message message) {
return message.media != null && message.media.document != null && isVoiceVideoDocument(message.media.document);
public static boolean isRoundVideoMessage(TLRPC.Message message) {
if (Build.VERSION.SDK_INT < 16) {
return false;
}
if (message.media instanceof TLRPC.TL_messageMediaWebPage) {
return isRoundVideoDocument(message.media.webpage.document);
}
return message.media != null && message.media.document != null && isRoundVideoDocument(message.media.document);
}
public static boolean isVoiceMessage(TLRPC.Message message) {
@ -1755,6 +2313,8 @@ public class MessageObject {
return AndroidUtilities.dp(30);
} else if (type == 11) {
return AndroidUtilities.dp(50);
} else if (type == 5) {
return AndroidUtilities.roundMessageSize;
} else if (type == 13) {
float maxHeight = AndroidUtilities.displaySize.y * 0.4f;
float maxWidth;
@ -1867,8 +2427,14 @@ public class MessageObject {
return isInvoiceMessage(messageOwner);
}
public boolean isVideoVoice() {
return isVideoVoiceMessage(messageOwner) && BuildVars.DEBUG_PRIVATE_VERSION;
public boolean isRoundVideo() {
if (Build.VERSION.SDK_INT < 16) {
return false;
}
if (isRoundVideoCached == 0) {
isRoundVideoCached = type == 5 || isRoundVideoMessage(messageOwner) ? 1 : 2;
}
return isRoundVideoCached == 1;
}
public boolean hasPhotoStickers() {
@ -1888,6 +2454,10 @@ public class MessageObject {
}
public String getMusicTitle() {
return getMusicTitle(true);
}
public String getMusicTitle(boolean unknown) {
TLRPC.Document document;
if (type == 0) {
document = messageOwner.media.webpage.document;
@ -1898,16 +2468,23 @@ public class MessageObject {
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
if (attribute.voice) {
if (!unknown) {
return null;
}
return LocaleController.formatDateAudio(messageOwner.date);
}
String title = attribute.title;
if (title == null || title.length() == 0) {
title = FileLoader.getDocumentFileName(document);
if (title == null || title.length() == 0) {
if (TextUtils.isEmpty(title) && unknown) {
title = LocaleController.getString("AudioUnknownTitle", R.string.AudioUnknownTitle);
}
}
return title;
} else if (attribute instanceof TLRPC.TL_documentAttributeVideo) {
if (attribute.round_message) {
return LocaleController.formatDateAudio(messageOwner.date);
}
}
}
return "";
@ -1930,41 +2507,57 @@ public class MessageObject {
}
public String getMusicAuthor() {
return getMusicAuthor(true);
}
public String getMusicAuthor(boolean unknown) {
TLRPC.Document document;
if (type == 0) {
document = messageOwner.media.webpage.document;
} else {
document = messageOwner.media.document;
}
boolean isVoice = false;
for (int a = 0; a < document.attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = document.attributes.get(a);
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
if (attribute.voice) {
if (isOutOwner() || messageOwner.fwd_from != null && messageOwner.fwd_from.from_id == UserConfig.getClientUserId()) {
return LocaleController.getString("FromYou", R.string.FromYou);
}
TLRPC.User user = null;
TLRPC.Chat chat = null;
if (messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) {
chat = MessagesController.getInstance().getChat(messageOwner.fwd_from.channel_id);
} else if (messageOwner.fwd_from != null && messageOwner.fwd_from.from_id != 0) {
user = MessagesController.getInstance().getUser(messageOwner.fwd_from.from_id);
} else if (messageOwner.from_id < 0) {
chat = MessagesController.getInstance().getChat(-messageOwner.from_id);
} else {
user = MessagesController.getInstance().getUser(messageOwner.from_id);
}
if (user != null) {
return UserObject.getUserName(user);
} else if (chat != null) {
return chat.title;
isVoice = true;
} else {
String performer = attribute.performer;
if (TextUtils.isEmpty(performer) && unknown) {
performer = LocaleController.getString("AudioUnknownArtist", R.string.AudioUnknownArtist);
}
return performer;
}
String performer = attribute.performer;
if (performer == null || performer.length() == 0) {
performer = LocaleController.getString("AudioUnknownArtist", R.string.AudioUnknownArtist);
} else if (attribute instanceof TLRPC.TL_documentAttributeVideo) {
if (attribute.round_message) {
isVoice = true;
}
}
if (isVoice) {
if (!unknown) {
return null;
}
if (isOutOwner() || messageOwner.fwd_from != null && messageOwner.fwd_from.from_id == UserConfig.getClientUserId()) {
return LocaleController.getString("FromYou", R.string.FromYou);
}
TLRPC.User user = null;
TLRPC.Chat chat = null;
if (messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) {
chat = MessagesController.getInstance().getChat(messageOwner.fwd_from.channel_id);
} else if (messageOwner.fwd_from != null && messageOwner.fwd_from.from_id != 0) {
user = MessagesController.getInstance().getUser(messageOwner.fwd_from.from_id);
} else if (messageOwner.from_id < 0) {
chat = MessagesController.getInstance().getChat(-messageOwner.from_id);
} else {
user = MessagesController.getInstance().getUser(messageOwner.from_id);
}
if (user != null) {
return UserObject.getUserName(user);
} else if (chat != null) {
return chat.title;
}
return performer;
}
}
return "";
@ -1979,7 +2572,7 @@ public class MessageObject {
}
public static boolean isForwardedMessage(TLRPC.Message message) {
return (message.flags & TLRPC.MESSAGE_FLAG_FWD) != 0;
return (message.flags & TLRPC.MESSAGE_FLAG_FWD) != 0 && message.fwd_from != null;
}
public boolean isReply() {
@ -1999,7 +2592,7 @@ public class MessageObject {
}
public static boolean canEditMessage(TLRPC.Message message, TLRPC.Chat chat) {
if (message == null || message.to_id == null || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0) {
if (message == null || message.to_id == null || message.media != null && isRoundVideoDocument(message.media.document) || message.action != null && !(message.action instanceof TLRPC.TL_messageActionEmpty) || isForwardedMessage(message) || message.via_bot_id != 0 || message.id < 0) {
return false;
}
if (message.from_id == message.to_id.user_id && message.from_id == UserConfig.getClientUserId()) {
@ -2021,7 +2614,7 @@ public class MessageObject {
return false;
}
}
if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && message.post) {
if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.admin_rights != null && chat.admin_rights.edit_messages) && message.post) {
if (message.media instanceof TLRPC.TL_messageMediaPhoto ||
message.media instanceof TLRPC.TL_messageMediaDocument && !isStickerMessage(message) ||
message.media instanceof TLRPC.TL_messageMediaEmpty ||
@ -2034,7 +2627,7 @@ public class MessageObject {
}
public boolean canDeleteMessage(TLRPC.Chat chat) {
return canDeleteMessage(messageOwner, chat);
return eventId == 0 && canDeleteMessage(messageOwner, chat);
}
public static boolean canDeleteMessage(TLRPC.Message message, TLRPC.Chat chat) {
@ -2045,22 +2638,7 @@ public class MessageObject {
chat = MessagesController.getInstance().getChat(message.to_id.channel_id);
}
if (ChatObject.isChannel(chat)) {
if (message.id == 1) {
return false;
}
if (chat.creator) {
return true;
} else if (chat.editor) {
if (isOut(message) || message.from_id > 0 && !message.post) {
return true;
}
} else if (chat.moderator) {
if (message.from_id > 0 && !message.post) {
return true;
}
} else {
return chat.megagroup && isOut(message) && message.from_id > 0;
}
return message.id != 1 && (chat.creator || chat.admin_rights != null && chat.admin_rights.delete_messages || chat.megagroup && isOut(message) && message.from_id > 0);
}
return isOut(message) || !ChatObject.isChannel(chat);
}
@ -2091,7 +2669,7 @@ public class MessageObject {
if (currentPhotoObject != null) {
mediaExists = FileLoader.getPathToMessage(messageOwner).exists();
}
} else if (type == 8 || type == 3 || type == 9 || type == 2 || type == 14) {
} else if (type == 8 || type == 3 || type == 9 || type == 2 || type == 14 || type == 5) {
if (messageOwner.attachPath != null && messageOwner.attachPath.length() > 0) {
File f = new File(messageOwner.attachPath);
attachPathExists = f.exists();

View File

@ -53,7 +53,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
private ConcurrentHashMap<Integer, TLRPC.Chat> chats = new ConcurrentHashMap<>(100, 1.0f, 2);
private ConcurrentHashMap<Integer, TLRPC.EncryptedChat> encryptedChats = new ConcurrentHashMap<>(10, 1.0f, 2);
private ConcurrentHashMap<Integer, TLRPC.User> users = new ConcurrentHashMap<>(100, 1.0f, 2);
private ConcurrentHashMap<String, TLRPC.User> usersByUsernames = new ConcurrentHashMap<>(100, 1.0f, 2);
private ConcurrentHashMap<String, TLObject> objectsByUsernames = new ConcurrentHashMap<>(100, 1.0f, 2);
private ArrayList<Integer> joiningToChannels = new ArrayList<>();
@ -79,6 +79,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
private HashMap<Long, Boolean> loadingPeerSettings = new HashMap<>();
private ArrayList<Long> createdDialogIds = new ArrayList<>();
private ArrayList<Long> createdDialogMainThreadIds = new ArrayList<>();
private SparseIntArray shortPollChannels = new SparseIntArray();
private SparseIntArray needShortPollChannels = new SparseIntArray();
@ -147,7 +148,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
public int fontSize = AndroidUtilities.dp(16);
public int maxGroupCount = 200;
public int maxBroadcastCount = 100;
public int maxMegagroupCount = 5000;
public int maxMegagroupCount = 10000;
public int minGroupConvertSize = 200;
public int maxEditTime = 172800;
public int groupBigSize;
@ -276,7 +277,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
secretWebpagePreview = preferences.getInt("secretWebpage2", 2);
maxGroupCount = preferences.getInt("maxGroupCount", 200);
maxMegagroupCount = preferences.getInt("maxMegagroupCount", 1000);
maxMegagroupCount = preferences.getInt("maxMegagroupCount", 10000);
maxRecentGifsCount = preferences.getInt("maxRecentGifsCount", 200);
maxRecentStickersCount = preferences.getInt("maxRecentStickersCount", 30);
maxEditTime = preferences.getInt("maxEditTime", 3600);
@ -317,6 +318,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
LocaleController.getInstance().loadRemoteLanguages();
//maxBroadcastCount = config.broadcast_size_max;
maxMegagroupCount = config.megagroup_size_max;
maxGroupCount = config.chat_size_max;
@ -605,7 +607,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
dialogMessagesByIds.clear();
dialogMessagesByRandomIds.clear();
users.clear();
usersByUsernames.clear();
objectsByUsernames.clear();
chats.clear();
dialogMessage.clear();
printingUsers.clear();
@ -629,6 +631,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
gettingDifference = false;
}
});
createdDialogMainThreadIds.clear();
blockedUsers.clear();
sendingTypings.clear();
loadingFullUsers.clear();
@ -681,11 +684,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
return users.get(id);
}
public TLRPC.User getUser(String username) {
public TLObject getUserOrChat(String username) {
if (username == null || username.length() == 0) {
return null;
}
return usersByUsernames.get(username.toLowerCase());
return objectsByUsernames.get(username.toLowerCase());
}
public ConcurrentHashMap<Integer, TLRPC.User> getUsers() {
@ -721,7 +724,16 @@ public class MessagesController implements NotificationCenter.NotificationCenter
return chat;
}
public boolean isDialogCreated(long dialog_id) {
return createdDialogMainThreadIds.contains(dialog_id);
}
public void setLastCreatedDialogId(final long dialog_id, final boolean set) {
if (set) {
createdDialogMainThreadIds.add(dialog_id);
} else {
createdDialogMainThreadIds.remove(dialog_id);
}
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
@ -745,10 +757,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
fromCache = fromCache && user.id / 1000 != 333 && user.id != 777000;
TLRPC.User oldUser = users.get(user.id);
if (oldUser != null && !TextUtils.isEmpty(oldUser.username)) {
usersByUsernames.remove(oldUser.username.toLowerCase());
objectsByUsernames.remove(oldUser.username.toLowerCase());
}
if (!TextUtils.isEmpty(user.username)) {
usersByUsernames.put(user.username.toLowerCase(), user);
objectsByUsernames.put(user.username.toLowerCase(), user);
}
if (user.min) {
if (oldUser != null) {
@ -827,12 +839,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
}
public void putChat(TLRPC.Chat chat, boolean fromCache) {
public void putChat(final TLRPC.Chat chat, boolean fromCache) {
if (chat == null) {
return;
}
TLRPC.Chat oldChat = chats.get(chat.id);
if (oldChat != null && !TextUtils.isEmpty(oldChat.username)) {
objectsByUsernames.remove(oldChat.username.toLowerCase());
}
if (!TextUtils.isEmpty(chat.username)) {
objectsByUsernames.put(chat.username.toLowerCase(), chat);
}
if (chat.min) {
if (oldChat != null) {
if (!fromCache) {
@ -855,8 +872,20 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
} else {
if (!fromCache) {
if (oldChat != null && chat.version != oldChat.version) {
loadedFullChats.remove((Integer) chat.id);
if (oldChat != null) {
if (chat.version != oldChat.version) {
loadedFullChats.remove((Integer) chat.id);
}
int oldFlags = oldChat.banned_rights != null ? oldChat.banned_rights.flags : 0;
int newFlags = chat.banned_rights != null ? chat.banned_rights.flags : 0;
if (oldFlags != newFlags) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.channelRightsUpdated, chat);
}
});
}
}
chats.put(chat.id, chat);
} else if (oldChat == null) {
@ -1360,8 +1389,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
return;
}
loadingPeerSettings.put(dialogId, true);
FileLog.d("request spam button for " + dialogId);
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
if (preferences.getInt("spam3_" + dialogId, 0) == 1) {
FileLog.d("spam button already hidden for " + dialogId);
return;
}
boolean hidden = preferences.getBoolean("spam_" + dialogId, false);
@ -1408,12 +1439,15 @@ public class MessagesController implements NotificationCenter.NotificationCenter
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
if (!res.report_spam) {
FileLog.d("don't show spam button for " + dialogId);
editor.putInt("spam3_" + dialogId, 1);
editor.commit();
} else {
FileLog.d("show spam button for " + dialogId);
editor.putInt("spam3_" + dialogId, 2);
editor.commit();
NotificationCenter.getInstance().postNotificationName(NotificationCenter.peerSettingsDidLoaded, dialogId);
}
editor.commit();
}
}
});
@ -1682,6 +1716,68 @@ public class MessagesController implements NotificationCenter.NotificationCenter
});
}
public static void setUserBannedRole(final int chatId, TLRPC.User user, TLRPC.TL_channelBannedRights rights, final boolean isMegagroup, final BaseFragment parentFragment) {
if (user == null || rights == null) {
return;
}
final TLRPC.TL_channels_editBanned req = new TLRPC.TL_channels_editBanned();
req.channel = MessagesController.getInputChannel(chatId);
req.user_id = MessagesController.getInputUser(user);
req.banned_rights = rights;
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(TLObject response, final TLRPC.TL_error error) {
if (error == null) {
MessagesController.getInstance().processUpdates((TLRPC.Updates) response, false);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().loadFullChat(chatId, 0, true);
}
}, 1000);
} else {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
AlertsCreator.processError(error, parentFragment, req, !isMegagroup);
}
});
}
}
});
}
public static void setUserAdminRole(final int chatId, TLRPC.User user, TLRPC.TL_channelAdminRights rights, final boolean isMegagroup, final BaseFragment parentFragment) {
if (user == null || rights == null) {
return;
}
final TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin();
req.channel = MessagesController.getInputChannel(chatId);
req.user_id = MessagesController.getInputUser(user);
req.admin_rights = rights;
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(TLObject response, final TLRPC.TL_error error) {
if (error == null) {
MessagesController.getInstance().processUpdates((TLRPC.Updates) response, false);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().loadFullChat(chatId, 0, true);
}
}, 1000);
} else {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
AlertsCreator.processError(error, parentFragment, req, !isMegagroup);
}
});
}
}
});
}
public void unblockUser(int user_id) {
TLRPC.TL_contacts_unblock req = new TLRPC.TL_contacts_unblock();
final TLRPC.User user = getUser(user_id);
@ -2437,6 +2533,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
newPrintingStrings.put(key, LocaleController.getString("RecordingAudio", R.string.RecordingAudio));
}
newPrintingStringsTypes.put(key, 1);
} else if (pu.action instanceof TLRPC.TL_sendMessageRecordRoundAction || pu.action instanceof TLRPC.TL_sendMessageUploadRoundAction) {
if (lower_id < 0) {
newPrintingStrings.put(key, LocaleController.formatString("IsRecordingRound", R.string.IsRecordingRound, getUserNameForTyping(user)));
} else {
newPrintingStrings.put(key, LocaleController.getString("RecordingRound", R.string.RecordingRound));
}
newPrintingStringsTypes.put(key, 4);
} else if (pu.action instanceof TLRPC.TL_sendMessageUploadAudioAction) {
if (lower_id < 0) {
newPrintingStrings.put(key, LocaleController.formatString("IsSendingAudio", R.string.IsSendingAudio, getUserNameForTyping(user)));
@ -2573,6 +2676,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
req.action = new TLRPC.TL_sendMessageUploadVideoAction();
} else if (action == 6) {
req.action = new TLRPC.TL_sendMessageGamePlayAction();
} else if (action == 7) {
req.action = new TLRPC.TL_sendMessageRecordRoundAction();
} else if (action == 8) {
req.action = new TLRPC.TL_sendMessageUploadRoundAction();
}
typings.put(dialog_id, true);
int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@ -2819,6 +2926,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.User user = usersDict.get(message.action.user_id);
if (user != null && user.bot) {
message.reply_markup = new TLRPC.TL_replyKeyboardHide();
message.flags |= 64;
}
}
if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate) {
@ -2901,35 +3009,62 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.TL_messages_getDialogs req = new TLRPC.TL_messages_getDialogs();
req.limit = count;
req.exclude_pinned = true;
boolean found = false;
for (int a = dialogs.size() - 1; a >= 0; a--) {
TLRPC.TL_dialog dialog = dialogs.get(a);
if (dialog.pinned) {
continue;
if (UserConfig.dialogsLoadOffsetId != -1) {
if (UserConfig.dialogsLoadOffsetId == Integer.MAX_VALUE) {
dialogsEndReached = true;
serverDialogsEndReached = true;
loadingDialogs = false;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
return;
}
int lower_id = (int) dialog.id;
int high_id = (int) (dialog.id >> 32);
if (lower_id != 0 && high_id != 1 && dialog.top_message > 0) {
MessageObject message = dialogMessage.get(dialog.id);
if (message != null && message.getId() > 0) {
req.offset_date = message.messageOwner.date;
req.offset_id = message.messageOwner.id;
int id;
if (message.messageOwner.to_id.channel_id != 0) {
id = -message.messageOwner.to_id.channel_id;
} else if (message.messageOwner.to_id.chat_id != 0) {
id = -message.messageOwner.to_id.chat_id;
} else {
id = message.messageOwner.to_id.user_id;
req.offset_id = UserConfig.dialogsLoadOffsetId;
req.offset_date = UserConfig.dialogsLoadOffsetDate;
if (req.offset_id == 0) {
req.offset_peer = new TLRPC.TL_inputPeerEmpty();
} else {
if (UserConfig.dialogsLoadOffsetChannelId != 0) {
req.offset_peer = new TLRPC.TL_inputPeerChannel();
req.offset_peer.channel_id = UserConfig.dialogsLoadOffsetChannelId;
} else if (UserConfig.dialogsLoadOffsetUserId != 0) {
req.offset_peer = new TLRPC.TL_inputPeerUser();
req.offset_peer.user_id = UserConfig.dialogsLoadOffsetUserId;
} else {
req.offset_peer = new TLRPC.TL_inputPeerChat();
req.offset_peer.chat_id = UserConfig.dialogsLoadOffsetChatId;
}
req.offset_peer.access_hash = UserConfig.dialogsLoadOffsetAccess;
}
} else {
boolean found = false;
for (int a = dialogs.size() - 1; a >= 0; a--) {
TLRPC.TL_dialog dialog = dialogs.get(a);
if (dialog.pinned) {
continue;
}
int lower_id = (int) dialog.id;
int high_id = (int) (dialog.id >> 32);
if (lower_id != 0 && high_id != 1 && dialog.top_message > 0) {
MessageObject message = dialogMessage.get(dialog.id);
if (message != null && message.getId() > 0) {
req.offset_date = message.messageOwner.date;
req.offset_id = message.messageOwner.id;
int id;
if (message.messageOwner.to_id.channel_id != 0) {
id = -message.messageOwner.to_id.channel_id;
} else if (message.messageOwner.to_id.chat_id != 0) {
id = -message.messageOwner.to_id.chat_id;
} else {
id = message.messageOwner.to_id.user_id;
}
req.offset_peer = getInputPeer(id);
found = true;
break;
}
req.offset_peer = getInputPeer(id);
found = true;
break;
}
}
}
if (!found) {
req.offset_peer = new TLRPC.TL_inputPeerEmpty();
if (!found) {
req.offset_peer = new TLRPC.TL_inputPeerEmpty();
}
}
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
@ -2950,9 +3085,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
migratingDialogs = true;
TLRPC.TL_messages_getDialogs req = new TLRPC.TL_messages_getDialogs();
req.exclude_pinned = true;
req.limit = 100;
req.offset_id = offset;
req.offset_date = offsetDate;
FileLog.e("start migrate with id " + offset + " date " + LocaleController.getInstance().formatterStats.format((long) offsetDate * 1000));
if (offset == 0) {
req.offset_peer = new TLRPC.TL_inputPeerEmpty();
} else {
@ -2978,51 +3115,26 @@ public class MessagesController implements NotificationCenter.NotificationCenter
public void run() {
try {
int offsetId;
if (dialogsRes.dialogs.size() == 100) {
TLRPC.Message lastMessage = null;
for (int a = 0; a < dialogsRes.messages.size(); a++) {
TLRPC.Message message = dialogsRes.messages.get(a);
if (lastMessage == null || message.date < lastMessage.date) {
lastMessage = message;
}
UserConfig.totalDialogsLoadCount += dialogsRes.dialogs.size();
TLRPC.Message lastMessage = null;
for (int a = 0; a < dialogsRes.messages.size(); a++) {
TLRPC.Message message = dialogsRes.messages.get(a);
FileLog.e("search migrate id " + message.id + " date " + LocaleController.getInstance().formatterStats.format((long) message.date * 1000));
if (lastMessage == null || message.date < lastMessage.date) {
lastMessage = message;
}
}
FileLog.e("migrate step with id " + lastMessage.id + " date " + LocaleController.getInstance().formatterStats.format((long) lastMessage.date * 1000));
if (dialogsRes.dialogs.size() >= 100) {
offsetId = lastMessage.id;
UserConfig.migrateOffsetDate = lastMessage.date;
if (lastMessage.to_id.channel_id != 0) {
UserConfig.migrateOffsetChannelId = lastMessage.to_id.channel_id;
UserConfig.migrateOffsetChatId = 0;
UserConfig.migrateOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.migrateOffsetChannelId) {
UserConfig.migrateOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.chat_id != 0) {
UserConfig.migrateOffsetChatId = lastMessage.to_id.chat_id;
UserConfig.migrateOffsetChannelId = 0;
UserConfig.migrateOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.migrateOffsetChatId) {
UserConfig.migrateOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.user_id != 0) {
UserConfig.migrateOffsetUserId = lastMessage.to_id.user_id;
UserConfig.migrateOffsetChatId = 0;
UserConfig.migrateOffsetChannelId = 0;
for (int a = 0; a < dialogsRes.users.size(); a++) {
TLRPC.User user = dialogsRes.users.get(a);
if (user.id == UserConfig.migrateOffsetUserId) {
UserConfig.migrateOffsetAccess = user.access_hash;
break;
}
}
}
} else {
FileLog.e("migrate stop due to not 100 dialogs");
UserConfig.dialogsLoadOffsetId = Integer.MAX_VALUE;
UserConfig.dialogsLoadOffsetDate = UserConfig.migrateOffsetDate;
UserConfig.dialogsLoadOffsetUserId = UserConfig.migrateOffsetUserId;
UserConfig.dialogsLoadOffsetChatId = UserConfig.migrateOffsetChatId;
UserConfig.dialogsLoadOffsetChannelId = UserConfig.migrateOffsetChannelId;
UserConfig.dialogsLoadOffsetAccess = UserConfig.migrateOffsetAccess;
offsetId = -1;
}
@ -3058,21 +3170,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter
a--;
if (message.id == dialog.top_message) {
dialog.top_message = 0;
}
if (dialog.top_message == 0) {
break;
}
}
}
}
cursor.dispose();
FileLog.e("migrate found missing dialogs " + dialogsRes.dialogs.size());
cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT min(date) FROM dialogs WHERE date != 0 AND did >> 32 IN (0, -1)");
if (cursor.next()) {
int date = Math.max(1441062000, cursor.intValue(0));
for (int a = 0; a < dialogsRes.messages.size(); a++) {
TLRPC.Message message = dialogsRes.messages.get(a);
if (message.date < date) {
offsetId = -1;
if (offset != -1) {
UserConfig.dialogsLoadOffsetId = UserConfig.migrateOffsetId;
UserConfig.dialogsLoadOffsetDate = UserConfig.migrateOffsetDate;
UserConfig.dialogsLoadOffsetUserId = UserConfig.migrateOffsetUserId;
UserConfig.dialogsLoadOffsetChatId = UserConfig.migrateOffsetChatId;
UserConfig.dialogsLoadOffsetChannelId = UserConfig.migrateOffsetChannelId;
UserConfig.dialogsLoadOffsetAccess = UserConfig.migrateOffsetAccess;
offsetId = -1;
FileLog.e("migrate stop due to reached loaded dialogs " + LocaleController.getInstance().formatterStats.format((long) date * 1000));
}
dialogsRes.messages.remove(a);
a--;
TLRPC.TL_dialog dialog = dialogHashMap.remove(MessageObject.getDialogId(message));
@ -3081,9 +3201,55 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
}
}
if (lastMessage != null && lastMessage.date < date && offset != -1) {
UserConfig.dialogsLoadOffsetId = UserConfig.migrateOffsetId;
UserConfig.dialogsLoadOffsetDate = UserConfig.migrateOffsetDate;
UserConfig.dialogsLoadOffsetUserId = UserConfig.migrateOffsetUserId;
UserConfig.dialogsLoadOffsetChatId = UserConfig.migrateOffsetChatId;
UserConfig.dialogsLoadOffsetChannelId = UserConfig.migrateOffsetChannelId;
UserConfig.dialogsLoadOffsetAccess = UserConfig.migrateOffsetAccess;
offsetId = -1;
FileLog.e("migrate stop due to reached loaded dialogs " + LocaleController.getInstance().formatterStats.format((long) date * 1000));
}
}
cursor.dispose();
UserConfig.migrateOffsetDate = lastMessage.date;
if (lastMessage.to_id.channel_id != 0) {
UserConfig.migrateOffsetChannelId = lastMessage.to_id.channel_id;
UserConfig.migrateOffsetChatId = 0;
UserConfig.migrateOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.migrateOffsetChannelId) {
UserConfig.migrateOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.chat_id != 0) {
UserConfig.migrateOffsetChatId = lastMessage.to_id.chat_id;
UserConfig.migrateOffsetChannelId = 0;
UserConfig.migrateOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.migrateOffsetChatId) {
UserConfig.migrateOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.user_id != 0) {
UserConfig.migrateOffsetUserId = lastMessage.to_id.user_id;
UserConfig.migrateOffsetChatId = 0;
UserConfig.migrateOffsetChannelId = 0;
for (int a = 0; a < dialogsRes.users.size(); a++) {
TLRPC.User user = dialogsRes.users.get(a);
if (user.id == UserConfig.migrateOffsetUserId) {
UserConfig.migrateOffsetAccess = user.access_hash;
break;
}
}
}
processLoadedDialogs(dialogsRes, null, offsetId, 0, 0, false, true, false);
} catch (Exception e) {
FileLog.e(e);
@ -3127,9 +3293,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter
if (resetEnd) {
dialogsEndReached = false;
serverDialogsEndReached = false;
} else if (UserConfig.dialogsLoadOffsetId == Integer.MAX_VALUE) {
dialogsEndReached = true;
serverDialogsEndReached = true;
} else {
loadDialogs(0, count, false);
}
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
loadDialogs(0, count, false);
}
});
return;
@ -3152,8 +3322,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
nextDialogsCacheOffset = offset + count;
}
TLRPC.Message lastMessage = null;
for (int a = 0; a < dialogsRes.messages.size(); a++) {
TLRPC.Message message = dialogsRes.messages.get(a);
if (lastMessage == null || message.date < lastMessage.date) {
lastMessage = message;
}
if (message.to_id.channel_id != 0) {
TLRPC.Chat chat = chatsDict.get(message.to_id.channel_id);
if (chat != null && chat.left) {
@ -3175,6 +3349,51 @@ public class MessagesController implements NotificationCenter.NotificationCenter
new_dialogMessage.put(messageObject.getDialogId(), messageObject);
}
if (!fromCache && !migrate && UserConfig.dialogsLoadOffsetId != -1 && loadType == 0) {
if (lastMessage != null && lastMessage.id != UserConfig.dialogsLoadOffsetId) {
UserConfig.totalDialogsLoadCount += dialogsRes.dialogs.size();
UserConfig.dialogsLoadOffsetId = lastMessage.id;
UserConfig.dialogsLoadOffsetDate = lastMessage.date;
if (lastMessage.to_id.channel_id != 0) {
UserConfig.dialogsLoadOffsetChannelId = lastMessage.to_id.channel_id;
UserConfig.dialogsLoadOffsetChatId = 0;
UserConfig.dialogsLoadOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.dialogsLoadOffsetChannelId) {
UserConfig.dialogsLoadOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.chat_id != 0) {
UserConfig.dialogsLoadOffsetChatId = lastMessage.to_id.chat_id;
UserConfig.dialogsLoadOffsetChannelId = 0;
UserConfig.dialogsLoadOffsetUserId = 0;
for (int a = 0; a < dialogsRes.chats.size(); a++) {
TLRPC.Chat chat = dialogsRes.chats.get(a);
if (chat.id == UserConfig.dialogsLoadOffsetChatId) {
UserConfig.dialogsLoadOffsetAccess = chat.access_hash;
break;
}
}
} else if (lastMessage.to_id.user_id != 0) {
UserConfig.dialogsLoadOffsetUserId = lastMessage.to_id.user_id;
UserConfig.dialogsLoadOffsetChatId = 0;
UserConfig.dialogsLoadOffsetChannelId = 0;
for (int a = 0; a < dialogsRes.users.size(); a++) {
TLRPC.User user = dialogsRes.users.get(a);
if (user.id == UserConfig.dialogsLoadOffsetUserId) {
UserConfig.dialogsLoadOffsetAccess = user.access_hash;
break;
}
}
}
} else {
UserConfig.dialogsLoadOffsetId = Integer.MAX_VALUE;
}
UserConfig.saveConfig(false);
}
final ArrayList<TLRPC.TL_dialog> dialogsToReload = new ArrayList<>();
for (int a = 0; a < dialogsRes.dialogs.size(); a++) {
TLRPC.TL_dialog d = dialogsRes.dialogs.get(a);
@ -3242,6 +3461,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.User user = usersDict.get(message.action.user_id);
if (user != null && user.bot) {
message.reply_markup = new TLRPC.TL_replyKeyboardHide();
message.flags |= 64;
}
}
@ -3370,6 +3590,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
}
}
if (!fromCache && !migrate && UserConfig.totalDialogsLoadCount < 400 && UserConfig.dialogsLoadOffsetId != -1 && UserConfig.dialogsLoadOffsetId != Integer.MAX_VALUE) {
loadDialogs(0, 100, false);
}
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
if (migrate) {
@ -3512,7 +3735,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
if (taskId == 0) {
NativeByteBuffer data = null;
try {
data = new NativeByteBuffer(48 + peer.getObjectSize());
data = new NativeByteBuffer(48 + req.peer.getObjectSize());
data.writeInt32(5);
data.writeInt64(dialog.id);
data.writeInt32(dialog.top_message);
@ -4321,18 +4544,27 @@ public class MessagesController implements NotificationCenter.NotificationCenter
request = req;
joiningToChannels.add(chat_id);
} else {
if (user.bot && !isMegagroup) {
/*if (user.bot && !isMegagroup) { TODO
TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin();
req.channel = getInputChannel(chat_id);
req.user_id = getInputUser(user);
req.role = new TLRPC.TL_channelRoleEditor();
req.rights = new TLRPC.TL_channelAdminRights();
req.rights.change_info = true;
req.rights.post_messages = true;
req.rights.delete_messages = true;
req.rights.ban_users = true;
req.rights.invite_users = true;
req.rights.invite_link = true;
req.rights.pin_messages = true;
req.rights.start_calls = true;
req.rights.add_admins = true;
request = req;
} else {
} else {*/
TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel();
req.channel = getInputChannel(chat_id);
req.users.add(inputUser);
request = req;
}
//}
}
} else {
TLRPC.TL_messages_addChatUser req = new TLRPC.TL_messages_addChatUser();
@ -4450,10 +4682,18 @@ public class MessagesController implements NotificationCenter.NotificationCenter
request = req;
}
} else {
TLRPC.TL_channels_kickFromChannel req = new TLRPC.TL_channels_kickFromChannel();
TLRPC.TL_channels_editBanned req = new TLRPC.TL_channels_editBanned();
req.channel = getInputChannel(chat);
req.user_id = inputUser;
req.kicked = true;
req.banned_rights = new TLRPC.TL_channelBannedRights();
req.banned_rights.view_messages = true;
req.banned_rights.send_media = true;
req.banned_rights.send_messages = true;
req.banned_rights.send_stickers = true;
req.banned_rights.send_gifs = true;
req.banned_rights.send_games = true;
req.banned_rights.send_inline = true;
req.banned_rights.embed_links = true;
request = req;
}
} else {
@ -5399,6 +5639,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.User user = usersDict.get(message.action.user_id);
if (user != null && user.bot) {
message.reply_markup = new TLRPC.TL_replyKeyboardHide();
message.flags |= 64;
}
}
if (message.action instanceof TLRPC.TL_messageActionChatMigrateTo || message.action instanceof TLRPC.TL_messageActionChannelCreate) {
@ -6166,9 +6407,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.Chat existChat = getChat(chat.id);
if (existChat == null || existChat.min) {
TLRPC.Chat cacheChat = MessagesStorage.getInstance().getChatSync(updates.chat_id);
if (existChat == null) {
putChat(cacheChat, true);
}
putChat(cacheChat, true);
existChat = cacheChat;
}
if (existChat == null || existChat.min) {
@ -6357,10 +6596,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
if (processUpdate) {
processUpdateArray(updates.updates, updates.users, updates.chats, false);
if (updates.date != 0) {
MessagesStorage.lastDateValue = updates.date;
}
if (updates.seq != 0) {
if (updates.date != 0) {
MessagesStorage.lastDateValue = updates.date;
}
MessagesStorage.lastSeqValue = updates.seq;
}
} else {
@ -6596,6 +6835,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
TLRPC.User user = usersDict.get(message.action.user_id);
if (user != null && user.bot) {
message.reply_markup = new TLRPC.TL_replyKeyboardHide();
message.flags |= 64;
} else if (message.from_id == UserConfig.getClientUserId() && message.action.user_id == UserConfig.getClientUserId()) {
continue;
}
@ -6896,7 +7136,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter
UserConfig.saveConfig(false);
newMessage.unread = true;
newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID;
newMessage.date = notification.inbox_date;
if (notification.inbox_date != 0) {
newMessage.date = notification.inbox_date;
} else {
newMessage.date = (int) (System.currentTimeMillis() / 1000);
}
newMessage.from_id = 777000;
newMessage.to_id = new TLRPC.TL_peerUser();
newMessage.to_id.user_id = UserConfig.getClientUserId();
@ -7120,6 +7364,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
updatesOnMainThread.add(update);
} else if (update instanceof TLRPC.TL_updatePhoneCall) {
updatesOnMainThread.add(update);
} else if (update instanceof TLRPC.TL_updateLangPack) {
LocaleController.getInstance().saveRemoteLocaleStrings(update.difference);
} else if (update instanceof TLRPC.TL_updateLangPackTooLong) {
LocaleController.getInstance().reloadCurrentRemoteLocale();
}
}
if (!messages.isEmpty()) {
@ -7223,10 +7471,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter
currentUser.last_name = update.last_name;
}
if (currentUser.username != null && currentUser.username.length() > 0) {
usersByUsernames.remove(currentUser.username);
objectsByUsernames.remove(currentUser.username);
}
if (update.username != null && update.username.length() > 0) {
usersByUsernames.put(update.username, currentUser);
objectsByUsernames.put(update.username, currentUser);
}
currentUser.username = update.username;
}
@ -7426,12 +7674,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter
intent.putExtra("user_id", call.participant_id == UserConfig.getClientUserId() ? call.admin_id : call.participant_id);
ApplicationLoader.applicationContext.startService(intent);
} else {
if (svc != null && call!=null) {
if (svc != null && call != null) {
svc.onCallUpdated(call);
}else if(VoIPService.callIShouldHavePutIntoIntent!=null){
} else if (VoIPService.callIShouldHavePutIntoIntent != null) {
FileLog.d("Updated the call while the service is starting");
if(call.id==VoIPService.callIShouldHavePutIntoIntent.id){
VoIPService.callIShouldHavePutIntoIntent=call;
if (call.id == VoIPService.callIShouldHavePutIntoIntent.id) {
VoIPService.callIShouldHavePutIntoIntent = call;
}
}
}
@ -7543,17 +7791,23 @@ public class MessagesController implements NotificationCenter.NotificationCenter
if (markAsReadMessagesInbox.size() != 0 || markAsReadMessagesOutbox.size() != 0) {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesRead, markAsReadMessagesInbox, markAsReadMessagesOutbox);
NotificationsController.getInstance().processReadMessages(markAsReadMessagesInbox, 0, 0, 0, false);
for (int b = 0; b < markAsReadMessagesInbox.size(); b++) {
int key = markAsReadMessagesInbox.keyAt(b);
int messageId = (int) ((long) markAsReadMessagesInbox.get(key));
TLRPC.TL_dialog dialog = dialogs_dict.get((long) key);
if (dialog != null && dialog.top_message <= messageId) {
MessageObject obj = dialogMessage.get(dialog.id);
if (obj != null && !obj.isOut()) {
obj.setIsRead();
updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE;
if (markAsReadMessagesInbox.size() != 0) {
SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit();
for (int b = 0; b < markAsReadMessagesInbox.size(); b++) {
int key = markAsReadMessagesInbox.keyAt(b);
int messageId = (int) ((long) markAsReadMessagesInbox.get(key));
TLRPC.TL_dialog dialog = dialogs_dict.get((long) key);
if (dialog != null && dialog.top_message <= messageId) {
MessageObject obj = dialogMessage.get(dialog.id);
if (obj != null && !obj.isOut()) {
obj.setIsRead();
updateMask |= UPDATE_MASK_READ_DIALOG_MESSAGE;
}
}
editor.remove("diditem" + key);
editor.remove("diditemo" + key);
}
editor.commit();
}
for (int b = 0; b < markAsReadMessagesOutbox.size(); b++) {
int key = markAsReadMessagesOutbox.keyAt(b);
@ -7843,7 +8097,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
dialogsServerOnly.add(d);
if (DialogObject.isChannel(d)) {
TLRPC.Chat chat = getChat(-lower_id);
if (chat != null && (chat.megagroup && chat.editor || chat.creator)) {
if (chat != null && (chat.megagroup && (chat.admin_rights != null && chat.admin_rights.post_messages) || chat.creator)) {
dialogsGroupsOnly.add(d);
}
} else if (lower_id < 0) {
@ -7940,6 +8194,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
if (type == 0) {
fragment.presentFragment(new ProfileActivity(args));
} else if (type == 2) {
fragment.presentFragment(new ChatActivity(args), true, true);
} else {
fragment.presentFragment(new ChatActivity(args), closeLast);
}
@ -7950,9 +8206,24 @@ public class MessagesController implements NotificationCenter.NotificationCenter
if (username == null || fragment == null) {
return;
}
TLRPC.User user = getInstance().getUser(username);
TLObject object = getInstance().getUserOrChat(username);
TLRPC.User user = null;
TLRPC.Chat chat = null;
if (object instanceof TLRPC.User) {
user = (TLRPC.User) object;
if (user.min) {
user = null;
}
} else if (object instanceof TLRPC.Chat) {
chat = (TLRPC.Chat) object;
if (chat.min) {
chat = null;
}
}
if (user != null) {
openChatOrProfileWith(user, null, fragment, type, false);
} else if (chat != null) {
openChatOrProfileWith(null, chat, fragment, 1, false);
} else {
if (fragment.getParentActivity() == null) {
return;

View File

@ -4008,7 +4008,8 @@ public class MessagesStorage {
if (message instanceof TLRPC.TL_message_secret && (
message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl > 0 && message.ttl <= 60 ||
MessageObject.isVoiceMessage(message) ||
MessageObject.isVideoMessage(message))) {
MessageObject.isVideoMessage(message) ||
MessageObject.isRoundVideoMessage(message))) {
return 1;
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto || MessageObject.isVideoMessage(message)) {
return 0;
@ -4437,13 +4438,21 @@ public class MessagesStorage {
long id = 0;
TLRPC.MessageMedia object = null;
if (MessageObject.isVoiceMessage(message)) {
if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0 && message.media.document.size < 1024 * 1024 * 5) {
if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0 && message.media.document.size < 1024 * 1024 * 2) {
id = message.media.document.id;
type = MediaController.AUTODOWNLOAD_MASK_AUDIO;
object = new TLRPC.TL_messageMediaDocument();
object.caption = "";
object.document = message.media.document;
}
} else if (MessageObject.isRoundVideoMessage(message)) {
if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE) != 0 && message.media.document.size < 1024 * 1024 * 5) {
id = message.media.document.id;
type = MediaController.AUTODOWNLOAD_MASK_VIDEOMESSAGE;
object = new TLRPC.TL_messageMediaDocument();
object.caption = "";
object.document = message.media.document;
}
} else if (message.media instanceof TLRPC.TL_messageMediaPhoto) {
if ((downloadMask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0) {
TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.media.photo.sizes, AndroidUtilities.getPhotoSize());
@ -6056,36 +6065,9 @@ public class MessagesStorage {
}
cursor.dispose();
if (!unpinnedDialogs.isEmpty()) {
int minDate = 0;
cursor = database.queryFinalized("SELECT min(date), min(date_i) FROM dialogs WHERE (date != 0 OR date_i != 0) AND pinned = 0");
if (cursor.next()) {
int date = cursor.intValue(0);
int date_i = cursor.intValue(1);
if (date != 0 && date_i != 0) {
minDate = Math.min(date, date_i);
} else if (date == 0) {
minDate = date_i;
} else {
minDate = date;
}
}
cursor.dispose();
SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?");
for (int a = 0; a < unpinnedDialogs.size(); a++) {
long did = unpinnedDialogs.get(a);
int dialogDate = 0;
cursor = database.queryFinalized("SELECT date FROM dialogs WHERE did = " + did);
if (cursor.next()) {
dialogDate = cursor.intValue(0);
}
cursor.dispose();
if (dialogDate <= minDate) {
database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose();
continue;
}
state.requery();
state.bindInteger(1, 0);
state.bindLong(2, did);
@ -6105,24 +6087,6 @@ public class MessagesStorage {
@Override
public void run() {
try {
if (pinned == 0 && (int) did != 0) {
int dialogDate = 0;
int minDate = 0;
SQLiteCursor cursor = database.queryFinalized("SELECT date FROM dialogs WHERE did = " + did);
if (cursor.next()) {
dialogDate = cursor.intValue(0);
}
cursor.dispose();
cursor = database.queryFinalized("SELECT min(date) FROM dialogs WHERE date != 0 AND pinned = 0");
if (cursor.next()) {
minDate = cursor.intValue(0);
}
cursor.dispose();
if (dialogDate <= minDate) {
database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose();
return;
}
}
SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?");
state.bindInteger(1, pinned);
state.bindLong(2, did);

View File

@ -108,9 +108,9 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
updatePlaybackState(null);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidStarted);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingDidReset);
}
@Override
@ -363,7 +363,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
if (messageObject == null) {
onPlayFromMediaId(lastSelectedDialog + "_" + 0, null);
} else {
MediaController.getInstance().playAudio(messageObject);
MediaController.getInstance().playMessage(messageObject);
}
}
@ -486,7 +486,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
if (MediaController.getInstance().isDownloadingCurrentMessage()) {
state = PlaybackState.STATE_BUFFERING;
} else {
state = MediaController.getInstance().isAudioPaused() ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING;
state = MediaController.getInstance().isMessagePaused() ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING;
}
}
@ -508,7 +508,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_PLAY_FROM_SEARCH;
MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject();
if (playingMessageObject != null) {
if (!MediaController.getInstance().isAudioPaused()) {
if (!MediaController.getInstance().isMessagePaused()) {
actions |= PlaybackState.ACTION_PAUSE;
}
actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
@ -523,9 +523,9 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
updatePlaybackState(withError);
stopSelf();
serviceStarted = false;
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidStarted);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingDidReset);
}
private void handlePlayRequest() {
@ -558,7 +558,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
}
private void handlePauseRequest() {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
delayedStopHandler.removeCallbacksAndMessages(null);
delayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
}
@ -581,7 +581,7 @@ public class MusicBrowserService extends MediaBrowserService implements Notifica
MusicBrowserService service = mWeakReference.get();
if (service != null) {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject != null && !MediaController.getInstance().isAudioPaused()) {
if (messageObject != null && !MediaController.getInstance().isMessagePaused()) {
return;
}
service.stopSelf();

View File

@ -31,17 +31,17 @@ public class MusicPlayerReceiver extends BroadcastReceiver {
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
if (MediaController.getInstance().isMessagePaused()) {
MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject());
} else {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
}
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
break;
@ -54,9 +54,9 @@ public class MusicPlayerReceiver extends BroadcastReceiver {
}
} else {
if (intent.getAction().equals(MusicPlayerService.NOTIFY_PLAY)) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().playMessage(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_PAUSE) || intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_NEXT)) {
MediaController.getInstance().playNextMessage();
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_CLOSE)) {

View File

@ -49,8 +49,8 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
@Override
public void onCreate() {
audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingProgressDidChanged);
NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
super.onCreate();
}
@ -159,7 +159,7 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
notification.bigContentView.setViewVisibility(R.id.player_progress_bar, View.GONE);
}
if (MediaController.getInstance().isAudioPaused()) {
if (MediaController.getInstance().isMessagePaused()) {
notification.contentView.setViewVisibility(R.id.player_pause, View.GONE);
notification.contentView.setViewVisibility(R.id.player_play, View.VISIBLE);
if (supportBigNotifications) {
@ -223,13 +223,13 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
metadataEditor.apply();
audioManager.unregisterRemoteControlClient(remoteControlClient);
}
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioProgressDidChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingProgressDidChanged);
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
}
@Override
public void didReceivedNotification(int id, Object... args) {
if (id == NotificationCenter.audioPlayStateChanged) {
if (id == NotificationCenter.messagePlayingPlayStateChanged) {
MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
if (messageObject != null) {
createNotification(messageObject);

View File

@ -23,7 +23,7 @@ import java.util.zip.ZipFile;
public class NativeLoader {
private final static int LIB_VERSION = 26;
private final static int LIB_VERSION = 27;
private final static String LIB_NAME = "tmessages." + LIB_VERSION;
private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so";
private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.so";

View File

@ -82,6 +82,10 @@ public class NotificationCenter {
public static final int didSetNewWallpapper = totalEvents++;
public static final int archivedStickersCountDidLoaded = totalEvents++;
public static final int paymentFinished = totalEvents++;
public static final int reloadInterface = totalEvents++;
public static final int suggestedLangpack = totalEvents++;
public static final int channelRightsUpdated = totalEvents++;
public static final int proxySettingsChanged = totalEvents++;
public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++;
@ -106,9 +110,10 @@ public class NotificationCenter {
public static final int FileNewChunkAvailable = totalEvents++;
public static final int FilePreparingFailed = totalEvents++;
public static final int audioProgressDidChanged = totalEvents++;
public static final int audioDidReset = totalEvents++;
public static final int audioPlayStateChanged = totalEvents++;
public static final int messagePlayingProgressDidChanged = totalEvents++;
public static final int messagePlayingDidReset = totalEvents++;
public static final int messagePlayingPlayStateChanged = totalEvents++;
public static final int messagePlayingDidStarted = totalEvents++;
public static final int recordProgressChanged = totalEvents++;
public static final int recordStarted = totalEvents++;
public static final int recordStartError = totalEvents++;
@ -116,7 +121,6 @@ public class NotificationCenter {
public static final int screenshotTook = totalEvents++;
public static final int albumsDidLoaded = totalEvents++;
public static final int audioDidSent = totalEvents++;
public static final int audioDidStarted = totalEvents++;
public static final int audioRouteChanged = totalEvents++;
public static final int didStartedCall = totalEvents++;

View File

@ -928,6 +928,8 @@ public class NotificationsController {
msg = LocaleController.formatString("NotificationMessageGame", R.string.NotificationMessageGame, name, messageObject.messageOwner.media.game.title);
} else if (messageObject.isVoice()) {
msg = LocaleController.formatString("NotificationMessageAudio", R.string.NotificationMessageAudio, name);
} else if (messageObject.isRoundVideo()) {
msg = LocaleController.formatString("NotificationMessageRound", R.string.NotificationMessageRound, name);
} else if (messageObject.isMusic()) {
msg = LocaleController.formatString("NotificationMessageMusic", R.string.NotificationMessageMusic, name);
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
@ -1086,6 +1088,12 @@ public class NotificationsController {
} else {
msg = LocaleController.formatString("NotificationActionPinnedVoiceChannel", R.string.NotificationActionPinnedVoiceChannel, chat.title);
}
} else if (object.isRoundVideo()) {
if (!ChatObject.isChannel(chat) || chat.megagroup) {
msg = LocaleController.formatString("NotificationActionPinnedRound", R.string.NotificationActionPinnedRound, name, chat.title);
} else {
msg = LocaleController.formatString("NotificationActionPinnedRoundChannel", R.string.NotificationActionPinnedRoundChannel, chat.title);
}
} else if (object.isSticker()) {
String emoji = messageObject.getStickerEmoji();
if (emoji != null) {
@ -1192,6 +1200,8 @@ public class NotificationsController {
}
} else if (messageObject.isVoice()) {
msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name);
} else if (messageObject.isRoundVideo()) {
msg = LocaleController.formatString("ChannelMessageRound", R.string.ChannelMessageRound, name);
} else if (messageObject.isMusic()) {
msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name);
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
@ -1241,6 +1251,8 @@ public class NotificationsController {
}
} else if (messageObject.isVoice()) {
msg = LocaleController.formatString("ChannelMessageGroupAudio", R.string.ChannelMessageGroupAudio, name, chat.title);
} else if (messageObject.isRoundVideo()) {
msg = LocaleController.formatString("ChannelMessageGroupRound", R.string.ChannelMessageGroupRound, name, chat.title);
} else if (messageObject.isMusic()) {
msg = LocaleController.formatString("ChannelMessageGroupMusic", R.string.ChannelMessageGroupMusic, name, chat.title);
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {
@ -1291,6 +1303,8 @@ public class NotificationsController {
}
} else if (messageObject.isVoice()) {
msg = LocaleController.formatString("NotificationMessageGroupAudio", R.string.NotificationMessageGroupAudio, name, chat.title);
} else if (messageObject.isRoundVideo()) {
msg = LocaleController.formatString("NotificationMessageGroupRound", R.string.NotificationMessageGroupRound, name, chat.title);
} else if (messageObject.isMusic()) {
msg = LocaleController.formatString("NotificationMessageGroupMusic", R.string.NotificationMessageGroupMusic, name, chat.title);
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) {

View File

@ -65,7 +65,7 @@ public class SecretChatHelper {
}
}
public static final int CURRENT_SECRET_CHAT_LAYER = 46;
public static final int CURRENT_SECRET_CHAT_LAYER = 66;
private ArrayList<Integer> sendingNotifyLayer = new ArrayList<>();
private HashMap<Integer, ArrayList<TL_decryptedMessageHolder>> secretHolesQueue = new HashMap<>();
@ -192,13 +192,12 @@ public class SecretChatHelper {
dialog.unread_count = 0;
dialog.top_message = 0;
dialog.last_message_date = update.date;
MessagesController.getInstance().putEncryptedChat(newChat, false);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().dialogs_dict.put(dialog.id, dialog);
MessagesController.getInstance().dialogs.add(dialog);
MessagesController.getInstance().putEncryptedChat(newChat, false);
MessagesController.getInstance().sortDialogs(null);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
}
@ -519,22 +518,9 @@ public class SecretChatHelper {
}
}
/*String fileName = audio.dc_id + "_" + audio.id + ".ogg"; TODO check
String fileName2 = newMsg.media.audio.dc_id + "_" + newMsg.media.audio.id + ".ogg";
if (!fileName.equals(fileName2)) {
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
File cacheFile2 = FileLoader.getPathToAttach(newMsg.media.audio);
if (cacheFile.renameTo(cacheFile2)) {
newMsg.attachPath = "";
}
}*/
ArrayList<TLRPC.Message> arr = new ArrayList<>();
arr.add(newMsg);
MessagesStorage.getInstance().putMessages(arr, false, true, false, 0);
//MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.document, 4); document
//MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.video, 5); video
}
}
}
@ -569,13 +555,14 @@ public class SecretChatHelper {
if (chat.seq_in == 0 && chat.seq_out == 0) {
if (chat.admin_id == UserConfig.getClientUserId()) {
chat.seq_out = 1;
chat.seq_in = -2;
} else {
chat.seq_in = 1;
chat.seq_in = -1;
}
}
if (newMsgObj.seq_in == 0 && newMsgObj.seq_out == 0) {
layer.in_seq_no = chat.seq_in;
layer.in_seq_no = chat.seq_in > 0 ? chat.seq_in : chat.seq_in + 2;
layer.out_seq_no = chat.seq_out;
chat.seq_out += 2;
if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) {
@ -719,7 +706,7 @@ public class SecretChatHelper {
newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, newMsgObj.id, newMsgObj.id, newMsgObj, newMsgObj.dialog_id);
SendMessagesHelper.getInstance().processSentMessage(newMsgObj.id);
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj)) {
SendMessagesHelper.getInstance().stopVideoService(attachPath);
}
SendMessagesHelper.getInstance().removeFromSendingMessages(newMsgObj.id);
@ -735,7 +722,7 @@ public class SecretChatHelper {
newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id);
SendMessagesHelper.getInstance().processSentMessage(newMsgObj.id);
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj)) {
SendMessagesHelper.getInstance().stopVideoService(newMsgObj.attachPath);
}
SendMessagesHelper.getInstance().removeFromSendingMessages(newMsgObj.id);
@ -964,7 +951,7 @@ public class SecretChatHelper {
newMessage.media.document.thumb.type = "s";
}
newMessage.media.document.dc_id = file.dc_id;
if (MessageObject.isVoiceMessage(newMessage)) {
if (MessageObject.isVoiceMessage(newMessage) || MessageObject.isRoundVideoMessage(newMessage)) {
newMessage.media_unread = true;
}
} else if (decryptedMessage.media instanceof TLRPC.TL_decryptedMessageMediaExternalDocument) {
@ -1456,8 +1443,9 @@ public class SecretChatHelper {
if (chat.seq_in == 0 && chat.seq_out == 0) {
if (chat.admin_id == UserConfig.getClientUserId()) {
chat.seq_out = 1;
chat.seq_in = -2;
} else {
chat.seq_in = 1;
chat.seq_in = -1;
}
}
if (layer.random_bytes.length < 15) {
@ -1466,7 +1454,7 @@ public class SecretChatHelper {
}
FileLog.e("current chat in_seq = " + chat.seq_in + " out_seq = " + chat.seq_out);
FileLog.e("got message with in_seq = " + layer.in_seq_no + " out_seq = " + layer.out_seq_no);
if (layer.out_seq_no < chat.seq_in) {
if (layer.out_seq_no <= chat.seq_in) {
return null;
}
if (chat.seq_in != layer.out_seq_no && chat.seq_in != layer.out_seq_no - 2) {
@ -1590,13 +1578,13 @@ public class SecretChatHelper {
if (encryptedChat.key_fingerprint == fingerprint) {
encryptedChat.auth_key = authKey;
encryptedChat.key_create_date = ConnectionsManager.getInstance().getCurrentTime();
encryptedChat.seq_in = 0;
encryptedChat.seq_in = -2;
encryptedChat.seq_out = 1;
MessagesStorage.getInstance().updateEncryptedChat(encryptedChat);
MessagesController.getInstance().putEncryptedChat(encryptedChat, false);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().putEncryptedChat(encryptedChat, false);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.encryptedChatUpdated, encryptedChat);
sendNotifyLayerMessage(encryptedChat, null);
}
@ -1665,7 +1653,7 @@ public class SecretChatHelper {
salt[a] = (byte) ((byte) (Utilities.random.nextDouble() * 256) ^ res.random[a]);
}
encryptedChat.a_or_b = salt;
encryptedChat.seq_in = 1;
encryptedChat.seq_in = -1;
encryptedChat.seq_out = 0;
BigInteger p = new BigInteger(1, MessagesStorage.secretPBytes);
BigInteger g_b = BigInteger.valueOf(MessagesStorage.secretG);
@ -1726,10 +1714,10 @@ public class SecretChatHelper {
newChat.key_use_count_in = encryptedChat.key_use_count_in;
newChat.key_use_count_out = encryptedChat.key_use_count_out;
MessagesStorage.getInstance().updateEncryptedChat(newChat);
MessagesController.getInstance().putEncryptedChat(newChat, false);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().putEncryptedChat(newChat, false);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.encryptedChatUpdated, newChat);
sendNotifyLayerMessage(newChat, null);
}
@ -1817,7 +1805,7 @@ public class SecretChatHelper {
}
TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) response;
chat.user_id = chat.participant_id;
chat.seq_in = 0;
chat.seq_in = -2;
chat.seq_out = 1;
chat.a_or_b = salt;
MessagesController.getInstance().putEncryptedChat(chat, false);

View File

@ -12,6 +12,7 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
@ -645,7 +646,23 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
webPage = messageObject.messageOwner.media.webpage;
}
sendMessage(messageObject.messageOwner.message, did, messageObject.replyMessageObject, webPage, true, messageObject.messageOwner.entities, null, null);
ArrayList<TLRPC.MessageEntity> entities;
if (messageObject.messageOwner.entities != null && !messageObject.messageOwner.entities.isEmpty()) {
entities = new ArrayList<>();
for (int a = 0; a < messageObject.messageOwner.entities.size(); a++) {
TLRPC.MessageEntity entity = messageObject.messageOwner.entities.get(a);
if (entity instanceof TLRPC.TL_messageEntityBold ||
entity instanceof TLRPC.TL_messageEntityItalic ||
entity instanceof TLRPC.TL_messageEntityPre ||
entity instanceof TLRPC.TL_messageEntityCode ||
entity instanceof TLRPC.TL_messageEntityTextUrl) {
entities.add(entity);
}
}
} else {
entities = null;
}
sendMessage(messageObject.messageOwner.message, did, messageObject.replyMessageObject, webPage, true, entities, null, null);
} else if ((int) did != 0) {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
@ -704,25 +721,34 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) document, null, null, peer, replyingMessageObject, null, null);
}
public void sendMessage(ArrayList<MessageObject> messages, final long peer) {
public int sendMessage(ArrayList<MessageObject> messages, final long peer) {
if (messages == null || messages.isEmpty()) {
return;
return 0;
}
int lower_id = (int) peer;
int sendResult = 0;
if (lower_id != 0) {
final TLRPC.Peer to_id = MessagesController.getPeer((int) peer);
boolean isMegagroup = false;
boolean isSignature = false;
boolean canSendStickers = true;
boolean canSendMedia = true;
boolean canSendPreview = true;
if (lower_id > 0) {
TLRPC.User sendToUser = MessagesController.getInstance().getUser(lower_id);
if (sendToUser == null) {
return;
return 0;
}
} else {
TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id);
if (ChatObject.isChannel(chat)) {
isMegagroup = chat.megagroup;
isSignature = chat.signatures;
if (chat.banned_rights != null) {
canSendStickers = !chat.banned_rights.send_stickers;
canSendMedia = !chat.banned_rights.send_media;
canSendPreview = !chat.banned_rights.embed_links;
}
}
}
@ -733,17 +759,30 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
HashMap<Long, TLRPC.Message> messagesByRandomIds = new HashMap<>();
TLRPC.InputPeer inputPeer = MessagesController.getInputPeer(lower_id);
long lastDialogId = 0;
final boolean toMyself = peer == UserConfig.getClientUserId();
int myId = UserConfig.getClientUserId();
final boolean toMyself = peer == myId;
for (int a = 0; a < messages.size(); a++) {
MessageObject msgObj = messages.get(a);
if (msgObj.getId() <= 0) {
continue;
}
if (!canSendStickers && (msgObj.isSticker() || msgObj.isGif() || msgObj.isGame())) {
if (sendResult == 0) {
sendResult = 1;
}
continue;
} else if (!canSendMedia && (msgObj.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto || msgObj.messageOwner.media instanceof TLRPC.TL_messageMediaDocument)) {
if (sendResult == 0) {
sendResult = 2;
}
continue;
}
final TLRPC.Message newMsg = new TLRPC.TL_message();
if (msgObj.isForwarded()) {
newMsg.fwd_from = msgObj.messageOwner.fwd_from;
} else {
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
} else if (msgObj.getDialogId() != myId) {
newMsg.fwd_from = new TLRPC.TL_messageFwdHeader();
if (msgObj.isFromUser()) {
newMsg.fwd_from.from_id = msgObj.messageOwner.from_id;
@ -761,9 +800,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
newMsg.date = msgObj.messageOwner.date;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
}
if (!canSendPreview && msgObj.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
newMsg.media = new TLRPC.TL_messageMediaEmpty();
} else {
newMsg.media = msgObj.messageOwner.media;
}
newMsg.media = msgObj.messageOwner.media;
newMsg.flags = TLRPC.MESSAGE_FLAG_FWD;
if (newMsg.media != null) {
newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA;
}
@ -816,7 +859,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
newMsg.dialog_id = peer;
newMsg.to_id = to_id;
if (MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) {
if ((MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) && newMsg.to_id.channel_id == 0) {
newMsg.media_unread = true;
}
if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) {
@ -939,7 +982,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
removeFromSendingMessages(oldId);
}
});
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
stopVideoService(newMsgObj.attachPath);
}
}
@ -965,7 +1008,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id);
processSentMessage(newMsgObj.id);
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
stopVideoService(newMsgObj.attachPath);
}
removeFromSendingMessages(newMsgObj.id);
@ -989,6 +1032,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
processForwardFromMyName(messages.get(a), peer);
}
}
return sendResult;
}
public int editMessage(MessageObject messageObject, String message, boolean searchLinks, final BaseFragment fragment, ArrayList<TLRPC.MessageEntity> entities, final Runnable callback) {
@ -1323,7 +1367,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
} else if (retryMessageObject.type == 1) {
photo = (TLRPC.TL_photo) newMsg.media.photo;
type = 2;
} else if (retryMessageObject.type == 3 || videoEditedInfo != null) {
} else if (retryMessageObject.type == 3 || retryMessageObject.type == 5 || videoEditedInfo != null) {
type = 3;
document = (TLRPC.TL_document) newMsg.media.document;
} else if (retryMessageObject.type == 12) {
@ -1452,7 +1496,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
newMsg.media.document = document;
if (params != null && params.containsKey("query_id")) {
type = 9;
} else if (MessageObject.isVideoDocument(document) || videoEditedInfo != null) {
} else if (MessageObject.isVideoDocument(document) || MessageObject.isRoundVideoDocument(document) || videoEditedInfo != null) {
type = 3;
} else if (MessageObject.isVoiceDocument(document)) {
type = 8;
@ -1614,7 +1658,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
newMsg.ttl = Math.max(encryptedChat.ttl, duration + 1);
} else if (MessageObject.isVideoMessage(newMsg)) {
} else if (MessageObject.isVideoMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) {
int duration = 0;
for (int a = 0; a < newMsg.media.document.attributes.size(); a++) {
TLRPC.DocumentAttribute attribute = newMsg.media.document.attributes.get(a);
@ -1627,14 +1671,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
}
if (high_id != 1 && MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) {
if (high_id != 1 && (MessageObject.isVoiceMessage(newMsg) || MessageObject.isRoundVideoMessage(newMsg)) && newMsg.to_id.channel_id == 0) {
newMsg.media_unread = true;
}
newMsg.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING;
newMsgObj = new MessageObject(newMsg, null, true);
newMsgObj.replyMessageObject = reply_to_msg;
if (!newMsgObj.isForwarded() && (newMsgObj.type == 3 || videoEditedInfo != null) && !TextUtils.isEmpty(newMsg.attachPath)) {
if (!newMsgObj.isForwarded() && (newMsgObj.type == 3 || videoEditedInfo != null || newMsgObj.type == 2) && !TextUtils.isEmpty(newMsg.attachPath)) {
newMsgObj.attachPathExists = true;
}
@ -1988,7 +2032,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
} else if (type == 3) {
ImageLoader.fillPhotoSizeWithBytes(document.thumb);
if (MessageObject.isNewGifDocument(document)) {
if (MessageObject.isNewGifDocument(document) || MessageObject.isRoundVideoDocument(document)) {
reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument();
reqSend.media.attributes = document.attributes;
if (document.thumb != null && document.thumb.bytes != null) {
@ -2210,7 +2254,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
} else if (message.type == 1) {
if (message.videoEditedInfo != null) {
if (message.videoEditedInfo != null && message.videoEditedInfo.needConvert()) {
String location = message.obj.messageOwner.attachPath;
if (location == null) {
location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4";
@ -2218,6 +2262,25 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
putToDelayedMessages(location, message);
MediaController.getInstance().scheduleVideoConvert(message.obj);
} else {
if (message.videoEditedInfo != null) {
if (message.videoEditedInfo.file != null) {
TLRPC.InputMedia media;
if (message.sendRequest instanceof TLRPC.TL_messages_sendMedia) {
media = ((TLRPC.TL_messages_sendMedia) message.sendRequest).media;
} else {
media = ((TLRPC.TL_messages_sendBroadcast) message.sendRequest).media;
}
media.file = message.videoEditedInfo.file;
message.videoEditedInfo.file = null;
} else if (message.videoEditedInfo.encryptedFile != null) {
message.sendEncryptedRequest.media.size = (int) message.videoEditedInfo.estimatedSize;
message.sendEncryptedRequest.media.key = message.videoEditedInfo.key;
message.sendEncryptedRequest.media.iv = message.videoEditedInfo.iv;
SecretChatHelper.getInstance().performSendEncryptedRequest(message.sendEncryptedRequest, message.obj.messageOwner, message.encryptedChat, message.videoEditedInfo.encryptedFile, message.originalPath, message.obj);
message.videoEditedInfo.encryptedFile = null;
return;
}
}
if (message.sendRequest != null) {
TLRPC.InputMedia media;
if (message.sendRequest instanceof TLRPC.TL_messages_sendMedia) {
@ -2231,7 +2294,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4";
}
putToDelayedMessages(location, message);
if (message.obj.videoEditedInfo != null) {
if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) {
FileLoader.getInstance().uploadFile(location, false, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo);
} else {
FileLoader.getInstance().uploadFile(location, false, false, ConnectionsManager.FileTypeVideo);
@ -2255,7 +2318,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
putToDelayedMessages(location, message);
if (message.obj.videoEditedInfo != null) {
if (message.obj.videoEditedInfo != null && message.obj.videoEditedInfo.needConvert()) {
FileLoader.getInstance().uploadFile(location, true, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo);
} else {
FileLoader.getInstance().uploadFile(location, true, false, ConnectionsManager.FileTypeVideo);
@ -2426,7 +2489,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (!isSentError) {
StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1);
newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id); //TODO remove later?
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id);
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
@Override
public void run() {
@ -2456,7 +2519,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
removeFromSendingMessages(oldId);
}
});
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
stopVideoService(attachPath);
}
}
@ -2471,7 +2534,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id);
processSentMessage(newMsgObj.id);
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isRoundVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) {
stopVideoService(newMsgObj.attachPath);
}
removeFromSendingMessages(newMsgObj.id);
@ -2545,7 +2608,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (MessageObject.isVideoMessage(sentMessage)) {
MessagesStorage.getInstance().putSentFile(originalPath, sentMessage.media.document, 2);
sentMessage.attachPath = newMsg.attachPath;
} else if (!MessageObject.isVoiceMessage(sentMessage)) {
} else if (!MessageObject.isVoiceMessage(sentMessage) && !MessageObject.isRoundVideoMessage(sentMessage)) {
MessagesStorage.getInstance().putSentFile(originalPath, sentMessage.media.document, 1);
}
@ -2704,7 +2767,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
private static boolean prepareSendingDocumentInternal(String path, String originalPath, Uri uri, String mime, final long dialog_id, final MessageObject reply_to_msg, String caption) {
private static boolean prepareSendingDocumentInternal(String path, String originalPath, Uri uri, String mime, final long dialog_id, final MessageObject reply_to_msg, CharSequence caption) {
if ((path == null || path.length() == 0) && uri == null) {
return false;
}
@ -2750,16 +2813,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (ext.toLowerCase().equals("mp3") || ext.toLowerCase().equals("m4a")) {
AudioInfo audioInfo = AudioInfo.getAudioInfo(f);
if (audioInfo != null && audioInfo.getDuration() != 0) {
if (isEncrypted) {
int high_id = (int) (dialog_id >> 32);
TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id);
if (encryptedChat == null) {
return false;
}
attributeAudio = new TLRPC.TL_documentAttributeAudio();
} else {
attributeAudio = new TLRPC.TL_documentAttributeAudio();
}
attributeAudio = new TLRPC.TL_documentAttributeAudio();
attributeAudio.duration = (int) (audioInfo.getDuration() / 1000);
attributeAudio.title = audioInfo.getTitle();
attributeAudio.performer = audioInfo.getArtist();
@ -2856,7 +2910,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
document.thumb.type = "s";
}
}
document.caption = caption;
if (caption != null) {
document.caption = caption.toString();
} else {
document.caption = "";
}
final HashMap<String, String> params = new HashMap<>();
if (originalPath != null) {
@ -3003,7 +3061,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
masks = new ArrayList<>();
masks.add(new ArrayList<>(stickers));
}
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, masks, inputContent);
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, masks, inputContent, false);
}
public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap<String, String> params, final long dialog_id, final MessageObject reply_to_msg) {
@ -3459,7 +3517,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
});
}
public static void prepareSendingPhotos(ArrayList<String> paths, ArrayList<Uri> uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList<String> captions, final ArrayList<ArrayList<TLRPC.InputDocument>> masks, final InputContentInfoCompat inputContent) {
public static void prepareSendingPhotos(ArrayList<String> paths, ArrayList<Uri> uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList<String> captions, final ArrayList<ArrayList<TLRPC.InputDocument>> masks, final InputContentInfoCompat inputContent, final boolean forceDocument) {
if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) {
return;
}
@ -3498,7 +3556,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
boolean isDocument = false;
if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) {
if (forceDocument) {
isDocument = true;
extension = FileLoader.getFileExtension(new File(tempPath));
} else if (tempPath != null && (tempPath.endsWith(".gif") || tempPath.endsWith(".webp"))) {
if (tempPath.endsWith(".gif")) {
extension = "gif";
} else {
@ -3519,6 +3580,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
if (isDocument) {
if (sendAsDocuments == null) {
sendAsDocuments = new ArrayList<>();
@ -3654,7 +3716,38 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
public static void prepareSendingVideo(final String videoPath, final long estimatedSize, final long duration, final int width, final int height, final VideoEditedInfo videoEditedInfo, final long dialog_id, final MessageObject reply_to_msg, final String caption) {
private static Bitmap createVideoThumbnail(String filePath, long time) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(filePath);
bitmap = retriever.getFrameAtTime(time, MediaMetadataRetriever.OPTION_NEXT_SYNC);
} catch (Exception ignore) {
// Assume this is a corrupt video file.
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
// Ignore failures while cleaning up.
}
}
if (bitmap == null) return null;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int max = Math.max(width, height);
if (max > 90) {
float scale = 90.0f / max;
int w = Math.round(scale * width);
int h = Math.round(scale * height);
bitmap = Bitmaps.createScaledBitmap(bitmap, w, h, true);
}
return bitmap;
}
public static void prepareSendingVideo(final String videoPath, final long estimatedSize, final long duration, final int width, final int height, final VideoEditedInfo videoEditedInfo, final long dialog_id, final MessageObject reply_to_msg, final CharSequence caption) {
if (videoPath == null || videoPath.length() == 0) {
return;
}
@ -3664,24 +3757,49 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
boolean isEncrypted = (int) dialog_id == 0;
if (videoEditedInfo != null || videoPath.endsWith("mp4")) {
boolean isRound = videoEditedInfo != null && videoEditedInfo.roundVideo;
Bitmap thumb = null;
String thumbKey = null;
if (videoEditedInfo != null || videoPath.endsWith("mp4") || isRound) {
String path = videoPath;
String originalPath = videoPath;
File temp = new File(originalPath);
long startTime = 0;
originalPath += temp.length() + "_" + temp.lastModified();
if (videoEditedInfo != null) {
originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime;
if (videoEditedInfo.resultWidth == videoEditedInfo.originalWidth) {
originalPath += "_" + videoEditedInfo.resultWidth;
if (!isRound) {
originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime;
if (videoEditedInfo.resultWidth == videoEditedInfo.originalWidth) {
originalPath += "_" + videoEditedInfo.resultWidth;
}
}
startTime = videoEditedInfo.startTime >= 0 ? videoEditedInfo.startTime : 0;
}
TLRPC.TL_document document = null;
if (!isEncrypted) {
//document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5);
document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5);
}
if (document == null) {
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND);
thumb = createVideoThumbnail(videoPath, startTime);
if (thumb == null) {
thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND);
}
TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(thumb, 90, 90, 55, isEncrypted);
if (thumb != null && size !=null) {
if (isRound) {
if (isEncrypted) {
Utilities.blurBitmap(thumb, 7, Build.VERSION.SDK_INT < 21 ? 0 : 1, thumb.getWidth(), thumb.getHeight(), thumb.getRowBytes());
thumbKey = String.format(size.location.volume_id + "_" + size.location.local_id + "@%d_%d_b2", (int) (AndroidUtilities.roundMessageSize / AndroidUtilities.density), (int) (AndroidUtilities.roundMessageSize / AndroidUtilities.density));
} else {
Utilities.blurBitmap(thumb, 3, Build.VERSION.SDK_INT < 21 ? 0 : 1, thumb.getWidth(), thumb.getHeight(), thumb.getRowBytes());
thumbKey = String.format(size.location.volume_id + "_" + size.location.local_id + "@%d_%d_b", (int) (AndroidUtilities.roundMessageSize / AndroidUtilities.density), (int) (AndroidUtilities.roundMessageSize / AndroidUtilities.density));
}
} else {
thumb = null;
}
}
document = new TLRPC.TL_document();
document.thumb = size;
if (document.thumb == null) {
@ -3692,14 +3810,31 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
document.mime_type = "video/mp4";
UserConfig.saveConfig(false);
TLRPC.TL_documentAttributeVideo attributeVideo = new TLRPC.TL_documentAttributeVideo();
TLRPC.TL_documentAttributeVideo attributeVideo;
if (isEncrypted) {
int high_id = (int) (dialog_id >> 32);
TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id);
if (encryptedChat == null) {
return;
}
if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 66) {
attributeVideo = new TLRPC.TL_documentAttributeVideo();
} else {
attributeVideo = new TLRPC.TL_documentAttributeVideo_layer65();
}
} else {
attributeVideo = new TLRPC.TL_documentAttributeVideo();
}
attributeVideo.round_message = isRound;
document.attributes.add(attributeVideo);
if (videoEditedInfo != null) {
if (videoEditedInfo.bitrate == -1) {
if (videoEditedInfo != null && videoEditedInfo.needConvert()) {
if (videoEditedInfo.muted) {
document.attributes.add(new TLRPC.TL_documentAttributeAnimated());
fillVideoAttribute(videoPath, attributeVideo, videoEditedInfo);
videoEditedInfo.originalWidth = videoEditedInfo.resultWidth = attributeVideo.w;
videoEditedInfo.originalHeight = videoEditedInfo.resultHeight = attributeVideo.h;
videoEditedInfo.originalWidth = attributeVideo.w;
videoEditedInfo.originalHeight = attributeVideo.h;
attributeVideo.w = videoEditedInfo.resultWidth;
attributeVideo.h = videoEditedInfo.resultHeight;
} else {
attributeVideo.duration = (int) (duration / 1000);
if (videoEditedInfo.rotationValue == 90 || videoEditedInfo.rotationValue == 270) {
@ -3727,13 +3862,22 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
final String originalPathFinal = originalPath;
final String finalPath = path;
final HashMap<String, String> params = new HashMap<>();
videoFinal.caption = caption;
final Bitmap thumbFinal = thumb;
final String thumbKeyFinal = thumbKey;
if (caption != null) {
videoFinal.caption = caption.toString();
} else {
videoFinal.caption = "";
}
if (originalPath != null) {
params.put("originalPath", originalPath);
}
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
if (thumbFinal != null && thumbKeyFinal != null) {
ImageLoader.getInstance().putImageToCache(new BitmapDrawable(thumbFinal), thumbKeyFinal);
}
SendMessagesHelper.getInstance().sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, null, params);
}
});

View File

@ -38,6 +38,7 @@ public class UserConfig {
public static int autoLockIn = 60 * 60;
public static boolean allowScreenCapture;
public static int lastPauseTime;
public static long lastAppPauseTime;
public static boolean isWaitingForPasscodeEnter;
public static boolean useFingerprint = true;
public static String lastUpdateVersion;
@ -55,6 +56,14 @@ public class UserConfig {
public static int migrateOffsetChannelId = -1;
public static long migrateOffsetAccess = -1;
public static int totalDialogsLoadCount = 0;
public static int dialogsLoadOffsetId = 0;
public static int dialogsLoadOffsetDate = 0;
public static int dialogsLoadOffsetUserId = 0;
public static int dialogsLoadOffsetChatId = 0;
public static int dialogsLoadOffsetChannelId = 0;
public static long dialogsLoadOffsetAccess = 0;
public static int getNewMessageId() {
int id;
synchronized (sync) {
@ -87,6 +96,7 @@ public class UserConfig {
editor.putInt("passcodeType", passcodeType);
editor.putInt("autoLockIn", autoLockIn);
editor.putInt("lastPauseTime", lastPauseTime);
editor.putLong("lastAppPauseTime", lastAppPauseTime);
editor.putString("lastUpdateVersion2", lastUpdateVersion);
editor.putInt("lastContactsSyncTime", lastContactsSyncTime);
editor.putBoolean("useFingerprint", useFingerprint);
@ -96,14 +106,24 @@ public class UserConfig {
editor.putBoolean("allowScreenCapture", allowScreenCapture);
editor.putBoolean("pinnedDialogsLoaded", pinnedDialogsLoaded);
editor.putInt("migrateOffsetId", migrateOffsetId);
editor.putInt("3migrateOffsetId", migrateOffsetId);
if (migrateOffsetId != -1) {
editor.putInt("migrateOffsetDate", migrateOffsetDate);
editor.putInt("migrateOffsetUserId", migrateOffsetUserId);
editor.putInt("migrateOffsetChatId", migrateOffsetChatId);
editor.putInt("migrateOffsetChannelId", migrateOffsetChannelId);
editor.putLong("migrateOffsetAccess", migrateOffsetAccess);
editor.putInt("3migrateOffsetDate", migrateOffsetDate);
editor.putInt("3migrateOffsetUserId", migrateOffsetUserId);
editor.putInt("3migrateOffsetChatId", migrateOffsetChatId);
editor.putInt("3migrateOffsetChannelId", migrateOffsetChannelId);
editor.putLong("3migrateOffsetAccess", migrateOffsetAccess);
}
editor.putInt("2totalDialogsLoadCount", totalDialogsLoadCount);
editor.putInt("2dialogsLoadOffsetId", dialogsLoadOffsetId);
editor.putInt("2dialogsLoadOffsetDate", dialogsLoadOffsetDate);
editor.putInt("2dialogsLoadOffsetUserId", dialogsLoadOffsetUserId);
editor.putInt("2dialogsLoadOffsetChatId", dialogsLoadOffsetChatId);
editor.putInt("2dialogsLoadOffsetChannelId", dialogsLoadOffsetChannelId);
editor.putLong("2dialogsLoadOffsetAccess", dialogsLoadOffsetAccess);
if (tmpPassword != null) {
SerializedData data = new SerializedData();
tmpPassword.serializeToStream(data);
@ -236,6 +256,7 @@ public class UserConfig {
passcodeType = preferences.getInt("passcodeType", 0);
autoLockIn = preferences.getInt("autoLockIn", 60 * 60);
lastPauseTime = preferences.getInt("lastPauseTime", 0);
lastAppPauseTime = preferences.getLong("lastAppPauseTime", 0);
useFingerprint = preferences.getBoolean("useFingerprint", true);
lastUpdateVersion = preferences.getString("lastUpdateVersion2", "3.5");
lastContactsSyncTime = preferences.getInt("lastContactsSyncTime", (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60);
@ -249,14 +270,36 @@ public class UserConfig {
lastPauseTime = (int) (System.currentTimeMillis() / 1000 - 60 * 10);
}
migrateOffsetId = preferences.getInt("migrateOffsetId", 0);
migrateOffsetId = preferences.getInt("3migrateOffsetId", 0);
if (migrateOffsetId != -1) {
migrateOffsetDate = preferences.getInt("migrateOffsetDate", 0);
migrateOffsetUserId = preferences.getInt("migrateOffsetUserId", 0);
migrateOffsetChatId = preferences.getInt("migrateOffsetChatId", 0);
migrateOffsetChannelId = preferences.getInt("migrateOffsetChannelId", 0);
migrateOffsetAccess = preferences.getLong("migrateOffsetAccess", 0);
migrateOffsetDate = preferences.getInt("3migrateOffsetDate", 0);
migrateOffsetUserId = preferences.getInt("3migrateOffsetUserId", 0);
migrateOffsetChatId = preferences.getInt("3migrateOffsetChatId", 0);
migrateOffsetChannelId = preferences.getInt("3migrateOffsetChannelId", 0);
migrateOffsetAccess = preferences.getLong("3migrateOffsetAccess", 0);
}
// migrateOffsetId = 0;
// migrateOffsetDate = 0;
// migrateOffsetUserId = 0;
// migrateOffsetChatId = 0;
// migrateOffsetChannelId = 0;
// migrateOffsetAccess = 0;
dialogsLoadOffsetId = preferences.getInt("2dialogsLoadOffsetId", -1);
totalDialogsLoadCount = preferences.getInt("2totalDialogsLoadCount", 0);
dialogsLoadOffsetDate = preferences.getInt("2dialogsLoadOffsetDate", -1);
dialogsLoadOffsetUserId = preferences.getInt("2dialogsLoadOffsetUserId", -1);
dialogsLoadOffsetChatId = preferences.getInt("2dialogsLoadOffsetChatId", -1);
dialogsLoadOffsetChannelId = preferences.getInt("2dialogsLoadOffsetChannelId", -1);
dialogsLoadOffsetAccess = preferences.getLong("2dialogsLoadOffsetAccess", -1);
// dialogsLoadOffsetId = -1;
// totalDialogsLoadCount = 0;
// dialogsLoadOffsetDate = -1;
// dialogsLoadOffsetUserId = -1;
// dialogsLoadOffsetChatId = -1;
// dialogsLoadOffsetChannelId = -1;
// dialogsLoadOffsetAccess = -1;
String string = preferences.getString("tmpPassword", null);
if (string != null) {
@ -413,6 +456,13 @@ public class UserConfig {
migrateOffsetChatId = -1;
migrateOffsetChannelId = -1;
migrateOffsetAccess = -1;
dialogsLoadOffsetId = 0;
totalDialogsLoadCount = 0;
dialogsLoadOffsetDate = 0;
dialogsLoadOffsetUserId = 0;
dialogsLoadOffsetChatId = 0;
dialogsLoadOffsetChannelId = 0;
dialogsLoadOffsetAccess = 0;
appLocked = false;
passcodeType = 0;
passcodeHash = "";

View File

@ -52,6 +52,7 @@ public class Utilities {
public native static boolean loadWebpImage(Bitmap bitmap, ByteBuffer buffer, int len, BitmapFactory.Options options, boolean unpin);
public native static int convertVideoFrame(ByteBuffer src, ByteBuffer dest, int destFormat, int width, int height, int padding, int swap);
private native static void aesIgeEncryption(ByteBuffer buffer, byte[] key, byte[] iv, boolean encrypt, int offset, int length);
public native static void aesCtrDecryption(ByteBuffer buffer, byte[] key, byte[] iv, int offset, int length);
public native static String readlink(String path);
public static void aesIgeEncryption(ByteBuffer buffer, byte[] key, byte[] iv, boolean encrypt, boolean changeIv, int offset, int length) {

View File

@ -8,6 +8,8 @@
package org.telegram.messenger;
import org.telegram.tgnet.TLRPC;
import java.util.Locale;
public class VideoEditedInfo {
@ -22,6 +24,12 @@ public class VideoEditedInfo {
public String originalPath;
public long estimatedSize;
public long estimatedDuration;
public boolean roundVideo;
public boolean muted;
public TLRPC.InputFile file;
public TLRPC.InputEncryptedFile encryptedFile;
public byte[] key;
public byte[] iv;
public String getString() {
return String.format(Locale.US, "-1_%d_%d_%d_%d_%d_%d_%d_%d_%s", startTime, endTime, rotationValue, originalWidth, originalHeight, bitrate, resultWidth, resultHeight, originalPath);
@ -56,4 +64,8 @@ public class VideoEditedInfo {
}
return false;
}
public boolean needConvert() {
return !roundVideo || roundVideo && (startTime > 0 || endTime != -1 && endTime != estimatedDuration);
}
}

View File

@ -0,0 +1,164 @@
package org.telegram.messenger;
import android.text.TextUtils;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Channel;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import org.json.JSONObject;
import org.telegram.tgnet.TLRPC;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by grishka on 16.06.17.
*/
public class WearDataLayerListenerService extends WearableListenerService{
@Override
public void onCreate(){
super.onCreate();
}
@Override
public void onDestroy(){
super.onDestroy();
}
@Override
public void onChannelOpened(final Channel ch){
new Thread(new Runnable(){
@Override
public void run(){
GoogleApiClient apiClient=new GoogleApiClient.Builder(WearDataLayerListenerService.this).addApi(Wearable.API).build();
if(!apiClient.blockingConnect().isSuccess()){
FileLog.e("failed to connect google api client");
return;
}
String path=ch.getPath();
FileLog.d("wear channel path: "+path);
try{
if("/getCurrentUser".equals(path)){
DataOutputStream out=new DataOutputStream(new BufferedOutputStream(ch.getOutputStream(apiClient).await().getOutputStream()));
if(UserConfig.isClientActivated()){
final TLRPC.User user=UserConfig.getCurrentUser();
out.writeInt(user.id);
out.writeUTF(user.first_name);
out.writeUTF(user.last_name);
out.writeUTF(user.phone);
if(user.photo!=null){
final File photo=FileLoader.getPathToAttach(user.photo.photo_small, true);
final CyclicBarrier barrier=new CyclicBarrier(2);
if(!photo.exists()){
final NotificationCenter.NotificationCenterDelegate listener=new NotificationCenter.NotificationCenterDelegate(){
@Override
public void didReceivedNotification(int id, Object... args){
if(id==NotificationCenter.FileDidLoaded){
FileLog.d("file loaded: "+args[0]+" "+args[0].getClass().getName());
if(args[0].equals(photo.getName())){
FileLog.e("LOADED USER PHOTO");
try{barrier.await(10, TimeUnit.MILLISECONDS);}catch(Exception x){}
}
}
}
};
AndroidUtilities.runOnUIThread(new Runnable(){
@Override
public void run(){
NotificationCenter.getInstance().addObserver(listener, NotificationCenter.FileDidLoaded);
FileLoader.getInstance().loadFile(user.photo.photo_small, null, 0, true);
}
});
try{barrier.await(10, TimeUnit.SECONDS);}catch(Exception x){}
AndroidUtilities.runOnUIThread(new Runnable(){
@Override
public void run(){
NotificationCenter.getInstance().removeObserver(listener, NotificationCenter.FileDidLoaded);
}
});
}
if(photo.exists() && photo.length()<=50*1024*1024){
byte[] photoData=new byte[(int)photo.length()];
FileInputStream photoIn=new FileInputStream(photo);
new DataInputStream(photoIn).readFully(photoData);
photoIn.close();
out.writeInt(photoData.length);
out.write(photoData);
}else{
out.writeInt(0);
}
}else{
out.writeInt(0);
}
}else{
out.writeInt(0);
}
out.flush();
out.close();
}else if("/waitForAuthCode".equals(path)){
final String[] code={null};
final CyclicBarrier barrier=new CyclicBarrier(2);
final NotificationCenter.NotificationCenterDelegate listener=new NotificationCenter.NotificationCenterDelegate(){
@Override
public void didReceivedNotification(int id, Object... args){
if(id==NotificationCenter.didReceivedNewMessages){
long did = (Long) args[0];
if(did==777000){
ArrayList<MessageObject> arr = (ArrayList<MessageObject>) args[1];
if(arr.size()>0){
MessageObject msg=arr.get(0);
if(!TextUtils.isEmpty(msg.messageText)){
Matcher matcher=Pattern.compile("[0-9]+").matcher(msg.messageText);
if(matcher.find()){
code[0]=matcher.group();
try{barrier.await(10, TimeUnit.MILLISECONDS);}catch(Exception x){}
}
}
}
}
}
}
};
AndroidUtilities.runOnUIThread(new Runnable(){
@Override
public void run(){
NotificationCenter.getInstance().addObserver(listener, NotificationCenter.didReceivedNewMessages);
}
});
try{barrier.await(10, TimeUnit.SECONDS);}catch(Exception x){}
AndroidUtilities.runOnUIThread(new Runnable(){
@Override
public void run(){
NotificationCenter.getInstance().removeObserver(listener, NotificationCenter.didReceivedNewMessages);
}
});
DataOutputStream out=new DataOutputStream(ch.getOutputStream(apiClient).await().getOutputStream());
if(code!=null)
out.writeUTF(code[0]);
else
out.writeUTF("");
out.flush();
out.close();
}
}catch(Exception x){
FileLog.e("error processing wear request", x);
}
ch.close(apiClient).await();
apiClient.disconnect();
}
}).start();
}
}

View File

@ -196,6 +196,14 @@ public class Browser {
public static boolean isInternalUri(Uri uri) {
String host = uri.getHost();
host = host != null ? host.toLowerCase() : "";
return "tg".equals(uri.getScheme()) || "telegram.me".equals(host) || "t.me".equals(host) || "telegram.dog".equals(host);
if ("tg".equals(uri.getScheme())) {
return true;
} else if ("telegram.me".equals(host) || "t.me".equals(host) || "telegram.dog".equals(host) || "telesco.pe".equals(host)) {
String path = uri.getPath();
if (path != null && path.length() > 1) {
return true;
}
}
return false;
}
}

View File

@ -53,6 +53,7 @@ public class CameraController implements MediaRecorder.OnInfoListener {
private VideoTakeCallback onVideoTakeCallback;
private boolean recordingSmallVideo;
private boolean cameraInitied;
private boolean loadingCameras;
private static volatile CameraController Instance = null;
@ -78,9 +79,10 @@ public class CameraController implements MediaRecorder.OnInfoListener {
}
public void initCamera() {
if (cameraInitied) {
if (loadingCameras || cameraInitied) {
return;
}
loadingCameras = true;
threadPool.execute(new Runnable() {
@Override
public void run() {
@ -102,6 +104,7 @@ public class CameraController implements MediaRecorder.OnInfoListener {
Camera.Size size = list.get(a);
if (size.height < 2160 && size.width < 2160) {
cameraInfo.previewSizes.add(new Size(size.width, size.height));
FileLog.e("preview size = " + size.width + " " + size.height);
}
}
@ -110,6 +113,7 @@ public class CameraController implements MediaRecorder.OnInfoListener {
Camera.Size size = list.get(a);
if (!"samsung".equals(Build.MANUFACTURER) || !"jflteuc".equals(Build.PRODUCT) || size.width < 2048) {
cameraInfo.pictureSizes.add(new Size(size.width, size.height));
FileLog.e("picture size = " + size.width + " " + size.height);
}
}
@ -121,11 +125,19 @@ public class CameraController implements MediaRecorder.OnInfoListener {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
loadingCameras = false;
cameraInitied = true;
NotificationCenter.getInstance().postNotificationName(NotificationCenter.cameraInitied);
}
});
} catch (Exception e) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
loadingCameras = false;
cameraInitied = false;
}
});
FileLog.e(e);
}
}
@ -392,6 +404,44 @@ public class CameraController implements MediaRecorder.OnInfoListener {
});
}
public void openRound(final CameraSession session, final SurfaceTexture texture, final Runnable callback, final Runnable configureCallback) {
if (session == null || texture == null) {
FileLog.e("failed to open round " + session + " tex = " + texture);
return;
}
threadPool.execute(new Runnable() {
@SuppressLint("NewApi")
@Override
public void run() {
Camera camera = session.cameraInfo.camera;
try {
FileLog.e("start creating round camera session");
if (camera == null) {
camera = session.cameraInfo.camera = Camera.open(session.cameraInfo.cameraId);
}
Camera.Parameters params = camera.getParameters();
session.configureRoundCamera();
if (configureCallback != null) {
configureCallback.run();
}
camera.setPreviewTexture(texture);
camera.startPreview();
if (callback != null) {
AndroidUtilities.runOnUIThread(callback);
}
FileLog.e("round camera session created");
} catch (Exception e) {
session.cameraInfo.camera = null;
if (camera != null) {
camera.release();
}
FileLog.e(e);
}
}
});
}
public void open(final CameraSession session, final SurfaceTexture texture, final Runnable callback, final Runnable prestartCallback) {
if (session == null || texture == null) {
return;
@ -477,7 +527,9 @@ public class CameraController implements MediaRecorder.OnInfoListener {
if (recordingSmallVideo) {
pictureSize = new Size(4, 3);
pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 640, 480, pictureSize);
recorder.setVideoEncodingBitRate(900000);
recorder.setVideoEncodingBitRate(900000 * 2);
recorder.setAudioEncodingBitRate(32000);
recorder.setAudioChannels(1);
} else {
pictureSize = new Size(16, 9);
pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 720, 480, pictureSize);

View File

@ -41,6 +41,10 @@ public class CameraInfo {
return pictureSizes;
}
public boolean isFrontface() {
return frontCamera != 0;
}
/*private int getScore(CameraSelectionCriteria criteria) {
int score = 10;
if (criteria != null) {

View File

@ -24,6 +24,7 @@ import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import java.util.ArrayList;
import java.util.List;
public class CameraSession {
@ -39,6 +40,7 @@ public class CameraSession {
private boolean initied;
private boolean meteringAreaSupported;
private int currentOrientation;
private int diffOrientation;
private int jpegOrientation;
private boolean sameTakePictureOrientation;
@ -143,11 +145,11 @@ public class CameraSession {
return currentFlashMode;
}
protected void setInitied() {
public void setInitied() {
initied = true;
}
protected boolean isInitied() {
public boolean isInitied() {
return initied;
}
@ -155,10 +157,113 @@ public class CameraSession {
return currentOrientation;
}
public int getWorldAngle() {
return diffOrientation;
}
public boolean isSameTakePictureOrientation() {
return sameTakePictureOrientation;
}
protected void configureRoundCamera() {
try {
isVideo = true;
Camera camera = cameraInfo.camera;
if (camera != null) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.Parameters params = null;
try {
params = camera.getParameters();
} catch (Exception e) {
FileLog.e(e);
}
Camera.getCameraInfo(cameraInfo.getCameraId(), info);
int displayOrientation = getDisplayOrientation(info, true);
int cameraDisplayOrientation;
if ("samsung".equals(Build.MANUFACTURER) && "sf2wifixx".equals(Build.PRODUCT)) {
cameraDisplayOrientation = 0;
} else {
int degrees = 0;
int temp = displayOrientation;
switch (temp) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
if (info.orientation % 90 != 0) {
info.orientation = 0;
}
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
temp = (info.orientation + degrees) % 360;
temp = (360 - temp) % 360;
} else {
temp = (info.orientation - degrees + 360) % 360;
}
cameraDisplayOrientation = temp;
}
camera.setDisplayOrientation(currentOrientation = cameraDisplayOrientation);
diffOrientation = currentOrientation - displayOrientation;
if (params != null) {
FileLog.e("set preview size = " + previewSize.getWidth() + " " + previewSize.getHeight());
params.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
FileLog.e("set picture size = " + pictureSize.getWidth() + " " + pictureSize.getHeight());
params.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
params.setPictureFormat(pictureFormat);
params.setRecordingHint(true);
String desiredMode = Camera.Parameters.FOCUS_MODE_AUTO;
if (params.getSupportedFocusModes().contains(desiredMode)) {
params.setFocusMode(desiredMode);
}
int outputOrientation = 0;
if (jpegOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
outputOrientation = (info.orientation - jpegOrientation + 360) % 360;
} else {
outputOrientation = (info.orientation + jpegOrientation) % 360;
}
}
try {
params.setRotation(outputOrientation);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
sameTakePictureOrientation = (360 - displayOrientation) % 360 == outputOrientation;
} else {
sameTakePictureOrientation = displayOrientation == outputOrientation;
}
} catch (Exception e) {
//
}
params.setFlashMode(currentFlashMode);
try {
camera.setParameters(params);
} catch (Exception e) {
//
}
if (params.getMaxNumMeteringAreas() > 0) {
meteringAreaSupported = true;
}
}
}
} catch (Throwable e) {
FileLog.e(e);
}
}
protected void configurePhotoCamera() {
try {
Camera camera = cameraInfo.camera;

View File

@ -51,6 +51,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur
private float focusProgress = 1.0f;
private float innerAlpha;
private float outerAlpha;
private boolean initialFrontface;
private int cx;
private int cy;
private Paint outerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@ -65,7 +66,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur
public CameraView(Context context, boolean frontface) {
super(context, null);
isFrontface = frontface;
initialFrontface = isFrontface = frontface;
textureView = new TextureView(context);
textureView.setSurfaceTextureListener(this);
addView(textureView);
@ -132,14 +133,20 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur
org.telegram.messenger.camera.Size aspectRatio;
int wantedWidth;
int wantedHeight;
if (Math.abs(screenSize - size4to3) < 0.1f) {
aspectRatio = new Size(4, 3);
wantedWidth = 1280;
wantedHeight = 960;
} else {
if (initialFrontface) {
aspectRatio = new Size(16, 9);
wantedWidth = 1280;
wantedHeight = 720;
wantedWidth = 480;
wantedHeight = 270;
} else {
if (Math.abs(screenSize - size4to3) < 0.1f) {
aspectRatio = new Size(4, 3);
wantedWidth = 1280;
wantedHeight = 960;
} else {
aspectRatio = new Size(16, 9);
wantedWidth = 1280;
wantedHeight = 720;
}
}
if (textureView.getWidth() > 0 && textureView.getHeight() > 0) {
int width = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y);

View File

@ -67,6 +67,6 @@ public final class Size {
return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
}
private final int mWidth;
private final int mHeight;
public final int mWidth;
public final int mHeight;
}

View File

@ -28,6 +28,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private final int trackType;
private RendererConfiguration configuration;
private int index;
private int state;
private SampleStream stream;
@ -70,9 +71,11 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
}
@Override
public final void enable(Format[] formats, SampleStream stream, long positionUs,
boolean joining, long offsetUs) throws ExoPlaybackException {
public final void enable(RendererConfiguration configuration, Format[] formats,
SampleStream stream, long positionUs, boolean joining, long offsetUs)
throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration;
state = STATE_ENABLED;
onEnabled(joining);
replaceStream(formats, stream, offsetUs);
@ -107,10 +110,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
}
@Override
public final void setCurrentStreamIsFinal() {
public final void setCurrentStreamFinal() {
streamIsFinal = true;
}
@Override
public final boolean isCurrentStreamFinal() {
return streamIsFinal;
}
@Override
public final void maybeThrowStreamError() throws IOException {
stream.maybeThrowError();
@ -119,6 +127,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
@Override
public final void resetPosition(long positionUs) throws ExoPlaybackException {
streamIsFinal = false;
readEndOfStream = false;
onPositionReset(positionUs, false);
}
@ -194,8 +203,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* @param joining Whether this renderer is being enabled to join an ongoing playback.
* @throws ExoPlaybackException If an error occurs.
*/
protected void onPositionReset(long positionUs, boolean joining)
throws ExoPlaybackException {
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
// Do nothing.
}
@ -232,10 +240,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
// Methods to be called by subclasses.
/**
* Returns the configuration set when the renderer was most recently enabled.
*/
protected final RendererConfiguration getConfiguration() {
return configuration;
}
/**
* Returns the index of the renderer within the player.
*
* @return The index of the renderer within the player.
*/
protected final int getIndex() {
return index;
@ -243,29 +256,48 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
/**
* Reads from the enabled upstream source. If the upstream source has been read to the end then
* {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamIsFinal()} has been
* {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been
* called. {@link C#RESULT_NOTHING_READ} is returned otherwise.
*
* @see SampleStream#readData(FormatHolder, DecoderInputBuffer)
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer) {
int result = stream.readData(formatHolder, buffer);
protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
int result = stream.readData(formatHolder, buffer, formatRequired);
if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) {
readEndOfStream = true;
return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
}
buffer.timeUs += streamOffsetUs;
} else if (result == C.RESULT_FORMAT_READ) {
Format format = formatHolder.format;
if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs);
formatHolder.format = format;
}
}
return result;
}
/**
* Attempts to skip to the keyframe before the specified position, or to the end of the stream if
* {@code positionUs} is beyond it.
*
* @param positionUs The position in microseconds.
*/
protected void skipSource(long positionUs) {
stream.skipData(positionUs - streamOffsetUs);
}
/**
* Returns whether the upstream source is ready.
*
@ -275,13 +307,4 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
return readEndOfStream ? streamIsFinal : stream.isReady();
}
/**
* Attempts to skip to the keyframe before the specified time.
*
* @param timeUs The specified time.
*/
protected void skipToKeyframeBefore(long timeUs) {
stream.skipToKeyframeBefore(timeUs);
}
}

View File

@ -15,9 +15,12 @@
*/
package org.telegram.messenger.exoplayer2;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.support.annotation.IntDef;
import android.view.Surface;
import org.telegram.messenger.exoplayer2.util.Util;
@ -74,6 +77,21 @@ public final class C {
*/
public static final String UTF8_NAME = "UTF-8";
/**
* The name of the UTF-16 charset.
*/
public static final String UTF16_NAME = "UTF-16";
/**
* * The name of the serif font family.
*/
public static final String SERIF_NAME = "serif";
/**
* * The name of the sans-serif font family.
*/
public static final String SANS_SERIF_NAME = "sans-serif";
/**
* Crypto modes for a codec.
*/
@ -96,6 +114,13 @@ public final class C {
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
/**
* Represents an unset {@link android.media.AudioTrack} session identifier. Equal to
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
*/
@SuppressWarnings("InlinedApi")
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
/**
* Represents an audio encoding, or an invalid or unset value.
*/
@ -434,9 +459,16 @@ public final class C {
*/
public static final UUID UUID_NIL = new UUID(0L, 0L);
/**
* UUID for the ClearKey DRM scheme.
* <p>
* ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up.
*/
public static final UUID CLEARKEY_UUID = new UUID(0x1077EFECC0B24D02L, 0xACE33C1E52E2FB4BL);
/**
* UUID for the Widevine DRM scheme.
* <p></p>
* <p>
* Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
*/
public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
@ -463,15 +495,6 @@ public final class C {
*/
public static final int MSG_SET_VOLUME = 2;
/**
* A type of a message that can be passed to an audio {@link Renderer} via
* {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object
* should be a {@link android.media.PlaybackParams}, or null, which will be used to configure the
* underlying {@link android.media.AudioTrack}. The message object should not be modified by the
* caller after it has been passed
*/
public static final int MSG_SET_PLAYBACK_PARAMS = 3;
/**
* A type of a message that can be passed to an audio {@link Renderer} via
* {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object
@ -484,7 +507,7 @@ public final class C {
* introduce a brief gap in audio output. Note also that tracks in the same audio session must
* share the same routing, so a new audio session id will be generated.
*/
public static final int MSG_SET_STREAM_TYPE = 4;
public static final int MSG_SET_STREAM_TYPE = 3;
/**
* The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer}
@ -494,7 +517,7 @@ public final class C {
* Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is
* owned by a {@link android.view.SurfaceView}.
*/
public static final int MSG_SET_SCALING_MODE = 5;
public static final int MSG_SET_SCALING_MODE = 4;
/**
* Applications or extensions may define custom {@code MSG_*} constants greater than or equal to
@ -506,7 +529,13 @@ public final class C {
* The stereo mode for 360/3D/VR videos.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
@IntDef({
Format.NO_VALUE,
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
})
public @interface StereoMode {}
/**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
@ -520,6 +549,86 @@ public final class C {
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_LEFT_RIGHT = 2;
/**
* Indicates a stereo layout where the left and right eyes have separate meshes,
* used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_STEREO_MESH = 3;
/**
* Video colorspaces.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
public @interface ColorSpace {}
/**
* @see MediaFormat#COLOR_STANDARD_BT709
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
/**
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
/**
* @see MediaFormat#COLOR_STANDARD_BT2020
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
/**
* Video color transfer characteristics.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
public @interface ColorTransfer {}
/**
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
/**
* @see MediaFormat#COLOR_TRANSFER_ST2084
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
/**
* @see MediaFormat#COLOR_TRANSFER_HLG
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
/**
* Video color range.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
public @interface ColorRange {}
/**
* @see MediaFormat#COLOR_RANGE_LIMITED
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
/**
* @see MediaFormat#COLOR_RANGE_FULL
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
/**
* Priority for media playback.
*
* <p>Larger values indicate higher priorities.
*/
public static final int PRIORITY_PLAYBACK = 0;
/**
* Priority for media downloading.
*
* <p>Larger values indicate higher priorities.
*/
public static final int PRIORITY_DOWNLOAD = PRIORITY_PLAYBACK - 1000;
/**
* Converts a time in microseconds to the corresponding time in milliseconds, preserving
@ -543,4 +652,13 @@ public final class C {
return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000);
}
/**
* Returns a newly generated {@link android.media.AudioTrack} session identifier.
*/
@TargetApi(21)
public static int generateAudioSessionIdV21(Context context) {
return ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE))
.generateAudioSessionId();
}
}

View File

@ -19,6 +19,7 @@ import org.telegram.messenger.exoplayer2.source.TrackGroupArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray;
import org.telegram.messenger.exoplayer2.upstream.Allocator;
import org.telegram.messenger.exoplayer2.upstream.DefaultAllocator;
import org.telegram.messenger.exoplayer2.util.PriorityTaskManager;
import org.telegram.messenger.exoplayer2.util.Util;
/**
@ -60,6 +61,7 @@ public final class DefaultLoadControl implements LoadControl {
private final long maxBufferUs;
private final long bufferForPlaybackUs;
private final long bufferForPlaybackAfterRebufferUs;
private final PriorityTaskManager priorityTaskManager;
private int targetBufferSize;
private boolean isBuffering;
@ -97,11 +99,36 @@ public final class DefaultLoadControl implements LoadControl {
*/
public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) {
this(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs,
null);
}
/**
* Constructs a new instance.
*
* @param allocator The {@link DefaultAllocator} used by the loader.
* @param minBufferMs The minimum duration of media that the player will attempt to ensure is
* buffered at all times, in milliseconds.
* @param maxBufferMs The maximum duration of media that the player will attempt buffer, in
* milliseconds.
* @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or
* resume following a user action such as a seek, in milliseconds.
* @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for
* playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by
* buffer depletion rather than a user action.
* @param priorityTaskManager If not null, registers itself as a task with priority
* {@link C#PRIORITY_PLAYBACK} during loading periods, and unregisters itself during draining
* periods.
*/
public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs,
long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs,
PriorityTaskManager priorityTaskManager) {
this.allocator = allocator;
minBufferUs = minBufferMs * 1000L;
maxBufferUs = maxBufferMs * 1000L;
bufferForPlaybackUs = bufferForPlaybackMs * 1000L;
bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L;
this.priorityTaskManager = priorityTaskManager;
}
@Override
@ -146,8 +173,16 @@ public final class DefaultLoadControl implements LoadControl {
public boolean shouldContinueLoading(long bufferedDurationUs) {
int bufferTimeState = getBufferTimeState(bufferedDurationUs);
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean wasBuffering = isBuffering;
isBuffering = bufferTimeState == BELOW_LOW_WATERMARK
|| (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached);
if (priorityTaskManager != null && isBuffering != wasBuffering) {
if (isBuffering) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
} else {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
}
}
return isBuffering;
}
@ -158,6 +193,9 @@ public final class DefaultLoadControl implements LoadControl {
private void reset(boolean resetAllocator) {
targetBufferSize = 0;
if (priorityTaskManager != null && isBuffering) {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
}
isBuffering = false;
if (resetAllocator) {
allocator.reset();

View File

@ -0,0 +1,327 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.util.Log;
import org.telegram.messenger.exoplayer2.audio.AudioCapabilities;
import org.telegram.messenger.exoplayer2.audio.AudioProcessor;
import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener;
import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer;
import org.telegram.messenger.exoplayer2.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto;
import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector;
import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer;
import org.telegram.messenger.exoplayer2.text.TextRenderer;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelector;
import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer;
import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
/**
* Default {@link RenderersFactory} implementation.
*/
public class DefaultRenderersFactory implements RenderersFactory {
/**
* The default maximum duration for which a video renderer can attempt to seamlessly join an
* ongoing playback.
*/
public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000;
/**
* Modes for using extension renderers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON,
EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {}
/**
* Do not allow use of extension renderers.
*/
public static final int EXTENSION_RENDERER_MODE_OFF = 0;
/**
* Allow use of extension renderers. Extension renderers are indexed after core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use a core renderer to an extension renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_ON = 1;
/**
* Allow use of extension renderers. Extension renderers are indexed before core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use an extension renderer to a core renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_PREFER = 2;
private static final String TAG = "DefaultRenderersFactory";
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final Context context;
private final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
private final @ExtensionRendererMode int extensionRendererMode;
private final long allowedVideoJoiningTimeMs;
/**
* @param context A {@link Context}.
*/
public DefaultRenderersFactory(Context context) {
this(context, null);
}
/**
* @param context A {@link Context}.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected
* playbacks are not required.
*/
public DefaultRenderersFactory(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
this(context, drmSessionManager, EXTENSION_RENDERER_MODE_OFF);
}
/**
* @param context A {@link Context}.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected
* playbacks are not required..
* @param extensionRendererMode The extension renderer mode, which determines if and how
* available extension renderers are used. Note that extensions must be included in the
* application build for them to be considered available.
*/
public DefaultRenderersFactory(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode) {
this(context, drmSessionManager, extensionRendererMode,
DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
/**
* @param context A {@link Context}.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if DRM protected
* playbacks are not required..
* @param extensionRendererMode The extension renderer mode, which determines if and how
* available extension renderers are used. Note that extensions must be included in the
* application build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt
* to seamlessly join an ongoing playback.
*/
public DefaultRenderersFactory(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) {
this.context = context;
this.drmSessionManager = drmSessionManager;
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
}
@Override
public Renderer[] createRenderers(Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput) {
ArrayList<Renderer> renderersList = new ArrayList<>();
buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs,
eventHandler, videoRendererEventListener, extensionRendererMode, renderersList);
buildAudioRenderers(context, drmSessionManager, buildAudioProcessors(),
eventHandler, audioRendererEventListener, extensionRendererMode, renderersList);
buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList);
buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList);
buildMiscellaneousRenderers(context, eventHandler, extensionRendererMode, renderersList);
return renderersList.toArray(new Renderer[renderersList.size()]);
}
/**
* Builds video renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player
* will not be used for DRM protected playbacks.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video
* renderers can attempt to seamlessly join an ongoing playback.
* @param eventHandler A handler associated with the main thread's looper.
* @param eventListener An event listener.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildVideoRenderers(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, long allowedVideoJoiningTimeMs,
Handler eventHandler, VideoRendererEventListener eventListener,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, drmSessionManager, false, eventHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.vp9.LibvpxVideoRenderer");
Constructor<?> constructor = clazz.getConstructor(boolean.class, long.class, Handler.class,
VideoRendererEventListener.class, int.class);
Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs,
eventHandler, eventListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibvpxVideoRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds audio renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player
* will not be used for DRM protected playbacks.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio
* buffers before output. May be empty.
* @param eventHandler A handler to use when invoking event listeners and outputs.
* @param eventListener An event listener.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AudioProcessor[] audioProcessors, Handler eventHandler,
AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode,
ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
eventHandler, eventListener, AudioCapabilities.getCapabilities(context), audioProcessors));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class, AudioProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener,
audioProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class, AudioProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener,
audioProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class, AudioProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener,
audioProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds text renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param output An output for the renderers.
* @param outputLooper The looper associated with the thread on which the output should be
* called.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildTextRenderers(Context context, TextRenderer.Output output,
Looper outputLooper, @ExtensionRendererMode int extensionRendererMode,
ArrayList<Renderer> out) {
out.add(new TextRenderer(output, outputLooper));
}
/**
* Builds metadata renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param output An output for the renderers.
* @param outputLooper The looper associated with the thread on which the output should be
* called.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMetadataRenderers(Context context, MetadataRenderer.Output output,
Looper outputLooper, @ExtensionRendererMode int extensionRendererMode,
ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, outputLooper));
}
/**
* Builds any miscellaneous renderers used by the player.
*
* @param context The {@link Context} associated with the player.
* @param eventHandler A handler to use when invoking event listeners and outputs.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMiscellaneousRenderers(Context context, Handler eventHandler,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
// Do nothing.
}
/**
* Builds an array of {@link AudioProcessor}s that will process PCM audio before output.
*/
protected AudioProcessor[] buildAudioProcessors() {
return new AudioProcessor[0];
}
}

View File

@ -56,8 +56,7 @@ public final class ExoPlaybackException extends Exception {
* The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and
* {@link #TYPE_UNEXPECTED}.
*/
@Type
public final int type;
@Type public final int type;
/**
* If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer.

View File

@ -15,6 +15,7 @@
*/
package org.telegram.messenger.exoplayer2;
import android.support.annotation.Nullable;
import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer;
import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer;
import org.telegram.messenger.exoplayer2.source.ConcatenatingMediaSource;
@ -23,9 +24,6 @@ import org.telegram.messenger.exoplayer2.source.MediaSource;
import org.telegram.messenger.exoplayer2.source.MergingMediaSource;
import org.telegram.messenger.exoplayer2.source.SingleSampleMediaSource;
import org.telegram.messenger.exoplayer2.source.TrackGroupArray;
import org.telegram.messenger.exoplayer2.source.dash.DashMediaSource;
import org.telegram.messenger.exoplayer2.source.hls.HlsMediaSource;
import org.telegram.messenger.exoplayer2.source.smoothstreaming.SsMediaSource;
import org.telegram.messenger.exoplayer2.text.TextRenderer;
import org.telegram.messenger.exoplayer2.trackselection.DefaultTrackSelector;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray;
@ -47,12 +45,11 @@ import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer;
* <ul>
* <li>A <b>{@link MediaSource}</b> that defines the media to be played, loads the media, and from
* which the loaded media can be read. A MediaSource is injected via {@link #prepare} at the start
* of playback. The library provides default implementations for regular media files
* ({@link ExtractorMediaSource}), DASH ({@link DashMediaSource}), SmoothStreaming
* ({@link SsMediaSource}) and HLS ({@link HlsMediaSource}), implementations for merging
* ({@link MergingMediaSource}) and concatenating ({@link ConcatenatingMediaSource}) other
* MediaSources, and an implementation for loading single samples
* ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed
* of playback. The library modules provide default implementations for regular media files
* ({@link ExtractorMediaSource}), DASH (DashMediaSource), SmoothStreaming (SsMediaSource) and HLS
* (HlsMediaSource), implementations for merging ({@link MergingMediaSource}) and concatenating
* ({@link ConcatenatingMediaSource}) other MediaSources, and an implementation for loading single
* samples ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed
* caption files.</li>
* <li><b>{@link Renderer}</b>s that render individual components of the media. The library
* provides default implementations for common media types ({@link MediaCodecVideoRenderer},
@ -120,8 +117,8 @@ public interface ExoPlayer {
* removed from the timeline. The will <em>not</em> be reported via a separate call to
* {@link #onPositionDiscontinuity()}.
*
* @param timeline The latest timeline, or null if the timeline is being cleared.
* @param manifest The latest manifest, or null if the manifest is being cleared.
* @param timeline The latest timeline. Never null, but may be empty.
* @param manifest The latest manifest. May be null.
*/
void onTimelineChanged(Timeline timeline, Object manifest);
@ -172,6 +169,16 @@ public interface ExoPlayer {
*/
void onPositionDiscontinuity();
/**
* Called when the current playback parameters change. The playback parameters may change due to
* a call to {@link ExoPlayer#setPlaybackParameters(PlaybackParameters)}, or the player itself
* may change them (for example, if audio playback switches to passthrough mode, where speed
* adjustment is no longer possible).
*
* @param playbackParameters The playback parameters.
*/
void onPlaybackParametersChanged(PlaybackParameters playbackParameters);
}
/**
@ -330,17 +337,41 @@ public interface ExoPlayer {
/**
* Seeks to a position specified in milliseconds in the current window.
*
* @param windowPositionMs The seek position in the current window.
* @param positionMs The seek position in the current window, or {@link C#TIME_UNSET} to seek to
* the window's default position.
*/
void seekTo(long windowPositionMs);
void seekTo(long positionMs);
/**
* Seeks to a position specified in milliseconds in the specified window.
*
* @param windowIndex The index of the window.
* @param windowPositionMs The seek position in the specified window.
* @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to
* the window's default position.
*/
void seekTo(int windowIndex, long windowPositionMs);
void seekTo(int windowIndex, long positionMs);
/**
* Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
* default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
* <p>
* Playback parameters changes may cause the player to buffer.
* {@link EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever
* the currently active playback parameters change. When that listener is called, the parameters
* passed to it may not match {@code playbackParameters}. For example, the chosen speed or pitch
* may be out of range, in which case they are constrained to a set of permitted values. If it is
* not possible to change the playback parameters, the listener will not be invoked.
*
* @param playbackParameters The playback parameters, or {@code null} to use the defaults.
*/
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
/**
* Returns the currently active playback parameters.
*
* @see EventListener#onPlaybackParametersChanged(PlaybackParameters)
*/
PlaybackParameters getPlaybackParameters();
/**
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
@ -445,4 +476,20 @@ public interface ExoPlayer {
*/
int getBufferedPercentage();
/**
* Returns whether the current window is dynamic, or {@code false} if the {@link Timeline} is
* empty.
*
* @see Timeline.Window#isDynamic
*/
boolean isCurrentWindowDynamic();
/**
* Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is
* empty.
*
* @see Timeline.Window#isSeekable
*/
boolean isCurrentWindowSeekable();
}

View File

@ -26,12 +26,6 @@ import org.telegram.messenger.exoplayer2.trackselection.TrackSelector;
*/
public final class ExoPlayerFactory {
/**
* The default maximum duration for which a video renderer can attempt to seamlessly join an
* ongoing playback.
*/
public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000;
private ExoPlayerFactory() {}
/**
@ -41,10 +35,13 @@ public final class ExoPlayerFactory {
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}.
*/
@Deprecated
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl) {
return newSimpleInstance(context, trackSelector, loadControl, null);
RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
return newSimpleInstance(renderersFactory, trackSelector, loadControl);
}
/**
@ -56,11 +53,13 @@ public final class ExoPlayerFactory {
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance
* will not be used for DRM protected playbacks.
* @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}.
*/
@Deprecated
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
return newSimpleInstance(context, trackSelector, loadControl,
drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF);
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager);
return newSimpleInstance(renderersFactory, trackSelector, loadControl);
}
/**
@ -75,12 +74,15 @@ public final class ExoPlayerFactory {
* @param extensionRendererMode The extension renderer mode, which determines if and how available
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
* @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}.
*/
@Deprecated
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) {
return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager,
extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) {
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager,
extensionRendererMode);
return newSimpleInstance(renderersFactory, trackSelector, loadControl);
}
/**
@ -97,13 +99,52 @@ public final class ExoPlayerFactory {
* build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to
* seamlessly join an ongoing playback.
* @deprecated Use {@link #newSimpleInstance(RenderersFactory, TrackSelector, LoadControl)}.
*/
@Deprecated
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector,
LoadControl loadControl, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager,
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, drmSessionManager,
extensionRendererMode, allowedVideoJoiningTimeMs);
return newSimpleInstance(renderersFactory, trackSelector, loadControl);
}
/**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
* {@link Looper}.
*
* @param context A {@link Context}.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) {
return newSimpleInstance(new DefaultRenderersFactory(context), trackSelector);
}
/**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
* {@link Looper}.
*
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory,
TrackSelector trackSelector) {
return newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl());
}
/**
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
* {@link Looper}.
*
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
*/
public static SimpleExoPlayer newSimpleInstance(RenderersFactory renderersFactory,
TrackSelector trackSelector, LoadControl loadControl) {
return new SimpleExoPlayer(renderersFactory, trackSelector, loadControl);
}
/**

View File

@ -19,15 +19,16 @@ import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.Log;
import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.PlaybackInfo;
import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.SourceInfo;
import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.TrackInfo;
import org.telegram.messenger.exoplayer2.source.MediaSource;
import org.telegram.messenger.exoplayer2.source.TrackGroupArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelection;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelector;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectorResult;
import org.telegram.messenger.exoplayer2.util.Assertions;
import org.telegram.messenger.exoplayer2.util.Util;
import java.util.concurrent.CopyOnWriteArraySet;
@ -52,17 +53,20 @@ import java.util.concurrent.CopyOnWriteArraySet;
private boolean playWhenReady;
private int playbackState;
private int pendingSeekAcks;
private int pendingPrepareAcks;
private boolean isLoading;
private Timeline timeline;
private Object manifest;
private TrackGroupArray trackGroups;
private TrackSelectionArray trackSelections;
private PlaybackParameters playbackParameters;
// Playback information when there is no pending seek/set source operation.
private PlaybackInfo playbackInfo;
// Playback information when there is a pending seek/set source operation.
private int maskingWindowIndex;
private int maskingPeriodIndex;
private long maskingWindowPositionMs;
/**
@ -74,7 +78,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION + " [" + Util.DEVICE_DEBUG_INFO + "]");
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION_SLASHY + " [" + Util.DEVICE_DEBUG_INFO + "]");
Assertions.checkState(renderers.length > 0);
this.renderers = Assertions.checkNotNull(renderers);
this.trackSelector = Assertions.checkNotNull(trackSelector);
@ -87,6 +91,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
period = new Timeline.Period();
trackGroups = TrackGroupArray.EMPTY;
trackSelections = emptyTrackSelections;
playbackParameters = PlaybackParameters.DEFAULT;
eventHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@ -95,7 +100,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
};
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0);
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady,
eventHandler, playbackInfo);
eventHandler, playbackInfo, this);
}
@Override
@ -125,7 +130,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
timeline = Timeline.EMPTY;
manifest = null;
for (EventListener listener : listeners) {
listener.onTimelineChanged(null, null);
listener.onTimelineChanged(timeline, manifest);
}
}
if (tracksSelected) {
@ -138,6 +143,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
}
pendingPrepareAcks++;
internalPlayer.prepare(mediaSource, resetPosition);
}
@ -180,10 +186,26 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void seekTo(int windowIndex, long positionMs) {
if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) {
throw new IndexOutOfBoundsException();
throw new IllegalSeekPositionException(timeline, windowIndex, positionMs);
}
pendingSeekAcks++;
maskingWindowIndex = windowIndex;
if (timeline.isEmpty()) {
maskingPeriodIndex = 0;
} else {
timeline.getWindow(windowIndex, window);
long resolvedPositionMs =
positionMs == C.TIME_UNSET ? window.getDefaultPositionUs() : positionMs;
int periodIndex = window.firstPeriodIndex;
long periodPositionUs = window.getPositionInFirstPeriodUs() + C.msToUs(resolvedPositionMs);
long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs();
while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs
&& periodIndex < window.lastPeriodIndex) {
periodPositionUs -= periodDurationUs;
periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs();
}
maskingPeriodIndex = periodIndex;
}
if (positionMs == C.TIME_UNSET) {
maskingWindowPositionMs = 0;
internalPlayer.seekTo(timeline, windowIndex, C.TIME_UNSET);
@ -196,6 +218,19 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
@Override
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
if (playbackParameters == null) {
playbackParameters = PlaybackParameters.DEFAULT;
}
internalPlayer.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return playbackParameters;
}
@Override
public void stop() {
internalPlayer.stop();
@ -219,7 +254,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public int getCurrentPeriodIndex() {
return playbackInfo.periodIndex;
if (timeline.isEmpty() || pendingSeekAcks > 0) {
return maskingPeriodIndex;
} else {
return playbackInfo.periodIndex;
}
}
@Override
@ -271,6 +310,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
}
@Override
public boolean isCurrentWindowDynamic() {
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
}
@Override
public boolean isCurrentWindowSeekable() {
return !timeline.isEmpty() && timeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
}
@Override
public int getRendererCount() {
return renderers.length;
@ -304,6 +353,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
// Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) {
switch (msg.what) {
case ExoPlayerImplInternal.MSG_PREPARE_ACK: {
pendingPrepareAcks--;
break;
}
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
playbackState = msg.arg1;
for (EventListener listener : listeners) {
@ -319,21 +372,25 @@ import java.util.concurrent.CopyOnWriteArraySet;
break;
}
case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: {
TrackInfo trackInfo = (TrackInfo) msg.obj;
tracksSelected = true;
trackGroups = trackInfo.groups;
trackSelections = trackInfo.selections;
trackSelector.onSelectionActivated(trackInfo.info);
for (EventListener listener : listeners) {
listener.onTracksChanged(trackGroups, trackSelections);
if (pendingPrepareAcks == 0) {
TrackSelectorResult trackSelectorResult = (TrackSelectorResult) msg.obj;
tracksSelected = true;
trackGroups = trackSelectorResult.groups;
trackSelections = trackSelectorResult.selections;
trackSelector.onSelectionActivated(trackSelectorResult.info);
for (EventListener listener : listeners) {
listener.onTracksChanged(trackGroups, trackSelections);
}
}
break;
}
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
if (--pendingSeekAcks == 0) {
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity();
if (msg.arg1 != 0) {
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity();
}
}
}
break;
@ -349,12 +406,24 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: {
SourceInfo sourceInfo = (SourceInfo) msg.obj;
timeline = sourceInfo.timeline;
manifest = sourceInfo.manifest;
playbackInfo = sourceInfo.playbackInfo;
pendingSeekAcks -= sourceInfo.seekAcks;
for (EventListener listener : listeners) {
listener.onTimelineChanged(timeline, manifest);
if (pendingPrepareAcks == 0) {
timeline = sourceInfo.timeline;
manifest = sourceInfo.manifest;
playbackInfo = sourceInfo.playbackInfo;
for (EventListener listener : listeners) {
listener.onTimelineChanged(timeline, manifest);
}
}
break;
}
case ExoPlayerImplInternal.MSG_PLAYBACK_PARAMETERS_CHANGED: {
PlaybackParameters playbackParameters = (PlaybackParameters) msg.obj;
if (!this.playbackParameters.equals(playbackParameters)) {
this.playbackParameters = playbackParameters;
for (EventListener listener : listeners) {
listener.onPlaybackParametersChanged(playbackParameters);
}
}
break;
}
@ -365,6 +434,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
break;
}
default:
throw new IllegalStateException();
}
}

View File

@ -26,16 +26,14 @@ import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerMessage;
import org.telegram.messenger.exoplayer2.source.MediaPeriod;
import org.telegram.messenger.exoplayer2.source.MediaSource;
import org.telegram.messenger.exoplayer2.source.SampleStream;
import org.telegram.messenger.exoplayer2.source.TrackGroupArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelection;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelector;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectorResult;
import org.telegram.messenger.exoplayer2.util.Assertions;
import org.telegram.messenger.exoplayer2.util.MediaClock;
import org.telegram.messenger.exoplayer2.util.PriorityHandlerThread;
import org.telegram.messenger.exoplayer2.util.StandaloneMediaClock;
import org.telegram.messenger.exoplayer2.util.TraceUtil;
import org.telegram.messenger.exoplayer2.util.Util;
import java.io.IOException;
/**
@ -72,20 +70,6 @@ import java.io.IOException;
}
public static final class TrackInfo {
public final TrackGroupArray groups;
public final TrackSelectionArray selections;
public final Object info;
public TrackInfo(TrackGroupArray groups, TrackSelectionArray selections, Object info) {
this.groups = groups;
this.selections = selections;
this.info = info;
}
}
public static final class SourceInfo {
public final Timeline timeline;
@ -105,26 +89,29 @@ import java.io.IOException;
private static final String TAG = "ExoPlayerImplInternal";
// External messages
public static final int MSG_PREPARE_ACK = 0;
public static final int MSG_STATE_CHANGED = 1;
public static final int MSG_LOADING_CHANGED = 2;
public static final int MSG_TRACKS_CHANGED = 3;
public static final int MSG_SEEK_ACK = 4;
public static final int MSG_POSITION_DISCONTINUITY = 5;
public static final int MSG_SOURCE_INFO_REFRESHED = 6;
public static final int MSG_ERROR = 7;
public static final int MSG_PLAYBACK_PARAMETERS_CHANGED = 7;
public static final int MSG_ERROR = 8;
// Internal messages
private static final int MSG_PREPARE = 0;
private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2;
private static final int MSG_SEEK_TO = 3;
private static final int MSG_STOP = 4;
private static final int MSG_RELEASE = 5;
private static final int MSG_REFRESH_SOURCE_INFO = 6;
private static final int MSG_PERIOD_PREPARED = 7;
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 8;
private static final int MSG_TRACK_SELECTION_INVALIDATED = 9;
private static final int MSG_CUSTOM = 10;
private static final int MSG_SET_PLAYBACK_PARAMETERS = 4;
private static final int MSG_STOP = 5;
private static final int MSG_RELEASE = 6;
private static final int MSG_REFRESH_SOURCE_INFO = 7;
private static final int MSG_PERIOD_PREPARED = 8;
private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 9;
private static final int MSG_TRACK_SELECTION_INVALIDATED = 10;
private static final int MSG_CUSTOM = 11;
private static final int PREPARING_SOURCE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10;
@ -137,6 +124,14 @@ import java.io.IOException;
*/
private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100;
/**
* Offset added to all sample timestamps read by renderers to make them non-negative. This is
* provided for convenience of sources that may return negative timestamps due to prerolling
* samples from a keyframe before their first sample with timestamp zero, so it must be set to a
* value greater than or equal to the maximum key-frame interval in seekable periods.
*/
private static final int RENDERER_TIMESTAMP_OFFSET_US = 60000000;
private final Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities;
private final TrackSelector trackSelector;
@ -145,10 +140,12 @@ import java.io.IOException;
private final Handler handler;
private final HandlerThread internalPlaybackThread;
private final Handler eventHandler;
private final ExoPlayer player;
private final Timeline.Window window;
private final Timeline.Period period;
private PlaybackInfo playbackInfo;
private PlaybackParameters playbackParameters;
private Renderer rendererMediaClockSource;
private MediaClock rendererMediaClock;
private MediaSource mediaSource;
@ -174,7 +171,7 @@ import java.io.IOException;
public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl, boolean playWhenReady, Handler eventHandler,
PlaybackInfo playbackInfo) {
PlaybackInfo playbackInfo, ExoPlayer player) {
this.renderers = renderers;
this.trackSelector = trackSelector;
this.loadControl = loadControl;
@ -182,6 +179,7 @@ import java.io.IOException;
this.eventHandler = eventHandler;
this.state = ExoPlayer.STATE_IDLE;
this.playbackInfo = playbackInfo;
this.player = player;
rendererCapabilities = new RendererCapabilities[renderers.length];
for (int i = 0; i < renderers.length; i++) {
@ -193,10 +191,11 @@ import java.io.IOException;
window = new Timeline.Window();
period = new Timeline.Period();
trackSelector.init(this);
playbackParameters = PlaybackParameters.DEFAULT;
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect.
internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",
internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler",
Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
handler = new Handler(internalPlaybackThread.getLooper(), this);
@ -216,6 +215,10 @@ import java.io.IOException;
.sendToTarget();
}
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
}
public void stop() {
handler.sendEmptyMessage(MSG_STOP);
}
@ -309,6 +312,10 @@ import java.io.IOException;
seekToInternal((SeekPosition) msg.obj);
return true;
}
case MSG_SET_PLAYBACK_PARAMETERS: {
setPlaybackParametersInternal((PlaybackParameters) msg.obj);
return true;
}
case MSG_STOP: {
stopInternal();
return true;
@ -376,13 +383,14 @@ import java.io.IOException;
}
private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
resetInternal();
eventHandler.sendEmptyMessage(MSG_PREPARE_ACK);
resetInternal(true);
loadControl.onPrepared();
if (resetPosition) {
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
}
this.mediaSource = mediaSource;
mediaSource.prepareSource(this);
mediaSource.prepareSource(player, true, this);
setState(ExoPlayer.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
@ -460,6 +468,8 @@ import java.io.IOException;
TraceUtil.beginSection("doSomeWork");
updatePlaybackPositions();
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs);
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
for (Renderer renderer : enabledRenderers) {
@ -481,6 +491,19 @@ import java.io.IOException;
maybeThrowPeriodPrepareError();
}
// The standalone media clock never changes playback parameters, so just check the renderer.
if (rendererMediaClock != null) {
PlaybackParameters playbackParameters = rendererMediaClock.getPlaybackParameters();
if (!playbackParameters.equals(this.playbackParameters)) {
// TODO: Make LoadControl, period transition position projection, adaptive track selection
// and potentially any time-related code in renderers take into account the playback speed.
this.playbackParameters = playbackParameters;
standaloneMediaClock.synchronize(rendererMediaClock);
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters)
.sendToTarget();
}
}
long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period)
.getDurationUs();
if (allRenderersEnded
@ -546,12 +569,20 @@ import java.io.IOException;
Pair<Integer, Long> periodPosition = resolveSeekPosition(seekPosition);
if (periodPosition == null) {
// TODO: We should probably propagate an error here.
// We failed to resolve the seek position. Stop the player.
stopInternal();
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed and a suitable seek position could not be resolved in the new one.
playbackInfo = new PlaybackInfo(0, 0);
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't
// ignored.
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
setState(ExoPlayer.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
return;
}
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second;
@ -561,10 +592,13 @@ import java.io.IOException;
// Seek position equals the current position. Do nothing.
return;
}
periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
} finally {
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget();
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
.sendToTarget();
}
}
@ -603,6 +637,7 @@ import java.io.IOException;
enabledRenderers = new Renderer[0];
rendererMediaClock = null;
rendererMediaClockSource = null;
playingPeriodHolder = null;
}
// Update the holders.
@ -628,7 +663,8 @@ import java.io.IOException;
}
private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException {
rendererPositionUs = playingPeriodHolder == null ? periodPositionUs
rendererPositionUs = playingPeriodHolder == null
? periodPositionUs + RENDERER_TIMESTAMP_OFFSET_US
: playingPeriodHolder.toRendererTime(periodPositionUs);
standaloneMediaClock.setPositionUs(rendererPositionUs);
for (Renderer renderer : enabledRenderers) {
@ -636,14 +672,22 @@ import java.io.IOException;
}
}
private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
playbackParameters = rendererMediaClock != null
? rendererMediaClock.setPlaybackParameters(playbackParameters)
: standaloneMediaClock.setPlaybackParameters(playbackParameters);
this.playbackParameters = playbackParameters;
eventHandler.obtainMessage(MSG_PLAYBACK_PARAMETERS_CHANGED, playbackParameters).sendToTarget();
}
private void stopInternal() {
resetInternal();
resetInternal(true);
loadControl.onStopped();
setState(ExoPlayer.STATE_IDLE);
}
private void releaseInternal() {
resetInternal();
resetInternal(true);
loadControl.onReleased();
setState(ExoPlayer.STATE_IDLE);
synchronized (this) {
@ -652,12 +696,13 @@ import java.io.IOException;
}
}
private void resetInternal() {
private void resetInternal(boolean releaseMediaSource) {
handler.removeMessages(MSG_DO_SOME_WORK);
rebuffering = false;
standaloneMediaClock.stop();
rendererMediaClock = null;
rendererMediaClockSource = null;
rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
for (Renderer renderer : enabledRenderers) {
try {
ensureStopped(renderer);
@ -670,15 +715,17 @@ import java.io.IOException;
enabledRenderers = new Renderer[0];
releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder
: loadingPeriodHolder);
if (mediaSource != null) {
mediaSource.releaseSource();
mediaSource = null;
}
loadingPeriodHolder = null;
readingPeriodHolder = null;
playingPeriodHolder = null;
timeline = null;
setIsLoading(false);
if (releaseMediaSource) {
if (mediaSource != null) {
mediaSource.releaseSource();
mediaSource = null;
}
timeline = null;
}
}
private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException {
@ -761,7 +808,7 @@ import java.io.IOException;
if (sampleStream == null) {
// The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take
// over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
standaloneMediaClock.synchronize(rendererMediaClock);
}
rendererMediaClock = null;
rendererMediaClockSource = null;
@ -774,7 +821,8 @@ import java.io.IOException;
}
}
}
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget();
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult)
.sendToTarget();
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
} else {
// Release and re-prepare/buffer periods after the one whose selection changed.
@ -803,9 +851,6 @@ import java.io.IOException;
}
private boolean haveSufficientBuffer(boolean rebuffering) {
if (loadingPeriodHolder == null) {
return false;
}
long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
? loadingPeriodHolder.startPositionUs
: loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
@ -843,18 +888,21 @@ import java.io.IOException;
if (oldTimeline == null) {
if (pendingInitialSeekCount > 0) {
Pair<Integer, Long> periodPosition = resolveSeekPosition(pendingSeekPosition);
if (periodPosition == null) {
// We failed to resolve the seek position. Stop the player.
notifySourceInfoRefresh(manifest, 0);
// TODO: We should probably propagate an error here.
stopInternal();
return;
}
playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
processedInitialSeekCount = pendingInitialSeekCount;
pendingInitialSeekCount = 0;
pendingSeekPosition = null;
if (periodPosition == null) {
// The seek position was valid for the timeline that it was performed into, but the
// timeline has changed and a suitable seek position could not be resolved in the new one.
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second);
} else if (playbackInfo.startPositionUs == C.TIME_UNSET) {
if (timeline.isEmpty()) {
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
Pair<Integer, Long> defaultPosition = getPeriodPosition(0, C.TIME_UNSET);
playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second);
}
@ -874,10 +922,8 @@ import java.io.IOException;
// period whose window we can restart from.
int newPeriodIndex = resolveSubsequentPeriod(periodHolder.index, oldTimeline, timeline);
if (newPeriodIndex == C.INDEX_UNSET) {
// We failed to resolve a subsequent period. Stop the player.
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
// TODO: We should probably propagate an error here.
stopInternal();
// We failed to resolve a suitable restart position.
handleSourceInfoRefreshEndedPlayback(manifest, processedInitialSeekCount);
return;
}
// We resolved a subsequent period. Seek to the default position in the corresponding window.
@ -947,6 +993,18 @@ import java.io.IOException;
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
}
private void handleSourceInfoRefreshEndedPlayback(Object manifest,
int processedInitialSeekCount) {
// Set the playback position to (0,0) for notifying the eventHandler.
playbackInfo = new PlaybackInfo(0, 0);
notifySourceInfoRefresh(manifest, processedInitialSeekCount);
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't ignored.
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
setState(ExoPlayer.STATE_ENDED);
// Reset, but retain the source so that it can still be used should a seek occur.
resetInternal(false);
}
private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) {
eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED,
new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget();
@ -978,6 +1036,8 @@ import java.io.IOException;
*
* @param seekPosition The position to resolve.
* @return The resolved position, or null if resolution was not successful.
* @throws IllegalSeekPositionException If the window index of the seek position is outside the
* bounds of the timeline.
*/
private Pair<Integer, Long> resolveSeekPosition(SeekPosition seekPosition) {
Timeline seekTimeline = seekPosition.timeline;
@ -985,11 +1045,17 @@ import java.io.IOException;
// The application performed a blind seek without a non-empty timeline (most likely based on
// knowledge of what the future timeline will be). Use the internal timeline.
seekTimeline = timeline;
Assertions.checkIndex(seekPosition.windowIndex, 0, timeline.getWindowCount());
}
// Map the SeekPosition to a position in the corresponding timeline.
Pair<Integer, Long> periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex,
seekPosition.windowPositionUs);
Pair<Integer, Long> periodPosition;
try {
periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex,
seekPosition.windowPositionUs);
} catch (IndexOutOfBoundsException e) {
// The window index of the seek position was outside the bounds of the timeline.
throw new IllegalSeekPositionException(timeline, seekPosition.windowIndex,
seekPosition.windowPositionUs);
}
if (timeline == seekTimeline) {
// Our internal timeline is the seek timeline, so the mapped position is correct.
return periodPosition;
@ -1042,6 +1108,7 @@ import java.io.IOException;
*/
private Pair<Integer, Long> getPeriodPosition(Timeline timeline, int windowIndex,
long windowPositionUs, long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs);
if (windowPositionUs == C.TIME_UNSET) {
windowPositionUs = window.getDefaultPositionUs();
@ -1067,66 +1134,8 @@ import java.io.IOException;
return;
}
if (loadingPeriodHolder == null
|| (loadingPeriodHolder.isFullyBuffered() && !loadingPeriodHolder.isLast
&& (playingPeriodHolder == null
|| loadingPeriodHolder.index - playingPeriodHolder.index < MAXIMUM_BUFFER_AHEAD_PERIODS))) {
// We don't have a loading period or it's fully loaded, so try and create the next one.
int newLoadingPeriodIndex = loadingPeriodHolder == null ? playbackInfo.periodIndex
: loadingPeriodHolder.index + 1;
if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
// The period is not available yet.
mediaSource.maybeThrowSourceInfoRefreshError();
} else {
int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
boolean isFirstPeriodInWindow = newLoadingPeriodIndex
== timeline.getWindow(windowIndex, window).firstPeriodIndex;
long periodStartPositionUs;
if (loadingPeriodHolder == null) {
periodStartPositionUs = playbackInfo.startPositionUs;
} else if (!isFirstPeriodInWindow) {
// We're starting to buffer a new period in the current window. Always start from the
// beginning of the period.
periodStartPositionUs = 0;
} else {
// We're starting to buffer a new window. When playback transitions to this window we'll
// want it to be from its default start position. The expected delay until playback
// transitions is equal the duration of media that's currently buffered (assuming no
// interruptions). Hence we project the default start position forward by the duration of
// the buffer, and start buffering from this point.
long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
- rendererPositionUs;
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, windowIndex,
C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
if (defaultPosition == null) {
newLoadingPeriodIndex = C.INDEX_UNSET;
periodStartPositionUs = C.TIME_UNSET;
} else {
newLoadingPeriodIndex = defaultPosition.first;
periodStartPositionUs = defaultPosition.second;
}
}
if (newLoadingPeriodIndex != C.INDEX_UNSET) {
long rendererPositionOffsetUs = loadingPeriodHolder == null ? periodStartPositionUs
: (loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs());
timeline.getPeriod(newLoadingPeriodIndex, period, true);
boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1
&& !timeline.getWindow(period.windowIndex, window).isDynamic;
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid,
newLoadingPeriodIndex, isLastPeriod, periodStartPositionUs);
if (loadingPeriodHolder != null) {
loadingPeriodHolder.next = newPeriodHolder;
}
loadingPeriodHolder = newPeriodHolder;
loadingPeriodHolder.mediaPeriod.prepare(this);
setIsLoading(true);
}
}
}
// Update the loading period if required.
maybeUpdateLoadingPeriod();
if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
setIsLoading(false);
} else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) {
@ -1152,28 +1161,49 @@ import java.io.IOException;
}
if (readingPeriodHolder.isLast) {
// The renderers have their final SampleStreams.
for (Renderer renderer : enabledRenderers) {
renderer.setCurrentStreamIsFinal();
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
// Defer setting the stream as final until the renderer has actually consumed the whole
// stream in case of playlist changes that cause the stream to be no longer final.
if (sampleStream != null && renderer.getStream() == sampleStream
&& renderer.hasReadStreamToEnd()) {
renderer.setCurrentStreamFinal();
}
}
return;
}
for (Renderer renderer : enabledRenderers) {
if (!renderer.hasReadStreamToEnd()) {
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
SampleStream sampleStream = readingPeriodHolder.sampleStreams[i];
if (renderer.getStream() != sampleStream
|| (sampleStream != null && !renderer.hasReadStreamToEnd())) {
return;
}
}
if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) {
TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections;
TrackSelectorResult oldTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
readingPeriodHolder = readingPeriodHolder.next;
TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections;
TrackSelectorResult newTrackSelectorResult = readingPeriodHolder.trackSelectorResult;
boolean initialDiscontinuity =
readingPeriodHolder.mediaPeriod.readDiscontinuity() != C.TIME_UNSET;
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i);
TrackSelection newSelection = newTrackSelections.get(i);
if (oldSelection != null) {
if (newSelection != null) {
TrackSelection oldSelection = oldTrackSelectorResult.selections.get(i);
if (oldSelection == null) {
// The renderer has no current stream and will be enabled when we play the next period.
} else if (initialDiscontinuity) {
// The new period starts with a discontinuity, so the renderer will play out all data then
// be disabled and re-enabled when it starts playing the next period.
renderer.setCurrentStreamFinal();
} else if (!renderer.isCurrentStreamFinal()) {
TrackSelection newSelection = newTrackSelectorResult.selections.get(i);
RendererConfiguration oldConfig = oldTrackSelectorResult.rendererConfigurations[i];
RendererConfiguration newConfig = newTrackSelectorResult.rendererConfigurations[i];
if (newSelection != null && newConfig.equals(oldConfig)) {
// Replace the renderer's SampleStream so the transition to playing the next period can
// be seamless.
Format[] formats = new Format[newSelection.length()];
@ -1183,15 +1213,90 @@ import java.io.IOException;
renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i],
readingPeriodHolder.getRendererOffset());
} else {
// The renderer will be disabled when transitioning to playing the next period. Mark the
// SampleStream as final to play out any remaining data.
renderer.setCurrentStreamIsFinal();
// The renderer will be disabled when transitioning to playing the next period, either
// because there's no new selection or because a configuration change is required. Mark
// the SampleStream as final to play out any remaining data.
renderer.setCurrentStreamFinal();
}
}
}
}
}
private void maybeUpdateLoadingPeriod() throws IOException {
int newLoadingPeriodIndex;
if (loadingPeriodHolder == null) {
newLoadingPeriodIndex = playbackInfo.periodIndex;
} else {
int loadingPeriodIndex = loadingPeriodHolder.index;
if (loadingPeriodHolder.isLast || !loadingPeriodHolder.isFullyBuffered()
|| timeline.getPeriod(loadingPeriodIndex, period).getDurationUs() == C.TIME_UNSET) {
// Either the existing loading period is the last period, or we are not ready to advance to
// loading the next period because it hasn't been fully buffered or its duration is unknown.
return;
}
if (playingPeriodHolder != null
&& loadingPeriodIndex - playingPeriodHolder.index == MAXIMUM_BUFFER_AHEAD_PERIODS) {
// We are already buffering the maximum number of periods ahead.
return;
}
newLoadingPeriodIndex = loadingPeriodHolder.index + 1;
}
if (newLoadingPeriodIndex >= timeline.getPeriodCount()) {
// The next period is not available yet.
mediaSource.maybeThrowSourceInfoRefreshError();
return;
}
long newLoadingPeriodStartPositionUs;
if (loadingPeriodHolder == null) {
newLoadingPeriodStartPositionUs = playbackInfo.positionUs;
} else {
int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
if (newLoadingPeriodIndex
!= timeline.getWindow(newLoadingWindowIndex, window).firstPeriodIndex) {
// We're starting to buffer a new period in the current window. Always start from the
// beginning of the period.
newLoadingPeriodStartPositionUs = 0;
} else {
// We're starting to buffer a new window. When playback transitions to this window we'll
// want it to be from its default start position. The expected delay until playback
// transitions is equal the duration of media that's currently buffered (assuming no
// interruptions). Hence we project the default start position forward by the duration of
// the buffer, and start buffering from this point.
long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()
- rendererPositionUs;
Pair<Integer, Long> defaultPosition = getPeriodPosition(timeline, newLoadingWindowIndex,
C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs));
if (defaultPosition == null) {
return;
}
newLoadingPeriodIndex = defaultPosition.first;
newLoadingPeriodStartPositionUs = defaultPosition.second;
}
}
long rendererPositionOffsetUs = loadingPeriodHolder == null
? newLoadingPeriodStartPositionUs + RENDERER_TIMESTAMP_OFFSET_US
: (loadingPeriodHolder.getRendererOffset()
+ timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs());
timeline.getPeriod(newLoadingPeriodIndex, period, true);
boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1
&& !timeline.getWindow(period.windowIndex, window).isDynamic;
MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities,
rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid,
newLoadingPeriodIndex, isLastPeriod, newLoadingPeriodStartPositionUs);
if (loadingPeriodHolder != null) {
loadingPeriodHolder.next = newPeriodHolder;
}
loadingPeriodHolder = newPeriodHolder;
loadingPeriodHolder.mediaPeriod.prepare(this);
setIsLoading(true);
}
private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException {
if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) {
// Stale event.
@ -1216,7 +1321,8 @@ import java.io.IOException;
}
private void maybeContinueLoading() {
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0
: loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
setIsLoading(false);
} else {
@ -1241,21 +1347,28 @@ import java.io.IOException;
}
private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException {
playingPeriodHolder = periodHolder;
if (playingPeriodHolder == periodHolder) {
return;
}
int enabledRendererCount = 0;
boolean[] rendererWasEnabledFlags = new boolean[renderers.length];
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED;
TrackSelection newSelection = periodHolder.trackSelections.get(i);
TrackSelection newSelection = periodHolder.trackSelectorResult.selections.get(i);
if (newSelection != null) {
// The renderer should be enabled when playing the new period.
enabledRendererCount++;
} else if (rendererWasEnabledFlags[i]) {
// The renderer should be disabled when playing the new period.
}
if (rendererWasEnabledFlags[i] && (newSelection == null
|| (renderer.isCurrentStreamFinal()
&& renderer.getStream() == playingPeriodHolder.sampleStreams[i]))) {
// The renderer should be disabled before playing the next period, either because it's not
// needed to play the next period, or because we need to re-enable it as its current stream
// is final and it's not reading ahead.
if (renderer == rendererMediaClockSource) {
// Sync standaloneMediaClock so that it can take over timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
standaloneMediaClock.synchronize(rendererMediaClock);
rendererMediaClock = null;
rendererMediaClockSource = null;
}
@ -1264,7 +1377,8 @@ import java.io.IOException;
}
}
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget();
playingPeriodHolder = periodHolder;
eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.trackSelectorResult).sendToTarget();
enableRenderers(rendererWasEnabledFlags, enabledRendererCount);
}
@ -1274,10 +1388,12 @@ import java.io.IOException;
enabledRendererCount = 0;
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection newSelection = playingPeriodHolder.trackSelections.get(i);
TrackSelection newSelection = playingPeriodHolder.trackSelectorResult.selections.get(i);
if (newSelection != null) {
enabledRenderers[enabledRendererCount++] = renderer;
if (renderer.getState() == Renderer.STATE_DISABLED) {
RendererConfiguration rendererConfiguration =
playingPeriodHolder.trackSelectorResult.rendererConfigurations[i];
// The renderer needs enabling with its new track selection.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining only if the renderer was previously disabled.
@ -1288,8 +1404,8 @@ import java.io.IOException;
formats[j] = newSelection.getFormat(j);
}
// Enable the renderer.
renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs,
joining, playingPeriodHolder.getRendererOffset());
renderer.enable(rendererConfiguration, formats, playingPeriodHolder.sampleStreams[i],
rendererPositionUs, joining, playingPeriodHolder.getRendererOffset());
MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) {
if (rendererMediaClock != null) {
@ -1298,6 +1414,7 @@ import java.io.IOException;
}
rendererMediaClock = mediaClock;
rendererMediaClockSource = renderer;
rendererMediaClock.setPlaybackParameters(playbackParameters);
}
// Start the renderer if playing.
if (playing) {
@ -1326,6 +1443,7 @@ import java.io.IOException;
public boolean hasEnabledTracks;
public MediaPeriodHolder next;
public boolean needsContinueLoading;
public TrackSelectorResult trackSelectorResult;
private final Renderer[] renderers;
private final RendererCapabilities[] rendererCapabilities;
@ -1333,10 +1451,7 @@ import java.io.IOException;
private final LoadControl loadControl;
private final MediaSource mediaSource;
private Object trackSelectionsInfo;
private TrackGroupArray trackGroups;
private TrackSelectionArray trackSelections;
private TrackSelectionArray periodTrackSelections;
private TrackSelectorResult periodTrackSelectorResult;
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl,
@ -1382,20 +1497,17 @@ import java.io.IOException;
public void handlePrepared() throws ExoPlaybackException {
prepared = true;
trackGroups = mediaPeriod.getTrackGroups();
selectTracks();
startPositionUs = updatePeriodTrackSelection(startPositionUs, false);
}
public boolean selectTracks() throws ExoPlaybackException {
Pair<TrackSelectionArray, Object> selectorResult = trackSelector.selectTracks(
rendererCapabilities, trackGroups);
TrackSelectionArray newTrackSelections = selectorResult.first;
if (newTrackSelections.equals(periodTrackSelections)) {
TrackSelectorResult selectorResult = trackSelector.selectTracks(rendererCapabilities,
mediaPeriod.getTrackGroups());
if (selectorResult.isEquivalent(periodTrackSelectorResult)) {
return false;
}
trackSelections = newTrackSelections;
trackSelectionsInfo = selectorResult.second;
trackSelectorResult = selectorResult;
return true;
}
@ -1406,16 +1518,16 @@ import java.io.IOException;
public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams,
boolean[] streamResetFlags) {
TrackSelectionArray trackSelections = trackSelectorResult.selections;
for (int i = 0; i < trackSelections.length; i++) {
mayRetainStreamFlags[i] = !forceRecreateStreams
&& Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i),
trackSelections.get(i));
&& trackSelectorResult.isEquivalent(periodTrackSelectorResult, i);
}
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags,
sampleStreams, streamResetFlags, positionUs);
periodTrackSelections = trackSelections;
periodTrackSelectorResult = trackSelectorResult;
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
hasEnabledTracks = false;
@ -1429,14 +1541,10 @@ import java.io.IOException;
}
// The track selection has changed.
loadControl.onTracksSelected(renderers, trackGroups, trackSelections);
loadControl.onTracksSelected(renderers, trackSelectorResult.groups, trackSelections);
return positionUs;
}
public TrackInfo getTrackInfo() {
return new TrackInfo(trackGroups, trackSelections, trackSelectionsInfo);
}
public void release() {
try {
mediaSource.releasePeriod(mediaPeriod);

View File

@ -21,18 +21,26 @@ package org.telegram.messenger.exoplayer2;
public interface ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
* The version of the library expressed as a string, for example "1.2.3".
*/
String VERSION = "2.0.4";
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
String VERSION = "2.4.0";
/**
* The version of the library, expressed as an integer.
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
String VERSION_SLASHY = "ExoPlayerLib/2.4.0";
/**
* The version of the library expressed as an integer, for example 1002003.
* <p>
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006).
*/
int VERSION_INT = 2000004;
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
int VERSION_INT = 2004000;
/**
* Whether the library was compiled with {@link org.telegram.messenger.exoplayer2.util.Assertions}
@ -45,5 +53,5 @@ public interface ExoPlayerLibraryInfo {
* trace enabled.
*/
boolean TRACE_ENABLED = true;
}

View File

@ -24,6 +24,7 @@ import org.telegram.messenger.exoplayer2.drm.DrmInitData;
import org.telegram.messenger.exoplayer2.metadata.Metadata;
import org.telegram.messenger.exoplayer2.util.MimeTypes;
import org.telegram.messenger.exoplayer2.util.Util;
import org.telegram.messenger.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@ -120,7 +121,7 @@ public final class Format implements Parcelable {
/**
* The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}.
* C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}.
*/
@C.StereoMode
public final int stereoMode;
@ -128,6 +129,10 @@ public final class Format implements Parcelable {
* The projection data for 360/VR video, or null if not applicable.
*/
public final byte[] projectionData;
/**
* The color metadata associated with the video, helps with accurate color reproduction.
*/
public final ColorInfo colorInfo;
// Audio specific.
@ -183,20 +188,18 @@ public final class Format implements Parcelable {
*/
public final int accessibilityChannel;
// Lazily initialized hashcode and framework media format.
// Lazily initialized hashcode.
private int hashCode;
private MediaFormat frameworkMediaFormat;
// Video.
public static Format createVideoContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, int width, int height,
float frameRate, List<byte[]> initializationData) {
float frameRate, List<byte[]> initializationData, @C.SelectionFlags int selectionFlags) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width,
height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null,
null);
height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, null, null);
}
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
@ -212,17 +215,18 @@ public final class Format implements Parcelable {
DrmInitData drmInitData) {
return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null,
NO_VALUE, drmInitData);
NO_VALUE, null, drmInitData);
}
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate,
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) {
byte[] projectionData, @C.StereoMode int stereoMode, ColorInfo colorInfo,
DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, drmInitData, null);
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE,
OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null);
}
// Audio.
@ -231,8 +235,8 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate,
NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, null, null);
}
@ -259,7 +263,7 @@ public final class Format implements Parcelable {
List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language, Metadata metadata) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding,
encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, drmInitData, metadata);
}
@ -277,38 +281,39 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language, int accessibilityChannel) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel,
OFFSET_SAMPLE_RELATIVE, null, null, null);
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) {
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE);
NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.<byte[]>emptyList());
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, @C.SelectionFlags int selectionFlags, String language,
int accessibilityChannel, DrmInitData drmInitData) {
int bitrate, @C.SelectionFlags int selectionFlags, String language, int accessibilityChannel,
DrmInitData drmInitData) {
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE);
accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE, Collections.<byte[]>emptyList());
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData,
long subsampleOffsetUs) {
return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language,
NO_VALUE, drmInitData, subsampleOffsetUs);
NO_VALUE, drmInitData, subsampleOffsetUs, Collections.<byte[]>emptyList());
}
public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, @C.SelectionFlags int selectionFlags, String language,
int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs) {
int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs,
List<byte[]> initializationData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, null,
drmInitData, null);
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, null);
}
// Image.
@ -316,32 +321,41 @@ public final class Format implements Parcelable {
public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, List<byte[]> initializationData, String language, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData,
null);
}
// Generic.
public static Format createContainerFormat(String id, String containerMimeType, String codecs,
String sampleMimeType, int bitrate) {
public static Format createContainerFormat(String id, String containerMimeType,
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, null);
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null,
null);
}
public static Format createSampleFormat(String id, String sampleMimeType,
long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null);
}
public static Format createSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null);
}
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay,
int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
ColorInfo colorInfo, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding,
int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
int accessibilityChannel, long subsampleOffsetUs, List<byte[]> initializationData,
DrmInitData drmInitData, Metadata metadata) {
this.id = id;
@ -357,6 +371,7 @@ public final class Format implements Parcelable {
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.projectionData = projectionData;
this.stereoMode = stereoMode;
this.colorInfo = colorInfo;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding;
@ -388,6 +403,7 @@ public final class Format implements Parcelable {
boolean hasProjectionData = in.readInt() != 0;
projectionData = hasProjectionData ? in.createByteArray() : null;
stereoMode = in.readInt();
colorInfo = in.readParcelable(ColorInfo.class.getClassLoader());
channelCount = in.readInt();
sampleRate = in.readInt();
pcmEncoding = in.readInt();
@ -409,67 +425,71 @@ public final class Format implements Parcelable {
public Format copyWithMaxInputSize(int maxInputSize) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
@C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
public Format copyWithManifestFormatInfo(Format manifestFormat,
boolean preferManifestDrmInitData) {
@SuppressWarnings("ReferenceEquality")
public Format copyWithManifestFormatInfo(Format manifestFormat) {
if (this == manifestFormat) {
// No need to copy from ourselves.
return this;
}
String id = manifestFormat.id;
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
DrmInitData drmInitData = manifestFormat.drmInitData != null ? manifestFormat.drmInitData
: this.drmInitData;
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags,
language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData,
metadata);
colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
}
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
public Format copyWithDrmInitData(DrmInitData drmInitData) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
public Format copyWithMetadata(Metadata metadata) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
drmInitData, metadata);
stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, metadata);
}
/**
@ -486,31 +506,29 @@ public final class Format implements Parcelable {
@SuppressLint("InlinedApi")
@TargetApi(16)
public final MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
frameworkMediaFormat = format;
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
maybeSetIntegerV16(format, "encoder-delay", encoderDelay);
maybeSetIntegerV16(format, "encoder-padding", encoderPadding);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
return frameworkMediaFormat;
maybeSetColorInfoV24(format, colorInfo);
return format;
}
@Override
public String toString() {
return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", "
+ ", " + language + ", [" + width + ", " + height + ", " + frameRate + "]"
+ language + ", [" + width + ", " + height + ", " + frameRate + "]"
+ ", [" + channelCount + ", " + sampleRate + "])";
}
@ -560,6 +578,7 @@ public final class Format implements Parcelable {
|| !Util.areEqual(codecs, other.codecs)
|| !Util.areEqual(drmInitData, other.drmInitData)
|| !Util.areEqual(metadata, other.metadata)
|| !Util.areEqual(colorInfo, other.colorInfo)
|| !Arrays.equals(projectionData, other.projectionData)
|| initializationData.size() != other.initializationData.size()) {
return false;
@ -572,6 +591,17 @@ public final class Format implements Parcelable {
return true;
}
@TargetApi(24)
private static void maybeSetColorInfoV24(MediaFormat format, ColorInfo colorInfo) {
if (colorInfo == null) {
return;
}
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
maybeSetByteBufferV16(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo);
}
@TargetApi(16)
private static void maybeSetStringV16(MediaFormat format, String key, String value) {
if (value != null) {
@ -593,6 +623,45 @@ public final class Format implements Parcelable {
}
}
@TargetApi(16)
private static void maybeSetByteBufferV16(MediaFormat format, String key, byte[] value) {
if (value != null) {
format.setByteBuffer(key, ByteBuffer.wrap(value));
}
}
// Utility methods
/**
* Returns a prettier {@link String} than {@link #toString()}, intended for logging.
*/
public static String toLogString(Format format) {
if (format == null) {
return "null";
}
StringBuilder builder = new StringBuilder();
builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType);
if (format.bitrate != Format.NO_VALUE) {
builder.append(", bitrate=").append(format.bitrate);
}
if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) {
builder.append(", res=").append(format.width).append("x").append(format.height);
}
if (format.frameRate != Format.NO_VALUE) {
builder.append(", fps=").append(format.frameRate);
}
if (format.channelCount != Format.NO_VALUE) {
builder.append(", channels=").append(format.channelCount);
}
if (format.sampleRate != Format.NO_VALUE) {
builder.append(", sample_rate=").append(format.sampleRate);
}
if (format.language != null) {
builder.append(", language=").append(format.language);
}
return builder.toString();
}
// Parcelable implementation.
@Override
@ -618,6 +687,7 @@ public final class Format implements Parcelable {
dest.writeByteArray(projectionData);
}
dest.writeInt(stereoMode);
dest.writeParcelable(colorInfo, flags);
dest.writeInt(channelCount);
dest.writeInt(sampleRate);
dest.writeInt(pcmEncoding);
@ -636,9 +706,6 @@ public final class Format implements Parcelable {
dest.writeParcelable(metadata, 0);
}
/**
* {@link Creator} implementation.
*/
public static final Creator<Format> CREATOR = new Creator<Format>() {
@Override

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2;
/**
* Thrown when an attempt is made to seek to a position that does not exist in the player's
* {@link Timeline}.
*/
public final class IllegalSeekPositionException extends IllegalStateException {
/**
* The {@link Timeline} in which the seek was attempted.
*/
public final Timeline timeline;
/**
* The index of the window being seeked to.
*/
public final int windowIndex;
/**
* The seek position in the specified window.
*/
public final long positionMs;
/**
* @param timeline The {@link Timeline} in which the seek was attempted.
* @param windowIndex The index of the window being seeked to.
* @param positionMs The seek position in the specified window.
*/
public IllegalSeekPositionException(Timeline timeline, int windowIndex, long positionMs) {
this.timeline = timeline;
this.windowIndex = windowIndex;
this.positionMs = positionMs;
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2;
/**
* The parameters that apply to playback.
*/
public final class PlaybackParameters {
/**
* The default playback parameters: real-time playback with no pitch modification.
*/
public static final PlaybackParameters DEFAULT = new PlaybackParameters(1f, 1f);
/**
* The factor by which playback will be sped up.
*/
public final float speed;
/**
* The factor by which the audio pitch will be scaled.
*/
public final float pitch;
private final int scaledUsPerMs;
/**
* Creates new playback parameters.
*
* @param speed The factor by which playback will be sped up.
* @param pitch The factor by which the audio pitch will be scaled.
*/
public PlaybackParameters(float speed, float pitch) {
this.speed = speed;
this.pitch = pitch;
scaledUsPerMs = Math.round(speed * 1000f);
}
/**
* Scales the millisecond duration {@code timeMs} by the playback speed, returning the result in
* microseconds.
*
* @param timeMs The time to scale, in milliseconds.
* @return The scaled time, in microseconds.
*/
public long getSpeedAdjustedDurationUs(long timeMs) {
return timeMs * scaledUsPerMs;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PlaybackParameters other = (PlaybackParameters) obj;
return this.speed == other.speed && this.pitch == other.pitch;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + Float.floatToRawIntBits(speed);
result = 31 * result + Float.floatToRawIntBits(pitch);
return result;
}
}

View File

@ -92,6 +92,7 @@ public interface Renderer extends ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_DISABLED}.
*
* @param configuration The renderer configuration.
* @param formats The enabled formats.
* @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position.
@ -100,8 +101,8 @@ public interface Renderer extends ExoPlayerComponent {
* before they are rendered.
* @throws ExoPlaybackException If an error occurs.
*/
void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining,
long offsetUs) throws ExoPlaybackException;
void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream,
long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;
/**
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be
@ -149,7 +150,13 @@ public interface Renderer extends ExoPlayerComponent {
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*/
void setCurrentStreamIsFinal();
void setCurrentStreamFinal();
/**
* Returns whether the current {@link SampleStream} will be the final one supplied before the
* renderer is next disabled or reset.
*/
boolean isCurrentStreamFinal();
/**
* Throws an error that's preventing the renderer from reading from its {@link SampleStream}. Does

View File

@ -79,6 +79,20 @@ public interface RendererCapabilities {
*/
int ADAPTIVE_NOT_SUPPORTED = 0b0000;
/**
* A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of
* {@link #TUNNELING_SUPPORTED} and {@link #TUNNELING_NOT_SUPPORTED}.
*/
int TUNNELING_SUPPORT_MASK = 0b10000;
/**
* The {@link Renderer} supports tunneled output.
*/
int TUNNELING_SUPPORTED = 0b10000;
/**
* The {@link Renderer} does not support tunneled output.
*/
int TUNNELING_NOT_SUPPORTED = 0b00000;
/**
* Returns the track type that the {@link Renderer} handles. For example, a video renderer will
* return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a
@ -91,7 +105,7 @@ public interface RendererCapabilities {
/**
* Returns the extent to which the {@link Renderer} supports a given format. The returned value is
* the bitwise OR of two properties:
* the bitwise OR of three properties:
* <ul>
* <li>The level of support for the format itself. One of {@link #FORMAT_HANDLED},
* {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_SUBTYPE} and
@ -99,9 +113,12 @@ public interface RendererCapabilities {
* <li>The level of support for adapting from the format to another format of the same mime type.
* One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and
* {@link #ADAPTIVE_NOT_SUPPORTED}.</li>
* <li>The level of support for tunneling. One of {@link #TUNNELING_SUPPORTED} and
* {@link #TUNNELING_NOT_SUPPORTED}.</li>
* </ul>
* The individual properties can be retrieved by performing a bitwise AND with
* {@link #FORMAT_SUPPORT_MASK} and {@link #ADAPTIVE_SUPPORT_MASK} respectively.
* {@link #FORMAT_SUPPORT_MASK}, {@link #ADAPTIVE_SUPPORT_MASK} and
* {@link #TUNNELING_SUPPORT_MASK} respectively.
*
* @param format The format.
* @return The extent to which the renderer is capable of supporting the given format.

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2;
/**
* The configuration of a {@link Renderer}.
*/
public final class RendererConfiguration {
/**
* The default configuration.
*/
public static final RendererConfiguration DEFAULT =
new RendererConfiguration(C.AUDIO_SESSION_ID_UNSET);
/**
* The audio session id to use for tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling
* should not be enabled.
*/
public final int tunnelingAudioSessionId;
/**
* @param tunnelingAudioSessionId The audio session id to use for tunneling, or
* {@link C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled.
*/
public RendererConfiguration(int tunnelingAudioSessionId) {
this.tunnelingAudioSessionId = tunnelingAudioSessionId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
RendererConfiguration other = (RendererConfiguration) obj;
return tunnelingAudioSessionId == other.tunnelingAudioSessionId;
}
@Override
public int hashCode() {
return tunnelingAudioSessionId;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2;
import android.os.Handler;
import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener;
import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer;
import org.telegram.messenger.exoplayer2.text.TextRenderer;
import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener;
/**
* Builds {@link Renderer} instances for use by a {@link SimpleExoPlayer}.
*/
public interface RenderersFactory {
/**
* Builds the {@link Renderer} instances for a {@link SimpleExoPlayer}.
*
* @param eventHandler A handler to use when invoking event listeners and outputs.
* @param videoRendererEventListener An event listener for video renderers.
* @param videoRendererEventListener An event listener for audio renderers.
* @param textRendererOutput An output for text renderers.
* @param metadataRendererOutput An output for metadata renderers.
* @return The {@link Renderer instances}.
*/
Renderer[] createRenderers(Handler eventHandler,
VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener,
TextRenderer.Output textRendererOutput, MetadataRenderer.Output metadataRendererOutput);
}

View File

@ -16,40 +16,27 @@
package org.telegram.messenger.exoplayer2;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaCodec;
import android.media.PlaybackParams;
import android.os.Handler;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import org.telegram.messenger.exoplayer2.audio.AudioCapabilities;
import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener;
import org.telegram.messenger.exoplayer2.audio.AudioTrack;
import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer;
import org.telegram.messenger.exoplayer2.decoder.DecoderCounters;
import org.telegram.messenger.exoplayer2.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto;
import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector;
import org.telegram.messenger.exoplayer2.metadata.Metadata;
import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer;
import org.telegram.messenger.exoplayer2.metadata.id3.Id3Decoder;
import org.telegram.messenger.exoplayer2.source.MediaSource;
import org.telegram.messenger.exoplayer2.source.TrackGroupArray;
import org.telegram.messenger.exoplayer2.text.Cue;
import org.telegram.messenger.exoplayer2.text.TextRenderer;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray;
import org.telegram.messenger.exoplayer2.trackselection.TrackSelector;
import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer;
import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
/**
@ -93,38 +80,12 @@ public class SimpleExoPlayer implements ExoPlayer {
void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture);
}
/**
* Modes for using extension renderers.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER})
public @interface ExtensionRendererMode {}
/**
* Do not allow use of extension renderers.
*/
public static final int EXTENSION_RENDERER_MODE_OFF = 0;
/**
* Allow use of extension renderers. Extension renderers are indexed after core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use a core renderer to an extension renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_ON = 1;
/**
* Allow use of extension renderers. Extension renderers are indexed before core renderers of the
* same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore
* prefer to use an extension renderer to a core renderer in the case that both are able to play
* a given track.
*/
public static final int EXTENSION_RENDERER_MODE_PREFER = 2;
private static final String TAG = "SimpleExoPlayer";
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
protected final Renderer[] renderers;
private final ExoPlayer player;
private final Renderer[] renderers;
private final ComponentListener componentListener;
private final Handler mainHandler;
private final int videoRendererCount;
private final int audioRendererCount;
@ -150,19 +111,12 @@ public class SimpleExoPlayer implements ExoPlayer {
@C.StreamType
private int audioStreamType;
private float audioVolume;
private PlaybackParamsHolder playbackParamsHolder;
protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) {
mainHandler = new Handler();
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
LoadControl loadControl) {
componentListener = new ComponentListener();
// Build the renderers.
ArrayList<Renderer> renderersList = new ArrayList<>();
buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
allowedVideoJoiningTimeMs, renderersList);
renderers = renderersList.toArray(new Renderer[renderersList.size()]);
renderers = renderersFactory.createRenderers(new Handler(), componentListener,
componentListener, componentListener, componentListener);
// Obtain counts of video and audio renderers.
int videoRendererCount = 0;
@ -182,7 +136,7 @@ public class SimpleExoPlayer implements ExoPlayer {
// Set initial values.
audioVolume = 1;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioStreamType = C.STREAM_TYPE_DEFAULT;
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
@ -244,6 +198,18 @@ public class SimpleExoPlayer implements ExoPlayer {
setVideoSurfaceInternal(surface, false);
}
/**
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surface The surface to clear.
*/
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
/**
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
* rendered. The player will track the lifecycle of the surface automatically.
@ -261,6 +227,18 @@ public class SimpleExoPlayer implements ExoPlayer {
}
}
/**
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
* rendered if it matches the one passed. Else does nothing.
*
* @param surfaceHolder The surface holder to clear.
*/
public void clearVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) {
setVideoSurfaceHolder(null);
}
}
/**
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
* lifecycle of the surface automatically.
@ -268,7 +246,17 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param surfaceView The surface view.
*/
public void setVideoSurfaceView(SurfaceView surfaceView) {
setVideoSurfaceHolder(surfaceView.getHolder());
setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
/**
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surfaceView The texture view to clear.
*/
public void clearVideoSurfaceView(SurfaceView surfaceView) {
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
/**
@ -277,9 +265,13 @@ public class SimpleExoPlayer implements ExoPlayer {
*
* @param textureView The texture view.
*/
public ComponentListener setVideoTextureView(TextureView textureView) {
public void setVideoTextureView(TextureView textureView) {
if (this.textureView == textureView) {
return;
}
removeSurfaceCallbacks();
this.textureView = textureView;
needSetSurface = true;
if (textureView == null) {
setVideoSurfaceInternal(null, true);
} else {
@ -288,13 +280,20 @@ public class SimpleExoPlayer implements ExoPlayer {
}
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture), true);
if (surfaceTexture != null) {
needSetSurface = false;
}
textureView.setSurfaceTextureListener(componentListener);
}
return componentListener;
}
/**
* Clears the {@link TextureView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param textureView The texture view to clear.
*/
public void clearVideoTextureView(TextureView textureView) {
if (textureView != null && textureView == this.textureView) {
setVideoTextureView(null);
}
}
/**
@ -354,37 +353,20 @@ public class SimpleExoPlayer implements ExoPlayer {
/**
* Sets the {@link PlaybackParams} governing audio playback.
*
* @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}.
* @param params The {@link PlaybackParams}, or null to clear any previously set parameters.
*/
@Deprecated
@TargetApi(23)
public void setPlaybackParams(PlaybackParams params) {
public void setPlaybackParams(@Nullable PlaybackParams params) {
PlaybackParameters playbackParameters;
if (params != null) {
// The audio renderers will call this on the playback thread to ensure they can query
// parameters without failure. We do the same up front, which is redundant except that it
// ensures an immediate call to getPlaybackParams will retrieve the instance with defaults
// allowed, rather than this change becoming visible sometime later once the audio renderers
// receive the parameters.
params.allowDefaults();
playbackParamsHolder = new PlaybackParamsHolder(params);
playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
} else {
playbackParamsHolder = null;
playbackParameters = null;
}
ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount];
int count = 0;
for (Renderer renderer : renderers) {
if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) {
messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_PLAYBACK_PARAMS, params);
}
}
player.sendMessages(messages);
}
/**
* Returns the {@link PlaybackParams} governing audio playback, or null if not set.
*/
@TargetApi(23)
public PlaybackParams getPlaybackParams() {
return playbackParamsHolder == null ? null : playbackParamsHolder.params;
setPlaybackParameters(playbackParameters);
}
/**
@ -402,7 +384,7 @@ public class SimpleExoPlayer implements ExoPlayer {
}
/**
* Returns the audio session identifier, or {@code AudioTrack.SESSION_ID_NOT_SET} if not set.
* Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set.
*/
public int getAudioSessionId() {
return audioSessionId;
@ -431,6 +413,57 @@ public class SimpleExoPlayer implements ExoPlayer {
videoListener = listener;
}
/**
* Clears the listener receiving video events if it matches the one passed. Else does nothing.
*
* @param listener The listener to clear.
*/
public void clearVideoListener(VideoListener listener) {
if (videoListener == listener) {
videoListener = null;
}
}
/**
* Sets an output to receive text events.
*
* @param output The output.
*/
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
}
/**
* Clears the output receiving text events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearTextOutput(TextRenderer.Output output) {
if (textOutput == output) {
textOutput = null;
}
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public void setMetadataOutput(MetadataRenderer.Output output) {
metadataOutput = output;
}
/**
* Clears the output receiving metadata events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearMetadataOutput(MetadataRenderer.Output output) {
if (metadataOutput == output) {
metadataOutput = null;
}
}
/**
* Sets a listener to receive debug events from the video renderer.
*
@ -449,33 +482,6 @@ public class SimpleExoPlayer implements ExoPlayer {
audioDebugListener = listener;
}
/**
* Sets an output to receive text events.
*
* @param output The output.
*/
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
}
/**
* @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead.
* @param output The output.
*/
@Deprecated
public void setId3Output(MetadataRenderer.Output output) {
setMetadataOutput(output);
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public void setMetadataOutput(MetadataRenderer.Output output) {
metadataOutput = output;
}
// ExoPlayer implementation
@Override
@ -499,8 +505,8 @@ public class SimpleExoPlayer implements ExoPlayer {
}
@Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline) {
player.prepare(mediaSource, resetPosition, resetTimeline);
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
player.prepare(mediaSource, resetPosition, resetState);
}
@Override
@ -538,6 +544,16 @@ public class SimpleExoPlayer implements ExoPlayer {
player.seekTo(windowIndex, positionMs);
}
@Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
player.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return player.getPlaybackParameters();
}
@Override
public void stop() {
player.stop();
@ -565,6 +581,36 @@ public class SimpleExoPlayer implements ExoPlayer {
player.blockingSendMessages(messages);
}
@Override
public int getRendererCount() {
return player.getRendererCount();
}
@Override
public int getRendererType(int index) {
return player.getRendererType(index);
}
@Override
public TrackGroupArray getCurrentTrackGroups() {
return player.getCurrentTrackGroups();
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return player.getCurrentTrackSelections();
}
@Override
public Timeline getCurrentTimeline() {
return player.getCurrentTimeline();
}
@Override
public Object getCurrentManifest() {
return player.getCurrentManifest();
}
@Override
public int getCurrentPeriodIndex() {
return player.getCurrentPeriodIndex();
@ -596,205 +642,13 @@ public class SimpleExoPlayer implements ExoPlayer {
}
@Override
public int getRendererCount() {
return player.getRendererCount();
public boolean isCurrentWindowDynamic() {
return player.isCurrentWindowDynamic();
}
@Override
public int getRendererType(int index) {
return player.getRendererType(index);
}
@Override
public TrackGroupArray getCurrentTrackGroups() {
return player.getCurrentTrackGroups();
}
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return player.getCurrentTrackSelections();
}
@Override
public Timeline getCurrentTimeline() {
return player.getCurrentTimeline();
}
@Override
public Object getCurrentManifest() {
return player.getCurrentManifest();
}
// Renderer building.
private void buildRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
}
/**
* Builds video renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers
* can attempt to seamlessly join an ongoing playback.
* @param out An array to which the built renderers should be appended.
*/
protected void buildVideoRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs, ArrayList<Renderer> out) {
out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.vp9.LibvpxVideoRenderer");
Constructor<?> constructor = clazz.getConstructor(boolean.class, long.class, Handler.class,
VideoRendererEventListener.class, int.class);
Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs,
mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibvpxVideoRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds audio renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
mainHandler, eventListener, AudioCapabilities.getCapabilities(context)));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
}
int extensionRendererIndex = out.size();
if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
extensionRendererIndex--;
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
Class<?> clazz =
Class.forName("org.telegram.messenger.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) {
// Expected if the app was built without the extension.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Builds text renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildTextRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, TextRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new TextRenderer(output, mainHandler.getLooper()));
}
/**
* Builds metadata renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param output An output for the renderers.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMetadataRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output,
ArrayList<Renderer> out) {
out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder()));
}
/**
* Builds any miscellaneous renderers used by the player.
*
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended.
*/
protected void buildMiscellaneousRenderers(Context context, Handler mainHandler,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
// Do nothing.
public boolean isCurrentWindowSeekable() {
return player.isCurrentWindowSeekable();
}
// Internal methods.
@ -838,11 +692,7 @@ public class SimpleExoPlayer implements ExoPlayer {
this.ownsSurface = ownsSurface;
}
public ComponentListener getComponentListener() {
return componentListener;
}
public final class ComponentListener implements VideoRendererEventListener,
private final class ComponentListener implements VideoRendererEventListener,
AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output,
SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
@ -962,7 +812,7 @@ public class SimpleExoPlayer implements ExoPlayer {
}
audioFormat = null;
audioDecoderCounters = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
}
// TextRenderer.Output implementation
@ -1032,15 +882,4 @@ public class SimpleExoPlayer implements ExoPlayer {
}
@TargetApi(23)
private static final class PlaybackParamsHolder {
public final PlaybackParams params;
public PlaybackParamsHolder(PlaybackParams params) {
this.params = params;
}
}
}

View File

@ -262,9 +262,24 @@ public abstract class Timeline {
*/
public int lastPeriodIndex;
private long defaultPositionUs;
private long durationUs;
private long positionInFirstPeriodUs;
/**
* The default position relative to the start of the window at which to begin playback, in
* microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a
* non-zero default position projection, and if the specified projection cannot be performed
* whilst remaining within the bounds of the window.
*/
public long defaultPositionUs;
/**
* The duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown.
*/
public long durationUs;
/**
* The position of the start of this window relative to the start of the first period belonging
* to it, in microseconds.
*/
public long positionInFirstPeriodUs;
/**
* Sets the data held by this window.
@ -363,19 +378,29 @@ public abstract class Timeline {
*/
public int windowIndex;
private long durationUs;
/**
* The duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown.
*/
public long durationUs;
/**
* Whether this period contains an ad.
*/
public boolean isAd;
private long positionInWindowUs;
/**
* Sets the data held by this period.
*/
public Period set(Object id, Object uid, int windowIndex, long durationUs,
long positionInWindowUs) {
long positionInWindowUs, boolean isAd) {
this.id = id;
this.uid = uid;
this.windowIndex = windowIndex;
this.durationUs = durationUs;
this.positionInWindowUs = positionInWindowUs;
this.isAd = isAd;
return this;
}

View File

@ -28,6 +28,44 @@ import java.nio.ByteBuffer;
*/
public final class Ac3Util {
/**
* Holds sample format information as presented by a syncframe header.
*/
public static final class Ac3SyncFrameInfo {
/**
* The sample mime type of the bitstream. One of {@link MimeTypes#AUDIO_AC3} and
* {@link MimeTypes#AUDIO_E_AC3}.
*/
public final String mimeType;
/**
* The audio sampling rate in Hz.
*/
public final int sampleRate;
/**
* The number of audio channels
*/
public final int channelCount;
/**
* The size of the frame.
*/
public final int frameSize;
/**
* Number of audio samples in the frame.
*/
public final int sampleCount;
private Ac3SyncFrameInfo(String mimeType, int channelCount, int sampleRate, int frameSize,
int sampleCount) {
this.mimeType = mimeType;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.frameSize = frameSize;
this.sampleCount = sampleCount;
}
}
/**
* The number of new samples per (E-)AC-3 audio block.
*/
@ -114,62 +152,61 @@ public final class Ac3Util {
}
/**
* Returns the AC-3 format given {@code data} containing a syncframe. The reading position of
* {@code data} will be modified.
* Returns (E-)AC-3 format information given {@code data} containing a syncframe. The reading
* position of {@code data} will be modified.
*
* @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null.
* @param language The language to set on the format.
* @param drmInitData {@link DrmInitData} to be included in the format.
* @return The AC-3 format parsed from data in the header.
* @return The (E-)AC-3 format data parsed from the header.
*/
public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
String language, DrmInitData drmInitData) {
data.skipBits(16 + 16); // syncword, crc1
int fscod = data.readBits(2);
data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod
int acmod = data.readBits(3);
if ((acmod & 0x01) != 0 && acmod != 1) {
data.skipBits(2); // cmixlev
}
if ((acmod & 0x04) != 0) {
data.skipBits(2); // surmixlev
}
if (acmod == 2) {
data.skipBits(2); // dsurmod
}
boolean lfeon = data.readBit();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATE_BY_FSCOD[fscod], null, drmInitData, 0, language);
}
/**
* Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of
* {@code data} will be modified.
*
* @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null.
* @param language The language to set on the format.
* @param drmInitData {@link DrmInitData} to be included in the format.
* @return The E-AC-3 format parsed from data in the header.
*/
public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
String language, DrmInitData drmInitData) {
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
public static Ac3SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) {
int initialPosition = data.getPosition();
data.skipBits(40);
boolean isEac3 = data.readBits(5) == 16;
data.setPosition(initialPosition);
String mimeType;
int sampleRate;
int fscod = data.readBits(2);
if (fscod == 3) {
sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)];
} else {
data.skipBits(2); // numblkscod
int acmod;
int frameSize;
int sampleCount;
if (isEac3) {
mimeType = MimeTypes.AUDIO_E_AC3;
data.skipBits(16 + 2 + 3); // syncword, strmtype, substreamid
frameSize = (data.readBits(11) + 1) * 2;
int fscod = data.readBits(2);
int audioBlocks;
if (fscod == 3) {
sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)];
audioBlocks = 6;
} else {
int numblkscod = data.readBits(2);
audioBlocks = BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod];
sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
}
sampleCount = AUDIO_SAMPLES_PER_AUDIO_BLOCK * audioBlocks;
acmod = data.readBits(3);
} else /* is AC-3 */ {
mimeType = MimeTypes.AUDIO_AC3;
data.skipBits(16 + 16); // syncword, crc1
int fscod = data.readBits(2);
int frmsizecod = data.readBits(6);
frameSize = getAc3SyncframeSize(fscod, frmsizecod);
data.skipBits(5 + 3); // bsid, bsmod
acmod = data.readBits(3);
if ((acmod & 0x01) != 0 && acmod != 1) {
data.skipBits(2); // cmixlev
}
if ((acmod & 0x04) != 0) {
data.skipBits(2); // surmixlev
}
if (acmod == 2) {
data.skipBits(2); // dsurmod
}
sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
sampleCount = AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT;
}
int acmod = data.readBits(3);
boolean lfeon = data.readBit();
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
drmInitData, 0, language);
int channelCount = CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0);
return new Ac3SyncFrameInfo(mimeType, channelCount, sampleRate, frameSize, sampleCount);
}
/**
@ -187,16 +224,6 @@ public final class Ac3Util {
return getAc3SyncframeSize(fscod, frmsizecod);
}
/**
* Returns the size in bytes of the given E-AC-3 syncframe.
*
* @param data The syncframe to parse.
* @return The syncframe size in bytes.
*/
public static int parseEAc3SyncframeSize(byte[] data) {
return 2 * (((data[2] & 0x07) << 8) + (data[3] & 0xFF) + 1); // frmsiz
}
/**
* Returns the number of audio samples in an AC-3 syncframe.
*/
@ -205,22 +232,10 @@ public final class Ac3Util {
}
/**
* Returns the number of audio samples represented by the given E-AC-3 syncframe.
* Reads the number of audio samples represented by the given E-AC-3 syncframe. The buffer's
* position is not modified.
*
* @param data The syncframe to parse.
* @return The number of audio samples represented by the syncframe.
*/
public static int parseEAc3SyncframeAudioSampleCount(byte[] data) {
// See ETSI TS 102 366 subsection E.1.2.2.
return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (((data[4] & 0xC0) >> 6) == 0x03 ? 6 // fscod
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(data[4] & 0x30) >> 4]);
}
/**
* Like {@link #parseEAc3SyncframeAudioSampleCount(byte[])} but reads from a {@link ByteBuffer}.
* The buffer's position is not modified.
*
* @param buffer The {@link ByteBuffer} from which to read.
* @param buffer The {@link ByteBuffer} from which to read the syncframe.
* @return The number of audio samples represented by the syncframe.
*/
public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) {

View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.audio;
import org.telegram.messenger.exoplayer2.C;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Interface for audio processors.
*/
public interface AudioProcessor {
/**
* Exception thrown when a processor can't be configured for a given input audio format.
*/
final class UnhandledFormatException extends Exception {
public UnhandledFormatException(int sampleRateHz, int channelCount, @C.Encoding int encoding) {
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
+ encoding);
}
}
/**
* An empty, direct {@link ByteBuffer}.
*/
ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
/**
* Configures the processor to process input audio with the specified format. After calling this
* method, {@link #isActive()} returns whether the processor needs to handle buffers; if not, the
* processor will not accept any buffers until it is reconfigured. Returns {@code true} if the
* processor must be flushed, or if the value returned by {@link #isActive()} has changed as a
* result of the call. If it's active, {@link #getOutputChannelCount()} and
* {@link #getOutputEncoding()} return the processor's output format.
*
* @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of input audio.
* @return {@code true} if the processor must be flushed or the value returned by
* {@link #isActive()} has changed as a result of the call.
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/
boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException;
/**
* Returns whether the processor is configured and active.
*/
boolean isActive();
/**
* Returns the number of audio channels in the data output by the processor.
*/
int getOutputChannelCount();
/**
* Returns the audio encoding used in the data output by the processor.
*/
@C.Encoding
int getOutputEncoding();
/**
* Queues audio data between the position and limit of the input {@code buffer} for processing.
* {@code buffer} must be a direct byte buffer with native byte order. Its contents are treated as
* read-only. Its position will be advanced by the number of bytes consumed (which may be zero).
* The caller retains ownership of the provided buffer. Calling this method invalidates any
* previous buffer returned by {@link #getOutput()}.
*
* @param buffer The input buffer to process.
*/
void queueInput(ByteBuffer buffer);
/**
* Queues an end of stream signal. After this method has been called,
* {@link #queueInput(ByteBuffer)} may not be called until after the next call to
* {@link #flush()}. Calling {@link #getOutput()} will return any remaining output data. Multiple
* calls may be required to read all of the remaining output data. {@link #isEnded()} will return
* {@code true} once all remaining output data has been read.
*/
void queueEndOfStream();
/**
* Returns a buffer containing processed output data between its position and limit. The buffer
* will always be a direct byte buffer with native byte order. Calling this method invalidates any
* previously returned buffer. The buffer will be empty if no output is available.
*
* @return A buffer containing processed output data between its position and limit.
*/
ByteBuffer getOutput();
/**
* Returns whether this processor will return no more output from {@link #getOutput()} until it
* has been {@link #flush()}ed and more input has been queued.
*/
boolean isEnded();
/**
* Clears any state in preparation for receiving a new stream of input buffers.
*/
void flush();
/**
* Resets the processor to its initial state.
*/
void reset();
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.audio;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.C.Encoding;
import org.telegram.messenger.exoplayer2.Format;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
* channels. This can be used to reorder, duplicate or discard channels.
*/
/* package */ final class ChannelMappingAudioProcessor implements AudioProcessor {
private int channelCount;
private int sampleRateHz;
private int[] pendingOutputChannels;
private boolean active;
private int[] outputChannels;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
private boolean inputEnded;
/**
* Creates a new processor that applies a channel mapping.
*/
public ChannelMappingAudioProcessor() {
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
}
/**
* Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)}
* to start using the new channel map.
*
* @see AudioTrack#configure(String, int, int, int, int, int[])
*/
public void setChannelMap(int[] outputChannels) {
pendingOutputChannels = outputChannels;
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
throws UnhandledFormatException {
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
outputChannels = pendingOutputChannels;
if (outputChannels == null) {
active = false;
return outputChannelsChanged;
}
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz
&& this.channelCount == channelCount) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
active = channelCount != outputChannels.length;
for (int i = 0; i < outputChannels.length; i++) {
int channelIndex = outputChannels[i];
if (channelIndex >= channelCount) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
active |= (channelIndex != i);
}
return true;
}
@Override
public boolean isActive() {
return active;
}
@Override
public int getOutputChannelCount() {
return outputChannels == null ? channelCount : outputChannels.length;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
int position = inputBuffer.position();
int limit = inputBuffer.limit();
int frameCount = (limit - position) / (2 * channelCount);
int outputSize = frameCount * outputChannels.length * 2;
if (buffer.capacity() < outputSize) {
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
} else {
buffer.clear();
}
while (position < limit) {
for (int channelIndex : outputChannels) {
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
}
position += channelCount * 2;
}
inputBuffer.position(limit);
buffer.flip();
outputBuffer = buffer;
}
@Override
public void queueEndOfStream() {
inputEnded = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
@SuppressWarnings("ReferenceEquality")
@Override
public boolean isEnded() {
return inputEnded && outputBuffer == EMPTY_BUFFER;
}
@Override
public void flush() {
outputBuffer = EMPTY_BUFFER;
inputEnded = false;
}
@Override
public void reset() {
flush();
buffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputChannels = null;
active = false;
}
}

View File

@ -19,12 +19,12 @@ import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.ExoPlaybackException;
import org.telegram.messenger.exoplayer2.Format;
import org.telegram.messenger.exoplayer2.PlaybackParameters;
import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import org.telegram.messenger.exoplayer2.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto;
@ -41,16 +41,16 @@ import java.nio.ByteBuffer;
* Decodes and renders audio using {@link MediaCodec} and {@link AudioTrack}.
*/
@TargetApi(16)
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock,
AudioTrack.Listener {
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock {
private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack;
private boolean passthroughEnabled;
private boolean codecNeedsDiscardChannelsWorkaround;
private android.media.MediaFormat passthroughMediaFormat;
private int pcmEncoding;
private int audioSessionId;
private int channelCount;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
@ -123,14 +123,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param audioProcessors Optional {@link AudioProcessor}s that will process PCM audio before
* output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
AudioProcessor... audioProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack(audioCapabilities, this);
audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener());
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
}
@ -141,8 +143,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
if (!MimeTypes.isAudio(mimeType)) {
return FORMAT_UNSUPPORTED_TYPE;
}
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) {
return ADAPTIVE_NOT_SEAMLESS | FORMAT_HANDLED;
return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | FORMAT_HANDLED;
}
MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false);
if (decoderInfo == null) {
@ -155,7 +158,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
&& (format.channelCount == Format.NO_VALUE
|| decoderInfo.isAudioChannelCountSupportedV21(format.channelCount)));
int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES;
return ADAPTIVE_NOT_SEAMLESS | formatSupport;
return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport;
}
@Override
@ -185,7 +188,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) {
codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name);
if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
@ -217,39 +222,72 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
// output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding
: C.ENCODING_PCM_16BIT;
channelCount = newFormat.channelCount;
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
int[] channelMap;
if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && this.channelCount < 6) {
channelMap = new int[this.channelCount];
for (int i = 0; i < this.channelCount; i++) {
channelMap[i] = i;
}
} else {
channelMap = null;
}
try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0, channelMap);
} catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
/**
* Called when the audio session id becomes known. Once the id is known it will not change (and
* hence this method will not be called again) unless the renderer is disabled and then
* subsequently re-enabled.
* <p>
* The default implementation is a no-op. One reason for overriding this method would be to
* instantiate and enable a {@link Virtualizer} in order to spatialize the audio channels. For
* this use case, any {@link Virtualizer} instances should be released in {@link #onDisabled()}
* (if not before).
* Called when the audio session id becomes known. The default implementation is a no-op. One
* reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in
* order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
* should be released in {@link #onDisabled()} (if not before).
*
* @param audioSessionId The audio session id.
* @see AudioTrack.Listener#onAudioSessionId(int)
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
}
/**
* @see AudioTrack.Listener#onPositionDiscontinuity()
*/
protected void onAudioTrackPositionDiscontinuity() {
// Do nothing.
}
/**
* @see AudioTrack.Listener#onUnderrun(int, long, long)
*/
protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
long elapsedSinceLastFeedMs) {
// Do nothing.
}
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining);
eventDispatcher.enabled(decoderCounters);
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else {
audioTrack.disableTunneling();
}
}
@Override
@ -274,7 +312,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected void onDisabled() {
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();
} finally {
@ -289,7 +326,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
public boolean isEnded() {
return super.isEnded() && !audioTrack.hasPendingData();
return super.isEnded() && audioTrack.isEnded();
}
@Override
@ -308,6 +345,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return currentPositionUs;
}
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
return audioTrack.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return audioTrack.getPlaybackParameters();
}
@Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs,
@ -325,54 +372,25 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return true;
}
if (!audioTrack.isInitialized()) {
// Initialize the AudioTrack now.
try {
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
eventDispatcher.audioSessionId(audioSessionId);
onAudioSessionId(audioSessionId);
} else {
audioTrack.initialize(audioSessionId);
}
} catch (AudioTrack.InitializationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
if (getState() == STATE_STARTED) {
audioTrack.play();
}
}
int handleBufferResult;
try {
handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs);
} catch (AudioTrack.WriteException e) {
if (audioTrack.handleBuffer(buffer, bufferPresentationTimeUs)) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.renderedOutputBufferCount++;
return true;
}
} catch (AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
// If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
handleAudioTrackDiscontinuity();
allowPositionDiscontinuity = true;
}
// Release the buffer if it was consumed.
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
codec.releaseOutputBuffer(bufferIndex, false);
decoderCounters.renderedOutputBufferCount++;
return true;
}
return false;
}
@Override
protected void onOutputStreamEnded() {
audioTrack.handleEndOfStream();
}
protected void handleAudioTrackDiscontinuity() {
// Do nothing
protected void renderToEndOfStream() throws ExoPlaybackException {
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
@Override
@ -381,14 +399,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
case C.MSG_SET_VOLUME:
audioTrack.setVolume((Float) message);
break;
case C.MSG_SET_PLAYBACK_PARAMS:
audioTrack.setPlaybackParams((PlaybackParams) message);
break;
case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message;
if (audioTrack.setStreamType(streamType)) {
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
}
audioTrack.setStreamType(streamType);
break;
default:
super.handleMessage(messageType, message);
@ -396,11 +409,41 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
}
// AudioTrack.Listener implementation.
/**
* Returns whether the decoder is known to output six audio channels when provided with input with
* fewer than six channels.
* <p>
* See [Internal: b/35655036].
*/
private static boolean codecNeedsDiscardChannelsWorkaround(String codecName) {
// The workaround applies to Samsung Galaxy S6 and Samsung Galaxy S7.
return Util.SDK_INT < 24 && "OMX.SEC.aac.dec".equals(codecName)
&& "samsung".equals(Util.MANUFACTURER)
&& (Util.DEVICE.startsWith("zeroflte") || Util.DEVICE.startsWith("herolte")
|| Util.DEVICE.startsWith("heroqlte"));
}
private final class AudioTrackListener implements AudioTrack.Listener {
@Override
public void onAudioSessionId(int audioSessionId) {
eventDispatcher.audioSessionId(audioSessionId);
MediaCodecAudioRenderer.this.onAudioSessionId(audioSessionId);
}
@Override
public void onPositionDiscontinuity() {
onAudioTrackPositionDiscontinuity();
// We are out of sync so allow currentPositionUs to jump backwards.
MediaCodecAudioRenderer.this.allowPositionDiscontinuity = true;
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
}

View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.audio;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.Format;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* An {@link AudioProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/
/* package */ final class ResamplingAudioProcessor implements AudioProcessor {
private int sampleRateHz;
private int channelCount;
@C.PcmEncoding
private int encoding;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
private boolean inputEnded;
/**
* Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/
public ResamplingAudioProcessor() {
sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE;
encoding = C.ENCODING_INVALID;
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
&& this.encoding == encoding) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.encoding = encoding;
if (encoding == C.ENCODING_PCM_16BIT) {
buffer = EMPTY_BUFFER;
}
return true;
}
@Override
public boolean isActive() {
return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT;
}
@Override
public int getOutputChannelCount() {
return channelCount;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
// Prepare the output buffer.
int position = inputBuffer.position();
int limit = inputBuffer.limit();
int size = limit - position;
int resampledSize;
switch (encoding) {
case C.ENCODING_PCM_8BIT:
resampledSize = size * 2;
break;
case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 2;
break;
case C.ENCODING_PCM_32BIT:
resampledSize = size / 2;
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
throw new IllegalStateException();
}
if (buffer.capacity() < resampledSize) {
buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder());
} else {
buffer.clear();
}
// Resample the little endian input and update the input/output buffers.
switch (encoding) {
case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = position; i < limit; i++) {
buffer.put((byte) 0);
buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128));
}
break;
case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte.
for (int i = position; i < limit; i += 3) {
buffer.put(inputBuffer.get(i + 1));
buffer.put(inputBuffer.get(i + 2));
}
break;
case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes.
for (int i = position; i < limit; i += 4) {
buffer.put(inputBuffer.get(i + 2));
buffer.put(inputBuffer.get(i + 3));
}
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
inputBuffer.position(inputBuffer.limit());
buffer.flip();
outputBuffer = buffer;
}
@Override
public void queueEndOfStream() {
inputEnded = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
@SuppressWarnings("ReferenceEquality")
@Override
public boolean isEnded() {
return inputEnded && outputBuffer == EMPTY_BUFFER;
}
@Override
public void flush() {
outputBuffer = EMPTY_BUFFER;
inputEnded = false;
}
@Override
public void reset() {
flush();
buffer = EMPTY_BUFFER;
sampleRateHz = Format.NO_VALUE;
channelCount = Format.NO_VALUE;
encoding = C.ENCODING_INVALID;
}
}

View File

@ -15,15 +15,17 @@
*/
package org.telegram.messenger.exoplayer2.audio;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.IntDef;
import org.telegram.messenger.exoplayer2.BaseRenderer;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.ExoPlaybackException;
import org.telegram.messenger.exoplayer2.Format;
import org.telegram.messenger.exoplayer2.FormatHolder;
import org.telegram.messenger.exoplayer2.PlaybackParameters;
import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
import org.telegram.messenger.exoplayer2.decoder.DecoderCounters;
import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer;
@ -32,23 +34,46 @@ import org.telegram.messenger.exoplayer2.decoder.SimpleOutputBuffer;
import org.telegram.messenger.exoplayer2.drm.DrmSession;
import org.telegram.messenger.exoplayer2.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer2.drm.ExoMediaCrypto;
import org.telegram.messenger.exoplayer2.util.Assertions;
import org.telegram.messenger.exoplayer2.util.MediaClock;
import org.telegram.messenger.exoplayer2.util.MimeTypes;
import org.telegram.messenger.exoplayer2.util.TraceUtil;
import org.telegram.messenger.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Decodes and renders audio using a {@link SimpleDecoder}.
*/
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock,
AudioTrack.Listener {
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock {
@Retention(RetentionPolicy.SOURCE)
@IntDef({REINITIALIZATION_STATE_NONE, REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM,
REINITIALIZATION_STATE_WAIT_END_OF_STREAM})
private @interface ReinitializationState {}
/**
* The decoder does not need to be re-initialized.
*/
private static final int REINITIALIZATION_STATE_NONE = 0;
/**
* The input format has changed in a way that requires the decoder to be re-initialized, but we
* haven't yet signaled an end of stream to the existing decoder. We need to do so in order to
* ensure that it outputs any remaining buffers before we release it.
*/
private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1;
/**
* The input format has changed in a way that requires the decoder to be re-initialized, and we've
* signaled an end of stream to the existing decoder. We're waiting for the decoder to output an
* end of stream signal to indicate that it has output any remaining buffers before we release it.
*/
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final FormatHolder formatHolder;
private final DecoderInputBuffer flagsOnlyBuffer;
private DecoderCounters decoderCounters;
private Format inputFormat;
@ -59,14 +84,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
private DrmSession<ExoMediaCrypto> drmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession;
@ReinitializationState private int decoderReinitializationState;
private boolean decoderReceivedBuffers;
private boolean audioTrackNeedsConfigure;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean waitingForKeys;
private int audioSessionId;
public SimpleDecoderAudioRenderer() {
this(null, null);
}
@ -75,10 +102,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) {
this(eventHandler, eventListener, null);
AudioRendererEventListener eventListener, AudioProcessor... audioProcessors) {
this(eventHandler, eventListener, null, null, false, audioProcessors);
}
/**
@ -106,17 +134,21 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
AudioProcessor... audioProcessors) {
super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, this);
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener());
formatHolder = new FormatHolder();
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true;
}
@Override
@ -124,59 +156,98 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return this;
}
@Override
public final int supportsFormat(Format format) {
int formatSupport = supportsFormatInternal(format);
if (formatSupport == FORMAT_UNSUPPORTED_TYPE || formatSupport == FORMAT_UNSUPPORTED_SUBTYPE) {
return formatSupport;
}
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport;
}
/**
* Returns the {@link #FORMAT_SUPPORT_MASK} component of the return value for
* {@link #supportsFormat(Format)}.
*
* @param format The format.
* @return The extent to which the renderer supports the format itself.
*/
protected abstract int supportsFormatInternal(Format format);
@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) {
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
return;
}
// Try and read a format if we don't have one already.
if (inputFormat == null && !readFormat()) {
// We can't make progress without one.
return;
}
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
if (inputFormat == null) {
// We don't have a format yet, so try and read one.
flagsOnlyBuffer.clear();
int result = readSource(formatHolder, flagsOnlyBuffer, true);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
// End of stream read having not read a format.
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
return;
} else {
// The drm session isn't open yet.
// We still don't have a format and can't make progress without one.
return;
}
}
// If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) {
maybeInitDecoder();
if (decoder != null) {
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createAudioDecoder");
decoder = createDecoder(inputFormat, mediaCrypto);
// Rendering loop.
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer()) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (AudioDecoderException e) {
} catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoderCounters.ensureUpdated();
}
}
// Rendering loop.
try {
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer()) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
| AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoderCounters.ensureUpdated();
/**
* Called when the audio session id becomes known. The default implementation is a no-op. One
* reason for overriding this method would be to instantiate and enable a {@link Virtualizer} in
* order to spatialize the audio channels. For this use case, any {@link Virtualizer} instances
* should be released in {@link #onDisabled()} (if not before).
*
* @see AudioTrack.Listener#onAudioSessionId(int)
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
}
/**
* @see AudioTrack.Listener#onPositionDiscontinuity()
*/
protected void onAudioTrackPositionDiscontinuity() {
// Do nothing.
}
/**
* @see AudioTrack.Listener#onUnderrun(int, long, long)
*/
protected void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
long elapsedSinceLastFeedMs) {
// Do nothing.
}
/**
@ -205,12 +276,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
null, null, 0, null);
}
private boolean drainOutputBuffer() throws AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException {
if (outputStreamEnded) {
return false;
}
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {
@ -220,38 +288,28 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
if (outputBuffer.isEndOfStream()) {
outputStreamEnded = true;
audioTrack.handleEndOfStream();
outputBuffer.release();
outputBuffer = null;
if (decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
// We're waiting to re-initialize the decoder, and have now processed all final buffers.
releaseDecoder();
maybeInitDecoder();
// The audio track may need to be recreated once the new output format is known.
audioTrackNeedsConfigure = true;
} else {
outputBuffer.release();
outputBuffer = null;
processEndOfStream();
}
return false;
}
if (!audioTrack.isInitialized()) {
if (audioTrackNeedsConfigure) {
Format outputFormat = getOutputFormat();
audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount,
outputFormat.sampleRate, outputFormat.pcmEncoding, 0);
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
eventDispatcher.audioSessionId(audioSessionId);
onAudioSessionId(audioSessionId);
} else {
audioTrack.initialize(audioSessionId);
}
if (getState() == STATE_STARTED) {
audioTrack.play();
}
audioTrackNeedsConfigure = false;
}
int handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs);
// If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
allowPositionDiscontinuity = true;
}
// Release the buffer if it was consumed.
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
if (audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs)) {
decoderCounters.renderedOutputBufferCount++;
outputBuffer.release();
outputBuffer = null;
@ -262,7 +320,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException {
if (inputStreamEnded) {
if (decoder == null || decoderReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM
|| inputStreamEnded) {
// We need to reinitialize the decoder or the input stream has ended.
return false;
}
@ -273,12 +333,20 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
}
if (decoderReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) {
inputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
decoder.queueInputBuffer(inputBuffer);
inputBuffer = null;
decoderReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM;
return false;
}
int result;
if (waitingForKeys) {
// We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ;
} else {
result = readSource(formatHolder, inputBuffer);
result = readSource(formatHolder, inputBuffer, false);
}
if (result == C.RESULT_NOTHING_READ) {
@ -301,6 +369,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer);
decoderReceivedBuffers = true;
decoderCounters.inputBufferCount++;
inputBuffer = null;
return true;
@ -318,19 +387,34 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
}
private void flushDecoder() {
inputBuffer = null;
waitingForKeys = false;
if (outputBuffer != null) {
outputBuffer.release();
outputBuffer = null;
private void processEndOfStream() throws ExoPlaybackException {
outputStreamEnded = true;
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
}
}
private void flushDecoder() throws ExoPlaybackException {
waitingForKeys = false;
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
releaseDecoder();
maybeInitDecoder();
} else {
inputBuffer = null;
if (outputBuffer != null) {
outputBuffer.release();
outputBuffer = null;
}
decoder.flush();
decoderReceivedBuffers = false;
}
decoder.flush();
}
@Override
public boolean isEnded() {
return outputStreamEnded && !audioTrack.hasPendingData();
return outputStreamEnded && audioTrack.isEnded();
}
@Override
@ -350,27 +434,30 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return currentPositionUs;
}
/**
* Called when the audio session id becomes known. Once the id is known it will not change (and
* hence this method will not be called again) unless the renderer is disabled and then
* subsequently re-enabled.
* <p>
* The default implementation is a no-op.
*
* @param audioSessionId The audio session id.
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
return audioTrack.setPlaybackParameters(playbackParameters);
}
@Override
public PlaybackParameters getPlaybackParameters() {
return audioTrack.getPlaybackParameters();
}
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException {
decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters);
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
audioTrack.enableTunnelingV21(tunnelingAudioSessionId);
} else {
audioTrack.disableTunneling();
}
}
@Override
protected void onPositionReset(long positionUs, boolean joining) {
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
@ -393,17 +480,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override
protected void onDisabled() {
inputBuffer = null;
outputBuffer = null;
inputFormat = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrackNeedsConfigure = true;
waitingForKeys = false;
try {
if (decoder != null) {
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
}
releaseDecoder();
audioTrack.release();
} finally {
try {
@ -425,13 +506,52 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
}
private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
return true;
private void maybeInitDecoder() throws ExoPlaybackException {
if (decoder != null) {
return;
}
return false;
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
} else {
// The drm session isn't open yet.
return;
}
}
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createAudioDecoder");
decoder = createDecoder(inputFormat, mediaCrypto);
TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
decoderCounters.decoderInitCount++;
} catch (AudioDecoderException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
private void releaseDecoder() {
if (decoder == null) {
return;
}
inputBuffer = null;
outputBuffer = null;
decoder.release();
decoder = null;
decoderCounters.decoderReleaseCount++;
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
decoderReceivedBuffers = false;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
@ -456,6 +576,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
}
if (decoderReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
} else {
// There aren't any final output buffers, so release the decoder immediately.
releaseDecoder();
maybeInitDecoder();
audioTrackNeedsConfigure = true;
}
eventDispatcher.inputFormatChanged(newFormat);
}
@ -465,14 +595,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
case C.MSG_SET_VOLUME:
audioTrack.setVolume((Float) message);
break;
case C.MSG_SET_PLAYBACK_PARAMS:
audioTrack.setPlaybackParams((PlaybackParams) message);
break;
case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message;
if (audioTrack.setStreamType(streamType)) {
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
}
audioTrack.setStreamType(streamType);
break;
default:
super.handleMessage(messageType, message);
@ -480,11 +605,27 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
}
// AudioTrack.Listener implementation.
private final class AudioTrackListener implements AudioTrack.Listener {
@Override
public void onAudioSessionId(int audioSessionId) {
eventDispatcher.audioSessionId(audioSessionId);
SimpleDecoderAudioRenderer.this.onAudioSessionId(audioSessionId);
}
@Override
public void onPositionDiscontinuity() {
onAudioTrackPositionDiscontinuity();
// We are out of sync so allow currentPositionUs to jump backwards.
SimpleDecoderAudioRenderer.this.allowPositionDiscontinuity = true;
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
}

View File

@ -0,0 +1,534 @@
/*
* Copyright (C) 2017 The Android Open Source Project
* Copyright (C) 2010 Bill Cox, Sonic Library
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.audio;
import org.telegram.messenger.exoplayer2.util.Assertions;
import java.nio.ShortBuffer;
import java.util.Arrays;
/**
* Sonic audio stream processor for time/pitch stretching.
* <p>
* Based on https://github.com/waywardgeek/sonic.
*/
/* package */ final class Sonic {
private static final boolean USE_CHORD_PITCH = false;
private static final int MINIMUM_PITCH = 65;
private static final int MAXIMUM_PITCH = 400;
private static final int AMDF_FREQUENCY = 4000;
private final int sampleRate;
private final int numChannels;
private final int minPeriod;
private final int maxPeriod;
private final int maxRequired;
private final short[] downSampleBuffer;
private int inputBufferSize;
private short[] inputBuffer;
private int outputBufferSize;
private short[] outputBuffer;
private int pitchBufferSize;
private short[] pitchBuffer;
private int oldRatePosition;
private int newRatePosition;
private float speed;
private float pitch;
private int numInputSamples;
private int numOutputSamples;
private int numPitchSamples;
private int remainingInputToCopy;
private int prevPeriod;
private int prevMinDiff;
private int minDiff;
private int maxDiff;
/**
* Creates a new Sonic audio stream processor.
*
* @param sampleRate The sample rate of input audio.
* @param numChannels The number of channels in the input audio.
*/
public Sonic(int sampleRate, int numChannels) {
this.sampleRate = sampleRate;
this.numChannels = numChannels;
minPeriod = sampleRate / MAXIMUM_PITCH;
maxPeriod = sampleRate / MINIMUM_PITCH;
maxRequired = 2 * maxPeriod;
downSampleBuffer = new short[maxRequired];
inputBufferSize = maxRequired;
inputBuffer = new short[maxRequired * numChannels];
outputBufferSize = maxRequired;
outputBuffer = new short[maxRequired * numChannels];
pitchBufferSize = maxRequired;
pitchBuffer = new short[maxRequired * numChannels];
oldRatePosition = 0;
newRatePosition = 0;
prevPeriod = 0;
speed = 1.0f;
pitch = 1.0f;
}
/**
* Sets the output speed.
*/
public void setSpeed(float speed) {
this.speed = speed;
}
/**
* Gets the output speed.
*/
public float getSpeed() {
return speed;
}
/**
* Sets the output pitch.
*/
public void setPitch(float pitch) {
this.pitch = pitch;
}
/**
* Gets the output pitch.
*/
public float getPitch() {
return pitch;
}
/**
* Queues remaining data from {@code buffer}, and advances its position by the number of bytes
* consumed.
*
* @param buffer A {@link ShortBuffer} containing input data between its position and limit.
*/
public void queueInput(ShortBuffer buffer) {
int samplesToWrite = buffer.remaining() / numChannels;
int bytesToWrite = samplesToWrite * numChannels * 2;
enlargeInputBufferIfNeeded(samplesToWrite);
buffer.get(inputBuffer, numInputSamples * numChannels, bytesToWrite / 2);
numInputSamples += samplesToWrite;
processStreamInput();
}
/**
* Gets available output, outputting to the start of {@code buffer}. The buffer's position will be
* advanced by the number of bytes written.
*
* @param buffer A {@link ShortBuffer} into which output will be written.
*/
public void getOutput(ShortBuffer buffer) {
int samplesToRead = Math.min(buffer.remaining() / numChannels, numOutputSamples);
buffer.put(outputBuffer, 0, samplesToRead * numChannels);
numOutputSamples -= samplesToRead;
System.arraycopy(outputBuffer, samplesToRead * numChannels, outputBuffer, 0,
numOutputSamples * numChannels);
}
/**
* Forces generating output using whatever data has been queued already. No extra delay will be
* added to the output, but flushing in the middle of words could introduce distortion.
*/
public void queueEndOfStream() {
int remainingSamples = numInputSamples;
float s = speed / pitch;
int expectedOutputSamples =
numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / pitch + 0.5f);
// Add enough silence to flush both input and pitch buffers.
enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired);
for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) {
inputBuffer[remainingSamples * numChannels + xSample] = 0;
}
numInputSamples += 2 * maxRequired;
processStreamInput();
// Throw away any extra samples we generated due to the silence we added.
if (numOutputSamples > expectedOutputSamples) {
numOutputSamples = expectedOutputSamples;
}
// Empty input and pitch buffers.
numInputSamples = 0;
remainingInputToCopy = 0;
numPitchSamples = 0;
}
/**
* Returns the number of output samples that can be read with {@link #getOutput(ShortBuffer)}.
*/
public int getSamplesAvailable() {
return numOutputSamples;
}
// Internal methods.
private void enlargeOutputBufferIfNeeded(int numSamples) {
if (numOutputSamples + numSamples > outputBufferSize) {
outputBufferSize += (outputBufferSize / 2) + numSamples;
outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * numChannels);
}
}
private void enlargeInputBufferIfNeeded(int numSamples) {
if (numInputSamples + numSamples > inputBufferSize) {
inputBufferSize += (inputBufferSize / 2) + numSamples;
inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSize * numChannels);
}
}
private void removeProcessedInputSamples(int position) {
int remainingSamples = numInputSamples - position;
System.arraycopy(inputBuffer, position * numChannels, inputBuffer, 0,
remainingSamples * numChannels);
numInputSamples = remainingSamples;
}
private void copyToOutput(short[] samples, int position, int numSamples) {
enlargeOutputBufferIfNeeded(numSamples);
System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels,
numSamples * numChannels);
numOutputSamples += numSamples;
}
private int copyInputToOutput(int position) {
int numSamples = Math.min(maxRequired, remainingInputToCopy);
copyToOutput(inputBuffer, position, numSamples);
remainingInputToCopy -= numSamples;
return numSamples;
}
private void downSampleInput(short[] samples, int position, int skip) {
// If skip is greater than one, average skip samples together and write them to the down-sample
// buffer. If numChannels is greater than one, mix the channels together as we down sample.
int numSamples = maxRequired / skip;
int samplesPerValue = numChannels * skip;
position *= numChannels;
for (int i = 0; i < numSamples; i++) {
int value = 0;
for (int j = 0; j < samplesPerValue; j++) {
value += samples[position + i * samplesPerValue + j];
}
value /= samplesPerValue;
downSampleBuffer[i] = (short) value;
}
}
private int findPitchPeriodInRange(short[] samples, int position, int minPeriod, int maxPeriod) {
// Find the best frequency match in the range, and given a sample skip multiple. For now, just
// find the pitch of the first channel.
int bestPeriod = 0;
int worstPeriod = 255;
int minDiff = 1;
int maxDiff = 0;
position *= numChannels;
for (int period = minPeriod; period <= maxPeriod; period++) {
int diff = 0;
for (int i = 0; i < period; i++) {
short sVal = samples[position + i];
short pVal = samples[position + period + i];
diff += sVal >= pVal ? sVal - pVal : pVal - sVal;
}
// Note that the highest number of samples we add into diff will be less than 256, since we
// skip samples. Thus, diff is a 24 bit number, and we can safely multiply by numSamples
// without overflow.
if (diff * bestPeriod < minDiff * period) {
minDiff = diff;
bestPeriod = period;
}
if (diff * worstPeriod > maxDiff * period) {
maxDiff = diff;
worstPeriod = period;
}
}
this.minDiff = minDiff / bestPeriod;
this.maxDiff = maxDiff / worstPeriod;
return bestPeriod;
}
/**
* Returns whether the previous pitch period estimate is a better approximation, which can occur
* at the abrupt end of voiced words.
*/
private boolean previousPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) {
if (minDiff == 0 || prevPeriod == 0) {
return false;
}
if (preferNewPeriod) {
if (maxDiff > minDiff * 3) {
// Got a reasonable match this period
return false;
}
if (minDiff * 2 <= prevMinDiff * 3) {
// Mismatch is not that much greater this period
return false;
}
} else {
if (minDiff <= prevMinDiff) {
return false;
}
}
return true;
}
private int findPitchPeriod(short[] samples, int position, boolean preferNewPeriod) {
// Find the pitch period. This is a critical step, and we may have to try multiple ways to get a
// good answer. This version uses AMDF. To improve speed, we down sample by an integer factor
// get in the 11 kHz range, and then do it again with a narrower frequency range without down
// sampling.
int period;
int retPeriod;
int skip = sampleRate > AMDF_FREQUENCY ? sampleRate / AMDF_FREQUENCY : 1;
if (numChannels == 1 && skip == 1) {
period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod);
} else {
downSampleInput(samples, position, skip);
period = findPitchPeriodInRange(downSampleBuffer, 0, minPeriod / skip, maxPeriod / skip);
if (skip != 1) {
period *= skip;
int minP = period - (skip * 4);
int maxP = period + (skip * 4);
if (minP < minPeriod) {
minP = minPeriod;
}
if (maxP > maxPeriod) {
maxP = maxPeriod;
}
if (numChannels == 1) {
period = findPitchPeriodInRange(samples, position, minP, maxP);
} else {
downSampleInput(samples, position, 1);
period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP);
}
}
}
if (previousPeriodBetter(minDiff, maxDiff, preferNewPeriod)) {
retPeriod = prevPeriod;
} else {
retPeriod = period;
}
prevMinDiff = minDiff;
prevPeriod = period;
return retPeriod;
}
private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) {
int numSamples = numOutputSamples - originalNumOutputSamples;
if (numPitchSamples + numSamples > pitchBufferSize) {
pitchBufferSize += (pitchBufferSize / 2) + numSamples;
pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * numChannels);
}
System.arraycopy(outputBuffer, originalNumOutputSamples * numChannels, pitchBuffer,
numPitchSamples * numChannels, numSamples * numChannels);
numOutputSamples = originalNumOutputSamples;
numPitchSamples += numSamples;
}
private void removePitchSamples(int numSamples) {
if (numSamples == 0) {
return;
}
System.arraycopy(pitchBuffer, numSamples * numChannels, pitchBuffer, 0,
(numPitchSamples - numSamples) * numChannels);
numPitchSamples -= numSamples;
}
private void adjustPitch(int originalNumOutputSamples) {
// Latency due to pitch changes could be reduced by looking at past samples to determine pitch,
// rather than future.
if (numOutputSamples == originalNumOutputSamples) {
return;
}
moveNewSamplesToPitchBuffer(originalNumOutputSamples);
int position = 0;
while (numPitchSamples - position >= maxRequired) {
int period = findPitchPeriod(pitchBuffer, position, false);
int newPeriod = (int) (period / pitch);
enlargeOutputBufferIfNeeded(newPeriod);
if (pitch >= 1.0f) {
overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer, position,
pitchBuffer, position + period - newPeriod);
} else {
int separation = newPeriod - period;
overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
pitchBuffer, position, pitchBuffer, position);
}
numOutputSamples += newPeriod;
position += period;
}
removePitchSamples(position);
}
private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) {
short left = in[inPos * numChannels];
short right = in[inPos * numChannels + numChannels];
int position = newRatePosition * oldSampleRate;
int leftPosition = oldRatePosition * newSampleRate;
int rightPosition = (oldRatePosition + 1) * newSampleRate;
int ratio = rightPosition - position;
int width = rightPosition - leftPosition;
return (short) ((ratio * left + (width - ratio) * right) / width);
}
private void adjustRate(float rate, int originalNumOutputSamples) {
if (numOutputSamples == originalNumOutputSamples) {
return;
}
int newSampleRate = (int) (sampleRate / rate);
int oldSampleRate = sampleRate;
// Set these values to help with the integer math.
while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
newSampleRate /= 2;
oldSampleRate /= 2;
}
moveNewSamplesToPitchBuffer(originalNumOutputSamples);
// Leave at least one pitch sample in the buffer.
for (int position = 0; position < numPitchSamples - 1; position++) {
while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) {
enlargeOutputBufferIfNeeded(1);
for (int i = 0; i < numChannels; i++) {
outputBuffer[numOutputSamples * numChannels + i] =
interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate);
}
newRatePosition++;
numOutputSamples++;
}
oldRatePosition++;
if (oldRatePosition == oldSampleRate) {
oldRatePosition = 0;
Assertions.checkState(newRatePosition == newSampleRate);
newRatePosition = 0;
}
}
removePitchSamples(numPitchSamples - 1);
}
private int skipPitchPeriod(short[] samples, int position, float speed, int period) {
// Skip over a pitch period, and copy period/speed samples to the output.
int newSamples;
if (speed >= 2.0f) {
newSamples = (int) (period / (speed - 1.0f));
} else {
newSamples = period;
remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f));
}
enlargeOutputBufferIfNeeded(newSamples);
overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples,
position + period);
numOutputSamples += newSamples;
return newSamples;
}
private int insertPitchPeriod(short[] samples, int position, float speed, int period) {
// Insert a pitch period, and determine how much input to copy directly.
int newSamples;
if (speed < 0.5f) {
newSamples = (int) (period * speed / (1.0f - speed));
} else {
newSamples = period;
remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed));
}
enlargeOutputBufferIfNeeded(period + newSamples);
System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels,
period * numChannels);
overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
position + period, samples, position);
numOutputSamples += period + newSamples;
return newSamples;
}
private void changeSpeed(float speed) {
if (numInputSamples < maxRequired) {
return;
}
int numSamples = numInputSamples;
int position = 0;
do {
if (remainingInputToCopy > 0) {
position += copyInputToOutput(position);
} else {
int period = findPitchPeriod(inputBuffer, position, true);
if (speed > 1.0) {
position += period + skipPitchPeriod(inputBuffer, position, speed, period);
} else {
position += insertPitchPeriod(inputBuffer, position, speed, period);
}
}
} while (position + maxRequired <= numSamples);
removeProcessedInputSamples(position);
}
private void processStreamInput() {
// Resample as many pitch periods as we have buffered on the input.
int originalNumOutputSamples = numOutputSamples;
float s = speed / pitch;
if (s > 1.00001 || s < 0.99999) {
changeSpeed(s);
} else {
copyToOutput(inputBuffer, 0, numInputSamples);
numInputSamples = 0;
}
if (USE_CHORD_PITCH) {
if (pitch != 1.0f) {
adjustPitch(originalNumOutputSamples);
}
} else if (!USE_CHORD_PITCH && pitch != 1.0f) {
adjustRate(pitch, originalNumOutputSamples);
}
}
private static void overlapAdd(int numSamples, int numChannels, short[] out, int outPos,
short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) {
for (int i = 0; i < numChannels; i++) {
int o = outPos * numChannels + i;
int u = rampUpPos * numChannels + i;
int d = rampDownPos * numChannels + i;
for (int t = 0; t < numSamples; t++) {
out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples);
o += numChannels;
d += numChannels;
u += numChannels;
}
}
}
private static void overlapAddWithSeparation(int numSamples, int numChannels, int separation,
short[] out, int outPos, short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) {
for (int i = 0; i < numChannels; i++) {
int o = outPos * numChannels + i;
int u = rampUpPos * numChannels + i;
int d = rampDownPos * numChannels + i;
for (int t = 0; t < numSamples + separation; t++) {
if (t < separation) {
out[o] = (short) (rampDown[d] * (numSamples - t) / numSamples);
d += numChannels;
} else if (t < numSamples) {
out[o] =
(short) ((rampDown[d] * (numSamples - t) + rampUp[u] * (t - separation))
/ numSamples);
d += numChannels;
u += numChannels;
} else {
out[o] = (short) (rampUp[u] * (t - separation) / numSamples);
u += numChannels;
}
o += numChannels;
}
}
}
}

View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.audio;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.C.Encoding;
import org.telegram.messenger.exoplayer2.Format;
import org.telegram.messenger.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
/**
* An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio.
*/
public final class SonicAudioProcessor implements AudioProcessor {
/**
* The maximum allowed playback speed in {@link #setSpeed(float)}.
*/
public static final float MAXIMUM_SPEED = 8.0f;
/**
* The minimum allowed playback speed in {@link #setSpeed(float)}.
*/
public static final float MINIMUM_SPEED = 0.1f;
/**
* The maximum allowed pitch in {@link #setPitch(float)}.
*/
public static final float MAXIMUM_PITCH = 8.0f;
/**
* The minimum allowed pitch in {@link #setPitch(float)}.
*/
public static final float MINIMUM_PITCH = 0.1f;
/**
* The threshold below which the difference between two pitch/speed factors is negligible.
*/
private static final float CLOSE_THRESHOLD = 0.01f;
private int channelCount;
private int sampleRateHz;
private Sonic sonic;
private float speed;
private float pitch;
private ByteBuffer buffer;
private ShortBuffer shortBuffer;
private ByteBuffer outputBuffer;
private long inputBytes;
private long outputBytes;
private boolean inputEnded;
/**
* Creates a new Sonic audio processor.
*/
public SonicAudioProcessor() {
speed = 1f;
pitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
buffer = EMPTY_BUFFER;
shortBuffer = buffer.asShortBuffer();
outputBuffer = EMPTY_BUFFER;
}
/**
* Sets the playback speed. The new speed will take effect after a call to {@link #flush()}.
*
* @param speed The requested new playback speed.
* @return The actual new playback speed.
*/
public float setSpeed(float speed) {
this.speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
return this.speed;
}
/**
* Sets the playback pitch. The new pitch will take effect after a call to {@link #flush()}.
*
* @param pitch The requested new pitch.
* @return The actual new pitch.
*/
public float setPitch(float pitch) {
this.pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
return pitch;
}
/**
* Returns the number of bytes of input queued since the last call to {@link #flush()}.
*/
public long getInputByteCount() {
return inputBytes;
}
/**
* Returns the number of bytes of output dequeued since the last call to {@link #flush()}.
*/
public long getOutputByteCount() {
return outputBytes;
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
return true;
}
@Override
public boolean isActive() {
return Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD;
}
@Override
public int getOutputChannelCount() {
return channelCount;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
if (inputBuffer.hasRemaining()) {
ShortBuffer shortBuffer = inputBuffer.asShortBuffer();
int inputSize = inputBuffer.remaining();
inputBytes += inputSize;
sonic.queueInput(shortBuffer);
inputBuffer.position(inputBuffer.position() + inputSize);
}
int outputSize = sonic.getSamplesAvailable() * channelCount * 2;
if (outputSize > 0) {
if (buffer.capacity() < outputSize) {
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
shortBuffer = buffer.asShortBuffer();
} else {
buffer.clear();
shortBuffer.clear();
}
sonic.getOutput(shortBuffer);
outputBytes += outputSize;
buffer.limit(outputSize);
outputBuffer = buffer;
}
}
@Override
public void queueEndOfStream() {
sonic.queueEndOfStream();
inputEnded = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer;
}
@Override
public boolean isEnded() {
return inputEnded && (sonic == null || sonic.getSamplesAvailable() == 0);
}
@Override
public void flush() {
sonic = new Sonic(sampleRateHz, channelCount);
sonic.setSpeed(speed);
sonic.setPitch(pitch);
outputBuffer = EMPTY_BUFFER;
inputBytes = 0;
outputBytes = 0;
inputEnded = false;
}
@Override
public void reset() {
sonic = null;
buffer = EMPTY_BUFFER;
shortBuffer = buffer.asShortBuffer();
outputBuffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
inputBytes = 0;
outputBytes = 0;
inputEnded = false;
}
}

View File

@ -49,11 +49,21 @@ public final class CryptoInfo {
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int patternBlocksToEncrypt;
/**
* @see android.media.MediaCodec.CryptoInfo.Pattern
*/
public int patternBlocksToSkip;
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
private final PatternHolderV24 patternHolder;
public CryptoInfo() {
frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null;
patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null;
}
/**
@ -67,11 +77,21 @@ public final class CryptoInfo {
this.key = key;
this.iv = iv;
this.mode = mode;
patternBlocksToEncrypt = 0;
patternBlocksToSkip = 0;
if (Util.SDK_INT >= 16) {
updateFrameworkCryptoInfoV16();
}
}
public void setPattern(int patternBlocksToEncrypt, int patternBlocksToSkip) {
this.patternBlocksToEncrypt = patternBlocksToEncrypt;
this.patternBlocksToSkip = patternBlocksToSkip;
if (Util.SDK_INT >= 24) {
patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip);
}
}
/**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
* <p>
@ -93,8 +113,35 @@ public final class CryptoInfo {
@TargetApi(16)
private void updateFrameworkCryptoInfoV16() {
frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv,
mode);
// Update fields directly because the framework's CryptoInfo.set performs an unnecessary object
// allocation on Android N.
frameworkCryptoInfo.numSubSamples = numSubSamples;
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData;
frameworkCryptoInfo.key = key;
frameworkCryptoInfo.iv = iv;
frameworkCryptoInfo.mode = mode;
if (Util.SDK_INT >= 24) {
patternHolder.set(patternBlocksToEncrypt, patternBlocksToSkip);
}
}
@TargetApi(24)
private static final class PatternHolderV24 {
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
private final android.media.MediaCodec.CryptoInfo.Pattern pattern;
private PatternHolderV24(android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) {
this.frameworkCryptoInfo = frameworkCryptoInfo;
pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0);
}
private void set(int blocksToEncrypt, int blocksToSkip) {
pattern.set(blocksToEncrypt, blocksToSkip);
frameworkCryptoInfo.setPattern(pattern);
}
}
}

View File

@ -61,8 +61,16 @@ public class DecoderInputBuffer extends Buffer {
*/
public long timeUs;
@BufferReplacementMode
private final int bufferReplacementMode;
@BufferReplacementMode private final int bufferReplacementMode;
/**
* Creates a new instance for which {@link #isFlagsOnly()} will return true.
*
* @return A new flags only input buffer.
*/
public static DecoderInputBuffer newFlagsOnlyInstance() {
return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
}
/**
* @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One
@ -110,6 +118,14 @@ public class DecoderInputBuffer extends Buffer {
data = newData;
}
/**
* Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and
* its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}.
*/
public final boolean isFlagsOnly() {
return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED;
}
/**
* Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set.
*/

View File

@ -24,7 +24,10 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.IntDef;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData;
import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.KeyRequest;
@ -32,19 +35,23 @@ import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.OnEventListener;
import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import org.telegram.messenger.exoplayer2.extractor.mp4.PsshAtomUtil;
import org.telegram.messenger.exoplayer2.util.Assertions;
import org.telegram.messenger.exoplayer2.util.MimeTypes;
import org.telegram.messenger.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}.
* A {@link DrmSessionManager} that supports playbacks using {@link MediaDrm}.
*/
@TargetApi(18)
public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
DrmSession<T> {
/**
* Listener of {@link StreamingDrmSessionManager} events.
* Listener of {@link DefaultDrmSessionManager} events.
*/
public interface EventListener {
@ -60,6 +67,16 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
*/
void onDrmSessionManagerError(Exception e);
/**
* Called each time offline keys are restored.
*/
void onDrmKeysRestored();
/**
* Called each time offline keys are removed.
*/
void onDrmKeysRemoved();
}
/**
@ -67,9 +84,33 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
*/
public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData";
/** Determines the action to be done after a session acquired. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE})
public @interface Mode {}
/**
* Loads and refreshes (if necessary) a license for playback. Supports streaming and offline
* licenses.
*/
public static final int MODE_PLAYBACK = 0;
/**
* Restores an offline license to allow its status to be queried. If the offline license is
* expired sets state to {@link #STATE_ERROR}.
*/
public static final int MODE_QUERY = 1;
/** Downloads an offline license or renews an existing one. */
public static final int MODE_DOWNLOAD = 2;
/** Releases an existing offline license. */
public static final int MODE_RELEASE = 3;
private static final String TAG = "OfflineDrmSessionMngr";
private static final String CENC_SCHEME_MIME_TYPE = "cenc";
private static final int MSG_PROVISION = 0;
private static final int MSG_KEYS = 1;
private static final int MAX_LICENSE_DURATION_TO_RENEW = 60;
private final Handler eventHandler;
private final EventListener eventListener;
private final ExoMediaDrm<T> mediaDrm;
@ -85,14 +126,17 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
private HandlerThread requestHandlerThread;
private Handler postRequestHandler;
private int mode;
private int openCount;
private boolean provisioningInProgress;
@DrmSession.State
private int state;
private T mediaCrypto;
private Exception lastException;
private SchemeData schemeData;
private DrmSessionException lastException;
private byte[] schemeInitData;
private String schemeMimeType;
private byte[] sessionId;
private byte[] offlineLicenseKeySetId;
/**
* Instantiates a new instance using the Widevine scheme.
@ -105,7 +149,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/
public static StreamingDrmSessionManager<FrameworkMediaCrypto> newWidevineInstance(
public static DefaultDrmSessionManager<FrameworkMediaCrypto> newWidevineInstance(
MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters,
Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException {
return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters,
@ -125,7 +169,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/
public static StreamingDrmSessionManager<FrameworkMediaCrypto> newPlayReadyInstance(
public static DefaultDrmSessionManager<FrameworkMediaCrypto> newPlayReadyInstance(
MediaDrmCallback callback, String customData, Handler eventHandler,
EventListener eventListener) throws UnsupportedDrmException {
HashMap<String, String> optionalKeyRequestParameters;
@ -151,10 +195,10 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedDrmException If the specified DRM scheme is not supported.
*/
public static StreamingDrmSessionManager<FrameworkMediaCrypto> newFrameworkInstance(
public static DefaultDrmSessionManager<FrameworkMediaCrypto> newFrameworkInstance(
UUID uuid, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters,
Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException {
return new StreamingDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), callback,
return new DefaultDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), callback,
optionalKeyRequestParameters, eventHandler, eventListener);
}
@ -168,7 +212,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public StreamingDrmSessionManager(UUID uuid, ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback,
public DefaultDrmSessionManager(UUID uuid, ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback,
HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler,
EventListener eventListener) {
this.uuid = uuid;
@ -179,6 +223,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
this.eventListener = eventListener;
mediaDrm.setOnEventListener(new MediaDrmEventListener());
state = STATE_CLOSED;
mode = MODE_PLAYBACK;
}
/**
@ -229,6 +274,37 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
mediaDrm.setPropertyByteArray(key, value);
}
/**
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
* called before {@link #acquireSession(Looper, DrmInitData)} is called.
*
* <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
* required.
*
* <p>{@code mode} must be one of these:
* <ul>
* <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is
* requested otherwise the offline license is restored.
* <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license
* is restored.
* <li>{@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is
* requested otherwise the offline license is renewed.
* <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license
* is released.
* </ul>
*
* @param mode The mode to be set.
* @param offlineLicenseKeySetId The key set id of the license to be used with the given mode.
*/
public void setMode(@Mode int mode, byte[] offlineLicenseKeySetId) {
Assertions.checkState(openCount == 0);
if (mode == MODE_QUERY || mode == MODE_RELEASE) {
Assertions.checkNotNull(offlineLicenseKeySetId);
}
this.mode = mode;
this.offlineLicenseKeySetId = offlineLicenseKeySetId;
}
// DrmSessionManager implementation.
@Override
@ -248,18 +324,28 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
requestHandlerThread.start();
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
schemeData = drmInitData.get(uuid);
if (schemeData == null) {
onError(new IllegalStateException("Media does not support uuid: " + uuid));
return this;
}
if (Util.SDK_INT < 21) {
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeData.data, C.WIDEVINE_UUID);
if (psshData == null) {
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
} else {
schemeData = new SchemeData(C.WIDEVINE_UUID, schemeData.mimeType, psshData);
if (offlineLicenseKeySetId == null) {
SchemeData schemeData = drmInitData.get(uuid);
if (schemeData == null) {
onError(new IllegalStateException("Media does not support uuid: " + uuid));
return this;
}
schemeInitData = schemeData.data;
schemeMimeType = schemeData.mimeType;
if (Util.SDK_INT < 21) {
// Prior to L the Widevine CDM required data to be extracted from the PSSH atom.
byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData, C.WIDEVINE_UUID);
if (psshData == null) {
// Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged.
} else {
schemeInitData = psshData;
}
}
if (Util.SDK_INT < 26 && C.CLEARKEY_UUID.equals(uuid)
&& (MimeTypes.VIDEO_MP4.equals(schemeMimeType)
|| MimeTypes.AUDIO_MP4.equals(schemeMimeType))) {
// Prior to API level 26 the ClearKey CDM only accepted "cenc" as the scheme for MP4.
schemeMimeType = CENC_SCHEME_MIME_TYPE;
}
}
state = STATE_OPENING;
@ -280,7 +366,8 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
postRequestHandler = null;
requestHandlerThread.quit();
requestHandlerThread = null;
schemeData = null;
schemeInitData = null;
schemeMimeType = null;
mediaCrypto = null;
lastException = null;
if (sessionId != null) {
@ -314,10 +401,25 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
}
@Override
public final Exception getError() {
public final DrmSessionException getError() {
return state == STATE_ERROR ? lastException : null;
}
@Override
public Map<String, String> queryKeyStatus() {
// User may call this method rightfully even if state == STATE_ERROR. So only check if there is
// a sessionId
if (sessionId == null) {
throw new IllegalStateException();
}
return mediaDrm.queryKeyStatus(sessionId);
}
@Override
public byte[] getOfflineLicenseKeySetId() {
return offlineLicenseKeySetId;
}
// Internal methods.
private void openInternal(boolean allowProvisioning) {
@ -325,7 +427,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
sessionId = mediaDrm.openSession();
mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId);
state = STATE_OPENED;
postKeyRequest();
doLicense();
} catch (NotProvisionedException e) {
if (allowProvisioning) {
postProvisionRequest();
@ -363,20 +465,86 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
if (state == STATE_OPENING) {
openInternal(false);
} else {
postKeyRequest();
doLicense();
}
} catch (DeniedByServerException e) {
onError(e);
}
}
private void postKeyRequest() {
KeyRequest keyRequest;
private void doLicense() {
switch (mode) {
case MODE_PLAYBACK:
case MODE_QUERY:
if (offlineLicenseKeySetId == null) {
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_STREAMING);
} else {
if (restoreKeys()) {
long licenseDurationRemainingSec = getLicenseDurationRemainingSec();
if (mode == MODE_PLAYBACK
&& licenseDurationRemainingSec <= MAX_LICENSE_DURATION_TO_RENEW) {
Log.d(TAG, "Offline license has expired or will expire soon. "
+ "Remaining seconds: " + licenseDurationRemainingSec);
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
} else if (licenseDurationRemainingSec <= 0) {
onError(new KeysExpiredException());
} else {
state = STATE_OPENED_WITH_KEYS;
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrmKeysRestored();
}
});
}
}
}
}
break;
case MODE_DOWNLOAD:
if (offlineLicenseKeySetId == null) {
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
} else {
// Renew
if (restoreKeys()) {
postKeyRequest(sessionId, MediaDrm.KEY_TYPE_OFFLINE);
}
}
break;
case MODE_RELEASE:
if (restoreKeys()) {
postKeyRequest(offlineLicenseKeySetId, MediaDrm.KEY_TYPE_RELEASE);
}
break;
}
}
private boolean restoreKeys() {
try {
keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData.data, schemeData.mimeType,
MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters);
mediaDrm.restoreKeys(sessionId, offlineLicenseKeySetId);
return true;
} catch (Exception e) {
Log.e(TAG, "Error trying to restore Widevine keys.", e);
onError(e);
}
return false;
}
private long getLicenseDurationRemainingSec() {
if (!C.WIDEVINE_UUID.equals(uuid)) {
return Long.MAX_VALUE;
}
Pair<Long, Long> pair = WidevineUtil.getLicenseDurationRemainingSec(this);
return Math.min(pair.first, pair.second);
}
private void postKeyRequest(byte[] scope, int keyType) {
try {
KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType,
optionalKeyRequestParameters);
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
} catch (NotProvisionedException e) {
} catch (Exception e) {
onKeysError(e);
}
}
@ -393,15 +561,31 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
}
try {
mediaDrm.provideKeyResponse(sessionId, (byte[]) response);
state = STATE_OPENED_WITH_KEYS;
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrmKeysLoaded();
}
});
if (mode == MODE_RELEASE) {
mediaDrm.provideKeyResponse(offlineLicenseKeySetId, (byte[]) response);
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrmKeysRemoved();
}
});
}
} else {
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response);
if ((mode == MODE_DOWNLOAD || (mode == MODE_PLAYBACK && offlineLicenseKeySetId != null))
&& keySetId != null && keySetId.length != 0) {
offlineLicenseKeySetId = keySetId;
}
state = STATE_OPENED_WITH_KEYS;
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrmKeysLoaded();
}
});
}
}
} catch (Exception e) {
onKeysError(e);
@ -417,7 +601,7 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
}
private void onError(final Exception e) {
lastException = e;
lastException = new DrmSessionException(e);
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
@ -446,11 +630,16 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
}
switch (msg.what) {
case MediaDrm.EVENT_KEY_REQUIRED:
postKeyRequest();
doLicense();
break;
case MediaDrm.EVENT_KEY_EXPIRED:
state = STATE_OPENED;
onError(new KeysExpiredException());
// When an already expired key is loaded MediaDrm sends this event immediately. Ignore
// this event if the state isn't STATE_OPENED_WITH_KEYS yet which means we're still
// waiting for key response.
if (state == STATE_OPENED_WITH_KEYS) {
state = STATE_OPENED;
onError(new KeysExpiredException());
}
break;
case MediaDrm.EVENT_PROVISION_REQUIRED:
state = STATE_OPENED;
@ -466,7 +655,9 @@ public class StreamingDrmSessionManager<T extends ExoMediaCrypto> implements Drm
@Override
public void onEvent(ExoMediaDrm<? extends T> md, byte[] sessionId, int event, int extra,
byte[] data) {
mediaDrmHandler.sendEmptyMessage(event);
if (mode == MODE_PLAYBACK) {
mediaDrmHandler.sendEmptyMessage(event);
}
}
}

View File

@ -16,9 +16,11 @@
package org.telegram.messenger.exoplayer2.drm;
import android.annotation.TargetApi;
import android.media.MediaDrm;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
/**
* A DRM session.
@ -26,6 +28,15 @@ import java.lang.annotation.RetentionPolicy;
@TargetApi(16)
public interface DrmSession<T extends ExoMediaCrypto> {
/** Wraps the exception which is the cause of the error state. */
class DrmSessionException extends Exception {
public DrmSessionException(Exception e) {
super(e);
}
}
/**
* The state of the DRM session.
*/
@ -59,8 +70,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
*/
@State
int getState();
@State int getState();
/**
* Returns a {@link ExoMediaCrypto} for the open session.
@ -96,6 +106,26 @@ public interface DrmSession<T extends ExoMediaCrypto> {
*
* @return An exception if the state is {@link #STATE_ERROR}. Null otherwise.
*/
Exception getError();
DrmSessionException getError();
/**
* Returns an informative description of the key status for the session. The status is in the form
* of {name, value} pairs.
*
* <p>Since DRM license policies vary by vendor, the specific status field names are determined by
* each DRM vendor. Refer to your DRM provider documentation for definitions of the field names
* for a particular DRM engine plugin.
*
* @return A map of key status.
* @throws IllegalStateException If called when the session isn't opened.
* @see MediaDrm#queryKeyStatus(byte[])
*/
Map<String, String> queryKeyStatus();
/**
* Returns the key set id of the offline license loaded into this session, if there is one. Null
* otherwise.
*/
byte[] getOfflineLicenseKeySetId();
}

View File

@ -23,6 +23,7 @@ import android.media.MediaDrm;
import android.media.NotProvisionedException;
import android.media.ResourceBusyException;
import android.media.UnsupportedSchemeException;
import android.support.annotation.NonNull;
import org.telegram.messenger.exoplayer2.util.Assertions;
import java.util.HashMap;
import java.util.Map;
@ -62,7 +63,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
final ExoMediaDrm.OnEventListener<? super FrameworkMediaCrypto> listener) {
mediaDrm.setOnEventListener(listener == null ? null : new MediaDrm.OnEventListener() {
@Override
public void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data) {
public void onEvent(@NonNull MediaDrm md, byte[] sessionId, int event, int extra,
byte[] data) {
listener.onEvent(FrameworkMediaDrm.this, sessionId, event, extra, data);
}
});

View File

@ -24,6 +24,8 @@ import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import org.telegram.messenger.exoplayer2.upstream.DataSourceInputStream;
import org.telegram.messenger.exoplayer2.upstream.DataSpec;
import org.telegram.messenger.exoplayer2.upstream.HttpDataSource;
import org.telegram.messenger.exoplayer2.upstream.HttpDataSource.Factory;
import org.telegram.messenger.exoplayer2.util.Assertions;
import org.telegram.messenger.exoplayer2.util.Util;
import java.io.IOException;
import java.util.HashMap;
@ -57,21 +59,62 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
}
/**
* @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request
* properties can be set by calling {@link #setKeyRequestProperty(String, String)}.
* @param defaultUrl The default license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param keyRequestProperties Request properties to set when making key requests, or null.
*/
@Deprecated
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory,
Map<String, String> keyRequestProperties) {
this.dataSourceFactory = dataSourceFactory;
this.defaultUrl = defaultUrl;
this.keyRequestProperties = keyRequestProperties;
this.keyRequestProperties = new HashMap<>();
if (keyRequestProperties != null) {
this.keyRequestProperties.putAll(keyRequestProperties);
}
}
/**
* Sets a header for key requests made by the callback.
*
* @param name The name of the header field.
* @param value The value of the field.
*/
public void setKeyRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (keyRequestProperties) {
keyRequestProperties.put(name, value);
}
}
/**
* Clears a header for key requests made by the callback.
*
* @param name The name of the header field.
*/
public void clearKeyRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (keyRequestProperties) {
keyRequestProperties.remove(name);
}
}
/**
* Clears all headers for key requests made by the callback.
*/
public void clearAllKeyRequestProperties() {
synchronized (keyRequestProperties) {
keyRequestProperties.clear();
}
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return executePost(url, new byte[0], null);
return executePost(dataSourceFactory, url, new byte[0], null);
}
@Override
@ -85,14 +128,14 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
if (C.PLAYREADY_UUID.equals(uuid)) {
requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES);
}
if (keyRequestProperties != null) {
synchronized (keyRequestProperties) {
requestProperties.putAll(keyRequestProperties);
}
return executePost(url, request.getData(), requestProperties);
return executePost(dataSourceFactory, url, request.getData(), requestProperties);
}
private byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws IOException {
private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url,
byte[] data, Map<String, String> requestProperties) throws IOException {
HttpDataSource dataSource = dataSourceFactory.createDataSource();
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
@ -105,7 +148,7 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
try {
return Util.toByteArray(inputStream);
} finally {
inputStream.close();
Util.closeQuietly(inputStream);
}
}

View File

@ -0,0 +1,213 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.drm;
import android.media.MediaDrm;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Pair;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.drm.DefaultDrmSessionManager.EventListener;
import org.telegram.messenger.exoplayer2.drm.DefaultDrmSessionManager.Mode;
import org.telegram.messenger.exoplayer2.drm.DrmSession.DrmSessionException;
import org.telegram.messenger.exoplayer2.upstream.HttpDataSource;
import org.telegram.messenger.exoplayer2.upstream.HttpDataSource.Factory;
import org.telegram.messenger.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.HashMap;
/**
* Helper class to download, renew and release offline licenses.
*/
public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
private final ConditionVariable conditionVariable;
private final DefaultDrmSessionManager<T> drmSessionManager;
private final HandlerThread handlerThread;
/**
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
* is no longer required.
*
* @param licenseUrl The default license URL.
* @param httpDataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @return A new instance which uses Widevine CDM.
* @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
* instantiated.
*/
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException {
return newWidevineInstance(
new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null);
}
/**
* Instantiates a new instance which uses Widevine CDM. Call {@link #release()} when the instance
* is no longer required.
*
* @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null.
* @return A new instance which uses Widevine CDM.
* @throws UnsupportedDrmException If the Widevine DRM scheme is unsupported or cannot be
* instantiated.
* @see DefaultDrmSessionManager#DefaultDrmSessionManager(java.util.UUID, ExoMediaDrm,
* MediaDrmCallback, HashMap, Handler, EventListener)
*/
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters)
throws UnsupportedDrmException {
return new OfflineLicenseHelper<>(FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID), callback,
optionalKeyRequestParameters);
}
/**
* Constructs an instance. Call {@link #release()} when the instance is no longer required.
*
* @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
* @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null.
* @see DefaultDrmSessionManager#DefaultDrmSessionManager(java.util.UUID, ExoMediaDrm,
* MediaDrmCallback, HashMap, Handler, EventListener)
*/
public OfflineLicenseHelper(ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback,
HashMap<String, String> optionalKeyRequestParameters) {
handlerThread = new HandlerThread("OfflineLicenseHelper");
handlerThread.start();
conditionVariable = new ConditionVariable();
EventListener eventListener = new EventListener() {
@Override
public void onDrmKeysLoaded() {
conditionVariable.open();
}
@Override
public void onDrmSessionManagerError(Exception e) {
conditionVariable.open();
}
@Override
public void onDrmKeysRestored() {
conditionVariable.open();
}
@Override
public void onDrmKeysRemoved() {
conditionVariable.open();
}
};
drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID, mediaDrm, callback,
optionalKeyRequestParameters, new Handler(handlerThread.getLooper()), eventListener);
}
/** Releases the helper. Should be called when the helper is no longer required. */
public void release() {
handlerThread.quit();
}
/**
* Downloads an offline license.
*
* @param drmInitData The {@link DrmInitData} for the content whose license is to be downloaded.
* @return The key set id for the downloaded license.
* @throws IOException If an error occurs reading data from the stream.
* @throws InterruptedException If the thread has been interrupted.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
public synchronized byte[] downloadLicense(DrmInitData drmInitData) throws IOException,
InterruptedException, DrmSessionException {
Assertions.checkArgument(drmInitData != null);
return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, null, drmInitData);
}
/**
* Renews an offline license.
*
* @param offlineLicenseKeySetId The key set id of the license to be renewed.
* @return The renewed offline license key set id.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
public synchronized byte[] renewLicense(byte[] offlineLicenseKeySetId)
throws DrmSessionException {
Assertions.checkNotNull(offlineLicenseKeySetId);
return blockingKeyRequest(DefaultDrmSessionManager.MODE_DOWNLOAD, offlineLicenseKeySetId, null);
}
/**
* Releases an offline license.
*
* @param offlineLicenseKeySetId The key set id of the license to be released.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
public synchronized void releaseLicense(byte[] offlineLicenseKeySetId)
throws DrmSessionException {
Assertions.checkNotNull(offlineLicenseKeySetId);
blockingKeyRequest(DefaultDrmSessionManager.MODE_RELEASE, offlineLicenseKeySetId, null);
}
/**
* Returns the remaining license and playback durations in seconds, for an offline license.
*
* @param offlineLicenseKeySetId The key set id of the license.
* @return The remaining license and playback durations, in seconds.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
public synchronized Pair<Long, Long> getLicenseDurationRemainingSec(byte[] offlineLicenseKeySetId)
throws DrmSessionException {
Assertions.checkNotNull(offlineLicenseKeySetId);
DrmSession<T> drmSession = openBlockingKeyRequest(DefaultDrmSessionManager.MODE_QUERY,
offlineLicenseKeySetId, null);
DrmSessionException error = drmSession.getError();
Pair<Long, Long> licenseDurationRemainingSec =
WidevineUtil.getLicenseDurationRemainingSec(drmSession);
drmSessionManager.releaseSession(drmSession);
if (error != null) {
if (error.getCause() instanceof KeysExpiredException) {
return Pair.create(0L, 0L);
}
throw error;
}
return licenseDurationRemainingSec;
}
private byte[] blockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId,
DrmInitData drmInitData) throws DrmSessionException {
DrmSession<T> drmSession = openBlockingKeyRequest(licenseMode, offlineLicenseKeySetId,
drmInitData);
DrmSessionException error = drmSession.getError();
byte[] keySetId = drmSession.getOfflineLicenseKeySetId();
drmSessionManager.releaseSession(drmSession);
if (error != null) {
throw error;
}
return keySetId;
}
private DrmSession<T> openBlockingKeyRequest(@Mode int licenseMode, byte[] offlineLicenseKeySetId,
DrmInitData drmInitData) {
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
conditionVariable.close();
DrmSession<T> drmSession = drmSessionManager.acquireSession(handlerThread.getLooper(),
drmInitData);
// Block current thread until key loading is finished
conditionVariable.block();
return drmSession;
}
}

View File

@ -43,8 +43,7 @@ public final class UnsupportedDrmException extends Exception {
/**
* Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.
*/
@Reason
public final int reason;
@Reason public final int reason;
/**
* @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}.

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer2.drm;
import android.util.Pair;
import org.telegram.messenger.exoplayer2.C;
import java.util.Map;
/**
* Utility methods for Widevine.
*/
public final class WidevineUtil {
/** Widevine specific key status field name for the remaining license duration, in seconds. */
public static final String PROPERTY_LICENSE_DURATION_REMAINING = "LicenseDurationRemaining";
/** Widevine specific key status field name for the remaining playback duration, in seconds. */
public static final String PROPERTY_PLAYBACK_DURATION_REMAINING = "PlaybackDurationRemaining";
private WidevineUtil() {}
/**
* Returns license and playback durations remaining in seconds.
*
* @return A {@link Pair} consisting of the remaining license and playback durations in seconds.
* @throws IllegalStateException If called when a session isn't opened.
* @param drmSession
*/
public static Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession<?> drmSession) {
Map<String, String> keyStatus = drmSession.queryKeyStatus();
return new Pair<>(
getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING),
getDurationRemainingSec(keyStatus, PROPERTY_PLAYBACK_DURATION_REMAINING));
}
private static long getDurationRemainingSec(Map<String, String> keyStatus, String property) {
if (keyStatus != null) {
try {
String value = keyStatus.get(property);
if (value != null) {
return Long.parseLong(value);
}
} catch (NumberFormatException e) {
// do nothing.
}
}
return C.TIME_UNSET;
}
}

Some files were not shown because too many files have changed in this diff Show More