mirror of https://github.com/NekoX-Dev/NekoX.git
Update to 4.1.1
This commit is contained in:
parent
6a1cf64f6f
commit
dd679bd7d1
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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("<", "<").replace(">", ">").replace("'", "\\'").replace("& ", "& ");
|
||||
} else {
|
||||
value = value.replace("\\n", "\n");
|
||||
value = value.replace("\\", "");
|
||||
String old = value;
|
||||
value = value.replace("<", "<");
|
||||
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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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}.
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue