1
0
mirror of https://github.com/MGislv/NekoX.git synced 2024-06-28 09:34:05 +00:00

Update to 3.10.1

Thanks to
https://github.com/DrKLO/Telegram/pull/1412
This commit is contained in:
DrKLO 2016-06-24 13:27:15 +03:00
parent f563fbbb05
commit 55463a93db
459 changed files with 56967 additions and 10579 deletions

View File

@ -5,11 +5,11 @@ repositories {
}
dependencies {
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:support-v4:23.4.0'
compile "com.google.android.gms:play-services-gcm:8.4.0"
compile "com.google.android.gms:play-services-maps:8.4.0"
compile 'net.hockeyapp.android:HockeySDK:4.0.+'
compile 'com.googlecode.mp4parser:isoparser:1.0.+'
compile 'net.hockeyapp.android:HockeySDK:4.0.1'
compile 'com.googlecode.mp4parser:isoparser:1.0.6'
}
android {
@ -63,7 +63,7 @@ android {
}
}
defaultConfig.versionCode = 803
defaultConfig.versionCode = 821
sourceSets.main {
jniLibs.srcDir 'libs'
@ -112,9 +112,9 @@ android {
}
defaultConfig {
minSdkVersion 9
minSdkVersion 14
targetSdkVersion 23
versionName "3.9.0"
versionName "3.10.1"
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.telegram.messenger"
android:installLocation="auto">
@ -62,6 +63,22 @@
<uses-library android:name="com.google.android.maps" android:required="false"/>
<receiver
tools:replace="android:enabled"
android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
android:enabled="false">
<intent-filter>
<action android:name="com.google.android.gms.measurement.UPLOAD" />
</intent-filter>
</receiver>
<service
tools:replace="android:enabled"
android:name="com.google.android.gms.measurement.AppMeasurementService"
android:enabled="false"
android:exported="false" />
</application>
</manifest>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.telegram.messenger"
android:installLocation="auto">
@ -62,6 +63,21 @@
<uses-library android:name="com.google.android.maps" android:required="false"/>
<receiver
tools:replace="android:enabled"
android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
android:enabled="false">
<intent-filter>
<action android:name="com.google.android.gms.measurement.UPLOAD" />
</intent-filter>
</receiver>
<service
tools:replace="android:enabled"
android:name="com.google.android.gms.measurement.AppMeasurementService"
android:enabled="false"
android:exported="false" />
</application>
</manifest>

26
TMessagesProj/google-services.json Normal file → Executable file
View File

@ -18,22 +18,9 @@
"oauth_client": [],
"api_key": [],
"services": {
"analytics_service": {
"status": 1
},
"cloud_messaging_service": {
"status": 2,
"apns_config": []
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"google_signin_service": {
"status": 1
},
"ads_service": {
"status": 1
}
}
},
@ -50,22 +37,9 @@
"oauth_client": [],
"api_key": [],
"services": {
"analytics_service": {
"status": 1
},
"cloud_messaging_service": {
"status": 2,
"apns_config": []
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"google_signin_service": {
"status": 1
},
"ads_service": {
"status": 1
}
}
}

View File

@ -265,6 +265,8 @@ else
else
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_CPPFLAGS += -Dx86fix
LOCAL_CFLAGS += -Dx86fix
LOCAL_ARM_MODE := arm
LOCAL_SRC_FILE += \
./libyuv/source/row_x86.asm

View File

@ -12,10 +12,8 @@
#define INCLUDE_LIBYUV_CONVERT_H_
#include "libyuv/basic_types.h"
// TODO(fbarchard): Remove the following headers includes.
#include "libyuv/convert_from.h"
#include "libyuv/planar_functions.h"
#include "libyuv/rotate.h"
#include "libyuv/rotate.h" // For enum RotationMode.
#ifdef __cplusplus
namespace libyuv {

View File

@ -12,10 +12,8 @@
#define INCLUDE_LIBYUV_CONVERT_ARGB_H_
#include "libyuv/basic_types.h"
// TODO(fbarchard): Remove the following headers includes
#include "libyuv/convert_from.h"
#include "libyuv/planar_functions.h"
#include "libyuv/rotate.h"
#include "libyuv/rotate.h" // For enum RotationMode.
// TODO(fbarchard): This set of functions should exactly match convert.h
// TODO(fbarchard): Add tests. Create random content of right size and convert

View File

@ -56,8 +56,6 @@ int I400Copy(const uint8* src_y, int src_stride_y,
uint8* dst_y, int dst_stride_y,
int width, int height);
// TODO(fbarchard): I420ToM420
LIBYUV_API
int I420ToNV12(const uint8* src_y, int src_stride_y,
const uint8* src_u, int src_stride_u,

View File

@ -62,7 +62,7 @@ static __inline int TestCpuFlag(int test_flag) {
// For testing, allow CPU flags to be disabled.
// ie MaskCpuFlags(~kCpuHasSSSE3) to disable SSSE3.
// MaskCpuFlags(-1) to enable all cpu specific optimizations.
// MaskCpuFlags(0) to disable all cpu specific optimizations.
// MaskCpuFlags(1) to disable all cpu specific optimizations.
LIBYUV_API
void MaskCpuFlags(int enable_flags);

View File

@ -288,6 +288,12 @@ int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb,
uint8* dst_argb, int dst_stride_argb,
int width, int height);
// Extract the alpha channel from ARGB.
LIBYUV_API
int ARGBExtractAlpha(const uint8* src_argb, int src_stride_argb,
uint8* dst_a, int dst_stride_a,
int width, int height);
// Copy Y channel to Alpha of ARGB.
LIBYUV_API
int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y,

View File

@ -104,6 +104,7 @@ extern "C" {
#define HAS_ARGBTOUVROW_SSSE3
#define HAS_ARGBTOYJROW_SSSE3
#define HAS_ARGBTOYROW_SSSE3
#define HAS_ARGBEXTRACTALPHAROW_SSE2
#define HAS_BGRATOUVROW_SSSE3
#define HAS_BGRATOYROW_SSSE3
#define HAS_COPYROW_ERMS
@ -179,8 +180,7 @@ extern "C" {
// The following functions fail on gcc/clang 32 bit with fpic and framepointer.
// caveat: clangcl uses row_win.cc which works.
#if defined(NDEBUG) || !(defined(_DEBUG) && defined(__i386__)) || \
!defined(__i386__) || defined(_MSC_VER)
#if !defined(x86fix)
// TODO(fbarchard): fix build error on x86 debug
// https://code.google.com/p/libyuv/issues/detail?id=524
#define HAS_I411TOARGBROW_SSSE3
@ -208,7 +208,7 @@ extern "C" {
#define HAS_COPYROW_AVX
#define HAS_H422TOARGBROW_AVX2
#define HAS_I400TOARGBROW_AVX2
#if !(defined(_DEBUG) && defined(__i386__))
#if !defined(x86fix)
// TODO(fbarchard): fix build error on android_full_debug=1
// https://code.google.com/p/libyuv/issues/detail?id=517
#define HAS_I422ALPHATOARGBROW_AVX2
@ -291,6 +291,7 @@ extern "C" {
#define HAS_ARGBTOUVROW_NEON
#define HAS_ARGBTOYJROW_NEON
#define HAS_ARGBTOYROW_NEON
#define HAS_ARGBEXTRACTALPHAROW_NEON
#define HAS_BGRATOUVROW_NEON
#define HAS_BGRATOYROW_NEON
#define HAS_COPYROW_NEON
@ -877,6 +878,14 @@ void ARGBCopyAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb,
void ARGBCopyAlphaRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb,
int width);
void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width);
void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width);
void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width);
void ARGBExtractAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_a,
int width);
void ARGBExtractAlphaRow_Any_NEON(const uint8* src_argb, uint8* dst_a,
int width);
void ARGBCopyYToAlphaRow_C(const uint8* src_y, uint8* dst_argb, int width);
void ARGBCopyYToAlphaRow_SSE2(const uint8* src_y, uint8* dst_argb, int width);
void ARGBCopyYToAlphaRow_AVX2(const uint8* src_y, uint8* dst_argb, int width);

View File

@ -11,6 +11,6 @@
#ifndef INCLUDE_LIBYUV_VERSION_H_ // NOLINT
#define INCLUDE_LIBYUV_VERSION_H_
#define LIBYUV_VERSION 1586
#define LIBYUV_VERSION 1597
#endif // INCLUDE_LIBYUV_VERSION_H_ NOLINT

View File

@ -14,6 +14,7 @@
#ifdef HAVE_JPEG
#include "libyuv/mjpeg_decoder.h"
#endif
#include "libyuv/planar_functions.h" // For CopyPlane and ARGBShuffle.
#include "libyuv/rotate_argb.h"
#include "libyuv/row.h"
#include "libyuv/video_common.h"

View File

@ -1077,7 +1077,6 @@ int ConvertFromI420(const uint8* y, int y_stride,
// Triplanar formats
// TODO(fbarchard): halfstride instead of halfwidth
case FOURCC_I420:
case FOURCC_YU12:
case FOURCC_YV12: {
int halfwidth = (width + 1) / 2;
int halfheight = (height + 1) / 2;

View File

@ -9,6 +9,7 @@
*/
#include "libyuv/convert.h"
#include "libyuv/convert_argb.h"
#ifdef HAVE_JPEG
#include "libyuv/mjpeg_decoder.h"

View File

@ -176,7 +176,6 @@ int ConvertToARGB(const uint8* sample, size_t sample_size,
break;
// Triplanar formats
case FOURCC_I420:
case FOURCC_YU12:
case FOURCC_YV12: {
const uint8* src_y = sample + (src_width * crop_y + crop_x);
const uint8* src_u;

View File

@ -39,12 +39,13 @@ int ConvertToI420(const uint8* sample,
int aligned_src_width = (src_width + 1) & ~1;
const uint8* src;
const uint8* src_uv;
int abs_src_height = (src_height < 0) ? -src_height : src_height;
int inv_crop_height = (crop_height < 0) ? -crop_height : crop_height;
const int abs_src_height = (src_height < 0) ? -src_height : src_height;
// TODO(nisse): Why allow crop_height < 0?
const int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height;
int r = 0;
LIBYUV_BOOL need_buf = (rotation && format != FOURCC_I420 &&
format != FOURCC_NV12 && format != FOURCC_NV21 &&
format != FOURCC_YU12 && format != FOURCC_YV12) || y == sample;
format != FOURCC_YV12) || y == sample;
uint8* tmp_y = y;
uint8* tmp_u = u;
uint8* tmp_v = v;
@ -52,16 +53,14 @@ int ConvertToI420(const uint8* sample,
int tmp_u_stride = u_stride;
int tmp_v_stride = v_stride;
uint8* rotate_buffer = NULL;
int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height;
const int inv_crop_height =
(src_height < 0) ? -abs_crop_height : abs_crop_height;
if (!y || !u || !v || !sample ||
src_width <= 0 || crop_width <= 0 ||
src_height == 0 || crop_height == 0) {
return -1;
}
if (src_height < 0) {
inv_crop_height = -inv_crop_height;
}
// One pass rotation is available for some formats. For the rest, convert
// to I420 (with optional vertical flipping) into a temporary I420 buffer,
@ -214,7 +213,6 @@ int ConvertToI420(const uint8* sample,
break;
// Triplanar formats
case FOURCC_I420:
case FOURCC_YU12:
case FOURCC_YV12: {
const uint8* src_y = sample + (src_width * crop_y + crop_x);
const uint8* src_u;

View File

@ -2374,6 +2374,49 @@ int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb,
return 0;
}
// Extract just the alpha channel from ARGB.
LIBYUV_API
int ARGBExtractAlpha(const uint8* src_argb, int src_stride,
uint8* dst_a, int dst_stride,
int width, int height) {
if (!src_argb || !dst_a || width <= 0 || height == 0) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_argb += (height - 1) * src_stride;
src_stride = -src_stride;
}
// Coalesce rows.
if (src_stride == width * 4 && dst_stride == width) {
width *= height;
height = 1;
src_stride = dst_stride = 0;
}
void (*ARGBExtractAlphaRow)(const uint8 *src_argb, uint8 *dst_a, int width) =
ARGBExtractAlphaRow_C;
#if defined(HAS_ARGBEXTRACTALPHAROW_SSE2)
if (TestCpuFlag(kCpuHasSSE2)) {
ARGBExtractAlphaRow = IS_ALIGNED(width, 8) ? ARGBExtractAlphaRow_SSE2
: ARGBExtractAlphaRow_Any_SSE2;
}
#endif
#if defined(HAS_ARGBEXTRACTALPHAROW_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
ARGBExtractAlphaRow = IS_ALIGNED(width, 16) ? ARGBExtractAlphaRow_NEON
: ARGBExtractAlphaRow_Any_NEON;
}
#endif
for (int y = 0; y < height; ++y) {
ARGBExtractAlphaRow(src_argb, dst_a, width);
src_argb += src_stride;
dst_a += dst_stride;
}
return 0;
}
// Copy a planar Y channel to the alpha channel of a destination ARGB image.
LIBYUV_API
int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y,

View File

@ -23,7 +23,7 @@ extern "C" {
(_MIPS_SIM == _MIPS_SIM_ABI32)
void TransposeWx8_DSPR2(const uint8* src, int src_stride,
uint8* dst, int dst_stride, int width) {
uint8* dst, int dst_stride, int width) {
__asm__ __volatile__ (
".set push \n"
".set noreorder \n"
@ -107,7 +107,7 @@ void TransposeWx8_DSPR2(const uint8* src, int src_stride,
}
void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride,
uint8* dst, int dst_stride, int width) {
uint8* dst, int dst_stride, int width) {
__asm__ __volatile__ (
".set noat \n"
".set push \n"
@ -309,9 +309,9 @@ void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride,
}
void TransposeUVWx8_DSPR2(const uint8* src, int src_stride,
uint8* dst_a, int dst_stride_a,
uint8* dst_b, int dst_stride_b,
int width) {
uint8* dst_a, int dst_stride_a,
uint8* dst_b, int dst_stride_b,
int width) {
__asm__ __volatile__ (
".set push \n"
".set noreorder \n"

View File

@ -466,38 +466,15 @@ ANY11(ARGBUnattenuateRow_Any_AVX2, ARGBUnattenuateRow_AVX2, 0, 4, 4, 7)
#ifdef HAS_ARGBATTENUATEROW_NEON
ANY11(ARGBAttenuateRow_Any_NEON, ARGBAttenuateRow_NEON, 0, 4, 4, 7)
#endif
#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2
ANY11(ARGBExtractAlphaRow_Any_SSE2, ARGBExtractAlphaRow_SSE2, 0, 4, 1, 7)
#endif
#ifdef HAS_ARGBEXTRACTALPHAROW_NEON
ANY11(ARGBExtractAlphaRow_Any_NEON, ARGBExtractAlphaRow_NEON, 0, 4, 1, 15)
#endif
#undef ANY11
// Any 1 to 1 with yuvconstants
#define ANY11C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \
void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, \
const struct YuvConstants* yuvconstants, int width) { \
SIMD_ALIGNED(uint8 temp[128 * 2]); \
memset(temp, 0, 128); /* for YUY2 and msan */ \
int r = width & MASK; \
int n = width & ~MASK; \
if (n > 0) { \
ANY_SIMD(src_ptr, dst_ptr, yuvconstants, n); \
} \
memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \
ANY_SIMD(temp, temp + 128, yuvconstants, MASK + 1); \
memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \
}
#if defined(HAS_YUY2TOARGBROW_SSSE3)
ANY11C(YUY2ToARGBRow_Any_SSSE3, YUY2ToARGBRow_SSSE3, 1, 4, 4, 15)
ANY11C(UYVYToARGBRow_Any_SSSE3, UYVYToARGBRow_SSSE3, 1, 4, 4, 15)
#endif
#if defined(HAS_YUY2TOARGBROW_AVX2)
ANY11C(YUY2ToARGBRow_Any_AVX2, YUY2ToARGBRow_AVX2, 1, 4, 4, 31)
ANY11C(UYVYToARGBRow_Any_AVX2, UYVYToARGBRow_AVX2, 1, 4, 4, 31)
#endif
#if defined(HAS_YUY2TOARGBROW_NEON)
ANY11C(YUY2ToARGBRow_Any_NEON, YUY2ToARGBRow_NEON, 1, 4, 4, 7)
ANY11C(UYVYToARGBRow_Any_NEON, UYVYToARGBRow_NEON, 1, 4, 4, 7)
#endif
#undef ANY11C
// Any 1 to 1 blended.
// Any 1 to 1 blended. Destination is read, modify, write.
#define ANY11B(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \
void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, int width) { \
SIMD_ALIGNED(uint8 temp[128 * 2]); \
@ -516,7 +493,7 @@ ANY11C(UYVYToARGBRow_Any_NEON, UYVYToARGBRow_NEON, 1, 4, 4, 7)
#ifdef HAS_ARGBCOPYALPHAROW_AVX2
ANY11B(ARGBCopyAlphaRow_Any_AVX2, ARGBCopyAlphaRow_AVX2, 0, 4, 4, 15)
#endif
#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2
#ifdef HAS_ARGBCOPYALPHAROW_SSE2
ANY11B(ARGBCopyAlphaRow_Any_SSE2, ARGBCopyAlphaRow_SSE2, 0, 4, 4, 7)
#endif
#ifdef HAS_ARGBCOPYYTOALPHAROW_AVX2
@ -569,6 +546,35 @@ ANY11P(ARGBShuffleRow_Any_NEON, ARGBShuffleRow_NEON, const uint8*, 4, 4, 3)
#endif
#undef ANY11P
// Any 1 to 1 with yuvconstants
#define ANY11C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \
void NAMEANY(const uint8* src_ptr, uint8* dst_ptr, \
const struct YuvConstants* yuvconstants, int width) { \
SIMD_ALIGNED(uint8 temp[128 * 2]); \
memset(temp, 0, 128); /* for YUY2 and msan */ \
int r = width & MASK; \
int n = width & ~MASK; \
if (n > 0) { \
ANY_SIMD(src_ptr, dst_ptr, yuvconstants, n); \
} \
memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \
ANY_SIMD(temp, temp + 128, yuvconstants, MASK + 1); \
memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \
}
#if defined(HAS_YUY2TOARGBROW_SSSE3)
ANY11C(YUY2ToARGBRow_Any_SSSE3, YUY2ToARGBRow_SSSE3, 1, 4, 4, 15)
ANY11C(UYVYToARGBRow_Any_SSSE3, UYVYToARGBRow_SSSE3, 1, 4, 4, 15)
#endif
#if defined(HAS_YUY2TOARGBROW_AVX2)
ANY11C(YUY2ToARGBRow_Any_AVX2, YUY2ToARGBRow_AVX2, 1, 4, 4, 31)
ANY11C(UYVYToARGBRow_Any_AVX2, UYVYToARGBRow_AVX2, 1, 4, 4, 31)
#endif
#if defined(HAS_YUY2TOARGBROW_NEON)
ANY11C(YUY2ToARGBRow_Any_NEON, YUY2ToARGBRow_NEON, 1, 4, 4, 7)
ANY11C(UYVYToARGBRow_Any_NEON, UYVYToARGBRow_NEON, 1, 4, 4, 7)
#endif
#undef ANY11C
// Any 1 to 1 interpolate. Takes 2 rows of source via stride.
#define ANY11T(NAMEANY, ANY_SIMD, SBPP, BPP, MASK) \
void NAMEANY(uint8* dst_ptr, const uint8* src_ptr, \

View File

@ -2381,6 +2381,19 @@ void ARGBCopyAlphaRow_C(const uint8* src, uint8* dst, int width) {
}
}
void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width) {
int i;
for (i = 0; i < width - 1; i += 2) {
dst_a[0] = src_argb[3];
dst_a[1] = src_argb[7];
dst_a += 2;
src_argb += 8;
}
if (width & 1) {
dst_a[0] = src_argb[3];
}
}
void ARGBCopyYToAlphaRow_C(const uint8* src, uint8* dst, int width) {
int i;
for (i = 0; i < width - 1; i += 2) {

View File

@ -2936,6 +2936,33 @@ void ARGBCopyAlphaRow_AVX2(const uint8* src, uint8* dst, int width) {
}
#endif // HAS_ARGBCOPYALPHAROW_AVX2
#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2
// width in pixels
void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width) {
asm volatile (
LABELALIGN
"1: \n"
"movdqu " MEMACCESS(0) ", %%xmm0 \n"
"movdqu " MEMACCESS2(0x10, 0) ", %%xmm1 \n"
"lea " MEMLEA(0x20, 0) ", %0 \n"
"psrld $0x18, %%xmm0 \n"
"psrld $0x18, %%xmm1 \n"
"packssdw %%xmm1, %%xmm0 \n"
"packuswb %%xmm0, %%xmm0 \n"
"movq %%xmm0," MEMACCESS(1) " \n"
"lea " MEMLEA(0x8, 1) ", %1 \n"
"sub $0x8, %2 \n"
"jg 1b \n"
: "+r"(src_argb), // %0
"+r"(dst_a), // %1
"+rm"(width) // %2
:
: "memory", "cc"
, "xmm0", "xmm1"
);
}
#endif // HAS_ARGBEXTRACTALPHAROW_SSE2
#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2
// width in pixels
void ARGBCopyYToAlphaRow_SSE2(const uint8* src, uint8* dst, int width) {
@ -3569,7 +3596,7 @@ void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1,
"+r"(src1), // %1
"+r"(alpha), // %2
"+r"(dst), // %3
"+r"(width) // %4
"+rm"(width) // %4
:: "memory", "cc", "eax", "xmm0", "xmm1", "xmm2", "xmm5", "xmm6", "xmm7"
);
}
@ -3626,7 +3653,7 @@ void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1,
"+r"(src1), // %1
"+r"(alpha), // %2
"+r"(dst), // %3
"+r"(width) // %4
"+rm"(width) // %4
:: "memory", "cc", "eax",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"
);
@ -4909,9 +4936,9 @@ void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr,
"jg 100b \n"
"99: \n"
: "+r"(dst_ptr), // %0
"+r"(src_ptr), // %1
"+r"(dst_width), // %2
: "+r"(dst_ptr), // %0
"+r"(src_ptr), // %1
"+rm"(dst_width), // %2
"+r"(source_y_fraction) // %3
: "r"((intptr_t)(src_stride)) // %4
: "memory", "cc", "eax", NACL_R14
@ -4987,7 +5014,7 @@ void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr,
"999: \n"
: "+D"(dst_ptr), // %0
"+S"(src_ptr), // %1
"+c"(dst_width), // %2
"+cm"(dst_width), // %2
"+r"(source_y_fraction) // %3
: "r"((intptr_t)(src_stride)) // %4
: "memory", "cc", "eax", NACL_R14

View File

@ -381,7 +381,7 @@ void CopyRow_MIPS(const uint8* src, uint8* dst, int count) {
(_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6)
void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
int width) {
int width) {
__asm__ __volatile__ (
".set push \n"
".set noreorder \n"
@ -497,7 +497,7 @@ void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width) {
}
void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
int width) {
int width) {
int x;
int y;
__asm__ __volatile__ (
@ -654,11 +654,11 @@ void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
// TODO(fbarchard): accept yuv conversion constants.
void I422ToARGBRow_DSPR2(const uint8* y_buf,
const uint8* u_buf,
const uint8* v_buf,
uint8* rgb_buf,
const struct YuvConstants* yuvconstants,
int width) {
const uint8* u_buf,
const uint8* v_buf,
uint8* rgb_buf,
const struct YuvConstants* yuvconstants,
int width) {
__asm__ __volatile__ (
".set push \n"
".set noreorder \n"
@ -717,8 +717,8 @@ void I422ToARGBRow_DSPR2(const uint8* y_buf,
// Bilinear filter 8x2 -> 8x1
void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr,
ptrdiff_t src_stride, int dst_width,
int source_y_fraction) {
ptrdiff_t src_stride, int dst_width,
int source_y_fraction) {
int y0_fraction = 256 - source_y_fraction;
const uint8* src_ptr1 = src_ptr + src_stride;

View File

@ -1298,6 +1298,24 @@ void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
);
}
void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width) {
asm volatile (
"1: \n"
MEMACCESS(0)
"vld4.8 {d0, d2, d4, d6}, [%0]! \n" // load 8 ARGB pixels
"vld4.8 {d1, d3, d5, d7}, [%0]! \n" // load next 8 ARGB pixels
"subs %2, %2, #16 \n" // 16 processed per loop
MEMACCESS(1)
"vst1.8 {q3}, [%1]! \n" // store 16 A's.
"bgt 1b \n"
: "+r"(src_argb), // %0
"+r"(dst_a), // %1
"+r"(width) // %2
:
: "cc", "memory", "q0", "q1", "q2", "q3" // Clobber List
);
}
void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
asm volatile (
"vmov.u8 d24, #15 \n" // B * 0.11400 coefficient
@ -2565,8 +2583,6 @@ void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb,
);
}
// TODO(fbarchard): fix vqshrun in ARGBMultiplyRow_NEON and reenable.
#ifdef HAS_ARGBMULTIPLYROW_NEON
// Multiply 2 rows of ARGB pixels together, 8 pixels at a time.
void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
uint8* dst_argb, int width) {
@ -2598,7 +2614,6 @@ void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
: "cc", "memory", "q0", "q1", "q2", "q3"
);
}
#endif // HAS_ARGBMULTIPLYROW_NEON
// Add 2 rows of ARGB pixels together, 8 pixels at a time.
void ARGBAddRow_NEON(const uint8* src_argb0, const uint8* src_argb1,

View File

@ -127,7 +127,6 @@ extern "C" {
"sqshrun " #vG ".8b, " #vG ".8h, #6 \n" /* G */ \
"sqshrun " #vR ".8b, " #vR ".8h, #6 \n" /* R */ \
#ifdef HAS_I444TOARGBROW_NEON
void I444ToARGBRow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -157,9 +156,7 @@ void I444ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I444TOARGBROW_NEON
#ifdef HAS_I422TOARGBROW_NEON
void I422ToARGBRow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -189,9 +186,7 @@ void I422ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TOARGBROW_NEON
#ifdef HAS_I422ALPHATOARGBROW_NEON
void I422AlphaToARGBRow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -224,9 +219,7 @@ void I422AlphaToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422ALPHATOARGBROW_NEON
#ifdef HAS_I411TOARGBROW_NEON
void I411ToARGBRow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -256,9 +249,7 @@ void I411ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I411TOARGBROW_NEON
#ifdef HAS_I422TORGBAROW_NEON
void I422ToRGBARow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -288,9 +279,7 @@ void I422ToRGBARow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TORGBAROW_NEON
#ifdef HAS_I422TORGB24ROW_NEON
void I422ToRGB24Row_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -319,7 +308,6 @@ void I422ToRGB24Row_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TORGB24ROW_NEON
#define ARGBTORGB565 \
"shll v0.8h, v22.8b, #8 \n" /* R */ \
@ -328,7 +316,6 @@ void I422ToRGB24Row_NEON(const uint8* src_y,
"sri v0.8h, v21.8h, #5 \n" /* RG */ \
"sri v0.8h, v20.8h, #11 \n" /* RGB */
#ifdef HAS_I422TORGB565ROW_NEON
void I422ToRGB565Row_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -358,7 +345,6 @@ void I422ToRGB565Row_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TORGB565ROW_NEON
#define ARGBTOARGB1555 \
"shll v0.8h, v23.8b, #8 \n" /* A */ \
@ -369,7 +355,6 @@ void I422ToRGB565Row_NEON(const uint8* src_y,
"sri v0.8h, v21.8h, #6 \n" /* ARG */ \
"sri v0.8h, v20.8h, #11 \n" /* ARGB */
#ifdef HAS_I422TOARGB1555ROW_NEON
void I422ToARGB1555Row_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -400,7 +385,6 @@ void I422ToARGB1555Row_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TOARGB1555ROW_NEON
#define ARGBTOARGB4444 \
/* Input v20.8b<=B, v21.8b<=G, v22.8b<=R, v23.8b<=A, v4.8b<=0x0f */ \
@ -412,7 +396,6 @@ void I422ToARGB1555Row_NEON(const uint8* src_y,
"orr v1.8b, v22.8b, v23.8b \n" /* RA */ \
"zip1 v0.16b, v0.16b, v1.16b \n" /* BGRA */
#ifdef HAS_I422TOARGB4444ROW_NEON
void I422ToARGB4444Row_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -444,13 +427,10 @@ void I422ToARGB4444Row_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I422TOARGB4444ROW_NEON
#ifdef HAS_I400TOARGBROW_NEON
void I400ToARGBRow_NEON(const uint8* src_y,
uint8* dst_argb,
int width) {
int64 width64 = (int64)(width);
asm volatile (
YUVTORGB_SETUP
"movi v23.8b, #255 \n"
@ -463,7 +443,7 @@ void I400ToARGBRow_NEON(const uint8* src_y,
"b.gt 1b \n"
: "+r"(src_y), // %0
"+r"(dst_argb), // %1
"+r"(width64) // %2
"+r"(width) // %2
: [kUVToRB]"r"(&kYuvI601Constants.kUVToRB),
[kUVToG]"r"(&kYuvI601Constants.kUVToG),
[kUVBiasBGR]"r"(&kYuvI601Constants.kUVBiasBGR),
@ -472,9 +452,7 @@ void I400ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_I400TOARGBROW_NEON
#ifdef HAS_J400TOARGBROW_NEON
void J400ToARGBRow_NEON(const uint8* src_y,
uint8* dst_argb,
int width) {
@ -496,9 +474,7 @@ void J400ToARGBRow_NEON(const uint8* src_y,
: "cc", "memory", "v20", "v21", "v22", "v23"
);
}
#endif // HAS_J400TOARGBROW_NEON
#ifdef HAS_NV12TOARGBROW_NEON
void NV12ToARGBRow_NEON(const uint8* src_y,
const uint8* src_uv,
uint8* dst_argb,
@ -526,9 +502,7 @@ void NV12ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_NV12TOARGBROW_NEON
#ifdef HAS_NV12TOARGBROW_NEON
void NV21ToARGBRow_NEON(const uint8* src_y,
const uint8* src_vu,
uint8* dst_argb,
@ -556,9 +530,7 @@ void NV21ToARGBRow_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_NV12TOARGBROW_NEON
#ifdef HAS_NV12TORGB565ROW_NEON
void NV12ToRGB565Row_NEON(const uint8* src_y,
const uint8* src_uv,
uint8* dst_rgb565,
@ -586,14 +558,11 @@ void NV12ToRGB565Row_NEON(const uint8* src_y,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_NV12TORGB565ROW_NEON
#ifdef HAS_YUY2TOARGBROW_NEON
void YUY2ToARGBRow_NEON(const uint8* src_yuy2,
uint8* dst_argb,
const struct YuvConstants* yuvconstants,
int width) {
int64 width64 = (int64)(width);
asm volatile (
YUVTORGB_SETUP
"movi v23.8b, #255 \n"
@ -606,7 +575,7 @@ void YUY2ToARGBRow_NEON(const uint8* src_yuy2,
"b.gt 1b \n"
: "+r"(src_yuy2), // %0
"+r"(dst_argb), // %1
"+r"(width64) // %2
"+r"(width) // %2
: [kUVToRB]"r"(&yuvconstants->kUVToRB),
[kUVToG]"r"(&yuvconstants->kUVToG),
[kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR),
@ -615,14 +584,11 @@ void YUY2ToARGBRow_NEON(const uint8* src_yuy2,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_YUY2TOARGBROW_NEON
#ifdef HAS_UYVYTOARGBROW_NEON
void UYVYToARGBRow_NEON(const uint8* src_uyvy,
uint8* dst_argb,
const struct YuvConstants* yuvconstants,
int width) {
int64 width64 = (int64)(width);
asm volatile (
YUVTORGB_SETUP
"movi v23.8b, #255 \n"
@ -635,7 +601,7 @@ void UYVYToARGBRow_NEON(const uint8* src_uyvy,
"b.gt 1b \n"
: "+r"(src_uyvy), // %0
"+r"(dst_argb), // %1
"+r"(width64) // %2
"+r"(width) // %2
: [kUVToRB]"r"(&yuvconstants->kUVToRB),
[kUVToG]"r"(&yuvconstants->kUVToG),
[kUVBiasBGR]"r"(&yuvconstants->kUVBiasBGR),
@ -644,10 +610,8 @@ void UYVYToARGBRow_NEON(const uint8* src_uyvy,
"v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30"
);
}
#endif // HAS_UYVYTOARGBROW_NEON
// Reads 16 pairs of UV and write even values to dst_u and odd to dst_v.
#ifdef HAS_SPLITUVROW_NEON
void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
int width) {
asm volatile (
@ -668,10 +632,8 @@ void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
: "cc", "memory", "v0", "v1" // Clobber List
);
}
#endif // HAS_SPLITUVROW_NEON
// Reads 16 U's and V's and writes out 16 pairs of UV.
#ifdef HAS_MERGEUVROW_NEON
void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv,
int width) {
asm volatile (
@ -693,10 +655,8 @@ void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv,
: "cc", "memory", "v0", "v1" // Clobber List
);
}
#endif // HAS_MERGEUVROW_NEON
// Copy multiple of 32. vld4.8 allow unaligned and is fastest on a15.
#ifdef HAS_COPYROW_NEON
void CopyRow_NEON(const uint8* src, uint8* dst, int count) {
asm volatile (
"1: \n"
@ -713,17 +673,16 @@ void CopyRow_NEON(const uint8* src, uint8* dst, int count) {
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_COPYROW_NEON
// SetRow writes 'count' bytes using an 8 bit value repeated.
void SetRow_NEON(uint8* dst, uint8 v8, int count) {
asm volatile (
"dup v0.16b, %w2 \n" // duplicate 16 bytes
"1: \n"
"subs %w1, %w1, #16 \n" // 16 bytes per loop
"subs %w1, %w1, #16 \n" // 16 bytes per loop
MEMACCESS(0)
"st1 {v0.16b}, [%0], #16 \n" // store
"b.gt 1b \n"
"b.gt 1b \n"
: "+r"(dst), // %0
"+r"(count) // %1
: "r"(v8) // %2
@ -735,10 +694,10 @@ void ARGBSetRow_NEON(uint8* dst, uint32 v32, int count) {
asm volatile (
"dup v0.4s, %w2 \n" // duplicate 4 ints
"1: \n"
"subs %w1, %w1, #4 \n" // 4 ints per loop
"subs %w1, %w1, #4 \n" // 4 ints per loop
MEMACCESS(0)
"st1 {v0.16b}, [%0], #16 \n" // store
"b.gt 1b \n"
"b.gt 1b \n"
: "+r"(dst), // %0
"+r"(count) // %1
: "r"(v32) // %2
@ -746,18 +705,15 @@ void ARGBSetRow_NEON(uint8* dst, uint32 v32, int count) {
);
}
#ifdef HAS_MIRRORROW_NEON
void MirrorRow_NEON(const uint8* src, uint8* dst, int width) {
int64 width64 = (int64) width;
asm volatile (
// Start at end of source row.
"add %0, %0, %2 \n"
"add %0, %0, %w2, sxtw \n"
"sub %0, %0, #16 \n"
"1: \n"
MEMACCESS(0)
"ld1 {v0.16b}, [%0], %3 \n" // src -= 16
"subs %2, %2, #16 \n" // 16 pixels per loop.
"subs %w2, %w2, #16 \n" // 16 pixels per loop.
"rev64 v0.16b, v0.16b \n"
MEMACCESS(1)
"st1 {v0.D}[1], [%1], #8 \n" // dst += 16
@ -766,26 +722,22 @@ void MirrorRow_NEON(const uint8* src, uint8* dst, int width) {
"b.gt 1b \n"
: "+r"(src), // %0
"+r"(dst), // %1
"+r"(width64) // %2
"+r"(width) // %2
: "r"((ptrdiff_t)-16) // %3
: "cc", "memory", "v0"
);
}
#endif // HAS_MIRRORROW_NEON
#ifdef HAS_MIRRORUVROW_NEON
void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
int width) {
int64 width64 = (int64) width;
asm volatile (
// Start at end of source row.
"add %0, %0, %3, lsl #1 \n"
"add %0, %0, %w3, sxtw #1 \n"
"sub %0, %0, #16 \n"
"1: \n"
MEMACCESS(0)
"ld2 {v0.8b, v1.8b}, [%0], %4 \n" // src -= 16
"subs %3, %3, #8 \n" // 8 pixels per loop.
"subs %w3, %w3, #8 \n" // 8 pixels per loop.
"rev64 v0.8b, v0.8b \n"
"rev64 v1.8b, v1.8b \n"
MEMACCESS(1)
@ -796,25 +748,21 @@ void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v,
: "+r"(src_uv), // %0
"+r"(dst_u), // %1
"+r"(dst_v), // %2
"+r"(width64) // %3
"+r"(width) // %3
: "r"((ptrdiff_t)-16) // %4
: "cc", "memory", "v0", "v1"
);
}
#endif // HAS_MIRRORUVROW_NEON
#ifdef HAS_ARGBMIRRORROW_NEON
void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width) {
int64 width64 = (int64) width;
asm volatile (
// Start at end of source row.
"add %0, %0, %2, lsl #2 \n"
// Start at end of source row.
"add %0, %0, %w2, sxtw #2 \n"
"sub %0, %0, #16 \n"
"1: \n"
MEMACCESS(0)
"ld1 {v0.16b}, [%0], %3 \n" // src -= 16
"subs %2, %2, #4 \n" // 4 pixels per loop.
"subs %w2, %w2, #4 \n" // 4 pixels per loop.
"rev64 v0.4s, v0.4s \n"
MEMACCESS(1)
"st1 {v0.D}[1], [%1], #8 \n" // dst += 16
@ -823,14 +771,12 @@ void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width) {
"b.gt 1b \n"
: "+r"(src), // %0
"+r"(dst), // %1
"+r"(width64) // %2
"+r"(width) // %2
: "r"((ptrdiff_t)-16) // %3
: "cc", "memory", "v0"
);
}
#endif // HAS_ARGBMIRRORROW_NEON
#ifdef HAS_RGB24TOARGBROW_NEON
void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width) {
asm volatile (
"movi v4.8b, #255 \n" // Alpha
@ -843,14 +789,12 @@ void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width) {
"b.gt 1b \n"
: "+r"(src_rgb24), // %0
"+r"(dst_argb), // %1
"+r"(width) // %2
"+r"(width) // %2
:
: "cc", "memory", "v1", "v2", "v3", "v4" // Clobber List
);
}
#endif // HAS_RGB24TOARGBROW_NEON
#ifdef HAS_RAWTOARGBROW_NEON
void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width) {
asm volatile (
"movi v5.8b, #255 \n" // Alpha
@ -865,12 +809,11 @@ void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width) {
"b.gt 1b \n"
: "+r"(src_raw), // %0
"+r"(dst_argb), // %1
"+r"(width) // %2
"+r"(width) // %2
:
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5" // Clobber List
);
}
#endif // HAS_RAWTOARGBROW_NEON
void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width) {
asm volatile (
@ -904,7 +847,6 @@ void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width) {
"orr v0.16b, v0.16b, v2.16b \n" /* R,B */ \
"dup v2.2D, v0.D[1] \n" /* R */
#ifdef HAS_RGB565TOARGBROW_NEON
void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width) {
asm volatile (
"movi v3.8b, #255 \n" // Alpha
@ -923,7 +865,6 @@ void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v6" // Clobber List
);
}
#endif // HAS_RGB565TOARGBROW_NEON
#define ARGB1555TOARGB \
"ushr v2.8h, v0.8h, #10 \n" /* R xxxRRRRR */ \
@ -962,7 +903,6 @@ void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width) {
"orr v2.16b, v1.16b, v3.16b \n" /* R */ \
"dup v1.2D, v0.D[1] \n" /* G */ \
#ifdef HAS_ARGB1555TOARGBROW_NEON
void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb,
int width) {
asm volatile (
@ -982,7 +922,6 @@ void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb,
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_ARGB1555TOARGBROW_NEON
#define ARGB4444TOARGB \
"shrn v1.8b, v0.8h, #8 \n" /* v1(l) AR */ \
@ -996,7 +935,6 @@ void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb,
"dup v0.2D, v2.D[1] \n" \
"dup v1.2D, v3.D[1] \n"
#ifdef HAS_ARGB4444TOARGBROW_NEON
void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb,
int width) {
asm volatile (
@ -1015,9 +953,7 @@ void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb,
: "cc", "memory", "v0", "v1", "v2", "v3", "v4" // Clobber List
);
}
#endif // HAS_ARGB4444TOARGBROW_NEON
#ifdef HAS_ARGBTORGB24ROW_NEON
void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb24, int width) {
asm volatile (
"1: \n"
@ -1034,9 +970,7 @@ void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb24, int width) {
: "cc", "memory", "v1", "v2", "v3", "v4" // Clobber List
);
}
#endif // HAS_ARGBTORGB24ROW_NEON
#ifdef HAS_ARGBTORAWROW_NEON
void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_raw, int width) {
asm volatile (
"1: \n"
@ -1055,9 +989,7 @@ void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_raw, int width) {
: "cc", "memory", "v1", "v2", "v3", "v4", "v5" // Clobber List
);
}
#endif // HAS_ARGBTORAWROW_NEON
#ifdef HAS_YUY2TOYROW_NEON
void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width) {
asm volatile (
"1: \n"
@ -1074,9 +1006,7 @@ void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1" // Clobber List
);
}
#endif // HAS_YUY2TOYROW_NEON
#ifdef HAS_UYVYTOYROW_NEON
void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width) {
asm volatile (
"1: \n"
@ -1093,9 +1023,7 @@ void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1" // Clobber List
);
}
#endif // HAS_UYVYTOYROW_NEON
#ifdef HAS_YUY2TOUV422ROW_NEON
void YUY2ToUV422Row_NEON(const uint8* src_yuy2, uint8* dst_u, uint8* dst_v,
int width) {
asm volatile (
@ -1116,9 +1044,7 @@ void YUY2ToUV422Row_NEON(const uint8* src_yuy2, uint8* dst_u, uint8* dst_v,
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_YUY2TOUV422ROW_NEON
#ifdef HAS_UYVYTOUV422ROW_NEON
void UYVYToUV422Row_NEON(const uint8* src_uyvy, uint8* dst_u, uint8* dst_v,
int width) {
asm volatile (
@ -1139,9 +1065,7 @@ void UYVYToUV422Row_NEON(const uint8* src_uyvy, uint8* dst_u, uint8* dst_v,
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_UYVYTOUV422ROW_NEON
#ifdef HAS_YUY2TOUVROW_NEON
void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_yuy2b = src_yuy2 + stride_yuy2;
@ -1169,9 +1093,7 @@ void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2,
"v5", "v6", "v7" // Clobber List
);
}
#endif // HAS_YUY2TOUVROW_NEON
#ifdef HAS_UYVYTOUVROW_NEON
void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_uyvyb = src_uyvy + stride_uyvy;
@ -1199,10 +1121,8 @@ void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy,
"v5", "v6", "v7" // Clobber List
);
}
#endif // HAS_UYVYTOUVROW_NEON
// For BGRAToARGB, ABGRToARGB, RGBAToARGB, and ARGBToRGBA.
#ifdef HAS_ARGBSHUFFLEROW_NEON
void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb,
const uint8* shuffler, int width) {
asm volatile (
@ -1223,9 +1143,7 @@ void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb,
: "cc", "memory", "v0", "v1", "v2" // Clobber List
);
}
#endif // HAS_ARGBSHUFFLEROW_NEON
#ifdef HAS_I422TOYUY2ROW_NEON
void I422ToYUY2Row_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -1252,9 +1170,7 @@ void I422ToYUY2Row_NEON(const uint8* src_y,
: "cc", "memory", "v0", "v1", "v2", "v3"
);
}
#endif // HAS_I422TOYUY2ROW_NEON
#ifdef HAS_I422TOUYVYROW_NEON
void I422ToUYVYRow_NEON(const uint8* src_y,
const uint8* src_u,
const uint8* src_v,
@ -1281,9 +1197,7 @@ void I422ToUYVYRow_NEON(const uint8* src_y,
: "cc", "memory", "v0", "v1", "v2", "v3"
);
}
#endif // HAS_I422TOUYVYROW_NEON
#ifdef HAS_ARGBTORGB565ROW_NEON
void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb565, int width) {
asm volatile (
"1: \n"
@ -1301,9 +1215,7 @@ void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb565, int width) {
: "cc", "memory", "v0", "v20", "v21", "v22", "v23"
);
}
#endif // HAS_ARGBTORGB565ROW_NEON
#ifdef HAS_ARGBTORGB565DITHERROW_NEON
void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb,
const uint32 dither4, int width) {
asm volatile (
@ -1326,9 +1238,7 @@ void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb,
: "cc", "memory", "v0", "v1", "v20", "v21", "v22", "v23"
);
}
#endif // HAS_ARGBTORGB565ROW_NEON
#ifdef HAS_ARGBTOARGB1555ROW_NEON
void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_argb1555,
int width) {
asm volatile (
@ -1347,9 +1257,7 @@ void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_argb1555,
: "cc", "memory", "v0", "v20", "v21", "v22", "v23"
);
}
#endif // HAS_ARGBTOARGB1555ROW_NEON
#ifdef HAS_ARGBTOARGB4444ROW_NEON
void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_argb4444,
int width) {
asm volatile (
@ -1369,9 +1277,7 @@ void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_argb4444,
: "cc", "memory", "v0", "v1", "v4", "v20", "v21", "v22", "v23"
);
}
#endif // HAS_ARGBTOARGB4444ROW_NEON
#ifdef HAS_ARGBTOYROW_NEON
void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #13 \n" // B * 0.1016 coefficient
@ -1397,9 +1303,24 @@ void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGBTOYROW_NEON
#ifdef HAS_ARGBTOYJROW_NEON
void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width) {
asm volatile (
"1: \n"
MEMACCESS(0)
"ld4 {v0.16b,v1.16b,v2.16b,v3.16b}, [%0], #64 \n" // load row 16 pixels
"subs %w2, %w2, #16 \n" // 16 processed per loop
MEMACCESS(1)
"st1 {v3.16b}, [%1], #16 \n" // store 16 A's.
"b.gt 1b \n"
: "+r"(src_argb), // %0
"+r"(dst_a), // %1
"+r"(width) // %2
:
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #15 \n" // B * 0.11400 coefficient
@ -1423,10 +1344,8 @@ void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6"
);
}
#endif // HAS_ARGBTOYJROW_NEON
// 8x1 pixels.
#ifdef HAS_ARGBTOUV444ROW_NEON
void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
int width) {
asm volatile (
@ -1467,7 +1386,6 @@ void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
"v24", "v25", "v26", "v27", "v28", "v29"
);
}
#endif // HAS_ARGBTOUV444ROW_NEON
#define RGBTOUV_SETUP_REG \
"movi v20.8h, #56, lsl #0 \n" /* UB/VR coefficient (0.875) / 2 */ \
@ -1478,7 +1396,6 @@ void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
"movi v25.16b, #0x80 \n" /* 128.5 (0x8080 in 16-bit) */
// 32x1 pixels -> 8x1. width is number of argb pixels. e.g. 32.
#ifdef HAS_ARGBTOUV411ROW_NEON
void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
int width) {
asm volatile (
@ -1528,7 +1445,6 @@ void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_ARGBTOUV411ROW_NEON
// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16.
#define RGBTOUV(QB, QG, QR) \
@ -1546,7 +1462,6 @@ void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v,
// TODO(fbarchard): Consider vhadd vertical, then vpaddl horizontal, avoid shr.
// TODO(fbarchard): consider ptrdiff_t for all strides.
#ifdef HAS_ARGBTOUVROW_NEON
void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_argb_1 = src_argb + src_stride_argb;
@ -1586,10 +1501,8 @@ void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_ARGBTOUVROW_NEON
// TODO(fbarchard): Subsample match C code.
#ifdef HAS_ARGBTOUVJROW_NEON
void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_argb_1 = src_argb + src_stride_argb;
@ -1633,9 +1546,7 @@ void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_ARGBTOUVJROW_NEON
#ifdef HAS_BGRATOUVROW_NEON
void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_bgra_1 = src_bgra + src_stride_bgra;
@ -1674,9 +1585,7 @@ void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_BGRATOUVROW_NEON
#ifdef HAS_ABGRTOUVROW_NEON
void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_abgr_1 = src_abgr + src_stride_abgr;
@ -1715,9 +1624,7 @@ void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_ABGRTOUVROW_NEON
#ifdef HAS_RGBATOUVROW_NEON
void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_rgba_1 = src_rgba + src_stride_rgba;
@ -1756,9 +1663,7 @@ void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_RGBATOUVROW_NEON
#ifdef HAS_RGB24TOUVROW_NEON
void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_rgb24_1 = src_rgb24 + src_stride_rgb24;
@ -1797,9 +1702,7 @@ void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_RGB24TOUVROW_NEON
#ifdef HAS_RAWTOUVROW_NEON
void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_raw_1 = src_raw + src_stride_raw;
@ -1838,10 +1741,8 @@ void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw,
"v20", "v21", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_RAWTOUVROW_NEON
// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16.
#ifdef HAS_RGB565TOUVROW_NEON
void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_rgb565_1 = src_rgb565 + src_stride_rgb565;
@ -1914,10 +1815,8 @@ void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565,
"v25", "v26", "v27"
);
}
#endif // HAS_RGB565TOUVROW_NEON
// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16.
#ifdef HAS_ARGB1555TOUVROW_NEON
void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_argb1555_1 = src_argb1555 + src_stride_argb1555;
@ -1985,10 +1884,8 @@ void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555,
"v26", "v27", "v28"
);
}
#endif // HAS_ARGB1555TOUVROW_NEON
// 16x2 pixels -> 8x1. width is number of argb pixels. e.g. 16.
#ifdef HAS_ARGB4444TOUVROW_NEON
void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444,
uint8* dst_u, uint8* dst_v, int width) {
const uint8* src_argb4444_1 = src_argb4444 + src_stride_argb4444;
@ -2057,9 +1954,7 @@ void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444,
);
}
#endif // HAS_ARGB4444TOUVROW_NEON
#ifdef HAS_RGB565TOYROW_NEON
void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width) {
asm volatile (
"movi v24.8b, #13 \n" // B * 0.1016 coefficient
@ -2087,9 +1982,7 @@ void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width) {
"v24", "v25", "v26", "v27"
);
}
#endif // HAS_RGB565TOYROW_NEON
#ifdef HAS_ARGB1555TOYROW_NEON
void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #13 \n" // B * 0.1016 coefficient
@ -2116,9 +2009,7 @@ void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGB1555TOYROW_NEON
#ifdef HAS_ARGB4444TOYROW_NEON
void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width) {
asm volatile (
"movi v24.8b, #13 \n" // B * 0.1016 coefficient
@ -2145,9 +2036,7 @@ void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v24", "v25", "v26", "v27"
);
}
#endif // HAS_ARGB4444TOYROW_NEON
#ifdef HAS_BGRATOYROW_NEON
void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #33 \n" // R * 0.2578 coefficient
@ -2173,9 +2062,7 @@ void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16"
);
}
#endif // HAS_BGRATOYROW_NEON
#ifdef HAS_ABGRTOYROW_NEON
void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #33 \n" // R * 0.2578 coefficient
@ -2201,9 +2088,7 @@ void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16"
);
}
#endif // HAS_ABGRTOYROW_NEON
#ifdef HAS_RGBATOYROW_NEON
void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #13 \n" // B * 0.1016 coefficient
@ -2229,9 +2114,7 @@ void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16"
);
}
#endif // HAS_RGBATOYROW_NEON
#ifdef HAS_RGB24TOYROW_NEON
void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #13 \n" // B * 0.1016 coefficient
@ -2257,9 +2140,7 @@ void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16"
);
}
#endif // HAS_RGB24TOYROW_NEON
#ifdef HAS_RAWTOYROW_NEON
void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width) {
asm volatile (
"movi v4.8b, #33 \n" // R * 0.2578 coefficient
@ -2285,10 +2166,8 @@ void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16"
);
}
#endif // HAS_RAWTOYROW_NEON
// Bilinear filter 16x2 -> 16x1
#ifdef HAS_INTERPOLATEROW_NEON
void InterpolateRow_NEON(uint8* dst_ptr,
const uint8* src_ptr, ptrdiff_t src_stride,
int dst_width, int source_y_fraction) {
@ -2354,10 +2233,8 @@ void InterpolateRow_NEON(uint8* dst_ptr,
: "cc", "memory", "v0", "v1", "v3", "v4", "v5"
);
}
#endif // HAS_INTERPOLATEROW_NEON
// dr * (256 - sa) / 256 + sr = dr - dr * sa / 256 + sr
#ifdef HAS_ARGBBLENDROW_NEON
void ARGBBlendRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
uint8* dst_argb, int width) {
asm volatile (
@ -2426,10 +2303,8 @@ void ARGBBlendRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
"v16", "v17", "v18"
);
}
#endif // HAS_ARGBBLENDROW_NEON
// Attenuate 8 pixels at a time.
#ifdef HAS_ARGBATTENUATEROW_NEON
void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) {
asm volatile (
// Attenuate 8 pixels.
@ -2453,11 +2328,9 @@ void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6"
);
}
#endif // HAS_ARGBATTENUATEROW_NEON
// Quantize 8 ARGB pixels (32 bytes).
// dst = (dst * scale >> 16) * interval_size + interval_offset;
#ifdef HAS_ARGBQUANTIZEROW_NEON
void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size,
int interval_offset, int width) {
asm volatile (
@ -2497,12 +2370,10 @@ void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size,
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6"
);
}
#endif // HAS_ARGBQUANTIZEROW_NEON
// Shade 8 pixels at a time by specified value.
// NOTE vqrdmulh.s16 q10, q10, d0[0] must use a scaler register from 0 to 8.
// Rounding in vqrdmulh does +1 to high if high bit of low s16 is set.
#ifdef HAS_ARGBSHADEROW_NEON
void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width,
uint32 value) {
asm volatile (
@ -2537,12 +2408,10 @@ void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width,
: "cc", "memory", "v0", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGBSHADEROW_NEON
// Convert 8 ARGB pixels (64 bytes) to 8 Gray ARGB pixels
// Similar to ARGBToYJ but stores ARGB.
// C code is (15 * b + 75 * g + 38 * r + 64) >> 7;
#ifdef HAS_ARGBGRAYROW_NEON
void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) {
asm volatile (
"movi v24.8b, #15 \n" // B * 0.11400 coefficient
@ -2568,14 +2437,12 @@ void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width) {
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v24", "v25", "v26"
);
}
#endif // HAS_ARGBGRAYROW_NEON
// Convert 8 ARGB pixels (32 bytes) to 8 Sepia ARGB pixels.
// b = (r * 35 + g * 68 + b * 17) >> 7
// g = (r * 45 + g * 88 + b * 22) >> 7
// r = (r * 50 + g * 98 + b * 24) >> 7
#ifdef HAS_ARGBSEPIAROW_NEON
void ARGBSepiaRow_NEON(uint8* dst_argb, int width) {
asm volatile (
"movi v20.8b, #17 \n" // BB coefficient
@ -2613,12 +2480,10 @@ void ARGBSepiaRow_NEON(uint8* dst_argb, int width) {
"v20", "v21", "v22", "v24", "v25", "v26", "v28", "v29", "v30"
);
}
#endif // HAS_ARGBSEPIAROW_NEON
// Tranform 8 ARGB pixels (32 bytes) with color matrix.
// TODO(fbarchard): Was same as Sepia except matrix is provided. This function
// needs to saturate. Consider doing a non-saturating version.
#ifdef HAS_ARGBCOLORMATRIXROW_NEON
void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb,
const int8* matrix_argb, int width) {
asm volatile (
@ -2678,11 +2543,9 @@ void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb,
"v18", "v19", "v22", "v23", "v24", "v25"
);
}
#endif // HAS_ARGBCOLORMATRIXROW_NEON
// TODO(fbarchard): fix vqshrun in ARGBMultiplyRow_NEON and reenable.
// Multiply 2 rows of ARGB pixels together, 8 pixels at a time.
#ifdef HAS_ARGBMULTIPLYROW_NEON
void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
uint8* dst_argb, int width) {
asm volatile (
@ -2713,10 +2576,8 @@ void ARGBMultiplyRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGBMULTIPLYROW_NEON
// Add 2 rows of ARGB pixels together, 8 pixels at a time.
#ifdef HAS_ARGBADDROW_NEON
void ARGBAddRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
uint8* dst_argb, int width) {
asm volatile (
@ -2743,10 +2604,8 @@ void ARGBAddRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGBADDROW_NEON
// Subtract 2 rows of ARGB pixels, 8 pixels at a time.
#ifdef HAS_ARGBSUBTRACTROW_NEON
void ARGBSubtractRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
uint8* dst_argb, int width) {
asm volatile (
@ -2773,14 +2632,12 @@ void ARGBSubtractRow_NEON(const uint8* src_argb0, const uint8* src_argb1,
: "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7"
);
}
#endif // HAS_ARGBSUBTRACTROW_NEON
// Adds Sobel X and Sobel Y and stores Sobel into ARGB.
// A = 255
// R = Sobel
// G = Sobel
// B = Sobel
#ifdef HAS_SOBELROW_NEON
void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
uint8* dst_argb, int width) {
asm volatile (
@ -2806,10 +2663,8 @@ void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
: "cc", "memory", "v0", "v1", "v2", "v3"
);
}
#endif // HAS_SOBELROW_NEON
// Adds Sobel X and Sobel Y and stores Sobel into plane.
#ifdef HAS_SOBELTOPLANEROW_NEON
void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
uint8* dst_y, int width) {
asm volatile (
@ -2832,14 +2687,12 @@ void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
: "cc", "memory", "v0", "v1"
);
}
#endif // HAS_SOBELTOPLANEROW_NEON
// Mixes Sobel X, Sobel Y and Sobel into ARGB.
// A = 255
// R = Sobel X
// G = Sobel
// B = Sobel Y
#ifdef HAS_SOBELXYROW_NEON
void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
uint8* dst_argb, int width) {
asm volatile (
@ -2863,13 +2716,11 @@ void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely,
: "cc", "memory", "v0", "v1", "v2", "v3"
);
}
#endif // HAS_SOBELXYROW_NEON
// SobelX as a matrix is
// -1 0 1
// -2 0 2
// -1 0 1
#ifdef HAS_SOBELXROW_NEON
void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1,
const uint8* src_y2, uint8* dst_sobelx, int width) {
asm volatile (
@ -2908,13 +2759,11 @@ void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1,
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_SOBELXROW_NEON
// SobelY as a matrix is
// -1 -2 -1
// 0 0 0
// 1 2 1
#ifdef HAS_SOBELYROW_NEON
void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1,
uint8* dst_sobely, int width) {
asm volatile (
@ -2952,7 +2801,6 @@ void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1,
: "cc", "memory", "v0", "v1", "v2", "v3" // Clobber List
);
}
#endif // HAS_SOBELYROW_NEON
#endif // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__)
#ifdef __cplusplus

View File

@ -3532,6 +3532,33 @@ void ARGBCopyAlphaRow_AVX2(const uint8* src, uint8* dst, int width) {
}
#endif // HAS_ARGBCOPYALPHAROW_AVX2
#ifdef HAS_ARGBEXTRACTALPHAROW_SSE2
// width in pixels
__declspec(naked)
void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width) {
__asm {
mov eax, [esp + 4] // src_argb
mov edx, [esp + 8] // dst_a
mov ecx, [esp + 12] // width
extractloop:
movdqu xmm0, [eax]
movdqu xmm1, [eax + 16]
lea eax, [eax + 32]
psrld xmm0, 24
psrld xmm1, 24
packssdw xmm0, xmm1
packuswb xmm0, xmm0
movq qword ptr [edx], xmm0
lea edx, [edx + 8]
sub ecx, 8
jg extractloop
ret
}
}
#endif // HAS_ARGBEXTRACTALPHAROW_SSE2
#ifdef HAS_ARGBCOPYYTOALPHAROW_SSE2
// width in pixels
__declspec(naked)
@ -5248,6 +5275,7 @@ void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely,
// dst points to pixel to store result to.
// count is number of averaged pixels to produce.
// Does 4 pixels at a time.
// This function requires alignment on accumulation buffer pointers.
void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft,
int width, int area, uint8* dst,
int count) {

View File

@ -25,6 +25,7 @@ struct FourCCAliasEntry {
static const struct FourCCAliasEntry kFourCCAliases[] = {
{FOURCC_IYUV, FOURCC_I420},
{FOURCC_YU12, FOURCC_I420},
{FOURCC_YU16, FOURCC_I422},
{FOURCC_YU24, FOURCC_I444},
{FOURCC_YUYV, FOURCC_YUY2},

View File

@ -231,6 +231,8 @@
<receiver android:name=".ShareBroadcastReceiver" android:enabled="true"/>
<receiver android:name=".NotificationDismissReceiver" android:exported="false"/>
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632dp" />

View File

@ -8,6 +8,9 @@
package org.telegram.messenger;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
@ -48,6 +51,7 @@ import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.AbsListView;
import android.widget.EdgeEffect;
import android.widget.EditText;
@ -61,10 +65,6 @@ import net.hockeyapp.android.UpdateManager;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.messenger.AnimationCompat.AnimatorListenerAdapterProxy;
import org.telegram.messenger.AnimationCompat.AnimatorSetProxy;
import org.telegram.messenger.AnimationCompat.ObjectAnimatorProxy;
import org.telegram.messenger.AnimationCompat.ViewProxy;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.Components.ForegroundDetector;
import org.telegram.ui.Components.NumberPicker;
@ -156,11 +156,7 @@ public class AndroidUtilities {
}
}
} else if (drawable instanceof ColorDrawable) {
if (Build.VERSION.SDK_INT >= 11) {
bitmapColor = ((ColorDrawable) drawable).getColor();
} else {
bitmapColor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).getInt("selectedColor", 0xff000000);
}
bitmapColor = ((ColorDrawable) drawable).getColor();
}
} catch (Exception e) {
FileLog.e("tmessages", e);
@ -500,11 +496,7 @@ public class AndroidUtilities {
Display display = manager.getDefaultDisplay();
if (display != null) {
display.getMetrics(displayMetrics);
if (android.os.Build.VERSION.SDK_INT < 13) {
displaySize.set(display.getWidth(), display.getHeight());
} else {
display.getSize(displaySize);
}
display.getSize(displaySize);
FileLog.e("tmessages", "display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi);
}
}
@ -685,7 +677,7 @@ public class AndroidUtilities {
}
public static void clearCursorDrawable(EditText editText) {
if (editText == null || Build.VERSION.SDK_INT < 12) {
if (editText == null) {
return;
}
try {
@ -883,6 +875,19 @@ public class AndroidUtilities {
return size;
}
public static CharSequence getTrimmedString(CharSequence src) {
if (src == null || src.length() == 0) {
return src;
}
while (src.length() > 0 && (src.charAt(0) == '\n' || src.charAt(0) == ' ')) {
src = src.subSequence(1, src.length());
}
while (src.length() > 0 && (src.charAt(src.length() - 1) == '\n' || src.charAt(src.length() - 1) == ' ')) {
src = src.subSequence(0, src.length() - 1);
}
return src;
}
public static void setListViewEdgeEffectColor(AbsListView listView, int color) {
if (Build.VERSION.SDK_INT >= 21) {
try {
@ -989,14 +994,9 @@ public class AndroidUtilities {
}
public static boolean needShowPasscode(boolean reset) {
boolean wasInBackground;
if (Build.VERSION.SDK_INT >= 14) {
wasInBackground = ForegroundDetector.getInstance().isWasInBackground(reset);
if (reset) {
ForegroundDetector.getInstance().resetBackgroundVar();
}
} else {
wasInBackground = UserConfig.lastPauseTime != 0;
boolean wasInBackground = ForegroundDetector.getInstance().isWasInBackground(reset);
if (reset) {
ForegroundDetector.getInstance().resetBackgroundVar();
}
return UserConfig.passcodeHash.length() > 0 && wasInBackground &&
(UserConfig.appLocked || UserConfig.autoLockIn != 0 && UserConfig.lastPauseTime != 0 && !UserConfig.appLocked && (UserConfig.lastPauseTime + UserConfig.autoLockIn) <= ConnectionsManager.getInstance().getCurrentTime());
@ -1004,24 +1004,21 @@ public class AndroidUtilities {
public static void shakeView(final View view, final float x, final int num) {
if (num == 6) {
ViewProxy.setTranslationX(view, 0);
view.clearAnimation();
view.setTranslationX(0);
return;
}
AnimatorSetProxy animatorSetProxy = new AnimatorSetProxy();
animatorSetProxy.playTogether(ObjectAnimatorProxy.ofFloat(view, "translationX", AndroidUtilities.dp(x)));
animatorSetProxy.setDuration(50);
animatorSetProxy.addListener(new AnimatorListenerAdapterProxy() {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(ObjectAnimator.ofFloat(view, "translationX", AndroidUtilities.dp(x)));
animatorSet.setDuration(50);
animatorSet.addListener(new AnimatorListenerAdapterProxy() {
@Override
public void onAnimationEnd(Object animation) {
public void onAnimationEnd(Animator animation) {
shakeView(view, num == 5 ? 0 : -x, num + 1);
}
});
animatorSetProxy.start();
animatorSet.start();
}
/*public static String ellipsize(String text, int maxLines, int maxWidth, TextPaint paint) {
if (text == null || paint == null) {
return null;
@ -1058,7 +1055,7 @@ public class AndroidUtilities {
}*/
/*public static void turnOffHardwareAcceleration(Window window) {
if (window == null || Build.MODEL == null || Build.VERSION.SDK_INT < 11) {
if (window == null || Build.MODEL == null) {
return;
}
if (Build.MODEL.contains("GT-S5301") ||
@ -1094,14 +1091,9 @@ public class AndroidUtilities {
public static void addToClipboard(CharSequence str) {
try {
if (Build.VERSION.SDK_INT < 11) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(str);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("label", str);
clipboard.setPrimaryClip(clip);
}
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("label", str);
clipboard.setPrimaryClip(clip);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
@ -1389,4 +1381,50 @@ public class AndroidUtilities {
System.arraycopy(sha1, 0, key_hash, 0, 16);
return key_hash;
}
public static void openForView(MessageObject message, Activity activity) throws Exception {
File f = null;
String fileName = message.getFileName();
if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) {
f = new File(message.messageOwner.attachPath);
}
if (f == null || !f.exists()) {
f = FileLoader.getPathToMessage(message.messageOwner);
}
if (f != null && f.exists()) {
String realMimeType = null;
Intent intent = new Intent(Intent.ACTION_VIEW);
MimeTypeMap myMime = MimeTypeMap.getSingleton();
int idx = fileName.lastIndexOf('.');
if (idx != -1) {
String ext = fileName.substring(idx + 1);
realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase());
if (realMimeType == null) {
if (message.type == 9 || message.type == 0) {
realMimeType = message.getDocument().mime_type;
}
if (realMimeType == null || realMimeType.length() == 0) {
realMimeType = null;
}
}
if (realMimeType != null) {
intent.setDataAndType(Uri.fromFile(f), realMimeType);
} else {
intent.setDataAndType(Uri.fromFile(f), "text/plain");
}
} else {
intent.setDataAndType(Uri.fromFile(f), "text/plain");
}
if (realMimeType != null) {
try {
activity.startActivityForResult(intent, 500);
} catch (Exception e) {
intent.setDataAndType(Uri.fromFile(f), "text/plain");
activity.startActivityForResult(intent, 500);
}
} else {
activity.startActivityForResult(intent, 500);
}
}
}
}

View File

@ -1,191 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.animation.Interpolator;
import java.util.ArrayList;
public abstract class Animator10 implements Cloneable {
ArrayList<AnimatorListener> mListeners = null;
ArrayList<AnimatorPauseListener> mPauseListeners = null;
boolean mPaused = false;
public abstract long getStartDelay();
public abstract void setStartDelay(long startDelay);
public abstract Animator10 setDuration(long duration);
public abstract long getDuration();
public abstract void setInterpolator(Interpolator value);
public abstract boolean isRunning();
public void start() {
}
public void cancel() {
}
public void end() {
}
@SuppressWarnings("unchecked")
public void pause() {
if (isStarted() && !mPaused) {
mPaused = true;
if (mPauseListeners != null) {
ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorPauseListener tmpListener : tmpListeners) {
tmpListener.onAnimationPause(this);
}
}
}
}
@SuppressWarnings("unchecked")
public void resume() {
if (mPaused) {
mPaused = false;
if (mPauseListeners != null) {
ArrayList<AnimatorPauseListener> tmpListeners = (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorPauseListener tmpListener : tmpListeners) {
tmpListener.onAnimationResume(this);
}
}
}
}
public boolean isPaused() {
return mPaused;
}
public boolean isStarted() {
return isRunning();
}
public Interpolator getInterpolator() {
return null;
}
public void addListener(AnimatorListener listener) {
if (mListeners == null) {
mListeners = new ArrayList<AnimatorListener>();
}
mListeners.add(listener);
}
public void removeListener(AnimatorListener listener) {
if (mListeners == null) {
return;
}
mListeners.remove(listener);
if (mListeners.size() == 0) {
mListeners = null;
}
}
public ArrayList<AnimatorListener> getListeners() {
return mListeners;
}
public void addPauseListener(AnimatorPauseListener listener) {
if (mPauseListeners == null) {
mPauseListeners = new ArrayList<AnimatorPauseListener>();
}
mPauseListeners.add(listener);
}
public void removePauseListener(AnimatorPauseListener listener) {
if (mPauseListeners == null) {
return;
}
mPauseListeners.remove(listener);
if (mPauseListeners.size() == 0) {
mPauseListeners = null;
}
}
public void removeAllListeners() {
if (mListeners != null) {
mListeners.clear();
mListeners = null;
}
if (mPauseListeners != null) {
mPauseListeners.clear();
mPauseListeners = null;
}
}
@Override
public Animator10 clone() {
try {
final Animator10 anim = (Animator10) super.clone();
if (mListeners != null) {
ArrayList<AnimatorListener> oldListeners = mListeners;
anim.mListeners = new ArrayList<AnimatorListener>();
int numListeners = oldListeners.size();
for (AnimatorListener oldListener : oldListeners) {
anim.mListeners.add(oldListener);
}
}
if (mPauseListeners != null) {
ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
int numListeners = oldListeners.size();
for (AnimatorPauseListener oldListener : oldListeners) {
anim.mPauseListeners.add(oldListener);
}
}
return anim;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public void setupStartValues() {
}
public void setupEndValues() {
}
public void setTarget(Object target) {
}
public interface AnimatorListener {
void onAnimationStart(Animator10 animation);
void onAnimationEnd(Animator10 animation);
void onAnimationCancel(Animator10 animation);
void onAnimationRepeat(Animator10 animation);
}
public interface AnimatorPauseListener {
void onAnimationPause(Animator10 animation);
void onAnimationResume(Animator10 animation);
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
public abstract class AnimatorListenerAdapter10 implements Animator10.AnimatorListener, Animator10.AnimatorPauseListener {
@Override
public void onAnimationCancel(Animator10 animation) {
}
@Override
public void onAnimationEnd(Animator10 animation) {
}
@Override
public void onAnimationRepeat(Animator10 animation) {
}
@Override
public void onAnimationStart(Animator10 animation) {
}
@Override
public void onAnimationPause(Animator10 animation) {
}
@Override
public void onAnimationResume(Animator10 animation) {
}
}

View File

@ -1,705 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
public final class AnimatorSet10 extends Animator10 {
private ArrayList<Animator10> mPlayingSet = new ArrayList<>();
private HashMap<Animator10, Node> mNodeMap = new HashMap<>();
private ArrayList<Node> mNodes = new ArrayList<>();
private ArrayList<Node> mSortedNodes = new ArrayList<>();
private boolean mNeedsSort = true;
private AnimatorSetListener mSetListener = null;
boolean mTerminated = false;
private boolean mStarted = false;
private long mStartDelay = 0;
private ValueAnimator mDelayAnim = null;
private long mDuration = -1;
private Interpolator mInterpolator = null;
public void playTogether(Animator10... items) {
if (items != null) {
mNeedsSort = true;
Builder builder = play(items[0]);
for (int i = 1; i < items.length; ++i) {
builder.with(items[i]);
}
}
}
public void playTogether(Collection<Animator10> items) {
if (items != null && items.size() > 0) {
mNeedsSort = true;
Builder builder = null;
for (Animator10 anim : items) {
if (builder == null) {
builder = play(anim);
} else {
builder.with(anim);
}
}
}
}
public void playSequentially(Animator10... items) {
if (items != null) {
mNeedsSort = true;
if (items.length == 1) {
play(items[0]);
} else {
for (int i = 0; i < items.length - 1; ++i) {
play(items[i]).before(items[i+1]);
}
}
}
}
public void playSequentially(List<Animator10> items) {
if (items != null && items.size() > 0) {
mNeedsSort = true;
if (items.size() == 1) {
play(items.get(0));
} else {
for (int i = 0; i < items.size() - 1; ++i) {
play(items.get(i)).before(items.get(i+1));
}
}
}
}
public ArrayList<Animator10> getChildAnimations() {
ArrayList<Animator10> childList = new ArrayList<>();
for (Node node : mNodes) {
childList.add(node.animation);
}
return childList;
}
@Override
public void setTarget(Object target) {
for (Node node : mNodes) {
Animator10 animation = node.animation;
if (animation instanceof AnimatorSet10) {
animation.setTarget(target);
} else if (animation instanceof ObjectAnimator10) {
animation.setTarget(target);
}
}
}
@Override
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
@Override
public Interpolator getInterpolator() {
return mInterpolator;
}
public Builder play(Animator10 anim) {
if (anim != null) {
mNeedsSort = true;
return new Builder(anim);
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public void cancel() {
mTerminated = true;
if (isStarted()) {
ArrayList<AnimatorListener> tmpListeners = null;
if (mListeners != null) {
tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
listener.onAnimationCancel(this);
}
}
if (mDelayAnim != null && mDelayAnim.isRunning()) {
mDelayAnim.cancel();
} else if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.cancel();
}
}
if (tmpListeners != null) {
for (AnimatorListener listener : tmpListeners) {
listener.onAnimationEnd(this);
}
}
mStarted = false;
}
}
@SuppressWarnings("unchecked")
@Override
public void end() {
mTerminated = true;
if (isStarted()) {
if (mSortedNodes.size() != mNodes.size()) {
// hasn't been started yet - sort the nodes now, then end them
sortNodes();
for (Node node : mSortedNodes) {
if (mSetListener == null) {
mSetListener = new AnimatorSetListener(this);
}
node.animation.addListener(mSetListener);
}
}
if (mDelayAnim != null) {
mDelayAnim.cancel();
}
if (mSortedNodes.size() > 0) {
for (Node node : mSortedNodes) {
node.animation.end();
}
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
listener.onAnimationEnd(this);
}
}
mStarted = false;
}
}
@Override
public boolean isRunning() {
for (Node node : mNodes) {
if (node.animation.isRunning()) {
return true;
}
}
return false;
}
@Override
public boolean isStarted() {
return mStarted;
}
@Override
public long getStartDelay() {
return mStartDelay;
}
@Override
public void setStartDelay(long startDelay) {
mStartDelay = startDelay;
}
@Override
public long getDuration() {
return mDuration;
}
@Override
public AnimatorSet10 setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("duration must be a value of zero or greater");
}
mDuration = duration;
return this;
}
@Override
public void setupStartValues() {
for (Node node : mNodes) {
node.animation.setupStartValues();
}
}
@Override
public void setupEndValues() {
for (Node node : mNodes) {
node.animation.setupEndValues();
}
}
@Override
public void pause() {
boolean previouslyPaused = mPaused;
super.pause();
if (!previouslyPaused && mPaused) {
if (mDelayAnim != null) {
mDelayAnim.pause();
} else {
for (Node node : mNodes) {
node.animation.pause();
}
}
}
}
@Override
public void resume() {
boolean previouslyPaused = mPaused;
super.resume();
if (previouslyPaused && !mPaused) {
if (mDelayAnim != null) {
mDelayAnim.resume();
} else {
for (Node node : mNodes) {
node.animation.resume();
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public void start() {
mTerminated = false;
mStarted = true;
mPaused = false;
if (mDuration >= 0) {
for (Node node : mNodes) {
node.animation.setDuration(mDuration);
}
}
if (mInterpolator != null) {
for (Node node : mNodes) {
node.animation.setInterpolator(mInterpolator);
}
}
sortNodes();
int numSortedNodes = mSortedNodes.size();
for (Node node : mSortedNodes) {
ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
if (oldListeners != null && oldListeners.size() > 0) {
final ArrayList<AnimatorListener> clonedListeners = new
ArrayList<>(oldListeners);
for (AnimatorListener listener : clonedListeners) {
if (listener instanceof DependencyListener ||
listener instanceof AnimatorSetListener) {
node.animation.removeListener(listener);
}
}
}
}
final ArrayList<Node> nodesToStart = new ArrayList<>();
for (Node node : mSortedNodes) {
if (mSetListener == null) {
mSetListener = new AnimatorSetListener(this);
}
if (node.dependencies == null || node.dependencies.size() == 0) {
nodesToStart.add(node);
} else {
int numDependencies = node.dependencies.size();
for (int j = 0; j < numDependencies; ++j) {
Dependency dependency = node.dependencies.get(j);
dependency.node.animation.addListener(
new DependencyListener(this, node, dependency.rule));
}
node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
}
node.animation.addListener(mSetListener);
}
if (mStartDelay <= 0) {
for (Node node : nodesToStart) {
node.animation.start();
mPlayingSet.add(node.animation);
}
} else {
mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
mDelayAnim.setDuration(mStartDelay);
mDelayAnim.addListener(new AnimatorListenerAdapter10() {
boolean canceled = false;
public void onAnimationCancel(Animator10 anim) {
canceled = true;
}
public void onAnimationEnd(Animator10 anim) {
if (!canceled) {
int numNodes = nodesToStart.size();
for (Node node : nodesToStart) {
node.animation.start();
mPlayingSet.add(node.animation);
}
}
mDelayAnim = null;
}
});
mDelayAnim.start();
}
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorListener tmpListener : tmpListeners) {
tmpListener.onAnimationStart(this);
}
}
if (mNodes.size() == 0 && mStartDelay == 0) {
mStarted = false;
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorListener tmpListener : tmpListeners) {
tmpListener.onAnimationEnd(this);
}
}
}
}
@Override
public AnimatorSet10 clone() {
final AnimatorSet10 anim = (AnimatorSet10) super.clone();
anim.mNeedsSort = true;
anim.mTerminated = false;
anim.mStarted = false;
anim.mPlayingSet = new ArrayList<>();
anim.mNodeMap = new HashMap<>();
anim.mNodes = new ArrayList<>();
anim.mSortedNodes = new ArrayList<>();
HashMap<Node, Node> nodeCloneMap = new HashMap<>();
for (Node node : mNodes) {
Node nodeClone = node.clone();
nodeCloneMap.put(node, nodeClone);
anim.mNodes.add(nodeClone);
anim.mNodeMap.put(nodeClone.animation, nodeClone);
nodeClone.dependencies = null;
nodeClone.tmpDependencies = null;
nodeClone.nodeDependents = null;
nodeClone.nodeDependencies = null;
ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
if (cloneListeners != null) {
ArrayList<AnimatorListener> listenersToRemove = null;
for (AnimatorListener listener : cloneListeners) {
if (listener instanceof AnimatorSetListener) {
if (listenersToRemove == null) {
listenersToRemove = new ArrayList<>();
}
listenersToRemove.add(listener);
}
}
if (listenersToRemove != null) {
for (AnimatorListener listener : listenersToRemove) {
cloneListeners.remove(listener);
}
}
}
}
for (Node node : mNodes) {
Node nodeClone = nodeCloneMap.get(node);
if (node.dependencies != null) {
for (Dependency dependency : node.dependencies) {
Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
Dependency cloneDependency = new Dependency(clonedDependencyNode, dependency.rule);
nodeClone.addDependency(cloneDependency);
}
}
}
return anim;
}
private static class DependencyListener implements AnimatorListener {
private AnimatorSet10 mAnimatorSet;
private Node mNode;
private int mRule;
public DependencyListener(AnimatorSet10 animatorSet, Node node, int rule) {
this.mAnimatorSet = animatorSet;
this.mNode = node;
this.mRule = rule;
}
public void onAnimationCancel(Animator10 animation) {
}
public void onAnimationEnd(Animator10 animation) {
if (mRule == Dependency.AFTER) {
startIfReady(animation);
}
}
public void onAnimationRepeat(Animator10 animation) {
}
public void onAnimationStart(Animator10 animation) {
if (mRule == Dependency.WITH) {
startIfReady(animation);
}
}
private void startIfReady(Animator10 dependencyAnimation) {
if (mAnimatorSet.mTerminated) {
return;
}
Dependency dependencyToRemove = null;
int numDependencies = mNode.tmpDependencies.size();
for (int i = 0; i < numDependencies; ++i) {
Dependency dependency = mNode.tmpDependencies.get(i);
if (dependency.rule == mRule && dependency.node.animation == dependencyAnimation) {
dependencyToRemove = dependency;
dependencyAnimation.removeListener(this);
break;
}
}
mNode.tmpDependencies.remove(dependencyToRemove);
if (mNode.tmpDependencies.size() == 0) {
mNode.animation.start();
mAnimatorSet.mPlayingSet.add(mNode.animation);
}
}
}
private class AnimatorSetListener implements AnimatorListener {
private AnimatorSet10 mAnimatorSet;
AnimatorSetListener(AnimatorSet10 animatorSet) {
mAnimatorSet = animatorSet;
}
public void onAnimationCancel(Animator10 animation) {
if (!mTerminated) {
if (mPlayingSet.size() == 0) {
if (mListeners != null) {
int numListeners = mListeners.size();
for (AnimatorListener mListener : mListeners) {
mListener.onAnimationCancel(mAnimatorSet);
}
}
}
}
}
@SuppressWarnings("unchecked")
public void onAnimationEnd(Animator10 animation) {
animation.removeListener(this);
mPlayingSet.remove(animation);
Node animNode = mAnimatorSet.mNodeMap.get(animation);
animNode.done = true;
if (!mTerminated) {
ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes;
boolean allDone = true;
int numSortedNodes = sortedNodes.size();
for (Node sortedNode : sortedNodes) {
if (!sortedNode.done) {
allDone = false;
break;
}
}
if (allDone) {
if (mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorListener tmpListener : tmpListeners) {
tmpListener.onAnimationEnd(mAnimatorSet);
}
}
mAnimatorSet.mStarted = false;
mAnimatorSet.mPaused = false;
}
}
}
public void onAnimationRepeat(Animator10 animation) {
}
public void onAnimationStart(Animator10 animation) {
}
}
private void sortNodes() {
if (mNeedsSort) {
mSortedNodes.clear();
ArrayList<Node> roots = new ArrayList<>();
int numNodes = mNodes.size();
for (Node node : mNodes) {
if (node.dependencies == null || node.dependencies.size() == 0) {
roots.add(node);
}
}
ArrayList<Node> tmpRoots = new ArrayList<>();
while (roots.size() > 0) {
int numRoots = roots.size();
for (Node root : roots) {
mSortedNodes.add(root);
if (root.nodeDependents != null) {
int numDependents = root.nodeDependents.size();
for (int j = 0; j < numDependents; ++j) {
Node node = root.nodeDependents.get(j);
node.nodeDependencies.remove(root);
if (node.nodeDependencies.size() == 0) {
tmpRoots.add(node);
}
}
}
}
roots.clear();
roots.addAll(tmpRoots);
tmpRoots.clear();
}
mNeedsSort = false;
if (mSortedNodes.size() != mNodes.size()) {
throw new IllegalStateException("Circular dependencies cannot exist in AnimatorSet");
}
} else {
int numNodes = mNodes.size();
for (Node node : mNodes) {
if (node.dependencies != null && node.dependencies.size() > 0) {
int numDependencies = node.dependencies.size();
for (int j = 0; j < numDependencies; ++j) {
Dependency dependency = node.dependencies.get(j);
if (node.nodeDependencies == null) {
node.nodeDependencies = new ArrayList<>();
}
if (!node.nodeDependencies.contains(dependency.node)) {
node.nodeDependencies.add(dependency.node);
}
}
}
node.done = false;
}
}
}
private static class Dependency {
static final int WITH = 0;
static final int AFTER = 1;
public Node node;
public int rule;
public Dependency(Node node, int rule) {
this.node = node;
this.rule = rule;
}
}
private static class Node implements Cloneable {
public Animator10 animation;
public ArrayList<Dependency> dependencies = null;
public ArrayList<Dependency> tmpDependencies = null;
public ArrayList<Node> nodeDependencies = null;
public ArrayList<Node> nodeDependents = null;
public boolean done = false;
public Node(Animator10 animation) {
this.animation = animation;
}
public void addDependency(Dependency dependency) {
if (dependencies == null) {
dependencies = new ArrayList<>();
nodeDependencies = new ArrayList<>();
}
dependencies.add(dependency);
if (!nodeDependencies.contains(dependency.node)) {
nodeDependencies.add(dependency.node);
}
Node dependencyNode = dependency.node;
if (dependencyNode.nodeDependents == null) {
dependencyNode.nodeDependents = new ArrayList<>();
}
dependencyNode.nodeDependents.add(this);
}
@Override
public Node clone() {
try {
Node node = (Node) super.clone();
node.animation = animation.clone();
return node;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class Builder {
private Node mCurrentNode;
Builder(Animator10 anim) {
mCurrentNode = mNodeMap.get(anim);
if (mCurrentNode == null) {
mCurrentNode = new Node(anim);
mNodeMap.put(anim, mCurrentNode);
mNodes.add(mCurrentNode);
}
}
public Builder with(Animator10 anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
node.addDependency(dependency);
return this;
}
public Builder before(Animator10 anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
node.addDependency(dependency);
return this;
}
public Builder after(Animator10 anim) {
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
Dependency dependency = new Dependency(node, Dependency.AFTER);
mCurrentNode.addDependency(dependency);
return this;
}
public Builder after(long delay) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(delay);
after(anim);
return this;
}
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.animation.Interpolator;
import org.telegram.messenger.Animation.Keyframe.FloatKeyframe;
import java.util.ArrayList;
class FloatKeyframeSet extends KeyframeSet {
private float firstValue;
private float lastValue;
private float deltaValue;
private boolean firstTime = true;
public FloatKeyframeSet(FloatKeyframe... keyframes) {
super(keyframes);
}
@Override
public Object getValue(float fraction) {
return getFloatValue(fraction);
}
@Override
public FloatKeyframeSet clone() {
ArrayList<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
}
return new FloatKeyframeSet(newKeyframes);
}
@SuppressWarnings("unchecked")
public float getFloatValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
firstTime = false;
firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + fraction * deltaValue;
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
}
}
if (fraction <= 0f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
} else if (fraction >= 1f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
}
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).floatValue();
}
prevKeyframe = nextKeyframe;
}
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.animation.Interpolator;
import org.telegram.messenger.Animation.Keyframe.IntKeyframe;
import java.util.ArrayList;
class IntKeyframeSet extends KeyframeSet {
private int firstValue;
private int lastValue;
private int deltaValue;
private boolean firstTime = true;
public IntKeyframeSet(IntKeyframe... keyframes) {
super(keyframes);
}
@Override
public Object getValue(float fraction) {
return getIntValue(fraction);
}
@Override
public IntKeyframeSet clone() {
ArrayList<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone();
}
return new IntKeyframeSet(newKeyframes);
}
@SuppressWarnings("unchecked")
public int getIntValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
firstTime = false;
firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue();
lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + (int)(fraction * deltaValue);
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
}
}
if (fraction <= 0f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
} else if (fraction >= 1f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
}
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
return mEvaluator == null ? prevValue + (int)(intervalFraction * (nextValue - prevValue)) : ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
}
prevKeyframe = nextKeyframe;
}
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
}

View File

@ -1,187 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.animation.Interpolator;
public abstract class Keyframe implements Cloneable {
float mFraction;
Class mValueType;
private Interpolator mInterpolator = null;
boolean mHasValue = false;
public static Keyframe ofInt(float fraction, int value) {
return new IntKeyframe(fraction, value);
}
public static Keyframe ofInt(float fraction) {
return new IntKeyframe(fraction);
}
public static Keyframe ofFloat(float fraction, float value) {
return new FloatKeyframe(fraction, value);
}
public static Keyframe ofFloat(float fraction) {
return new FloatKeyframe(fraction);
}
public static Keyframe ofObject(float fraction, Object value) {
return new ObjectKeyframe(fraction, value);
}
public static Keyframe ofObject(float fraction) {
return new ObjectKeyframe(fraction, null);
}
public boolean hasValue() {
return mHasValue;
}
public abstract Object getValue();
public abstract void setValue(Object value);
public float getFraction() {
return mFraction;
}
public void setFraction(float fraction) {
mFraction = fraction;
}
public Interpolator getInterpolator() {
return mInterpolator;
}
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
public Class getType() {
return mValueType;
}
@Override
public abstract Keyframe clone();
static class ObjectKeyframe extends Keyframe {
Object mValue;
ObjectKeyframe(float fraction, Object value) {
mFraction = fraction;
mValue = value;
mHasValue = (value != null);
mValueType = mHasValue ? value.getClass() : Object.class;
}
public Object getValue() {
return mValue;
}
public void setValue(Object value) {
mValue = value;
mHasValue = (value != null);
}
@Override
public ObjectKeyframe clone() {
ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mHasValue ? mValue : null);
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
}
static class IntKeyframe extends Keyframe {
int mValue;
IntKeyframe(float fraction, int value) {
mFraction = fraction;
mValue = value;
mValueType = int.class;
mHasValue = true;
}
IntKeyframe(float fraction) {
mFraction = fraction;
mValueType = int.class;
}
public int getIntValue() {
return mValue;
}
public Object getValue() {
return mValue;
}
public void setValue(Object value) {
if (value != null && value.getClass() == Integer.class) {
mValue = (Integer) value;
mHasValue = true;
}
}
@Override
public IntKeyframe clone() {
IntKeyframe kfClone = mHasValue ? new IntKeyframe(getFraction(), mValue) : new IntKeyframe(getFraction());
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
}
static class FloatKeyframe extends Keyframe {
float mValue;
FloatKeyframe(float fraction, float value) {
mFraction = fraction;
mValue = value;
mValueType = float.class;
mHasValue = true;
}
FloatKeyframe(float fraction) {
mFraction = fraction;
mValueType = float.class;
}
public float getFloatValue() {
return mValue;
}
public Object getValue() {
return mValue;
}
public void setValue(Object value) {
if (value != null && value.getClass() == Float.class) {
mValue = (Float) value;
mHasValue = true;
}
}
@Override
public FloatKeyframe clone() {
FloatKeyframe kfClone = mHasValue ? new FloatKeyframe(getFraction(), mValue) : new FloatKeyframe(getFraction());
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
}
}

View File

@ -1,200 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import java.util.ArrayList;
import java.util.Arrays;
import android.util.Log;
import android.view.animation.Interpolator;
import org.telegram.messenger.Animation.Keyframe.IntKeyframe;
import org.telegram.messenger.Animation.Keyframe.FloatKeyframe;
import org.telegram.messenger.Animation.Keyframe.ObjectKeyframe;
class KeyframeSet {
int mNumKeyframes;
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
Interpolator mInterpolator;
ArrayList<Keyframe> mKeyframes;
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
mKeyframes = new ArrayList<Keyframe>();
mKeyframes.addAll(Arrays.asList(keyframes));
mFirstKeyframe = mKeyframes.get(0);
mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
mInterpolator = mLastKeyframe.getInterpolator();
}
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
int numKeyframes = keyframes.length;
boolean hasFloat = false;
boolean hasInt = false;
boolean hasOther = false;
for (Keyframe keyframe : keyframes) {
if (keyframe instanceof FloatKeyframe) {
hasFloat = true;
} else if (keyframe instanceof IntKeyframe) {
hasInt = true;
} else {
hasOther = true;
}
}
if (hasFloat && !hasInt && !hasOther) {
FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
floatKeyframes[i] = (FloatKeyframe) keyframes[i];
}
return new FloatKeyframeSet(floatKeyframes);
} else if (hasInt && !hasFloat && !hasOther) {
IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
intKeyframes[i] = (IntKeyframe) keyframes[i];
}
return new IntKeyframeSet(intKeyframes);
} else {
return new KeyframeSet(keyframes);
}
}
public static KeyframeSet ofObject(Object... values) {
int numKeyframes = values.length;
ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
} else {
keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
}
}
return new KeyframeSet(keyframes);
}
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
}
@Override
public KeyframeSet clone() {
ArrayList<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
Keyframe[] newKeyframes = new Keyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = keyframes.get(i).clone();
}
return new KeyframeSet(newKeyframes);
}
@SuppressWarnings("unchecked")
public Object getValue(float fraction) {
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(), mLastKeyframe.getValue());
}
if (fraction <= 0f) {
final Keyframe nextKeyframe = mKeyframes.get(1);
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = mFirstKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(), nextKeyframe.getValue());
} else if (fraction >= 1f) {
final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
final Interpolator interpolator = mLastKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) / (mLastKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), mLastKeyframe.getValue());
}
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final Interpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) / (nextKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(), nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
return mLastKeyframe.getValue();
}
@Override
public String toString() {
String returnVal = " ";
for (int i = 0; i < mNumKeyframes; ++i) {
returnVal += mKeyframes.get(i).getValue() + " ";
}
return returnVal;
}
}

View File

@ -1,488 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.view.View;
import java.util.HashMap;
public final class ObjectAnimator10 extends ValueAnimator {
private static final HashMap<String, Property> PROXY_PROPERTIES = new HashMap<String, Property>();
static {
Property<View, Float> ALPHA = new FloatProperty10<View>("alpha") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setAlpha(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getAlpha();
}
};
Property<View, Float> PIVOT_X = new FloatProperty10<View>("pivotX") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setPivotX(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getPivotX();
}
};
Property<View, Float> PIVOT_Y = new FloatProperty10<View>("pivotY") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setPivotY(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getPivotY();
}
};
Property<View, Float> TRANSLATION_X = new FloatProperty10<View>("translationX") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setTranslationX(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getTranslationX();
}
};
Property<View, Float> TRANSLATION_Y = new FloatProperty10<View>("translationY") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setTranslationY(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getTranslationY();
}
};
Property<View, Float> ROTATION = new FloatProperty10<View>("rotation") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setRotation(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getRotation();
}
};
Property<View, Float> ROTATION_X = new FloatProperty10<View>("rotationX") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setRotationX(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getRotationX();
}
};
Property<View, Float> ROTATION_Y = new FloatProperty10<View>("rotationY") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setRotationY(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getRotationY();
}
};
Property<View, Float> SCALE_X = new FloatProperty10<View>("scaleX") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setScaleX(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getScaleX();
}
};
Property<View, Float> SCALE_Y = new FloatProperty10<View>("scaleY") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setScaleY(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getScaleY();
}
};
Property<View, Integer> SCROLL_X = new IntProperty<View>("scrollX") {
@Override
public void setValue(View object, int value) {
View10.wrap(object).setScrollX(value);
}
@Override
public Integer get(View object) {
return View10.wrap(object).getScrollX();
}
};
Property<View, Integer> SCROLL_Y = new IntProperty<View>("scrollY") {
@Override
public void setValue(View object, int value) {
View10.wrap(object).setScrollY(value);
}
@Override
public Integer get(View object) {
return View10.wrap(object).getScrollY();
}
};
Property<View, Float> X = new FloatProperty10<View>("x") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setX(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getX();
}
};
Property<View, Float> Y = new FloatProperty10<View>("y") {
@Override
public void setValue(View object, float value) {
View10.wrap(object).setY(value);
}
@Override
public Float get(View object) {
return View10.wrap(object).getY();
}
};
PROXY_PROPERTIES.put("alpha", ALPHA);
PROXY_PROPERTIES.put("pivotX", PIVOT_X);
PROXY_PROPERTIES.put("pivotY", PIVOT_Y);
PROXY_PROPERTIES.put("translationX", TRANSLATION_X);
PROXY_PROPERTIES.put("translationY", TRANSLATION_Y);
PROXY_PROPERTIES.put("rotation", ROTATION);
PROXY_PROPERTIES.put("rotationX", ROTATION_X);
PROXY_PROPERTIES.put("rotationY", ROTATION_Y);
PROXY_PROPERTIES.put("scaleX", SCALE_X);
PROXY_PROPERTIES.put("scaleY", SCALE_Y);
PROXY_PROPERTIES.put("scrollX", SCROLL_X);
PROXY_PROPERTIES.put("scrollY", SCROLL_Y);
PROXY_PROPERTIES.put("x", X);
PROXY_PROPERTIES.put("y", Y);
}
private Object mTarget;
private String mPropertyName;
private Property mProperty;
private boolean mAutoCancel = false;
public void setPropertyName(String propertyName) {
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setPropertyName(propertyName);
mValuesMap.remove(oldName);
mValuesMap.put(propertyName, valuesHolder);
}
mPropertyName = propertyName;
mInitialized = false;
}
public void setProperty(Property property) {
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setProperty(property);
mValuesMap.remove(oldName);
mValuesMap.put(mPropertyName, valuesHolder);
}
if (mProperty != null) {
mPropertyName = property.getName();
}
mProperty = property;
mInitialized = false;
}
public String getPropertyName() {
String propertyName = null;
if (mPropertyName != null) {
propertyName = mPropertyName;
} else if (mProperty != null) {
propertyName = mProperty.getName();
} else if (mValues != null && mValues.length > 0) {
for (int i = 0; i < mValues.length; ++i) {
if (i == 0) {
propertyName = "";
} else {
propertyName += ",";
}
propertyName += mValues[i].getPropertyName();
}
}
return propertyName;
}
public ObjectAnimator10() {
}
private ObjectAnimator10(Object target, String propertyName) {
mTarget = target;
setPropertyName(propertyName);
}
private <T> ObjectAnimator10(T target, Property<T, ?> property) {
mTarget = target;
setProperty(property);
}
public static ObjectAnimator10 ofInt(Object target, String propertyName, int... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
anim.setIntValues(values);
return anim;
}
public static <T> ObjectAnimator10 ofInt(T target, Property<T, Integer> property, int... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
anim.setIntValues(values);
return anim;
}
public static ObjectAnimator10 ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
anim.setFloatValues(values);
return anim;
}
public static <T> ObjectAnimator10 ofFloat(T target, Property<T, Float> property, float... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
anim.setFloatValues(values);
return anim;
}
public static ObjectAnimator10 ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, propertyName);
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}
public static <T, V> ObjectAnimator10 ofObject(T target, Property<T, V> property, TypeEvaluator<V> evaluator, V... values) {
ObjectAnimator10 anim = new ObjectAnimator10(target, property);
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}
public static ObjectAnimator10 ofPropertyValuesHolder(Object target, PropertyValuesHolder... values) {
ObjectAnimator10 anim = new ObjectAnimator10();
anim.mTarget = target;
anim.setValues(values);
return anim;
}
@SuppressWarnings("unchecked")
@Override
public void setIntValues(int... values) {
if (mValues == null || mValues.length == 0) {
if (mProperty != null) {
setValues(PropertyValuesHolder.ofInt(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
}
} else {
super.setIntValues(values);
}
}
@SuppressWarnings("unchecked")
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
@Override
public void setObjectValues(Object... values) {
if (mValues == null || mValues.length == 0) {
if (mProperty != null) {
setValues(PropertyValuesHolder.ofObject(mProperty, null, values));
} else {
setValues(PropertyValuesHolder.ofObject(mPropertyName, null, values));
}
} else {
super.setObjectValues(values);
}
}
public void setAutoCancel(boolean cancel) {
mAutoCancel = cancel;
}
private boolean hasSameTargetAndProperties(Animator10 anim) {
if (anim instanceof ObjectAnimator10) {
PropertyValuesHolder[] theirValues = ((ObjectAnimator10) anim).getValues();
if (((ObjectAnimator10) anim).getTarget() == mTarget &&
mValues.length == theirValues.length) {
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvhMine = mValues[i];
PropertyValuesHolder pvhTheirs = theirValues[i];
if (pvhMine.getPropertyName() == null ||
!pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
return false;
}
}
return true;
}
}
return false;
}
@Override
public void start() {
AnimationHandler handler = sAnimationHandler.get();
if (handler != null) {
int numAnims = handler.mAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mAnimations.get(i) instanceof ObjectAnimator10) {
ObjectAnimator10 anim = (ObjectAnimator10) handler.mAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mPendingAnimations.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator10) {
ObjectAnimator10 anim = (ObjectAnimator10) handler.mPendingAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mDelayedAnims.size();
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator10) {
ObjectAnimator10 anim = (ObjectAnimator10) handler.mDelayedAnims.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
}
super.start();
}
@Override
void initAnimation() {
if (!mInitialized) {
if ((mProperty == null) && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
setProperty(PROXY_PROPERTIES.get(mPropertyName));
}
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.setupSetterAndGetter(mTarget);
}
super.initAnimation();
}
}
@Override
public ObjectAnimator10 setDuration(long duration) {
super.setDuration(duration);
return this;
}
public Object getTarget() {
return mTarget;
}
@Override
public void setTarget(Object target) {
if (mTarget != target) {
final Object oldTarget = mTarget;
mTarget = target;
if (oldTarget != null && target != null && oldTarget.getClass() == target.getClass()) {
return;
}
mInitialized = false;
}
}
@Override
public void setupStartValues() {
initAnimation();
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.setupStartValue(mTarget);
}
}
@Override
public void setupEndValues() {
initAnimation();
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.setupEndValue(mTarget);
}
}
@Override
void animateValue(float fraction) {
super.animateValue(fraction);
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.setAnimatedValue(mTarget);
}
}
@Override
public ObjectAnimator10 clone() {
return (ObjectAnimator10) super.clone();
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright (C) 2011 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.Animation;
public abstract class Property<T, V> {
private final String mName;
private final Class<V> mType;
public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
return new ReflectiveProperty<T, V>(hostType, valueType, name);
}
public Property(Class<V> type, String name) {
mName = name;
mType = type;
}
public boolean isReadOnly() {
return false;
}
public void set(T object, V value) {
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
}
public abstract V get(T object);
public String getName() {
return mName;
}
public Class<V> getType() {
return mType;
}
}

View File

@ -1,545 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class PropertyValuesHolder implements Cloneable {
String mPropertyName;
protected Property mProperty;
Method mSetter = null;
private Method mGetter = null;
Class mValueType;
KeyframeSet mKeyframeSet = null;
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, Double.class, Integer.class};
private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, Float.class, Double.class};
private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class, Float.class, Integer.class};
private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap = new HashMap<Class, HashMap<String, Method>>();
private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap = new HashMap<Class, HashMap<String, Method>>();
final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock();
final Object[] mTmpValueArray = new Object[1];
private TypeEvaluator mEvaluator;
private Object mAnimatedValue;
private PropertyValuesHolder(String propertyName) {
mPropertyName = propertyName;
}
private PropertyValuesHolder(Property property) {
mProperty = property;
if (property != null) {
mPropertyName = property.getName();
}
}
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
return new IntPropertyValuesHolder(property, values);
}
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
return new FloatPropertyValuesHolder(propertyName, values);
}
public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
return new FloatPropertyValuesHolder(property, values);
}
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
Object... values) {
PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
pvh.setObjectValues(values);
pvh.setEvaluator(evaluator);
return pvh;
}
public static <V> PropertyValuesHolder ofObject(Property property,
TypeEvaluator<V> evaluator, V... values) {
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
pvh.setObjectValues(values);
pvh.setEvaluator(evaluator);
return pvh;
}
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
if (keyframeSet instanceof IntKeyframeSet) {
return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet);
} else if (keyframeSet instanceof FloatKeyframeSet) {
return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet);
} else {
PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
pvh.mKeyframeSet = keyframeSet;
pvh.mValueType = values[0].getType();
return pvh;
}
}
public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
if (keyframeSet instanceof IntKeyframeSet) {
return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
} else if (keyframeSet instanceof FloatKeyframeSet) {
return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
} else {
PropertyValuesHolder pvh = new PropertyValuesHolder(property);
pvh.mKeyframeSet = keyframeSet;
pvh.mValueType = values[0].getType();
return pvh;
}
}
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframeSet = KeyframeSet.ofInt(values);
}
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframeSet = KeyframeSet.ofFloat(values);
}
public void setKeyframes(Keyframe... values) {
int numKeyframes = values.length;
Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
mValueType = values[0].getType();
System.arraycopy(values, 0, keyframes, 0, numKeyframes);
mKeyframeSet = new KeyframeSet(keyframes);
}
public void setObjectValues(Object... values) {
mValueType = values[0].getClass();
mKeyframeSet = KeyframeSet.ofObject(values);
}
@SuppressWarnings("unchecked")
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
Method returnVal = null;
String methodName = getMethodName(prefix, mPropertyName);
Class args[] = null;
if (valueType == null) {
try {
returnVal = targetClass.getMethod(methodName);
} catch (Throwable e) {
try {
returnVal = targetClass.getDeclaredMethod(methodName);
returnVal.setAccessible(true);
} catch (Throwable e2) {
e2.printStackTrace();
}
}
} else {
args = new Class[1];
Class typeVariants[];
if (mValueType.equals(Float.class)) {
typeVariants = FLOAT_VARIANTS;
} else if (mValueType.equals(Integer.class)) {
typeVariants = INTEGER_VARIANTS;
} else if (mValueType.equals(Double.class)) {
typeVariants = DOUBLE_VARIANTS;
} else {
typeVariants = new Class[1];
typeVariants[0] = mValueType;
}
for (Class typeVariant : typeVariants) {
args[0] = typeVariant;
try {
returnVal = targetClass.getMethod(methodName, args);
mValueType = typeVariant;
return returnVal;
} catch (Throwable e) {
try {
returnVal = targetClass.getDeclaredMethod(methodName, args);
returnVal.setAccessible(true);
mValueType = typeVariant;
return returnVal;
} catch (Throwable e2) {
// Swallow the error and keep trying other variants
}
}
}
}
return returnVal;
}
private Method setupSetterOrGetter(Class targetClass, HashMap<Class, HashMap<String, Method>> propertyMapMap, String prefix, Class valueType) {
Method setterOrGetter = null;
try {
mPropertyMapLock.writeLock().lock();
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
if (propertyMap != null) {
setterOrGetter = propertyMap.get(mPropertyName);
}
if (setterOrGetter == null) {
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null) {
propertyMap = new HashMap<String, Method>();
propertyMapMap.put(targetClass, propertyMap);
}
propertyMap.put(mPropertyName, setterOrGetter);
}
} finally {
mPropertyMapLock.writeLock().unlock();
}
return setterOrGetter;
}
void setupSetter(Class targetClass) {
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
}
private void setupGetter(Class targetClass) {
mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
}
@SuppressWarnings("unchecked")
void setupSetterAndGetter(Object target) {
if (mProperty != null) {
try {
Object testValue = mProperty.get(target);
for (Keyframe kf : mKeyframeSet.mKeyframes) {
if (!kf.hasValue()) {
kf.setValue(mProperty.get(target));
}
}
return;
} catch (Throwable e) {
mProperty = null;
}
}
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
}
for (Keyframe kf : mKeyframeSet.mKeyframes) {
if (!kf.hasValue()) {
if (mGetter == null) {
setupGetter(targetClass);
if (mGetter == null) {
return;
}
}
try {
kf.setValue(mGetter.invoke(target));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
@SuppressWarnings("unchecked")
private void setupValue(Object target, Keyframe kf) {
if (mProperty != null) {
kf.setValue(mProperty.get(target));
}
try {
if (mGetter == null) {
Class targetClass = target.getClass();
setupGetter(targetClass);
if (mGetter == null) {
return;
}
}
kf.setValue(mGetter.invoke(target));
} catch (Throwable e) {
e.printStackTrace();
}
}
void setupStartValue(Object target) {
setupValue(target, mKeyframeSet.mKeyframes.get(0));
}
void setupEndValue(Object target) {
setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
}
@Override
public PropertyValuesHolder clone() {
try {
PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
newPVH.mPropertyName = mPropertyName;
newPVH.mProperty = mProperty;
newPVH.mKeyframeSet = mKeyframeSet.clone();
newPVH.mEvaluator = mEvaluator;
return newPVH;
} catch (CloneNotSupportedException e) {
return null;
}
}
@SuppressWarnings("unchecked")
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
void init() {
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null;
}
if (mEvaluator != null) {
mKeyframeSet.setEvaluator(mEvaluator);
}
}
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
mKeyframeSet.setEvaluator(evaluator);
}
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
public void setPropertyName(String propertyName) {
mPropertyName = propertyName;
}
public void setProperty(Property property) {
mProperty = property;
}
public String getPropertyName() {
return mPropertyName;
}
Object getAnimatedValue() {
return mAnimatedValue;
}
@Override
public String toString() {
return mPropertyName + ": " + mKeyframeSet.toString();
}
static String getMethodName(String prefix, String propertyName) {
if (propertyName == null || propertyName.length() == 0) {
return prefix;
}
char firstLetter = Character.toUpperCase(propertyName.charAt(0));
String theRest = propertyName.substring(1);
return prefix + firstLetter + theRest;
}
static class IntPropertyValuesHolder extends PropertyValuesHolder {
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>();
private IntProperty mIntProperty;
IntKeyframeSet mIntKeyframeSet;
int mIntAnimatedValue;
public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) {
super(propertyName);
mValueType = int.class;
mKeyframeSet = keyframeSet;
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
}
public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
super(property);
mValueType = int.class;
mKeyframeSet = keyframeSet;
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
if (property instanceof IntProperty) {
mIntProperty = (IntProperty) mProperty;
}
}
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
}
public IntPropertyValuesHolder(Property property, int... values) {
super(property);
setIntValues(values);
if (property instanceof IntProperty) {
mIntProperty = (IntProperty) mProperty;
}
}
@Override
public void setIntValues(int... values) {
super.setIntValues(values);
mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
}
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction);
}
@Override
Object getAnimatedValue() {
return mIntAnimatedValue;
}
@Override
public IntPropertyValuesHolder clone() {
IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet;
return newPVH;
}
@SuppressWarnings("unchecked")
@Override
void setAnimatedValue(Object target) {
if (mIntProperty != null) {
mIntProperty.setValue(target, mIntAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mIntAnimatedValue);
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mIntAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
void setupSetter(Class targetClass) {
if (mProperty != null) {
return;
}
super.setupSetter(targetClass);
}
}
static class FloatPropertyValuesHolder extends PropertyValuesHolder {
private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap = new HashMap<Class, HashMap<String, Integer>>();
private FloatProperty10 mFloatProperty;
FloatKeyframeSet mFloatKeyframeSet;
float mFloatAnimatedValue;
public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
super(propertyName);
mValueType = float.class;
mKeyframeSet = keyframeSet;
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
}
public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
super(property);
mValueType = float.class;
mKeyframeSet = keyframeSet;
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
if (property instanceof FloatProperty10) {
mFloatProperty = (FloatProperty10) mProperty;
}
}
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
public FloatPropertyValuesHolder(Property property, float... values) {
super(property);
setFloatValues(values);
if (property instanceof FloatProperty10) {
mFloatProperty = (FloatProperty10) mProperty;
}
}
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
}
@Override
void calculateValue(float fraction) {
mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
}
@Override
Object getAnimatedValue() {
return mFloatAnimatedValue;
}
@Override
public FloatPropertyValuesHolder clone() {
FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet;
return newPVH;
}
@SuppressWarnings("unchecked")
@Override
void setAnimatedValue(Object target) {
if (mFloatProperty != null) {
mFloatProperty.setValue(target, mFloatAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mFloatAnimatedValue);
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
void setupSetter(Class targetClass) {
if (mProperty != null) {
return;
}
super.setupSetter(targetClass);
}
}
}

View File

@ -1,182 +0,0 @@
/*
* Copyright (C) 2011 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.Animation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Internal class to automatically generate a Property for a given class/name pair, given the
* specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
*/
class ReflectiveProperty<T, V> extends Property<T, V> {
private static final String PREFIX_GET = "get";
private static final String PREFIX_IS = "is";
private static final String PREFIX_SET = "set";
private Method mSetter;
private Method mGetter;
private Field mField;
/**
* For given property name 'name', look for getName/isName method or 'name' field.
* Also look for setName method (optional - could be readonly). Failing method getters and
* field results in throwing NoSuchPropertyException.
*
* @param propertyHolder The class on which the methods or field are found
* @param name The name of the property, where this name is capitalized and appended to
* "get" and "is to search for the appropriate methods. If the get/is methods are not found,
* the constructor will search for a field with that exact name.
*/
public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
// TODO: cache reflection info for each new class/name pair
super(valueType, name);
char firstLetter = Character.toUpperCase(name.charAt(0));
String theRest = name.substring(1);
String capitalizedName = firstLetter + theRest;
String getterName = PREFIX_GET + capitalizedName;
try {
mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
} catch (NoSuchMethodException e) {
try {
/* The native implementation uses JNI to do reflection, which allows access to private methods.
* getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
*/
mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
mGetter.setAccessible(true);
} catch (NoSuchMethodException e2) {
// getName() not available - try isName() instead
getterName = PREFIX_IS + capitalizedName;
try {
mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
} catch (NoSuchMethodException e3) {
try {
/* The native implementation uses JNI to do reflection, which allows access to private methods.
* getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
*/
mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
mGetter.setAccessible(true);
} catch (NoSuchMethodException e4) {
// Try public field instead
try {
mField = propertyHolder.getField(name);
Class fieldType = mField.getType();
if (!typesMatch(valueType, fieldType)) {
throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
"does not match Property type (" + valueType + ")");
}
return;
} catch (NoSuchFieldException e5) {
// no way to access property - throw appropriate exception
throw new NoSuchPropertyException("No accessor method or field found for"
+ " property with name " + name);
}
}
}
}
}
Class getterType = mGetter.getReturnType();
// Check to make sure our getter type matches our valueType
if (!typesMatch(valueType, getterType)) {
throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
"does not match Property type (" + valueType + ")");
}
String setterName = PREFIX_SET + capitalizedName;
try {
// mSetter = propertyHolder.getMethod(setterName, getterType);
// The native implementation uses JNI to do reflection, which allows access to private methods.
mSetter = propertyHolder.getDeclaredMethod(setterName, getterType);
mSetter.setAccessible(true);
} catch (NoSuchMethodException ignored) {
// Okay to not have a setter - just a readonly property
}
}
/**
* Utility method to check whether the type of the underlying field/method on the target
* object matches the type of the Property. The extra checks for primitive types are because
* generics will force the Property type to be a class, whereas the type of the underlying
* method/field will probably be a primitive type instead. Accept float as matching Float,
* etc.
*/
private boolean typesMatch(Class<V> valueType, Class getterType) {
if (getterType != valueType) {
if (getterType.isPrimitive()) {
return (getterType == float.class && valueType == Float.class) ||
(getterType == int.class && valueType == Integer.class) ||
(getterType == boolean.class && valueType == Boolean.class) ||
(getterType == long.class && valueType == Long.class) ||
(getterType == double.class && valueType == Double.class) ||
(getterType == short.class && valueType == Short.class) ||
(getterType == byte.class && valueType == Byte.class) ||
(getterType == char.class && valueType == Character.class);
}
return false;
}
return true;
}
@Override
public void set(T object, V value) {
if (mSetter != null) {
try {
mSetter.invoke(object, value);
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
} else if (mField != null) {
try {
mField.set(object, value);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
} else {
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
}
}
@Override
public V get(T object) {
if (mGetter != null) {
try {
return (V) mGetter.invoke(object, (Object[])null);
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
} else if (mField != null) {
try {
return (V) mField.get(object);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
// Should not get here: there should always be a non-null getter or field
throw new AssertionError();
}
/**
* Returns false if there is no setter or public field underlying this Property.
*/
@Override
public boolean isReadOnly() {
return (mSetter == null && mField == null);
}
}

View File

@ -1,675 +0,0 @@
/*
* Copyright (C) 2010 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.Animation;
import android.os.Looper;
import android.util.AndroidRuntimeException;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import org.telegram.messenger.AndroidUtilities;
import java.util.ArrayList;
import java.util.HashMap;
public class ValueAnimator extends Animator10 {
private static float sDurationScale = 1.0f;
static final int STOPPED = 0;
static final int RUNNING = 1;
static final int SEEKED = 2;
long mStartTime;
long mSeekTime = -1;
private long mPauseTime;
private boolean mResumed = false;
protected static ThreadLocal<AnimationHandler> sAnimationHandler = new ThreadLocal<AnimationHandler>();
private static final Interpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator();
private boolean mPlayingBackwards = false;
private int mCurrentIteration = 0;
private float mCurrentFraction = 0f;
private boolean mStartedDelay = false;
private long mDelayStartTime;
int mPlayingState = STOPPED;
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mStartListenersCalled = false;
boolean mInitialized = false;
private long mDuration = (long)(300 * sDurationScale);
private long mUnscaledDuration = 300;
private long mStartDelay = 0;
private long mUnscaledStartDelay = 0;
private int mRepeatCount = 0;
private int mRepeatMode = RESTART;
private Interpolator mInterpolator = sDefaultInterpolator;
private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
PropertyValuesHolder[] mValues;
HashMap<String, PropertyValuesHolder> mValuesMap;
public static final int RESTART = 1;
public static final int REVERSE = 2;
public static final int INFINITE = -1;
public static void setDurationScale(float durationScale) {
sDurationScale = durationScale;
}
public static float getDurationScale() {
return sDurationScale;
}
public ValueAnimator() {
}
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
public static ValueAnimator ofFloat(float... values) {
ValueAnimator anim = new ValueAnimator();
anim.setFloatValues(values);
return anim;
}
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
ValueAnimator anim = new ValueAnimator();
anim.setValues(values);
return anim;
}
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
mInitialized = false;
}
public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
mInitialized = false;
}
public void setObjectValues(Object... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofObject("", null, values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setObjectValues(values);
}
mInitialized = false;
}
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (PropertyValuesHolder valuesHolder : values) {
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
mInitialized = false;
}
public PropertyValuesHolder[] getValues() {
return mValues;
}
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.init();
}
mInitialized = true;
}
}
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " + duration);
}
mUnscaledDuration = duration;
mDuration = (long)(duration * sDurationScale);
return this;
}
public long getDuration() {
return mUnscaledDuration;
}
public void setCurrentPlayTime(long playTime) {
initAnimation();
long currentTime = AnimationUtils.currentAnimationTimeMillis();
if (mPlayingState != RUNNING) {
mSeekTime = playTime;
mPlayingState = SEEKED;
}
mStartTime = currentTime - playTime;
doAnimationFrame(currentTime);
}
public long getCurrentPlayTime() {
if (!mInitialized || mPlayingState == STOPPED) {
return 0;
}
return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
}
@SuppressWarnings("unchecked")
protected static class AnimationHandler implements Runnable {
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
private boolean mAnimationScheduled;
public void start() {
scheduleAnimation();
}
private void doAnimationFrame(long frameTime) {
while (mPendingAnimations.size() > 0) {
ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) mPendingAnimations.clone();
mPendingAnimations.clear();
int count = pendingCopy.size();
for (ValueAnimator anim : pendingCopy) {
if (anim.mStartDelay == 0) {
anim.startAnimation(this);
} else {
mDelayedAnims.add(anim);
}
}
}
int numDelayedAnims = mDelayedAnims.size();
for (ValueAnimator anim : mDelayedAnims) {
if (anim.delayedAnimationFrame(frameTime)) {
mReadyAnims.add(anim);
}
}
int numReadyAnims = mReadyAnims.size();
if (numReadyAnims > 0) {
for (ValueAnimator anim : mReadyAnims) {
anim.startAnimation(this);
anim.mRunning = true;
mDelayedAnims.remove(anim);
}
mReadyAnims.clear();
}
int numAnims = mAnimations.size();
for (ValueAnimator mAnimation : mAnimations) {
mTmpAnimations.add(mAnimation);
}
for (int i = 0; i < numAnims; ++i) {
ValueAnimator anim = mTmpAnimations.get(i);
if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
mEndingAnims.add(anim);
}
}
mTmpAnimations.clear();
if (mEndingAnims.size() > 0) {
for (ValueAnimator mEndingAnim : mEndingAnims) {
mEndingAnim.endAnimation(this);
}
mEndingAnims.clear();
}
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
scheduleAnimation();
}
}
@Override
public void run() {
mAnimationScheduled = false;
doAnimationFrame(System.nanoTime() / 1000000);
}
private void scheduleAnimation() {
if (!mAnimationScheduled) {
AndroidUtilities.runOnUIThread(this);
mAnimationScheduled = true;
}
}
}
public long getStartDelay() {
return mUnscaledStartDelay;
}
public void setStartDelay(long startDelay) {
this.mStartDelay = (long)(startDelay * sDurationScale);
mUnscaledStartDelay = startDelay;
}
public Object getAnimatedValue() {
if (mValues != null && mValues.length > 0) {
return mValues[0].getAnimatedValue();
}
return null;
}
public Object getAnimatedValue(String propertyName) {
PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
if (valuesHolder != null) {
return valuesHolder.getAnimatedValue();
} else {
return null;
}
}
public void setRepeatCount(int value) {
mRepeatCount = value;
}
public int getRepeatCount() {
return mRepeatCount;
}
public void setRepeatMode(int value) {
mRepeatMode = value;
}
public int getRepeatMode() {
return mRepeatMode;
}
public void addUpdateListener(AnimatorUpdateListener listener) {
if (mUpdateListeners == null) {
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
}
mUpdateListeners.add(listener);
}
public void removeAllUpdateListeners() {
if (mUpdateListeners == null) {
return;
}
mUpdateListeners.clear();
mUpdateListeners = null;
}
public void removeUpdateListener(AnimatorUpdateListener listener) {
if (mUpdateListeners == null) {
return;
}
mUpdateListeners.remove(listener);
if (mUpdateListeners.size() == 0) {
mUpdateListeners = null;
}
}
@Override
public void setInterpolator(Interpolator value) {
if (value != null) {
mInterpolator = value;
} else {
mInterpolator = new LinearInterpolator();
}
}
@Override
public Interpolator getInterpolator() {
return mInterpolator;
}
public void setEvaluator(TypeEvaluator value) {
if (value != null && mValues != null && mValues.length > 0) {
mValues[0].setEvaluator(value);
}
}
@SuppressWarnings("unchecked")
private void notifyStartListeners() {
if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorListener tmpListener : tmpListeners) {
tmpListener.onAnimationStart(this);
}
}
mStartListenersCalled = true;
}
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mPlayingBackwards = playBackwards;
mCurrentIteration = 0;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
mPaused = false;
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
setCurrentPlayTime(0);
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
animationHandler.start();
}
@Override
public void start() {
start(false);
}
@SuppressWarnings("unchecked")
@Override
public void cancel() {
AnimationHandler handler = getOrCreateAnimationHandler();
if (mPlayingState != STOPPED || handler.mPendingAnimations.contains(this) || handler.mDelayedAnims.contains(this)) {
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
notifyStartListeners();
}
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
listener.onAnimationCancel(this);
}
}
endAnimation(handler);
}
}
@Override
public void end() {
AnimationHandler handler = getOrCreateAnimationHandler();
if (!handler.mAnimations.contains(this) && !handler.mPendingAnimations.contains(this)) {
mStartedDelay = false;
startAnimation(handler);
mStarted = true;
} else if (!mInitialized) {
initAnimation();
}
animateValue(mPlayingBackwards ? 0f : 1f);
endAnimation(handler);
}
@Override
public void resume() {
if (mPaused) {
mResumed = true;
}
super.resume();
}
@Override
public void pause() {
boolean previouslyPaused = mPaused;
super.pause();
if (!previouslyPaused && mPaused) {
mPauseTime = -1;
mResumed = false;
}
}
@Override
public boolean isRunning() {
return (mPlayingState == RUNNING || mRunning);
}
@Override
public boolean isStarted() {
return mStarted;
}
public void reverse() {
mPlayingBackwards = !mPlayingBackwards;
if (mPlayingState == RUNNING) {
long currentTime = AnimationUtils.currentAnimationTimeMillis();
long currentPlayTime = currentTime - mStartTime;
long timeLeft = mDuration - currentPlayTime;
mStartTime = currentTime - timeLeft;
} else if (mStarted) {
end();
} else {
start(true);
}
}
@SuppressWarnings("unchecked")
private void endAnimation(AnimationHandler handler) {
handler.mAnimations.remove(this);
handler.mPendingAnimations.remove(this);
handler.mDelayedAnims.remove(this);
mPlayingState = STOPPED;
mPaused = false;
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
notifyStartListeners();
}
ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (AnimatorListener tmpListener : tmpListeners) {
tmpListener.onAnimationEnd(this);
}
}
mRunning = false;
mStarted = false;
mStartListenersCalled = false;
mPlayingBackwards = false;
}
private void startAnimation(AnimationHandler handler) {
initAnimation();
handler.mAnimations.add(this);
if (mStartDelay > 0 && mListeners != null) {
notifyStartListeners();
}
}
private boolean delayedAnimationFrame(long currentTime) {
if (!mStartedDelay) {
mStartedDelay = true;
mDelayStartTime = currentTime;
} else {
if (mPaused) {
if (mPauseTime < 0) {
mPauseTime = currentTime;
}
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mDelayStartTime += (currentTime - mPauseTime);
}
}
long deltaTime = currentTime - mDelayStartTime;
if (deltaTime > mStartDelay) {
mStartTime = currentTime - (deltaTime - mStartDelay);
mPlayingState = RUNNING;
return true;
}
}
return false;
}
boolean animationFrame(long currentTime) {
boolean done = false;
switch (mPlayingState) {
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
if (mListeners != null) {
int numListeners = mListeners.size();
for (AnimatorListener mListener : mListeners) {
mListener.onAnimationRepeat(this);
}
}
if (mRepeatMode == REVERSE) {
mPlayingBackwards = !mPlayingBackwards;
}
mCurrentIteration += (int)fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
} else {
done = true;
fraction = Math.min(fraction, 1.0f);
}
}
if (mPlayingBackwards) {
fraction = 1f - fraction;
}
animateValue(fraction);
break;
}
return done;
}
final boolean doAnimationFrame(long frameTime) {
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = frameTime;
} else {
mStartTime = frameTime - mSeekTime;
mSeekTime = -1;
}
}
if (mPaused) {
if (mPauseTime < 0) {
mPauseTime = frameTime;
}
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mStartTime += (frameTime - mPauseTime);
}
}
final long currentTime = Math.max(frameTime, mStartTime);
return animationFrame(currentTime);
}
public float getAnimatedFraction() {
return mCurrentFraction;
}
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (PropertyValuesHolder mValue : mValues) {
mValue.calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (AnimatorUpdateListener mUpdateListener : mUpdateListeners) {
mUpdateListener.onAnimationUpdate(this);
}
}
}
@Override
public ValueAnimator clone() {
final ValueAnimator anim = (ValueAnimator) super.clone();
if (mUpdateListeners != null) {
ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
int numListeners = oldListeners.size();
for (AnimatorUpdateListener oldListener : oldListeners) {
anim.mUpdateListeners.add(oldListener);
}
}
anim.mSeekTime = -1;
anim.mPlayingBackwards = false;
anim.mCurrentIteration = 0;
anim.mInitialized = false;
anim.mPlayingState = STOPPED;
anim.mStartedDelay = false;
PropertyValuesHolder[] oldValues = mValues;
if (oldValues != null) {
int numValues = oldValues.length;
anim.mValues = new PropertyValuesHolder[numValues];
anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder newValuesHolder = oldValues[i].clone();
anim.mValues[i] = newValuesHolder;
anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
}
}
return anim;
}
public interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
public static int getCurrentAnimationsCount() {
AnimationHandler handler = sAnimationHandler.get();
return handler != null ? handler.mAnimations.size() : 0;
}
public static void clearAllAnimations() {
AnimationHandler handler = sAnimationHandler.get();
if (handler != null) {
handler.mAnimations.clear();
handler.mPendingAnimations.clear();
handler.mDelayedAnims.clear();
}
}
private static AnimationHandler getOrCreateAnimationHandler() {
AnimationHandler handler = sAnimationHandler.get();
if (handler == null) {
handler = new AnimationHandler();
sAnimationHandler.set(handler);
}
return handler;
}
}

View File

@ -1,349 +0,0 @@
/*
Copyright 2012 Jake Wharton
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.Animation;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
public class View10 extends Animation {
public static boolean NEED_PROXY = Build.VERSION.SDK_INT < 11;
private static final WeakHashMap<View, View10> PROXIES = new WeakHashMap<>();
public static View10 wrap(View view) {
View10 proxy = PROXIES.get(view);
Animation animation = view.getAnimation();
if (proxy == null || proxy != animation && animation != null) {
proxy = new View10(view);
PROXIES.put(view, proxy);
} else if (animation == null) {
view.setAnimation(proxy);
}
return proxy;
}
private final WeakReference<View> mView;
private final Camera mCamera = new Camera();
private boolean mHasPivot;
private float mAlpha = 1;
private float mPivotX;
private float mPivotY;
private float mRotationX;
private float mRotationY;
private float mRotationZ;
private float mScaleX = 1;
private float mScaleY = 1;
private float mTranslationX;
private float mTranslationY;
private final RectF mBefore = new RectF();
private final RectF mAfter = new RectF();
private final Matrix mTempMatrix = new Matrix();
private View10(View view) {
setDuration(0);
setFillAfter(true);
view.setAnimation(this);
mView = new WeakReference<>(view);
}
public float getAlpha() {
return mAlpha;
}
public void setAlpha(float alpha) {
if (mAlpha != alpha) {
mAlpha = alpha;
View view = mView.get();
if (view != null) {
view.invalidate();
}
}
}
public float getPivotX() {
return mPivotX;
}
public void setPivotX(float pivotX) {
if (!mHasPivot || mPivotX != pivotX) {
prepareForUpdate();
mHasPivot = true;
mPivotX = pivotX;
invalidateAfterUpdate();
}
}
public float getPivotY() {
return mPivotY;
}
public void setPivotY(float pivotY) {
if (!mHasPivot || mPivotY != pivotY) {
prepareForUpdate();
mHasPivot = true;
mPivotY = pivotY;
invalidateAfterUpdate();
}
}
public float getRotation() {
return mRotationZ;
}
public void setRotation(float rotation) {
if (mRotationZ != rotation) {
prepareForUpdate();
mRotationZ = rotation;
invalidateAfterUpdate();
}
}
public float getRotationX() {
return mRotationX;
}
public void setRotationX(float rotationX) {
if (mRotationX != rotationX) {
prepareForUpdate();
mRotationX = rotationX;
invalidateAfterUpdate();
}
}
public float getRotationY() {
return mRotationY;
}
public void setRotationY(float rotationY) {
if (mRotationY != rotationY) {
prepareForUpdate();
mRotationY = rotationY;
invalidateAfterUpdate();
}
}
public float getScaleX() {
return mScaleX;
}
public void setScaleX(float scaleX) {
if (mScaleX != scaleX) {
prepareForUpdate();
mScaleX = scaleX;
invalidateAfterUpdate();
}
}
public float getScaleY() {
return mScaleY;
}
public void setScaleY(float scaleY) {
if (mScaleY != scaleY) {
prepareForUpdate();
mScaleY = scaleY;
invalidateAfterUpdate();
}
}
public int getScrollX() {
View view = mView.get();
if (view == null) {
return 0;
}
return view.getScrollX();
}
public void setScrollX(int value) {
View view = mView.get();
if (view != null) {
view.scrollTo(value, view.getScrollY());
}
}
public int getScrollY() {
View view = mView.get();
if (view == null) {
return 0;
}
return view.getScrollY();
}
public void setScrollY(int value) {
View view = mView.get();
if (view != null) {
view.scrollTo(view.getScrollX(), value);
}
}
public float getTranslationX() {
return mTranslationX;
}
public void setTranslationX(float translationX) {
if (mTranslationX != translationX) {
prepareForUpdate();
mTranslationX = translationX;
invalidateAfterUpdate();
}
}
public float getTranslationY() {
return mTranslationY;
}
public void setTranslationY(float translationY) {
if (mTranslationY != translationY) {
prepareForUpdate();
mTranslationY = translationY;
invalidateAfterUpdate();
}
}
public float getX() {
View view = mView.get();
if (view == null) {
return 0;
}
return view.getLeft() + mTranslationX;
}
public void setX(float x) {
View view = mView.get();
if (view != null) {
setTranslationX(x - view.getLeft());
}
}
public float getY() {
View view = mView.get();
if (view == null) {
return 0;
}
return view.getTop() + mTranslationY;
}
public void setY(float y) {
View view = mView.get();
if (view != null) {
setTranslationY(y - view.getTop());
}
}
private void prepareForUpdate() {
View view = mView.get();
if (view != null) {
computeRect(mBefore, view);
}
}
private void invalidateAfterUpdate() {
View view = mView.get();
if (view == null || view.getParent() == null) {
return;
}
final RectF after = mAfter;
computeRect(after, view);
after.union(mBefore);
((View) view.getParent()).invalidate(
(int) Math.floor(after.left),
(int) Math.floor(after.top),
(int) Math.ceil(after.right),
(int) Math.ceil(after.bottom));
}
private void computeRect(final RectF r, View view) {
final float w = view.getWidth();
final float h = view.getHeight();
r.set(0, 0, w, h);
final Matrix m = mTempMatrix;
m.reset();
transformMatrix(m, view);
mTempMatrix.mapRect(r);
r.offset(view.getLeft(), view.getTop());
if (r.right < r.left) {
final float f = r.right;
r.right = r.left;
r.left = f;
}
if (r.bottom < r.top) {
final float f = r.top;
r.top = r.bottom;
r.bottom = f;
}
}
private void transformMatrix(Matrix m, View view) {
final float w = view.getWidth();
final float h = view.getHeight();
final boolean hasPivot = mHasPivot;
final float pX = hasPivot ? mPivotX : w / 2f;
final float pY = hasPivot ? mPivotY : h / 2f;
final float rX = mRotationX;
final float rY = mRotationY;
final float rZ = mRotationZ;
if ((rX != 0) || (rY != 0) || (rZ != 0)) {
final Camera camera = mCamera;
camera.save();
camera.rotateX(rX);
camera.rotateY(rY);
camera.rotateZ(-rZ);
camera.getMatrix(m);
camera.restore();
m.preTranslate(-pX, -pY);
m.postTranslate(pX, pY);
}
final float sX = mScaleX;
final float sY = mScaleY;
if ((sX != 1.0f) || (sY != 1.0f)) {
m.postScale(sX, sY);
final float sPX = -(pX / w) * ((sX * w) - w);
final float sPY = -(pY / h) * ((sY * h) - h);
m.postTranslate(sPX, sPY);
}
m.postTranslate(mTranslationX, mTranslationY);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
View view = mView.get();
if (view != null) {
t.setAlpha(mAlpha);
transformMatrix(t.getMatrix(), view);
}
}
}

View File

@ -1,112 +0,0 @@
/*
* 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-2016.
*/
package org.telegram.messenger.AnimationCompat;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import org.telegram.messenger.Animation.Animator10;
import org.telegram.messenger.Animation.AnimatorListenerAdapter10;
import org.telegram.messenger.Animation.View10;
public class AnimatorListenerAdapterProxy {
protected Object animatorListenerAdapter;
public AnimatorListenerAdapterProxy() {
if (View10.NEED_PROXY) {
animatorListenerAdapter = new AnimatorListenerAdapter10() {
@Override
public void onAnimationCancel(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationRepeat(animation);
}
@Override
public void onAnimationStart(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationStart(animation);
}
@Override
public void onAnimationPause(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationPause(animation);
}
@Override
public void onAnimationResume(Animator10 animation) {
AnimatorListenerAdapterProxy.this.onAnimationResume(animation);
}
};
} else {
animatorListenerAdapter = new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationCancel(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationEnd(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationRepeat(animation);
}
@Override
public void onAnimationStart(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationStart(animation);
}
@Override
public void onAnimationPause(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationPause(animation);
}
@Override
public void onAnimationResume(Animator animation) {
AnimatorListenerAdapterProxy.this.onAnimationResume(animation);
}
};
}
}
public void onAnimationCancel(Object animation) {
}
public void onAnimationEnd(Object animation) {
}
public void onAnimationRepeat(Object animation) {
}
public void onAnimationStart(Object animation) {
}
public void onAnimationPause(Object animation) {
}
public void onAnimationResume(Object animation) {
}
}

View File

@ -1,137 +0,0 @@
/*
* 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-2016.
*/
package org.telegram.messenger.AnimationCompat;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.view.animation.Interpolator;
import org.telegram.messenger.Animation.Animator10;
import org.telegram.messenger.Animation.AnimatorListenerAdapter10;
import org.telegram.messenger.Animation.AnimatorSet10;
import org.telegram.messenger.Animation.View10;
import java.lang.reflect.Array;
import java.util.ArrayList;
public class AnimatorSetProxy {
private Object animatorSet;
public static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
return copyOfRange(original, 0, newLength, newType);
}
@SuppressWarnings("unchecked")
public static <T, U> T[] copyOfRange(U[] original, int start, int end, Class<? extends T[]> newType) {
if (start > end) {
throw new IllegalArgumentException();
}
int originalLength = original.length;
if (start < 0 || start > originalLength) {
throw new ArrayIndexOutOfBoundsException();
}
int resultLength = end - start;
int copyLength = Math.min(resultLength, originalLength - start);
T[] result = (T[]) Array.newInstance(newType.getComponentType(), resultLength);
System.arraycopy(original, start, result, 0, copyLength);
return result;
}
public AnimatorSetProxy() {
if (View10.NEED_PROXY) {
animatorSet = new AnimatorSet10();
} else {
animatorSet = new AnimatorSet();
}
}
@SuppressWarnings("unchecked")
public void playTogether(Object... items) {
if (View10.NEED_PROXY) {
Animator10[] animators = copyOf(items, items.length, Animator10[].class);
((AnimatorSet10) animatorSet).playTogether(animators);
} else {
Animator[] animators = copyOf(items, items.length, Animator[].class);
((AnimatorSet) animatorSet).playTogether(animators);
}
}
public void playTogether(ArrayList<Object> items) {
if (View10.NEED_PROXY) {
ArrayList<Animator10> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator10)obj);
}
((AnimatorSet10) animatorSet).playTogether(animators);
} else {
ArrayList<Animator> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator)obj);
}
((AnimatorSet) animatorSet).playTogether(animators);
}
}
public AnimatorSetProxy setDuration(long duration) {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).setDuration(duration);
} else {
((AnimatorSet) animatorSet).setDuration(duration);
}
return this;
}
public AnimatorSetProxy setStartDelay(long delay) {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).setStartDelay(delay);
} else {
((AnimatorSet) animatorSet).setStartDelay(delay);
}
return this;
}
public void start() {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).start();
} else {
((AnimatorSet) animatorSet).start();
}
}
public void cancel() {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).cancel();
} else {
((AnimatorSet) animatorSet).cancel();
}
}
public void addListener(AnimatorListenerAdapterProxy listener) {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).addListener((AnimatorListenerAdapter10) listener.animatorListenerAdapter);
} else {
((AnimatorSet) animatorSet).addListener((AnimatorListenerAdapter) listener.animatorListenerAdapter);
}
}
public void setInterpolator(Interpolator interpolator) {
if (View10.NEED_PROXY) {
((AnimatorSet10) animatorSet).setInterpolator(interpolator);
} else {
((AnimatorSet) animatorSet).setInterpolator(interpolator);
}
}
@Override
public boolean equals(Object o) {
return animatorSet == o;
}
}

View File

@ -1,130 +0,0 @@
/*
* 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-2016.
*/
package org.telegram.messenger.AnimationCompat;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.view.animation.Interpolator;
import org.telegram.messenger.Animation.AnimatorListenerAdapter10;
import org.telegram.messenger.Animation.ObjectAnimator10;
import org.telegram.messenger.Animation.View10;
public class ObjectAnimatorProxy {
private Object objectAnimator;
public ObjectAnimatorProxy(Object animator) {
objectAnimator = animator;
}
public static Object ofFloat(Object target, String propertyName, float... values) {
if (View10.NEED_PROXY) {
return ObjectAnimator10.ofFloat(target, propertyName, values);
} else {
return ObjectAnimator.ofFloat(target, propertyName, values);
}
}
public static Object ofInt(Object target, String propertyName, int... values) {
if (View10.NEED_PROXY) {
return ObjectAnimator10.ofInt(target, propertyName, values);
} else {
return ObjectAnimator.ofInt(target, propertyName, values);
}
}
public static ObjectAnimatorProxy ofFloatProxy(Object target, String propertyName, float... values) {
if (View10.NEED_PROXY) {
return new ObjectAnimatorProxy(ObjectAnimator10.ofFloat(target, propertyName, values));
} else {
return new ObjectAnimatorProxy(ObjectAnimator.ofFloat(target, propertyName, values));
}
}
public static ObjectAnimatorProxy ofIntProxy(Object target, String propertyName, int... values) {
if (View10.NEED_PROXY) {
return new ObjectAnimatorProxy(ObjectAnimator10.ofInt(target, propertyName, values));
} else {
return new ObjectAnimatorProxy(ObjectAnimator.ofInt(target, propertyName, values));
}
}
public ObjectAnimatorProxy setDuration(long duration) {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).setDuration(duration);
} else {
((ObjectAnimator) objectAnimator).setDuration(duration);
}
return this;
}
public void setInterpolator(Interpolator value) {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).setInterpolator(value);
} else {
((ObjectAnimator) objectAnimator).setInterpolator(value);
}
}
public ObjectAnimatorProxy start() {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).start();
} else {
((ObjectAnimator) objectAnimator).start();
}
return this;
}
public void setAutoCancel(boolean cancel) {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).setAutoCancel(cancel);
} else {
((ObjectAnimator) objectAnimator).setAutoCancel(cancel);
}
}
public boolean isRunning() {
if (View10.NEED_PROXY) {
return ((ObjectAnimator10) objectAnimator).isRunning();
} else {
return ((ObjectAnimator) objectAnimator).isRunning();
}
}
public void end() {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).end();
} else {
((ObjectAnimator) objectAnimator).end();
}
}
public void cancel() {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).cancel();
} else {
((ObjectAnimator) objectAnimator).cancel();
}
}
public ObjectAnimatorProxy addListener(AnimatorListenerAdapterProxy listener) {
if (View10.NEED_PROXY) {
((ObjectAnimator10) objectAnimator).addListener((AnimatorListenerAdapter10) listener.animatorListenerAdapter);
} else {
((ObjectAnimator) objectAnimator).addListener((AnimatorListenerAdapter) listener.animatorListenerAdapter);
}
return this;
}
@Override
public boolean equals(Object o) {
return objectAnimator == o;
}
}

View File

@ -1,248 +0,0 @@
/*
* 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-2016.
*/
package org.telegram.messenger.AnimationCompat;
import android.view.View;
import org.telegram.messenger.Animation.View10;
public class ViewProxy {
public static float getAlpha(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getAlpha();
} else {
return view.getAlpha();
}
}
public static void setAlpha(View view, float alpha) {
if (View10.NEED_PROXY) {
View10.wrap(view).setAlpha(alpha);
} else {
view.setAlpha(alpha);
}
}
public static float getPivotX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getPivotX();
} else {
return view.getPivotX();
}
}
public static void setPivotX(View view, float pivotX) {
if (View10.NEED_PROXY) {
View10.wrap(view).setPivotX(pivotX);
} else {
view.setPivotX(pivotX);
}
}
public static float getPivotY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getPivotY();
} else {
return view.getPivotY();
}
}
public static void setPivotY(View view, float pivotY) {
if (View10.NEED_PROXY) {
View10.wrap(view).setPivotY(pivotY);
} else {
view.setPivotY(pivotY);
}
}
public static float getRotation(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getRotation();
} else {
return view.getRotation();
}
}
public static void setRotation(View view, float rotation) {
if (View10.NEED_PROXY) {
View10.wrap(view).setRotation(rotation);
} else {
view.setRotation(rotation);
}
}
public static float getRotationX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getRotationX();
} else {
return view.getRotationX();
}
}
public void setRotationX(View view, float rotationX) {
if (View10.NEED_PROXY) {
View10.wrap(view).setRotationX(rotationX);
} else {
view.setRotationX(rotationX);
}
}
public static float getRotationY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getRotationY();
} else {
return view.getRotationY();
}
}
public void setRotationY(View view, float rotationY) {
if (View10.NEED_PROXY) {
View10.wrap(view).setRotationY(rotationY);
} else {
view.setRotationY(rotationY);
}
}
public static float getScaleX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getScaleX();
} else {
return view.getScaleX();
}
}
public static void setScaleX(View view, float scaleX) {
if (View10.NEED_PROXY) {
View10.wrap(view).setScaleX(scaleX);
} else {
view.setScaleX(scaleX);
}
}
public static float getScaleY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getScaleY();
} else {
return view.getScaleY();
}
}
public static void setScaleY(View view, float scaleY) {
if (View10.NEED_PROXY) {
View10.wrap(view).setScaleY(scaleY);
} else {
view.setScaleY(scaleY);
}
}
public static int getScrollX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getScrollX();
} else {
return view.getScrollX();
}
}
public static void setScrollX(View view, int value) {
if (View10.NEED_PROXY) {
View10.wrap(view).setScrollX(value);
} else {
view.setScrollX(value);
}
}
public static int getScrollY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getScrollY();
} else {
return view.getScrollY();
}
}
public static void setScrollY(View view, int value) {
if (View10.NEED_PROXY) {
View10.wrap(view).setScrollY(value);
} else {
view.setScrollY(value);
}
}
public static float getTranslationX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getTranslationX();
} else {
return view.getTranslationX();
}
}
public static void setTranslationX(View view, float translationX) {
if (View10.NEED_PROXY) {
View10.wrap(view).setTranslationX(translationX);
} else {
view.setTranslationX(translationX);
}
}
public static float getTranslationY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getTranslationY();
} else {
return view.getTranslationY();
}
}
public static void setTranslationY(View view, float translationY) {
if (View10.NEED_PROXY) {
View10.wrap(view).setTranslationY(translationY);
} else {
view.setTranslationY(translationY);
}
}
public static float getX(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getX();
} else {
return view.getX();
}
}
public static void setX(View view, float x) {
if (View10.NEED_PROXY) {
View10.wrap(view).setX(x);
} else {
view.setX(x);
}
}
public static float getY(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view).getY();
} else {
return view.getY();
}
}
public static void setY(View view, float y) {
if (View10.NEED_PROXY) {
View10.wrap(view).setY(y);
} else {
view.setY(y);
}
}
public static Object wrap(View view) {
if (View10.NEED_PROXY) {
return View10.wrap(view);
} else {
return view;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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-2016.
*/
package org.telegram.messenger;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
public class AnimatorListenerAdapterProxy extends AnimatorListenerAdapter {
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationPause(Animator animator) {
}
@Override
public void onAnimationResume(Animator animator) {
}
}

View File

@ -287,40 +287,40 @@ public class ApplicationLoader extends Application {
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT < 11) {
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
}
applicationContext = getApplicationContext();
NativeLoader.initNativeLibs(ApplicationLoader.applicationContext);
ConnectionsManager.native_setJava(Build.VERSION.SDK_INT == 14 || Build.VERSION.SDK_INT == 15);
if (Build.VERSION.SDK_INT >= 14) {
new ForegroundDetector(this);
}
new ForegroundDetector(this);
applicationHandler = new Handler(applicationContext.getMainLooper());
startPushService();
}
/*public static void sendRegIdToBackend(final String token) {
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
UserConfig.pushString = token;
UserConfig.registeredForPush = false;
UserConfig.saveConfig(false);
if (UserConfig.getClientUserId() != 0) {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
MessagesController.getInstance().registerForPush(token);
}
});
}
}
});
}*/
public static void startPushService() {
SharedPreferences preferences = applicationContext.getSharedPreferences("Notifications", MODE_PRIVATE);
if (preferences.getBoolean("pushService", true)) {
applicationContext.startService(new Intent(applicationContext, NotificationsService.class));
if (android.os.Build.VERSION.SDK_INT >= 19) {
// Calendar cal = Calendar.getInstance();
// PendingIntent pintent = PendingIntent.getService(applicationContext, 0, new Intent(applicationContext, NotificationsService.class), 0);
// AlarmManager alarm = (AlarmManager) applicationContext.getSystemService(Context.ALARM_SERVICE);
// alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 30000, pintent);
PendingIntent pintent = PendingIntent.getService(applicationContext, 0, new Intent(applicationContext, NotificationsService.class), 0);
AlarmManager alarm = (AlarmManager)applicationContext.getSystemService(Context.ALARM_SERVICE);
alarm.cancel(pintent);
}
} else {
stopPushService();
}
@ -357,8 +357,8 @@ public class ApplicationLoader extends Application {
}
//if (UserConfig.pushString == null || UserConfig.pushString.length() == 0) {
Intent intent = new Intent(applicationContext, GcmRegistrationIntentService.class);
startService(intent);
Intent intent = new Intent(applicationContext, GcmRegistrationIntentService.class);
startService(intent);
//} else {
// FileLog.d("tmessages", "GCM regId = " + UserConfig.pushString);
//}
@ -369,6 +369,33 @@ public class ApplicationLoader extends Application {
}, 1000);
}
/*private void initPlayServices() {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
if (checkPlayServices()) {
if (UserConfig.pushString != null && UserConfig.pushString.length() != 0) {
FileLog.d("tmessages", "GCM regId = " + UserConfig.pushString);
} else {
FileLog.d("tmessages", "GCM Registration not found.");
}
try {
if (!FirebaseApp.getApps(ApplicationLoader.applicationContext).isEmpty()) {
String token = FirebaseInstanceId.getInstance().getToken();
if (token != null) {
sendRegIdToBackend(token);
}
}
} catch (Throwable e) {
FileLog.e("tmessages", e);
}
} else {
FileLog.d("tmessages", "No valid Google Play Services APK found.");
}
}
}, 2000);
}*/
private boolean checkPlayServices() {
try {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

View File

@ -32,7 +32,7 @@ public class AutoMessageReplyReceiver extends BroadcastReceiver {
if (dialog_id == 0 || max_id == 0) {
return;
}
SendMessagesHelper.getInstance().sendMessage(text.toString(), dialog_id, null, null, true, false, null, null, null);
SendMessagesHelper.getInstance().sendMessage(text.toString(), dialog_id, null, null, true, null, null, null);
MessagesController.getInstance().markDialogAsRead(dialog_id, max_id, max_id, 0, true, false);
}
}

View File

@ -110,7 +110,7 @@ public class Bitmaps {
public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
Bitmap bitmap;
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 21) {
if (Build.VERSION.SDK_INT < 21) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPreferredConfig = config;
@ -212,9 +212,7 @@ public class Bitmaps {
}
}
bitmap.setDensity(source.getDensity());
if (Build.VERSION.SDK_INT >= 12) {
bitmap.setHasAlpha(source.hasAlpha());
}
bitmap.setHasAlpha(source.hasAlpha());
if (Build.VERSION.SDK_INT >= 19) {
bitmap.setPremultiplied(source.isPremultiplied());
}

View File

@ -10,8 +10,8 @@ package org.telegram.messenger;
public class BuildVars {
public static boolean DEBUG_VERSION = false;
public static int BUILD_VERSION = 803;
public static String BUILD_VERSION_STRING = "3.9";
public static int BUILD_VERSION = 821;
public static String BUILD_VERSION_STRING = "3.10";
public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id
public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id
public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here";

View File

@ -71,17 +71,17 @@ public class ContactsController {
}
private String[] projectionPhones = {
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE,
ContactsContract.CommonDataKinds.Phone.LABEL
};
private String[] projectionNames = {
ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
};
public HashMap<Integer, Contact> contactsBook = new HashMap<>();
@ -98,7 +98,10 @@ public class ContactsController {
public HashMap<String, TLRPC.TL_contact> contactsByPhone = new HashMap<>();
private int completedRequestsCount;
private static volatile ContactsController Instance = null;
public static ContactsController getInstance() {
ContactsController localInstance = Instance;
if (localInstance == null) {
@ -163,6 +166,12 @@ public class ContactsController {
deleteAccountTTL = 0;
loadingLastSeenInfo = 0;
loadingGroupInfo = 0;
Utilities.globalQueue.postRunnable(new Runnable() {
@Override
public void run() {
completedRequestsCount = 0;
}
});
privacyRules = null;
}
@ -170,13 +179,9 @@ public class ContactsController {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
inviteText = preferences.getString("invitetext", null);
int time = preferences.getInt("invitetexttime", 0);
if (!updatingInviteText && (inviteText == null || time + 86400 < (int)(System.currentTimeMillis() / 1000))) {
if (!updatingInviteText && (inviteText == null || time + 86400 < (int) (System.currentTimeMillis() / 1000))) {
updatingInviteText = true;
TLRPC.TL_help_getInviteText req = new TLRPC.TL_help_getInviteText();
req.lang_code = LocaleController.getLocaleStringIso639();
if (req.lang_code.length() == 0) {
req.lang_code = "en";
}
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
@ -275,12 +280,21 @@ public class ContactsController {
public void run() {
if (checkContactsInternal()) {
FileLog.e("tmessages", "detected contacts change");
ContactsController.getInstance().performSyncPhoneBook(ContactsController.getInstance().getContactsCopy(ContactsController.getInstance().contactsBook), true, false, true);
ContactsController.getInstance().performSyncPhoneBook(ContactsController.getInstance().getContactsCopy(ContactsController.getInstance().contactsBook), true, false, true, false);
}
}
});
}
public void forceImportContacts() {
Utilities.globalQueue.postRunnable(new Runnable() {
@Override
public void run() {
ContactsController.getInstance().performSyncPhoneBook(new HashMap<Integer, Contact>(), true, true, true, true);
}
});
}
private boolean checkContactsInternal() {
boolean reload = false;
try {
@ -444,7 +458,7 @@ public class ContactsController {
}
try {
pCur = cr.query(ContactsContract.RawContacts.CONTENT_URI, new String[] { "display_name", ContactsContract.RawContacts.SYNC1, ContactsContract.RawContacts.CONTACT_ID }, ContactsContract.RawContacts.ACCOUNT_TYPE + " = " + "'com.whatsapp'", null, null);
pCur = cr.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{"display_name", ContactsContract.RawContacts.SYNC1, ContactsContract.RawContacts.CONTACT_ID}, ContactsContract.RawContacts.ACCOUNT_TYPE + " = " + "'com.whatsapp'", null, null);
if (pCur != null) {
while ((pCur.moveToNext())) {
String phone = pCur.getString(1);
@ -466,8 +480,8 @@ public class ContactsController {
}
String name = pCur.getString(0);
if (name == null || name.length() == 0) {
name = PhoneFormat.getInstance().format(phone);
if (TextUtils.isEmpty(name)) {
continue;
}
Contact contact = new Contact();
@ -528,7 +542,7 @@ public class ContactsController {
return ret;
}
public void performSyncPhoneBook(final HashMap<Integer, Contact> contactHashMap, final boolean requ, final boolean first, final boolean schedule) {
protected void performSyncPhoneBook(final HashMap<Integer, Contact> contactHashMap, final boolean request, final boolean first, final boolean schedule, final boolean force) {
if (!first && !contactsBookLoaded) {
return;
}
@ -560,21 +574,11 @@ public class ContactsController {
}
}*/
boolean request = requ;
if (request && first) {
if (UserConfig.importHash != null && UserConfig.importHash.length() != 0 || UserConfig.contactsVersion != 1) {
UserConfig.importHash = "";
UserConfig.contactsVersion = 1;
UserConfig.saveConfig(false);
request = false;
}
}
HashMap<String, Contact> contactShortHashMap = new HashMap<>();
for (HashMap.Entry<Integer, Contact> entry : contactHashMap.entrySet()) {
Contact c = entry.getValue();
for (String sphone : c.shortPhones) {
contactShortHashMap.put(sphone, c);
for (int a = 0; a < c.shortPhones.size(); a++) {
contactShortHashMap.put(c.shortPhones.get(a), c);
}
}
@ -593,8 +597,8 @@ public class ContactsController {
Contact value = pair.getValue();
Contact existing = contactHashMap.get(id);
if (existing == null) {
for (String s : value.shortPhones) {
Contact c = contactShortHashMap.get(s);
for (int a = 0; a < value.shortPhones.size(); a++) {
Contact c = contactShortHashMap.get(value.shortPhones.get(a));
if (c != null) {
existing = c;
id = existing.id;
@ -603,7 +607,7 @@ public class ContactsController {
}
}
boolean nameChanged = existing != null && (value.first_name != null && value.first_name.length() != 0 && !existing.first_name.equals(value.first_name) || value.last_name != null && existing.last_name != null && !existing.last_name.equals(value.last_name));
boolean nameChanged = existing != null && (TextUtils.isEmpty(value.first_name) && !existing.first_name.equals(value.first_name) || !TextUtils.isEmpty(value.last_name) && !existing.last_name.equals(value.last_name));
if (existing == null || nameChanged) {
for (int a = 0; a < value.phones.size(); a++) {
String sphone = value.shortPhones.get(a);
@ -625,6 +629,7 @@ public class ContactsController {
TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact();
imp.client_id = value.id;
imp.client_id |= ((long) a) << 32;
imp.first_name = value.first_name;
imp.last_name = value.last_name;
imp.phone = value.phones.get(a);
@ -644,13 +649,14 @@ public class ContactsController {
TLRPC.TL_contact contact = contactsByPhone.get(sphone);
if (contact != null) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
if (user == null || user.first_name != null && user.first_name.length() != 0 || user.last_name != null && user.last_name.length() != 0) {
if (user == null || !TextUtils.isEmpty(user.first_name) || !TextUtils.isEmpty(user.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) {
continue;
}
}
TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact();
imp.client_id = value.id;
imp.client_id |= ((long) a) << 32;
imp.first_name = value.first_name;
imp.last_name = value.last_name;
imp.phone = value.phones.get(a);
@ -697,7 +703,8 @@ public class ContactsController {
try {
final HashMap<String, TLRPC.User> contactsPhonesShort = new HashMap<>();
for (TLRPC.TL_contact value : contacts) {
for (int a = 0; a < contacts.size(); a++) {
TLRPC.TL_contact value = contacts.get(a);
TLRPC.User user = MessagesController.getInstance().getUser(value.user_id);
if (user == null || user.phone == null || user.phone.length() == 0) {
continue;
@ -739,16 +746,19 @@ public class ContactsController {
Contact value = pair.getValue();
int id = pair.getKey();
for (int a = 0; a < value.phones.size(); a++) {
String phone = value.shortPhones.get(a);
TLRPC.TL_contact contact = contactsByPhone.get(phone);
if (contact != null) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
if (user == null || user.first_name != null && user.first_name.length() != 0 || user.last_name != null && user.last_name.length() != 0) {
continue;
if (!force) {
String phone = value.shortPhones.get(a);
TLRPC.TL_contact contact = contactsByPhone.get(phone);
if (contact != null) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
if (user == null || !TextUtils.isEmpty(user.first_name) || !TextUtils.isEmpty(user.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) {
continue;
}
}
}
TLRPC.TL_inputPhoneContact imp = new TLRPC.TL_inputPhoneContact();
imp.client_id = id;
imp.client_id |= ((long) a) << 32;
imp.first_name = value.first_name;
imp.last_name = value.last_name;
imp.phone = value.phones.get(a);
@ -767,23 +777,34 @@ public class ContactsController {
FileLog.e("tmessages", "add contact " + contact.first_name + " " + contact.last_name + " " + contact.phone);
}
}*/
final int count = (int)Math.ceil(toImport.size() / 500.0f);
final HashMap<Integer, Contact> contactsMapToSave = new HashMap<>(contactsMap);
completedRequestsCount = 0;
final int count = (int) Math.ceil(toImport.size() / 500.0f);
for (int a = 0; a < count; a++) {
ArrayList<TLRPC.TL_inputPhoneContact> finalToImport = new ArrayList<>();
finalToImport.addAll(toImport.subList(a * 500, Math.min((a + 1) * 500, toImport.size())));
TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts();
req.contacts = finalToImport;
req.replace = false;
final boolean isLastQuery = a == count - 1;
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
completedRequestsCount++;
if (error == null) {
FileLog.e("tmessages", "contacts imported");
if (isLastQuery && !contactsMap.isEmpty()) {
MessagesStorage.getInstance().putCachedPhoneBook(contactsMap);
TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response;
if (!res.retry_contacts.isEmpty()) {
for (int a = 0; a < res.retry_contacts.size(); a++) {
long id = res.retry_contacts.get(a);
contactsMapToSave.remove((int) id);
}
}
TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts)response;
if (completedRequestsCount == count && !contactsMapToSave.isEmpty()) {
MessagesStorage.getInstance().putCachedPhoneBook(contactsMapToSave);
}
/*if (BuildVars.DEBUG_VERSION) {
for (TLRPC.User user : res.users) {
FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone);
@ -791,16 +812,16 @@ public class ContactsController {
}*/
MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true);
ArrayList<TLRPC.TL_contact> cArr = new ArrayList<>();
for (TLRPC.TL_importedContact c : res.imported) {
for (int a = 0; a < res.imported.size(); a++) {
TLRPC.TL_contact contact = new TLRPC.TL_contact();
contact.user_id = c.user_id;
contact.user_id = res.imported.get(a).user_id;
cArr.add(contact);
}
processLoadedContacts(cArr, res.users, 2);
} else {
FileLog.e("tmessages", "import contacts error " + error.text);
}
if (isLastQuery) {
if (completedRequestsCount == count) {
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
@ -836,13 +857,13 @@ public class ContactsController {
applyContactsUpdates(delayedContactsUpdate, null, null, null);
delayedContactsUpdate.clear();
}
}
});
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
updateUnregisteredContacts(contacts);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded);
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
updateUnregisteredContacts(contacts);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded);
}
});
}
});
}
@ -892,7 +913,7 @@ public class ContactsController {
@Override
public void run(TLObject response, TLRPC.TL_error error) {
if (error == null) {
TLRPC.contacts_Contacts res = (TLRPC.contacts_Contacts)response;
TLRPC.contacts_Contacts res = (TLRPC.contacts_Contacts) response;
if (res instanceof TLRPC.TL_contacts_contactsNotModified) {
contactsLoaded = true;
if (!delayedContactsUpdate.isEmpty() && contactsBookLoaded) {
@ -940,11 +961,10 @@ public class ContactsController {
contactsArr.addAll(contacts);
}
for (TLRPC.TL_contact contact : contactsArr) {
TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id);
for (int a = 0; a < contactsArr.size(); a++) {
TLRPC.User user = MessagesController.getInstance().getUser(contactsArr.get(a).user_id);
if (user != null) {
usersDict.put(user.id, user);
//if (BuildVars.DEBUG_VERSION) {
// FileLog.e("tmessages", "loaded user contact " + user.first_name + " " + user.last_name + " " + user.phone);
//}
@ -955,7 +975,7 @@ public class ContactsController {
@Override
public void run() {
FileLog.e("tmessages", "done loading contacts");
if (from == 1 && (contactsArr.isEmpty() || UserConfig.lastContactsSyncTime < (int) (System.currentTimeMillis() / 1000) - 24 * 60 * 60)) {
if (from == 1 && (contactsArr.isEmpty() || Math.abs(System.currentTimeMillis() / 1000 - UserConfig.lastContactsSyncTime) >= 24 * 60 * 60)) {
loadContacts(false, true);
return;
}
@ -1021,7 +1041,8 @@ public class ContactsController {
final HashMap<String, TLRPC.TL_contact> contactsByPhonesDictFinal = contactsByPhonesDict;
for (TLRPC.TL_contact value : contactsArr) {
for (int a = 0; a < contactsArr.size(); a++) {
TLRPC.TL_contact value = contactsArr.get(a);
TLRPC.User user = usersDict.get(value.user_id);
if (user == null) {
continue;
@ -1354,7 +1375,8 @@ public class ContactsController {
if (newC == null || contactsTD == null) {
newC = new ArrayList<>();
contactsTD = new ArrayList<>();
for (Integer uid : ids) {
for (int a = 0; a < ids.size(); a++) {
Integer uid = ids.get(a);
if (uid > 0) {
TLRPC.TL_contact contact = new TLRPC.TL_contact();
contact.user_id = uid;
@ -1370,7 +1392,8 @@ public class ContactsController {
StringBuilder toDelete = new StringBuilder();
boolean reloadContacts = false;
for (TLRPC.TL_contact newContact : newC) {
for (int a = 0; a < newC.size(); a++) {
TLRPC.TL_contact newContact = newC.get(a);
TLRPC.User user = null;
if (userDict != null) {
user = userDict.get(newContact.user_id);
@ -1398,7 +1421,8 @@ public class ContactsController {
toAdd.append(user.phone);
}
for (final Integer uid : contactsTD) {
for (int a = 0; a < contactsTD.size(); a++) {
final Integer uid = contactsTD.get(a);
Utilities.phoneBookQueue.postRunnable(new Runnable() {
@Override
public void run() {
@ -1452,13 +1476,15 @@ public class ContactsController {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
for (TLRPC.TL_contact contact : newContacts) {
for (int a = 0; a < newContacts.size(); a++) {
TLRPC.TL_contact contact = newContacts.get(a);
if (contactsDict.get(contact.user_id) == null) {
contacts.add(contact);
contactsDict.put(contact.user_id, contact);
}
}
for (Integer uid : contactsToDelete) {
for (int a = 0; a < contactsToDelete.size(); a++) {
Integer uid = contactsToDelete.get(a);
TLRPC.TL_contact contact = contactsDict.get(uid);
if (contact != null) {
contacts.remove(contact);
@ -1469,7 +1495,7 @@ public class ContactsController {
updateUnregisteredContacts(contacts);
performWriteContactsToPhoneBook();
}
performSyncPhoneBook(getContactsCopy(contactsBook), false, false, false);
performSyncPhoneBook(getContactsCopy(contactsBook), false, false, false, false);
buildContactsSectionsArrays(!newContacts.isEmpty());
NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded);
}
@ -1643,7 +1669,7 @@ public class ContactsController {
if (error != null) {
return;
}
final TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts)response;
final TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response;
MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true);
/*if (BuildVars.DEBUG_VERSION) {

View File

@ -0,0 +1,18 @@
/*
* 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-2016.
*/
package org.telegram.messenger;
import org.telegram.tgnet.TLRPC;
public class DialogObject {
public static boolean isChannel(TLRPC.TL_dialog dialog) {
return dialog != null && (dialog.flags & 1) != 0;
}
}

View File

@ -79,7 +79,6 @@ public class ImageLoader {
private HashMap<String, Runnable> retryHttpsTasks = new HashMap<>();
private int currentHttpFileLoadTasksCount = 0;
public VMRuntimeHack runtimeHack = null;
private String ignoreRemoval = null;
private volatile long lastCacheOutTime = 0;
@ -631,7 +630,7 @@ public class ImageLoader {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 1;
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 21) {
if (Build.VERSION.SDK_INT < 21) {
opts.inPurgeable = true;
}
@ -685,9 +684,6 @@ public class ImageLoader {
} else if (blurType == 0 && opts.inPurgeable) {
Utilities.pinBitmap(image);
}
if (runtimeHack != null) {
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
}
}
} catch (Throwable e) {
FileLog.e("tmessages", e);
@ -715,9 +711,6 @@ public class ImageLoader {
}
int delay = 20;
if (runtimeHack != null) {
delay = 60;
}
if (mediaId != null) {
delay = 0;
}
@ -782,7 +775,7 @@ public class ImageLoader {
} else {
opts.inPreferredConfig = Bitmap.Config.RGB_565;
}
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 21) {
if (Build.VERSION.SDK_INT < 21) {
opts.inPurgeable = true;
}
@ -850,9 +843,6 @@ public class ImageLoader {
if (!blured && opts.inPurgeable) {
Utilities.pinBitmap(image);
}
if (runtimeHack != null && image != null) {
runtimeHack.trackFree(image.getRowBytes() * image.getHeight());
}
}
} catch (Throwable e) {
//don't promt
@ -877,9 +867,6 @@ public class ImageLoader {
toSet = bitmapDrawable;
} else {
Bitmap image = bitmapDrawable.getBitmap();
if (runtimeHack != null) {
runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight());
}
image.recycle();
}
}
@ -1098,19 +1085,10 @@ public class ImageLoader {
int cacheSize = Math.min(15, ((ActivityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() / 7) * 1024 * 1024;
if (Build.VERSION.SDK_INT < 11) {
runtimeHack = new VMRuntimeHack();
cacheSize = 1024 * 1024 * 3;
}
memCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable value) {
Bitmap b = value.getBitmap();
if (Build.VERSION.SDK_INT < 12) {
return b.getRowBytes() * b.getHeight();
} else {
return b.getByteCount();
}
return value.getBitmap().getByteCount();
}
@Override
@ -1121,9 +1099,6 @@ public class ImageLoader {
final Integer count = bitmapUseCounts.get(key);
if (count == null || count == 0) {
Bitmap b = oldValue.getBitmap();
if (runtimeHack != null) {
runtimeHack.trackAlloc(b.getRowBytes() * b.getHeight());
}
if (!b.isRecycled()) {
b.recycle();
}
@ -1937,11 +1912,7 @@ public class ImageLoader {
}
while (currentHttpTasksCount < 4 && !httpTasks.isEmpty()) {
HttpImageTask task = httpTasks.poll();
if (android.os.Build.VERSION.SDK_INT >= 11) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
} else {
task.execute(null, null, null);
}
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
currentHttpTasksCount++;
}
}
@ -2006,11 +1977,7 @@ public class ImageLoader {
}
while (currentHttpFileLoadTasksCount < 2 && !httpFileLoadTasks.isEmpty()) {
HttpFileTask task = httpFileLoadTasks.poll();
if (android.os.Build.VERSION.SDK_INT >= 11) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
} else {
task.execute(null, null, null);
}
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
currentHttpFileLoadTasksCount++;
}
}
@ -2057,7 +2024,7 @@ public class ImageLoader {
}
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = (int) scaleFactor;
bmOptions.inPurgeable = Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 21;
bmOptions.inPurgeable = Build.VERSION.SDK_INT < 21;
String exifPath = null;
if (path != null) {

View File

@ -652,6 +652,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
public Bitmap getBitmap() {
if (currentImage instanceof AnimatedFileDrawable) {
return ((AnimatedFileDrawable) currentImage).getAnimatedBitmap();
} else if (staticThumb instanceof AnimatedFileDrawable) {
return ((AnimatedFileDrawable) staticThumb).getAnimatedBitmap();
} else if (currentImage instanceof BitmapDrawable) {
return ((BitmapDrawable) currentImage).getBitmap();
} else if (currentThumb instanceof BitmapDrawable) {
@ -665,6 +667,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
public int getBitmapWidth() {
if (currentImage instanceof AnimatedFileDrawable) {
return orientation % 360 == 0 || orientation % 360 == 180 ? currentImage.getIntrinsicWidth() : currentImage.getIntrinsicHeight();
} else if (staticThumb instanceof AnimatedFileDrawable) {
return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicWidth() : staticThumb.getIntrinsicHeight();
}
Bitmap bitmap = getBitmap();
return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getWidth() : bitmap.getHeight();
@ -673,6 +677,8 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
public int getBitmapHeight() {
if (currentImage instanceof AnimatedFileDrawable) {
return orientation % 360 == 0 || orientation % 360 == 180 ? currentImage.getIntrinsicHeight() : currentImage.getIntrinsicWidth();
} else if (staticThumb instanceof AnimatedFileDrawable) {
return orientation % 360 == 0 || orientation % 360 == 180 ? staticThumb.getIntrinsicHeight() : staticThumb.getIntrinsicWidth();
}
Bitmap bitmap = getBitmap();
return orientation % 360 == 0 || orientation % 360 == 180 ? bitmap.getHeight() : bitmap.getWidth();
@ -872,6 +878,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
return currentImage instanceof AnimatedFileDrawable && ((AnimatedFileDrawable) currentImage).isRunning();
}
public AnimatedFileDrawable getAnimation() {
return currentImage instanceof AnimatedFileDrawable ? (AnimatedFileDrawable) currentImage : null;
}
protected Integer getTag(boolean thumb) {
if (thumb) {
return thumbTag;
@ -990,15 +1000,11 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
if (key != null && (newKey == null || !newKey.equals(key)) && image != null) {
if (image instanceof AnimatedFileDrawable) {
AnimatedFileDrawable fileDrawable = (AnimatedFileDrawable) image;
fileDrawable.stop();
fileDrawable.recycle();
} else if (image instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) image).getBitmap();
boolean canDelete = ImageLoader.getInstance().decrementUseCount(key);
if (!ImageLoader.getInstance().isInCache(key)) {
if (ImageLoader.getInstance().runtimeHack != null) {
ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight());
}
if (canDelete) {
bitmap.recycle();
}

View File

@ -323,7 +323,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
private long recordTimeCount;
private long recordDialogId;
private MessageObject recordReplyingMessageObject;
private boolean recordAsAdmin;
private DispatchQueue fileDecodingQueue;
private DispatchQueue playerQueue;
private ArrayList<AudioBuffer> usedPlayerBuffers = new ArrayList<>();
@ -605,12 +604,12 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
fileDecodingQueue = new DispatchQueue("fileDecodingQueue");
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
mobileDataDownloadMask = preferences.getInt("mobileDataDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | (Build.VERSION.SDK_INT >= 11 ? AUTODOWNLOAD_MASK_GIF : 0));
wifiDownloadMask = preferences.getInt("wifiDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | (Build.VERSION.SDK_INT >= 11 ? AUTODOWNLOAD_MASK_GIF : 0));
mobileDataDownloadMask = preferences.getInt("mobileDataDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF);
wifiDownloadMask = preferences.getInt("wifiDownloadMask", AUTODOWNLOAD_MASK_PHOTO | AUTODOWNLOAD_MASK_AUDIO | AUTODOWNLOAD_MASK_MUSIC | AUTODOWNLOAD_MASK_GIF);
roamingDownloadMask = preferences.getInt("roamingDownloadMask", 0);
saveToGallery = preferences.getBoolean("save_gallery", false);
autoplayGifs = preferences.getBoolean("autoplay_gif", true) && Build.VERSION.SDK_INT >= 11;
raiseToSpeak = preferences.getBoolean("raise_to_speak", true) && Build.VERSION.SDK_INT >= 11;
autoplayGifs = preferences.getBoolean("autoplay_gif", true);
raiseToSpeak = preferences.getBoolean("raise_to_speak", true);
customTabs = preferences.getBoolean("custom_tabs", true);
directShare = preferences.getBoolean("direct_share", true);
shuffleMusic = preferences.getBoolean("shuffleMusic", false);
@ -1085,9 +1084,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
}
public void startMediaObserver() {
if (android.os.Build.VERSION.SDK_INT < 14) {
return;
}
ApplicationLoader.applicationHandler.removeCallbacks(stopMediaObserverRunnable);
startObserverToken++;
try {
@ -1107,9 +1103,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
}
public void stopMediaObserver() {
if (android.os.Build.VERSION.SDK_INT < 14) {
return;
}
if (stopMediaObserverRunnable == null) {
stopMediaObserverRunnable = new StopMediaObserverRunnable();
}
@ -1655,7 +1648,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
if (!raiseChat.playFirstUnreadVoiceMessage()) {
raiseToEarRecord = true;
useFrontSpeaker = false;
startRecording(raiseChat.getDialogId(), null, false);
startRecording(raiseChat.getDialogId(), null);
}
ignoreOnPause = true;
if (proximityHasDifferentValues && proximityWakeLock != null && !proximityWakeLock.isHeld()) {
@ -1720,7 +1713,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
return;
}
raiseToEarRecord = true;
startRecording(raiseChat.getDialogId(), null, false);
startRecording(raiseChat.getDialogId(), null);
ignoreOnPause = true;
}
@ -2025,6 +2018,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
if (currentPlaylistNum == -1) {
playlist.clear();
shuffledPlaylist.clear();
currentPlaylistNum = playlist.size();
playlist.add(current);
}
if (current.isMusic()) {
@ -2540,7 +2534,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
return downloadingCurrentMessage;
}
public void startRecording(final long dialog_id, final MessageObject reply_to_msg, final boolean asAdmin) {
public void startRecording(final long dialog_id, final MessageObject reply_to_msg) {
boolean paused = false;
if (playingMessageObject != null && isPlayingAudio(playingMessageObject) && !isAudioPaused()) {
paused = true;
@ -2592,17 +2586,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
});
return;
}
//if (Build.VERSION.SDK_INT >= 11) {
// audioRecorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, recordBufferSize * 10);
//} else {
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, recordBufferSize * 10);
//}
audioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, recordBufferSize * 10);
recordStartTime = System.currentTimeMillis();
recordTimeCount = 0;
samplesCount = 0;
recordDialogId = dialog_id;
recordReplyingMessageObject = reply_to_msg;
recordAsAdmin = asAdmin;
fileBuffer.rewind();
audioRecorder.startRecording();
@ -2670,7 +2660,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
}
TLRPC.TL_messages_messages messagesRes = new TLRPC.TL_messages_messages();
messagesRes.messages.add(messageObject.messageOwner);
MessagesStorage.getInstance().putMessages(messagesRes, messageObject.getDialogId(), -1, 0, 0, false);
MessagesStorage.getInstance().putMessages(messagesRes, messageObject.getDialogId(), -1, 0, false);
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.replaceMessagesObjects, messageObject.getDialogId(), arrayList);
@ -2705,7 +2695,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
audioToSend.attributes.add(attributeAudio);
if (duration > 700) {
if (send == 1) {
SendMessagesHelper.getInstance().sendMessage(audioToSend, null, recordingAudioFileToSend.getAbsolutePath(), recordDialogId, recordReplyingMessageObject, recordAsAdmin, null, null);
SendMessagesHelper.getInstance().sendMessage(audioToSend, null, recordingAudioFileToSend.getAbsolutePath(), recordDialogId, recordReplyingMessageObject, null, null);
}
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioDidSent, send == 2 ? audioToSend : null, send == 2 ? recordingAudioFileToSend.getAbsolutePath() : null);
} else {
@ -2866,10 +2856,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
if (result) {
if (type == 2) {
if (Build.VERSION.SDK_INT >= 12) {
DownloadManager downloadManager = (DownloadManager) ApplicationLoader.applicationContext.getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.addCompletedDownload(destFile.getName(), destFile.getName(), false, mime, destFile.getAbsolutePath(), destFile.length(), true);
}
DownloadManager downloadManager = (DownloadManager) ApplicationLoader.applicationContext.getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.addCompletedDownload(destFile.getName(), destFile.getName(), false, mime, destFile.getAbsolutePath(), destFile.length(), true);
} else {
AndroidUtilities.addMediaToGallery(Uri.fromFile(destFile));
}

View File

@ -314,6 +314,8 @@ public class MessageObject {
messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup);
} else if (message.action instanceof TLRPC.TL_messageActionPinMessage) {
generatePinMessageText(fromUser, fromUser == null ? chats.get(message.to_id.channel_id) : null);
} else if (message.action instanceof TLRPC.TL_messageActionHistoryClear) {
messageText = LocaleController.getString("HistoryCleared", R.string.HistoryCleared);
}
}
} else if (!isMediaEmpty()) {
@ -370,7 +372,9 @@ public class MessageObject {
if (messageOwner.message != null && messageOwner.id < 0 && messageOwner.message.length() > 6 && isVideo()) {
videoEditedInfo = new VideoEditedInfo();
videoEditedInfo.parseString(messageOwner.message);
if (!videoEditedInfo.parseString(messageOwner.message)) {
videoEditedInfo = null;
}
}
generateCaption();
@ -510,6 +514,9 @@ public class MessageObject {
contentType = -1;
type = -1;
}
} else if (messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear) {
contentType = -1;
type = -1;
} else {
contentType = 1;
type = 10;
@ -1199,27 +1206,10 @@ public class MessageObject {
return message.media_unread;
}
public boolean isImportant() {
return isImportant(messageOwner);
}
public boolean isMegagroup() {
return isMegagroup(messageOwner);
}
public static boolean isImportant(TLRPC.Message message) {
if (isMegagroup(message)) {
return message.post;
}
if (message.to_id.channel_id != 0) {
if (message instanceof TLRPC.TL_message_layer47 || message instanceof TLRPC.TL_message_old7) {
return message.to_id.channel_id != 0 && (message.from_id <= 0 || message.mentioned || message.out || (message.flags & TLRPC.MESSAGE_FLAG_HAS_FROM_ID) == 0);
}
return message.post;
}
return false;
}
public static boolean isMegagroup(TLRPC.Message message) {
return (message.flags & TLRPC.MESSAGE_FLAG_MEGAGROUP) != 0;
}
@ -1658,7 +1648,7 @@ public class MessageObject {
return false;
}
}
if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && isImportant(message)) {
if (chat.megagroup && message.out || !chat.megagroup && (chat.creator || chat.editor && isOut(message)) && message.post) {
if (message.media instanceof TLRPC.TL_messageMediaPhoto ||
message.media instanceof TLRPC.TL_messageMediaDocument && !isStickerMessage(message) ||
message.media instanceof TLRPC.TL_messageMediaEmpty ||

View File

@ -190,7 +190,11 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
metadataEditor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, authorName);
metadataEditor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, songName);
if (audioInfo != null && audioInfo.getCover() != null) {
metadataEditor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, audioInfo.getCover());
try {
metadataEditor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, audioInfo.getCover());
} catch (Throwable e) {
FileLog.e("tmessages", e);
}
}
metadataEditor.apply();
}

View File

@ -142,10 +142,12 @@ public class NativeLoader {
folder = "x86";
}
if (Build.VERSION.SDK_INT == 8) {
File destFile = new File(context.getApplicationInfo().dataDir + "/lib", LIB_SO_NAME);
File destFile = getNativeLibraryDir(context);
if (destFile != null) {
destFile = new File(destFile, LIB_SO_NAME);
if (destFile.exists()) {
FileLog.d("tmessages", "Load normal lib");
FileLog.d("tmessages", "load normal lib");
try {
System.loadLibrary(LIB_NAME);
init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION);
@ -154,31 +156,6 @@ public class NativeLoader {
} catch (Error e) {
FileLog.e("tmessages", e);
}
} else {
try {
System.loadLibrary(LIB_NAME);
init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION);
nativeLoaded = true;
return;
} catch (Error e) {
FileLog.e("tmessages", e);
}
}
} else {
File destFile = getNativeLibraryDir(context);
if (destFile != null) {
destFile = new File(destFile, LIB_SO_NAME);
if (destFile.exists()) {
FileLog.d("tmessages", "load normal lib");
try {
System.loadLibrary(LIB_NAME);
init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION);
nativeLoaded = true;
return;
} catch (Error e) {
FileLog.e("tmessages", e);
}
}
}
}

View File

@ -36,7 +36,7 @@ public class NotificationCenter {
public static final int encryptedChatUpdated = totalEvents++;
public static final int messagesReadEncrypted = totalEvents++;
public static final int encryptedChatCreated = totalEvents++;
public static final int userPhotosLoaded = totalEvents++;
public static final int dialogPhotosLoaded = totalEvents++;
public static final int removeAllMessagesFromDialog = totalEvents++;
public static final int notificationsSettingsUpdated = totalEvents++;
public static final int pushMessagesUpdated = totalEvents++;
@ -73,6 +73,7 @@ public class NotificationCenter {
public static final int wasUnableToFindCurrentLocation = totalEvents++;
public static final int reloadHints = totalEvents++;
public static final int reloadInlineHints = totalEvents++;
public static final int newDraftReceived = totalEvents++;
public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++;

View File

@ -0,0 +1,23 @@
/*
* 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-2016.
*/
package org.telegram.messenger;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
public class NotificationDismissReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
preferences.edit().putInt("dismissDate", intent.getIntExtra("messageDate", 0)).commit();
}
}

View File

@ -18,6 +18,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
@ -956,7 +958,7 @@ public class NotificationsController {
}
} else {
if (ChatObject.isChannel(chat) && !chat.megagroup) {
if (messageObject.isImportant()) {
if (messageObject.messageOwner.post) {
if (messageObject.isMediaEmpty()) {
if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) {
msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message);
@ -1271,6 +1273,12 @@ public class NotificationsController {
ConnectionsManager.getInstance().resumeNetworkMaybe();
MessageObject lastMessageObject = pushMessages.get(0);
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
int dismissDate = preferences.getInt("dismissDate", 0);
if (lastMessageObject.messageOwner.date <= dismissDate) {
dismissNotification();
return;
}
long dialog_id = lastMessageObject.getDialogId();
long override_dialog_id = dialog_id;
@ -1305,7 +1313,6 @@ public class NotificationsController {
int priorityOverride;
int vibrateOverride;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
int notifyOverride = getNotifyOverride(preferences, override_dialog_id);
if (!notifyAboutLast || notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0) {
notifyDisabled = true;
@ -1475,6 +1482,7 @@ public class NotificationsController {
int silent = 2;
String lastMessage = null;
boolean hasNewMessages = false;
if (pushMessages.size() == 1) {
MessageObject messageObject = pushMessages.get(0);
String message = lastMessage = getStringForMessage(messageObject, false);
@ -1499,7 +1507,7 @@ public class NotificationsController {
for (int i = 0; i < count; i++) {
MessageObject messageObject = pushMessages.get(i);
String message = getStringForMessage(messageObject, false);
if (message == null) {
if (message == null || messageObject.messageOwner.date <= dismissDate) {
continue;
}
if (silent == 2) {
@ -1521,10 +1529,26 @@ public class NotificationsController {
mBuilder.setStyle(inboxStyle);
}
Intent dismissIntent = new Intent(ApplicationLoader.applicationContext, NotificationDismissReceiver.class);
dismissIntent.putExtra("messageDate", lastMessageObject.messageOwner.date);
mBuilder.setDeleteIntent(PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 1, dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT));
if (photoPath != null) {
BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50");
if (img != null) {
mBuilder.setLargeIcon(img.getBitmap());
} else {
try {
float scaleFactor = 160.0f / AndroidUtilities.dp(50);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor;
Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photoPath, true).toString(), options);
if (bitmap != null) {
mBuilder.setLargeIcon(bitmap);
}
} catch (Throwable e) {
//ignore
}
}
}
@ -1742,6 +1766,7 @@ public class NotificationsController {
.setSmallIcon(R.drawable.notification)
.setGroup("messages")
.setContentText(text)
.setAutoCancel(true)
.setColor(0xff2ca5e0)
.setGroupSummary(false)
.setContentIntent(contentIntent)

View File

@ -88,7 +88,7 @@ public class SecretChatHelper {
return localInstance;
}
public void cleanUp() {
public void cleanup() {
sendingNotifyLayer.clear();
acceptingChats.clear();
secretHolesQueue.clear();
@ -194,7 +194,7 @@ public class SecretChatHelper {
user = usersDict.get(user_id);
}
newChat.user_id = user_id;
final TLRPC.Dialog dialog = new TLRPC.TL_dialog();
final TLRPC.TL_dialog dialog = new TLRPC.TL_dialog();
dialog.id = dialog_id;
dialog.unread_count = 0;
dialog.top_message = 0;
@ -206,18 +206,7 @@ public class SecretChatHelper {
MessagesController.getInstance().dialogs_dict.put(dialog.id, dialog);
MessagesController.getInstance().dialogs.add(dialog);
MessagesController.getInstance().putEncryptedChat(newChat, false);
Collections.sort(MessagesController.getInstance().dialogs, new Comparator<TLRPC.Dialog>() {
@Override
public int compare(TLRPC.Dialog tl_dialog, TLRPC.Dialog tl_dialog2) {
if (tl_dialog.last_message_date == tl_dialog2.last_message_date) {
return 0;
} else if (tl_dialog.last_message_date < tl_dialog2.last_message_date) {
return 1;
} else {
return -1;
}
}
});
MessagesController.getInstance().sortDialogs(null);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
}
});
@ -1153,7 +1142,7 @@ public class SecretChatHelper {
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
TLRPC.Dialog dialog = MessagesController.getInstance().dialogs_dict.get(did);
TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(did);
if (dialog != null) {
dialog.unread_count = 0;
MessagesController.getInstance().dialogMessage.remove(dialog.id);
@ -1813,25 +1802,14 @@ public class SecretChatHelper {
chat.seq_out = 1;
chat.a_or_b = salt;
MessagesController.getInstance().putEncryptedChat(chat, false);
TLRPC.Dialog dialog = new TLRPC.TL_dialog();
TLRPC.TL_dialog dialog = new TLRPC.TL_dialog();
dialog.id = ((long) chat.id) << 32;
dialog.unread_count = 0;
dialog.top_message = 0;
dialog.last_message_date = ConnectionsManager.getInstance().getCurrentTime();
MessagesController.getInstance().dialogs_dict.put(dialog.id, dialog);
MessagesController.getInstance().dialogs.add(dialog);
Collections.sort(MessagesController.getInstance().dialogs, new Comparator<TLRPC.Dialog>() {
@Override
public int compare(TLRPC.Dialog tl_dialog, TLRPC.Dialog tl_dialog2) {
if (tl_dialog.last_message_date == tl_dialog2.last_message_date) {
return 0;
} else if (tl_dialog.last_message_date < tl_dialog2.last_message_date) {
return 1;
} else {
return -1;
}
}
});
MessagesController.getInstance().sortDialogs(null);
MessagesStorage.getInstance().putEncryptedChat(chat, user, dialog);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload);
NotificationCenter.getInstance().postNotificationName(NotificationCenter.encryptedChatCreated, chat);
@ -1839,7 +1817,7 @@ public class SecretChatHelper {
@Override
public void run() {
if (!delayedEncryptedChatUpdates.isEmpty()) {
MessagesController.getInstance().processUpdateArray(delayedEncryptedChatUpdates, null, null);
MessagesController.getInstance().processUpdateArray(delayedEncryptedChatUpdates, null, null, false);
delayedEncryptedChatUpdates.clear();
}
}

View File

@ -20,13 +20,13 @@ import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import org.telegram.messenger.audioinfo.AudioInfo;
import org.telegram.messenger.query.DraftQuery;
import org.telegram.messenger.query.SearchQuery;
import org.telegram.messenger.query.StickersQuery;
import org.telegram.tgnet.ConnectionsManager;
@ -233,7 +233,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad);
}
public void cleanUp() {
public void cleanup() {
delayedMessages.clear();
unsentMessages.clear();
sendingMessages.clear();
@ -587,7 +587,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (unsent) {
unsentMessages.put(messageObject.getId(), messageObject);
}
sendMessage(messageObject, messageObject.messageOwner.post);
sendMessage(messageObject);
return true;
}
@ -599,43 +599,43 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
public void processForwardFromMyName(MessageObject messageObject, long did, boolean asAdmin) {
public void processForwardFromMyName(MessageObject messageObject, long did) {
if (messageObject == null) {
return;
}
if (messageObject.messageOwner.media != null && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage)) {
if (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photo) {
sendMessage((TLRPC.TL_photo) messageObject.messageOwner.media.photo, null, did, messageObject.replyMessageObject, asAdmin, null, null);
sendMessage((TLRPC.TL_photo) messageObject.messageOwner.media.photo, null, did, messageObject.replyMessageObject, null, null);
} else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_document) {
sendMessage((TLRPC.TL_document) messageObject.messageOwner.media.document, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject, asAdmin, null, null);
sendMessage((TLRPC.TL_document) messageObject.messageOwner.media.document, null, messageObject.messageOwner.attachPath, did, messageObject.replyMessageObject, null, null);
} else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo) {
sendMessage(messageObject.messageOwner.media, did, messageObject.replyMessageObject, asAdmin, null, null);
sendMessage(messageObject.messageOwner.media, did, messageObject.replyMessageObject, null, null);
} else if (messageObject.messageOwner.media.phone_number != null) {
TLRPC.User user = new TLRPC.TL_userContact_old2();
user.phone = messageObject.messageOwner.media.phone_number;
user.first_name = messageObject.messageOwner.media.first_name;
user.last_name = messageObject.messageOwner.media.last_name;
user.id = messageObject.messageOwner.media.user_id;
sendMessage(user, did, messageObject.replyMessageObject, asAdmin, null, null);
sendMessage(user, did, messageObject.replyMessageObject, null, null);
} else {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
sendMessage(arrayList, did, asAdmin);
sendMessage(arrayList, did);
}
} else if (messageObject.messageOwner.message != null) {
TLRPC.WebPage webPage = null;
if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) {
webPage = messageObject.messageOwner.media.webpage;
}
sendMessage(messageObject.messageOwner.message, did, messageObject.replyMessageObject, webPage, true, asAdmin, messageObject.messageOwner.entities, null, null);
sendMessage(messageObject.messageOwner.message, did, messageObject.replyMessageObject, webPage, true, messageObject.messageOwner.entities, null, null);
} else {
ArrayList<MessageObject> arrayList = new ArrayList<>();
arrayList.add(messageObject);
sendMessage(arrayList, did, asAdmin);
sendMessage(arrayList, did);
}
}
public void sendSticker(TLRPC.Document document, long peer, MessageObject replyingMessageObject, boolean asAdmin) {
public void sendSticker(TLRPC.Document document, long peer, MessageObject replyingMessageObject) {
if (document == null) {
return;
}
@ -679,10 +679,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
}
SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) document, null, null, peer, replyingMessageObject, asAdmin, null, null);
SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) document, null, null, peer, replyingMessageObject, null, null);
}
public void sendMessage(ArrayList<MessageObject> messages, final long peer, boolean asAdmin) {
public void sendMessage(ArrayList<MessageObject> messages, final long peer) {
if ((int) peer == 0 || messages == null || messages.isEmpty()) {
return;
}
@ -762,7 +762,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
newMsg.local_id = newMsg.id = UserConfig.getNewMessageId();
newMsg.out = true;
if (asAdmin && to_id.channel_id != 0 && !isMegagroup) {
if (to_id.channel_id != 0 && !isMegagroup) {
newMsg.from_id = isSignature ? UserConfig.getClientUserId() : -to_id.channel_id;
newMsg.post = true;
} else {
@ -777,9 +777,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
ids.add(newMsg.fwd_msg_id);
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
if (inputPeer instanceof TLRPC.TL_inputPeerChannel) {
if (asAdmin && !isMegagroup) {
if (!isMegagroup) {
newMsg.views = 1;
newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS;
} else {
newMsg.unread = true;
}
} else {
if ((msgObj.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) {
@ -832,10 +834,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
req.random_id = randomIds;
req.id = ids;
if (asAdmin && req.to_peer.channel_id != 0 && !isMegagroup) {
req.broadcast = true;
}
final ArrayList<TLRPC.Message> newMsgObjArr = arr;
final ArrayList<MessageObject> newMsgArr = objArr;
final HashMap<Long, TLRPC.Message> messagesByRandomIdsFinal = messagesByRandomIds;
@ -855,6 +853,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
a--;
}
}
Integer value = MessagesController.getInstance().dialogs_read_outbox_max.get(peer);
if (value == null) {
value = MessagesStorage.getInstance().getDialogReadMax(true, peer);
MessagesController.getInstance().dialogs_read_outbox_max.put(peer, value);
}
for (int a = 0; a < updates.updates.size(); a++) {
TLRPC.Update update = updates.updates.get(a);
if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateNewChannelMessage) {
@ -869,6 +873,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP;
}
}
message.unread = value < message.id;
Long random_id = newMessagesByIds.get(message.id);
if (random_id != null) {
final TLRPC.Message newMsgObj = messagesByRandomIdsFinal.get(random_id);
@ -996,7 +1002,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
mediaGeo.geo._long = location.getLongitude();
for (HashMap.Entry<String, MessageObject> entry : waitingForLocation.entrySet()) {
MessageObject messageObject = entry.getValue();
SendMessagesHelper.getInstance().sendMessage(mediaGeo, messageObject.getDialogId(), messageObject, false, null, null);
SendMessagesHelper.getInstance().sendMessage(mediaGeo, messageObject.getDialogId(), messageObject, null, null);
}
}
@ -1062,31 +1068,31 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
return !(messageObject == null || button == null) && waitingForCallback.containsKey(messageObject.getId() + "_" + Utilities.bytesToHex(button.data));
}
public void sendMessage(MessageObject retryMessageObject, boolean asAdmin) {
sendMessage(null, null, null, null, null, null, retryMessageObject.getDialogId(), retryMessageObject.messageOwner.attachPath, null, null, true, asAdmin, retryMessageObject, null, retryMessageObject.messageOwner.reply_markup, retryMessageObject.messageOwner.params);
public void sendMessage(MessageObject retryMessageObject) {
sendMessage(null, null, null, null, null, null, retryMessageObject.getDialogId(), retryMessageObject.messageOwner.attachPath, null, null, true, retryMessageObject, null, retryMessageObject.messageOwner.reply_markup, retryMessageObject.messageOwner.params);
}
public void sendMessage(TLRPC.User user, long peer, MessageObject reply_to_msg, boolean asAdmin, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, null, null, user, null, peer, null, reply_to_msg, null, true, asAdmin, null, null, replyMarkup, params);
public void sendMessage(TLRPC.User user, long peer, MessageObject reply_to_msg, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, null, null, user, null, peer, null, reply_to_msg, null, true, null, null, replyMarkup, params);
}
public void sendMessage(TLRPC.TL_document document, VideoEditedInfo videoEditedInfo, String path, long peer, MessageObject reply_to_msg, boolean asAdmin, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, null, videoEditedInfo, null, document, peer, path, reply_to_msg, null, true, asAdmin, null, null, replyMarkup, params);
public void sendMessage(TLRPC.TL_document document, VideoEditedInfo videoEditedInfo, String path, long peer, MessageObject reply_to_msg, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, null, videoEditedInfo, null, document, peer, path, reply_to_msg, null, true, null, null, replyMarkup, params);
}
public void sendMessage(String message, long peer, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks, boolean asAdmin, ArrayList<TLRPC.MessageEntity> entities, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(message, null, null, null, null, null, peer, null, reply_to_msg, webPage, searchLinks, asAdmin, null, entities, replyMarkup, params);
public void sendMessage(String message, long peer, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks, ArrayList<TLRPC.MessageEntity> entities, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(message, null, null, null, null, null, peer, null, reply_to_msg, webPage, searchLinks, null, entities, replyMarkup, params);
}
public void sendMessage(TLRPC.MessageMedia location, long peer, MessageObject reply_to_msg, boolean asAdmin, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, location, null, null, null, null, peer, null, reply_to_msg, null, true, asAdmin, null, null, replyMarkup, params);
public void sendMessage(TLRPC.MessageMedia location, long peer, MessageObject reply_to_msg, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, location, null, null, null, null, peer, null, reply_to_msg, null, true, null, null, replyMarkup, params);
}
public void sendMessage(TLRPC.TL_photo photo, String path, long peer, MessageObject reply_to_msg, boolean asAdmin, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, photo, null, null, null, peer, path, reply_to_msg, null, true, asAdmin, null, null, replyMarkup, params);
public void sendMessage(TLRPC.TL_photo photo, String path, long peer, MessageObject reply_to_msg, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
sendMessage(null, null, photo, null, null, null, peer, path, reply_to_msg, null, true, null, null, replyMarkup, params);
}
private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.User user, TLRPC.TL_document document, long peer, String path, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks, boolean asAdmin, MessageObject retryMessageObject, ArrayList<TLRPC.MessageEntity> entities, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_photo photo, VideoEditedInfo videoEditedInfo, TLRPC.User user, TLRPC.TL_document document, long peer, String path, MessageObject reply_to_msg, TLRPC.WebPage webPage, boolean searchLinks, MessageObject retryMessageObject, ArrayList<TLRPC.MessageEntity> entities, TLRPC.ReplyMarkup replyMarkup, HashMap<String, String> params) {
if (peer == 0) {
return;
}
@ -1101,6 +1107,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
int type = -1;
int lower_id = (int) peer;
int high_id = (int) (peer >> 32);
boolean isChannel = false;
TLRPC.EncryptedChat encryptedChat = null;
TLRPC.InputPeer sendToPeer = lower_id != 0 ? MessagesController.getInputPeer(lower_id) : null;
ArrayList<TLRPC.InputUser> sendToPeers = null;
@ -1115,11 +1122,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
return;
}
} else if (asAdmin && sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
} else if (sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
TLRPC.Chat chat = MessagesController.getInstance().getChat(sendToPeer.channel_id);
if (chat.megagroup) {
asAdmin = false;
}
isChannel = chat != null && !chat.megagroup;
}
try {
@ -1304,7 +1309,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
newMsg.local_id = newMsg.id = UserConfig.getNewMessageId();
newMsg.out = true;
if (asAdmin && sendToPeer != null && sendToPeer.channel_id != 0) {
if (isChannel && sendToPeer != null) {
newMsg.from_id = -sendToPeer.channel_id;
} else {
newMsg.from_id = UserConfig.getClientUserId();
@ -1330,7 +1335,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
newMsg.date = ConnectionsManager.getInstance().getCurrentTime();
newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA;
if (sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
if (asAdmin) {
if (isChannel) {
newMsg.views = 1;
newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS;
}
@ -1338,6 +1343,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (chat != null) {
if (chat.megagroup) {
newMsg.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP;
newMsg.unread = true;
} else {
newMsg.post = true;
if (chat.signatures) {
@ -1466,14 +1472,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
} else {
TLRPC.TL_messages_sendMessage reqSend = new TLRPC.TL_messages_sendMessage();
reqSend.message = message;
reqSend.clear_draft = retryMessageObject == null;
if (newMsg.to_id instanceof TLRPC.TL_peerChannel) {
reqSend.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false);
}
reqSend.peer = sendToPeer;
reqSend.random_id = newMsg.random_id;
if (asAdmin && sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
reqSend.broadcast = true;
}
if (reply_to_msg != null) {
reqSend.flags |= 1;
reqSend.reply_to_msg_id = reply_to_msg.getId();
@ -1486,6 +1490,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
reqSend.flags |= 8;
}
performSendMessageRequest(reqSend, newMsgObj, null);
if (retryMessageObject == null) {
DraftQuery.cleanDraft(peer, false);
}
}
} else {
TLRPC.TL_decryptedMessage reqSend;
@ -1522,6 +1529,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
reqSend.media = new TLRPC.TL_decryptedMessageMediaEmpty();
}
SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj);
if (retryMessageObject == null) {
DraftQuery.cleanDraft(peer, false);
}
}
} else if (type >= 1 && type <= 3 || type >= 5 && type <= 8 || type == 9 && encryptedChat != null) {
if (encryptedChat == null) {
@ -1660,6 +1670,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
delayedMessage.sendRequest = request;
}
reqSend = request;
if (retryMessageObject == null) {
DraftQuery.cleanDraft(peer, false);
}
} else {
TLRPC.TL_messages_sendMedia request = new TLRPC.TL_messages_sendMedia();
request.peer = sendToPeer;
@ -1668,9 +1681,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
request.random_id = newMsg.random_id;
request.media = inputMedia;
if (asAdmin && sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
request.broadcast = true;
}
if (reply_to_msg != null) {
request.flags |= 1;
request.reply_to_msg_id = reply_to_msg.getId();
@ -1971,6 +1981,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
performSendDelayedMessage(delayedMessage);
}
if (retryMessageObject == null) {
DraftQuery.cleanDraft(peer, false);
}
}
} else if (type == 4) {
TLRPC.TL_messages_forwardMessages reqSend = new TLRPC.TL_messages_forwardMessages();
@ -1994,16 +2007,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
} else {
reqSend.id.add(retryMessageObject.messageOwner.fwd_msg_id);
}
if (asAdmin && reqSend.to_peer.channel_id != 0) {
reqSend.broadcast = true;
}
performSendMessageRequest(reqSend, newMsgObj, null);
} else if (type == 9) {
TLRPC.TL_messages_sendInlineBotResult reqSend = new TLRPC.TL_messages_sendInlineBotResult();
reqSend.peer = sendToPeer;
if (asAdmin && sendToPeer instanceof TLRPC.TL_inputPeerChannel) {
reqSend.broadcast = true;
}
reqSend.random_id = newMsg.random_id;
if (reply_to_msg != null) {
reqSend.flags |= 1;
@ -2014,6 +2021,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
reqSend.query_id = Utilities.parseLong(params.get("query_id"));
reqSend.id = params.get("id");
if (retryMessageObject == null) {
reqSend.clear_draft = true;
DraftQuery.cleanDraft(peer, false);
}
performSendMessageRequest(reqSend, newMsgObj, null);
}
} catch (Exception e) {
@ -2239,6 +2250,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
}
if (message != null) {
Integer value = MessagesController.getInstance().dialogs_read_outbox_max.get(message.dialog_id);
if (value == null) {
value = MessagesStorage.getInstance().getDialogReadMax(message.out, message.dialog_id);
MessagesController.getInstance().dialogs_read_outbox_max.put(message.dialog_id, value);
}
message.unread = value < message.id;
newMsgObj.id = message.id;
updateMediaPaths(msgObj, message, originalPath, false);
} else {
@ -2518,7 +2536,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, final boolean asAdmin, String caption) {
private static boolean prepareSendingDocumentInternal(String path, String originalPath, Uri uri, String mime, final long dialog_id, final MessageObject reply_to_msg, String caption) {
if ((path == null || path.length() == 0) && uri == null) {
return false;
}
@ -2678,13 +2696,13 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, null, params);
}
});
return true;
}
public static void prepareSendingDocument(String path, String originalPath, Uri uri, String mine, long dialog_id, MessageObject reply_to_msg, boolean asAdmin) {
public static void prepareSendingDocument(String path, String originalPath, Uri uri, String mine, long dialog_id, MessageObject reply_to_msg) {
if ((path == null || originalPath == null) && uri == null) {
return;
}
@ -2696,10 +2714,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
paths.add(path);
originalPaths.add(originalPath);
prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg, asAdmin);
prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg);
}
public static void prepareSendingAudioDocuments(final ArrayList<MessageObject> messageObjects, final long dialog_id, final MessageObject reply_to_msg, final boolean asAdmin) {
public static void prepareSendingAudioDocuments(final ArrayList<MessageObject> messageObjects, final long dialog_id, final MessageObject reply_to_msg) {
new Thread(new Runnable() {
@Override
public void run() {
@ -2751,7 +2769,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg, null, params);
}
});
}
@ -2759,7 +2777,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}).start();
}
public static void prepareSendingDocuments(final ArrayList<String> paths, final ArrayList<String> originalPaths, final ArrayList<Uri> uris, final String mime, final long dialog_id, final MessageObject reply_to_msg, final boolean asAdmin) {
public static void prepareSendingDocuments(final ArrayList<String> paths, final ArrayList<String> originalPaths, final ArrayList<Uri> uris, final String mime, final long dialog_id, final MessageObject reply_to_msg) {
if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) {
return;
}
@ -2769,14 +2787,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
boolean error = false;
if (paths != null) {
for (int a = 0; a < paths.size(); a++) {
if (!prepareSendingDocumentInternal(paths.get(a), originalPaths.get(a), null, mime, dialog_id, reply_to_msg, asAdmin, null)) {
if (!prepareSendingDocumentInternal(paths.get(a), originalPaths.get(a), null, mime, dialog_id, reply_to_msg, null)) {
error = true;
}
}
}
if (uris != null) {
for (int a = 0; a < uris.size(); a++) {
if (!prepareSendingDocumentInternal(null, null, uris.get(a), mime, dialog_id, reply_to_msg, asAdmin, null)) {
if (!prepareSendingDocumentInternal(null, null, uris.get(a), mime, dialog_id, reply_to_msg, null)) {
error = true;
}
}
@ -2798,7 +2816,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}).start();
}
public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, boolean asAdmin) {
public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption) {
ArrayList<String> paths = null;
ArrayList<Uri> uris = null;
ArrayList<String> captions = null;
@ -2814,10 +2832,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
captions = new ArrayList<>();
captions.add(caption.toString());
}
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, asAdmin);
prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions);
}
public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap<String, String> params, final long dialog_id, final MessageObject reply_to_msg, final boolean asAdmin) {
public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap<String, String> params, final long dialog_id, final MessageObject reply_to_msg) {
if (result == null) {
return;
}
@ -3014,17 +3032,17 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
public void run() {
if (finalDocument != null) {
finalDocument.caption = result.send_message.caption;
SendMessagesHelper.getInstance().sendMessage(finalDocument, null, finalPathFinal, dialog_id, reply_to_msg, asAdmin, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(finalDocument, null, finalPathFinal, dialog_id, reply_to_msg, result.send_message.reply_markup, params);
} else if (finalPhoto != null) {
finalPhoto.caption = result.send_message.caption;
SendMessagesHelper.getInstance().sendMessage(finalPhoto, result.content_url, dialog_id, reply_to_msg, asAdmin, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(finalPhoto, result.content_url, dialog_id, reply_to_msg, result.send_message.reply_markup, params);
}
}
});
}
}).run();
} else if (result.send_message instanceof TLRPC.TL_botInlineMessageText) {
SendMessagesHelper.getInstance().sendMessage(result.send_message.message, dialog_id, reply_to_msg, null, !result.send_message.no_webpage, asAdmin, result.send_message.entities, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(result.send_message.message, dialog_id, reply_to_msg, null, !result.send_message.no_webpage, result.send_message.entities, result.send_message.reply_markup, params);
} else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaVenue) {
TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue();
venue.geo = result.send_message.geo;
@ -3032,21 +3050,21 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
venue.title = result.send_message.title;
venue.provider = result.send_message.provider;
venue.venue_id = result.send_message.venue_id;
SendMessagesHelper.getInstance().sendMessage(venue, dialog_id, reply_to_msg, asAdmin, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(venue, dialog_id, reply_to_msg, result.send_message.reply_markup, params);
} else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaGeo) {
TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo();
location.geo = result.send_message.geo;
SendMessagesHelper.getInstance().sendMessage(location, dialog_id, reply_to_msg, asAdmin, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(location, dialog_id, reply_to_msg, result.send_message.reply_markup, params);
} else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaContact) {
TLRPC.User user = new TLRPC.TL_user();
user.phone = result.send_message.phone_number;
user.first_name = result.send_message.first_name;
user.last_name = result.send_message.last_name;
SendMessagesHelper.getInstance().sendMessage(user, dialog_id, reply_to_msg, asAdmin, result.send_message.reply_markup, params);
SendMessagesHelper.getInstance().sendMessage(user, dialog_id, reply_to_msg, result.send_message.reply_markup, params);
}
}
public static void prepareSendingPhotosSearch(final ArrayList<MediaController.SearchImage> photos, final long dialog_id, final MessageObject reply_to_msg, final boolean asAdmin) {
public static void prepareSendingPhotosSearch(final ArrayList<MediaController.SearchImage> photos, final long dialog_id, final MessageObject reply_to_msg) {
if (photos == null || photos.isEmpty()) {
return;
}
@ -3142,7 +3160,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(documentFinal, null, pathFinal, dialog_id, reply_to_msg, null, params);
}
});
} else {
@ -3192,7 +3210,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(photoFinal, needDownloadHttpFinal ? searchImage.imageUrl : null, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(photoFinal, needDownloadHttpFinal ? searchImage.imageUrl : null, dialog_id, reply_to_msg, null, params);
}
});
}
@ -3216,7 +3234,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
return src;
}
public static void prepareSendingText(final String text, final long dialog_id, final boolean asAdmin) {
public static void prepareSendingText(final String text, final long dialog_id) {
MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() {
@Override
public void run() {
@ -3231,7 +3249,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
int count = (int) Math.ceil(textFinal.length() / 4096.0f);
for (int a = 0; a < count; a++) {
String mess = textFinal.substring(a * 4096, Math.min((a + 1) * 4096, textFinal.length()));
SendMessagesHelper.getInstance().sendMessage(mess, dialog_id, null, null, true, asAdmin, null, null, null);
SendMessagesHelper.getInstance().sendMessage(mess, dialog_id, null, null, true, null, null, null);
}
}
}
@ -3242,7 +3260,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 boolean asAdmin) {
public static void prepareSendingPhotos(ArrayList<String> paths, ArrayList<Uri> uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList<String> captions) {
if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) {
return;
}
@ -3340,7 +3358,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(photoFinal, null, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(photoFinal, null, dialog_id, reply_to_msg, null, params);
}
});
}
@ -3348,14 +3366,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
}
if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) {
for (int a = 0; a < sendAsDocuments.size(); a++) {
prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, asAdmin, sendAsDocumentsCaptions.get(a));
prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, sendAsDocumentsCaptions.get(a));
}
}
}
}).start();
}
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 boolean asAdmin) {
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) {
if (videoPath == null || videoPath.length() == 0) {
return;
}
@ -3415,34 +3433,33 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
document.size = (int) temp.length();
}
boolean infoObtained = false;
if (Build.VERSION.SDK_INT >= 14) {
MediaMetadataRetriever mediaMetadataRetriever = null;
MediaMetadataRetriever mediaMetadataRetriever = null;
try {
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(videoPath);
String width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
if (width != null) {
attributeVideo.w = Integer.parseInt(width);
}
String height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
if (height != null) {
attributeVideo.h = Integer.parseInt(height);
}
String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
if (duration != null) {
attributeVideo.duration = (int) Math.ceil(Long.parseLong(duration) / 1000.0f);
}
infoObtained = true;
} catch (Exception e) {
FileLog.e("tmessages", e);
} finally {
try {
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(videoPath);
String width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
if (width != null) {
attributeVideo.w = Integer.parseInt(width);
if (mediaMetadataRetriever != null) {
mediaMetadataRetriever.release();
}
String height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
if (height != null) {
attributeVideo.h = Integer.parseInt(height);
}
String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
if (duration != null) {
attributeVideo.duration = (int) Math.ceil(Long.parseLong(duration) / 1000.0f);
}
infoObtained = true;
} catch (Exception e) {
FileLog.e("tmessages", e);
} finally {
try {
if (mediaMetadataRetriever != null) {
mediaMetadataRetriever.release();
}
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
}
if (!infoObtained) {
@ -3470,11 +3487,11 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, asAdmin, null, params);
SendMessagesHelper.getInstance().sendMessage(videoFinal, videoEditedInfo, finalPath, dialog_id, reply_to_msg, null, params);
}
});
} else {
prepareSendingDocumentInternal(videoPath, videoPath, null, null, dialog_id, reply_to_msg, asAdmin, null);
prepareSendingDocumentInternal(videoPath, videoPath, null, null, dialog_id, reply_to_msg, null);
}
}
}).start();

View File

@ -20,28 +20,27 @@ import java.io.File;
public class UserConfig {
private static TLRPC.User currentUser;
public static boolean registeredForPush = false;
public static boolean registeredForPush;
public static String pushString = "";
public static int lastSendMessageId = -210000;
public static int lastLocalId = -210000;
public static int lastBroadcastId = -1;
public static String contactsHash = "";
public static String importHash = "";
public static boolean blockedUsersLoaded = false;
public static boolean blockedUsersLoaded;
private final static Object sync = new Object();
public static boolean saveIncomingPhotos = false;
public static int contactsVersion = 1;
public static boolean saveIncomingPhotos;
public static String passcodeHash = "";
public static byte[] passcodeSalt = new byte[0];
public static boolean appLocked = false;
public static int passcodeType = 0;
public static boolean appLocked;
public static int passcodeType;
public static int autoLockIn = 60 * 60;
public static int lastPauseTime = 0;
public static boolean isWaitingForPasscodeEnter = false;
public static int lastPauseTime;
public static boolean isWaitingForPasscodeEnter;
public static boolean useFingerprint = true;
public static String lastUpdateVersion;
public static int lastContactsSyncTime;
public static int lastHintsSyncTime;
public static boolean draftsLoaded;
public static int migrateOffsetId = -1;
public static int migrateOffsetDate = -1;
@ -73,9 +72,7 @@ public class UserConfig {
editor.putInt("lastSendMessageId", lastSendMessageId);
editor.putInt("lastLocalId", lastLocalId);
editor.putString("contactsHash", contactsHash);
editor.putString("importHash", importHash);
editor.putBoolean("saveIncomingPhotos", saveIncomingPhotos);
editor.putInt("contactsVersion", contactsVersion);
editor.putInt("lastBroadcastId", lastBroadcastId);
editor.putBoolean("blockedUsersLoaded", blockedUsersLoaded);
editor.putString("passcodeHash1", passcodeHash);
@ -88,6 +85,7 @@ public class UserConfig {
editor.putInt("lastContactsSyncTime", lastContactsSyncTime);
editor.putBoolean("useFingerprint", useFingerprint);
editor.putInt("lastHintsSyncTime", lastHintsSyncTime);
editor.putBoolean("draftsLoaded", draftsLoaded);
editor.putInt("migrateOffsetId", migrateOffsetId);
if (migrateOffsetId != -1) {
@ -162,9 +160,8 @@ public class UserConfig {
lastSendMessageId = data.readInt32(false);
lastLocalId = data.readInt32(false);
contactsHash = data.readString(false);
importHash = data.readString(false);
data.readString(false);
saveIncomingPhotos = data.readBool(false);
contactsVersion = 0;
MessagesStorage.lastQtsValue = data.readInt32(false);
MessagesStorage.lastSecretVersion = data.readInt32(false);
int val = data.readInt32(false);
@ -188,9 +185,7 @@ public class UserConfig {
lastSendMessageId = preferences.getInt("lastSendMessageId", -210000);
lastLocalId = preferences.getInt("lastLocalId", -210000);
contactsHash = preferences.getString("contactsHash", "");
importHash = preferences.getString("importHash", "");
saveIncomingPhotos = preferences.getBoolean("saveIncomingPhotos", false);
contactsVersion = preferences.getInt("contactsVersion", 0);
}
if (lastLocalId > -210000) {
lastLocalId = -210000;
@ -215,9 +210,7 @@ public class UserConfig {
lastSendMessageId = preferences.getInt("lastSendMessageId", -210000);
lastLocalId = preferences.getInt("lastLocalId", -210000);
contactsHash = preferences.getString("contactsHash", "");
importHash = preferences.getString("importHash", "");
saveIncomingPhotos = preferences.getBoolean("saveIncomingPhotos", false);
contactsVersion = preferences.getInt("contactsVersion", 0);
lastBroadcastId = preferences.getInt("lastBroadcastId", -1);
blockedUsersLoaded = preferences.getBoolean("blockedUsersLoaded", false);
passcodeHash = preferences.getString("passcodeHash1", "");
@ -229,6 +222,7 @@ public class UserConfig {
lastUpdateVersion = preferences.getString("lastUpdateVersion2", "3.5");
lastContactsSyncTime = preferences.getInt("lastContactsSyncTime", (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60);
lastHintsSyncTime = preferences.getInt("lastHintsSyncTime", (int) (System.currentTimeMillis() / 1000) - 25 * 60 * 60);
draftsLoaded = preferences.getBoolean("draftsLoaded", false);
migrateOffsetId = preferences.getInt("migrateOffsetId", 0);
if (migrateOffsetId != -1) {
@ -297,9 +291,7 @@ public class UserConfig {
currentUser = null;
registeredForPush = false;
contactsHash = "";
importHash = "";
lastSendMessageId = -210000;
contactsVersion = 1;
lastBroadcastId = -1;
saveIncomingPhotos = false;
blockedUsersLoaded = false;
@ -316,6 +308,7 @@ public class UserConfig {
autoLockIn = 60 * 60;
lastPauseTime = 0;
useFingerprint = true;
draftsLoaded = true;
isWaitingForPasscodeEnter = false;
lastUpdateVersion = BuildVars.BUILD_VERSION_STRING;
lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60;

View File

@ -22,7 +22,7 @@ import java.util.regex.Pattern;
public class Utilities {
public static Pattern pattern = Pattern.compile("[0-9]+");
public static Pattern pattern = Pattern.compile("[\\-0-9]+");
public static SecureRandom random = new SecureRandom();
public static volatile DispatchQueue stageQueue = new DispatchQueue("stageQueue");

View File

@ -25,27 +25,33 @@ public class VideoEditedInfo {
return String.format(Locale.US, "-1_%d_%d_%d_%d_%d_%d_%d_%d_%s", startTime, endTime, rotationValue, originalWidth, originalHeight, bitrate, resultWidth, resultHeight, originalPath);
}
public void parseString(String string) {
public boolean parseString(String string) {
if (string.length() < 6) {
return;
return false;
}
String args[] = string.split("_");
if (args.length >= 10) {
startTime = Long.parseLong(args[1]);
endTime = Long.parseLong(args[2]);
rotationValue = Integer.parseInt(args[3]);
originalWidth = Integer.parseInt(args[4]);
originalHeight = Integer.parseInt(args[5]);
bitrate = Integer.parseInt(args[6]);
resultWidth = Integer.parseInt(args[7]);
resultHeight = Integer.parseInt(args[8]);
for (int a = 9; a < args.length; a++) {
if (originalPath == null) {
originalPath = args[a];
} else {
originalPath += "_" + args[a];
try {
String args[] = string.split("_");
if (args.length >= 10) {
startTime = Long.parseLong(args[1]);
endTime = Long.parseLong(args[2]);
rotationValue = Integer.parseInt(args[3]);
originalWidth = Integer.parseInt(args[4]);
originalHeight = Integer.parseInt(args[5]);
bitrate = Integer.parseInt(args[6]);
resultWidth = Integer.parseInt(args[7]);
resultHeight = Integer.parseInt(args[8]);
for (int a = 9; a < args.length; a++) {
if (originalPath == null) {
originalPath = args[a];
} else {
originalPath += "_" + args[a];
}
}
}
return true;
} catch (Exception e) {
FileLog.e("tmessages", e);
}
return false;
}
}

View File

@ -32,7 +32,7 @@ public class WearReplyReceiver extends BroadcastReceiver {
if (dialog_id == 0 || max_id == 0) {
return;
}
SendMessagesHelper.getInstance().sendMessage(text.toString(), dialog_id, null, null, true, false, null, null, null);
SendMessagesHelper.getInstance().sendMessage(text.toString(), dialog_id, null, null, true, null, null, null);
MessagesController.getInstance().markDialogAsRead(dialog_id, max_id, max_id, 0, true, false);
}
}

View File

@ -88,11 +88,13 @@ public class Browser {
@Override
public void onServiceConnected(CustomTabsClient client) {
customTabsClient = client;
if (customTabsClient != null) {
try {
customTabsClient.warmup(0);
} catch (Exception e) {
FileLog.e("tmessages", e);
if (MediaController.getInstance().canCustomTabs()) {
if (customTabsClient != null) {
try {
customTabsClient.warmup(0);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2014 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.exoplayer;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
*/
public final class AspectRatioFrameLayout extends FrameLayout {
/**
* The {@link FrameLayout} will not resize itself if the fractional difference between its natural
* aspect ratio and the requested aspect ratio falls below this threshold.
* <p>
* This tolerance allows the view to occupy the whole of the screen when the requested aspect
* ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce
* the number of view layers that need to be composited by the underlying system, which can help
* to reduce power consumption.
*/
private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f;
private float videoAspectRatio;
public AspectRatioFrameLayout(Context context) {
super(context);
}
public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the aspect ratio that this view should satisfy.
*
* @param widthHeightRatio The width to height ratio.
*/
public void setAspectRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (videoAspectRatio == 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
return;
}
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2014 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.exoplayer;
import java.io.IOException;
/**
* Thrown when a live playback falls behind the available media window.
*/
public final class BehindLiveWindowException extends IOException {
public BehindLiveWindowException() {
super();
}
public BehindLiveWindowException(String message) {
super(message);
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.util.Util;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaExtractor;
/**
* Defines constants that are generally useful throughout the library.
*/
public final class C {
/**
* Represents an unknown microsecond time or duration.
*/
public static final long UNKNOWN_TIME_US = -1L;
/**
* Represents a microsecond duration whose exact value is unknown, but which should match the
* longest of some other known durations.
*/
public static final long MATCH_LONGEST_US = -2L;
/**
* The number of microseconds in one second.
*/
public static final long MICROS_PER_SECOND = 1000000L;
/**
* Represents an unbounded length of data.
*/
public static final int LENGTH_UNBOUNDED = -1;
/**
* The name of the UTF-8 charset.
*/
public static final String UTF8_NAME = "UTF-8";
/**
* @see MediaCodec#CRYPTO_MODE_AES_CTR
*/
@SuppressWarnings("InlinedApi")
public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
/**
* @see AudioFormat#ENCODING_INVALID
*/
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
/**
* @see AudioFormat#ENCODING_PCM_8BIT
*/
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/**
* @see AudioFormat#ENCODING_PCM_16BIT
*/
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/**
* PCM encoding with 24 bits per sample.
*/
public static final int ENCODING_PCM_24BIT = 0x80000000;
/**
* PCM encoding with 32 bits per sample.
*/
public static final int ENCODING_PCM_32BIT = 0x40000000;
/**
* @see AudioFormat#ENCODING_AC3
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/**
* @see AudioFormat#ENCODING_E_AC3
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
/**
* @see AudioFormat#ENCODING_DTS
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
/**
* @see AudioFormat#ENCODING_DTS_HD
*/
@SuppressWarnings("InlinedApi")
public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
/**
* @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND
*/
@SuppressWarnings({"InlinedApi", "deprecation"})
public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23
? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
/**
* @see MediaExtractor#SAMPLE_FLAG_SYNC
*/
@SuppressWarnings("InlinedApi")
public static final int SAMPLE_FLAG_SYNC = MediaExtractor.SAMPLE_FLAG_SYNC;
/**
* @see MediaExtractor#SAMPLE_FLAG_ENCRYPTED
*/
@SuppressWarnings("InlinedApi")
public static final int SAMPLE_FLAG_ENCRYPTED = MediaExtractor.SAMPLE_FLAG_ENCRYPTED;
/**
* Indicates that a sample should be decoded but not rendered.
*/
public static final int SAMPLE_FLAG_DECODE_ONLY = 0x8000000;
/**
* A return value for methods where the end of an input was encountered.
*/
public static final int RESULT_END_OF_INPUT = -1;
/**
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
*/
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
private C() {}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Maintains codec event counts, for debugging purposes only.
* <p>
* Counters should be written from the playback thread only. Counters may be read from any thread.
* To ensure that the counter values are correctly reflected between threads, users of this class
* should invoke {@link #ensureUpdated()} prior to reading and after writing.
*/
public final class CodecCounters {
public int codecInitCount;
public int codecReleaseCount;
public int inputBufferCount;
public int outputFormatChangedCount;
public int outputBuffersChangedCount;
public int renderedOutputBufferCount;
public int skippedOutputBufferCount;
public int droppedOutputBufferCount;
public int maxConsecutiveDroppedOutputBufferCount;
/**
* Should be invoked from the playback thread after the counters have been updated. Should also
* be invoked from any other thread that wishes to read the counters, before reading. These calls
* ensure that counter updates are made visible to the reading threads.
*/
public synchronized void ensureUpdated() {
// Do nothing. The use of synchronized ensures a memory barrier should another thread also
// call this method.
}
public String getDebugString() {
ensureUpdated();
StringBuilder builder = new StringBuilder();
builder.append("cic:").append(codecInitCount);
builder.append(" crc:").append(codecReleaseCount);
builder.append(" ibc:").append(inputBufferCount);
builder.append(" ofc:").append(outputFormatChangedCount);
builder.append(" obc:").append(outputBuffersChangedCount);
builder.append(" ren:").append(renderedOutputBufferCount);
builder.append(" sob:").append(skippedOutputBufferCount);
builder.append(" dob:").append(droppedOutputBufferCount);
builder.append(" mcdob:").append(maxConsecutiveDroppedOutputBufferCount);
return builder.toString();
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaExtractor;
/**
* Compatibility wrapper around {@link android.media.MediaCodec.CryptoInfo}.
*/
public final class CryptoInfo {
/**
* @see android.media.MediaCodec.CryptoInfo#iv
*/
public byte[] iv;
/**
* @see android.media.MediaCodec.CryptoInfo#key
*/
public byte[] key;
/**
* @see android.media.MediaCodec.CryptoInfo#mode
*/
public int mode;
/**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
*/
public int[] numBytesOfClearData;
/**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
*/
public int[] numBytesOfEncryptedData;
/**
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
public CryptoInfo() {
frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null;
}
/**
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
byte[] key, byte[] iv, int mode) {
this.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
this.key = key;
this.iv = iv;
this.mode = mode;
if (Util.SDK_INT >= 16) {
updateFrameworkCryptoInfoV16();
}
}
/**
* Equivalent to {@link MediaExtractor#getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo)}.
*
* @param extractor The extractor from which to retrieve the crypto information.
*/
@TargetApi(16)
public void setFromExtractorV16(MediaExtractor extractor) {
extractor.getSampleCryptoInfo(frameworkCryptoInfo);
numSubSamples = frameworkCryptoInfo.numSubSamples;
numBytesOfClearData = frameworkCryptoInfo.numBytesOfClearData;
numBytesOfEncryptedData = frameworkCryptoInfo.numBytesOfEncryptedData;
key = frameworkCryptoInfo.key;
iv = frameworkCryptoInfo.iv;
mode = frameworkCryptoInfo.mode;
}
/**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
* <p>
* Successive calls to this method on a single {@link CryptoInfo} will return the same instance.
* Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object
* should not be modified directly.
*
* @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
*/
@TargetApi(16)
public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() {
return frameworkCryptoInfo;
}
@TargetApi(16)
private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() {
return new android.media.MediaCodec.CryptoInfo();
}
@TargetApi(16)
private void updateFrameworkCryptoInfoV16() {
frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv,
mode);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo.CodecCapabilities;
/**
* Contains information about a media decoder.
*/
@TargetApi(16)
public final class DecoderInfo {
/**
* The name of the decoder.
* <p>
* May be passed to {@link android.media.MediaCodec#createByCodecName(String)} to create an
* instance of the decoder.
*/
public final String name;
/**
* {@link CodecCapabilities} for this decoder.
*/
public final CodecCapabilities capabilities;
/**
* Whether the decoder supports seamless resolution switches.
*
* @see android.media.MediaCodecInfo.CodecCapabilities#isFeatureSupported(String)
* @see android.media.MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback
*/
public final boolean adaptive;
/**
* @param name The name of the decoder.
* @param capabilities {@link CodecCapabilities} of the decoder.
*/
/* package */ DecoderInfo(String name, CodecCapabilities capabilities) {
this.name = name;
this.capabilities = capabilities;
this.adaptive = isAdaptive(capabilities);
}
private static boolean isAdaptive(CodecCapabilities capabilities) {
return capabilities != null && Util.SDK_INT >= 19 && isAdaptiveV19(capabilities);
}
@TargetApi(19)
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
}

View File

@ -0,0 +1,278 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.upstream.Allocator;
import org.telegram.messenger.exoplayer.upstream.NetworkLock;
import android.os.Handler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* A {@link LoadControl} implementation that allows loads to continue in a sequence that prevents
* any loader from getting too far ahead or behind any of the other loaders.
* <p>
* Loads are scheduled so as to fill the available buffer space as rapidly as possible. Once the
* duration of buffered media and the buffer utilization both exceed respective thresholds, the
* control switches to a draining state during which no loads are permitted to start. During
* draining periods, resources such as the device radio have an opportunity to switch into low
* power modes. The control reverts back to the loading state when either the duration of buffered
* media or the buffer utilization fall below respective thresholds.
* <p>
* This implementation of {@link LoadControl} integrates with {@link NetworkLock}, by registering
* itself as a task with priority {@link NetworkLock#STREAMING_PRIORITY} during loading periods,
* and unregistering itself during draining periods.
*/
public final class DefaultLoadControl implements LoadControl {
/**
* Interface definition for a callback to be notified of {@link DefaultLoadControl} events.
*/
public interface EventListener {
/**
* Invoked when the control transitions from a loading to a draining state, or vice versa.
*
* @param loading Whether the control is now in a loading state.
*/
void onLoadingChanged(boolean loading);
}
public static final int DEFAULT_LOW_WATERMARK_MS = 15000;
public static final int DEFAULT_HIGH_WATERMARK_MS = 30000;
public static final float DEFAULT_LOW_BUFFER_LOAD = 0.2f;
public static final float DEFAULT_HIGH_BUFFER_LOAD = 0.8f;
private static final int ABOVE_HIGH_WATERMARK = 0;
private static final int BETWEEN_WATERMARKS = 1;
private static final int BELOW_LOW_WATERMARK = 2;
private final Allocator allocator;
private final List<Object> loaders;
private final HashMap<Object, LoaderState> loaderStates;
private final Handler eventHandler;
private final EventListener eventListener;
private final long lowWatermarkUs;
private final long highWatermarkUs;
private final float lowBufferLoad;
private final float highBufferLoad;
private int targetBufferSize;
private long maxLoadStartPositionUs;
private int bufferState;
private boolean fillingBuffers;
private boolean streamingPrioritySet;
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*
* @param allocator The {@link Allocator} used by the loader.
*/
public DefaultLoadControl(Allocator allocator) {
this(allocator, null, null);
}
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*
* @param allocator The {@link Allocator} used by the loader.
* @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.
*/
public DefaultLoadControl(Allocator allocator, Handler eventHandler,
EventListener eventListener) {
this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS,
DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_BUFFER_LOAD, DEFAULT_HIGH_BUFFER_LOAD);
}
/**
* Constructs a new instance.
*
* @param allocator The {@link Allocator} used by the loader.
* @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 lowWatermarkMs The minimum duration of media that can be buffered for the control to
* be in the draining state. If less media is buffered, then the control will transition to
* the filling state.
* @param highWatermarkMs The minimum duration of media that can be buffered for the control to
* transition from filling to draining.
* @param lowBufferLoad The minimum fraction of the buffer that must be utilized for the control
* to be in the draining state. If the utilization is lower, then the control will transition
* to the filling state.
* @param highBufferLoad The minimum fraction of the buffer that must be utilized for the control
* to transition from the loading state to the draining state.
*/
public DefaultLoadControl(Allocator allocator, Handler eventHandler, EventListener eventListener,
int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, float highBufferLoad) {
this.allocator = allocator;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.loaders = new ArrayList<>();
this.loaderStates = new HashMap<>();
this.lowWatermarkUs = lowWatermarkMs * 1000L;
this.highWatermarkUs = highWatermarkMs * 1000L;
this.lowBufferLoad = lowBufferLoad;
this.highBufferLoad = highBufferLoad;
}
@Override
public void register(Object loader, int bufferSizeContribution) {
loaders.add(loader);
loaderStates.put(loader, new LoaderState(bufferSizeContribution));
targetBufferSize += bufferSizeContribution;
}
@Override
public void unregister(Object loader) {
loaders.remove(loader);
LoaderState state = loaderStates.remove(loader);
targetBufferSize -= state.bufferSizeContribution;
updateControlState();
}
@Override
public void trimAllocator() {
allocator.trim(targetBufferSize);
}
@Override
public Allocator getAllocator() {
return allocator;
}
@Override
public boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs,
boolean loading) {
// Update the loader state.
int loaderBufferState = getLoaderBufferState(playbackPositionUs, nextLoadPositionUs);
LoaderState loaderState = loaderStates.get(loader);
boolean loaderStateChanged = loaderState.bufferState != loaderBufferState
|| loaderState.nextLoadPositionUs != nextLoadPositionUs || loaderState.loading != loading;
if (loaderStateChanged) {
loaderState.bufferState = loaderBufferState;
loaderState.nextLoadPositionUs = nextLoadPositionUs;
loaderState.loading = loading;
}
// Update the buffer state.
int currentBufferSize = allocator.getTotalBytesAllocated();
int bufferState = getBufferState(currentBufferSize);
boolean bufferStateChanged = this.bufferState != bufferState;
if (bufferStateChanged) {
this.bufferState = bufferState;
}
// If either of the individual states have changed, update the shared control state.
if (loaderStateChanged || bufferStateChanged) {
updateControlState();
}
return currentBufferSize < targetBufferSize && nextLoadPositionUs != -1
&& nextLoadPositionUs <= maxLoadStartPositionUs;
}
private int getLoaderBufferState(long playbackPositionUs, long nextLoadPositionUs) {
if (nextLoadPositionUs == -1) {
return ABOVE_HIGH_WATERMARK;
} else {
long timeUntilNextLoadPosition = nextLoadPositionUs - playbackPositionUs;
return timeUntilNextLoadPosition > highWatermarkUs ? ABOVE_HIGH_WATERMARK :
timeUntilNextLoadPosition < lowWatermarkUs ? BELOW_LOW_WATERMARK :
BETWEEN_WATERMARKS;
}
}
private int getBufferState(int currentBufferSize) {
float bufferLoad = (float) currentBufferSize / targetBufferSize;
return bufferLoad > highBufferLoad ? ABOVE_HIGH_WATERMARK
: bufferLoad < lowBufferLoad ? BELOW_LOW_WATERMARK
: BETWEEN_WATERMARKS;
}
private void updateControlState() {
boolean loading = false;
boolean haveNextLoadPosition = false;
int highestState = bufferState;
for (int i = 0; i < loaders.size(); i++) {
LoaderState loaderState = loaderStates.get(loaders.get(i));
loading |= loaderState.loading;
haveNextLoadPosition |= loaderState.nextLoadPositionUs != -1;
highestState = Math.max(highestState, loaderState.bufferState);
}
fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition)
&& (highestState == BELOW_LOW_WATERMARK
|| (highestState == BETWEEN_WATERMARKS && fillingBuffers));
if (fillingBuffers && !streamingPrioritySet) {
NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY);
streamingPrioritySet = true;
notifyLoadingChanged(true);
} else if (!fillingBuffers && streamingPrioritySet && !loading) {
NetworkLock.instance.remove(NetworkLock.STREAMING_PRIORITY);
streamingPrioritySet = false;
notifyLoadingChanged(false);
}
maxLoadStartPositionUs = -1;
if (fillingBuffers) {
for (int i = 0; i < loaders.size(); i++) {
Object loader = loaders.get(i);
LoaderState loaderState = loaderStates.get(loader);
long loaderTime = loaderState.nextLoadPositionUs;
if (loaderTime != -1
&& (maxLoadStartPositionUs == -1 || loaderTime < maxLoadStartPositionUs)) {
maxLoadStartPositionUs = loaderTime;
}
}
}
}
private void notifyLoadingChanged(final boolean loading) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onLoadingChanged(loading);
}
});
}
}
private static class LoaderState {
public final int bufferSizeContribution;
public int bufferState;
public boolean loading;
public long nextLoadPositionUs;
public LoaderState(int bufferSizeContribution) {
this.bufferSizeContribution = bufferSizeContribution;
bufferState = ABOVE_HIGH_WATERMARK;
loading = false;
nextLoadPositionUs = -1;
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2014 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.exoplayer;
/**
* A {@link TrackRenderer} that does nothing.
* <p>
* This renderer returns 0 from {@link #getTrackCount()} in order to request that it should be
* ignored. {@link IllegalStateException} is thrown from all other methods documented to indicate
* that they should not be invoked unless the renderer is prepared.
*/
public final class DummyTrackRenderer extends TrackRenderer {
@Override
protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
return true;
}
@Override
protected int getTrackCount() {
return 0;
}
@Override
protected MediaFormat getFormat(int track) {
throw new IllegalStateException();
}
@Override
protected boolean isEnded() {
throw new IllegalStateException();
}
@Override
protected boolean isReady() {
throw new IllegalStateException();
}
@Override
protected void seekTo(long positionUs) {
throw new IllegalStateException();
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) {
throw new IllegalStateException();
}
@Override
protected void maybeThrowError() {
throw new IllegalStateException();
}
@Override
protected long getDurationUs() {
throw new IllegalStateException();
}
@Override
protected long getBufferedPositionUs() {
throw new IllegalStateException();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Thrown when a non-recoverable playback failure occurs.
* <p>
* Where possible, the cause returned by {@link #getCause()} will indicate the reason for failure.
*/
public final class ExoPlaybackException extends Exception {
/**
* True if the cause (i.e. the {@link Throwable} returned by {@link #getCause()}) was only caught
* by a fail-safe at the top level of the player. False otherwise.
*/
public final boolean caughtAtTopLevel;
public ExoPlaybackException(String message) {
super(message);
caughtAtTopLevel = false;
}
public ExoPlaybackException(Throwable cause) {
super(cause);
caughtAtTopLevel = false;
}
public ExoPlaybackException(String message, Throwable cause) {
super(message, cause);
caughtAtTopLevel = false;
}
/* package */ ExoPlaybackException(Throwable cause, boolean caughtAtTopLevel) {
super(cause);
this.caughtAtTopLevel = caughtAtTopLevel;
}
}

View File

@ -0,0 +1,415 @@
/*
* Copyright (C) 2014 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.exoplayer;
import android.os.Looper;
/**
* An extensible media player exposing traditional high-level media player functionality, such as
* the ability to prepare, play, pause and seek.
*
* <p>Topics covered here are:
* <ol>
* <li><a href="#Assumptions">Assumptions and player composition</a>
* <li><a href="#Threading">Threading model</a>
* <li><a href="#State">Player state</a>
* </ol>
*
* <a name="Assumptions"></a>
* <h3>Assumptions and player construction</h3>
*
* <p>The implementation is designed to make no assumptions about (and hence impose no restrictions
* on) the type of the media being played, how and where it is stored, or how it is rendered.
* Rather than implementing the loading and rendering of media directly, {@link ExoPlayer} instead
* delegates this work to one or more {@link TrackRenderer}s, which are injected when the player
* is prepared. Hence {@link ExoPlayer} is capable of loading and playing any media for which a
* {@link TrackRenderer} implementation can be provided.
*
* <p>{@link MediaCodecAudioTrackRenderer} and {@link MediaCodecVideoTrackRenderer} can be used for
* the common cases of rendering audio and video. These components in turn require an
* <i>upstream</i> {@link SampleSource} to be injected through their constructors, where upstream
* is defined to denote a component that is closer to the source of the media. This pattern of
* upstream dependency injection is actively encouraged, since it means that the functionality of
* the player is built up through the composition of components that can easily be exchanged for
* alternate implementations. For example a {@link SampleSource} implementation may require a
* further upstream data loading component to be injected through its constructor, with different
* implementations enabling the loading of data from various sources.
*
* <a name="Threading"></a>
* <h3>Threading model</h3>
*
* <p>The figure below shows the {@link ExoPlayer} threading model.</p>
* <p align="center"><img src="../../../../../images/exoplayer_threading_model.png"
* alt="MediaPlayer state diagram"
* border="0"/></p>
*
* <ul>
* <li>It is recommended that instances are created and accessed from a single application thread.
* An application's main thread is ideal. Accessing an instance from multiple threads is
* discouraged, however if an application does wish to do this then it may do so provided that it
* ensures accesses are synchronized.
* </li>
* <li>Registered {@link Listener}s are invoked on the thread that created the {@link ExoPlayer}
* instance.</li>
* <li>An internal playback thread is responsible for managing playback and invoking the
* {@link TrackRenderer}s in order to load and play the media.</li>
* <li>{@link TrackRenderer} implementations (or any upstream components that they depend on) may
* use additional background threads (e.g. to load data). These are implementation specific.</li>
* </ul>
*
* <a name="State"></a>
* <h3>Player state</h3>
*
* <p>The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State
* accessed by {@link #getSelectedTrack(int)} and {@link #getPlayWhenReady()} is only ever
* changed by invoking the player's methods, and are never changed as a result of operations that
* have been performed asynchronously by the playback thread. In contrast, the playback state
* accessed by {@link #getPlaybackState()} is only ever changed as a result of operations
* completing on the playback thread, as illustrated below.</p>
* <p align="center"><img src="../../../../../images/exoplayer_state.png"
* alt="ExoPlayer state"
* border="0"/></p>
*
* <p>The possible playback state transitions are shown below. Transitions can be triggered either
* by changes in the state of the {@link TrackRenderer}s being used, or as a result of
* {@link #prepare(TrackRenderer[])}, {@link #stop()} or {@link #release()} being invoked.</p>
* <p align="center"><img src="../../../../../images/exoplayer_playbackstate.png"
* alt="ExoPlayer playback state transitions"
* border="0"/></p>
*/
public interface ExoPlayer {
/**
* A factory for instantiating ExoPlayer instances.
*/
public static final class Factory {
/**
* The default minimum duration of data that must be buffered for playback to start or resume
* following a user action such as a seek.
*/
public static final int DEFAULT_MIN_BUFFER_MS = 2500;
/**
* The default minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static final int DEFAULT_MIN_REBUFFER_MS = 5000;
private Factory() {}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be invoked from a thread that has an associated {@link Looper}.
*
* @param rendererCount The number of {@link TrackRenderer}s that will be passed to
* {@link #prepare(TrackRenderer[])}.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) {
return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs);
}
/**
* Obtains an {@link ExoPlayer} instance.
* <p>
* Must be invoked from a thread that has an associated {@link Looper}.
*
* @param rendererCount The number of {@link TrackRenderer}s that will be passed to
* {@link #prepare(TrackRenderer[])}.
*/
public static ExoPlayer newInstance(int rendererCount) {
return new ExoPlayerImpl(rendererCount, DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS);
}
}
/**
* Interface definition for a callback to be notified of changes in player state.
*/
public interface Listener {
/**
* Invoked when the value returned from either {@link ExoPlayer#getPlayWhenReady()} or
* {@link ExoPlayer#getPlaybackState()} changes.
*
* @param playWhenReady Whether playback will proceed when ready.
* @param playbackState One of the {@code STATE} constants defined in the {@link ExoPlayer}
* interface.
*/
void onPlayerStateChanged(boolean playWhenReady, int playbackState);
/**
* Invoked when the current value of {@link ExoPlayer#getPlayWhenReady()} has been reflected
* by the internal playback thread.
* <p>
* An invocation of this method will shortly follow any call to
* {@link ExoPlayer#setPlayWhenReady(boolean)} that changes the state. If multiple calls are
* made in rapid succession, then this method will be invoked only once, after the final state
* has been reflected.
*/
void onPlayWhenReadyCommitted();
/**
* Invoked when an error occurs. The playback state will transition to
* {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance
* can still be used, and {@link ExoPlayer#release()} must still be called on the player should
* it no longer be required.
*
* @param error The error.
*/
void onPlayerError(ExoPlaybackException error);
}
/**
* A component of an {@link ExoPlayer} that can receive messages on the playback thread.
* <p>
* Messages can be delivered to a component via {@link ExoPlayer#sendMessage} and
* {@link ExoPlayer#blockingSendMessage}.
*/
public interface ExoPlayerComponent {
/**
* Handles a message delivered to the component. Invoked on the playback thread.
*
* @param messageType An integer identifying the type of message.
* @param message The message object.
* @throws ExoPlaybackException If an error occurred whilst handling the message.
*/
void handleMessage(int messageType, Object message) throws ExoPlaybackException;
}
/**
* The player is neither prepared or being prepared.
*/
public static final int STATE_IDLE = 1;
/**
* The player is being prepared.
*/
public static final int STATE_PREPARING = 2;
/**
* The player is prepared but not able to immediately play from the current position. The cause
* is {@link TrackRenderer} specific, but this state typically occurs when more data needs
* to be buffered for playback to start.
*/
public static final int STATE_BUFFERING = 3;
/**
* The player is prepared and able to immediately play from the current position. The player will
* be playing if {@link #getPlayWhenReady()} returns true, and paused otherwise.
*/
public static final int STATE_READY = 4;
/**
* The player has finished playing the media.
*/
public static final int STATE_ENDED = 5;
/**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* disable the renderer.
*/
public static final int TRACK_DISABLED = -1;
/**
* A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to
* select the default track.
*/
public static final int TRACK_DEFAULT = 0;
/**
* Represents an unknown time or duration.
*/
public static final long UNKNOWN_TIME = -1;
/**
* Gets the {@link Looper} associated with the playback thread.
*
* @return The {@link Looper} associated with the playback thread.
*/
public Looper getPlaybackLooper();
/**
* Register a listener to receive events from the player. The listener's methods will be invoked
* on the thread that was used to construct the player.
*
* @param listener The listener to register.
*/
public void addListener(Listener listener);
/**
* Unregister a listener. The listener will no longer receive events from the player.
*
* @param listener The listener to unregister.
*/
public void removeListener(Listener listener);
/**
* Returns the current state of the player.
*
* @return One of the {@code STATE} constants defined in this interface.
*/
public int getPlaybackState();
/**
* Prepares the player for playback.
*
* @param renderers The {@link TrackRenderer}s to use. The number of renderers must match the
* value that was passed to the {@link ExoPlayer.Factory#newInstance} method.
*/
public void prepare(TrackRenderer... renderers);
/**
* Returns the number of tracks exposed by the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The number of tracks.
*/
public int getTrackCount(int rendererIndex);
/**
* Returns the format of a track.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track.
* @return The format of the track.
*/
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex);
/**
* Selects a track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @param trackIndex The index of the track. A negative value or a value greater than or equal to
* the renderer's track count will disable the renderer.
*/
public void setSelectedTrack(int rendererIndex, int trackIndex);
/**
* Returns the index of the currently selected track for the specified renderer.
*
* @param rendererIndex The index of the renderer.
* @return The selected track. A negative value or a value greater than or equal to the renderer's
* track count indicates that the renderer is disabled.
*/
public int getSelectedTrack(int rendererIndex);
/**
* Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
* If the player is already in this state, then this method can be used to pause and resume
* playback.
*
* @param playWhenReady Whether playback should proceed when ready.
*/
public void setPlayWhenReady(boolean playWhenReady);
/**
* Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}.
*
* @return Whether playback will proceed when ready.
*/
public boolean getPlayWhenReady();
/**
* Whether the current value of {@link ExoPlayer#getPlayWhenReady()} has been reflected by the
* internal playback thread.
*
* @return True if the current value has been reflected. False otherwise.
*/
public boolean isPlayWhenReadyCommitted();
/**
* Seeks to a position specified in milliseconds.
*
* @param positionMs The seek position.
*/
public void seekTo(long positionMs);
/**
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
* is to pause playback.
* <p>
* Calling this method will cause the playback state to transition to
* {@link ExoPlayer#STATE_IDLE}. The player instance can still be used, and
* {@link ExoPlayer#release()} must still be called on the player if it's no longer required.
* <p>
* Calling this method does not reset the playback position. If this player instance will be used
* to play another video from its start, then {@code seekTo(0)} should be called after stopping
* the player and before preparing it for the next video.
*/
public void stop();
/**
* Releases the player. This method must be called when the player is no longer required.
* <p>
* The player must not be used after calling this method.
*/
public void release();
/**
* Sends a message to a specified component. The message is delivered to the component on the
* playback thread. If the component throws a {@link ExoPlaybackException}, then it is
* propagated out of the player as an error.
*
* @param target The target to which the message should be delivered.
* @param messageType An integer that can be used to identify the type of the message.
* @param message The message object.
*/
public void sendMessage(ExoPlayerComponent target, int messageType, Object message);
/**
* Blocking variant of {@link #sendMessage(ExoPlayerComponent, int, Object)} that does not return
* until after the message has been delivered.
*
* @param target The target to which the message should be delivered.
* @param messageType An integer that can be used to identify the type of the message.
* @param message The message object.
*/
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message);
/**
* Gets the duration of the track in milliseconds.
*
* @return The duration of the track in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME} if the
* duration is not known.
*/
public long getDuration();
/**
* Gets the current playback position in milliseconds.
*
* @return The current playback position in milliseconds.
*/
public long getCurrentPosition();
/**
* Gets an estimate of the absolute position in milliseconds up to which data is buffered.
*
* @return An estimate of the absolute position in milliseconds up to which data is buffered,
* or {@link ExoPlayer#UNKNOWN_TIME} if no estimate is available.
*/
public long getBufferedPosition();
/**
* Gets an estimate of the percentage into the media up to which data is buffered.
*
* @return An estimate of the percentage into the media up to which data is buffered. 0 if the
* duration of the media is not known or if no estimate is available.
*/
public int getBufferedPercentage();
}

View File

@ -0,0 +1,230 @@
/*
* Copyright (C) 2014 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.exoplayer;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Concrete implementation of {@link ExoPlayer}.
*/
/* package */ final class ExoPlayerImpl implements ExoPlayer {
private static final String TAG = "ExoPlayerImpl";
private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<Listener> listeners;
private final MediaFormat[][] trackFormats;
private final int[] selectedTrackIndices;
private boolean playWhenReady;
private int playbackState;
private int pendingPlayWhenReadyAcks;
/**
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
*
* @param rendererCount The number of {@link TrackRenderer}s that will be passed to
* {@link #prepare(TrackRenderer[])}.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
this.playWhenReady = false;
this.playbackState = STATE_IDLE;
this.listeners = new CopyOnWriteArraySet<>();
this.trackFormats = new MediaFormat[rendererCount][];
this.selectedTrackIndices = new int[rendererCount];
eventHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg);
}
};
internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,
minBufferMs, minRebufferMs);
}
@Override
public Looper getPlaybackLooper() {
return internalPlayer.getPlaybackLooper();
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
@Override
public int getPlaybackState() {
return playbackState;
}
@Override
public void prepare(TrackRenderer... renderers) {
Arrays.fill(trackFormats, null);
internalPlayer.prepare(renderers);
}
@Override
public int getTrackCount(int rendererIndex) {
return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0;
}
@Override
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
return trackFormats[rendererIndex][trackIndex];
}
@Override
public void setSelectedTrack(int rendererIndex, int trackIndex) {
if (selectedTrackIndices[rendererIndex] != trackIndex) {
selectedTrackIndices[rendererIndex] = trackIndex;
internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex);
}
}
@Override
public int getSelectedTrack(int rendererIndex) {
return selectedTrackIndices[rendererIndex];
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
if (this.playWhenReady != playWhenReady) {
this.playWhenReady = playWhenReady;
pendingPlayWhenReadyAcks++;
internalPlayer.setPlayWhenReady(playWhenReady);
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
}
}
@Override
public boolean getPlayWhenReady() {
return playWhenReady;
}
@Override
public boolean isPlayWhenReadyCommitted() {
return pendingPlayWhenReadyAcks == 0;
}
@Override
public void seekTo(long positionMs) {
internalPlayer.seekTo(positionMs);
}
@Override
public void stop() {
internalPlayer.stop();
}
@Override
public void release() {
internalPlayer.release();
eventHandler.removeCallbacksAndMessages(null);
}
@Override
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
internalPlayer.sendMessage(target, messageType, message);
}
@Override
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) {
internalPlayer.blockingSendMessage(target, messageType, message);
}
@Override
public long getDuration() {
return internalPlayer.getDuration();
}
@Override
public long getCurrentPosition() {
return internalPlayer.getCurrentPosition();
}
@Override
public long getBufferedPosition() {
return internalPlayer.getBufferedPosition();
}
@Override
public int getBufferedPercentage() {
long bufferedPosition = getBufferedPosition();
long duration = getDuration();
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
}
// 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_PREPARED: {
System.arraycopy(msg.obj, 0, trackFormats, 0, trackFormats.length);
playbackState = msg.arg1;
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
break;
}
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
playbackState = msg.arg1;
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
break;
}
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
pendingPlayWhenReadyAcks--;
if (pendingPlayWhenReadyAcks == 0) {
for (Listener listener : listeners) {
listener.onPlayWhenReadyCommitted();
}
}
break;
}
case ExoPlayerImplInternal.MSG_ERROR: {
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
for (Listener listener : listeners) {
listener.onPlayerError(exception);
}
break;
}
}
}
}

View File

@ -0,0 +1,668 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.ExoPlayer.ExoPlayerComponent;
import org.telegram.messenger.exoplayer.util.Assertions;
import org.telegram.messenger.exoplayer.util.PriorityHandlerThread;
import org.telegram.messenger.exoplayer.util.TraceUtil;
import org.telegram.messenger.exoplayer.util.Util;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Implements the internal behavior of {@link ExoPlayerImpl}.
*/
/* package */ final class ExoPlayerImplInternal implements Handler.Callback {
private static final String TAG = "ExoPlayerImplInternal";
// External messages
public static final int MSG_PREPARED = 1;
public static final int MSG_STATE_CHANGED = 2;
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
public static final int MSG_ERROR = 4;
// Internal messages
private static final int MSG_PREPARE = 1;
private static final int MSG_INCREMENTAL_PREPARE = 2;
private static final int MSG_SET_PLAY_WHEN_READY = 3;
private static final int MSG_STOP = 4;
private static final int MSG_RELEASE = 5;
private static final int MSG_SEEK_TO = 6;
private static final int MSG_DO_SOME_WORK = 7;
private static final int MSG_SET_RENDERER_SELECTED_TRACK = 8;
private static final int MSG_CUSTOM = 9;
private static final int PREPARE_INTERVAL_MS = 10;
private static final int RENDERING_INTERVAL_MS = 10;
private static final int IDLE_INTERVAL_MS = 1000;
private final Handler handler;
private final HandlerThread internalPlaybackThread;
private final Handler eventHandler;
private final StandaloneMediaClock standaloneMediaClock;
private final AtomicInteger pendingSeekCount;
private final List<TrackRenderer> enabledRenderers;
private final MediaFormat[][] trackFormats;
private final int[] selectedTrackIndices;
private final long minBufferUs;
private final long minRebufferUs;
private TrackRenderer[] renderers;
private TrackRenderer rendererMediaClockSource;
private MediaClock rendererMediaClock;
private boolean released;
private boolean playWhenReady;
private boolean rebuffering;
private int state;
private int customMessagesSent = 0;
private int customMessagesProcessed = 0;
private long lastSeekPositionMs;
private long elapsedRealtimeUs;
private volatile long durationUs;
private volatile long positionUs;
private volatile long bufferedPositionUs;
public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,
int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {
this.eventHandler = eventHandler;
this.playWhenReady = playWhenReady;
this.minBufferUs = minBufferMs * 1000L;
this.minRebufferUs = minRebufferMs * 1000L;
this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);
this.state = ExoPlayer.STATE_IDLE;
this.durationUs = TrackRenderer.UNKNOWN_TIME_US;
this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
standaloneMediaClock = new StandaloneMediaClock();
pendingSeekCount = new AtomicInteger();
enabledRenderers = new ArrayList<>(selectedTrackIndices.length);
trackFormats = new MediaFormat[selectedTrackIndices.length][];
// 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",
Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
handler = new Handler(internalPlaybackThread.getLooper(), this);
}
public Looper getPlaybackLooper() {
return internalPlaybackThread.getLooper();
}
public long getCurrentPosition() {
return pendingSeekCount.get() > 0 ? lastSeekPositionMs : (positionUs / 1000);
}
public long getBufferedPosition() {
return bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US ? ExoPlayer.UNKNOWN_TIME
: bufferedPositionUs / 1000;
}
public long getDuration() {
return durationUs == TrackRenderer.UNKNOWN_TIME_US ? ExoPlayer.UNKNOWN_TIME
: durationUs / 1000;
}
public void prepare(TrackRenderer... renderers) {
handler.obtainMessage(MSG_PREPARE, renderers).sendToTarget();
}
public void setPlayWhenReady(boolean playWhenReady) {
handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget();
}
public void seekTo(long positionMs) {
lastSeekPositionMs = positionMs;
pendingSeekCount.incrementAndGet();
handler.obtainMessage(MSG_SEEK_TO, Util.getTopInt(positionMs),
Util.getBottomInt(positionMs)).sendToTarget();
}
public void stop() {
handler.sendEmptyMessage(MSG_STOP);
}
public void setRendererSelectedTrack(int rendererIndex, int trackIndex) {
handler.obtainMessage(MSG_SET_RENDERER_SELECTED_TRACK, rendererIndex, trackIndex)
.sendToTarget();
}
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
customMessagesSent++;
handler.obtainMessage(MSG_CUSTOM, messageType, 0, Pair.create(target, message)).sendToTarget();
}
public synchronized void blockingSendMessage(ExoPlayerComponent target, int messageType,
Object message) {
if (released) {
Log.w(TAG, "Sent message(" + messageType + ") after release. Message ignored.");
return;
}
int messageNumber = customMessagesSent++;
handler.obtainMessage(MSG_CUSTOM, messageType, 0, Pair.create(target, message)).sendToTarget();
while (customMessagesProcessed <= messageNumber) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public synchronized void release() {
if (released) {
return;
}
handler.sendEmptyMessage(MSG_RELEASE);
while (!released) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
internalPlaybackThread.quit();
}
@Override
public boolean handleMessage(Message msg) {
try {
switch (msg.what) {
case MSG_PREPARE: {
prepareInternal((TrackRenderer[]) msg.obj);
return true;
}
case MSG_INCREMENTAL_PREPARE: {
incrementalPrepareInternal();
return true;
}
case MSG_SET_PLAY_WHEN_READY: {
setPlayWhenReadyInternal(msg.arg1 != 0);
return true;
}
case MSG_DO_SOME_WORK: {
doSomeWork();
return true;
}
case MSG_SEEK_TO: {
seekToInternal(Util.getLong(msg.arg1, msg.arg2));
return true;
}
case MSG_STOP: {
stopInternal();
return true;
}
case MSG_RELEASE: {
releaseInternal();
return true;
}
case MSG_CUSTOM: {
sendMessageInternal(msg.arg1, msg.obj);
return true;
}
case MSG_SET_RENDERER_SELECTED_TRACK: {
setRendererSelectedTrackInternal(msg.arg1, msg.arg2);
return true;
}
default:
return false;
}
} catch (ExoPlaybackException e) {
Log.e(TAG, "Internal track renderer error.", e);
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
stopInternal();
return true;
} catch (RuntimeException e) {
Log.e(TAG, "Internal runtime error.", e);
eventHandler.obtainMessage(MSG_ERROR, new ExoPlaybackException(e, true)).sendToTarget();
stopInternal();
return true;
}
}
private void setState(int state) {
if (this.state != state) {
this.state = state;
eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget();
}
}
private void prepareInternal(TrackRenderer[] renderers) throws ExoPlaybackException {
resetInternal();
this.renderers = renderers;
Arrays.fill(trackFormats, null);
setState(ExoPlayer.STATE_PREPARING);
incrementalPrepareInternal();
}
private void incrementalPrepareInternal() throws ExoPlaybackException {
long operationStartTimeMs = SystemClock.elapsedRealtime();
boolean prepared = true;
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) {
int state = renderer.prepare(positionUs);
if (state == TrackRenderer.STATE_UNPREPARED) {
renderer.maybeThrowError();
prepared = false;
}
}
}
if (!prepared) {
// We're still waiting for some sources to be prepared.
scheduleNextOperation(MSG_INCREMENTAL_PREPARE, operationStartTimeMs, PREPARE_INTERVAL_MS);
return;
}
long durationUs = 0;
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
int rendererTrackCount = renderer.getTrackCount();
MediaFormat[] rendererTrackFormats = new MediaFormat[rendererTrackCount];
for (int trackIndex = 0; trackIndex < rendererTrackCount; trackIndex++) {
rendererTrackFormats[trackIndex] = renderer.getFormat(trackIndex);
}
trackFormats[rendererIndex] = rendererTrackFormats;
if (rendererTrackCount > 0) {
if (durationUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the duration is unknown, so the media
// duration is unknown regardless of the duration of this track.
} else {
long trackDurationUs = renderer.getDurationUs();
if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) {
durationUs = TrackRenderer.UNKNOWN_TIME_US;
} else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) {
// Do nothing.
} else {
durationUs = Math.max(durationUs, trackDurationUs);
}
}
int trackIndex = selectedTrackIndices[rendererIndex];
if (0 <= trackIndex && trackIndex < rendererTrackFormats.length) {
enableRenderer(renderer, trackIndex, false);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);
}
}
}
this.durationUs = durationUs;
if (allRenderersEnded
&& (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
// We don't expect this case, but handle it anyway.
state = ExoPlayer.STATE_ENDED;
} else {
state = allRenderersReadyOrEnded ? ExoPlayer.STATE_READY : ExoPlayer.STATE_BUFFERING;
}
// Fire an event indicating that the player has been prepared, passing the initial state and
// renderer track information.
eventHandler.obtainMessage(MSG_PREPARED, state, 0, trackFormats).sendToTarget();
// Start the renderers if required, and schedule the first piece of work.
if (playWhenReady && state == ExoPlayer.STATE_READY) {
startRenderers();
}
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
private void enableRenderer(TrackRenderer renderer, int trackIndex, boolean joining)
throws ExoPlaybackException {
renderer.enable(trackIndex, positionUs, joining);
enabledRenderers.add(renderer);
MediaClock mediaClock = renderer.getMediaClock();
if (mediaClock != null) {
Assertions.checkState(rendererMediaClock == null);
rendererMediaClock = mediaClock;
rendererMediaClockSource = renderer;
}
}
private boolean rendererReadyOrEnded(TrackRenderer renderer) {
if (renderer.isEnded()) {
return true;
}
if (!renderer.isReady()) {
return false;
}
if (state == ExoPlayer.STATE_READY) {
return true;
}
long rendererDurationUs = renderer.getDurationUs();
long rendererBufferedPositionUs = renderer.getBufferedPositionUs();
long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs;
return minBufferDurationUs <= 0
|| rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US
|| rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US
|| rendererBufferedPositionUs >= positionUs + minBufferDurationUs
|| (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US
&& rendererDurationUs != TrackRenderer.MATCH_LONGEST_US
&& rendererBufferedPositionUs >= rendererDurationUs);
}
private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException {
try {
rebuffering = false;
this.playWhenReady = playWhenReady;
if (!playWhenReady) {
stopRenderers();
updatePositionUs();
} else {
if (state == ExoPlayer.STATE_READY) {
startRenderers();
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} else if (state == ExoPlayer.STATE_BUFFERING) {
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
} finally {
eventHandler.obtainMessage(MSG_SET_PLAY_WHEN_READY_ACK).sendToTarget();
}
}
private void startRenderers() throws ExoPlaybackException {
rebuffering = false;
standaloneMediaClock.start();
for (int i = 0; i < enabledRenderers.size(); i++) {
enabledRenderers.get(i).start();
}
}
private void stopRenderers() throws ExoPlaybackException {
standaloneMediaClock.stop();
for (int i = 0; i < enabledRenderers.size(); i++) {
ensureStopped(enabledRenderers.get(i));
}
}
private void updatePositionUs() {
if (rendererMediaClock != null && enabledRenderers.contains(rendererMediaClockSource)
&& !rendererMediaClockSource.isEnded()) {
positionUs = rendererMediaClock.getPositionUs();
standaloneMediaClock.setPositionUs(positionUs);
} else {
positionUs = standaloneMediaClock.getPositionUs();
}
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
}
private void doSomeWork() throws ExoPlaybackException {
TraceUtil.beginSection("doSomeWork");
long operationStartTimeMs = SystemClock.elapsedRealtime();
long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs
: Long.MAX_VALUE;
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
updatePositionUs();
for (int i = 0; i < enabledRenderers.size(); i++) {
TrackRenderer renderer = enabledRenderers.get(i);
// TODO: Each renderer should return the maximum delay before which it wishes to be
// invoked again. The minimum of these values should then be used as the delay before the next
// invocation of this method.
renderer.doSomeWork(positionUs, elapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
// Determine whether the renderer is ready (or ended). If it's not, throw an error that's
// preventing the renderer from making progress, if such an error exists.
boolean rendererReadyOrEnded = rendererReadyOrEnded(renderer);
if (!rendererReadyOrEnded) {
renderer.maybeThrowError();
}
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the buffered position is unknown. Hence the
// media buffer position unknown regardless of the buffered position of this track.
} else {
long rendererDurationUs = renderer.getDurationUs();
long rendererBufferedPositionUs = renderer.getBufferedPositionUs();
if (rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
} else if (rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US
|| (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US
&& rendererDurationUs != TrackRenderer.MATCH_LONGEST_US
&& rendererBufferedPositionUs >= rendererDurationUs)) {
// This track is fully buffered.
} else {
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
}
this.bufferedPositionUs = bufferedPositionUs;
if (allRenderersEnded
&& (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
setState(ExoPlayer.STATE_ENDED);
stopRenderers();
} else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded) {
setState(ExoPlayer.STATE_READY);
if (playWhenReady) {
startRenderers();
}
} else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) {
rebuffering = playWhenReady;
setState(ExoPlayer.STATE_BUFFERING);
stopRenderers();
}
handler.removeMessages(MSG_DO_SOME_WORK);
if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS);
} else if (!enabledRenderers.isEmpty()) {
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS);
}
TraceUtil.endSection();
}
private void scheduleNextOperation(int operationType, long thisOperationStartTimeMs,
long intervalMs) {
long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs;
long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime();
if (nextOperationDelayMs <= 0) {
handler.sendEmptyMessage(operationType);
} else {
handler.sendEmptyMessageDelayed(operationType, nextOperationDelayMs);
}
}
private void seekToInternal(long positionMs) throws ExoPlaybackException {
try {
if (positionMs == (positionUs / 1000)) {
// Seek is to the current position. Do nothing.
return;
}
rebuffering = false;
positionUs = positionMs * 1000;
standaloneMediaClock.stop();
standaloneMediaClock.setPositionUs(positionUs);
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
return;
}
for (int i = 0; i < enabledRenderers.size(); i++) {
TrackRenderer renderer = enabledRenderers.get(i);
ensureStopped(renderer);
renderer.seekTo(positionUs);
}
setState(ExoPlayer.STATE_BUFFERING);
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
} finally {
pendingSeekCount.decrementAndGet();
}
}
private void stopInternal() {
resetInternal();
setState(ExoPlayer.STATE_IDLE);
}
private void releaseInternal() {
resetInternal();
setState(ExoPlayer.STATE_IDLE);
synchronized (this) {
released = true;
notifyAll();
}
}
private void resetInternal() {
handler.removeMessages(MSG_DO_SOME_WORK);
handler.removeMessages(MSG_INCREMENTAL_PREPARE);
rebuffering = false;
standaloneMediaClock.stop();
if (renderers == null) {
return;
}
for (int i = 0; i < renderers.length; i++) {
TrackRenderer renderer = renderers[i];
stopAndDisable(renderer);
release(renderer);
}
renderers = null;
rendererMediaClock = null;
rendererMediaClockSource = null;
enabledRenderers.clear();
}
private void stopAndDisable(TrackRenderer renderer) {
try {
ensureDisabled(renderer);
} catch (ExoPlaybackException e) {
// There's nothing we can do.
Log.e(TAG, "Stop failed.", e);
} catch (RuntimeException e) {
// Ditto.
Log.e(TAG, "Stop failed.", e);
}
}
private void release(TrackRenderer renderer) {
try {
renderer.release();
} catch (ExoPlaybackException e) {
// There's nothing we can do.
Log.e(TAG, "Release failed.", e);
} catch (RuntimeException e) {
// Ditto.
Log.e(TAG, "Release failed.", e);
}
}
private <T> void sendMessageInternal(int what, Object obj)
throws ExoPlaybackException {
try {
@SuppressWarnings("unchecked")
Pair<ExoPlayerComponent, Object> targetAndMessage = (Pair<ExoPlayerComponent, Object>) obj;
targetAndMessage.first.handleMessage(what, targetAndMessage.second);
if (state != ExoPlayer.STATE_IDLE && state != ExoPlayer.STATE_PREPARING) {
// The message may have caused something to change that now requires us to do work.
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
} finally {
synchronized (this) {
customMessagesProcessed++;
notifyAll();
}
}
}
private void setRendererSelectedTrackInternal(int rendererIndex, int trackIndex)
throws ExoPlaybackException {
if (selectedTrackIndices[rendererIndex] == trackIndex) {
return;
}
selectedTrackIndices[rendererIndex] = trackIndex;
if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) {
return;
}
TrackRenderer renderer = renderers[rendererIndex];
int rendererState = renderer.getState();
if (rendererState == TrackRenderer.STATE_UNPREPARED
|| rendererState == TrackRenderer.STATE_RELEASED
|| renderer.getTrackCount() == 0) {
return;
}
boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED
|| rendererState == TrackRenderer.STATE_STARTED;
boolean shouldEnable = 0 <= trackIndex && trackIndex < trackFormats[rendererIndex].length;
if (isEnabled) {
// The renderer is currently enabled. We need to disable it, so that we can either re-enable
// it with the newly selected track (if shouldEnable is true) or because we want to leave it
// disabled (if shouldEnable is false).
if (!shouldEnable && renderer == rendererMediaClockSource) {
// We've been using rendererMediaClockSource to advance the current position, but it's being
// disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take over
// timing responsibilities.
standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs());
}
ensureDisabled(renderer);
enabledRenderers.remove(renderer);
}
if (shouldEnable) {
// Re-enable the renderer with the newly selected track.
boolean playing = playWhenReady && state == ExoPlayer.STATE_READY;
// Consider as joining if the renderer was previously disabled, but not when switching tracks.
boolean joining = !isEnabled && playing;
enableRenderer(renderer, trackIndex, joining);
if (playing) {
renderer.start();
}
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
}
private void ensureStopped(TrackRenderer renderer) throws ExoPlaybackException {
if (renderer.getState() == TrackRenderer.STATE_STARTED) {
renderer.stop();
}
}
private void ensureDisabled(TrackRenderer renderer) throws ExoPlaybackException {
ensureStopped(renderer);
if (renderer.getState() == TrackRenderer.STATE_ENABLED) {
renderer.disable();
if (renderer == rendererMediaClockSource) {
rendererMediaClock = null;
rendererMediaClockSource = null;
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Information about the ExoPlayer library.
*/
public final class ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
public static final String VERSION = "1.5.8";
/**
* The version of the library, expressed as an integer.
* <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).
*/
public static final int VERSION_INT = 1005008;
/**
* Whether the library was compiled with {@link org.telegram.messenger.exoplayer.util.Assertions}
* checks enabled.
*/
public static final boolean ASSERTIONS_ENABLED = true;
/**
* Whether the library was compiled with {@link org.telegram.messenger.exoplayer.util.TraceUtil}
* trace enabled.
*/
public static final boolean TRACE_ENABLED = true;
private ExoPlayerLibraryInfo() {}
}

View File

@ -0,0 +1,349 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader;
import org.telegram.messenger.exoplayer.drm.DrmInitData;
import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData;
import org.telegram.messenger.exoplayer.extractor.ExtractorSampleSource;
import org.telegram.messenger.exoplayer.extractor.mp4.PsshAtomUtil;
import org.telegram.messenger.exoplayer.util.Assertions;
import org.telegram.messenger.exoplayer.util.MimeTypes;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaExtractor;
import android.net.Uri;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
/**
* Extracts samples from a stream using Android's {@link MediaExtractor}.
* <p>
* Warning - This class is marked as deprecated because there are known device specific issues
* associated with its use, including playbacks not starting, playbacks stuttering and other
* miscellaneous failures. For mp4, m4a, mp3, webm, mkv, mpeg-ts, ogg, wav and aac playbacks it is
* strongly recommended to use {@link ExtractorSampleSource} instead. Where this is not possible
* this class can still be used, but please be aware of the associated risks. Playing container
* formats for which an ExoPlayer extractor does not yet exist (e.g. avi) is a valid use case of
* this class.
* <p>
* Over time we hope to enhance {@link ExtractorSampleSource} to support more formats, and hence
* make use of this class unnecessary.
*/
// TODO: This implementation needs to be fixed so that its methods are non-blocking (either
// through use of a background thread, or through changes to the framework's MediaExtractor API).
@Deprecated
@TargetApi(16)
public final class FrameworkSampleSource implements SampleSource, SampleSourceReader {
private static final int ALLOWED_FLAGS_MASK = C.SAMPLE_FLAG_SYNC | C.SAMPLE_FLAG_ENCRYPTED;
private static final int TRACK_STATE_DISABLED = 0;
private static final int TRACK_STATE_ENABLED = 1;
private static final int TRACK_STATE_FORMAT_SENT = 2;
// Parameters for a Uri data source.
private final Context context;
private final Uri uri;
private final Map<String, String> headers;
// Parameters for a FileDescriptor data source.
private final FileDescriptor fileDescriptor;
private final long fileDescriptorOffset;
private final long fileDescriptorLength;
private IOException preparationError;
private MediaExtractor extractor;
private MediaFormat[] trackFormats;
private boolean prepared;
private int remainingReleaseCount;
private int[] trackStates;
private boolean[] pendingDiscontinuities;
private long lastSeekPositionUs;
private long pendingSeekPositionUs;
/**
* Instantiates a new sample extractor reading from the specified {@code uri}.
*
* @param context Context for resolving {@code uri}.
* @param uri The content URI from which to extract data.
* @param headers Headers to send with requests for data.
*/
public FrameworkSampleSource(Context context, Uri uri, Map<String, String> headers) {
Assertions.checkState(Util.SDK_INT >= 16);
this.context = Assertions.checkNotNull(context);
this.uri = Assertions.checkNotNull(uri);
this.headers = headers;
fileDescriptor = null;
fileDescriptorOffset = 0;
fileDescriptorLength = 0;
}
/**
* Instantiates a new sample extractor reading from the specified seekable {@code fileDescriptor}.
* The caller is responsible for releasing the file descriptor.
*
* @param fileDescriptor File descriptor from which to read.
* @param fileDescriptorOffset The offset in bytes where the data to be extracted starts.
* @param fileDescriptorLength The length in bytes of the data to be extracted.
*/
public FrameworkSampleSource(FileDescriptor fileDescriptor, long fileDescriptorOffset,
long fileDescriptorLength) {
Assertions.checkState(Util.SDK_INT >= 16);
this.fileDescriptor = Assertions.checkNotNull(fileDescriptor);
this.fileDescriptorOffset = fileDescriptorOffset;
this.fileDescriptorLength = fileDescriptorLength;
context = null;
uri = null;
headers = null;
}
@Override
public SampleSourceReader register() {
remainingReleaseCount++;
return this;
}
@Override
public boolean prepare(long positionUs) {
if (!prepared) {
if (preparationError != null) {
return false;
}
extractor = new MediaExtractor();
try {
if (context != null) {
extractor.setDataSource(context, uri, headers);
} else {
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
}
} catch (IOException e) {
preparationError = e;
return false;
}
trackStates = new int[extractor.getTrackCount()];
pendingDiscontinuities = new boolean[trackStates.length];
trackFormats = new MediaFormat[trackStates.length];
for (int i = 0; i < trackStates.length; i++) {
trackFormats[i] = createMediaFormat(extractor.getTrackFormat(i));
}
prepared = true;
}
return true;
}
@Override
public int getTrackCount() {
Assertions.checkState(prepared);
return trackStates.length;
}
@Override
public MediaFormat getFormat(int track) {
Assertions.checkState(prepared);
return trackFormats[track];
}
@Override
public void enable(int track, long positionUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
trackStates[track] = TRACK_STATE_ENABLED;
extractor.selectTrack(track);
seekToUsInternal(positionUs, positionUs != 0);
}
@Override
public boolean continueBuffering(int track, long positionUs) {
// MediaExtractor takes care of buffering and blocks until it has samples, so we can always
// return true here. Although note that the blocking behavior is itself as bug, as per the
// TODO further up this file. This method will need to return something else as part of fixing
// the TODO.
return true;
}
@Override
public long readDiscontinuity(int track) {
if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false;
return lastSeekPositionUs;
}
return NO_DISCONTINUITY;
}
@Override
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
if (pendingDiscontinuities[track]) {
return NOTHING_READ;
}
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
formatHolder.format = trackFormats[track];
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null;
trackStates[track] = TRACK_STATE_FORMAT_SENT;
return FORMAT_READ;
}
int extractorTrackIndex = extractor.getSampleTrackIndex();
if (extractorTrackIndex == track) {
if (sampleHolder.data != null) {
int offset = sampleHolder.data.position();
sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset);
sampleHolder.data.position(offset + sampleHolder.size);
} else {
sampleHolder.size = 0;
}
sampleHolder.timeUs = extractor.getSampleTime();
sampleHolder.flags = extractor.getSampleFlags() & ALLOWED_FLAGS_MASK;
if (sampleHolder.isEncrypted()) {
sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
}
pendingSeekPositionUs = C.UNKNOWN_TIME_US;
extractor.advance();
return SAMPLE_READ;
} else {
return extractorTrackIndex < 0 ? END_OF_STREAM : NOTHING_READ;
}
}
@Override
public void disable(int track) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
extractor.unselectTrack(track);
pendingDiscontinuities[track] = false;
trackStates[track] = TRACK_STATE_DISABLED;
}
@Override
public void maybeThrowError() throws IOException {
if (preparationError != null) {
throw preparationError;
}
}
@Override
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
seekToUsInternal(positionUs, false);
}
@Override
public long getBufferedPositionUs() {
Assertions.checkState(prepared);
long bufferedDurationUs = extractor.getCachedDuration();
if (bufferedDurationUs == -1) {
return TrackRenderer.UNKNOWN_TIME_US;
} else {
long sampleTime = extractor.getSampleTime();
return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs;
}
}
@Override
public void release() {
Assertions.checkState(remainingReleaseCount > 0);
if (--remainingReleaseCount == 0 && extractor != null) {
extractor.release();
extractor = null;
}
}
@TargetApi(18)
private DrmInitData getDrmInitDataV18() {
// MediaExtractor only supports psshInfo for MP4, so it's ok to hard code the mimeType here.
Map<UUID, byte[]> psshInfo = extractor.getPsshInfo();
if (psshInfo == null || psshInfo.isEmpty()) {
return null;
}
DrmInitData.Mapped drmInitData = new DrmInitData.Mapped();
for (UUID uuid : psshInfo.keySet()) {
byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid));
drmInitData.put(uuid, new SchemeInitData(MimeTypes.VIDEO_MP4, psshAtom));
}
return drmInitData;
}
private void seekToUsInternal(long positionUs, boolean force) {
// Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case
// that there have been no interleaving calls to readSample.
if (force || pendingSeekPositionUs != positionUs) {
lastSeekPositionUs = positionUs;
pendingSeekPositionUs = positionUs;
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
for (int i = 0; i < trackStates.length; ++i) {
if (trackStates[i] != TRACK_STATE_DISABLED) {
pendingDiscontinuities[i] = true;
}
}
}
}
@SuppressLint("InlinedApi")
private static MediaFormat createMediaFormat(android.media.MediaFormat format) {
String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE);
int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees");
int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
int encoderDelay = getOptionalIntegerV16(format, "encoder-delay");
int encoderPadding = getOptionalIntegerV16(format, "encoder-padding");
ArrayList<byte[]> initializationData = new ArrayList<>();
for (int i = 0; format.containsKey("csd-" + i); i++) {
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
byte[] data = new byte[buffer.limit()];
buffer.get(data);
initializationData.add(data);
buffer.flip();
}
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT
: MediaFormat.NO_VALUE;
MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize,
durationUs, width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate,
language, MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, pcmEncoding, encoderDelay, encoderPadding);
mediaFormat.setFrameworkFormatV16(format);
return mediaFormat;
}
@TargetApi(16)
private static final String getOptionalStringV16(android.media.MediaFormat format, String key) {
return format.containsKey(key) ? format.getString(key) : null;
}
@TargetApi(16)
private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.upstream.Allocator;
/**
* Coordinates multiple loaders of time series data.
*/
public interface LoadControl {
/**
* Registers a loader.
*
* @param loader The loader being registered.
* @param bufferSizeContribution For instances whose {@link Allocator} maintains a pool of memory
* for the purpose of satisfying allocation requests, this is a hint indicating the loader's
* desired contribution to the size of the pool, in bytes.
*/
void register(Object loader, int bufferSizeContribution);
/**
* Unregisters a loader.
*
* @param loader The loader being unregistered.
*/
void unregister(Object loader);
/**
* Gets the {@link Allocator} that loaders should use to obtain memory allocations into which
* data can be loaded.
*
* @return The {@link Allocator} to use.
*/
Allocator getAllocator();
/**
* Hints to the control that it should consider trimming any unused memory being held in order
* to satisfy allocation requests.
* <p>
* This method is typically invoked by a recently unregistered loader, once it has released all
* of its allocations back to the {@link Allocator}.
*/
void trimAllocator();
/**
* Invoked by a loader to update the control with its current state.
* <p>
* This method must be called by a registered loader whenever its state changes. This is true
* even if the registered loader does not itself wish to start its next load (since the state of
* the loader will still affect whether other registered loaders are allowed to proceed).
*
* @param loader The loader invoking the update.
* @param playbackPositionUs The loader's playback position.
* @param nextLoadPositionUs The loader's next load position. -1 if finished, failed, or if the
* next load position is not yet known.
* @param loading Whether the loader is currently loading data.
* @return True if the loader is allowed to start its next load. False otherwise.
*/
boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs, boolean loading);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 The Android Open Source Project
* Copyright (C) 2014 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.
@ -13,12 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.messenger.exoplayer;
package org.telegram.messenger.Animation;
/**
* Tracks the progression of media time.
*/
public interface MediaClock {
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
/**
* @return The current media position in microseconds.
*/
long getPositionUs();
}

View File

@ -0,0 +1,486 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
import org.telegram.messenger.exoplayer.audio.AudioCapabilities;
import org.telegram.messenger.exoplayer.audio.AudioTrack;
import org.telegram.messenger.exoplayer.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer.util.MimeTypes;
import android.annotation.TargetApi;
import android.media.AudioManager;
import android.media.MediaCodec;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
import android.os.SystemClock;
import java.nio.ByteBuffer;
/**
* Decodes and renders audio using {@link MediaCodec} and {@link android.media.AudioTrack}.
*/
@TargetApi(16)
public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implements MediaClock {
/**
* Interface definition for a callback to be notified of {@link MediaCodecAudioTrackRenderer}
* events.
*/
public interface EventListener extends MediaCodecTrackRenderer.EventListener {
/**
* Invoked when an {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
/**
* Invoked when an {@link AudioTrack} write fails.
*
* @param e The corresponding exception.
*/
void onAudioTrackWriteError(AudioTrack.WriteException e);
/**
* Invoked when an {@link AudioTrack} underrun occurs.
*
* @param bufferSize The size of the {@link AudioTrack}'s buffer, in bytes.
* @param bufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds, if it is
* configured for PCM output. -1 if it is configured for passthrough output, as the buffered
* media can have a variable bitrate so the duration may be unknown.
* @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data.
*/
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
}
/**
* The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
* should be a {@link Float} with 0 being silence and 1 being unity gain.
*/
public static final int MSG_SET_VOLUME = 1;
/**
* The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
* should be a {@link android.media.PlaybackParams}, 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 = 2;
private final EventListener eventListener;
private final AudioTrack audioTrack;
private boolean passthroughEnabled;
private android.media.MediaFormat passthroughMediaFormat;
private int pcmEncoding;
private int audioSessionId;
private long currentPositionUs;
private boolean allowPositionDiscontinuity;
private boolean audioTrackHasData;
private long lastFeedElapsedRealtimeMs;
/**
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector) {
this(source, mediaCodecSelector, null, true);
}
/**
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) {
this(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null);
}
/**
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @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.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector,
Handler eventHandler, EventListener eventListener) {
this(source, mediaCodecSelector, null, true, eventHandler, eventListener);
}
/**
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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 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.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener) {
this(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener, null, AudioManager.STREAM_MUSIC);
}
/**
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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 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 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 streamType The type of audio stream for the {@link AudioTrack}.
*/
public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener, AudioCapabilities audioCapabilities,
int streamType) {
this (new SampleSource[] {source}, mediaCodecSelector, drmSessionManager,
playClearSamplesWithoutKeys, eventHandler, eventListener, audioCapabilities, streamType);
}
/**
* @param sources The upstream sources from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* 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 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 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 streamType The type of audio stream for the {@link AudioTrack}.
*/
public MediaCodecAudioTrackRenderer(SampleSource[] sources, MediaCodecSelector mediaCodecSelector,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener, AudioCapabilities audioCapabilities,
int streamType) {
super(sources, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener);
this.eventListener = eventListener;
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
this.audioTrack = new AudioTrack(audioCapabilities, streamType);
}
@Override
protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
return MimeTypes.isAudio(mimeType) && (MimeTypes.AUDIO_UNKNOWN.equals(mimeType)
|| (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null)
|| mediaCodecSelector.getDecoderInfo(mimeType, false) != null);
}
@Override
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, String mimeType,
boolean requiresSecureDecoder) throws DecoderQueryException {
if (allowPassthrough(mimeType)) {
DecoderInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) {
passthroughEnabled = true;
return passthroughDecoderInfo;
}
}
passthroughEnabled = false;
return super.getDecoderInfo(mediaCodecSelector, mimeType, requiresSecureDecoder);
}
/**
* Returns whether encoded audio passthrough should be used for playing back the input format.
* This implementation returns true if the {@link AudioTrack}'s audio capabilities indicate that
* passthrough is supported.
*
* @param mimeType The type of input media.
* @return True if passthrough playback should be used. False otherwise.
*/
protected boolean allowPassthrough(String mimeType) {
return audioTrack.isPassthroughSupported(mimeType);
}
@Override
protected void configureCodec(MediaCodec codec, boolean codecIsAdaptive,
android.media.MediaFormat format, android.media.MediaCrypto crypto) {
String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
format.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW);
codec.configure(format, null, crypto, 0);
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
passthroughMediaFormat = format;
} else {
codec.configure(format, null, crypto, 0);
passthroughMediaFormat = null;
}
}
@Override
protected MediaClock getMediaClock() {
return this;
}
@Override
protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder);
// If the input format is anything other than PCM then we assume that the audio decoder will
// output 16-bit PCM.
pcmEncoding = MimeTypes.AUDIO_RAW.equals(holder.format.mimeType) ? holder.format.pcmEncoding
: C.ENCODING_PCM_16BIT;
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough
? passthroughMediaFormat.getString(android.media.MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW;
android.media.MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(android.media.MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding);
}
/**
* Invoked when the audio session id becomes known. Once the id is known it will not change
* (and hence this method will not be invoked 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).
*
* @param audioSessionId The audio session id.
*/
protected void onAudioSessionId(int audioSessionId) {
// Do nothing.
}
@Override
protected void onStarted() {
super.onStarted();
audioTrack.play();
}
@Override
protected void onStopped() {
audioTrack.pause();
super.onStopped();
}
@Override
protected boolean isEnded() {
return super.isEnded() && !audioTrack.hasPendingData();
}
@Override
protected boolean isReady() {
return audioTrack.hasPendingData() || super.isReady();
}
@Override
public long getPositionUs() {
long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs
: Math.max(currentPositionUs, newCurrentPositionUs);
allowPositionDiscontinuity = false;
}
return currentPositionUs;
}
@Override
protected void onDisabled() throws ExoPlaybackException {
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();
} finally {
super.onDisabled();
}
}
@Override
protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
super.onDiscontinuity(positionUs);
audioTrack.reset();
currentPositionUs = positionUs;
allowPositionDiscontinuity = true;
}
@Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip)
throws ExoPlaybackException {
if (passthroughEnabled && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// Discard output buffers from the passthrough (raw) decoder containing codec specific data.
codec.releaseOutputBuffer(bufferIndex, false);
return true;
}
if (shouldSkip) {
codec.releaseOutputBuffer(bufferIndex, false);
codecCounters.skippedOutputBufferCount++;
audioTrack.handleDiscontinuity();
return true;
}
if (!audioTrack.isInitialized()) {
// Initialize the AudioTrack now.
try {
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
audioTrack.initialize(audioSessionId);
} else {
audioSessionId = audioTrack.initialize();
onAudioSessionId(audioSessionId);
}
audioTrackHasData = false;
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw new ExoPlaybackException(e);
}
if (getState() == TrackRenderer.STATE_STARTED) {
audioTrack.play();
}
} else {
// Check for AudioTrack underrun.
boolean audioTrackHadData = audioTrackHasData;
audioTrackHasData = audioTrack.hasPendingData();
if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
long bufferSizeUs = audioTrack.getBufferSizeUs();
long bufferSizeMs = bufferSizeUs == C.UNKNOWN_TIME_US ? -1 : bufferSizeUs / 1000;
notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs);
}
}
int handleBufferResult;
try {
handleBufferResult = audioTrack.handleBuffer(
buffer, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
} catch (AudioTrack.WriteException e) {
notifyAudioTrackWriteError(e);
throw new ExoPlaybackException(e);
}
// 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);
codecCounters.renderedOutputBufferCount++;
return true;
}
return false;
}
@Override
protected void onOutputStreamEnded() {
audioTrack.handleEndOfStream();
}
protected void handleAudioTrackDiscontinuity() {
// Do nothing
}
@Override
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
switch (messageType) {
case MSG_SET_VOLUME:
audioTrack.setVolume((Float) message);
break;
case MSG_SET_PLAYBACK_PARAMS:
audioTrack.setPlaybackParams((PlaybackParams) message);
break;
default:
super.handleMessage(messageType, message);
break;
}
}
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackInitializationError(e);
}
});
}
}
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackWriteError(e);
}
});
}
}
private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs,
final long elapsedSinceLastFeedMs) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
}
});
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
import android.media.MediaCodec;
/**
* Selector of {@link MediaCodec} instances.
*/
public interface MediaCodecSelector {
/**
* Default implementation of {@link MediaCodecSelector}.
*/
MediaCodecSelector DEFAULT = new MediaCodecSelector() {
@Override
public DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder)
throws DecoderQueryException {
return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder);
}
@Override
public DecoderInfo getPassthroughDecoderInfo() throws DecoderQueryException {
return MediaCodecUtil.getPassthroughDecoderInfo();
}
};
/**
* Selects a decoder to instantiate for a given mime type.
*
* @param mimeType The mime type for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required.
* @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder)
throws DecoderQueryException;
/**
* Selects a decoder to instantiate for audio passthrough.
*
* @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
DecoderInfo getPassthroughDecoderInfo() throws DecoderQueryException;
}

View File

@ -0,0 +1,501 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.util.Assertions;
import org.telegram.messenger.exoplayer.util.MimeTypes;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A utility class for querying the available codecs.
*/
@TargetApi(16)
public final class MediaCodecUtil {
/**
* Thrown when an error occurs querying the device for its underlying media capabilities.
* <p>
* Such failures are not expected in normal operation and are normally temporary (e.g. if the
* mediaserver process has crashed and is yet to restart).
*/
public static class DecoderQueryException extends IOException {
private DecoderQueryException(Throwable cause) {
super("Failed to query underlying media codecs", cause);
}
}
private static final String TAG = "MediaCodecUtil";
private static final DecoderInfo PASSTHROUGH_DECODER_INFO =
new DecoderInfo("OMX.google.raw.decoder", null);
private static final Map<CodecKey, List<DecoderInfo>> decoderInfosCache = new HashMap<>();
// Lazily initialized.
private static int maxH264DecodableFrameSize = -1;
private MediaCodecUtil() {}
/**
* Optional call to warm the codec cache for a given mime type.
* <p>
* Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
*/
public static void warmCodec(String mimeType, boolean secure) {
try {
getDecoderInfos(mimeType, secure);
} catch (DecoderQueryException e) {
// Codec warming is best effort, so we can swallow the exception.
Log.e(TAG, "Codec warming failed", e);
}
}
/**
* Gets information about a decoder suitable for audio passthrough.
**
* @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists.
*/
public static DecoderInfo getPassthroughDecoderInfo() {
// TODO: Return null if the raw decoder doesn't exist.
return PASSTHROUGH_DECODER_INFO;
}
/**
* Get information about the preferred decoder for a given mime type.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists.
*/
public static DecoderInfo getDecoderInfo(String mimeType, boolean secure)
throws DecoderQueryException {
List<DecoderInfo> decoderInfos = getDecoderInfos(mimeType, secure);
return decoderInfos.isEmpty() ? null : decoderInfos.get(0);
}
/**
* Returns all @{link DecoderInfo}s for a given mime type, in the order given by
* {@link MediaCodecList}.
*
* @param mimeType The mime type.
* @param secure Whether the decoders are required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @return A list of all @{link DecoderInfo}s for the given mime type. May be empty if no suitable
* decoders exist.
*/
public static synchronized List<DecoderInfo> getDecoderInfos(String mimeType, boolean secure)
throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure);
List<DecoderInfo> decoderInfos = decoderInfosCache.get(key);
if (decoderInfos != null) {
return decoderInfos;
}
MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16();
decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
// Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the
// legacy path. We also try this path on API levels 22 and 23 as a defensive measure.
mediaCodecList = new MediaCodecListCompatV16();
decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
if (!decoderInfos.isEmpty()) {
Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType
+ ". Assuming: " + decoderInfos.get(0).name);
}
}
decoderInfos = Collections.unmodifiableList(decoderInfos);
decoderInfosCache.put(key, decoderInfos);
return decoderInfos;
}
private static List<DecoderInfo> getDecoderInfosInternal(
CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException {
try {
List<DecoderInfo> decoderInfos = new ArrayList<>();
String mimeType = key.mimeType;
int numberOfCodecs = mediaCodecList.getCodecCount();
boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit();
// Note: MediaCodecList is sorted by the framework such that the best decoders come first.
for (int i = 0; i < numberOfCodecs; i++) {
MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i);
String codecName = codecInfo.getName();
if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit)) {
for (String supportedType : codecInfo.getSupportedTypes()) {
if (supportedType.equalsIgnoreCase(mimeType)) {
try {
CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType);
boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities);
if ((secureDecodersExplicit && key.secure == secure)
|| (!secureDecodersExplicit && !key.secure)) {
decoderInfos.add(new DecoderInfo(codecName, capabilities));
} else if (!secureDecodersExplicit && secure) {
decoderInfos.add(new DecoderInfo(codecName + ".secure", capabilities));
// It only makes sense to have one synthesized secure decoder, return immediately.
return decoderInfos;
}
} catch (Exception e) {
if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) {
// Suppress error querying secondary codec capabilities up to API level 23.
Log.e(TAG, "Skipping codec " + codecName + " (failed to query capabilities)");
} else {
throw e;
}
}
}
}
}
}
return decoderInfos;
} catch (Exception e) {
// If the underlying mediaserver is in a bad state, we may catch an IllegalStateException
// or an IllegalArgumentException here.
throw new DecoderQueryException(e);
}
}
/**
* Returns whether the specified codec is usable for decoding on the current device.
*/
private static boolean isCodecUsableDecoder(MediaCodecInfo info, String name,
boolean secureDecodersExplicit) {
if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) {
return false;
}
// Work around broken audio decoders.
if (Util.SDK_INT < 21
&& ("CIPAACDecoder".equals(name))
|| "CIPMP3Decoder".equals(name)
|| "CIPVorbisDecoder".equals(name)
|| "AACDecoder".equals(name)
|| "MP3Decoder".equals(name)) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/398
if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/1528
if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name)
&& "a70".equals(Util.DEVICE)) {
return false;
}
// Work around an issue where creating a particular MP3 decoder on some devices on platform API
// version 16 crashes mediaserver.
if (Util.SDK_INT == 16
&& "OMX.qcom.audio.decoder.mp3".equals(name)
&& ("dlxu".equals(Util.DEVICE) // HTC Butterfly
|| "protou".equals(Util.DEVICE) // HTC Desire X
|| "C6602".equals(Util.DEVICE) // Sony Xperia Z
|| "C6603".equals(Util.DEVICE)
|| "C6606".equals(Util.DEVICE)
|| "C6616".equals(Util.DEVICE)
|| "L36h".equals(Util.DEVICE)
|| "SO-02E".equals(Util.DEVICE))) {
return false;
}
// Work around an issue where large timestamps are not propagated correctly.
if (Util.SDK_INT == 16
&& "OMX.qcom.audio.decoder.aac".equals(name)
&& ("C1504".equals(Util.DEVICE) // Sony Xperia E
|| "C1505".equals(Util.DEVICE)
|| "C1604".equals(Util.DEVICE) // Sony Xperia E dual
|| "C1605".equals(Util.DEVICE))) {
return false;
}
// Work around an issue where the VP8 decoder on Samsung Galaxy S3/S4 Mini does not render
// video.
if (Util.SDK_INT <= 19 && Util.DEVICE != null
&& (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano"))
&& "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) {
return false;
}
return true;
}
/**
* Tests whether the device advertises it can decode video of a given type at a specified width
* and height.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @param width Width in pixels.
* @param height Height in pixels.
* @return Whether the decoder advertises support of the given size.
*/
@TargetApi(21)
public static boolean isSizeSupportedV21(String mimeType, boolean secure, int width,
int height) throws DecoderQueryException {
Assertions.checkState(Util.SDK_INT >= 21);
MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure);
return videoCapabilities != null && videoCapabilities.isSizeSupported(width, height);
}
/**
* Tests whether the device advertises it can decode video of a given type at a specified
* width, height, and frame rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required.
* @param width Width in pixels.
* @param height Height in pixels.
* @param frameRate Frame rate in frames per second.
* @return Whether the decoder advertises support of the given size and frame rate.
*/
@TargetApi(21)
public static boolean isSizeAndRateSupportedV21(String mimeType, boolean secure,
int width, int height, double frameRate) throws DecoderQueryException {
Assertions.checkState(Util.SDK_INT >= 21);
MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure);
return videoCapabilities != null
&& videoCapabilities.areSizeAndRateSupported(width, height, frameRate);
}
/**
* @param profile An AVC profile constant from {@link CodecProfileLevel}.
* @param level An AVC profile level from {@link CodecProfileLevel}.
* @return Whether the specified profile is supported at the specified level.
* @deprecated Prefer {@link #getDecoderInfos(String, boolean)} for new code.
*/
@Deprecated
public static boolean isH264ProfileSupported(int profile, int level)
throws DecoderQueryException {
DecoderInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false);
if (decoderInfo == null) {
return false;
}
for (CodecProfileLevel profileLevel : decoderInfo.capabilities.profileLevels) {
if (profileLevel.profile == profile && profileLevel.level >= level) {
return true;
}
}
return false;
}
/**
* @return the maximum frame size for an H264 stream that can be decoded on the device.
*/
public static int maxH264DecodableFrameSize() throws DecoderQueryException {
if (maxH264DecodableFrameSize == -1) {
int result = 0;
DecoderInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false);
if (decoderInfo != null) {
for (CodecProfileLevel profileLevel : decoderInfo.capabilities.profileLevels) {
result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result);
}
// We assume support for at least 360p.
result = Math.max(result, 480 * 360);
}
maxH264DecodableFrameSize = result;
}
return maxH264DecodableFrameSize;
}
@TargetApi(21)
private static MediaCodecInfo.VideoCapabilities getVideoCapabilitiesV21(String mimeType,
boolean secure) throws DecoderQueryException {
DecoderInfo decoderInfo = getDecoderInfo(mimeType, secure);
return decoderInfo == null ? null : decoderInfo.capabilities.getVideoCapabilities();
}
/**
* Conversion values taken from ISO 14496-10 Table A-1.
*
* @param avcLevel one of CodecProfileLevel.AVCLevel* constants.
* @return maximum frame size that can be decoded by a decoder with the specified avc level
* (or {@code -1} if the level is not recognized)
*/
private static int avcLevelToMaxFrameSize(int avcLevel) {
switch (avcLevel) {
case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16;
case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16;
case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16;
case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16;
case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16;
case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16;
case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16;
case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16;
case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16;
case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16;
case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16;
case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16;
case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16;
case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16;
case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16;
default: return -1;
}
}
private interface MediaCodecListCompat {
/**
* The number of codecs in the list.
*/
int getCodecCount();
/**
* The info at the specified index in the list.
*
* @param index The index.
*/
MediaCodecInfo getCodecInfoAt(int index);
/**
* @return Returns whether secure decoders are explicitly listed, if present.
*/
boolean secureDecodersExplicit();
/**
* Whether secure playback is supported for the given {@link CodecCapabilities}, which should
* have been obtained from a {@link MediaCodecInfo} obtained from this list.
*/
boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities);
}
@TargetApi(21)
private static final class MediaCodecListCompatV21 implements MediaCodecListCompat {
private final int codecKind;
private MediaCodecInfo[] mediaCodecInfos;
public MediaCodecListCompatV21(boolean includeSecure) {
codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
}
@Override
public int getCodecCount() {
ensureMediaCodecInfosInitialized();
return mediaCodecInfos.length;
}
@Override
public MediaCodecInfo getCodecInfoAt(int index) {
ensureMediaCodecInfosInitialized();
return mediaCodecInfos[index];
}
@Override
public boolean secureDecodersExplicit() {
return true;
}
@Override
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
}
private void ensureMediaCodecInfosInitialized() {
if (mediaCodecInfos == null) {
mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos();
}
}
}
@SuppressWarnings("deprecation")
private static final class MediaCodecListCompatV16 implements MediaCodecListCompat {
@Override
public int getCodecCount() {
return MediaCodecList.getCodecCount();
}
@Override
public MediaCodecInfo getCodecInfoAt(int index) {
return MediaCodecList.getCodecInfoAt(index);
}
@Override
public boolean secureDecodersExplicit() {
return false;
}
@Override
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) {
// Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure
// H264 decoder exists.
return MimeTypes.VIDEO_H264.equals(mimeType);
}
}
private static final class CodecKey {
public final String mimeType;
public final boolean secure;
public CodecKey(String mimeType, boolean secure) {
this.mimeType = mimeType;
this.secure = secure;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode());
result = prime * result + (secure ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || obj.getClass() != CodecKey.class) {
return false;
}
CodecKey other = (CodecKey) obj;
return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure;
}
}
}

View File

@ -0,0 +1,611 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
import org.telegram.messenger.exoplayer.drm.DrmSessionManager;
import org.telegram.messenger.exoplayer.util.MimeTypes;
import org.telegram.messenger.exoplayer.util.TraceUtil;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.os.Handler;
import android.os.SystemClock;
import android.view.Surface;
import android.view.TextureView;
import java.nio.ByteBuffer;
/**
* Decodes and renders video using {@link MediaCodec}.
*/
@TargetApi(16)
public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
/**
* Interface definition for a callback to be notified of {@link MediaCodecVideoTrackRenderer}
* events.
*/
public interface EventListener extends MediaCodecTrackRenderer.EventListener {
/**
* Invoked to report the number of frames dropped by the renderer. Dropped frames are reported
* whenever the renderer is stopped having dropped frames, and optionally, whenever the count
* reaches a specified threshold whilst the renderer is started.
*
* @param count The number of dropped frames.
* @param elapsed The duration in milliseconds over which the frames were dropped. This
* duration is timed from when the renderer was started or from when dropped frames were
* last reported (whichever was more recent), and not from when the first of the reported
* drops occurred.
*/
void onDroppedFrames(int count, long elapsed);
/**
* Invoked each time there's a change in the size of the video being rendered.
*
* @param width The video width in pixels.
* @param height The video height in pixels.
* @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise
* rotation in degrees that the application should apply for the video for it to be rendered
* in the correct orientation. This value will always be zero on API levels 21 and above,
* since the renderer will apply all necessary rotations internally. On earlier API levels
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
* rotated videos can safely ignore this parameter.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case
* of square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content.
*/
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio);
/**
* Invoked when a frame is rendered to a surface for the first time following that surface
* having been set as the target for the renderer.
*
* @param surface The surface to which a first frame has been rendered.
*/
void onDrawnToSurface(Surface surface);
}
// TODO: Use MediaFormat constants if these get exposed through the API. See
// [Internal: b/14127601].
private static final String KEY_CROP_LEFT = "crop-left";
private static final String KEY_CROP_RIGHT = "crop-right";
private static final String KEY_CROP_BOTTOM = "crop-bottom";
private static final String KEY_CROP_TOP = "crop-top";
/**
* The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
* should be the target {@link Surface}, or null.
*/
public static final int MSG_SET_SURFACE = 1;
private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper;
private final EventListener eventListener;
private final long allowedJoiningTimeUs;
private final int videoScalingMode;
private final int maxDroppedFrameCountToNotify;
private Surface surface;
private boolean reportedDrawnToSurface;
private boolean renderedFirstFrame;
private long joiningDeadlineUs;
private long droppedFrameAccumulationStartTimeMs;
private int droppedFrameCount;
private int consecutiveDroppedFrameCount;
private int pendingRotationDegrees;
private float pendingPixelWidthHeightRatio;
private int currentWidth;
private int currentHeight;
private int currentUnappliedRotationDegrees;
private float currentPixelWidthHeightRatio;
private int lastReportedWidth;
private int lastReportedHeight;
private int lastReportedUnappliedRotationDegrees;
private float lastReportedPixelWidthHeightRatio;
/**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}.
*/
public MediaCodecVideoTrackRenderer(Context context, SampleSource source,
MediaCodecSelector mediaCodecSelector, int videoScalingMode) {
this(context, source, mediaCodecSelector, videoScalingMode, 0);
}
/**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
*/
public MediaCodecVideoTrackRenderer(Context context, SampleSource source,
MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs) {
this(context, source, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, null,
-1);
}
/**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @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 maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}.
*/
public MediaCodecVideoTrackRenderer(Context context, SampleSource source,
MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs,
Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) {
this(context, source, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, false,
eventHandler, eventListener, maxDroppedFrameCountToNotify);
}
/**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples.
* @param mediaCodecSelector A decoder selector.
* @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisision. 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 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 maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}.
*/
public MediaCodecVideoTrackRenderer(Context context, SampleSource source,
MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs,
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys,
Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) {
super(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler,
eventListener);
this.frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
this.videoScalingMode = videoScalingMode;
this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000;
this.eventListener = eventListener;
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
joiningDeadlineUs = -1;
currentWidth = -1;
currentHeight = -1;
currentPixelWidthHeightRatio = -1;
pendingPixelWidthHeightRatio = -1;
lastReportedWidth = -1;
lastReportedHeight = -1;
lastReportedPixelWidthHeightRatio = -1;
}
@Override
protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
return MimeTypes.isVideo(mimeType) && (MimeTypes.VIDEO_UNKNOWN.equals(mimeType)
|| mediaCodecSelector.getDecoderInfo(mimeType, false) != null);
}
@Override
protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
}
frameReleaseTimeHelper.enable();
}
@Override
protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
super.onDiscontinuity(positionUs);
renderedFirstFrame = false;
consecutiveDroppedFrameCount = 0;
joiningDeadlineUs = -1;
}
@Override
protected boolean isReady() {
if (super.isReady() && (renderedFirstFrame || !codecInitialized()
|| getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL)) {
// Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineUs = -1;
return true;
} else if (joiningDeadlineUs == -1) {
// Not joining.
return false;
} else if (SystemClock.elapsedRealtime() * 1000 < joiningDeadlineUs) {
// Joining and still within the joining deadline.
return true;
} else {
// The joining deadline has been exceeded. Give up and clear the deadline.
joiningDeadlineUs = -1;
return false;
}
}
@Override
protected void onStarted() {
super.onStarted();
droppedFrameCount = 0;
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
}
@Override
protected void onStopped() {
joiningDeadlineUs = -1;
maybeNotifyDroppedFrameCount();
super.onStopped();
}
@Override
protected void onDisabled() throws ExoPlaybackException {
currentWidth = -1;
currentHeight = -1;
currentPixelWidthHeightRatio = -1;
pendingPixelWidthHeightRatio = -1;
lastReportedWidth = -1;
lastReportedHeight = -1;
lastReportedPixelWidthHeightRatio = -1;
frameReleaseTimeHelper.disable();
super.onDisabled();
}
@Override
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
if (messageType == MSG_SET_SURFACE) {
setSurface((Surface) message);
} else {
super.handleMessage(messageType, message);
}
}
/**
* @param surface The surface to set.
* @throws ExoPlaybackException
*/
private void setSurface(Surface surface) throws ExoPlaybackException {
if (this.surface == surface) {
return;
}
this.surface = surface;
this.reportedDrawnToSurface = false;
int state = getState();
if (state == TrackRenderer.STATE_ENABLED || state == TrackRenderer.STATE_STARTED) {
releaseCodec();
maybeInitCodec();
}
}
@Override
protected boolean shouldInitCodec() {
return super.shouldInitCodec() && surface != null && surface.isValid();
}
// Override configureCodec to provide the surface.
@Override
protected void configureCodec(MediaCodec codec, boolean codecIsAdaptive,
android.media.MediaFormat format, MediaCrypto crypto) {
maybeSetMaxInputSize(format, codecIsAdaptive);
codec.configure(format, surface, crypto, 0);
}
@Override
protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder);
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1
: holder.format.pixelWidthHeightRatio;
pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0
: holder.format.rotationDegrees;
}
/**
* @return True if the first frame has been rendered (playback has not necessarily begun).
*/
protected final boolean haveRenderedFirstFrame() {
return renderedFirstFrame;
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
&& outputFormat.containsKey(KEY_CROP_TOP);
currentWidth = hasCrop
? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1
: outputFormat.getInteger(android.media.MediaFormat.KEY_WIDTH);
currentHeight = hasCrop
? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
: outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT);
currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio;
if (Util.SDK_INT >= 21) {
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
// Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need
// to flip the width, height and pixel aspect ratio to reflect the rotation that was applied.
if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) {
int rotatedHeight = currentWidth;
currentWidth = currentHeight;
currentHeight = rotatedHeight;
currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio;
}
} else {
// On API level 20 and below the decoder does not apply the rotation.
currentUnappliedRotationDegrees = pendingRotationDegrees;
}
// Must be applied each time the output format changes.
codec.setVideoScalingMode(videoScalingMode);
}
@Override
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
MediaFormat oldFormat, MediaFormat newFormat) {
return newFormat.mimeType.equals(oldFormat.mimeType)
&& (codecIsAdaptive
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
}
@Override
protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec,
ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) {
if (shouldSkip) {
skipOutputBuffer(codec, bufferIndex);
consecutiveDroppedFrameCount = 0;
return true;
}
if (!renderedFirstFrame) {
if (Util.SDK_INT >= 21) {
renderOutputBufferV21(codec, bufferIndex, System.nanoTime());
} else {
renderOutputBuffer(codec, bufferIndex);
}
consecutiveDroppedFrameCount = 0;
return true;
}
if (getState() != TrackRenderer.STATE_STARTED) {
return false;
}
// Compute how many microseconds it is until the buffer's presentation time.
long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;
// Compute the buffer's desired release time in nanoseconds.
long systemTimeNs = System.nanoTime();
long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);
// Apply a timestamp adjustment, if there is one.
long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(
bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex);
return true;
}
if (Util.SDK_INT >= 21) {
// Let the underlying framework time the release.
if (earlyUs < 50000) {
renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs);
consecutiveDroppedFrameCount = 0;
return true;
}
} else {
// We need to time the release ourselves.
if (earlyUs < 30000) {
if (earlyUs > 11000) {
// We're a little too early to render the frame. Sleep until the frame can be rendered.
// Note: The 11ms threshold was chosen fairly arbitrarily.
try {
// Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms.
Thread.sleep((earlyUs - 10000) / 1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
renderOutputBuffer(codec, bufferIndex);
consecutiveDroppedFrameCount = 0;
return true;
}
}
// We're either not playing, or it's not time to render the frame yet.
return false;
}
protected void skipOutputBuffer(MediaCodec codec, int bufferIndex) {
TraceUtil.beginSection("skipVideoBuffer");
codec.releaseOutputBuffer(bufferIndex, false);
TraceUtil.endSection();
codecCounters.skippedOutputBufferCount++;
}
protected void dropOutputBuffer(MediaCodec codec, int bufferIndex) {
TraceUtil.beginSection("dropVideoBuffer");
codec.releaseOutputBuffer(bufferIndex, false);
TraceUtil.endSection();
codecCounters.droppedOutputBufferCount++;
droppedFrameCount++;
consecutiveDroppedFrameCount++;
codecCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount,
codecCounters.maxConsecutiveDroppedOutputBufferCount);
if (droppedFrameCount == maxDroppedFrameCountToNotify) {
maybeNotifyDroppedFrameCount();
}
}
protected void renderOutputBuffer(MediaCodec codec, int bufferIndex) {
maybeNotifyVideoSizeChanged();
TraceUtil.beginSection("releaseOutputBuffer");
codec.releaseOutputBuffer(bufferIndex, true);
TraceUtil.endSection();
codecCounters.renderedOutputBufferCount++;
renderedFirstFrame = true;
maybeNotifyDrawnToSurface();
}
@TargetApi(21)
protected void renderOutputBufferV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) {
maybeNotifyVideoSizeChanged();
TraceUtil.beginSection("releaseOutputBuffer");
codec.releaseOutputBuffer(bufferIndex, releaseTimeNs);
TraceUtil.endSection();
codecCounters.renderedOutputBufferCount++;
renderedFirstFrame = true;
maybeNotifyDrawnToSurface();
}
@SuppressLint("InlinedApi")
private void maybeSetMaxInputSize(android.media.MediaFormat format, boolean codecIsAdaptive) {
if (format.containsKey(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)) {
// Already set. The source of the format may know better, so do nothing.
return;
}
int maxHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT);
if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) {
maxHeight = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_HEIGHT));
}
int maxWidth = format.getInteger(android.media.MediaFormat.KEY_WIDTH);
if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_WIDTH)) {
maxWidth = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_WIDTH));
}
int maxPixels;
int minCompressionRatio;
switch (format.getString(android.media.MediaFormat.KEY_MIME)) {
case MimeTypes.VIDEO_H263:
case MimeTypes.VIDEO_MP4V:
maxPixels = maxWidth * maxHeight;
minCompressionRatio = 2;
break;
case MimeTypes.VIDEO_H264:
if ("BRAVIA 4K 2015".equals(Util.MODEL)) {
// The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video
// maximum input size, so use the default value.
return;
}
// Round up width/height to an integer number of macroblocks.
maxPixels = ((maxWidth + 15) / 16) * ((maxHeight + 15) / 16) * 16 * 16;
minCompressionRatio = 2;
break;
case MimeTypes.VIDEO_VP8:
// VPX does not specify a ratio so use the values from the platform's SoftVPX.cpp.
maxPixels = maxWidth * maxHeight;
minCompressionRatio = 2;
break;
case MimeTypes.VIDEO_H265:
case MimeTypes.VIDEO_VP9:
maxPixels = maxWidth * maxHeight;
minCompressionRatio = 4;
break;
default:
// Leave the default max input size.
return;
}
// Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames.
int maxInputSize = (maxPixels * 3) / (2 * minCompressionRatio);
format.setInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
}
private void maybeNotifyVideoSizeChanged() {
if (eventHandler == null || eventListener == null
|| (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight
&& lastReportedUnappliedRotationDegrees == currentUnappliedRotationDegrees
&& lastReportedPixelWidthHeightRatio == currentPixelWidthHeightRatio)) {
return;
}
// Make final copies to ensure the runnable reports the correct values.
final int currentWidth = this.currentWidth;
final int currentHeight = this.currentHeight;
final int currentUnappliedRotationDegrees = this.currentUnappliedRotationDegrees;
final float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onVideoSizeChanged(currentWidth, currentHeight,
currentUnappliedRotationDegrees, currentPixelWidthHeightRatio);
}
});
// Update the last reported values.
lastReportedWidth = currentWidth;
lastReportedHeight = currentHeight;
lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees;
lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
}
private void maybeNotifyDrawnToSurface() {
if (eventHandler == null || eventListener == null || reportedDrawnToSurface) {
return;
}
// Make a final copy to ensure the runnable reports the correct surface.
final Surface surface = this.surface;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDrawnToSurface(surface);
}
});
// Record that we have reported that the surface has been drawn to.
reportedDrawnToSurface = true;
}
private void maybeNotifyDroppedFrameCount() {
if (eventHandler == null || eventListener == null || droppedFrameCount == 0) {
return;
}
long now = SystemClock.elapsedRealtime();
// Make final copies to ensure the runnable reports the correct values.
final int countToNotify = droppedFrameCount;
final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs;
eventHandler.post(new Runnable() {
@Override
public void run() {
eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
}
});
// Reset the dropped frame tracking.
droppedFrameCount = 0;
droppedFrameAccumulationStartTimeMs = now;
}
}

View File

@ -0,0 +1,502 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.util.Assertions;
import org.telegram.messenger.exoplayer.util.MimeTypes;
import org.telegram.messenger.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Defines the format of an elementary media stream.
*/
public final class MediaFormat implements Parcelable {
public static final int NO_VALUE = -1;
/**
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples.
*/
public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE;
/**
* The identifier for the track represented by the format, or null if unknown or not applicable.
*/
public final String trackId;
/**
* The mime type of the format.
*/
public final String mimeType;
/**
* The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int bitrate;
/**
* The maximum size of a buffer of data (typically one sample) in the format, or {@link #NO_VALUE}
* if unknown or not applicable.
*/
public final int maxInputSize;
/**
* The duration in microseconds, or {@link C#UNKNOWN_TIME_US} if the duration is unknown, or
* {@link C#MATCH_LONGEST_US} if the duration should match the duration of the longest track whose
* duration is known.
*/
public final long durationUs;
/**
* Initialization data that must be provided to the decoder. Will not be null, but may be empty
* if initialization data is not required.
*/
public final List<byte[]> initializationData;
/**
* Whether the format represents an adaptive track, meaning that the format of the actual media
* data may change (e.g. to adapt to network conditions).
*/
public final boolean adaptive;
// Video specific.
/**
* The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int width;
/**
* The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int height;
/**
* For formats that belong to an adaptive video track (either describing the track, or describing
* a specific format within it), this is the maximum width of the video in pixels that will be
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
*/
public final int maxWidth;
/**
* For formats that belong to an adaptive video track (either describing the track, or describing
* a specific format within it), this is the maximum height of the video in pixels that will be
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
*/
public final int maxHeight;
/**
* The clockwise rotation that should be applied to the video for it to be rendered in the correct
* orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are
* supported.
*/
public final int rotationDegrees;
/**
* The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not
* applicable.
*/
public final float pixelWidthHeightRatio;
// Audio specific.
/**
* The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int channelCount;
/**
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int sampleRate;
/**
* The encoding for PCM audio streams. If {@link #mimeType} is {@link MimeTypes#AUDIO_RAW} then
* one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT}
* and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for other media types.
*/
public final int pcmEncoding;
/**
* The number of samples to trim from the start of the decoded audio stream.
*/
public final int encoderDelay;
/**
* The number of samples to trim from the end of the decoded audio stream.
*/
public final int encoderPadding;
// Text specific.
/**
* The language of the track, or null if unknown or not applicable.
*/
public final String language;
/**
* For samples that contain subsamples, this is an offset that should be added to subsample
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
* relative to the timestamps of their parent samples.
*/
public final long subsampleOffsetUs;
// Lazy-initialized hashcode and framework media format.
private int hashCode;
private android.media.MediaFormat frameworkMediaFormat;
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, long durationUs, int width, int height, List<byte[]> initializationData) {
return createVideoFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
initializationData, NO_VALUE, NO_VALUE);
}
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, long durationUs, int width, int height, List<byte[]> initializationData,
int rotationDegrees, float pixelWidthHeightRatio) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
initializationData, false, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE);
}
public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, long durationUs, int channelCount, int sampleRate,
List<byte[]> initializationData, String language) {
return createAudioFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, channelCount,
sampleRate, initializationData, language, NO_VALUE);
}
public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, long durationUs, int channelCount, int sampleRate,
List<byte[]> initializationData, String language, int pcmEncoding) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
initializationData, false, NO_VALUE, NO_VALUE, pcmEncoding, NO_VALUE, NO_VALUE);
}
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
long durationUs, String language) {
return createTextFormat(trackId, mimeType, bitrate, durationUs, language,
OFFSET_SAMPLE_RELATIVE);
}
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
long durationUs, String language, long subsampleOffsetUs) {
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE);
}
public static MediaFormat createImageFormat(String trackId, String mimeType, int bitrate,
long durationUs, List<byte[]> initializationData, String language) {
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, OFFSET_SAMPLE_RELATIVE,
initializationData, false, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE);
}
public static MediaFormat createFormatForMimeType(String trackId, String mimeType, int bitrate,
long durationUs) {
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE);
}
public static MediaFormat createId3Format() {
return createFormatForMimeType(null, MimeTypes.APPLICATION_ID3, MediaFormat.NO_VALUE,
C.UNKNOWN_TIME_US);
}
/* package */ MediaFormat(Parcel in) {
trackId = in.readString();
mimeType = in.readString();
bitrate = in.readInt();
maxInputSize = in.readInt();
durationUs = in.readLong();
width = in.readInt();
height = in.readInt();
rotationDegrees = in.readInt();
pixelWidthHeightRatio = in.readFloat();
channelCount = in.readInt();
sampleRate = in.readInt();
language = in.readString();
subsampleOffsetUs = in.readLong();
initializationData = new ArrayList<>();
in.readList(initializationData, null);
adaptive = in.readInt() == 1;
maxWidth = in.readInt();
maxHeight = in.readInt();
pcmEncoding = in.readInt();
encoderDelay = in.readInt();
encoderPadding = in.readInt();
}
/* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize,
long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio,
int channelCount, int sampleRate, String language, long subsampleOffsetUs,
List<byte[]> initializationData, boolean adaptive, int maxWidth, int maxHeight,
int pcmEncoding, int encoderDelay, int encoderPadding) {
this.trackId = trackId;
this.mimeType = Assertions.checkNotEmpty(mimeType);
this.bitrate = bitrate;
this.maxInputSize = maxInputSize;
this.durationUs = durationUs;
this.width = width;
this.height = height;
this.rotationDegrees = rotationDegrees;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.language = language;
this.subsampleOffsetUs = subsampleOffsetUs;
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
: initializationData;
this.adaptive = adaptive;
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
this.pcmEncoding = pcmEncoding;
this.encoderDelay = encoderDelay;
this.encoderPadding = encoderPadding;
}
public MediaFormat copyWithMaxInputSize(int maxInputSize) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyWithDurationUs(long durationUs) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyWithLanguage(String language) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyWithFixedTrackInfo(String trackId, int bitrate, int width, int height,
String language) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, NO_VALUE, NO_VALUE, pcmEncoding,
encoderDelay, encoderPadding);
}
public MediaFormat copyAsAdaptive(String trackId) {
return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, durationUs, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth,
maxHeight, NO_VALUE, NO_VALUE, NO_VALUE);
}
public MediaFormat copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding,
encoderDelay, encoderPadding);
}
/**
* @return A {@link MediaFormat} representation of this format.
*/
@SuppressLint("InlinedApi")
@TargetApi(16)
public final android.media.MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
android.media.MediaFormat format = new android.media.MediaFormat();
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, android.media.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)));
}
if (durationUs != C.UNKNOWN_TIME_US) {
format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs);
}
frameworkMediaFormat = format;
}
return frameworkMediaFormat;
}
/**
* Sets the framework format returned by {@link #getFrameworkMediaFormatV16()}.
*
* @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated.
* @param format The framework format.
*/
@Deprecated
@TargetApi(16)
/* package */ final void setFrameworkFormatV16(android.media.MediaFormat format) {
frameworkMediaFormat = format;
}
@Override
public String toString() {
return "MediaFormat(" + trackId + ", " + mimeType + ", " + bitrate + ", " + maxInputSize
+ ", " + width + ", " + height + ", " + rotationDegrees + ", " + pixelWidthHeightRatio
+ ", " + channelCount + ", " + sampleRate + ", " + language + ", " + durationUs + ", "
+ adaptive + ", " + maxWidth + ", " + maxHeight + ", " + pcmEncoding + ", " + encoderDelay
+ ", " + encoderPadding + ")";
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + (trackId == null ? 0 : trackId.hashCode());
result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
result = 31 * result + bitrate;
result = 31 * result + maxInputSize;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + rotationDegrees;
result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
result = 31 * result + (int) durationUs;
result = 31 * result + (adaptive ? 1231 : 1237);
result = 31 * result + maxWidth;
result = 31 * result + maxHeight;
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
result = 31 * result + pcmEncoding;
result = 31 * result + encoderDelay;
result = 31 * result + encoderPadding;
result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + (int) subsampleOffsetUs;
for (int i = 0; i < initializationData.size(); i++) {
result = 31 * result + Arrays.hashCode(initializationData.get(i));
}
hashCode = result;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MediaFormat other = (MediaFormat) obj;
if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize
|| durationUs != other.durationUs || width != other.width || height != other.height
|| rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| maxWidth != other.maxWidth || maxHeight != other.maxHeight
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|| pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay
|| encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs
|| !Util.areEqual(trackId, other.trackId) || !Util.areEqual(language, other.language)
|| !Util.areEqual(mimeType, other.mimeType)
|| initializationData.size() != other.initializationData.size()) {
return false;
}
for (int i = 0; i < initializationData.size(); i++) {
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
return false;
}
}
return true;
}
@TargetApi(16)
private static final void maybeSetStringV16(android.media.MediaFormat format, String key,
String value) {
if (value != null) {
format.setString(key, value);
}
}
@TargetApi(16)
private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key,
int value) {
if (value != NO_VALUE) {
format.setInteger(key, value);
}
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(trackId);
dest.writeString(mimeType);
dest.writeInt(bitrate);
dest.writeInt(maxInputSize);
dest.writeLong(durationUs);
dest.writeInt(width);
dest.writeInt(height);
dest.writeInt(rotationDegrees);
dest.writeFloat(pixelWidthHeightRatio);
dest.writeInt(channelCount);
dest.writeInt(sampleRate);
dest.writeString(language);
dest.writeLong(subsampleOffsetUs);
dest.writeList(initializationData);
dest.writeInt(adaptive ? 1 : 0);
dest.writeInt(maxWidth);
dest.writeInt(maxHeight);
dest.writeInt(pcmEncoding);
dest.writeInt(encoderDelay);
dest.writeInt(encoderPadding);
}
public static final Creator<MediaFormat> CREATOR = new Creator<MediaFormat>() {
@Override
public MediaFormat createFromParcel(Parcel in) {
return new MediaFormat(in);
}
@Override
public MediaFormat[] newArray(int size) {
return new MediaFormat[size];
}
};
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2014 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.exoplayer;
import org.telegram.messenger.exoplayer.drm.DrmInitData;
/**
* Holds a {@link MediaFormat} and corresponding drm scheme initialization data.
*/
public final class MediaFormatHolder {
/**
* The format of the media.
*/
public MediaFormat format;
/**
* Initialization data for drm schemes supported by the media. Null if the media is not encrypted.
*/
public DrmInitData drmInitData;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 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.exoplayer;
import java.io.IOException;
/**
* Thrown when an error occurs parsing media data.
*/
public class ParserException extends IOException {
public ParserException() {
super();
}
public ParserException(String message) {
super(message);
}
public ParserException(Throwable cause) {
super(cause);
}
public ParserException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (C) 2014 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.exoplayer;
import java.nio.ByteBuffer;
/**
* Holds sample data and corresponding metadata.
*/
public final class SampleHolder {
/**
* Disallows buffer replacement.
*/
public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0;
/**
* Allows buffer replacement using {@link ByteBuffer#allocate(int)}.
*/
public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1;
/**
* Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}.
*/
public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2;
public final CryptoInfo cryptoInfo;
/**
* A buffer holding the sample data.
*/
public ByteBuffer data;
/**
* The size of the sample in bytes.
*/
public int size;
/**
* Flags that accompany the sample. A combination of {@link C#SAMPLE_FLAG_SYNC},
* {@link C#SAMPLE_FLAG_ENCRYPTED} and {@link C#SAMPLE_FLAG_DECODE_ONLY}.
*/
public int flags;
/**
* The time at which the sample should be presented.
*/
public long timeUs;
private final int bufferReplacementMode;
/**
* @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
* {@link #BUFFER_REPLACEMENT_MODE_DIRECT}.
*/
public SampleHolder(int bufferReplacementMode) {
this.cryptoInfo = new CryptoInfo();
this.bufferReplacementMode = bufferReplacementMode;
}
/**
* Ensures that {@link #data} is large enough to accommodate a write of a given length at its
* current position.
* <p>
* If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is
* insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer}
* whose capacity is sufficient. Data up to the current position is copied to the new buffer.
*
* @param length The length of the write that must be accommodated, in bytes.
* @throws IllegalStateException If there is insufficient capacity to accommodate the write and
* the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}.
*/
public void ensureSpaceForWrite(int length) throws IllegalStateException {
if (data == null) {
data = createReplacementBuffer(length);
return;
}
// Check whether the current buffer is sufficient.
int capacity = data.capacity();
int position = data.position();
int requiredCapacity = position + length;
if (capacity >= requiredCapacity) {
return;
}
// Instantiate a new buffer if possible.
ByteBuffer newData = createReplacementBuffer(requiredCapacity);
// Copy data up to the current position from the old buffer to the new one.
if (position > 0) {
data.position(0);
data.limit(position);
newData.put(data);
}
// Set the new buffer.
data = newData;
}
/**
* Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_ENCRYPTED} set.
*/
public boolean isEncrypted() {
return (flags & C.SAMPLE_FLAG_ENCRYPTED) != 0;
}
/**
* Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_DECODE_ONLY} set.
*/
public boolean isDecodeOnly() {
return (flags & C.SAMPLE_FLAG_DECODE_ONLY) != 0;
}
/**
* Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_SYNC} set.
*/
public boolean isSyncFrame() {
return (flags & C.SAMPLE_FLAG_SYNC) != 0;
}
/**
* Clears {@link #data}. Does nothing if {@link #data} is null.
*/
public void clearData() {
if (data != null) {
data.clear();
}
}
private ByteBuffer createReplacementBuffer(int requiredCapacity) {
if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) {
return ByteBuffer.allocate(requiredCapacity);
} else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) {
return ByteBuffer.allocateDirect(requiredCapacity);
} else {
int currentCapacity = data == null ? 0 : data.capacity();
throw new IllegalStateException("Buffer too small (" + currentCapacity + " < "
+ requiredCapacity + ")");
}
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (C) 2014 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.exoplayer;
import java.io.IOException;
/**
* A source of media samples.
* <p>
* A {@link SampleSource} may expose one or multiple tracks. The number of tracks and each track's
* media format can be queried using {@link SampleSourceReader#getTrackCount()} and
* {@link SampleSourceReader#getFormat(int)} respectively.
*/
public interface SampleSource {
/**
* The end of stream has been reached.
*/
public static final int END_OF_STREAM = -1;
/**
* Neither a sample nor a format was read in full. This may be because insufficient data is
* buffered upstream. If multiple tracks are enabled, this return value may indicate that the
* next piece of data to be returned from the {@link SampleSource} corresponds to a different
* track than the one for which data was requested.
*/
public static final int NOTHING_READ = -2;
/**
* A sample was read.
*/
public static final int SAMPLE_READ = -3;
/**
* A format was read.
*/
public static final int FORMAT_READ = -4;
/**
* Returned from {@link SampleSourceReader#readDiscontinuity(int)} to indicate no discontinuity.
*/
public static final long NO_DISCONTINUITY = Long.MIN_VALUE;
/**
* A consumer of samples should call this method to register themselves and gain access to the
* source through the returned {@link SampleSourceReader}.
* <p>
* {@link SampleSourceReader#release()} should be called on the returned object when access is no
* longer required.
*
* @return A {@link SampleSourceReader} that provides access to the source.
*/
public SampleSourceReader register();
/**
* An interface providing read access to a {@link SampleSource}.
*/
public interface SampleSourceReader {
/**
* If the source is currently having difficulty preparing or loading samples, then this method
* throws the underlying error. Otherwise does nothing.
*
* @throws IOException The underlying error.
*/
public void maybeThrowError() throws IOException;
/**
* Prepares the source.
* <p>
* Preparation may require reading from the data source (e.g. to determine the available tracks
* and formats). If insufficient data is available then the call will return {@code false}
* rather than block. The method can be called repeatedly until the return value indicates
* success.
*
* @param positionUs The player's current playback position.
* @return True if the source was prepared, false otherwise.
*/
public boolean prepare(long positionUs);
/**
* Returns the number of tracks exposed by the source.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The number of tracks.
*/
public int getTrackCount();
/**
* Returns the format of the specified track.
* <p>
* Note that whilst the format of a track will remain constant, the format of the actual media
* stream may change dynamically. An example of this is where the track is adaptive
* (i.e. @link {@link MediaFormat#adaptive} is true). Hence the track formats returned through
* this method should not be used to configure decoders. Decoder configuration should be
* performed using the formats obtained when reading the media stream through calls to
* {@link #readData(int, long, MediaFormatHolder, SampleHolder)}.
* <p>
* This method should only be called after the source has been prepared.
*
* @param track The track index.
* @return The format of the specified track.
*/
public MediaFormat getFormat(int track);
/**
* Enable the specified track. This allows the track's format and samples to be read from
* {@link #readData(int, long, MediaFormatHolder, SampleHolder)}.
* <p>
* This method should only be called after the source has been prepared, and when the specified
* track is disabled.
*
* @param track The track to enable.
* @param positionUs The player's current playback position.
*/
public void enable(int track, long positionUs);
/**
* Indicates to the source that it should still be buffering data for the specified track.
* <p>
* This method should only be called when the specified track is enabled.
*
* @param track The track to continue buffering.
* @param positionUs The current playback position.
* @return True if the track has available samples, or if the end of the stream has been
* reached. False if more data needs to be buffered for samples to become available.
*/
public boolean continueBuffering(int track, long positionUs);
/**
* Attempts to read a pending discontinuity from the source.
* <p>
* This method should only be called when the specified track is enabled.
*
* @param track The track from which to read.
* @return If a discontinuity was read then the playback position after the discontinuity. Else
* {@link #NO_DISCONTINUITY}.
*/
public long readDiscontinuity(int track);
/**
* Attempts to read a sample or a new format from the source.
* <p>
* This method should only be called when the specified track is enabled.
* <p>
* Note that where multiple tracks are enabled, {@link #NOTHING_READ} may be returned if the
* next piece of data to be read from the {@link SampleSource} corresponds to a different track
* than the one for which data was requested.
* <p>
* This method will always return {@link #NOTHING_READ} in the case that there's a pending
* discontinuity to be read from {@link #readDiscontinuity(int)} for the specified track.
*
* @param track The track from which to read.
* @param positionUs The current playback position.
* @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new
* format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample.
* If the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
* @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ},
* {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
*/
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder);
/**
* Seeks to the specified time in microseconds.
* <p>
* This method should only be called when at least one track is enabled.
*
* @param positionUs The seek position in microseconds.
*/
public void seekToUs(long positionUs);
/**
* Returns an estimate of the position up to which data is buffered.
* <p>
* This method should only be called when at least one track is enabled.
*
* @return An estimate of the absolute position in microseconds up to which data is buffered,
* or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream,
* or {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
*/
public long getBufferedPositionUs();
/**
* Disable the specified track.
* <p>
* This method should only be called when the specified track is enabled.
*
* @param track The track to disable.
*/
public void disable(int track);
/**
* Releases the {@link SampleSourceReader}.
* <p>
* This method should be called when access to the {@link SampleSource} is no longer required.
*/
public void release();
}
}

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