mirror of
https://github.com/ArcaneChat/android.git
synced 2026-07-03 14:05:24 +02:00
Compare commits
181 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46463f13c6 | |||
| 35c8fb2399 | |||
| 8cda23ccaf | |||
| f7831cc6a0 | |||
| 2be0a78f6f | |||
| b505d9b020 | |||
| 9320d992b5 | |||
| c9f81e447a | |||
| 9d5edba4aa | |||
| f689b2fb94 | |||
| 0c22f40bf0 | |||
| 8957823dae | |||
| 243ec39eb8 | |||
| 529435316a | |||
| f541fd668b | |||
| ec29371d3f | |||
| 1add172770 | |||
| 37f401f2cd | |||
| f88c8231eb | |||
| 04b318f753 | |||
| 2af0e1ffb6 | |||
| 806ed69432 | |||
| 491e5e0190 | |||
| 93d9d053ee | |||
| 58de8d2830 | |||
| 6d26bbd3fb | |||
| e7c432e0de | |||
| 8c00d6e09e | |||
| 310d8f79bc | |||
| 8127e3427e | |||
| 4a12b055e7 | |||
| de7c71931c | |||
| 5a6fc7761c | |||
| 7410cb21a0 | |||
| 99d4142c13 | |||
| 27de528e57 | |||
| 521360f667 | |||
| 4e003ec6f5 | |||
| 98c8a0c550 | |||
| 039eb0d924 | |||
| e7636cee4e | |||
| 4b844ebb71 | |||
| c704ed4898 | |||
| ada4ef461e | |||
| 8dd4193f4e | |||
| 704d6f0516 | |||
| 097f3e8149 | |||
| 9d8988b08b | |||
| 0dcdc2d0bf | |||
| 83ed86575e | |||
| 40e7b08238 | |||
| 0ac7648bd6 | |||
| 41f1233a27 | |||
| 0bba3d94c7 | |||
| 67c5b1a281 | |||
| dbf6c98ddb | |||
| af666374ea | |||
| 45d10ab435 | |||
| 602ad36e6f | |||
| 6fc3c38b28 | |||
| a0e2aed195 | |||
| 9aff89ea9a | |||
| 60a8ab9bcf | |||
| 0d21883546 | |||
| 9fde45d272 | |||
| 61c15ea56d | |||
| d821e43fe9 | |||
| 03e5ee1098 | |||
| 646b2f028c | |||
| 794bdeafce | |||
| bd4d3b3e11 | |||
| 58c8cecee8 | |||
| 801406ce58 | |||
| 20a93a30b3 | |||
| 38d3c6c634 | |||
| 43bf4a4616 | |||
| 059edeaf18 | |||
| e1e9462fb1 | |||
| 125e70c04b | |||
| e4fc9502fe | |||
| 72a6627059 | |||
| 73cf5105dd | |||
| c69e128e83 | |||
| fcb037122d | |||
| 7eab23aa19 | |||
| 4d839e40ca | |||
| 7a06e3ac0c | |||
| 8392c34f65 | |||
| 67b8486b99 | |||
| 308018e6e6 | |||
| 6c625d078f | |||
| a69afe11f4 | |||
| 4ff15e433b | |||
| 6c81d03c50 | |||
| 9f0b322249 | |||
| e8831b0dfc | |||
| d6e9ba2ea9 | |||
| 2c8c5a7558 | |||
| d2e76a5e61 | |||
| 2263c24019 | |||
| 19e414e65c | |||
| f5a52c1aa2 | |||
| 1cf5039c81 | |||
| 90a5e39872 | |||
| 30868b107e | |||
| 546d4a3bda | |||
| 33eaf71e5b | |||
| a61e73b91b | |||
| f03a3a9b50 | |||
| e2225be331 | |||
| f1f23a5099 | |||
| db3bcb017c | |||
| ab0ce1393e | |||
| b941b9e21a | |||
| b838e0d69e | |||
| 8459f5552d | |||
| 293d249024 | |||
| 38a39aec17 | |||
| f4fb855cb6 | |||
| 8fa34e8659 | |||
| eb2f2de29c | |||
| 1b2c5a028a | |||
| 4057c08df9 | |||
| 074d2054ec | |||
| cf2ea4cc56 | |||
| cff6b6bba7 | |||
| 595111fb56 | |||
| cfc0485579 | |||
| 7e7d4176f2 | |||
| 8abaa7bc02 | |||
| 714e079b17 | |||
| 6983491586 | |||
| 2cea64ccff | |||
| 0ef81716c5 | |||
| c780ddf081 | |||
| 30b778c4f6 | |||
| 5e0c7553e2 | |||
| b5fe0acd89 | |||
| 01551fe71a | |||
| edd4176890 | |||
| f4f27ac0ae | |||
| aeb297548c | |||
| eb3aa46ff7 | |||
| 05fbf5bdce | |||
| 8a5f05fe2e | |||
| 590841d139 | |||
| 44caf01cae | |||
| db36341251 | |||
| 0a63816ece | |||
| ceb0155f5f | |||
| 723e677b2b | |||
| fab24ee956 | |||
| 94f4c8b302 | |||
| 008a2410a7 | |||
| b43f337af9 | |||
| 21895cf00f | |||
| 721d016d86 | |||
| 89aaa9c072 | |||
| bcb00a9d23 | |||
| 34cd40fdd9 | |||
| c8001cedce | |||
| 880353888b | |||
| 2c67d20233 | |||
| 42c08a4d5b | |||
| 01bf0f3af5 | |||
| e765968c2c | |||
| 3837463e41 | |||
| 545a2ddc24 | |||
| 4f6f0b4685 | |||
| 1d1bad8b3b | |||
| af5769f231 | |||
| 42c6337c4d | |||
| 37a49c9d39 | |||
| 3b2f778114 | |||
| b55cc03f6d | |||
| 2e6eead045 | |||
| e77c3303a1 | |||
| e83c4f315c | |||
| 833c75d874 | |||
| 9688003648 | |||
| a2609d5bbe |
@@ -1,5 +1,71 @@
|
||||
# Delta Chat Changelog
|
||||
|
||||
## v0.1.32
|
||||
2017-04-22
|
||||
|
||||
* Update Spanish and Portuguese translations
|
||||
* Update internal sqlite library to version 3.18.0, released on 2017-03-28
|
||||
* Remove more of the custom language handling, use Android's routines instead
|
||||
* General code cleanup
|
||||
* Play GIF files
|
||||
* Option to disable autoplaying GIF files
|
||||
* When sending contacts, only use the names the receivers have set themselves
|
||||
* Show some hints when long-pressing icons in the action bar
|
||||
|
||||
## v0.1.29
|
||||
2017-04-19
|
||||
|
||||
* Add Russian translation
|
||||
* For outgoing (group-)messages, only use the names the receivers have set themselves
|
||||
|
||||
## v0.1.28
|
||||
2017-04-14
|
||||
|
||||
* Pimp notifications
|
||||
* Bug fixes
|
||||
|
||||
## v0.1.27
|
||||
2017-04-12
|
||||
|
||||
* Use a permanent foreground service for reliable notifications
|
||||
* Monitor the IMAP-IDLE thread and reconnect if IMAP-IDLE seems to hang
|
||||
* Various battery and background optimizations
|
||||
|
||||
## v0.1.25
|
||||
2017-04-04
|
||||
|
||||
* Use system or user selected video player.
|
||||
* Do not connect if not configured (avoids a warning on the first time startup)
|
||||
* Add vertical scrollbar, eg. to settings activities.
|
||||
* Pimp GUI and logo.
|
||||
* Update Korean.
|
||||
|
||||
## v0.1.24
|
||||
2017-03-31
|
||||
|
||||
* Share images and documents from other apps to Delta Chat
|
||||
* Offer to mailto:-link-support to other apps
|
||||
* Ignore implausible sending time of incoming messages; use the receive time in these rare cases
|
||||
* Show errors only when Delta Chat is in foreground
|
||||
* Dynamically adapt video bitrate for longer videos to an attachment-size of max. 25 MB
|
||||
|
||||
## v0.1.23
|
||||
2017-03-28
|
||||
|
||||
* Retry connecting to IMAP if there is not network available on the first try
|
||||
* Notify about new messages if the app is not active for hours, optimize battery consumption
|
||||
|
||||
## v0.1.22
|
||||
2017-03-22
|
||||
|
||||
* Show HTML-only messages
|
||||
* Show connection errors
|
||||
* Add options for SSL/TLS and STARTTLS
|
||||
* Automatic account configuration, if possible
|
||||
* Recode large videos
|
||||
* Add Hungarian translation
|
||||
* Add Korean translation
|
||||
|
||||
## v0.1.21
|
||||
2017-03-10
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ tasks.withType(JavaCompile) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:appcompat-v7:25.1.1'
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'com.googlecode.mp4parser:isoparser:1.0.6'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
compile fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')
|
||||
@@ -76,7 +76,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig.versionCode = 20
|
||||
defaultConfig.versionCode = 32
|
||||
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'libs'
|
||||
@@ -115,8 +115,8 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14 // 14: Android 4.0 Ice Cream Sandwich 2011 (Telegram default), 21: Android 5.0 Lollipop 2014 (recommended for InstantRun)
|
||||
targetSdkVersion 25
|
||||
versionName "0.1.21" // do NOT forget to increase defaultConfig.versionCode!
|
||||
minSdkVersion 14 // 14: Android 4.0 Ice Cream Sandwich 2011 (Telegram default), 21: Android 5.0 Lollipop 2014 (recommended for InstantRun)
|
||||
targetSdkVersion 25 // 25: Nougat. CAVE: Do NOT target "Andoid O" without checking the background tasks carefully, see https://developer.android.com/preview/behavior-changes.html#back-all . As long as we target "Nougat", everything works as expected even for "Andoid O" or later
|
||||
versionName "0.1.32" // do NOT forget to increase defaultConfig.versionCode!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1598,9 +1598,8 @@ ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
./jni.c \
|
||||
./mrjnimain.c \
|
||||
./audio.c \
|
||||
./utils.c \
|
||||
./image.c \
|
||||
./video.c \
|
||||
./gifvideo.cpp \
|
||||
@@ -1611,14 +1610,16 @@ LOCAL_SRC_FILES += \
|
||||
./messenger-backend/src/mre2ee.c \
|
||||
./messenger-backend/src/mrimap.c \
|
||||
./messenger-backend/src/mrjob.c \
|
||||
./messenger-backend/src/mrlog.c \
|
||||
./messenger-backend/src/mrloginparam.c \
|
||||
./messenger-backend/src/mrmailbox.c \
|
||||
./messenger-backend/src/mrmailbox_configure.c \
|
||||
./messenger-backend/src/mrmailbox_log.c \
|
||||
./messenger-backend/src/mrmimeparser.c \
|
||||
./messenger-backend/src/mrmsg.c \
|
||||
./messenger-backend/src/mrosnative.c \
|
||||
./messenger-backend/src/mrparam.c \
|
||||
./messenger-backend/src/mrpoortext.c \
|
||||
./messenger-backend/src/mrsaxparser.c \
|
||||
./messenger-backend/src/mrsimplify.c \
|
||||
./messenger-backend/src/mrsmtp.c \
|
||||
./messenger-backend/src/mrsqlite3.c \
|
||||
|
||||
+64
-40
@@ -1,3 +1,26 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <ogg/ogg.h>
|
||||
#include <stdio.h>
|
||||
@@ -6,7 +29,7 @@
|
||||
#include <time.h>
|
||||
#include <opusfile.h>
|
||||
#include <math.h>
|
||||
#include "utils.h"
|
||||
#include "mrjnimain.h"
|
||||
|
||||
typedef struct {
|
||||
int version;
|
||||
@@ -115,7 +138,7 @@ static int read_chars(ROPacket *p, unsigned char *str, int nb_chars)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len) {
|
||||
static int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len) {
|
||||
int i;
|
||||
Packet p;
|
||||
unsigned char ch;
|
||||
@@ -219,34 +242,34 @@ static int writeOggPage(ogg_page *page, FILE *os) {
|
||||
return written;
|
||||
}
|
||||
|
||||
const opus_int32 bitrate = 16000;
|
||||
const opus_int32 rate = 16000;
|
||||
const opus_int32 frame_size = 960;
|
||||
const int with_cvbr = 1;
|
||||
const int max_ogg_delay = 0;
|
||||
const int comment_padding = 512;
|
||||
static const opus_int32 bitrate = 16000;
|
||||
static const opus_int32 rate = 16000;
|
||||
static const opus_int32 frame_size = 960;
|
||||
static const int with_cvbr = 1;
|
||||
static const int max_ogg_delay = 0;
|
||||
static const int comment_padding = 512;
|
||||
|
||||
opus_int32 coding_rate = 16000;
|
||||
ogg_int32_t _packetId;
|
||||
OpusEncoder *_encoder = 0;
|
||||
uint8_t *_packet = 0;
|
||||
ogg_stream_state os;
|
||||
FILE *_fileOs = 0;
|
||||
oe_enc_opt inopt;
|
||||
OpusHeader header;
|
||||
opus_int32 min_bytes;
|
||||
int max_frame_bytes;
|
||||
ogg_packet op;
|
||||
ogg_page og;
|
||||
opus_int64 bytes_written;
|
||||
opus_int64 pages_out;
|
||||
opus_int64 total_samples;
|
||||
ogg_int64_t enc_granulepos;
|
||||
ogg_int64_t last_granulepos;
|
||||
int size_segments;
|
||||
int last_segments;
|
||||
static opus_int32 coding_rate = 16000;
|
||||
static ogg_int32_t _packetId;
|
||||
static OpusEncoder *_encoder = 0;
|
||||
static uint8_t *_packet = 0;
|
||||
static ogg_stream_state os;
|
||||
static FILE *_fileOs = 0;
|
||||
static oe_enc_opt inopt;
|
||||
static OpusHeader header;
|
||||
static opus_int32 min_bytes;
|
||||
static int max_frame_bytes;
|
||||
static ogg_packet op;
|
||||
static ogg_page og;
|
||||
static opus_int64 bytes_written;
|
||||
static opus_int64 pages_out;
|
||||
static opus_int64 total_samples;
|
||||
static ogg_int64_t enc_granulepos;
|
||||
static ogg_int64_t last_granulepos;
|
||||
static int size_segments;
|
||||
static int last_segments;
|
||||
|
||||
void cleanupRecorder() {
|
||||
static void cleanupRecorder() {
|
||||
|
||||
ogg_stream_flush(&os, &og);
|
||||
|
||||
@@ -282,7 +305,7 @@ void cleanupRecorder() {
|
||||
memset(&og, 0, sizeof(ogg_page));
|
||||
}
|
||||
|
||||
int initRecorder(const char *path) {
|
||||
static int initRecorder(const char *path) {
|
||||
cleanupRecorder();
|
||||
|
||||
if (!path) {
|
||||
@@ -421,7 +444,7 @@ int initRecorder(const char *path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int writeFrame(uint8_t *framePcmBytes, unsigned int frameByteCount) {
|
||||
static int writeFrame(uint8_t *framePcmBytes, unsigned int frameByteCount) {
|
||||
int cur_frame_size = frame_size;
|
||||
_packetId++;
|
||||
|
||||
@@ -527,15 +550,15 @@ JNIEXPORT void Java_com_b44t_messenger_MediaController_stopRecord(JNIEnv *env, j
|
||||
}
|
||||
|
||||
//player
|
||||
OggOpusFile *_opusFile;
|
||||
int _isSeekable = 0;
|
||||
int64_t _totalPcmDuration = 0;
|
||||
int64_t _currentPcmOffset = 0;
|
||||
int _finished = 0;
|
||||
static OggOpusFile *_opusFile;
|
||||
static int _isSeekable = 0;
|
||||
static int64_t _totalPcmDuration = 0;
|
||||
static int64_t _currentPcmOffset = 0;
|
||||
static int _finished = 0;
|
||||
static const int playerBuffersCount = 3;
|
||||
static const int playerSampleRate = 48000;
|
||||
|
||||
void cleanupPlayer() {
|
||||
static void cleanupPlayer() {
|
||||
if (_opusFile) {
|
||||
op_free(_opusFile);
|
||||
_opusFile = 0;
|
||||
@@ -546,7 +569,7 @@ void cleanupPlayer() {
|
||||
_finished = 0;
|
||||
}
|
||||
|
||||
int seekPlayer(float position) {
|
||||
static int seekPlayer(float position) {
|
||||
if (!_opusFile || !_isSeekable || position < 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -559,7 +582,7 @@ int seekPlayer(float position) {
|
||||
return result == OPUS_OK;
|
||||
}
|
||||
|
||||
int initPlayer(const char *path) {
|
||||
static int initPlayer(const char *path) {
|
||||
cleanupPlayer();
|
||||
|
||||
int openError = OPUS_OK;
|
||||
@@ -576,7 +599,7 @@ int initPlayer(const char *path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void fillBuffer(uint8_t *buffer, int capacity, int *args) {
|
||||
static void fillBuffer(uint8_t *buffer, int capacity, int *args) {
|
||||
if (_opusFile) {
|
||||
args[1] = max(0, op_pcm_tell(_opusFile));
|
||||
|
||||
@@ -735,7 +758,7 @@ JNIEXPORT jbyteArray Java_com_b44t_messenger_MediaController_getWaveform2(JNIEnv
|
||||
return result;
|
||||
}
|
||||
|
||||
int16_t *sampleBuffer = NULL;
|
||||
static int16_t *sampleBuffer = NULL;
|
||||
|
||||
|
||||
JNIEXPORT jbyteArray Java_com_b44t_messenger_MediaController_getWaveform(JNIEnv *env, jclass class, jstring path) {
|
||||
@@ -819,3 +842,4 @@ JNIEXPORT jbyteArray Java_com_b44t_messenger_MediaController_getWaveform(JNIEnv
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <utils.h>
|
||||
#include "mrjnimain.h"
|
||||
#include <libyuv.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <cstdint>
|
||||
@@ -53,19 +76,14 @@ typedef struct VideoInfo {
|
||||
AVPacket orig_pkt;
|
||||
};
|
||||
|
||||
jobject makeGlobarRef(JNIEnv *env, jobject object) {
|
||||
if (object) {
|
||||
return env->NewGlobalRef(object);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gifvideoOnJNILoad(JavaVM *vm, JNIEnv *env) {
|
||||
int gifvideoOnJNILoad(JavaVM *vm, JNIEnv *env) { /* called from JNI_OnLoad() */
|
||||
av_register_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int open_codec_context(int *stream_idx, AVFormatContext *fmt_ctx, enum AVMediaType type) {
|
||||
|
||||
static int open_codec_context(int *stream_idx, AVFormatContext *fmt_ctx, enum AVMediaType type) {
|
||||
int ret;
|
||||
AVStream *st;
|
||||
AVCodecContext *dec_ctx = NULL;
|
||||
@@ -97,7 +115,7 @@ int open_codec_context(int *stream_idx, AVFormatContext *fmt_ctx, enum AVMediaTy
|
||||
return 0;
|
||||
}
|
||||
|
||||
int decode_packet(VideoInfo *info, int *got_frame) {
|
||||
static int decode_packet(VideoInfo *info, int *got_frame) {
|
||||
int ret = 0;
|
||||
int decoded = info->pkt.size;
|
||||
|
||||
@@ -263,4 +281,5 @@ jint Java_com_b44t_ui_Components_AnimatedFileDrawable_getVideoFrame(JNIEnv *env,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
+8
-6
@@ -21,11 +21,13 @@
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
#ifndef __GIFVIDEO_H__
|
||||
#define __GIFVIDEO_H__
|
||||
|
||||
public class UserObject {
|
||||
|
||||
public static String getUserName(TLRPC.User user) {
|
||||
return "ErrName"; // use MrContact.getName() instead
|
||||
}
|
||||
}
|
||||
#include <jni.h>
|
||||
|
||||
int gifvideoOnJNILoad(JavaVM *vm, JNIEnv *env);
|
||||
|
||||
|
||||
#endif /* __GIFVIDEO_H__ */
|
||||
+52
-15
@@ -1,3 +1,26 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
@@ -5,21 +28,35 @@
|
||||
#include <android/bitmap.h>
|
||||
#include <libwebp/webp/decode.h>
|
||||
#include <libwebp/webp/encode.h>
|
||||
#include "utils.h"
|
||||
#include "mrjnimain.h"
|
||||
#include "image.h"
|
||||
|
||||
jclass jclass_NullPointerException;
|
||||
jclass jclass_RuntimeException;
|
||||
|
||||
jclass jclass_Options;
|
||||
jfieldID jclass_Options_inJustDecodeBounds;
|
||||
jfieldID jclass_Options_outHeight;
|
||||
jfieldID jclass_Options_outWidth;
|
||||
static jclass jclass_NullPointerException;
|
||||
static jclass jclass_RuntimeException;
|
||||
|
||||
const uint32_t PGPhotoEnhanceHistogramBins = 256;
|
||||
const uint32_t PGPhotoEnhanceSegments = 4;
|
||||
static jclass jclass_Options;
|
||||
static jfieldID jclass_Options_inJustDecodeBounds;
|
||||
static jfieldID jclass_Options_outHeight;
|
||||
static jfieldID jclass_Options_outWidth;
|
||||
|
||||
jclass createGlobarRef(JNIEnv *env, jclass class) {
|
||||
static const uint32_t PGPhotoEnhanceHistogramBins = 256;
|
||||
static const uint32_t PGPhotoEnhanceSegments = 4;
|
||||
|
||||
static void throwException(JNIEnv *env, char *format, ...) {
|
||||
jclass exClass = (*env)->FindClass(env, "java/lang/UnsupportedOperationException");
|
||||
if (!exClass) {
|
||||
return;
|
||||
}
|
||||
char dest[256];
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vsprintf(dest, format, argptr);
|
||||
va_end(argptr);
|
||||
(*env)->ThrowNew(env, exClass, dest);
|
||||
}
|
||||
|
||||
static jclass createGlobalRef(JNIEnv *env, jclass class) {
|
||||
if (class) {
|
||||
return (*env)->NewGlobalRef(env, class);
|
||||
}
|
||||
@@ -27,16 +64,16 @@ jclass createGlobarRef(JNIEnv *env, jclass class) {
|
||||
}
|
||||
|
||||
jint imageOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env) {
|
||||
jclass_NullPointerException = createGlobarRef(env, (*env)->FindClass(env, "java/lang/NullPointerException"));
|
||||
jclass_NullPointerException = createGlobalRef(env, (*env)->FindClass(env, "java/lang/NullPointerException"));
|
||||
if (jclass_NullPointerException == 0) {
|
||||
return -1;
|
||||
}
|
||||
jclass_RuntimeException = createGlobarRef(env, (*env)->FindClass(env, "java/lang/RuntimeException"));
|
||||
jclass_RuntimeException = createGlobalRef(env, (*env)->FindClass(env, "java/lang/RuntimeException"));
|
||||
if (jclass_RuntimeException == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
jclass_Options = createGlobarRef(env, (*env)->FindClass(env, "android/graphics/BitmapFactory$Options"));
|
||||
jclass_Options = createGlobalRef(env, (*env)->FindClass(env, "android/graphics/BitmapFactory$Options"));
|
||||
if (jclass_Options == 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -262,7 +299,7 @@ typedef struct my_error_mgr {
|
||||
} *my_error_ptr;
|
||||
|
||||
|
||||
METHODDEF(void) my_error_exit(j_common_ptr cinfo) {
|
||||
METHODDEF(void) my_jpeglib_error_exit(j_common_ptr cinfo) {
|
||||
my_error_ptr myerr = (my_error_ptr) cinfo->err;
|
||||
(*cinfo->err->output_message) (cinfo);
|
||||
longjmp(myerr->setjmp_buffer, 1);
|
||||
@@ -424,7 +461,7 @@ JNIEXPORT void Java_com_b44t_messenger_Utilities_loadBitmap(JNIEnv *env, jclass
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = my_error_exit;
|
||||
jerr.pub.error_exit = my_jpeglib_error_exit;
|
||||
|
||||
if (!setjmp(jerr.setjmp_buffer)) {
|
||||
jpeg_create_decompress(&cinfo);
|
||||
|
||||
@@ -1,8 +1,34 @@
|
||||
#ifndef image_h
|
||||
#define image_h
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#ifndef __IMAGE_H__
|
||||
#define __IMAGE_H__
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
jint imageOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env);
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* __IMAGE_H__ */
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
#include <sys/types.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <unistd.h>
|
||||
#include "utils.h"
|
||||
#include "image.h"
|
||||
|
||||
int gifvideoOnJNILoad(JavaVM *vm, JNIEnv *env);
|
||||
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) { /* this function is called automatically by the JNI when the library gets loaded */
|
||||
JNIEnv *env = 0;
|
||||
srand(time(NULL));
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (imageOnJNILoad(vm, reserved, env) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gifvideoOnJNILoad(vm, env) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
void JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
JNIEXPORT void Java_com_b44t_messenger_Utilities_aesIgeEncryption(JNIEnv *env, jclass class, jobject buffer, jbyteArray key, jbyteArray iv, jboolean encrypt, int offset, int length) {
|
||||
jbyte *what = (*env)->GetDirectBufferAddress(env, buffer) + offset;
|
||||
unsigned char *keyBuff = (unsigned char *)(*env)->GetByteArrayElements(env, key, NULL);
|
||||
unsigned char *ivBuff = (unsigned char *)(*env)->GetByteArrayElements(env, iv, NULL);
|
||||
|
||||
AES_KEY akey;
|
||||
if (!encrypt) {
|
||||
AES_set_decrypt_key(keyBuff, 32 * 8, &akey);
|
||||
AES_ige_encrypt(what, what, length, &akey, ivBuff, AES_DECRYPT);
|
||||
} else {
|
||||
AES_set_encrypt_key(keyBuff, 32 * 8, &akey);
|
||||
AES_ige_encrypt(what, what, length, &akey, ivBuff, AES_ENCRYPT);
|
||||
}
|
||||
(*env)->ReleaseByteArrayElements(env, key, keyBuff, JNI_ABORT);
|
||||
(*env)->ReleaseByteArrayElements(env, iv, ivBuff, 0);
|
||||
}
|
||||
*/
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_Utilities_readlink(JNIEnv *env, jclass class, jstring path) {
|
||||
static char buf[1000];
|
||||
char *fileName = (*env)->GetStringUTFChars(env, path, NULL);
|
||||
int result = readlink(fileName, buf, 999);
|
||||
jstring value = 0;
|
||||
if (result != -1) {
|
||||
buf[result] = '\0';
|
||||
value = (*env)->NewStringUTF(env, buf);
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, path, fileName);
|
||||
return value;
|
||||
}
|
||||
@@ -122,13 +122,13 @@
|
||||
#define LIBETPAN_REENTRANT 1
|
||||
|
||||
/* Define this to the version of libEtPan */
|
||||
#define LIBETPAN_VERSION "1.2-dev-20141203"
|
||||
//#define LIBETPAN_VERSION "1.2-dev-20141203"
|
||||
|
||||
/* Define this to the major version of libEtPan */
|
||||
#define LIBETPAN_VERSION_MAJOR 1
|
||||
//#define LIBETPAN_VERSION_MAJOR 1
|
||||
|
||||
/* Define this to the minor version of libEtPan */
|
||||
#define LIBETPAN_VERSION_MINOR 2
|
||||
//#define LIBETPAN_VERSION_MINOR 2
|
||||
|
||||
/* Define to the sub-directory in which libtool stores uninstalled libraries.
|
||||
*/
|
||||
@@ -144,7 +144,7 @@
|
||||
#define PACKAGE_NAME "libetpan"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "libetpan 1.2"
|
||||
//#define PACKAGE_STRING "libetpan 1.2"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "libetpan"
|
||||
@@ -153,7 +153,7 @@
|
||||
#define PACKAGE_URL ""
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "1.2"
|
||||
//#define PACKAGE_VERSION "1.2"
|
||||
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
#define STDC_HEADERS 1
|
||||
@@ -171,7 +171,7 @@
|
||||
#define USE_SSL 1
|
||||
|
||||
/* Version number of package */
|
||||
#define VERSION "1.2"
|
||||
//#define VERSION "1.2"
|
||||
|
||||
/* Define to `__inline__' or `__inline' if that's what the C compiler
|
||||
calls it, or to nothing if 'inline' is not supported under any name. */
|
||||
|
||||
Submodule MessengerProj/jni/messenger-backend updated: 4b922e786b...ca9c0060b0
@@ -0,0 +1,77 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
#include <sys/types.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <unistd.h>
|
||||
#include "mrjnimain.h"
|
||||
#include "image.h"
|
||||
#include "gifvideo.h"
|
||||
|
||||
|
||||
jint JNI_OnLoad(JavaVM *vm, void *reserved) { /* this function is called automatically by the JNI when the library gets loaded */
|
||||
JNIEnv *env = 0;
|
||||
srand(time(NULL));
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "DeltaChat", "JNI_OnLoad() ..."); /* please note, that __android_log_print() may not work (eg. on LG X Cam), however, we don't have an option here. */
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (imageOnJNILoad(vm, reserved, env) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (gifvideoOnJNILoad(vm, env) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "DeltaChat", "JNI_OnLoad() succeeded.");
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
|
||||
void JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_Utilities_readlink(JNIEnv *env, jclass class, jstring path) {
|
||||
static char buf[1000];
|
||||
char *fileName = (*env)->GetStringUTFChars(env, path, NULL);
|
||||
int result = readlink(fileName, buf, 999);
|
||||
jstring value = 0;
|
||||
if (result != -1) {
|
||||
buf[result] = '\0';
|
||||
value = (*env)->NewStringUTF(env, buf);
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, path, fileName);
|
||||
return value;
|
||||
}
|
||||
|
||||
+31
-4
@@ -1,6 +1,7 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
@@ -20,8 +21,34 @@
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
#ifndef __MRJNIMAIN_H__
|
||||
#define __MRJNIMAIN_H__
|
||||
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
|
||||
#define LOG_TAG "DeltaChat"
|
||||
#ifndef LOG_DISABLED
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define LOGI(...)
|
||||
#define LOGD(...)
|
||||
#define LOGE(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
|
||||
#ifndef max
|
||||
#define max(x, y) ((x) > (y)) ? (x) : (y)
|
||||
#endif
|
||||
#ifndef min
|
||||
#define min(x, y) ((x) < (y)) ? (x) : (y)
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* __MRJNIMAIN_H__*/
|
||||
|
||||
public class BuildVars {
|
||||
public static boolean DEBUG_VERSION = false;
|
||||
}
|
||||
@@ -23,8 +23,8 @@
|
||||
* Purpose: The C part of the Java<->C Wrapper, see also MrMailbox.java
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include "messenger-backend/src/mrmailbox.h"
|
||||
@@ -66,45 +66,51 @@ static jstring jstring_new__(JNIEnv* env, const char* a)
|
||||
}
|
||||
|
||||
|
||||
/* our log handler */
|
||||
|
||||
static void s_log_callback_(int type, const char* msg)
|
||||
{
|
||||
int prio;
|
||||
|
||||
switch( type ) {
|
||||
case 'd': prio = ANDROID_LOG_DEBUG; break;
|
||||
case 'i': prio = ANDROID_LOG_INFO; break;
|
||||
case 'w': prio = ANDROID_LOG_WARN; break;
|
||||
default: prio = ANDROID_LOG_ERROR; break;
|
||||
}
|
||||
__android_log_print(prio, "DeltaChat", "%s\n", msg); /* on problems, add `-llog` to `Android.mk` */
|
||||
}
|
||||
|
||||
|
||||
/* global stuff */
|
||||
|
||||
static JavaVM* s_jvm = NULL;
|
||||
static jclass s_MrMailbox_class = NULL;
|
||||
static jmethodID s_MrCallback_methodID = NULL;
|
||||
static int s_global_init_done = 0;
|
||||
|
||||
|
||||
static void s_init_globals(JNIEnv *env, jclass MrMailbox_class)
|
||||
{
|
||||
/* make sure, the intialisation is done only once */
|
||||
static bool s_global_init_done = 0;
|
||||
if( s_global_init_done ) { return; }
|
||||
s_global_init_done = 1;
|
||||
|
||||
/* init global callback */
|
||||
mrlog_set_handler(s_log_callback_);
|
||||
|
||||
/* prepare calling back a Java function */
|
||||
__android_log_print(ANDROID_LOG_INFO, "DeltaChat", "JNI: s_init_globals()..."); /* low-level logging, mrmailbox_log_*() may not be yet available. However, please note that __android_log_print() may not work (eg. on LG X Cam) */
|
||||
|
||||
(*env)->GetJavaVM(env, &s_jvm); /* JNIEnv cannot be shared between threads, so we share the JavaVM object */
|
||||
s_MrMailbox_class = (*env)->NewGlobalRef(env, MrMailbox_class);
|
||||
s_MrCallback_methodID = (*env)->GetStaticMethodID(env, MrMailbox_class, "MrCallback","(IJJ)J" /*signature as "(param)ret" with I=int, J=long*/ );
|
||||
}
|
||||
|
||||
/* system-specific backend initialisations */
|
||||
mrosnative_init_android(env); /*this should be called before any other "important" routine is called*/
|
||||
|
||||
/* setup threads, only called directly by the backed */
|
||||
|
||||
int mrosnative_setup_thread(mrmailbox_t* mailbox)
|
||||
{
|
||||
if( s_jvm == NULL ) {
|
||||
mrmailbox_log_error(mailbox, 0, "Not ready, cannot setup thread.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mrmailbox_log_info(mailbox, 0, "Attaching C-thread to Java VM...");
|
||||
JNIEnv* env = NULL;
|
||||
(*s_jvm)->AttachCurrentThread(s_jvm, &env, NULL);
|
||||
mrmailbox_log_info(mailbox, 0, "Attaching ok.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void mrosnative_unsetup_thread(mrmailbox_t* mailbox)
|
||||
{
|
||||
mrmailbox_log_info(mailbox, 0, "Detaching C-thread from Java VM...");
|
||||
(*s_jvm)->DetachCurrentThread(s_jvm);
|
||||
mrmailbox_log_info(mailbox, 0, "DeltaChat", "Detaching done.");
|
||||
}
|
||||
|
||||
|
||||
@@ -189,15 +195,24 @@ static uintptr_t s_mailbox_callback_(mrmailbox_t* mailbox, int event, uintptr_t
|
||||
jlong l;
|
||||
JNIEnv* env;
|
||||
|
||||
#if 0 /* -- __android_log_print() does not log eg. on LG X Cam - but Javas Log.i() etc. do. So, we do not optimize these calls and just use the Java logging. */
|
||||
if( event==MR_EVENT_INFO || event==MR_EVENT_WARNING ) {
|
||||
__android_log_print(event==MR_EVENT_INFO? ANDROID_LOG_INFO : ANDROID_LOG_WARN, "DeltaChat", "%s", (char*)data2); /* on problems, add `-llog` to `Android.mk` */
|
||||
return 0; /* speed up things for info/warning */
|
||||
}
|
||||
else if( event == MR_EVENT_ERROR ) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "DeltaChat", "%s", (char*)data2);
|
||||
/* errors are also forwarded to Java to show them in a bubble or so */
|
||||
}
|
||||
#endif
|
||||
|
||||
if( s_jvm==NULL || s_MrMailbox_class==NULL || s_MrCallback_methodID==NULL ) {
|
||||
s_log_callback_('e', "Callback called but JavaVM not ready.");
|
||||
return 0;
|
||||
return 0; /* may happen on startup */
|
||||
}
|
||||
|
||||
(*s_jvm)->GetEnv(s_jvm, &env, JNI_VERSION_1_6); /* as this function may be called from _any_ thread, we cannot use a static pointer to JNIEnv */
|
||||
if( env==NULL ) {
|
||||
s_log_callback_('e', "Callback called but cannot get JNIEnv.");
|
||||
return 0;
|
||||
return 0; /* may happen on startup */
|
||||
}
|
||||
|
||||
l = (*env)->CallStaticLongMethod(env, s_MrMailbox_class, s_MrCallback_methodID, (jint)event, (jlong)data1, (jlong)data2);
|
||||
@@ -236,42 +251,33 @@ JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_getBlobdir(JNIEnv *env, jcla
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxConfigure(JNIEnv *env, jclass c, jlong hMailbox)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_configureAndConnect(JNIEnv *env, jclass cls)
|
||||
{
|
||||
return mrmailbox_configure((mrmailbox_t*)hMailbox);
|
||||
mrmailbox_configure_and_connect(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxIsConfigured(JNIEnv *env, jclass c, jlong hMailbox)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_configureCancel(JNIEnv *env, jclass cls)
|
||||
{
|
||||
return (jint)mrmailbox_is_configured((mrmailbox_t*)hMailbox);
|
||||
mrmailbox_configure_cancel(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxConnect(JNIEnv *env, jclass c, jlong hMailbox)
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_isConfigured(JNIEnv *env, jclass cls)
|
||||
{
|
||||
return mrmailbox_connect((mrmailbox_t*)hMailbox);
|
||||
return (jint)mrmailbox_is_configured(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_MrMailboxDisconnect(JNIEnv *env, jclass c, jlong hMailbox)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_connect(JNIEnv *env, jclass cls)
|
||||
{
|
||||
mrmailbox_disconnect((mrmailbox_t*)hMailbox);
|
||||
mrmailbox_connect(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxFetch(JNIEnv *env, jclass c, jlong hMailbox)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_disconnect(JNIEnv *env, jclass cls)
|
||||
{
|
||||
return mrmailbox_fetch((mrmailbox_t*)hMailbox);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_getErrorDescr(JNIEnv *env, jclass cls)
|
||||
{
|
||||
char* c = mrmailbox_get_error_descr(get_mrmailbox_t(env, cls));
|
||||
jstring ret = JSTRING_NEW(c);
|
||||
free(c);
|
||||
return ret;
|
||||
mrmailbox_disconnect(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
@@ -351,9 +357,9 @@ JNIEXPORT jlong Java_com_b44t_messenger_MrMailbox_MrMailboxGetChat(JNIEnv *env,
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxGetChatIdByContactId(JNIEnv *env, jclass c, jlong hMailbox, jint contact_id)
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_getChatIdByContactId(JNIEnv *env, jclass cls, jint contact_id)
|
||||
{
|
||||
return (jint)mrmailbox_get_chat_id_by_contact_id((mrmailbox_t*)hMailbox, contact_id);
|
||||
return (jint)mrmailbox_get_chat_id_by_contact_id(get_mrmailbox_t(env, cls), contact_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -369,9 +375,9 @@ JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_markseenChat(JNIEnv *env, jclas
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxCreateChatByContactId(JNIEnv *env, jclass c, jlong hMailbox, jint contact_id)
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_createChatByContactId(JNIEnv *env, jclass cls, jint contact_id)
|
||||
{
|
||||
return (jint)mrmailbox_create_chat_by_contact_id((mrmailbox_t*)hMailbox, contact_id);
|
||||
return (jint)mrmailbox_create_chat_by_contact_id(get_mrmailbox_t(env, cls), contact_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -454,22 +460,29 @@ JNIEXPORT void Java_com_b44t_messenger_MrMailbox_forwardMsgs(JNIEnv *env, jclass
|
||||
|
||||
/* MrMailbox - handle config */
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxSetConfig(JNIEnv *env, jclass c, jlong hMailbox, jstring key, jstring value)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_setConfig(JNIEnv *env, jclass cls, jstring key, jstring value)
|
||||
{
|
||||
CHAR_REF(key);
|
||||
CHAR_REF(value);
|
||||
jint ret = (jint)mrmailbox_set_config((mrmailbox_t*)hMailbox, keyPtr, valuePtr);
|
||||
mrmailbox_set_config(get_mrmailbox_t(env, cls), keyPtr, valuePtr);
|
||||
CHAR_UNREF(key);
|
||||
CHAR_UNREF(value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_MrMailboxGetConfig(JNIEnv *env, jclass c, jlong hMailbox, jstring key, jstring def)
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_setConfigInt(JNIEnv *env, jclass cls, jstring key, jint value)
|
||||
{
|
||||
CHAR_REF(key);
|
||||
mrmailbox_set_config_int(get_mrmailbox_t(env, cls), keyPtr, value);
|
||||
CHAR_UNREF(key);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_getConfig(JNIEnv *env, jclass cls, jstring key, jstring def)
|
||||
{
|
||||
CHAR_REF(key);
|
||||
CHAR_REF(def);
|
||||
char* temp = mrmailbox_get_config((mrmailbox_t*)hMailbox, keyPtr, defPtr);
|
||||
char* temp = mrmailbox_get_config(get_mrmailbox_t(env, cls), keyPtr, defPtr);
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
free(temp);
|
||||
CHAR_UNREF(key);
|
||||
@@ -478,10 +491,10 @@ JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_MrMailboxGetConfig(JNIEnv *e
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxGetConfigInt(JNIEnv *env, jclass c, jlong hMailbox, jstring key, jint def)
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_getConfigInt(JNIEnv *env, jclass cls, jstring key, jint def)
|
||||
{
|
||||
CHAR_REF(key);
|
||||
jint ret = mrmailbox_get_config_int((mrmailbox_t*)hMailbox, keyPtr, def);
|
||||
jint ret = mrmailbox_get_config_int(get_mrmailbox_t(env, cls), keyPtr, def);
|
||||
CHAR_UNREF(key);
|
||||
return ret;
|
||||
}
|
||||
@@ -509,6 +522,12 @@ JNIEXPORT jstring Java_com_b44t_messenger_MrMailbox_cmdline(JNIEnv *env, jclass
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrMailbox_heartbeat(JNIEnv *env, jclass cls)
|
||||
{
|
||||
mrmailbox_heartbeat(get_mrmailbox_t(env, cls));
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_MrMailbox_MrMailboxAddAddressBook(JNIEnv *env, jclass c, jlong hMailbox, jstring adrbook)
|
||||
{
|
||||
CHAR_REF(adrbook);
|
||||
@@ -678,7 +697,7 @@ JNIEXPORT jint Java_com_b44t_messenger_MrChat_sendMedia(JNIEnv *env, jclass cls,
|
||||
CHAR_REF(file);
|
||||
mrparam_set(msg->m_param, 'f', filePtr);
|
||||
CHAR_UNREF(file);
|
||||
if( type == MR_MSG_IMAGE || type == MR_MSG_VIDEO ) {
|
||||
if( type == MR_MSG_IMAGE || type == MR_MSG_GIF || type == MR_MSG_VIDEO ) {
|
||||
mrparam_set_int(msg->m_param, 'w', w);
|
||||
mrparam_set_int(msg->m_param, 'h', h);
|
||||
}
|
||||
@@ -905,6 +924,20 @@ JNIEXPORT jint Java_com_b44t_messenger_MrMsg_isIncreation(JNIEnv *env, jobject o
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
static mrcontact_t* get_mrcontact_t(JNIEnv *env, jobject obj)
|
||||
{
|
||||
static jfieldID fid = 0;
|
||||
if( fid == 0 ) {
|
||||
jclass cls = (*env)->GetObjectClass(env, obj);
|
||||
fid = (*env)->GetFieldID(env, cls, "m_hContact", "J" /*Signature, J=long*/);
|
||||
}
|
||||
if( fid ) {
|
||||
return (mrcontact_t*)(*env)->GetLongField(env, obj, fid);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT void Java_com_b44t_messenger_MrContact_MrContactUnref(JNIEnv *env, jclass c, jlong hContact)
|
||||
{
|
||||
mrcontact_unref((mrcontact_t*)hContact);
|
||||
@@ -918,6 +951,13 @@ JNIEXPORT jstring Java_com_b44t_messenger_MrContact_MrContactGetName(JNIEnv *env
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_MrContact_getAuthName(JNIEnv *env, jclass cls)
|
||||
{
|
||||
mrcontact_t* ths = get_mrcontact_t(env, cls); if( ths == NULL ) { return JSTRING_NEW(NULL); }
|
||||
return JSTRING_NEW(ths->m_authname);
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_MrContact_MrContactGetAddr(JNIEnv *env, jclass c, jlong hContact)
|
||||
{
|
||||
mrcontact_t* ths = (mrcontact_t*)hContact; if( ths == NULL ) { return JSTRING_NEW(NULL); }
|
||||
|
||||
+12322
-7051
File diff suppressed because it is too large
Load Diff
+663
-360
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
#include "utils.h"
|
||||
|
||||
void throwException(JNIEnv *env, char *format, ...) {
|
||||
jclass exClass = (*env)->FindClass(env, "java/lang/UnsupportedOperationException");
|
||||
if (!exClass) {
|
||||
return;
|
||||
}
|
||||
char dest[256];
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vsprintf(dest, format, argptr);
|
||||
va_end(argptr);
|
||||
(*env)->ThrowNew(env, exClass, dest);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef log_h
|
||||
#define log_h
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define LOG_TAG "DeltaChat"
|
||||
#ifndef LOG_DISABLED
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define LOGI(...)
|
||||
#define LOGD(...)
|
||||
#define LOGE(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
|
||||
#ifndef max
|
||||
#define max(x, y) ((x) > (y)) ? (x) : (y)
|
||||
#endif
|
||||
#ifndef min
|
||||
#define min(x, y) ((x) < (y)) ? (x) : (y)
|
||||
#endif
|
||||
|
||||
void throwException(JNIEnv *env, char *format, ...);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,30 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <libyuv.h>
|
||||
#include <utils.h>
|
||||
#include "mrjnimain.h"
|
||||
|
||||
|
||||
enum COLOR_FORMATTYPE {
|
||||
COLOR_FormatMonochrome = 1,
|
||||
@@ -52,7 +76,7 @@ enum COLOR_FORMATTYPE {
|
||||
COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00
|
||||
};
|
||||
|
||||
int isSemiPlanarYUV(int colorFormat) {
|
||||
static int isSemiPlanarYUV(int colorFormat) {
|
||||
switch (colorFormat) {
|
||||
case COLOR_FormatYUV420Planar:
|
||||
case COLOR_FormatYUV420PackedPlanar:
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
<application
|
||||
android:name=".ApplicationLoader"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="@bool/useHardwareAcceleration"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/AppName"
|
||||
android:largeHeap="true"
|
||||
@@ -56,7 +55,6 @@
|
||||
<activity
|
||||
android:name="com.b44t.ui.LaunchActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:hardwareAccelerated="@bool/useHardwareAcceleration"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
@@ -99,27 +97,17 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="vnd.android.cursor.item/vnd.com.b44t.messenger.android.profile"/>
|
||||
</intent-filter>
|
||||
<!-- <intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:host="t'gram.me" android:scheme="http" />
|
||||
<data android:host="t'gram.me" android:scheme="https" />
|
||||
<data android:host="t'gram.dog" android:scheme="http" />
|
||||
<data android:host="t'gram.dog" android:scheme="https" />
|
||||
</intent-filter> -->
|
||||
<!-- <intent-filter android:icon="@drawable/ic_launcher" android:priority="1">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="tg" />
|
||||
</intent-filter> -->
|
||||
<!-- see note below, <meta-data android:name="android.service.chooser.chooser_target_service" android:value=".TgChooserTargetService" /> -->
|
||||
<action android:name="android.intent.action.SENDTO"/> <!-- VIEW seems to be standard, however, SENDTO is also mentioned in some examples -->
|
||||
<data android:scheme="mailto"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.b44t.ui.ManageSpaceActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:hardwareAccelerated="@bool/useHardwareAcceleration"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
</activity>
|
||||
@@ -181,7 +169,7 @@
|
||||
|
||||
<!-- this would implement the "direct share" functionality (the system shows some direct links eg.
|
||||
to the most recent chats when calling "share" from another app, see https://developer.android.com/reference/android/service/chooser/ChooserTargetService.html )
|
||||
however, if we want this feature, we have to reimplement TgChooserTargetService or sth. like that.
|
||||
however, if we want this feature, we have to implement a ChooserTargetService and add <meta-data android:name="android.service.chooser.chooser_target_service" android:value=".OurChooserTargetService" />
|
||||
<service
|
||||
android:name="com.b44t.messenger.TgChooserTargetService"
|
||||
android:label="@string/AppName"
|
||||
@@ -191,9 +179,8 @@
|
||||
</intent-filter>
|
||||
</service> -->
|
||||
|
||||
<service android:name=".NotificationsService" android:enabled="true"/>
|
||||
<service android:name=".KeepAliveService" android:enabled="true"/>
|
||||
<service android:name=".NotificationRepeat" android:exported="false"/>
|
||||
<service android:name=".ClearCacheService" android:exported="false"/>
|
||||
<service android:name=".MusicPlayerService" android:exported="true" android:enabled="true"/>
|
||||
<!-- Do not expose data through MediaBrowserService, this seems unexpected for an messenger.
|
||||
Instead, the user can use 'save to music' or 'share' explicitly for items not privacy related.
|
||||
@@ -215,9 +202,8 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".AppStartReceiver" android:enabled="true">
|
||||
<receiver android:name=".BootCompletedReceiver" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.b44t.start" />
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
@@ -229,6 +215,8 @@
|
||||
<receiver android:name=".ShareBroadcastReceiver" android:enabled="true"/>
|
||||
-->
|
||||
|
||||
<receiver android:name=".TimerReceiver" android:enabled="true"/>
|
||||
|
||||
<receiver android:name=".NotificationDismissReceiver" android:exported="false"/>
|
||||
|
||||
<provider android:name=".AttachmentsContentProvider" android:authorities="${applicationId}.attachments" android:exported="true" />
|
||||
|
||||
@@ -51,6 +51,7 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.StateSet;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
@@ -113,7 +114,7 @@ public class AndroidUtilities {
|
||||
final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD;
|
||||
final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +194,7 @@ public class AndroidUtilities {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ public class AndroidUtilities {
|
||||
prevOrientation = -10;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ public class AndroidUtilities {
|
||||
InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +235,7 @@ public class AndroidUtilities {
|
||||
}
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +245,7 @@ public class AndroidUtilities {
|
||||
try {
|
||||
state = Environment.getExternalStorageState();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
if (state == null || state.startsWith(Environment.MEDIA_MOUNTED)) {
|
||||
try {
|
||||
@@ -253,7 +254,7 @@ public class AndroidUtilities {
|
||||
return file;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -262,7 +263,7 @@ public class AndroidUtilities {
|
||||
return file;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return new File("");
|
||||
}
|
||||
@@ -301,11 +302,10 @@ public class AndroidUtilities {
|
||||
if (display != null) {
|
||||
display.getMetrics(displayMetrics);
|
||||
display.getSize(displaySize);
|
||||
FileLog.e("messenger", "display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi);
|
||||
Log.i("DeltaChat", "Display size: " + displaySize.x + " x " + displaySize.y + ", DPI: " + displayMetrics.xdpi + " x " + displayMetrics.ydpi);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,15 +330,21 @@ public class AndroidUtilities {
|
||||
}
|
||||
|
||||
public static boolean isTablet() {
|
||||
/* -- we do not make any special for tablet or not. _If_ sth. like this is desired, check for an appropriate screen size.
|
||||
if (isTablet == null) {
|
||||
isTablet = ApplicationLoader.applicationContext.getResources().getBoolean(R.bool.isTablet);
|
||||
}
|
||||
return isTablet;
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isSmallTablet() {
|
||||
/*
|
||||
float minSide = Math.min(displaySize.x, displaySize.y) / density;
|
||||
return minSide <= 700;
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getMinTabletSide() {
|
||||
@@ -380,7 +386,7 @@ public class AndroidUtilities {
|
||||
mCursorDrawableRes.setAccessible(true);
|
||||
mCursorDrawableRes.setInt(editText, 0);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +416,7 @@ public class AndroidUtilities {
|
||||
addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
ApplicationLoader.applicationContext.sendBroadcast(addIntent);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +429,7 @@ public class AndroidUtilities {
|
||||
addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
|
||||
ApplicationLoader.applicationContext.sendBroadcast(addIntent);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
@@ -442,7 +448,7 @@ public class AndroidUtilities {
|
||||
return insets.bottom;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -460,11 +466,11 @@ public class AndroidUtilities {
|
||||
size.set((Integer) mGetRawW.invoke(windowManager.getDefaultDisplay()), (Integer) mGetRawH.invoke(windowManager.getDefaultDisplay()));
|
||||
} catch (Exception e) {
|
||||
size.set(windowManager.getDefaultDisplay().getWidth(), windowManager.getDefaultDisplay().getHeight());
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return size;
|
||||
}
|
||||
@@ -482,28 +488,6 @@ public class AndroidUtilities {
|
||||
return src;
|
||||
}
|
||||
|
||||
public static void setListViewEdgeEffectColor(AbsListView listView, int color) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
try {
|
||||
Field field = AbsListView.class.getDeclaredField("mEdgeGlowTop");
|
||||
field.setAccessible(true);
|
||||
EdgeEffect mEdgeGlowTop = (EdgeEffect) field.get(listView);
|
||||
if (mEdgeGlowTop != null) {
|
||||
mEdgeGlowTop.setColor(color);
|
||||
}
|
||||
|
||||
field = AbsListView.class.getDeclaredField("mEdgeGlowBottom");
|
||||
field.setAccessible(true);
|
||||
EdgeEffect mEdgeGlowBottom = (EdgeEffect) field.get(listView);
|
||||
if (mEdgeGlowBottom != null) {
|
||||
mEdgeGlowBottom.setColor(color);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static void clearDrawableAnimation(View view) {
|
||||
if (Build.VERSION.SDK_INT < 21 || view == null) {
|
||||
@@ -560,7 +544,7 @@ public class AndroidUtilities {
|
||||
}
|
||||
}
|
||||
ArrayList<Integer> colors = new ArrayList<>();
|
||||
if ((flag & FLAG_TAG_COLOR) != 0) {
|
||||
if ((flag & FLAG_TAG_COLOR) != 0) { // used eg. by the message preview when following a mailto:-link
|
||||
while ((start = stringBuilder.indexOf("<c#")) != -1) {
|
||||
stringBuilder.replace(start, start + 2, "");
|
||||
end = stringBuilder.indexOf(">", start);
|
||||
@@ -582,7 +566,7 @@ public class AndroidUtilities {
|
||||
}
|
||||
return spannableStringBuilder;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return new SpannableStringBuilder(str);
|
||||
}
|
||||
@@ -593,7 +577,7 @@ public class AndroidUtilities {
|
||||
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());
|
||||
(UserConfig.appLocked || UserConfig.autoLockIn != 0 && UserConfig.lastPauseTime != 0 && !UserConfig.appLocked && (UserConfig.lastPauseTime + UserConfig.autoLockIn) <= MrMailbox.getCurrentTime());
|
||||
}
|
||||
|
||||
public static void shakeView(final View view, final float x, final int num) {
|
||||
@@ -639,7 +623,7 @@ public class AndroidUtilities {
|
||||
android.content.ClipData clip = android.content.ClipData.newPlainText("label", str);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +645,7 @@ public class AndroidUtilities {
|
||||
mediaScanIntent.setData(uri);
|
||||
ApplicationLoader.applicationContext.sendBroadcast(mediaScanIntent);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,12 +658,12 @@ public class AndroidUtilities {
|
||||
storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Delta Chat");
|
||||
if (!storageDir.mkdirs()) {
|
||||
if (!storageDir.exists()){
|
||||
FileLog.d("messenger", "failed to create directory");
|
||||
Log.w("DeltaChat", "failed to create directory");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FileLog.d("messenger", "External storage is not mounted READ/WRITE.");
|
||||
Log.w("DeltaChat", "External storage is not mounted READ/WRITE.");
|
||||
}
|
||||
|
||||
return storageDir;
|
||||
@@ -732,7 +716,7 @@ public class AndroidUtilities {
|
||||
return uri.getPath();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -756,7 +740,7 @@ public class AndroidUtilities {
|
||||
return value;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
@@ -783,7 +767,7 @@ public class AndroidUtilities {
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
||||
return new File(storageDir, "IMG_" + timeStamp + ".jpg");
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -794,7 +778,7 @@ public class AndroidUtilities {
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
|
||||
return new File(storageDir, "VID_" + timeStamp + ".mp4");
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -824,7 +808,7 @@ public class AndroidUtilities {
|
||||
final int l = Character.digit((char) bytes[++i], 16);
|
||||
buffer.write((char) ((u << 4) + l));
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
@@ -835,7 +819,7 @@ public class AndroidUtilities {
|
||||
try {
|
||||
buffer.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return array;
|
||||
}
|
||||
@@ -863,7 +847,7 @@ public class AndroidUtilities {
|
||||
destination = new FileOutputStream(destFile);
|
||||
destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size());
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return false;
|
||||
} finally {
|
||||
if (source != null) {
|
||||
@@ -906,7 +890,7 @@ public class AndroidUtilities {
|
||||
try {
|
||||
File file = new File(path);
|
||||
if( !file.exists() ) {
|
||||
showHint(activity, LocaleController.formatString("FileNotFound", R.string.FileNotFound, path));
|
||||
showHint(activity, String.format(ApplicationLoader.applicationContext.getString(R.string.FileNotFound), path));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -933,11 +917,11 @@ public class AndroidUtilities {
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
showHint(activity, LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, mimeType));
|
||||
showHint(activity, String.format(ApplicationLoader.applicationContext.getString(R.string.NoHandleAppInstalled), mimeType));
|
||||
}
|
||||
}
|
||||
|
||||
private static File getFineFilename(File path, String desiredName)
|
||||
public static File getFineFilename(File path, String desiredName)
|
||||
{
|
||||
// get a fine file name by adding a number to the basename and avoid overwrites
|
||||
for( int i = 0; i < 1000; i++ ) {
|
||||
@@ -973,11 +957,11 @@ public class AndroidUtilities {
|
||||
final String msg_file_name = msg.getFilename();
|
||||
final File sourceFile = new File(msg_file_path);
|
||||
if( !sourceFile.exists() ) {
|
||||
showHint(context, LocaleController.formatString("FileNotFound", R.string.FileNotFound, msg_file_path));
|
||||
showHint(context, String.format(context.getString(R.string.FileNotFound), msg_file_path));
|
||||
return;
|
||||
}
|
||||
|
||||
final Toast waitingHint = showHint(context, ApplicationLoader.applicationContext.getString(R.string.OneMoment));
|
||||
final Toast waitingHint = showHint(context, context.getString(R.string.OneMoment));
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
@@ -988,7 +972,7 @@ public class AndroidUtilities {
|
||||
// get destination path
|
||||
File destPath = null;
|
||||
boolean add_to_download_manager = false;
|
||||
if (msg_type == MrMsg.MR_MSG_IMAGE || msg_type == MrMsg.MR_MSG_VIDEO) {
|
||||
if (msg_type == MrMsg.MR_MSG_IMAGE || msg_type == MrMsg.MR_MSG_GIF || msg_type == MrMsg.MR_MSG_VIDEO) {
|
||||
destPath = AndroidUtilities.getAlbumDir();
|
||||
} else if (msg_type == MrMsg.MR_MSG_FILE) {
|
||||
destPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
|
||||
@@ -33,40 +33,38 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.b44t.ui.Components.ForegroundDetector;
|
||||
import com.b44t.ui.SettingsAdvActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ApplicationLoader extends Application {
|
||||
private static PendingIntent pendingIntent;
|
||||
|
||||
private static Drawable cachedWallpaper;
|
||||
private static int selectedColor;
|
||||
private static boolean isCustomTheme;
|
||||
private static final Object sync = new Object();
|
||||
|
||||
public static volatile Context applicationContext;
|
||||
public static volatile Handler applicationHandler;
|
||||
private static volatile boolean applicationInited = false;
|
||||
|
||||
public static volatile boolean isScreenOn = false;
|
||||
public static volatile boolean mainInterfacePaused = true;
|
||||
|
||||
public static boolean isCustomTheme() {
|
||||
return isCustomTheme;
|
||||
}
|
||||
public static PowerManager.WakeLock backendWakeLock = null;
|
||||
public static PowerManager.WakeLock wakeupWakeLock = null;
|
||||
private static PowerManager.WakeLock stayAwakeWakeLock = null;
|
||||
|
||||
public static int getSelectedColor() {
|
||||
return selectedColor;
|
||||
}
|
||||
|
||||
public static int fontSize;
|
||||
|
||||
public static void reloadWallpaper() {
|
||||
cachedWallpaper = null;
|
||||
@@ -93,15 +91,12 @@ public class ApplicationLoader extends Application {
|
||||
if (selectedColor == 0) {
|
||||
if (selectedBackground == 1000001) {
|
||||
cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd);
|
||||
isCustomTheme = false;
|
||||
} else {
|
||||
File toFile = new File(getFilesDirFixed(), "wallpaper.jpg");
|
||||
if (toFile.exists()) {
|
||||
cachedWallpaper = Drawable.createFromPath(toFile.getAbsolutePath());
|
||||
isCustomTheme = true;
|
||||
} else {
|
||||
cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd);
|
||||
isCustomTheme = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,25 +133,56 @@ public class ApplicationLoader extends Application {
|
||||
path.mkdirs();
|
||||
return path;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return new File("/data/data/com.b44t.messenger/files"); // EDIT BY MR
|
||||
return new File("/data/data/com.b44t.messenger/files");
|
||||
}
|
||||
|
||||
public static void postInitApplication() {
|
||||
if (applicationInited) {
|
||||
return;
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
applicationContext = getApplicationContext(); // should be set very first as it may be needed eg. for logging
|
||||
|
||||
MrMailbox.log_i("DeltaChat", "*************** ApplicationLoader.onCreate() ***************");
|
||||
System.loadLibrary("messenger.1");
|
||||
new ForegroundDetector(this);
|
||||
applicationHandler = new Handler(applicationContext.getMainLooper());
|
||||
|
||||
// create wake locks
|
||||
try {
|
||||
PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
|
||||
|
||||
backendWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "backendWakeLock" /*any name*/);
|
||||
// bakendWakeLock _is_ reference counted by the backend (every acquire() has a release())
|
||||
|
||||
wakeupWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wakeupWakeLock" /*any name*/);
|
||||
wakeupWakeLock.setReferenceCounted(false);
|
||||
|
||||
stayAwakeWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "stayAwakeWakeLock" /*any name*/);
|
||||
stayAwakeWakeLock.setReferenceCounted(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("DeltaChat", "Cannot acquire wakeLock");
|
||||
}
|
||||
|
||||
applicationInited = true;
|
||||
//convertConfig();
|
||||
// create a MrMailbox object; as android stops the App by just killing it, we do never call MrMailboxUnref()
|
||||
// however, we may want to to have a look at onPause() eg. of activities (eg. for flushing data, if needed)
|
||||
MrMailbox.MrCallback(0, 0, 0); // do not remove this call; this makes sure, the function is not removed from build or warnings are printed!
|
||||
MrMailbox.init();
|
||||
|
||||
// start keep-alive service that restarts the app as soon it is terminated
|
||||
// (this is done by just marking the service as START_STICKY which recreates the service as
|
||||
// it goes away which also inititialized the app indirectly by calling this function)
|
||||
applicationContext.startService(new Intent(applicationContext, KeepAliveService.class));
|
||||
|
||||
// init locale
|
||||
try {
|
||||
LocaleController.getInstance();
|
||||
LocaleController.getInstance(); // this call does _not_ do nothing; in fact, it creates eg. the formatters while creating the static object.
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// track screen on/ff
|
||||
try {
|
||||
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
|
||||
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
@@ -169,49 +195,20 @@ public class ApplicationLoader extends Application {
|
||||
try {
|
||||
PowerManager pm = (PowerManager)ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
|
||||
isScreenOn = pm.isScreenOn();
|
||||
FileLog.e("messenger", "screen state = " + isScreenOn);
|
||||
//Log.i("DeltaChat", "screen state = " + isScreenOn);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
UserConfig.loadConfig();
|
||||
String deviceModel;
|
||||
String langCode;
|
||||
String appVersion;
|
||||
String systemVersion;
|
||||
String configPath = getFilesDirFixed().toString();
|
||||
|
||||
try {
|
||||
langCode = LocaleController.getLocaleStringIso639();
|
||||
deviceModel = Build.MANUFACTURER + Build.MODEL;
|
||||
PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0);
|
||||
appVersion = pInfo.versionName + " (" + pInfo.versionCode + ")";
|
||||
systemVersion = "SDK " + Build.VERSION.SDK_INT;
|
||||
} catch (Exception e) {
|
||||
langCode = "en";
|
||||
deviceModel = "Android unknown";
|
||||
appVersion = "App version unknown";
|
||||
systemVersion = "SDK " + Build.VERSION.SDK_INT;
|
||||
}
|
||||
if (langCode.trim().length() == 0) {
|
||||
langCode = "en";
|
||||
}
|
||||
if (deviceModel.trim().length() == 0) {
|
||||
deviceModel = "Android unknown";
|
||||
}
|
||||
if (appVersion.trim().length() == 0) {
|
||||
appVersion = "App version unknown";
|
||||
}
|
||||
if (systemVersion.trim().length() == 0) {
|
||||
systemVersion = "SDK Unknown";
|
||||
}
|
||||
// create a timer that wakes up the CPU from time to time
|
||||
TimerReceiver.scheduleNextAlarm();
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
boolean enablePushConnection = preferences.getBoolean("pushConnection", true);
|
||||
|
||||
if( preferences.getInt("notify2_"+MrChat.MR_CHAT_ID_DEADDROP, 666)==666 ) {
|
||||
// make sure, the notifications for the "deaddrop" dialog are muted by default
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
// make sure, the notifications for the "deaddrop" dialog are muted by default
|
||||
SharedPreferences notificationPreferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
if( notificationPreferences.getInt("notify2_"+MrChat.MR_CHAT_ID_DEADDROP, 666)==666 ) {
|
||||
SharedPreferences.Editor editor = notificationPreferences.edit();
|
||||
editor.putInt("notify2_"+MrChat.MR_CHAT_ID_DEADDROP, 2);
|
||||
editor.apply();
|
||||
}
|
||||
@@ -220,70 +217,53 @@ public class ApplicationLoader extends Application {
|
||||
// open() should be called before MessagesController.getInstance() as this also initilizes directories based upon getBlobdir().
|
||||
File dbfile = new File(getFilesDirFixed(), "messenger.db");
|
||||
MrMailbox.open(dbfile.getAbsolutePath());
|
||||
MrMailbox.connect();
|
||||
if( MrMailbox.isConfigured()!=0 ) {
|
||||
MrMailbox.connect();
|
||||
}
|
||||
|
||||
// create other default objects
|
||||
MessagesController.getInstance();
|
||||
ConnectionsManager.getInstance().init(deviceModel, systemVersion, appVersion, langCode, configPath, FileLog.getNetworkLogPath(), UserConfig.getClientUserId(), enablePushConnection);
|
||||
if (UserConfig.getCurrentUser() != null) {
|
||||
SendMessagesHelper.getInstance().checkUnsentMessages();
|
||||
}
|
||||
SharedPreferences mainPreferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
fontSize = mainPreferences.getInt("msg_font_size", SettingsAdvActivity.defMsgFontSize());
|
||||
|
||||
ImageLoader.getInstance();
|
||||
MediaController.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
applicationContext = getApplicationContext();
|
||||
NativeLoader.initNativeLibs(ApplicationLoader.applicationContext);
|
||||
//ConnectionsManager.native_setJava(Build.VERSION.SDK_INT == 14 || Build.VERSION.SDK_INT == 15);
|
||||
new ForegroundDetector(this);
|
||||
|
||||
// EDIT BY MR - create a MrMailbox object; as android stops the App by just killing it, we do never call MrMailboxUnref()
|
||||
// however, we may want to to have a look at onPause() eg. of activities (eg. for flushing data, if needed)
|
||||
MrMailbox.MrCallback(0, 0, 0); // do not remove this call; this makes sure, the function is not removed from build or warnings are printed!
|
||||
MrMailbox.init();
|
||||
|
||||
applicationHandler = new Handler(applicationContext.getMainLooper());
|
||||
|
||||
startPushService();
|
||||
}
|
||||
|
||||
public static void startPushService() {
|
||||
SharedPreferences preferences = applicationContext.getSharedPreferences("Notifications", MODE_PRIVATE);
|
||||
|
||||
if (preferences.getBoolean("pushService", true)) {
|
||||
AlarmManager am = (AlarmManager) applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent i = new Intent(applicationContext, ApplicationLoader.class);
|
||||
pendingIntent = PendingIntent.getBroadcast(applicationContext, 0, i, 0);
|
||||
|
||||
am.cancel(pendingIntent);
|
||||
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 60000, pendingIntent);
|
||||
|
||||
applicationContext.startService(new Intent(applicationContext, NotificationsService.class));
|
||||
} else {
|
||||
stopPushService();
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopPushService() {
|
||||
applicationContext.stopService(new Intent(applicationContext, NotificationsService.class));
|
||||
|
||||
PendingIntent pintent = PendingIntent.getService(applicationContext, 0, new Intent(applicationContext, NotificationsService.class), 0);
|
||||
AlarmManager alarm = (AlarmManager)applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
alarm.cancel(pintent);
|
||||
alarm.cancel(pendingIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
// this function is needed to react to changed system configuration, eg. locale or display size
|
||||
super.onConfigurationChanged(newConfig);
|
||||
try {
|
||||
// change some locale stuff that is not updated automatically, see comments in onDeviceConfigurationChange() and rebuildUiParts()
|
||||
LocaleController.getInstance().onDeviceConfigurationChange(newConfig);
|
||||
|
||||
// re-calculate some things regarding the display (the size may have changed)
|
||||
AndroidUtilities.checkDisplaySize();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static int lastClassGuid = 1;
|
||||
public static int generateClassGuid() {
|
||||
return lastClassGuid++;
|
||||
}
|
||||
|
||||
public static boolean isNetworkOnline() {
|
||||
try {
|
||||
ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo netInfo = cm.getActiveNetworkInfo();
|
||||
if (netInfo != null && netInfo.isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void stayAwakeForAMoment()
|
||||
{
|
||||
stayAwakeWakeLock.acquire(1*60*1000); // 1 Minute to wait for "after chat" messages, after that, we sleep most time, see wakeupWakeLock
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,13 @@ package com.b44t.messenger;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class AutoMessageHeardReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ApplicationLoader.postInitApplication();
|
||||
|
||||
long dialog_id = intent.getLongExtra("dialog_id", 0);
|
||||
int max_id = intent.getIntExtra("max_id", 0);
|
||||
if (dialog_id == 0 || max_id == 0) {
|
||||
|
||||
@@ -28,12 +28,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.util.Log;
|
||||
|
||||
public class AutoMessageReplyReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ApplicationLoader.postInitApplication();
|
||||
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput == null) {
|
||||
return;
|
||||
|
||||
+6
-8
@@ -1,7 +1,6 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
@@ -26,14 +25,13 @@ package com.b44t.messenger;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class AppStartReceiver extends BroadcastReceiver {
|
||||
|
||||
public class BootCompletedReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ApplicationLoader.startPushService();
|
||||
}
|
||||
});
|
||||
MrMailbox.log_i("DeltaChat", "*** BootCompletedReceiver.onReceive()");
|
||||
// there's nothing more to do here as all initialisation stuff is already done in
|
||||
// ApplicationLoader.onCreate() which is called before this broadcast is sended.
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.StructStat;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ClearCacheService extends IntentService {
|
||||
|
||||
public ClearCacheService() {
|
||||
super("ClearCacheService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
ApplicationLoader.postInitApplication();
|
||||
|
||||
/*
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
final int keepMedia = preferences.getInt("keep_media", 2);
|
||||
if (keepMedia == 2) {
|
||||
return;
|
||||
}
|
||||
Utilities.globalQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long diff = 60 * 60 * 1000 * 24 * (keepMedia == 0 ? 7 : 30);
|
||||
final HashMap<Integer, File> paths = ImageLoader.getInstance().createMediaPaths();
|
||||
for (HashMap.Entry<Integer, File> entry : paths.entrySet()) {
|
||||
if (entry.getKey() == FileLoader.MEDIA_DIR_CACHE) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
File[] array = entry.getValue().listFiles();
|
||||
if (array != null) {
|
||||
for (int b = 0; b < array.length; b++) {
|
||||
File f = array[b];
|
||||
if (f.isFile()) {
|
||||
if (f.getName().equals(".nomedia")) {
|
||||
continue;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
try {
|
||||
StructStat stat = Os.stat(f.getPath());
|
||||
if (stat.st_atime != 0) {
|
||||
if (stat.st_atime + diff < currentTime) {
|
||||
f.delete();
|
||||
}
|
||||
} else if (stat.st_mtime + diff < currentTime) {
|
||||
f.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
} else if (f.lastModified() + diff < currentTime) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.PowerManager;
|
||||
|
||||
public class ConnectionsManager {
|
||||
|
||||
public final static int RequestFlagFailOnServerErrors = 2;
|
||||
|
||||
public final static int ConnectionStateConnecting = 1;
|
||||
public final static int ConnectionStateWaitingForNetwork = 2;
|
||||
public final static int ConnectionStateConnected = 3;
|
||||
public final static int ConnectionStateUpdating = 4;
|
||||
|
||||
private long lastPauseTime = System.currentTimeMillis();
|
||||
private boolean appPaused = true;
|
||||
private int lastClassGuid = 1;
|
||||
private PowerManager.WakeLock wakeLock = null;
|
||||
|
||||
private static volatile ConnectionsManager Instance = null;
|
||||
|
||||
public static ConnectionsManager getInstance() {
|
||||
ConnectionsManager localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
synchronized (ConnectionsManager.class) {
|
||||
localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
Instance = localInstance = new ConnectionsManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return localInstance;
|
||||
}
|
||||
|
||||
public ConnectionsManager() {
|
||||
try {
|
||||
PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentTime() {
|
||||
return MrMailbox.getCurrentTime();
|
||||
}
|
||||
|
||||
public void cancelRequest(int token, boolean notifyServer) {
|
||||
//native_cancelRequest(token, notifyServer);
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
//native_cleanUp();
|
||||
}
|
||||
|
||||
public int getConnectionState() {
|
||||
return ConnectionStateWaitingForNetwork; // EDIT BY MR - we're always disconnected from the view of the caller
|
||||
/* EDIT BY MR
|
||||
if (connectionState == ConnectionStateConnected && isUpdating) {
|
||||
return ConnectionStateUpdating;
|
||||
}
|
||||
return connectionState;
|
||||
*/
|
||||
}
|
||||
|
||||
public void setPushConnectionEnabled(boolean value) {
|
||||
//native_setPushConnectionEnabled(value);
|
||||
}
|
||||
|
||||
public void init(String deviceModel, String systemVersion, String appVersion, String langCode, String configPath, String logPath, int userId, boolean enablePushConnection) {
|
||||
//native_init(version, layer, apiId, deviceModel, systemVersion, appVersion, langCode, configPath, logPath, userId, enablePushConnection);
|
||||
//checkConnection();
|
||||
BroadcastReceiver networkStateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
//checkConnection();
|
||||
}
|
||||
};
|
||||
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
ApplicationLoader.applicationContext.registerReceiver(networkStateReceiver, filter);
|
||||
}
|
||||
|
||||
public void resumeNetworkMaybe() {
|
||||
//native_resumeNetwork(true);
|
||||
}
|
||||
|
||||
public void setAppPaused(final boolean value, final boolean byScreenState) {
|
||||
if (!byScreenState) {
|
||||
appPaused = value;
|
||||
FileLog.d("messenger", "app paused = " + value);
|
||||
}
|
||||
if (value) {
|
||||
if (lastPauseTime == 0) {
|
||||
lastPauseTime = System.currentTimeMillis();
|
||||
}
|
||||
//native_pauseNetwork();
|
||||
} else {
|
||||
if (appPaused) {
|
||||
return;
|
||||
}
|
||||
FileLog.e("messenger", "reset app pause time");
|
||||
/*if (lastPauseTime != 0 && System.currentTimeMillis() - lastPauseTime > 5000) {
|
||||
ContactsController.getInstance().checkContacts();
|
||||
}*/
|
||||
lastPauseTime = 0;
|
||||
//native_resumeNetwork(false);
|
||||
}
|
||||
}
|
||||
|
||||
public int generateClassGuid() {
|
||||
return lastClassGuid++;
|
||||
}
|
||||
|
||||
/* -- leave this for future use
|
||||
public static boolean isRoaming() {
|
||||
try {
|
||||
ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo netInfo = cm.getActiveNetworkInfo();
|
||||
if (netInfo != null) {
|
||||
return netInfo.isRoaming();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
return false;
|
||||
} */
|
||||
|
||||
/* -- leave this for future use
|
||||
public static boolean isConnectedToWiFi() {
|
||||
try {
|
||||
ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
||||
if (netInfo != null && netInfo.getState() == NetworkInfo.State.CONNECTED) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
return false;
|
||||
} */
|
||||
|
||||
public static boolean isNetworkOnline() {
|
||||
try {
|
||||
ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo netInfo = cm.getActiveNetworkInfo();
|
||||
if (netInfo != null && (netInfo.isConnectedOrConnecting() || netInfo.isAvailable())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
|
||||
|
||||
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
|
||||
return true;
|
||||
} else {
|
||||
netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
||||
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class ContactsController {
|
||||
pCur.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return allContacts;
|
||||
}
|
||||
@@ -122,40 +122,6 @@ public class ContactsController {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String formatName(String firstName, String lastName) {
|
||||
|
||||
if (firstName != null) {
|
||||
firstName = firstName.trim();
|
||||
}
|
||||
if (lastName != null) {
|
||||
lastName = lastName.trim();
|
||||
}
|
||||
StringBuilder result = new StringBuilder((firstName != null ? firstName.length() : 0) + (lastName != null ? lastName.length() : 0) + 1);
|
||||
if (LocaleController.nameDisplayOrder == 1) {
|
||||
if (firstName != null && firstName.length() > 0) {
|
||||
result.append(firstName);
|
||||
if (lastName != null && lastName.length() > 0) {
|
||||
result.append(" ");
|
||||
result.append(lastName);
|
||||
}
|
||||
} else if (lastName != null && lastName.length() > 0) {
|
||||
result.append(lastName);
|
||||
}
|
||||
} else {
|
||||
if (lastName != null && lastName.length() > 0) {
|
||||
result.append(lastName);
|
||||
if (firstName != null && firstName.length() > 0) {
|
||||
result.append(" ");
|
||||
result.append(firstName);
|
||||
}
|
||||
} else if (firstName != null && firstName.length() > 0) {
|
||||
result.append(firstName);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
/* Handle contact images
|
||||
**********************************************************************************************/
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class DispatchQueue extends Thread {
|
||||
handler.sendMessageDelayed(msg, delay);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -58,7 +58,7 @@ public class DispatchQueue extends Thread {
|
||||
syncLatch.await();
|
||||
handler.removeCallbacks(runnable);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class DispatchQueue extends Thread {
|
||||
handler.postDelayed(runnable, delay);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class DispatchQueue extends Thread {
|
||||
syncLatch.await();
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -117,7 +117,7 @@ public class Emoji {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ public class Emoji {
|
||||
}
|
||||
});
|
||||
} catch (Throwable x) {
|
||||
FileLog.e("messenger", "Error loading emoji", x);
|
||||
//Log.i("DeltaChat", "Error loading emoji", x);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class Emoji {
|
||||
public static EmojiDrawable getEmojiDrawable(CharSequence code) {
|
||||
DrawableInfo info = rects.get(code);
|
||||
if (info == null) {
|
||||
FileLog.e("messenger", "No drawable for emoji " + code);
|
||||
//Log.i("DeltaChat", "No drawable for emoji " + code);
|
||||
return null;
|
||||
}
|
||||
EmojiDrawable ed = new EmojiDrawable(info);
|
||||
@@ -434,7 +434,7 @@ public class Emoji {
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// FileLog.e("messenger", e);
|
||||
//
|
||||
// return cs;
|
||||
// }
|
||||
return s;
|
||||
|
||||
@@ -1,443 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class FileLoadOperation {
|
||||
|
||||
private static class RequestInfo {
|
||||
private int requestToken;
|
||||
private int offset;
|
||||
}
|
||||
|
||||
private final static int stateIdle = 0;
|
||||
private final static int stateDownloading = 1;
|
||||
private final static int stateFailed = 2;
|
||||
private final static int stateFinished = 3;
|
||||
|
||||
private final static int downloadChunkSize = 1024 * 32;
|
||||
private final static int downloadChunkSizeBig = 1024 * 128;
|
||||
private final static int maxDownloadRequests = 4;
|
||||
private final static int maxDownloadRequestsBig = 2;
|
||||
private final static int bigFileSizeFrom = 1024 * 1024;
|
||||
|
||||
private int datacenter_id;
|
||||
private TLRPC.InputFileLocation location;
|
||||
private volatile int state = stateIdle;
|
||||
private int downloadedBytes;
|
||||
private int totalBytesCount;
|
||||
private int bytesCountPadding;
|
||||
private FileLoadOperationDelegate delegate;
|
||||
private byte[] key;
|
||||
private byte[] iv;
|
||||
private int currentDownloadChunkSize;
|
||||
private int currentMaxDownloadRequests;
|
||||
private int requestsCount;
|
||||
private int renameRetryCount;
|
||||
|
||||
private int nextDownloadOffset;
|
||||
private ArrayList<RequestInfo> requestInfos;
|
||||
private ArrayList<RequestInfo> delayedRequestInfos;
|
||||
|
||||
private File cacheFileTemp;
|
||||
private File cacheFileFinal;
|
||||
private File cacheIvTemp;
|
||||
|
||||
private String ext;
|
||||
private RandomAccessFile fileOutputStream;
|
||||
private RandomAccessFile fiv;
|
||||
private File storePath;
|
||||
private File tempPath;
|
||||
private boolean isForceRequest;
|
||||
|
||||
public interface FileLoadOperationDelegate {
|
||||
void didFinishLoadingFile(FileLoadOperation operation, File finalFile);
|
||||
void didFailedLoadingFile(FileLoadOperation operation, int state);
|
||||
void didChangedLoadProgress(FileLoadOperation operation, float progress);
|
||||
}
|
||||
|
||||
public FileLoadOperation(TLRPC.FileLocation photoLocation, String extension, int size) {
|
||||
/*if (photoLocation instanceof TLRPC.TL_fileEncryptedLocation) {
|
||||
location = new TLRPC.TL_inputEncryptedFileLocation();
|
||||
location.id = photoLocation.volume_id;
|
||||
location.volume_id = photoLocation.volume_id;
|
||||
location.access_hash = photoLocation.secret;
|
||||
location.local_id = photoLocation.local_id;
|
||||
iv = new byte[32];
|
||||
System.arraycopy(photoLocation.iv, 0, iv, 0, iv.length);
|
||||
key = photoLocation.key;
|
||||
datacenter_id = photoLocation.dc_id;
|
||||
} else*/ if (photoLocation instanceof TLRPC.TL_fileLocation) {
|
||||
location = new TLRPC.TL_inputFileLocation();
|
||||
location.volume_id = photoLocation.volume_id;
|
||||
location.local_id = photoLocation.local_id;
|
||||
datacenter_id = photoLocation.dc_id;
|
||||
}
|
||||
totalBytesCount = size;
|
||||
ext = extension != null ? extension : "jpg";
|
||||
}
|
||||
|
||||
public FileLoadOperation(TLRPC.Document documentLocation) {
|
||||
try {
|
||||
/*if (documentLocation instanceof TLRPC.TL_documentEncrypted) {
|
||||
location = new TLRPC.TL_inputEncryptedFileLocation();
|
||||
location.id = documentLocation.id;
|
||||
location.access_hash = documentLocation.access_hash;
|
||||
datacenter_id = documentLocation.dc_id;
|
||||
iv = new byte[32];
|
||||
System.arraycopy(documentLocation.iv, 0, iv, 0, iv.length);
|
||||
key = documentLocation.key;
|
||||
} else*/ if (documentLocation instanceof TLRPC.TL_document) {
|
||||
location = new TLRPC.TL_inputDocumentFileLocation();
|
||||
location.id = documentLocation.id;
|
||||
location.access_hash = documentLocation.access_hash;
|
||||
datacenter_id = documentLocation.dc_id;
|
||||
}
|
||||
totalBytesCount = documentLocation.size;
|
||||
if (key != null) {
|
||||
int toAdd = 0;
|
||||
if (totalBytesCount % 16 != 0) {
|
||||
bytesCountPadding = 16 - totalBytesCount % 16;
|
||||
totalBytesCount += bytesCountPadding;
|
||||
}
|
||||
}
|
||||
ext = FileLoader.getDocumentFileName(documentLocation);
|
||||
int idx;
|
||||
if (ext == null || (idx = ext.lastIndexOf('.')) == -1) {
|
||||
ext = "";
|
||||
} else {
|
||||
ext = ext.substring(idx);
|
||||
}
|
||||
if (ext.length() <= 1) {
|
||||
if (documentLocation.mime_type != null) {
|
||||
switch (documentLocation.mime_type) {
|
||||
case "video/mp4":
|
||||
ext = ".mp4";
|
||||
break;
|
||||
case "audio/ogg":
|
||||
ext = ".ogg";
|
||||
break;
|
||||
default:
|
||||
ext = "";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ext = "";
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
state = stateFailed;
|
||||
cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setForceRequest(boolean forceRequest) {
|
||||
isForceRequest = forceRequest;
|
||||
}
|
||||
|
||||
public boolean isForceRequest() {
|
||||
return isForceRequest;
|
||||
}
|
||||
|
||||
public void setPaths(File store, File temp) {
|
||||
storePath = store;
|
||||
tempPath = temp;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (state != stateIdle) {
|
||||
return;
|
||||
}
|
||||
currentDownloadChunkSize = totalBytesCount >= bigFileSizeFrom ? downloadChunkSizeBig : downloadChunkSize;
|
||||
currentMaxDownloadRequests = totalBytesCount >= bigFileSizeFrom ? maxDownloadRequestsBig : maxDownloadRequests;
|
||||
requestInfos = new ArrayList<>(currentMaxDownloadRequests);
|
||||
delayedRequestInfos = new ArrayList<>(currentMaxDownloadRequests - 1);
|
||||
state = stateDownloading;
|
||||
if (location == null) {
|
||||
cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
String fileNameFinal;
|
||||
String fileNameTemp;
|
||||
String fileNameIv = null;
|
||||
if (location.volume_id != 0 && location.local_id != 0) {
|
||||
fileNameTemp = location.volume_id + "_" + location.local_id + ".temp";
|
||||
fileNameFinal = location.volume_id + "_" + location.local_id + "." + ext;
|
||||
if (key != null) {
|
||||
fileNameIv = location.volume_id + "_" + location.local_id + ".iv";
|
||||
}
|
||||
if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) {
|
||||
cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
fileNameTemp = datacenter_id + "_" + location.id + ".temp";
|
||||
fileNameFinal = datacenter_id + "_" + location.id + ext;
|
||||
if (key != null) {
|
||||
fileNameIv = datacenter_id + "_" + location.id + ".iv";
|
||||
}
|
||||
if (datacenter_id == 0 || location.id == 0) {
|
||||
cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cacheFileFinal = new File(storePath, fileNameFinal);
|
||||
boolean exist = cacheFileFinal.exists();
|
||||
if (exist && totalBytesCount != 0 && totalBytesCount != cacheFileFinal.length()) {
|
||||
cacheFileFinal.delete();
|
||||
}
|
||||
|
||||
if (!cacheFileFinal.exists()) {
|
||||
cacheFileTemp = new File(tempPath, fileNameTemp);
|
||||
if (cacheFileTemp.exists()) {
|
||||
downloadedBytes = (int) cacheFileTemp.length();
|
||||
nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize;
|
||||
}
|
||||
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.d("messenger", "start loading file to temp = " + cacheFileTemp + " final = " + cacheFileFinal);
|
||||
}
|
||||
|
||||
if (fileNameIv != null) {
|
||||
cacheIvTemp = new File(tempPath, fileNameIv);
|
||||
try {
|
||||
fiv = new RandomAccessFile(cacheIvTemp, "rws");
|
||||
long len = cacheIvTemp.length();
|
||||
if (len > 0 && len % 32 == 0) {
|
||||
fiv.read(iv, 0, 32);
|
||||
} else {
|
||||
downloadedBytes = 0;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
downloadedBytes = 0;
|
||||
}
|
||||
}
|
||||
try {
|
||||
fileOutputStream = new RandomAccessFile(cacheFileTemp, "rws");
|
||||
if (downloadedBytes != 0) {
|
||||
fileOutputStream.seek(downloadedBytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
if (fileOutputStream == null) {
|
||||
cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (totalBytesCount != 0 && downloadedBytes == totalBytesCount) {
|
||||
try {
|
||||
onFinishLoadingFile();
|
||||
} catch (Exception e) {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
} else {
|
||||
startDownloadRequest();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
onFinishLoadingFile();
|
||||
} catch (Exception e) {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state == stateFinished || state == stateFailed) {
|
||||
return;
|
||||
}
|
||||
state = stateFailed;
|
||||
cleanup();
|
||||
if (requestInfos != null) {
|
||||
for (int a = 0; a < requestInfos.size(); a++) {
|
||||
RequestInfo requestInfo = requestInfos.get(a);
|
||||
if (requestInfo.requestToken != 0) {
|
||||
ConnectionsManager.getInstance().cancelRequest(requestInfo.requestToken, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
try {
|
||||
if (fileOutputStream != null) {
|
||||
try {
|
||||
fileOutputStream.getChannel().close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
fileOutputStream.close();
|
||||
fileOutputStream = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (fiv != null) {
|
||||
fiv.close();
|
||||
fiv = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
if (delayedRequestInfos != null) {
|
||||
for (int a = 0; a < delayedRequestInfos.size(); a++) {
|
||||
RequestInfo requestInfo = delayedRequestInfos.get(a);
|
||||
/*if (requestInfo.response != null) {
|
||||
requestInfo.response.disableFree = false;
|
||||
requestInfo.response.freeResources();
|
||||
}*/
|
||||
}
|
||||
delayedRequestInfos.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void onFinishLoadingFile() throws Exception {
|
||||
if (state != stateDownloading) {
|
||||
return;
|
||||
}
|
||||
state = stateFinished;
|
||||
cleanup();
|
||||
if (cacheIvTemp != null) {
|
||||
cacheIvTemp.delete();
|
||||
cacheIvTemp = null;
|
||||
}
|
||||
if (cacheFileTemp != null) {
|
||||
boolean renameResult = cacheFileTemp.renameTo(cacheFileFinal);
|
||||
if (!renameResult) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.e("messenger", "unable to rename temp = " + cacheFileTemp + " to final = " + cacheFileFinal + " retry = " + renameRetryCount);
|
||||
}
|
||||
renameRetryCount++;
|
||||
if (renameRetryCount < 3) {
|
||||
state = stateDownloading;
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
onFinishLoadingFile();
|
||||
} catch (Exception e) {
|
||||
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
cacheFileFinal = cacheFileTemp;
|
||||
}
|
||||
}
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.e("messenger", "finished downloading file to " + cacheFileFinal);
|
||||
}
|
||||
delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void startDownloadRequest() {
|
||||
if (state != stateDownloading || totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount || requestInfos.size() + delayedRequestInfos.size() >= currentMaxDownloadRequests) {
|
||||
return;
|
||||
}
|
||||
int count = 1;
|
||||
if (totalBytesCount > 0) {
|
||||
count = Math.max(0, currentMaxDownloadRequests - requestInfos.size() - delayedRequestInfos.size());
|
||||
}
|
||||
|
||||
for (int a = 0; a < count; a++) {
|
||||
if (totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount) {
|
||||
break;
|
||||
}
|
||||
boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + currentDownloadChunkSize >= totalBytesCount;
|
||||
//TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
|
||||
//req.location = location;
|
||||
//req.offset = nextDownloadOffset;
|
||||
//req.limit = currentDownloadChunkSize;
|
||||
nextDownloadOffset += currentDownloadChunkSize;
|
||||
|
||||
final RequestInfo requestInfo = new RequestInfo();
|
||||
requestInfos.add(requestInfo);
|
||||
requestInfo.offset = nextDownloadOffset;
|
||||
requestInfo.requestToken = 0;/*ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
requestInfo.response = (TLRPC.TL_upload_file) response;
|
||||
processRequestResult(requestInfo, error);
|
||||
}
|
||||
}, null, (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors, datacenter_id, requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2, isLast);*/
|
||||
requestsCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDelegate(FileLoadOperationDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
}
|
||||
@@ -32,15 +32,6 @@ import java.util.concurrent.Semaphore;
|
||||
|
||||
public class FileLoader {
|
||||
|
||||
public interface FileLoaderDelegate {
|
||||
|
||||
void fileDidLoaded(String location, File finalFile, int type);
|
||||
|
||||
void fileDidFailedLoad(String location, int state);
|
||||
|
||||
void fileLoadProgressChanged(String location, float progress);
|
||||
}
|
||||
|
||||
public static final int MEDIA_DIR_IMAGE = 0;
|
||||
public static final int MEDIA_DIR_AUDIO = 1;
|
||||
public static final int MEDIA_DIR_VIDEO = 2;
|
||||
@@ -48,18 +39,6 @@ public class FileLoader {
|
||||
public static final int MEDIA_DIR_CACHE = 4;
|
||||
|
||||
private HashMap<Integer, File> mediaDirs = null;
|
||||
private volatile DispatchQueue fileLoaderQueue = new DispatchQueue("fileUploadQueue");
|
||||
|
||||
private LinkedList<FileLoadOperation> loadOperationQueue = new LinkedList<>();
|
||||
private LinkedList<FileLoadOperation> audioLoadOperationQueue = new LinkedList<>();
|
||||
private LinkedList<FileLoadOperation> photoLoadOperationQueue = new LinkedList<>();
|
||||
private ConcurrentHashMap<String, FileLoadOperation> loadOperationPaths = new ConcurrentHashMap<>();
|
||||
|
||||
private FileLoaderDelegate delegate = null;
|
||||
|
||||
private int currentLoadOperationsCount = 0;
|
||||
private int currentAudioLoadOperationsCount = 0;
|
||||
private int currentPhotoLoadOperationsCount = 0;
|
||||
|
||||
private static volatile FileLoader Instance = null;
|
||||
|
||||
@@ -95,257 +74,8 @@ public class FileLoader {
|
||||
return dir;
|
||||
}
|
||||
|
||||
public void cancelLoadFile(TLRPC.Document document) {
|
||||
cancelLoadFile(document, null, null);
|
||||
}
|
||||
|
||||
public void cancelLoadFile(TLRPC.PhotoSize photo) {
|
||||
cancelLoadFile(null, photo.location, null);
|
||||
}
|
||||
|
||||
public void cancelLoadFile(TLRPC.FileLocation location, String ext) {
|
||||
cancelLoadFile(null, location, ext);
|
||||
}
|
||||
|
||||
private void cancelLoadFile(final TLRPC.Document document, final TLRPC.FileLocation location, final String locationExt) {
|
||||
if (location == null && document == null) {
|
||||
return;
|
||||
}
|
||||
fileLoaderQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String fileName = null;
|
||||
if (location != null) {
|
||||
fileName = getAttachFileName(location, locationExt);
|
||||
} else if (document != null) {
|
||||
fileName = getAttachFileName(document);
|
||||
}
|
||||
if (fileName == null) {
|
||||
return;
|
||||
}
|
||||
FileLoadOperation operation = loadOperationPaths.remove(fileName);
|
||||
if (operation != null) {
|
||||
if (MessageObject.isVoiceDocument(document)) {
|
||||
audioLoadOperationQueue.remove(operation);
|
||||
} else if (location != null) {
|
||||
photoLoadOperationQueue.remove(operation);
|
||||
} else {
|
||||
loadOperationQueue.remove(operation);
|
||||
}
|
||||
operation.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isLoadingFile(final String fileName) {
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
final Boolean[] result = new Boolean[1];
|
||||
fileLoaderQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
result[0] = loadOperationPaths.containsKey(fileName);
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
try {
|
||||
semaphore.acquire();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
public void loadFile(TLRPC.PhotoSize photo, String ext, boolean cacheOnly) {
|
||||
loadFile(null, photo.location, ext, photo.size, false, cacheOnly || (photo != null && photo.size == 0 || photo.location.key != null));
|
||||
}
|
||||
|
||||
public void loadFile(TLRPC.Document document, boolean force, boolean cacheOnly) {
|
||||
loadFile(document, null, null, 0, force, cacheOnly || document != null && document.key != null);
|
||||
}
|
||||
|
||||
public void loadFile(TLRPC.FileLocation location, String ext, int size, boolean cacheOnly) {
|
||||
loadFile(null, location, ext, size, true, cacheOnly || size == 0 || (location != null && location.key != null));
|
||||
}
|
||||
|
||||
private void loadFile(final TLRPC.Document document, final TLRPC.FileLocation location, final String locationExt, final int locationSize, final boolean force, final boolean cacheOnly) {
|
||||
fileLoaderQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String fileName = null;
|
||||
if (location != null) {
|
||||
fileName = getAttachFileName(location, locationExt);
|
||||
} else if (document != null) {
|
||||
fileName = getAttachFileName(document);
|
||||
}
|
||||
if (fileName == null || fileName.contains("" + Integer.MIN_VALUE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileLoadOperation operation;
|
||||
operation = loadOperationPaths.get(fileName);
|
||||
if (operation != null) {
|
||||
if (force) {
|
||||
LinkedList<FileLoadOperation> downloadQueue;
|
||||
if (MessageObject.isVoiceDocument(document)) {
|
||||
downloadQueue = audioLoadOperationQueue;
|
||||
} else if (location != null) {
|
||||
downloadQueue = photoLoadOperationQueue;
|
||||
} else {
|
||||
downloadQueue = loadOperationQueue;
|
||||
}
|
||||
if (downloadQueue != null) {
|
||||
int index = downloadQueue.indexOf(operation);
|
||||
if (index != -1) {
|
||||
downloadQueue.remove(index);
|
||||
downloadQueue.add(0, operation);
|
||||
operation.setForceRequest(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
File tempDir = getDirectory(MEDIA_DIR_CACHE);
|
||||
File storeDir = tempDir;
|
||||
int type = MEDIA_DIR_CACHE;
|
||||
|
||||
if (location != null) {
|
||||
operation = new FileLoadOperation(location, locationExt, locationSize);
|
||||
type = MEDIA_DIR_IMAGE;
|
||||
} else if (document != null) {
|
||||
operation = new FileLoadOperation(document);
|
||||
if (MessageObject.isVoiceDocument(document)) {
|
||||
type = MEDIA_DIR_AUDIO;
|
||||
} else if (MessageObject.isVideoDocument(document)) {
|
||||
type = MEDIA_DIR_VIDEO;
|
||||
} else {
|
||||
type = MEDIA_DIR_DOCUMENT;
|
||||
}
|
||||
}
|
||||
if (!cacheOnly) {
|
||||
storeDir = getDirectory(type);
|
||||
}
|
||||
operation.setPaths(storeDir, tempDir);
|
||||
|
||||
final String finalFileName = fileName;
|
||||
final int finalType = type;
|
||||
loadOperationPaths.put(fileName, operation);
|
||||
operation.setDelegate(new FileLoadOperation.FileLoadOperationDelegate() {
|
||||
@Override
|
||||
public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) {
|
||||
if (delegate != null) {
|
||||
delegate.fileDidLoaded(finalFileName, finalFile, finalType);
|
||||
}
|
||||
checkDownloadQueue(document, location, finalFileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didFailedLoadingFile(FileLoadOperation operation, int canceled) {
|
||||
checkDownloadQueue(document, location, finalFileName);
|
||||
if (delegate != null) {
|
||||
delegate.fileDidFailedLoad(finalFileName, canceled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangedLoadProgress(FileLoadOperation operation, float progress) {
|
||||
if (delegate != null) {
|
||||
delegate.fileLoadProgressChanged(finalFileName, progress);
|
||||
}
|
||||
}
|
||||
});
|
||||
int maxCount = force ? 3 : 1;
|
||||
if (type == MEDIA_DIR_AUDIO) {
|
||||
if (currentAudioLoadOperationsCount < maxCount) {
|
||||
currentAudioLoadOperationsCount++;
|
||||
operation.start();
|
||||
} else {
|
||||
if (force) {
|
||||
audioLoadOperationQueue.add(0, operation);
|
||||
} else {
|
||||
audioLoadOperationQueue.add(operation);
|
||||
}
|
||||
}
|
||||
} else if (location != null) {
|
||||
if (currentPhotoLoadOperationsCount < maxCount) {
|
||||
currentPhotoLoadOperationsCount++;
|
||||
operation.start();
|
||||
} else {
|
||||
if (force) {
|
||||
photoLoadOperationQueue.add(0, operation);
|
||||
} else {
|
||||
photoLoadOperationQueue.add(operation);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentLoadOperationsCount < maxCount) {
|
||||
currentLoadOperationsCount++;
|
||||
operation.start();
|
||||
} else {
|
||||
if (force) {
|
||||
loadOperationQueue.add(0, operation);
|
||||
} else {
|
||||
loadOperationQueue.add(operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkDownloadQueue(final TLRPC.Document document, final TLRPC.FileLocation location, final String arg1) {
|
||||
fileLoaderQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadOperationPaths.remove(arg1);
|
||||
FileLoadOperation operation;
|
||||
if (MessageObject.isVoiceDocument(document)) {
|
||||
currentAudioLoadOperationsCount--;
|
||||
if (!audioLoadOperationQueue.isEmpty()) {
|
||||
operation = audioLoadOperationQueue.get(0);
|
||||
int maxCount = operation.isForceRequest() ? 3 : 1;
|
||||
if (currentAudioLoadOperationsCount < maxCount) {
|
||||
operation = audioLoadOperationQueue.poll();
|
||||
if (operation != null) {
|
||||
currentAudioLoadOperationsCount++;
|
||||
operation.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (location != null) {
|
||||
currentPhotoLoadOperationsCount--;
|
||||
if (!photoLoadOperationQueue.isEmpty()) {
|
||||
operation = photoLoadOperationQueue.get(0);
|
||||
int maxCount = operation.isForceRequest() ? 3 : 1;
|
||||
if (currentPhotoLoadOperationsCount < maxCount) {
|
||||
operation = photoLoadOperationQueue.poll();
|
||||
if (operation != null) {
|
||||
currentPhotoLoadOperationsCount++;
|
||||
operation.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentLoadOperationsCount--;
|
||||
if (!loadOperationQueue.isEmpty()) {
|
||||
operation = loadOperationQueue.get(0);
|
||||
int maxCount = operation.isForceRequest() ? 3 : 1;
|
||||
if (currentLoadOperationsCount < maxCount) {
|
||||
operation = loadOperationQueue.poll();
|
||||
if (operation != null) {
|
||||
currentLoadOperationsCount++;
|
||||
operation.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setDelegate(FileLoaderDelegate delegate) {
|
||||
this.delegate = delegate;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getMessageFileName(TLRPC.Message message) {
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.b44t.messenger.time.FastDateFormat;
|
||||
|
||||
import java.io.File;
|
||||
@@ -37,7 +35,6 @@ public class FileLog {
|
||||
private FastDateFormat dateFormat = null;
|
||||
private DispatchQueue logQueue = null;
|
||||
private File currentFile = null;
|
||||
private File networkFile = null;
|
||||
|
||||
private static volatile FileLog Instance = null;
|
||||
public static FileLog getInstance() {
|
||||
@@ -54,10 +51,7 @@ public class FileLog {
|
||||
}
|
||||
|
||||
public FileLog() {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
dateFormat = FastDateFormat.getInstance("dd_MM_yyyy_HH_mm_ss", Locale.US);
|
||||
dateFormat = FastDateFormat.getInstance("yyyyMMdd_HHmmss", Locale.US);
|
||||
try {
|
||||
File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null);
|
||||
if (sdCard == null) {
|
||||
@@ -81,37 +75,14 @@ public class FileLog {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNetworkLogPath() {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null);
|
||||
if (sdCard == null) {
|
||||
return "";
|
||||
}
|
||||
File dir = new File(sdCard.getAbsolutePath() + "/logs");
|
||||
dir.mkdirs();
|
||||
getInstance().networkFile = new File(dir, getInstance().dateFormat.format(System.currentTimeMillis()) + "_net.txt");
|
||||
return getInstance().networkFile.getAbsolutePath();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void e(final String tag, final String message, final Throwable exception) {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
Log.e(tag, message, exception);
|
||||
private static void log(final String what, final String tag, final String message)
|
||||
{
|
||||
if (getInstance().streamWriter != null) {
|
||||
getInstance().logQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + message + "\n");
|
||||
getInstance().streamWriter.write(exception.toString());
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " " + what + "/" + tag + ": " + message + "\n");
|
||||
getInstance().streamWriter.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -122,109 +93,14 @@ public class FileLog {
|
||||
}
|
||||
|
||||
public static void e(final String tag, final String message) {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
Log.e(tag, message);
|
||||
if (getInstance().streamWriter != null) {
|
||||
getInstance().logQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + message + "\n");
|
||||
getInstance().streamWriter.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(final String tag, final Throwable e) {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
e.printStackTrace();
|
||||
if (getInstance().streamWriter != null) {
|
||||
getInstance().logQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + e + "\n");
|
||||
StackTraceElement[] stack = e.getStackTrace();
|
||||
for (int a = 0; a < stack.length; a++) {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + stack[a] + "\n");
|
||||
}
|
||||
getInstance().streamWriter.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void d(final String tag, final String message) {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
Log.d(tag, message);
|
||||
if (getInstance().streamWriter != null) {
|
||||
getInstance().logQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " D/" + tag + "﹕ " + message + "\n");
|
||||
getInstance().streamWriter.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
log("E", tag, message);
|
||||
}
|
||||
|
||||
public static void w(final String tag, final String message) {
|
||||
if (!BuildVars.DEBUG_VERSION) {
|
||||
return;
|
||||
}
|
||||
Log.w(tag, message);
|
||||
if (getInstance().streamWriter != null) {
|
||||
getInstance().logQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/" + tag + ": " + message + "\n");
|
||||
getInstance().streamWriter.flush();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
log("W", tag, message);
|
||||
}
|
||||
|
||||
public static void cleanupLogs() {
|
||||
File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null);
|
||||
if (sdCard == null) {
|
||||
return;
|
||||
}
|
||||
File dir = new File (sdCard.getAbsolutePath() + "/logs");
|
||||
File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (int a = 0; a < files.length; a++) {
|
||||
File file = files[a];
|
||||
if (getInstance().currentFile != null && file.getAbsolutePath().equals(getInstance().currentFile.getAbsolutePath())) {
|
||||
continue;
|
||||
}
|
||||
if (getInstance().networkFile != null && file.getAbsolutePath().equals(getInstance().networkFile.getAbsolutePath())) {
|
||||
continue;
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
public static void i(final String tag, final String message) {
|
||||
log("I", tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.media.ExifInterface;
|
||||
import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
@@ -47,20 +46,10 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ImageLoader {
|
||||
|
||||
@@ -71,29 +60,20 @@ public class ImageLoader {
|
||||
private HashMap<Integer, CacheImage> imageLoadingByTag = new HashMap<>();
|
||||
private HashMap<String, ThumbGenerateInfo> waitingForQualityThumb = new HashMap<>();
|
||||
private HashMap<Integer, String> waitingForQualityThumbByTag = new HashMap<>();
|
||||
private LinkedList<HttpImageTask> httpTasks = new LinkedList<>();
|
||||
private DispatchQueue cacheOutQueue = new DispatchQueue("cacheOutQueue");
|
||||
private DispatchQueue cacheThumbOutQueue = new DispatchQueue("cacheThumbOutQueue");
|
||||
private DispatchQueue thumbGeneratingQueue = new DispatchQueue("thumbGeneratingQueue");
|
||||
private DispatchQueue imageLoadQueue = new DispatchQueue("imageLoadQueue");
|
||||
private ConcurrentHashMap<String, Float> fileProgresses = new ConcurrentHashMap<>();
|
||||
private HashMap<String, ThumbGenerateTask> thumbGenerateTasks = new HashMap<>();
|
||||
private static byte[] bytes;
|
||||
private static byte[] bytesThumb;
|
||||
private static byte[] header = new byte[12];
|
||||
private static byte[] headerThumb = new byte[12];
|
||||
private int currentHttpTasksCount = 0;
|
||||
|
||||
private LinkedList<HttpFileTask> httpFileLoadTasks = new LinkedList<>();
|
||||
private HashMap<String, HttpFileTask> httpFileLoadTasksByKeys = new HashMap<>();
|
||||
private HashMap<String, Runnable> retryHttpsTasks = new HashMap<>();
|
||||
private int currentHttpFileLoadTasksCount = 0;
|
||||
|
||||
private String ignoreRemoval = null;
|
||||
|
||||
private volatile long lastCacheOutTime = 0;
|
||||
private int lastImageNum = 0;
|
||||
private long lastProgressUpdateTime = 0;
|
||||
|
||||
private class ThumbGenerateInfo {
|
||||
private int count;
|
||||
@@ -101,343 +81,6 @@ public class ImageLoader {
|
||||
private String filter;
|
||||
}
|
||||
|
||||
private class HttpFileTask extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
private String url;
|
||||
private File tempFile;
|
||||
private String ext;
|
||||
private RandomAccessFile fileOutputStream = null;
|
||||
private boolean canRetry = true;
|
||||
|
||||
public HttpFileTask(String url, File tempFile, String ext) {
|
||||
this.url = url;
|
||||
this.tempFile = tempFile;
|
||||
this.ext = ext;
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
InputStream httpConnectionStream = null;
|
||||
boolean done = false;
|
||||
|
||||
URLConnection httpConnection = null;
|
||||
try {
|
||||
URL downloadUrl = new URL(url);
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
|
||||
httpConnection.addRequestProperty("Referer", "google.com");
|
||||
httpConnection.setConnectTimeout(5000);
|
||||
httpConnection.setReadTimeout(5000);
|
||||
if (httpConnection instanceof HttpURLConnection) {
|
||||
HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection;
|
||||
httpURLConnection.setInstanceFollowRedirects(true);
|
||||
int status = httpURLConnection.getResponseCode();
|
||||
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
String newUrl = httpURLConnection.getHeaderField("Location");
|
||||
String cookies = httpURLConnection.getHeaderField("Set-Cookie");
|
||||
downloadUrl = new URL(newUrl);
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.setRequestProperty("Cookie", cookies);
|
||||
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
|
||||
httpConnection.addRequestProperty("Referer", "google.com");
|
||||
}
|
||||
}
|
||||
httpConnection.connect();
|
||||
httpConnectionStream = httpConnection.getInputStream();
|
||||
|
||||
fileOutputStream = new RandomAccessFile(tempFile, "rws");
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof UnknownHostException) {
|
||||
canRetry = false;
|
||||
}
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
if (canRetry) {
|
||||
try {
|
||||
if (httpConnection != null && httpConnection instanceof HttpURLConnection) {
|
||||
int code = ((HttpURLConnection) httpConnection).getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
canRetry = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
if (httpConnectionStream != null) {
|
||||
try {
|
||||
byte[] data = new byte[1024 * 4];
|
||||
while (true) {
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
int read = httpConnectionStream.read(data);
|
||||
if (read > 0) {
|
||||
fileOutputStream.write(data, 0, read);
|
||||
} else if (read == -1) {
|
||||
done = true;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
fileOutputStream = null;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (httpConnectionStream != null) {
|
||||
httpConnectionStream.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
runHttpFileLoadTasks(this, result ? 2 : 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
runHttpFileLoadTasks(this, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private class HttpImageTask extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
private CacheImage cacheImage = null;
|
||||
private RandomAccessFile fileOutputStream = null;
|
||||
private int imageSize;
|
||||
private long lastProgressTime;
|
||||
private boolean canRetry = true;
|
||||
private URLConnection httpConnection = null;
|
||||
|
||||
public HttpImageTask(CacheImage cacheImage, int size) {
|
||||
this.cacheImage = cacheImage;
|
||||
imageSize = size;
|
||||
}
|
||||
|
||||
private void reportProgress(final float progress) {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (progress == 1 || lastProgressTime == 0 || lastProgressTime < currentTime - 500) {
|
||||
lastProgressTime = currentTime;
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fileProgresses.put(cacheImage.url, progress);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, cacheImage.url, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
InputStream httpConnectionStream = null;
|
||||
boolean done = false;
|
||||
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
URL downloadUrl = new URL(cacheImage.httpUrl);
|
||||
httpConnection = downloadUrl.openConnection();
|
||||
httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36");
|
||||
httpConnection.addRequestProperty("Referer", "google.com");
|
||||
httpConnection.setConnectTimeout(5000);
|
||||
httpConnection.setReadTimeout(5000);
|
||||
if (httpConnection instanceof HttpURLConnection) {
|
||||
((HttpURLConnection) httpConnection).setInstanceFollowRedirects(true);
|
||||
}
|
||||
if (!isCancelled()) {
|
||||
httpConnection.connect();
|
||||
httpConnectionStream = httpConnection.getInputStream();
|
||||
fileOutputStream = new RandomAccessFile(cacheImage.tempFilePath, "rws");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof SocketTimeoutException) {
|
||||
if (ConnectionsManager.isNetworkOnline()) {
|
||||
canRetry = false;
|
||||
}
|
||||
} else if (e instanceof UnknownHostException) {
|
||||
canRetry = false;
|
||||
} else if (e instanceof SocketException) {
|
||||
if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) {
|
||||
canRetry = false;
|
||||
}
|
||||
}
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
if (httpConnection != null && httpConnection instanceof HttpURLConnection) {
|
||||
int code = ((HttpURLConnection) httpConnection).getResponseCode();
|
||||
if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
canRetry = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
if (imageSize == 0 && httpConnection != null) {
|
||||
try {
|
||||
Map<String, List<String>> headerFields = httpConnection.getHeaderFields();
|
||||
if (headerFields != null) {
|
||||
List values = headerFields.get("content-Length");
|
||||
if (values != null && !values.isEmpty()) {
|
||||
String length = (String) values.get(0);
|
||||
if (length != null) {
|
||||
imageSize = Utilities.parseInt(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (httpConnectionStream != null) {
|
||||
try {
|
||||
byte[] data = new byte[1024 * 8];
|
||||
int totalLoaded = 0;
|
||||
while (true) {
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
int read = httpConnectionStream.read(data);
|
||||
if (read > 0) {
|
||||
totalLoaded += read;
|
||||
fileOutputStream.write(data, 0, read);
|
||||
if (imageSize != 0) {
|
||||
reportProgress(totalLoaded / (float) imageSize);
|
||||
}
|
||||
} else if (read == -1) {
|
||||
done = true;
|
||||
if (imageSize != 0) {
|
||||
reportProgress(1.0f);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.close();
|
||||
fileOutputStream = null;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (httpConnectionStream != null) {
|
||||
httpConnectionStream.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
if (done) {
|
||||
if (cacheImage.tempFilePath != null) {
|
||||
if (!cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath)) {
|
||||
cacheImage.finalFilePath = cacheImage.tempFilePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Boolean result) {
|
||||
if (result || !canRetry) {
|
||||
fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, FileLoader.MEDIA_DIR_IMAGE);
|
||||
} else {
|
||||
httpFileLoadError(cacheImage.url);
|
||||
}
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fileProgresses.remove(cacheImage.url);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (result) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, cacheImage.url);
|
||||
} else {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 2);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
imageLoadQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runHttpTasks(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
imageLoadQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runHttpTasks(true);
|
||||
}
|
||||
});
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fileProgresses.remove(cacheImage.url);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, cacheImage.url, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ThumbGenerateTask implements Runnable {
|
||||
|
||||
private File originalPath;
|
||||
@@ -514,7 +157,7 @@ public class ImageLoader {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@@ -531,7 +174,7 @@ public class ImageLoader {
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
removeTask();
|
||||
}
|
||||
}
|
||||
@@ -593,13 +236,13 @@ public class ImageLoader {
|
||||
}
|
||||
randomAccessFile.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (randomAccessFile != null) {
|
||||
try {
|
||||
randomAccessFile.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -684,7 +327,7 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -933,7 +576,7 @@ public class ImageLoader {
|
||||
trackAllocation = cl.getMethod("trackExternalAllocation", new Class[]{long.class});
|
||||
trackFree = cl.getMethod("trackExternalFree", new Class[]{long.class});
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
runtime = null;
|
||||
trackAllocation = null;
|
||||
trackFree = null;
|
||||
@@ -955,7 +598,6 @@ public class ImageLoader {
|
||||
protected boolean thumb;
|
||||
|
||||
protected String httpUrl;
|
||||
protected HttpImageTask httpTask;
|
||||
protected CacheOutTask cacheTask;
|
||||
|
||||
protected ArrayList<ImageReceiver> imageReceiverArray = new ArrayList<>();
|
||||
@@ -992,9 +634,9 @@ public class ImageLoader {
|
||||
imageReceiverArray.clear();
|
||||
if (location != null) {
|
||||
if (location instanceof TLRPC.FileLocation) {
|
||||
FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location, ext);
|
||||
//FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location, ext);
|
||||
} else if (location instanceof TLRPC.Document) {
|
||||
FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location);
|
||||
//FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location);
|
||||
}
|
||||
}
|
||||
if (cacheTask != null) {
|
||||
@@ -1006,11 +648,11 @@ public class ImageLoader {
|
||||
cacheTask.cancel();
|
||||
cacheTask = null;
|
||||
}
|
||||
if (httpTask != null) {
|
||||
httpTasks.remove(httpTask);
|
||||
httpTask.cancel(true);
|
||||
httpTask = null;
|
||||
}
|
||||
//if (httpTask != null) {
|
||||
// httpTasks.remove(httpTask);
|
||||
// httpTask.cancel(true);
|
||||
// httpTask = null;
|
||||
//}
|
||||
if (url != null) {
|
||||
imageLoadingByUrl.remove(url);
|
||||
}
|
||||
@@ -1106,106 +748,9 @@ public class ImageLoader {
|
||||
}
|
||||
};
|
||||
|
||||
FileLoader.getInstance().setDelegate(new FileLoader.FileLoaderDelegate() {
|
||||
/*
|
||||
@Override
|
||||
public void fileUploadProgressChanged(final String location, final float progress, final boolean isEncrypted) {
|
||||
fileProgresses.put(location, progress);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (lastProgressUpdateTime == 0 || lastProgressUpdateTime < currentTime - 500) {
|
||||
lastProgressUpdateTime = currentTime;
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileUploadProgressChanged, location, progress, isEncrypted);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileDidUploaded(final String location, final TLRPC.InputFile inputFile, final TLRPC.InputEncryptedFile inputEncryptedFile, final byte[] key, final byte[] iv, final long totalFileSize) {
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidUpload, location, inputFile, inputEncryptedFile, key, iv, totalFileSize);
|
||||
}
|
||||
});
|
||||
fileProgresses.remove(location);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileDidFailedUpload(final String location, final boolean isEncrypted) {
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailUpload, location, isEncrypted);
|
||||
}
|
||||
});
|
||||
fileProgresses.remove(location);
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void fileDidLoaded(final String location, final File finalFile, final int type) {
|
||||
fileProgresses.remove(location);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
/*if (MediaController.getInstance().canSaveToGallery() && messengerPath != null && finalFile != null && (location.endsWith(".mp4") || location.endsWith(".jpg"))) {
|
||||
if (finalFile.toString().startsWith(messengerPath.toString())) {
|
||||
AndroidUtilities.addMediaToGallery(finalFile.toString());
|
||||
}
|
||||
}*/
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, location);
|
||||
ImageLoader.this.fileDidLoaded(location, finalFile, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileDidFailedLoad(final String location, final int canceled) {
|
||||
fileProgresses.remove(location);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ImageLoader.this.fileDidFailedLoad(location, canceled);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, canceled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileLoadProgressChanged(final String location, final float progress) {
|
||||
fileProgresses.put(location, progress);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (lastProgressUpdateTime == 0 || lastProgressUpdateTime < currentTime - 500) {
|
||||
lastProgressUpdateTime = currentTime;
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileLoadProgressChanged, location, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context arg0, Intent intent) {
|
||||
FileLog.e("messenger", "file system changed");
|
||||
Runnable r = new Runnable() {
|
||||
public void run() {
|
||||
checkMediaPaths();
|
||||
@@ -1247,13 +792,6 @@ public class ImageLoader {
|
||||
FileLoader.getInstance().setMediaDirs(mediaDirs);
|
||||
}
|
||||
|
||||
public Float getFileProgress(String location) {
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
return fileProgresses.get(location);
|
||||
}
|
||||
|
||||
public void incrementUseCount(String key) {
|
||||
Integer count = bitmapUseCounts.get(key);
|
||||
if (count == null) {
|
||||
@@ -1490,18 +1028,18 @@ public class ImageLoader {
|
||||
if (httpLocation == null) {
|
||||
if (imageLocation instanceof TLRPC.FileLocation) {
|
||||
TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation;
|
||||
FileLoader.getInstance().loadFile(location, ext, size, size == 0 || location.key != null || cacheOnly);
|
||||
//FileLoader.getInstance().loadFile(location, ext, size, size == 0 || location.key != null || cacheOnly);
|
||||
} else if (imageLocation instanceof TLRPC.Document) {
|
||||
FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, cacheOnly);
|
||||
//FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, cacheOnly);
|
||||
}
|
||||
} else {
|
||||
String file = Utilities.MD5(httpLocation);
|
||||
File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE);
|
||||
img.tempFilePath = new File(cacheDir, file + "_temp.jpg");
|
||||
img.finalFilePath = cacheFile;
|
||||
img.httpTask = new HttpImageTask(img, size);
|
||||
httpTasks.add(img.httpTask);
|
||||
runHttpTasks(false);
|
||||
//img.httpTask = new HttpImageTask(img, size);
|
||||
//httpTasks.add(img.httpTask);
|
||||
//runHttpTasks(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1618,130 +1156,6 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private void httpFileLoadError(final String location) {
|
||||
imageLoadQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CacheImage img = imageLoadingByUrl.get(location);
|
||||
if (img == null) {
|
||||
return;
|
||||
}
|
||||
HttpImageTask oldTask = img.httpTask;
|
||||
img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize);
|
||||
httpTasks.add(img.httpTask);
|
||||
runHttpTasks(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fileDidLoaded(final String location, final File finalFile, final int type) {
|
||||
imageLoadQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ThumbGenerateInfo info = waitingForQualityThumb.get(location);
|
||||
if (info != null) {
|
||||
generateThumb(type, finalFile, info.fileLocation, info.filter);
|
||||
waitingForQualityThumb.remove(location);
|
||||
}
|
||||
CacheImage img = imageLoadingByUrl.get(location);
|
||||
if (img == null) {
|
||||
return;
|
||||
}
|
||||
imageLoadingByUrl.remove(location);
|
||||
CacheOutTask task = null;
|
||||
for (int a = 0; a < img.imageReceiverArray.size(); a++) {
|
||||
ImageReceiver imageReceiver = img.imageReceiverArray.get(a);
|
||||
CacheImage cacheImage = imageLoadingByKeys.get(img.key);
|
||||
if (cacheImage == null) {
|
||||
cacheImage = new CacheImage();
|
||||
cacheImage.finalFilePath = finalFile;
|
||||
cacheImage.key = img.key;
|
||||
cacheImage.httpUrl = img.httpUrl;
|
||||
cacheImage.thumb = img.thumb;
|
||||
cacheImage.ext = img.ext;
|
||||
cacheImage.cacheTask = task = new CacheOutTask(cacheImage);
|
||||
cacheImage.filter = img.filter;
|
||||
cacheImage.animatedFile = img.animatedFile;
|
||||
imageLoadingByKeys.put(cacheImage.key, cacheImage);
|
||||
}
|
||||
cacheImage.addImageReceiver(imageReceiver);
|
||||
}
|
||||
if (task != null) {
|
||||
if (img.thumb) {
|
||||
cacheThumbOutQueue.postRunnable(task);
|
||||
} else {
|
||||
cacheOutQueue.postRunnable(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fileDidFailedLoad(final String location, int canceled) {
|
||||
if (canceled == 1) {
|
||||
return;
|
||||
}
|
||||
imageLoadQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CacheImage img = imageLoadingByUrl.get(location);
|
||||
if (img != null) {
|
||||
img.setImageAndClear(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void runHttpTasks(boolean complete) {
|
||||
if (complete) {
|
||||
currentHttpTasksCount--;
|
||||
}
|
||||
while (currentHttpTasksCount < 4 && !httpTasks.isEmpty()) {
|
||||
HttpImageTask task = httpTasks.poll();
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
|
||||
currentHttpTasksCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private void runHttpFileLoadTasks(final HttpFileTask oldTask, final int reason) {
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (oldTask != null) {
|
||||
currentHttpFileLoadTasksCount--;
|
||||
}
|
||||
if (oldTask != null) {
|
||||
if (reason == 1) {
|
||||
if (oldTask.canRetry) {
|
||||
final HttpFileTask newTask = new HttpFileTask(oldTask.url, oldTask.tempFile, oldTask.ext);
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
httpFileLoadTasks.add(newTask);
|
||||
runHttpFileLoadTasks(null, 0);
|
||||
}
|
||||
};
|
||||
retryHttpsTasks.put(oldTask.url, runnable);
|
||||
AndroidUtilities.runOnUIThread(runnable, 1000);
|
||||
} else {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidFailedLoad, oldTask.url);
|
||||
}
|
||||
} else if (reason == 2) {
|
||||
httpFileLoadTasksByKeys.remove(oldTask.url);
|
||||
File file = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.MD5(oldTask.url) + "." + oldTask.ext);
|
||||
String result = oldTask.tempFile.renameTo(file) ? file.toString() : oldTask.tempFile.toString();
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.httpFileDidLoaded, oldTask.url, result);
|
||||
}
|
||||
}
|
||||
while (currentHttpFileLoadTasksCount < 2 && !httpFileLoadTasks.isEmpty()) {
|
||||
HttpFileTask task = httpFileLoadTasks.poll();
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
|
||||
currentHttpFileLoadTasksCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) {
|
||||
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
|
||||
bmOptions.inJustDecodeBounds = true;
|
||||
@@ -1754,7 +1168,7 @@ public class ImageLoader {
|
||||
try {
|
||||
path = AndroidUtilities.getPath(uri);
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1768,7 +1182,7 @@ public class ImageLoader {
|
||||
inputStream.close();
|
||||
inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri);
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1809,7 +1223,7 @@ public class ImageLoader {
|
||||
break;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1828,7 +1242,7 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
ImageLoader.getInstance().clearMemory();
|
||||
try {
|
||||
if (b == null) {
|
||||
@@ -1845,7 +1259,6 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
}
|
||||
} else if (uri != null) {
|
||||
@@ -1862,12 +1275,12 @@ public class ImageLoader {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1970,13 +1383,12 @@ public class ImageLoader {
|
||||
try {
|
||||
return scaleAndSaveImageInternal(cacheFile, bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway);
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
ImageLoader.getInstance().clearMemory();
|
||||
System.gc();
|
||||
try {
|
||||
return scaleAndSaveImageInternal(cacheFile, bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway);
|
||||
} catch (Throwable e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2034,7 +1446,7 @@ public class ImageLoader {
|
||||
writeFile.write(photoSize.bytes);
|
||||
writeFile.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
TLRPC.TL_photoSize newPhotoSize = new TLRPC.TL_photoSize();
|
||||
|
||||
@@ -140,7 +140,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
setImage(path, null, filter, thumb, null, null, size, ext, cacheOnly);
|
||||
}
|
||||
|
||||
public void setImage(String httpUrl, String filter, Drawable thumb, String ext, int size) {
|
||||
public void setImageByPath(String httpUrl, String filter, Drawable thumb, String ext, int size) {
|
||||
setImage(null, httpUrl, filter, thumb, null, null, size, ext, true);
|
||||
}
|
||||
|
||||
@@ -371,12 +371,10 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
setImageBackup.ext = currentExt;
|
||||
setImageBackup.cacheOnly = currentCacheOnly;
|
||||
}
|
||||
NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
clearImage();
|
||||
}
|
||||
|
||||
public boolean onAttachedToWindow() {
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReplacedPhotoInMemCache);
|
||||
if (setImageBackup != null && (setImageBackup.fileLocation != null || setImageBackup.httpUrl != null || setImageBackup.thumbLocation != null || setImageBackup.thumb != null)) {
|
||||
setImage(setImageBackup.fileLocation, setImageBackup.httpUrl, setImageBackup.filter, setImageBackup.thumb, setImageBackup.thumbLocation, setImageBackup.thumbFilter, setImageBackup.size, setImageBackup.ext, setImageBackup.cacheOnly);
|
||||
return true;
|
||||
@@ -485,7 +483,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
canvas.restore();
|
||||
} else {
|
||||
@@ -530,7 +528,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +565,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
currentThumbKey = null;
|
||||
}
|
||||
setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly);
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
@@ -582,7 +580,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
drawable.setAlpha(alpha);
|
||||
drawable.draw(canvas);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,7 +662,7 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
checkAlphaAnimation(animationNotReady);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1058,26 +1056,6 @@ public class ImageReceiver implements NotificationCenter.NotificationCenterDeleg
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (id == NotificationCenter.didReplacedPhotoInMemCache) {
|
||||
String oldKey = (String) args[0];
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (setImageBackup != null) {
|
||||
if (currentKey != null && currentKey.equals(oldKey)) {
|
||||
currentKey = (String) args[1];
|
||||
currentImageLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
if (currentThumbKey != null && currentThumbKey.equals(oldKey)) {
|
||||
currentThumbKey = (String) args[1];
|
||||
currentThumbLocation = (TLRPC.FileLocation) args[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class KeepAliveService extends Service {
|
||||
|
||||
static KeepAliveService s_this = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
MrMailbox.log_i("DeltaChat", "*** KeepAliveService.onCreate()");
|
||||
// there's nothing more to do here as all initialisation stuff is already done in
|
||||
// ApplicationLoader.onCreate() which is called before this broadcast is sended.
|
||||
s_this = this;
|
||||
setSelfAsForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// START_STICKY ensured, the service is recreated as soon it is terminted for any reasons.
|
||||
// as ApplicationLoader.onCreate() is called before a service starts, there is no more to do here,
|
||||
// the app is just running fine.
|
||||
MrMailbox.log_i("DeltaChat", "*** KeepAliveService.onStartCommand()");
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
MrMailbox.log_i("DeltaChat", "*** KeepAliveService.onDestroy()");
|
||||
// the service will be restarted due to START_STICKY automatically, there's nothing more to do.
|
||||
}
|
||||
|
||||
private void setSelfAsForeground() {
|
||||
stopForeground(true);
|
||||
startForeground(FG_NOTIFICATION_ID, createNotification()); // TODO: if we target Android O, we should use startServiceInForeground()
|
||||
}
|
||||
|
||||
static public KeepAliveService getInstance()
|
||||
{
|
||||
return s_this; // may be null
|
||||
}
|
||||
|
||||
/* The notification
|
||||
* A notification is required for a foreground service; and without a foreground service,
|
||||
* Delta Chat won't get new messages reliable
|
||||
**********************************************************************************************/
|
||||
|
||||
public static final int FG_NOTIFICATION_ID = 4142;
|
||||
private Notification createNotification()
|
||||
{
|
||||
// a notification _must_ contain a small icon, a title and a text, see https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Required
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||
|
||||
if( MrMailbox.isConfigured()!=0) {
|
||||
builder.setContentTitle(String.format(getString(R.string.PermNotificationTitle), MrMailbox.getConfig("addr", "")));
|
||||
builder.setContentText(getString(R.string.PermNotificationText));
|
||||
}
|
||||
else {
|
||||
builder.setContentTitle(getString(R.string.AppName));
|
||||
builder.setContentText(getString(R.string.AccountNotConfigured));
|
||||
}
|
||||
|
||||
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ) {
|
||||
builder.setPriority(NotificationCompat.PRIORITY_MIN);
|
||||
}
|
||||
builder.setWhen(0);
|
||||
builder.setSmallIcon(R.drawable.notification_permanent);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void updateForegroundNotification()
|
||||
{
|
||||
// update the notification by simply creating a new notification with the same ID, see https://developer.android.com/training/notify-user/managing.html#Updating
|
||||
NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(FG_NOTIFICATION_ID, createNotification());
|
||||
}
|
||||
}
|
||||
@@ -23,36 +23,25 @@
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Xml;
|
||||
import android.view.View;
|
||||
|
||||
import com.b44t.messenger.time.FastDateFormat;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class LocaleController {
|
||||
|
||||
public static boolean isRTL = false;
|
||||
public static int nameDisplayOrder = 1;
|
||||
private static boolean is24HourFormat = false;
|
||||
public FastDateFormat formatterDay;
|
||||
public FastDateFormat formatterWeek;
|
||||
public FastDateFormat formatterMonth;
|
||||
@@ -61,12 +50,7 @@ public class LocaleController {
|
||||
public FastDateFormat chatDate;
|
||||
public FastDateFormat chatFullDate;
|
||||
|
||||
private Locale currentLocale;
|
||||
private LocaleInfo currentLocaleInfo;
|
||||
private LocaleInfo defaultLocalInfo;
|
||||
private HashMap<String, String> localeValues = new HashMap<>();
|
||||
private String languageOverride;
|
||||
private boolean changingConfiguration = false;
|
||||
private String formattersCreatedFor = "";
|
||||
|
||||
private class TimeZoneChangedReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
@@ -82,38 +66,6 @@ public class LocaleController {
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocaleInfo {
|
||||
public String name;
|
||||
public String nameEnglish;
|
||||
public String shortName;
|
||||
public String pathToFile;
|
||||
|
||||
public String getSaveString() {
|
||||
return name + "|" + nameEnglish + "|" + shortName + "|" + pathToFile;
|
||||
}
|
||||
|
||||
public static LocaleInfo createWithString(String string) {
|
||||
if (string == null || string.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
String[] args = string.split("\\|");
|
||||
if (args.length != 4) {
|
||||
return null;
|
||||
}
|
||||
LocaleInfo localeInfo = new LocaleInfo();
|
||||
localeInfo.name = args[0];
|
||||
localeInfo.nameEnglish = args[1];
|
||||
localeInfo.shortName = args[2];
|
||||
localeInfo.pathToFile = args[3];
|
||||
return localeInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayList<LocaleInfo> sortedLanguages = new ArrayList<>();
|
||||
public HashMap<String, LocaleInfo> languagesDict = new HashMap<>();
|
||||
|
||||
private ArrayList<LocaleInfo> otherLanguages = new ArrayList<>();
|
||||
|
||||
private static volatile LocaleController Instance = null;
|
||||
public static LocaleController getInstance() {
|
||||
LocaleController localInstance = Instance;
|
||||
@@ -129,420 +81,37 @@ public class LocaleController {
|
||||
}
|
||||
|
||||
public LocaleController() {
|
||||
LocaleInfo localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "English";
|
||||
localeInfo.nameEnglish = "English";
|
||||
localeInfo.shortName = "en";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Italiano";
|
||||
localeInfo.nameEnglish = "Italian";
|
||||
localeInfo.shortName = "it";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Español";
|
||||
localeInfo.nameEnglish = "Spanish";
|
||||
localeInfo.shortName = "es";
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Deutsch";
|
||||
localeInfo.nameEnglish = "German";
|
||||
localeInfo.shortName = "de";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Français";
|
||||
localeInfo.nameEnglish = "French";
|
||||
localeInfo.shortName = "fr";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Nederlands";
|
||||
localeInfo.nameEnglish = "Dutch";
|
||||
localeInfo.shortName = "nl";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Polski";
|
||||
localeInfo.nameEnglish = "Polish";
|
||||
localeInfo.shortName = "pl";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
localeInfo = new LocaleInfo();
|
||||
localeInfo.name = "Português";
|
||||
localeInfo.nameEnglish = "Portuguese";
|
||||
localeInfo.shortName = "pt";
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(localeInfo);
|
||||
languagesDict.put(localeInfo.shortName, localeInfo);
|
||||
|
||||
loadOtherLanguages();
|
||||
|
||||
for (LocaleInfo locale : otherLanguages) {
|
||||
sortedLanguages.add(locale);
|
||||
languagesDict.put(locale.shortName, locale);
|
||||
}
|
||||
|
||||
Collections.sort(sortedLanguages, new Comparator<LocaleInfo>() {
|
||||
@Override
|
||||
public int compare(LocaleController.LocaleInfo o, LocaleController.LocaleInfo o2) {
|
||||
return o.name.compareTo(o2.name);
|
||||
}
|
||||
});
|
||||
|
||||
defaultLocalInfo = localeInfo = new LocaleController.LocaleInfo();
|
||||
localeInfo.name = "System default";
|
||||
localeInfo.nameEnglish = "System default";
|
||||
localeInfo.shortName = null;
|
||||
localeInfo.pathToFile = null;
|
||||
sortedLanguages.add(0, localeInfo);
|
||||
|
||||
is24HourFormat = DateFormat.is24HourFormat(ApplicationLoader.applicationContext);
|
||||
LocaleInfo currentInfo = null;
|
||||
boolean override = false;
|
||||
|
||||
try {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
String lang = preferences.getString("language", null);
|
||||
if (lang != null) {
|
||||
currentInfo = languagesDict.get(lang);
|
||||
if (currentInfo != null) {
|
||||
override = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInfo == null && Locale.getDefault().getLanguage() != null) {
|
||||
currentInfo = languagesDict.get(Locale.getDefault().getLanguage());
|
||||
}
|
||||
if (currentInfo == null) {
|
||||
currentInfo = languagesDict.get(getLocaleString(Locale.getDefault()));
|
||||
}
|
||||
if (currentInfo == null) {
|
||||
currentInfo = languagesDict.get("en");
|
||||
}
|
||||
applyLanguage(currentInfo, override);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
recreateFormatters();
|
||||
|
||||
try {
|
||||
IntentFilter timezoneFilter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
ApplicationLoader.applicationContext.registerReceiver(new TimeZoneChangedReceiver(), timezoneFilter);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String getLocaleString(Locale locale) {
|
||||
if (locale == null) {
|
||||
return "en";
|
||||
}
|
||||
String languageCode = locale.getLanguage();
|
||||
String countryCode = locale.getCountry();
|
||||
String variantCode = locale.getVariant();
|
||||
if (languageCode.length() == 0 && countryCode.length() == 0) {
|
||||
return "en";
|
||||
}
|
||||
StringBuilder result = new StringBuilder(11);
|
||||
result.append(languageCode);
|
||||
if (countryCode.length() > 0 || variantCode.length() > 0) {
|
||||
result.append('_');
|
||||
}
|
||||
result.append(countryCode);
|
||||
if (variantCode.length() > 0) {
|
||||
result.append('_');
|
||||
}
|
||||
result.append(variantCode);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String getLocaleStringIso639() {
|
||||
Locale locale = Locale.getDefault();
|
||||
if (locale == null) {
|
||||
return "en";
|
||||
}
|
||||
String languageCode = locale.getLanguage();
|
||||
String countryCode = locale.getCountry();
|
||||
String variantCode = locale.getVariant();
|
||||
if (languageCode.length() == 0 && countryCode.length() == 0) {
|
||||
return "en";
|
||||
}
|
||||
StringBuilder result = new StringBuilder(11);
|
||||
result.append(languageCode);
|
||||
if (countryCode.length() > 0 || variantCode.length() > 0) {
|
||||
result.append('-');
|
||||
}
|
||||
result.append(countryCode);
|
||||
if (variantCode.length() > 0) {
|
||||
result.append('_');
|
||||
}
|
||||
result.append(variantCode);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void saveOtherLanguages() {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("langconfig", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
String locales = "";
|
||||
for (LocaleInfo localeInfo : otherLanguages) {
|
||||
String loc = localeInfo.getSaveString();
|
||||
if (loc != null) {
|
||||
if (locales.length() != 0) {
|
||||
locales += "&";
|
||||
}
|
||||
locales += loc;
|
||||
}
|
||||
}
|
||||
editor.putString("locales", locales);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public boolean deleteLanguage(LocaleInfo localeInfo) {
|
||||
if (localeInfo.pathToFile == null) {
|
||||
return false;
|
||||
}
|
||||
if (currentLocaleInfo == localeInfo) {
|
||||
applyLanguage(defaultLocalInfo, true);
|
||||
}
|
||||
|
||||
otherLanguages.remove(localeInfo);
|
||||
sortedLanguages.remove(localeInfo);
|
||||
languagesDict.remove(localeInfo.shortName);
|
||||
File file = new File(localeInfo.pathToFile);
|
||||
file.delete();
|
||||
saveOtherLanguages();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadOtherLanguages() {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("langconfig", Activity.MODE_PRIVATE);
|
||||
String locales = preferences.getString("locales", null);
|
||||
if (locales == null || locales.length() == 0) {
|
||||
return;
|
||||
}
|
||||
String[] localesArr = locales.split("&");
|
||||
for (String locale : localesArr) {
|
||||
LocaleInfo localeInfo = LocaleInfo.createWithString(locale);
|
||||
if (localeInfo != null) {
|
||||
otherLanguages.add(localeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, String> getLocaleFileStrings(File file) {
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
HashMap<String, String> stringMap = new HashMap<>();
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
stream = new FileInputStream(file);
|
||||
parser.setInput(stream, "UTF-8");
|
||||
int eventType = parser.getEventType();
|
||||
String name = null;
|
||||
String value = null;
|
||||
String attrName = null;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if(eventType == XmlPullParser.START_TAG) {
|
||||
name = parser.getName();
|
||||
int c = parser.getAttributeCount();
|
||||
if (c > 0) {
|
||||
attrName = parser.getAttributeValue(0);
|
||||
}
|
||||
} else if(eventType == XmlPullParser.TEXT) {
|
||||
if (attrName != null) {
|
||||
value = parser.getText();
|
||||
if (value != null) {
|
||||
value = value.trim();
|
||||
value = value.replace("\\n", "\n");
|
||||
value = value.replace("\\", "");
|
||||
}
|
||||
}
|
||||
} else if (eventType == XmlPullParser.END_TAG) {
|
||||
value = null;
|
||||
attrName = null;
|
||||
name = null;
|
||||
}
|
||||
if (name != null && name.equals("string") && value != null && attrName != null && value.length() != 0 && attrName.length() != 0) {
|
||||
stringMap.put(attrName, value);
|
||||
name = null;
|
||||
value = null;
|
||||
attrName = null;
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return stringMap;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
} finally {
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
public void applyLanguage(LocaleInfo localeInfo, boolean override) {
|
||||
applyLanguage(localeInfo, override, false);
|
||||
}
|
||||
|
||||
public void applyLanguage(LocaleInfo localeInfo, boolean override, boolean fromFile) {
|
||||
if (localeInfo == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Locale newLocale;
|
||||
if (localeInfo.shortName != null) {
|
||||
String[] args = localeInfo.shortName.split("_");
|
||||
if (args.length == 1) {
|
||||
newLocale = new Locale(localeInfo.shortName);
|
||||
} else {
|
||||
newLocale = new Locale(args[0], args[1]);
|
||||
}
|
||||
if (newLocale != null) {
|
||||
if (override) {
|
||||
languageOverride = localeInfo.shortName;
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("language", localeInfo.shortName);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newLocale = Locale.getDefault();
|
||||
languageOverride = null;
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.remove("language");
|
||||
editor.apply();
|
||||
|
||||
if (newLocale != null) {
|
||||
LocaleInfo info = null;
|
||||
if (newLocale.getLanguage() != null) {
|
||||
info = languagesDict.get(newLocale.getLanguage());
|
||||
}
|
||||
if (info == null) {
|
||||
info = languagesDict.get(getLocaleString(newLocale));
|
||||
}
|
||||
if (info == null) {
|
||||
newLocale = Locale.US;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newLocale != null) {
|
||||
if (localeInfo.pathToFile == null) {
|
||||
localeValues.clear();
|
||||
} else if (!fromFile) {
|
||||
localeValues = getLocaleFileStrings(new File(localeInfo.pathToFile));
|
||||
}
|
||||
currentLocale = newLocale;
|
||||
currentLocaleInfo = localeInfo;
|
||||
changingConfiguration = true;
|
||||
Locale.setDefault(currentLocale);
|
||||
android.content.res.Configuration config = new android.content.res.Configuration();
|
||||
config.locale = currentLocale;
|
||||
ApplicationLoader.applicationContext.getResources().updateConfiguration(config, ApplicationLoader.applicationContext.getResources().getDisplayMetrics());
|
||||
changingConfiguration = false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
changingConfiguration = false;
|
||||
}
|
||||
recreateFormatters();
|
||||
}
|
||||
|
||||
public static String getCurrentLanguageName() {
|
||||
return getString("LanguageName", R.string.LanguageName);
|
||||
}
|
||||
|
||||
private String getStringInternal(String key, int res) {
|
||||
String value = localeValues.get(key);
|
||||
if (value == null) {
|
||||
try {
|
||||
value = ApplicationLoader.applicationContext.getString(res);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
if (value == null) {
|
||||
value = "LOC_ERR:" + key;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static String getString(String key, int res) {
|
||||
return getInstance().getStringInternal(key, res);
|
||||
}
|
||||
|
||||
public static String formatString(String key, int res, Object... args) {
|
||||
try {
|
||||
String value = getInstance().localeValues.get(key);
|
||||
if (value == null) {
|
||||
value = ApplicationLoader.applicationContext.getString(res);
|
||||
}
|
||||
|
||||
if (getInstance().currentLocale != null) {
|
||||
return String.format(getInstance().currentLocale, value, args);
|
||||
} else {
|
||||
return String.format(value, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
return "LOC_ERR: " + key;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatStringSimple(String string, Object... args) {
|
||||
try {
|
||||
if (getInstance().currentLocale != null) {
|
||||
return String.format(getInstance().currentLocale, string, args);
|
||||
} else {
|
||||
return String.format(string, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
return "LOC_ERR: " + string;
|
||||
public void rebuildUiParts()
|
||||
{
|
||||
// While android makes sure, the activities are re-translated on locale changes, this is
|
||||
// not done for the permanent notification "Connected to foo@bar - Waiting for messages ..."
|
||||
// To re-translate this, it is needed to update the notification (which is a good idea as the
|
||||
// notification is really permanent and would stuck in the wrong language for weeks otherwise ...)
|
||||
KeepAliveService kas = KeepAliveService.getInstance();
|
||||
if( kas != null ) {
|
||||
kas.updateForegroundNotification();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeviceConfigurationChange(Configuration newConfig) {
|
||||
if (changingConfiguration) {
|
||||
return;
|
||||
}
|
||||
is24HourFormat = DateFormat.is24HourFormat(ApplicationLoader.applicationContext);
|
||||
if (languageOverride != null) {
|
||||
LocaleInfo toSet = currentLocaleInfo;
|
||||
currentLocaleInfo = null;
|
||||
applyLanguage(toSet, false);
|
||||
} else {
|
||||
Locale newLocale = newConfig.locale;
|
||||
if (newLocale != null) {
|
||||
String d1 = newLocale.getDisplayName();
|
||||
String d2 = currentLocale.getDisplayName();
|
||||
if (d1 != null && d2 != null && !d1.equals(d2)) {
|
||||
recreateFormatters();
|
||||
}
|
||||
currentLocale = newLocale;
|
||||
}
|
||||
// rebuild some UI parts after a language change:
|
||||
// - the permanent notification (Waiting for messages ...) - notfifications are not re-translated after a locale change. This cannot be done without listening to the language-change-envent.
|
||||
// - some formatters and other cached values, maybe this can be done in other ways, however, it's just working.
|
||||
Locale newLocale = Locale.getDefault();
|
||||
if( newLocale!=null && !newLocale.getDisplayName().equals(formattersCreatedFor) ) { // onDeviceConfigurationChange() is also called on screen orientation changes; do not rebuild the locale stuff in these cases
|
||||
recreateFormatters();
|
||||
rebuildUiParts(); // this is really needed, see comment in rebuildUiParts()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +128,7 @@ public class LocaleController {
|
||||
}
|
||||
return getInstance().chatFullDate.format(date * 1000);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return "LOC_ERR: formatDateChat";
|
||||
}
|
||||
@@ -578,25 +147,29 @@ public class LocaleController {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
public void recreateFormatters() {
|
||||
Locale locale = currentLocale;
|
||||
if (locale == null) {
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
String lang = locale.getLanguage();
|
||||
if (lang == null) {
|
||||
lang = "en";
|
||||
}
|
||||
isRTL = lang.toLowerCase().equals("ar");
|
||||
nameDisplayOrder = lang.toLowerCase().equals("ko") ? 2 : 1;
|
||||
private void recreateFormatters() {
|
||||
Locale locale = Locale.getDefault();
|
||||
|
||||
formatterMonth = createFormatter(locale, getStringInternal("formatterMonth", R.string.formatterMonth), "dd MMM");
|
||||
formatterYear = createFormatter(locale, getStringInternal("formatterYear", R.string.formatterYear), "dd.MM.yyyy");
|
||||
chatDate = createFormatter(locale, getStringInternal("chatDate", R.string.chatDate), "d MMMM");
|
||||
chatFullDate = createFormatter(locale, getStringInternal("chatFullDate", R.string.chatFullDate), "d MMMM yyyy");
|
||||
formatterWeek = createFormatter(locale, getStringInternal("formatterWeek", R.string.formatterWeek), "EEE");
|
||||
formatterMonthYear = createFormatter(locale, getStringInternal("formatterMonthYear", R.string.formatterMonthYear), "MMMM yyyy");
|
||||
formatterDay = createFormatter(lang.toLowerCase().equals("ar") || lang.toLowerCase().equals("ko") ? locale : Locale.US, is24HourFormat ? getStringInternal("formatterDay24H", R.string.formatterDay24H) : getStringInternal("formatterDay12H", R.string.formatterDay12H), is24HourFormat ? "HH:mm" : "h:mm a");
|
||||
isRTL = false;
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
// ViewCompat.getLayoutDirection() can also be used, however, this function always returns LTR for SDK < 17, too, but requires a additinal view handle
|
||||
Configuration config = ApplicationLoader.applicationContext.getResources().getConfiguration();
|
||||
if(config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
|
||||
isRTL = true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean is24HourFormat = DateFormat.is24HourFormat(ApplicationLoader.applicationContext);
|
||||
|
||||
formatterMonth = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.formatterMonth), "dd MMM");
|
||||
formatterYear = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.formatterYear), "dd.MM.yyyy");
|
||||
chatDate = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.chatDate), "d MMMM");
|
||||
chatFullDate = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.chatFullDate), "d MMMM yyyy");
|
||||
formatterWeek = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.formatterWeek), "EEE");
|
||||
formatterMonthYear = createFormatter(locale, ApplicationLoader.applicationContext.getString(R.string.formatterMonthYear), "MMMM yyyy");
|
||||
formatterDay = createFormatter(locale, is24HourFormat ? ApplicationLoader.applicationContext.getString(R.string.formatterDay24H) : ApplicationLoader.applicationContext.getString(R.string.formatterDay12H), is24HourFormat ? "HH:mm" : "h:mm a");
|
||||
|
||||
formattersCreatedFor = locale.getDisplayName();
|
||||
}
|
||||
|
||||
public static String stringForMessageListDate(long date) {
|
||||
@@ -621,7 +194,7 @@ public class LocaleController {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return "LOC_ERR";
|
||||
}
|
||||
|
||||
@@ -27,10 +27,8 @@ import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
@@ -50,7 +48,6 @@ import android.media.MediaExtractor;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaRecorder;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
@@ -84,6 +81,7 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
|
||||
public class MediaController implements AudioManager.OnAudioFocusChangeListener, NotificationCenter.NotificationCenterDelegate, SensorEventListener {
|
||||
|
||||
private native int startRecord(String path);
|
||||
@@ -268,7 +266,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
public int wifiDownloadMask = 0;
|
||||
public int roamingDownloadMask = 0;
|
||||
|
||||
private final boolean autoplayGifs = true;
|
||||
private boolean autoplayGifs = true;
|
||||
private boolean raiseToSpeak = true;
|
||||
private boolean directShare = true;
|
||||
|
||||
@@ -370,7 +368,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
samplesCount = newSamplesCount;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
buffer.position(0);
|
||||
final double amplitude = Math.sqrt(sum / len / 2);
|
||||
@@ -499,14 +497,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
freePlayerBuffers.add(new AudioBuffer(playerBufferSize));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
sensorManager = (SensorManager) ApplicationLoader.applicationContext.getSystemService(Context.SENSOR_SERVICE);
|
||||
linearSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
|
||||
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
|
||||
if (linearSensor == null || gravitySensor == null) {
|
||||
FileLog.e("messenger", "gravity or linear sensor not found");
|
||||
//Log.i("DeltaChat", "gravity or linear sensor not found");
|
||||
accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
linearSensor = null;
|
||||
gravitySensor = null;
|
||||
@@ -515,7 +513,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
PowerManager powerManager = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
|
||||
proximityWakeLock = powerManager.newWakeLock(0x00000020, "proximity");
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
fileBuffer = ByteBuffer.allocateDirect(1920);
|
||||
recordQueue = new DispatchQueue("recordQueue");
|
||||
@@ -529,34 +527,18 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
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);
|
||||
//autoplayGifs = preferences.getBoolean("autoplay_gif", true);
|
||||
autoplayGifs = preferences.getBoolean("autoplay_gif", true);
|
||||
raiseToSpeak = preferences.getBoolean("raise_to_speak", true);
|
||||
directShare = preferences.getBoolean("direct_share", true);
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.FileDidFailedLoad);
|
||||
NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.didReceivedNewMessages);
|
||||
NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.messagesDeleted);
|
||||
NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.FileDidLoaded);
|
||||
NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.FileLoadProgressChanged);
|
||||
//NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.FileUploadProgressChanged);
|
||||
//NotificationCenter.getInstance().addObserver(MediaController.this, NotificationCenter.musicDidLoaded);
|
||||
}
|
||||
});
|
||||
|
||||
BroadcastReceiver networkStateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
checkAutodownloadSettings();
|
||||
}
|
||||
};
|
||||
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
ApplicationLoader.applicationContext.registerReceiver(networkStateReceiver, filter);
|
||||
|
||||
checkAutodownloadSettings();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
mediaProjections = new String[]{
|
||||
MediaStore.Images.ImageColumns.DATA,
|
||||
@@ -580,12 +562,12 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, new GalleryObserverExternal());
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, new GalleryObserverInternal());
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -611,7 +593,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +639,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
audioTrackPlayer.setStereoVolume(volume, volume);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +650,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
progressTimer.cancel();
|
||||
progressTimer = null;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
progressTimer = new Timer();
|
||||
@@ -705,7 +687,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
currentPlayingMessageObject.audioProgressSec = lastProgress / 1000;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioProgressDidChanged, currentPlayingMessageObject.getId(), value);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,7 +705,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
progressTimer.cancel();
|
||||
progressTimer = null;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -739,31 +721,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
cancelVideoConvert(null);
|
||||
}
|
||||
|
||||
|
||||
public void checkAutodownloadSettings() {
|
||||
/* -- leave this for future use
|
||||
int currentMask = getCurrentDownloadMask();
|
||||
if (currentMask == lastCheckMask) {
|
||||
return;
|
||||
}
|
||||
lastCheckMask = currentMask;
|
||||
|
||||
... maybe tell the backend what can be downloaded ...
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
/* -- leave this for future use
|
||||
private int getCurrentDownloadMask() {
|
||||
if (ConnectionsManager.isConnectedToWiFi()) {
|
||||
return wifiDownloadMask;
|
||||
} else if (ConnectionsManager.isRoaming()) {
|
||||
return roamingDownloadMask;
|
||||
} else {
|
||||
return mobileDataDownloadMask;
|
||||
}
|
||||
} */
|
||||
|
||||
public int generateObserverTag() {
|
||||
return lastTag++;
|
||||
}
|
||||
@@ -835,103 +792,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void didReceivedNotification(int id, Object... args) {
|
||||
if (id == NotificationCenter.FileDidFailedLoad) {
|
||||
listenerInProgress = true;
|
||||
String fileName = (String) args[0];
|
||||
loadingFileMessagesObservers.get(fileName);
|
||||
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
|
||||
if (arrayList != null) {
|
||||
for (int a = 0; a < arrayList.size(); a++) {
|
||||
WeakReference<FileDownloadProgressListener> reference = arrayList.get(a);
|
||||
if (reference.get() != null) {
|
||||
reference.get().onFailedDownload(fileName);
|
||||
observersByTag.remove(reference.get().getObserverTag());
|
||||
}
|
||||
}
|
||||
loadingFileObservers.remove(fileName);
|
||||
}
|
||||
listenerInProgress = false;
|
||||
processLaterArrays();
|
||||
} else if (id == NotificationCenter.FileDidLoaded) {
|
||||
listenerInProgress = true;
|
||||
String fileName = (String) args[0];
|
||||
ArrayList<MessageObject> messageObjects = loadingFileMessagesObservers.get(fileName);
|
||||
if (messageObjects != null) {
|
||||
for (int a = 0; a < messageObjects.size(); a++) {
|
||||
MessageObject messageObject = messageObjects.get(a);
|
||||
messageObject.mediaExists = true;
|
||||
}
|
||||
loadingFileMessagesObservers.remove(fileName);
|
||||
}
|
||||
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
|
||||
if (arrayList != null) {
|
||||
for (int a = 0; a < arrayList.size(); a++) {
|
||||
WeakReference<FileDownloadProgressListener> reference = arrayList.get(a);
|
||||
if (reference.get() != null) {
|
||||
reference.get().onSuccessDownload(fileName);
|
||||
observersByTag.remove(reference.get().getObserverTag());
|
||||
}
|
||||
}
|
||||
loadingFileObservers.remove(fileName);
|
||||
}
|
||||
listenerInProgress = false;
|
||||
processLaterArrays();
|
||||
} else if (id == NotificationCenter.FileLoadProgressChanged) {
|
||||
listenerInProgress = true;
|
||||
String fileName = (String) args[0];
|
||||
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
|
||||
if (arrayList != null) {
|
||||
Float progress = (Float) args[1];
|
||||
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
|
||||
if (reference.get() != null) {
|
||||
reference.get().onProgressDownload(fileName, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
listenerInProgress = false;
|
||||
processLaterArrays();
|
||||
}
|
||||
/*else if (id == NotificationCenter.FileUploadProgressChanged) {
|
||||
listenerInProgress = true;
|
||||
String fileName = (String) args[0];
|
||||
ArrayList<WeakReference<FileDownloadProgressListener>> arrayList = loadingFileObservers.get(fileName);
|
||||
if (arrayList != null) {
|
||||
Float progress = (Float) args[1];
|
||||
Boolean enc = (Boolean) args[2];
|
||||
for (WeakReference<FileDownloadProgressListener> reference : arrayList) {
|
||||
if (reference.get() != null) {
|
||||
reference.get().onProgressUpload(fileName, progress, enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
listenerInProgress = false;
|
||||
processLaterArrays();
|
||||
try {
|
||||
ArrayList<SendMessagesHelper.DelayedMessage> delayedMessages = SendMessagesHelper.getInstance().getDelayedMessages(fileName);
|
||||
if (delayedMessages != null) {
|
||||
for (int a = 0; a < delayedMessages.size(); a++) {
|
||||
SendMessagesHelper.DelayedMessage delayedMessage = delayedMessages.get(a);
|
||||
if (delayedMessage.encryptedChat == null) {
|
||||
long dialog_id = delayedMessage.obj.getDialogId();
|
||||
Long lastTime = typingTimes.get(dialog_id);
|
||||
if (lastTime == null || lastTime + 4000 < System.currentTimeMillis()) {
|
||||
if (MessageObject.isVideoDocument(delayedMessage.documentLocation)) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 5, 0);
|
||||
} else if (delayedMessage.documentLocation != null) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 3, 0);
|
||||
} else if (delayedMessage.location != null) {
|
||||
MessagesController.getInstance().sendTyping(dialog_id, 4, 0);
|
||||
}
|
||||
typingTimes.put(dialog_id, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
} */
|
||||
else if (id == NotificationCenter.messagesDeleted) {
|
||||
if (id == NotificationCenter.messagesDeleted) {
|
||||
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>) args[0];
|
||||
if (playingMessageObject != null) {
|
||||
if (markAsDeletedMessages.contains(playingMessageObject.getId())) {
|
||||
@@ -939,19 +800,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
}
|
||||
/*else if (id == NotificationCenter.musicDidLoaded) {
|
||||
long did = (Long) args[0];
|
||||
if (playingMessageObject != null && playingMessageObject.isMusic() && playingMessageObject.getDialogId() == did) {
|
||||
ArrayList<MessageObject> arrayList = (ArrayList<MessageObject>) args[1];
|
||||
playlist.addAll(0, arrayList);
|
||||
if (shuffleMusic) {
|
||||
buildShuffledPlayList();
|
||||
currentPlaylistNum = 0;
|
||||
} else {
|
||||
currentPlaylistNum += arrayList.size();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void checkDecoderQueue() {
|
||||
@@ -1028,7 +876,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
count = audioTrackPlayer.write(buffer.bufferBytes, 0, buffer.size);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
buffersWrited++;
|
||||
|
||||
@@ -1083,7 +931,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
return;
|
||||
}
|
||||
if (event.sensor == proximitySensor) {
|
||||
FileLog.e("messenger", "proximity changed to " + event.values[0]);
|
||||
//Log.i("DeltaChat", "proximity changed to " + event.values[0]);
|
||||
if (lastProximityValue == -100) {
|
||||
lastProximityValue = event.values[0];
|
||||
} else if (lastProximityValue != event.values[0]) {
|
||||
@@ -1148,7 +996,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
raisedToTop = 0;
|
||||
countLess = 0;
|
||||
timeSinceRaise = System.currentTimeMillis();
|
||||
//FileLog.e("messenger", "motion detected");
|
||||
//Log.i("DeltaChat", "motion detected");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1165,13 +1013,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
previousAccValue = val;
|
||||
accelerometerVertical = gravityFast[1] > 2.5f && Math.abs(gravityFast[2]) < 4.0f && /*Math.abs(gravityFast[0]) < 9.0f &&*/ Math.abs(gravityFast[0]) > 1.5f;
|
||||
//FileLog.e("messenger", accelerometerVertical + " val = " + val + " acc (" + linearAcceleration[0] + ", " + linearAcceleration[1] + ", " + linearAcceleration[2] + ") grav (" + gravityFast[0] + ", " + gravityFast[1] + ", " + gravityFast[2] + ")");
|
||||
//Log.i("DeltaChat", accelerometerVertical + " val = " + val + " acc (" + linearAcceleration[0] + ", " + linearAcceleration[1] + ", " + linearAcceleration[2] + ") grav (" + gravityFast[0] + ", " + gravityFast[1] + ", " + gravityFast[2] + ")");
|
||||
}
|
||||
if (raisedToBack == minCount && accelerometerVertical && proximityTouched && !NotificationsController.getInstance().audioManager.isWiredHeadsetOn()) {
|
||||
FileLog.e("messenger", "sensor values reached");
|
||||
//Log.i("DeltaChat", "sensor values reached");
|
||||
if (playingMessageObject == null && recordStartRunnable == null && recordingAudio == null && !PhotoViewer.getInstance().isVisible() && ApplicationLoader.isScreenOn && !inputFieldHasText && allowStartRecord && raiseChat != null && !callInProgress) {
|
||||
if (!raiseToEarRecord) {
|
||||
FileLog.e("messenger", "start record");
|
||||
//Log.i("DeltaChat", "start record");
|
||||
useFrontSpeaker = true;
|
||||
if (!raiseChat.playFirstUnreadVoiceMessage() /*if there is nothing to play, the function checks for record permission*/ ) {
|
||||
raiseToEarRecord = true;
|
||||
@@ -1185,7 +1033,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
} /*else if (playingMessageObject != null && playingMessageObject.isVoice()) {
|
||||
if (!useFrontSpeaker) {
|
||||
FileLog.e("messenger", "start listen");
|
||||
//Log.i("DeltaChat", "start listen");
|
||||
if (proximityHasDifferentValues && proximityWakeLock != null && !proximityWakeLock.isHeld()) {
|
||||
proximityWakeLock.acquire();
|
||||
}
|
||||
@@ -1200,7 +1048,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
} else if (proximityTouched) {
|
||||
/*if (playingMessageObject != null && playingMessageObject.isVoice()) {
|
||||
if (!useFrontSpeaker) {
|
||||
FileLog.e("messenger", "start listen by proximity only");
|
||||
//Log.i("DeltaChat", "start listen by proximity only");
|
||||
if (proximityHasDifferentValues && proximityWakeLock != null && !proximityWakeLock.isHeld()) {
|
||||
proximityWakeLock.acquire();
|
||||
}
|
||||
@@ -1211,7 +1059,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}*/
|
||||
} else if (!proximityTouched) {
|
||||
if (raiseToEarRecord) {
|
||||
FileLog.e("messenger", "stop record");
|
||||
//Log.i("DeltaChat", "stop record");
|
||||
stopRecording(2);
|
||||
raiseToEarRecord = false;
|
||||
ignoreOnPause = false;
|
||||
@@ -1219,7 +1067,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
proximityWakeLock.release();
|
||||
}
|
||||
} else if (useFrontSpeaker) {
|
||||
FileLog.e("messenger", "stop listen");
|
||||
//Log.i("DeltaChat", "stop listen");
|
||||
useFrontSpeaker = false;
|
||||
startAudioAgain(true);
|
||||
ignoreOnPause = false;
|
||||
@@ -1363,18 +1211,18 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
audioPlayer.reset();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
audioPlayer.stop();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
audioPlayer.release();
|
||||
audioPlayer = null;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
} else if (audioTrackPlayer != null) {
|
||||
synchronized (playerObjectSync) {
|
||||
@@ -1382,13 +1230,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
audioTrackPlayer.pause();
|
||||
audioTrackPlayer.flush();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
audioTrackPlayer.release();
|
||||
audioTrackPlayer = null;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1475,7 +1323,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
seekOpusPlayer(progress);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1552,7 +1400,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
return true;
|
||||
}
|
||||
if (!messageObject.isOut() && messageObject.isContentUnread()) {
|
||||
MessagesController.getInstance().markMessageContentAsRead(messageObject);
|
||||
MrMailbox.markMessageContentAsRead(messageObject);
|
||||
}
|
||||
boolean notify = !playMusicAgain;
|
||||
if (playingMessageObject != null) {
|
||||
@@ -1609,7 +1457,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
});
|
||||
audioTrackPlayer.play();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
if (audioTrackPlayer != null) {
|
||||
audioTrackPlayer.release();
|
||||
audioTrackPlayer = null;
|
||||
@@ -1640,11 +1488,11 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
audioInfo = AudioInfo.getAudioInfo(cacheFile);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject != null ? playingMessageObject.getId() : 0);
|
||||
if (audioPlayer != null) {
|
||||
audioPlayer.release();
|
||||
@@ -1678,7 +1526,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
playingMessageObject.audioProgress = 0;
|
||||
playingMessageObject.audioProgressSec = 0;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioProgressDidChanged, playingMessageObject.getId(), 0);
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
} else if (audioTrackPlayer != null) {
|
||||
if (playingMessageObject.audioProgress == 1) {
|
||||
@@ -1693,7 +1540,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
seekOpusFile(playingMessageObject.audioProgress);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
synchronized (playerSync) {
|
||||
freePlayerBuffers.addAll(usedPlayerBuffers);
|
||||
@@ -1721,7 +1568,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
audioPlayer.reset();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
audioPlayer.stop();
|
||||
} else if (audioTrackPlayer != null) {
|
||||
@@ -1729,7 +1576,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
audioTrackPlayer.flush();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
if (audioPlayer != null) {
|
||||
@@ -1742,7 +1589,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
stopProgressTimer();
|
||||
playingMessageObject = null;
|
||||
@@ -1770,7 +1617,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
isPaused = true;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
isPaused = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1794,7 +1641,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
isPaused = false;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId());
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1823,7 +1670,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
v.vibrate(50);
|
||||
//NotificationsController.getInstance().playRecordSound(); -- was also disabled in Telegram; playing a sound while recording may result in a feedback
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
recordQueue.postRunnable(recordStartRunnable = new Runnable() {
|
||||
@@ -1843,12 +1690,12 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
recordingAudio = new TLRPC.TL_document();
|
||||
recordingAudio.dc_id = Integer.MIN_VALUE;
|
||||
recordingAudio.id = UserConfig.lastLocalId;
|
||||
recordingAudio.user_id = UserConfig.getClientUserId();
|
||||
recordingAudio.user_id = MrContact.MR_CONTACT_ID_SELF;
|
||||
recordingAudio.mime_type = "audio/ogg";
|
||||
recordingAudio.thumb = new TLRPC.TL_photoSizeEmpty();
|
||||
recordingAudio.thumb.type = "s";
|
||||
UserConfig.lastLocalId--;
|
||||
UserConfig.saveConfig(false);
|
||||
UserConfig.saveConfig();
|
||||
|
||||
recordingAudioFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), FileLoader.getAttachFileName(recordingAudio));
|
||||
|
||||
@@ -1873,7 +1720,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
audioRecorder.startRecording();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
recordingAudio = null;
|
||||
stopRecord();
|
||||
recordingAudioFile.delete();
|
||||
@@ -1882,7 +1729,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
audioRecorder.release();
|
||||
audioRecorder = null;
|
||||
} catch (Exception e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@@ -1976,7 +1822,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
audioToSend.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
audioToSend.date = MrMailbox.getCurrentTime();
|
||||
audioToSend.size = (int) recordingAudioFileToSend.length();
|
||||
TLRPC.TL_documentAttributeAudio attributeAudio = new TLRPC.TL_documentAttributeAudio();
|
||||
attributeAudio.voice = true;
|
||||
@@ -2005,7 +1851,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
audioRecorder = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
recordingAudio = null;
|
||||
recordingAudioFile = null;
|
||||
@@ -2026,7 +1872,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
sendAfterDone = send;
|
||||
audioRecorder.stop();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
if (recordingAudioFile != null) {
|
||||
recordingAudioFile.delete();
|
||||
}
|
||||
@@ -2038,7 +1884,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
v.vibrate(50);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
@@ -2066,14 +1912,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -2091,14 +1936,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -2114,7 +1958,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
@@ -2139,7 +1983,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
if (name == null) {
|
||||
int id = UserConfig.lastLocalId;
|
||||
UserConfig.lastLocalId--;
|
||||
UserConfig.saveConfig(false);
|
||||
UserConfig.saveConfig();
|
||||
name = String.format(Locale.US, "%d.%s", id, ext);
|
||||
}
|
||||
inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri);
|
||||
@@ -2152,27 +1996,24 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
return f.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
try {
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
FileLog.e("messenger", e2);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
public void toggleAutoplayGifs() {
|
||||
autoplayGifs = !autoplayGifs;
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
@@ -2180,7 +2021,6 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
editor.putBoolean("autoplay_gif", autoplayGifs);
|
||||
editor.apply();
|
||||
}
|
||||
*/
|
||||
|
||||
public void toogleRaiseToSpeak() {
|
||||
raiseToSpeak = !raiseToSpeak;
|
||||
@@ -2249,7 +2089,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, orientation, false);
|
||||
|
||||
if (allPhotosAlbum == null) {
|
||||
allPhotosAlbum = new AlbumEntry(0, LocaleController.getString("AllPhotos", R.string.AllPhotos), photoEntry, false);
|
||||
allPhotosAlbum = new AlbumEntry(0, ApplicationLoader.applicationContext.getString(R.string.AllPhotos), photoEntry, false);
|
||||
albumsSorted.add(0, allPhotosAlbum);
|
||||
}
|
||||
if (allPhotosAlbum != null) {
|
||||
@@ -2273,13 +2113,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2310,7 +2150,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
PhotoEntry photoEntry = new PhotoEntry(bucketId, imageId, dateTaken, path, 0, true);
|
||||
|
||||
if (allVideosAlbum == null) {
|
||||
allVideosAlbum = new AlbumEntry(0, LocaleController.getString("AllVideo", R.string.AllVideo), photoEntry, true);
|
||||
allVideosAlbum = new AlbumEntry(0, ApplicationLoader.applicationContext.getString(R.string.AllVideo), photoEntry, true);
|
||||
videoAlbumsSorted.add(0, allVideosAlbum);
|
||||
}
|
||||
if (allVideosAlbum != null) {
|
||||
@@ -2334,13 +2174,13 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2587,7 +2427,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
th.start();
|
||||
th.join();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -2614,7 +2454,8 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
int rotationValue = messageObject.videoEditedInfo.rotationValue;
|
||||
int originalWidth = messageObject.videoEditedInfo.originalWidth;
|
||||
int originalHeight = messageObject.videoEditedInfo.originalHeight;
|
||||
int bitrate = messageObject.videoEditedInfo.bitrate;
|
||||
int originalBitrate = messageObject.videoEditedInfo.originalBitrate;
|
||||
int resultBitrate = messageObject.videoEditedInfo.resultBitrate;
|
||||
int rotateRender = 0;
|
||||
File cacheFile = new File(messageObject.messageOwner.attachPath);
|
||||
|
||||
@@ -2676,7 +2517,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
checkConversionCanceled();
|
||||
|
||||
if (resultWidth != originalWidth || resultHeight != originalHeight || rotateRender != 0) {
|
||||
if (resultBitrate<originalBitrate || resultWidth != originalWidth || resultHeight != originalHeight || rotateRender != 0) {
|
||||
int videoIndex;
|
||||
videoIndex = selectTrack(extractor, false);
|
||||
if (videoIndex >= 0) {
|
||||
@@ -2720,11 +2561,11 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
} else if (codecName.equals("OMX.TI.DUCATI1.VIDEO.H264E")) {
|
||||
processorType = PROCESSOR_TYPE_TI;
|
||||
}
|
||||
FileLog.e("messenger", "codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL);
|
||||
//Log.i("DeltaChat", "codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL);
|
||||
} else {
|
||||
colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
|
||||
}
|
||||
FileLog.e("messenger", "colorFormat = " + colorFormat);
|
||||
//Log.i("DeltaChat", "colorFormat = " + colorFormat);
|
||||
|
||||
int resultHeightAligned = resultHeight;
|
||||
int padding = 0;
|
||||
@@ -2765,7 +2606,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight);
|
||||
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
|
||||
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate != 0 ? bitrate : 921600);
|
||||
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, resultBitrate != 0 ? resultBitrate : 921600);
|
||||
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
|
||||
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
|
||||
if (Build.VERSION.SDK_INT < 18) {
|
||||
@@ -2916,7 +2757,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
|
||||
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
MediaFormat newFormat = decoder.getOutputFormat();
|
||||
FileLog.e("messenger", "newFormat = " + newFormat);
|
||||
//Log.i("DeltaChat", "newFormat = " + newFormat);
|
||||
} else if (decoderStatus < 0) {
|
||||
throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
|
||||
} else {
|
||||
@@ -2935,7 +2776,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
if (startTime > 0 && videoTime == -1) {
|
||||
if (info.presentationTimeUs < startTime) {
|
||||
doRender = false;
|
||||
FileLog.e("messenger", "drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs);
|
||||
//Log.i("DeltaChat", "drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs);
|
||||
} else {
|
||||
videoTime = info.presentationTimeUs;
|
||||
}
|
||||
@@ -2947,7 +2788,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
outputSurface.awaitNewImage();
|
||||
} catch (Exception e) {
|
||||
errorWait = true;
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
if (!errorWait) {
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
@@ -2964,14 +2805,14 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
Utilities.convertVideoFrame(rgbBuf, yuvBuf, colorFormat, resultWidth, resultHeight, padding, swapUV);
|
||||
encoder.queueInputBuffer(inputBufIndex, 0, bufferSize, info.presentationTimeUs, 0);
|
||||
} else {
|
||||
FileLog.e("messenger", "input buffer not available");
|
||||
//Log.i("DeltaChat", "input buffer not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||
decoderOutputAvailable = false;
|
||||
FileLog.e("messenger", "decoder stream end");
|
||||
//Log.i("DeltaChat", "decoder stream end");
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
encoder.signalEndOfInputStream();
|
||||
} else {
|
||||
@@ -2989,7 +2830,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
videoStartTime = videoTime;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
error = true;
|
||||
}
|
||||
|
||||
@@ -3023,7 +2864,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
}
|
||||
} catch (Exception e) {
|
||||
error = true;
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
if (extractor != null) {
|
||||
extractor.release();
|
||||
@@ -3032,10 +2873,10 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener,
|
||||
try {
|
||||
mediaMuxer.finishMovie(false);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
FileLog.e("messenger", "time = " + (System.currentTimeMillis() - time));
|
||||
//Log.i("DeltaChat", "time = " + (System.currentTimeMillis() - time));
|
||||
}
|
||||
} else {
|
||||
preferences.edit().putBoolean("isPreviousOk", true).apply();
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MessageObject {
|
||||
textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR;
|
||||
}
|
||||
|
||||
textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize));
|
||||
textPaint.setTextSize(AndroidUtilities.dp(ApplicationLoader.fontSize));
|
||||
|
||||
messageOwner = message;
|
||||
|
||||
@@ -106,13 +106,6 @@ public class MessageObject {
|
||||
|
||||
setType();
|
||||
|
||||
if (messageOwner.message != null && messageOwner.id < 0 && messageOwner.message.length() > 6 && isVideo()) {
|
||||
videoEditedInfo = new VideoEditedInfo();
|
||||
if (!videoEditedInfo.parseString(messageOwner.message)) {
|
||||
videoEditedInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
generateCaption();
|
||||
if (generateLayout) {
|
||||
messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
|
||||
@@ -127,7 +120,7 @@ public class MessageObject {
|
||||
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(Theme.MSG_TEXT_COLOR);
|
||||
textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR;
|
||||
textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize));
|
||||
textPaint.setTextSize(AndroidUtilities.dp(ApplicationLoader.fontSize));
|
||||
}
|
||||
return textPaint;
|
||||
}
|
||||
@@ -168,18 +161,7 @@ public class MessageObject {
|
||||
}
|
||||
|
||||
public static boolean isGifDocument(TLRPC.Document document) {
|
||||
return document != null && document.thumb != null && document.mime_type != null && (document.mime_type.equals("image/gif") || isNewGifDocument(document));
|
||||
}
|
||||
|
||||
public static boolean isNewGifDocument(TLRPC.Document document) {
|
||||
if (document != null && document.mime_type != null && document.mime_type.equals("video/mp4")) {
|
||||
for (int a = 0; a < document.attributes.size(); a++) {
|
||||
if (document.attributes.get(a) instanceof TLRPC.TL_documentAttributeAnimated) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return document != null && document.thumb != null && document.mime_type != null && document.mime_type.equals("image/gif");
|
||||
}
|
||||
|
||||
private void generateThumbs(boolean update) {
|
||||
@@ -316,7 +298,7 @@ public class MessageObject {
|
||||
try {
|
||||
Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,7 +309,7 @@ public class MessageObject {
|
||||
try {
|
||||
Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS | Linkify.EMAIL_ADDRESSES);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,7 +344,7 @@ public class MessageObject {
|
||||
try {
|
||||
textLayout = new StaticLayout(messageText, textPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -399,7 +381,7 @@ public class MessageObject {
|
||||
block.height = Math.max(block.height, block.textLayout.getLineBottom(block.textLayout.getLineCount() - 1));
|
||||
prevOffset = block.textYOffset;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (a == blocksCount - 1) {
|
||||
@@ -407,7 +389,7 @@ public class MessageObject {
|
||||
try {
|
||||
textHeight = Math.max(textHeight, (int) (block.textYOffset + block.textLayout.getHeight()));
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,14 +400,14 @@ public class MessageObject {
|
||||
try {
|
||||
lastLeft = block.textXOffset = block.textLayout.getLineLeft(currentBlockLinesCount - 1);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
float lastLine = 0;
|
||||
try {
|
||||
lastLine = block.textLayout.getLineWidth(currentBlockLinesCount - 1);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
int linesMaxWidth = (int) Math.ceil(lastLine);
|
||||
@@ -448,7 +430,7 @@ public class MessageObject {
|
||||
try {
|
||||
lineWidth = block.textLayout.getLineWidth(n);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
lineWidth = 0;
|
||||
}
|
||||
|
||||
@@ -459,7 +441,7 @@ public class MessageObject {
|
||||
try {
|
||||
lineLeft = block.textLayout.getLineLeft(n);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
lineLeft = 0;
|
||||
}
|
||||
|
||||
@@ -727,10 +709,6 @@ public class MessageObject {
|
||||
return messageOwner.media instanceof TLRPC.TL_messageMediaDocument && isGifDocument(messageOwner.media.document);
|
||||
}
|
||||
|
||||
public boolean isNewGif() {
|
||||
return messageOwner.media != null && isNewGifDocument(messageOwner.media.document);
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
TLRPC.Document document;
|
||||
document = messageOwner.media.document;
|
||||
@@ -802,11 +780,10 @@ public class MessageObject {
|
||||
}
|
||||
}
|
||||
|
||||
// EDIT BY MR: we use the attachPath for the normal images
|
||||
// we use the attachPath for the normal images (bp)
|
||||
if( (mediaExists || attachPathExists) && messageOwner.created_by_mr ) {
|
||||
attachPathExists = true;
|
||||
mediaExists = true;
|
||||
}
|
||||
// /EDIT BY MR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.b44t.ui.SettingsAdvActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MessagesController {
|
||||
|
||||
public int fontSize;
|
||||
|
||||
public static final int UPDATE_MASK_NAME = 1;
|
||||
public static final int UPDATE_MASK_AVATAR = 2;
|
||||
public static final int UPDATE_MASK_STATUS = 4;
|
||||
public static final int UPDATE_MASK_CHAT_AVATAR = 8;
|
||||
public static final int UPDATE_MASK_CHAT_NAME = 16;
|
||||
public static final int UPDATE_MASK_CHAT_MEMBERS = 32;
|
||||
public static final int UPDATE_MASK_SELECT_DIALOG = 512;
|
||||
public static final int UPDATE_MASK_NEW_MESSAGE = 2048;
|
||||
public static final int UPDATE_MASK_SEND_STATE = 4096;
|
||||
|
||||
private static volatile MessagesController Instance = null;
|
||||
|
||||
public static MessagesController getInstance() {
|
||||
MessagesController localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
synchronized (MessagesController.class) {
|
||||
localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
Instance = localInstance = new MessagesController();
|
||||
}
|
||||
}
|
||||
}
|
||||
return localInstance;
|
||||
}
|
||||
|
||||
public MessagesController() {
|
||||
ImageLoader.getInstance();
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE);
|
||||
fontSize = preferences.getInt("msg_font_size", SettingsAdvActivity.defMsgFontSize());
|
||||
}
|
||||
|
||||
public TLRPC.User getUser(Integer id) {
|
||||
// EDIT BY MR - additional information should be loaded as needed by the caller
|
||||
TLRPC.User u = new TLRPC.User();
|
||||
u.id = id;
|
||||
return u;
|
||||
}
|
||||
|
||||
public MediaController.SearchImage saveGif(TLRPC.Document document) {
|
||||
MediaController.SearchImage searchImage = new MediaController.SearchImage();
|
||||
searchImage.type = 2;
|
||||
searchImage.document = document;
|
||||
searchImage.date = (int) (System.currentTimeMillis() / 1000);
|
||||
searchImage.id = "" + searchImage.document.id;
|
||||
|
||||
ArrayList<MediaController.SearchImage> arrayList = new ArrayList<>();
|
||||
arrayList.add(searchImage);
|
||||
//MessagesStorage.getInstance().putWebRecent(arrayList);
|
||||
/*TLRPC.TL_messages_saveGif req = new TLRPC.TL_messages_saveGif();
|
||||
req.id = new TLRPC.TL_inputDocument();
|
||||
req.id.id = searchImage.document.id;
|
||||
req.id.access_hash = searchImage.document.access_hash;
|
||||
req.unsave = false;
|
||||
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
|
||||
}
|
||||
});*/
|
||||
return searchImage;
|
||||
}
|
||||
|
||||
public void cancelTyping(int action, long dialog_id) {
|
||||
}
|
||||
|
||||
public void sendTyping(final long dialog_id, final int action, int classGuid) {
|
||||
}
|
||||
|
||||
public void markMessageContentAsRead(final MessageObject messageObject) {
|
||||
}
|
||||
|
||||
public void changeChatAvatar(int chat_id, TLRPC.InputFile uploadedAvatar) {
|
||||
/*TLObject request;
|
||||
{
|
||||
TLRPC.TL_messages_editChatPhoto req = new TLRPC.TL_messages_editChatPhoto();
|
||||
req.chat_id = chat_id;
|
||||
if (uploadedAvatar != null) {
|
||||
req.photo = new TLRPC.TL_inputChatUploadedPhoto();
|
||||
req.photo.file = uploadedAvatar;
|
||||
req.photo.crop = new TLRPC.TL_inputPhotoCropAuto();
|
||||
} else {
|
||||
req.photo = new TLRPC.TL_inputChatPhotoEmpty();
|
||||
}
|
||||
request = req;
|
||||
}*/
|
||||
/*ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
if (error != null) {
|
||||
return;
|
||||
}
|
||||
processUpdates((TLRPC.Updates) response, false);
|
||||
}
|
||||
}, ConnectionsManager.RequestFlagInvokeAfter);*/
|
||||
}
|
||||
|
||||
public boolean isDialogMuted(long dialog_id) {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
int mute_type = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (mute_type == 2) {
|
||||
return true;
|
||||
} else if (mute_type == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class MrContact {
|
||||
public String getName() {
|
||||
return MrContactGetName(m_hContact);
|
||||
}
|
||||
public native String getAuthName();
|
||||
|
||||
public String getAddr() {
|
||||
return MrContactGetAddr(m_hContact);
|
||||
|
||||
@@ -28,6 +28,22 @@
|
||||
package com.b44t.messenger;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import com.b44t.ui.Components.ForegroundDetector;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class MrMailbox {
|
||||
|
||||
public static void init () {
|
||||
@@ -38,53 +54,25 @@ public class MrMailbox {
|
||||
public native static void close();
|
||||
public native static String getBlobdir();
|
||||
|
||||
public static int configure() {
|
||||
return MrMailboxConfigure(m_hMailbox);
|
||||
}
|
||||
public native static void configureAndConnect();
|
||||
public native static void configureCancel();
|
||||
|
||||
public static int isConfigured() {
|
||||
return MrMailboxIsConfigured(m_hMailbox);
|
||||
}
|
||||
public native static int isConfigured();
|
||||
|
||||
public static int connect() {
|
||||
return MrMailboxConnect(m_hMailbox);
|
||||
}
|
||||
public native static void connect();
|
||||
public native static void disconnect();
|
||||
|
||||
public static void disconnect() {
|
||||
MrMailboxDisconnect(m_hMailbox);
|
||||
}
|
||||
|
||||
public static int fetch() {
|
||||
return MrMailboxFetch(m_hMailbox);
|
||||
}
|
||||
|
||||
public native static String getErrorDescr();
|
||||
|
||||
public static int setConfig(String key, String value) {
|
||||
return MrMailboxSetConfig(m_hMailbox, key, value);
|
||||
}
|
||||
|
||||
public static String getConfig(String key, String def) {
|
||||
return MrMailboxGetConfig(m_hMailbox, key, def);
|
||||
}
|
||||
|
||||
public static int getConfigInt(String key, int def) {
|
||||
return MrMailboxGetConfigInt(m_hMailbox, key, def);
|
||||
}
|
||||
public native static void setConfig(String key, String value);
|
||||
public native static void setConfigInt(String key, int value);
|
||||
public native static String getConfig(String key, String def);
|
||||
public native static int getConfigInt(String key, int def);
|
||||
|
||||
public native static String getInfo();
|
||||
public native static String cmdline(String cmd);
|
||||
public native static void heartbeat();
|
||||
|
||||
private static long m_hMailbox = 0; // do not rename this, is used in C-part
|
||||
private native static long MrMailboxNew (); // returns hMailbox which must be unref'd after usage (Names as mrmailbox_new don't work due to the additional underscore)
|
||||
private native static int MrMailboxConfigure (long hMailbox);
|
||||
private native static int MrMailboxIsConfigured (long hMailbox);
|
||||
private native static int MrMailboxConnect (long hMailbox);
|
||||
private native static void MrMailboxDisconnect (long hMailbox);
|
||||
private native static int MrMailboxFetch (long hMailbox);
|
||||
private native static int MrMailboxSetConfig (long hMailbox, String key, String value); // value may be NULL
|
||||
private native static String MrMailboxGetConfig (long hMailbox, String key, String def); // def may be NULL, returns empty string as NULL
|
||||
private native static int MrMailboxGetConfigInt (long hMailbox, String key, int def); // def may be NULL, returns empty string as NULL
|
||||
private native static long MrMailboxNew(); // returns hMailbox which must be unref'd after usage (Names as mrmailbox_new don't work due to the additional underscore)
|
||||
|
||||
// contacts
|
||||
public native static int[] getKnownContacts(String query);
|
||||
@@ -130,13 +118,8 @@ public class MrMailbox {
|
||||
public native static int markseenMsg (int msg_id);
|
||||
public native static int markseenChat (int chat_id);
|
||||
|
||||
public static int getChatIdByContactId (int contact_id) {
|
||||
return MrMailboxGetChatIdByContactId(m_hMailbox, contact_id);
|
||||
}
|
||||
|
||||
public static int createChatByContactId(int contact_id) {
|
||||
return MrMailboxCreateChatByContactId(m_hMailbox, contact_id);
|
||||
}
|
||||
public native static int getChatIdByContactId (int contact_id);
|
||||
public native static int createChatByContactId(int contact_id); // returns chat_id
|
||||
|
||||
public native static int createGroupChat (String name);
|
||||
public native static int isContactInChat (int chat_id, int contact_id);
|
||||
@@ -158,8 +141,6 @@ public class MrMailbox {
|
||||
|
||||
private native static long MrMailboxGetChatlist (long hMailbox, String query); // returns hChatlist which must be unref'd after usage
|
||||
private native static long MrMailboxGetChat (long hMailbox, int chat_id); // return hChat which must be unref'd after usage
|
||||
private native static int MrMailboxGetChatIdByContactId (long hMailbox, int contact_id);
|
||||
private native static int MrMailboxCreateChatByContactId(long hMailbox, int contact_id); // returns chat_id
|
||||
|
||||
|
||||
// msgs
|
||||
@@ -185,27 +166,51 @@ public class MrMailbox {
|
||||
/* receive events
|
||||
**********************************************************************************************/
|
||||
|
||||
public final static int MR_EVENT_INFO = 100;
|
||||
public final static int MR_EVENT_WARNING = 300;
|
||||
public final static int MR_EVENT_ERROR = 400; // INFO and WARNING are blocked in the mrwrapper.c
|
||||
|
||||
public final static int MR_EVENT_MSGS_CHANGED = 2000;
|
||||
public final static int MR_EVENT_INCOMING_MSG = 2005;
|
||||
public final static int MR_EVENT_MSG_DELIVERED = 2010;
|
||||
public final static int MR_EVENT_MSG_READ = 2015;
|
||||
|
||||
public final static int MR_EVENT_CHAT_MODIFIED = 2020;
|
||||
|
||||
public final static int MR_EVENT_CONTACTS_CHANGED = 2030;
|
||||
public final static int MR_EVENT_CONNECTION_STATE_CHANGED = 2040;
|
||||
public final static int MR_EVENT_REPORT = 2050;
|
||||
|
||||
public final static int MR_EVENT_CONFIGURE_ENDED = 2040;
|
||||
public final static int MR_EVENT_CONFIGURE_PROGRESS = 2041;
|
||||
|
||||
public final static int MR_EVENT_IS_ONLINE = 2080;
|
||||
public final static int MR_EVENT_GET_STRING = 2091;
|
||||
public final static int MR_EVENT_GET_QUANTITIY_STRING = 2092;
|
||||
public final static int MR_EVENT_HTTP_GET = 2100;
|
||||
public final static int MR_EVENT_WAKE_LOCK = 2110;
|
||||
|
||||
public final static int MR_REPORT_ERR_SELF_NOT_IN_GROUP = 1;
|
||||
|
||||
public static final Object m_lastErrorLock = new Object();
|
||||
public static int m_lastErrorCode = 0;
|
||||
public static String m_lastErrorString = "";
|
||||
public static boolean m_showNextErrorAsToast = true;
|
||||
|
||||
public static long MrCallback(final int event, final long data1, final long data2) // this function is called from within the C-wrapper
|
||||
{
|
||||
switch(event) {
|
||||
case MR_EVENT_CONNECTION_STATE_CHANGED:
|
||||
case MR_EVENT_CONFIGURE_ENDED:
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.connectionStateChanged, (int)data1);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.configureEnded, (int)data1);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
|
||||
case MR_EVENT_CONFIGURE_PROGRESS:
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.configureProgress, (int)data1);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
@@ -254,18 +259,45 @@ public class MrMailbox {
|
||||
public void run() {
|
||||
reloadMainChatlist();
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces,
|
||||
MessagesController.UPDATE_MASK_NAME|MessagesController.UPDATE_MASK_CHAT_NAME|
|
||||
MessagesController.UPDATE_MASK_CHAT_MEMBERS|MessagesController.UPDATE_MASK_AVATAR);
|
||||
UPDATE_MASK_NAME|UPDATE_MASK_CHAT_NAME|
|
||||
UPDATE_MASK_CHAT_MEMBERS|UPDATE_MASK_AVATAR);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
|
||||
case MR_EVENT_REPORT:
|
||||
case MR_EVENT_INFO:
|
||||
Log.i("DeltaChat", CPtr2String(data2));
|
||||
if( BuildConfig.BUILD_TYPE.equals("debug") ) {
|
||||
FileLog.i("DeltaChat", CPtr2String(data2)); // TODO: Can be removed as soon as notifications are reliable.
|
||||
}
|
||||
break;
|
||||
|
||||
case MR_EVENT_WARNING:
|
||||
Log.w("DeltaChat", CPtr2String(data2));
|
||||
if( BuildConfig.BUILD_TYPE.equals("debug") ) {
|
||||
FileLog.w("DeltaChat", CPtr2String(data2)); // TODO: Can be removed as soon as notifications are reliable.
|
||||
}
|
||||
break;
|
||||
|
||||
case MR_EVENT_ERROR:
|
||||
Log.e("DeltaChat", CPtr2String(data2));
|
||||
if( BuildConfig.BUILD_TYPE.equals("debug") ) {
|
||||
FileLog.e("DeltaChat", CPtr2String(data2)); // TODO: Can be removed as soon as notifications are reliable.
|
||||
}
|
||||
synchronized (m_lastErrorLock) {
|
||||
m_lastErrorCode = (int)data1;
|
||||
m_lastErrorString = CPtr2String(data2);
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(data1==MR_REPORT_ERR_SELF_NOT_IN_GROUP) {
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.errSelfNotInGroup);
|
||||
synchronized (m_lastErrorLock) {
|
||||
if( m_showNextErrorAsToast ) {
|
||||
if(ForegroundDetector.getInstance().isForeground()) {
|
||||
AndroidUtilities.showHint(ApplicationLoader.applicationContext, m_lastErrorString);
|
||||
}
|
||||
}
|
||||
m_showNextErrorAsToast = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -274,6 +306,7 @@ public class MrMailbox {
|
||||
case MR_EVENT_GET_STRING:
|
||||
String s = "ErrStrBadId";
|
||||
switch( (int)data1 ) {
|
||||
// the string-IDs are defined in the backend; as this is the only place where they're used, there is no benefit in creating an enum or sth. like that.
|
||||
case 1: s = ApplicationLoader.applicationContext.getString(R.string.NoMessages); break;
|
||||
case 2: s = ApplicationLoader.applicationContext.getString(R.string.FromSelf); break;
|
||||
case 3: s = ApplicationLoader.applicationContext.getString(R.string.Draft); break;
|
||||
@@ -281,7 +314,7 @@ public class MrMailbox {
|
||||
case 8: s = ApplicationLoader.applicationContext.getString(R.string.Deaddrop); break;
|
||||
case 9: s = ApplicationLoader.applicationContext.getString(R.string.AttachPhoto); break;
|
||||
case 10: s = ApplicationLoader.applicationContext.getString(R.string.AttachVideo); break;
|
||||
case 11: s = ApplicationLoader.applicationContext.getString(R.string.AttachMusic); break;
|
||||
case 11: s = ApplicationLoader.applicationContext.getString(R.string.Audio); break;
|
||||
case 12: s = ApplicationLoader.applicationContext.getString(R.string.AttachDocument); break;
|
||||
case 13: s = ApplicationLoader.applicationContext.getString(R.string.DefaultStatusText); break;
|
||||
case 14: s = ApplicationLoader.applicationContext.getString(R.string.MsgNewGroupDraft); break;
|
||||
@@ -290,16 +323,58 @@ public class MrMailbox {
|
||||
case 17: s = ApplicationLoader.applicationContext.getString(R.string.MsgMemberAddedToGroup); break;
|
||||
case 18: s = ApplicationLoader.applicationContext.getString(R.string.MsgMemberRemovedFromToGroup); break;
|
||||
case 19: s = ApplicationLoader.applicationContext.getString(R.string.MsgGroupLeft); break;
|
||||
case 20: s = ApplicationLoader.applicationContext.getString(R.string.Error); break;
|
||||
case 21: s = ApplicationLoader.applicationContext.getString(R.string.ErrSelfNotInGroup); break;
|
||||
case 22: s = ApplicationLoader.applicationContext.getString(R.string.NoNetwork); break;
|
||||
case 23: s = ApplicationLoader.applicationContext.getString(R.string.AttachGif); break;
|
||||
}
|
||||
return String2CPtr(s);
|
||||
|
||||
case MR_EVENT_GET_QUANTITIY_STRING:
|
||||
String sp = "ErrQtyStrBadId";
|
||||
switch( (int)data1 ) {
|
||||
// the string-IDs are defined in the backend; as this is the only place where they're used, there is no benefit in creating an enum or sth. like that.
|
||||
case 4: sp = ApplicationLoader.applicationContext.getResources().getQuantityString(R.plurals.Members, (int)data2, (int)data2); break;
|
||||
case 6: sp = ApplicationLoader.applicationContext.getResources().getQuantityString(R.plurals.Contacts, (int)data2, (int)data2); break;
|
||||
}
|
||||
return String2CPtr(sp);
|
||||
|
||||
case MR_EVENT_IS_ONLINE:
|
||||
return ApplicationLoader.isNetworkOnline()? 1 : 0;
|
||||
|
||||
case MR_EVENT_HTTP_GET:
|
||||
String httpContent = null;
|
||||
try {
|
||||
URL url = new URL(CPtr2String(data1));
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
try {
|
||||
InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream());
|
||||
|
||||
BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
|
||||
StringBuilder total = new StringBuilder();
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
total.append(line).append('\n');
|
||||
}
|
||||
httpContent = total.toString();
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
}
|
||||
catch(Exception e) {}
|
||||
return String2CPtr(httpContent);
|
||||
|
||||
case MR_EVENT_WAKE_LOCK:
|
||||
if( data1 != 0 ) {
|
||||
ApplicationLoader.backendWakeLock.acquire();
|
||||
}
|
||||
else {
|
||||
if( !ApplicationLoader.wakeupWakeLock.isHeld()) {
|
||||
ApplicationLoader.wakeupWakeLock.acquire(1 * 1000); /* make sure, subsequent release/acquires do not make the CPU sleep */
|
||||
}
|
||||
ApplicationLoader.backendWakeLock.release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -308,6 +383,14 @@ public class MrMailbox {
|
||||
/* additional functions that are not 1:1 available in the backend
|
||||
**********************************************************************************************/
|
||||
|
||||
public static void log_i(String tag, String msg)
|
||||
{
|
||||
Log.i(tag, msg);
|
||||
if( BuildConfig.BUILD_TYPE.equals("debug") ) {
|
||||
FileLog.i(tag, msg); // TODO: Can be removed as soon as notifications are reliable.
|
||||
}
|
||||
}
|
||||
|
||||
public static MrChatlist m_currChatlist = new MrChatlist(0);
|
||||
public native static int getCurrentTime ();
|
||||
public static void reloadMainChatlist()
|
||||
@@ -336,7 +419,47 @@ public class MrMailbox {
|
||||
public static String getInviteText() {
|
||||
String url = "https://getdelta.org";
|
||||
String email = getConfig("addr", "");
|
||||
String text = LocaleController.formatString("InviteText", R.string.InviteText, url, email);
|
||||
String text = String.format(ApplicationLoader.applicationContext.getString(R.string.InviteText), url, email);
|
||||
return text;
|
||||
}
|
||||
|
||||
public static TLRPC.User getUser(Integer id) {
|
||||
TLRPC.User u = new TLRPC.User(); // legacy function, information should be loaded as needed by the caller
|
||||
u.id = id;
|
||||
return u;
|
||||
}
|
||||
|
||||
public static void cancelTyping(int action, long dialog_id) {
|
||||
}
|
||||
|
||||
public static void sendTyping(final long dialog_id, final int action, int classGuid) {
|
||||
}
|
||||
|
||||
public static void markMessageContentAsRead(final MessageObject messageObject) {
|
||||
}
|
||||
|
||||
public static boolean isDialogMuted(long dialog_id) {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
int mute_type = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (mute_type == 2) {
|
||||
return true;
|
||||
} else if (mute_type == 3) {
|
||||
int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (mute_until >= MrMailbox.getCurrentTime()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// legacy update masks
|
||||
public static final int UPDATE_MASK_NAME = 1;
|
||||
public static final int UPDATE_MASK_AVATAR = 2;
|
||||
public static final int UPDATE_MASK_STATUS = 4;
|
||||
public static final int UPDATE_MASK_CHAT_AVATAR = 8;
|
||||
public static final int UPDATE_MASK_CHAT_NAME = 16;
|
||||
public static final int UPDATE_MASK_CHAT_MEMBERS = 32;
|
||||
public static final int UPDATE_MASK_SELECT_DIALOG = 512;
|
||||
public static final int UPDATE_MASK_NEW_MESSAGE = 2048;
|
||||
public static final int UPDATE_MASK_SEND_STATE = 4096;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class MrMsg {
|
||||
public final static int MR_MSG_UNDEFINED = 0;
|
||||
public final static int MR_MSG_TEXT = 10;
|
||||
public final static int MR_MSG_IMAGE = 20;
|
||||
public final static int MR_MSG_GIF = 21;
|
||||
public final static int MR_MSG_AUDIO = 40;
|
||||
public final static int MR_MSG_VOICE = 41;
|
||||
public final static int MR_MSG_VIDEO = 50;
|
||||
@@ -195,7 +196,7 @@ public class MrMsg {
|
||||
ret.message = "<cannot load image>";
|
||||
}
|
||||
}
|
||||
else if( type == MR_MSG_AUDIO || type == MR_MSG_VOICE || type == MR_MSG_VIDEO || type == MR_MSG_FILE ) {
|
||||
else if( type == MR_MSG_GIF || type == MR_MSG_AUDIO || type == MR_MSG_VOICE || type == MR_MSG_VIDEO || type == MR_MSG_FILE ) {
|
||||
String path = getParam('f', "");
|
||||
if( !path.isEmpty()) {
|
||||
ret.message = "-1"; // may be misused for video editing information
|
||||
@@ -205,7 +206,18 @@ public class MrMsg {
|
||||
ret.media.document.file_name = getFilename();
|
||||
ret.media.document.mr_path = path;
|
||||
ret.media.document.size = getBytes();
|
||||
if( type == MR_MSG_AUDIO || type == MR_MSG_VOICE ) {
|
||||
if( type == MR_MSG_GIF ) {
|
||||
ret.media.document.mime_type = getParam('m', "image/gif");
|
||||
TLRPC.PhotoSize size = new TLRPC.PhotoSize();
|
||||
size.location = new TLRPC.TL_fileLocation();
|
||||
size.location.mr_path = path;
|
||||
size.location.local_id = -ret.id;
|
||||
size.w = getParamInt('w', 320);
|
||||
size.h = getParamInt('h', 240);
|
||||
size.type = "s";
|
||||
ret.media.document.thumb = size;
|
||||
}
|
||||
else if( type == MR_MSG_AUDIO || type == MR_MSG_VOICE ) {
|
||||
TLRPC.TL_documentAttributeAudio attr = new TLRPC.TL_documentAttributeAudio();
|
||||
attr.voice = type == MR_MSG_VOICE;
|
||||
attr.duration = getParamInt('d', 0) / 1000;
|
||||
@@ -215,15 +227,18 @@ public class MrMsg {
|
||||
File vfile = new File(path);
|
||||
File tfile = new File(MrMailbox.getBlobdir(), vfile.getName()+"-preview.jpg");
|
||||
if( !tfile.exists() ) {
|
||||
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||
TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(tfile, thumb, 90, 90, 55, false);
|
||||
size.location.mr_path = tfile.getAbsolutePath();
|
||||
size.type = "s";
|
||||
ret.media.document.thumb = size;
|
||||
try {
|
||||
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||
TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(tfile, thumb, 90, 90, 55, false);
|
||||
size.location.mr_path = tfile.getAbsolutePath();
|
||||
size.type = "s";
|
||||
ret.media.document.thumb = size;
|
||||
|
||||
setParamInt('w', size.w);
|
||||
setParamInt('h', size.h);
|
||||
saveParamToDisk();
|
||||
setParamInt('w', size.w);
|
||||
setParamInt('h', size.h);
|
||||
saveParamToDisk();
|
||||
}
|
||||
catch (Exception e) {}
|
||||
}
|
||||
else {
|
||||
TLRPC.PhotoSize size = new TLRPC.PhotoSize();
|
||||
@@ -238,8 +253,8 @@ public class MrMsg {
|
||||
|
||||
TLRPC.TL_documentAttributeVideo attr = new TLRPC.TL_documentAttributeVideo();
|
||||
attr.duration = getParamInt('d', 0) / 1000;
|
||||
attr.w = ret.media.document.thumb.w;
|
||||
attr.h = ret.media.document.thumb.h;
|
||||
attr.w = getParamInt('w', 320);
|
||||
attr.h = getParamInt('h', 240);
|
||||
ret.media.document.attributes.add(attr);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -96,7 +96,7 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
|
||||
remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
|
||||
RemoteControlClient.FLAG_KEY_MEDIA_STOP | RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS | RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
createNotification(messageObject);
|
||||
@@ -182,7 +182,7 @@ public class MusicPlayerService extends Service implements NotificationCenter.No
|
||||
try {
|
||||
metadataEditor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, audioInfo.getCover());
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
metadataEditor.apply();
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class NativeLoader {
|
||||
|
||||
private final static int LIB_VERSION = 1;
|
||||
private final static String LIB_NAME = "messenger." + LIB_VERSION;
|
||||
private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so";
|
||||
private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.so";
|
||||
//private String crashPath = "";
|
||||
|
||||
private static volatile boolean nativeLoaded = false;
|
||||
|
||||
private static File getNativeLibraryDir(Context context) {
|
||||
File f = null;
|
||||
if (context != null) {
|
||||
try {
|
||||
f = new File((String)ApplicationInfo.class.getField("nativeLibraryDir").get(context.getApplicationInfo()));
|
||||
} catch (Throwable th) {
|
||||
th.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (f == null) {
|
||||
f = new File(context.getApplicationInfo().dataDir, "lib");
|
||||
}
|
||||
if (f.isDirectory()) {
|
||||
return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean loadFromZip(Context context, File destDir, File destLocalFile, String folder) {
|
||||
try {
|
||||
for (File file : destDir.listFiles()) {
|
||||
file.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
|
||||
ZipFile zipFile = null;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
zipFile = new ZipFile(context.getApplicationInfo().sourceDir);
|
||||
ZipEntry entry = zipFile.getEntry("lib/" + folder + "/" + LIB_SO_NAME);
|
||||
if (entry == null) {
|
||||
throw new Exception("Unable to find file in apk:" + "lib/" + folder + "/" + LIB_NAME);
|
||||
}
|
||||
stream = zipFile.getInputStream(entry);
|
||||
|
||||
OutputStream out = new FileOutputStream(destLocalFile);
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = stream.read(buf)) > 0) {
|
||||
Thread.yield();
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.close();
|
||||
|
||||
destLocalFile.setReadable(true, false);
|
||||
destLocalFile.setExecutable(true, false);
|
||||
destLocalFile.setWritable(true);
|
||||
|
||||
try {
|
||||
System.load(destLocalFile.getAbsolutePath());
|
||||
//init(context.getCacheDir().getAbsolutePath(), BuildVars.DEBUG_VERSION);
|
||||
nativeLoaded = true;
|
||||
} catch (Error e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
if (zipFile != null) {
|
||||
try {
|
||||
zipFile.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static synchronized void initNativeLibs(Context context) {
|
||||
if (nativeLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
//String crashDir = context.getCacheDir().getAbsolutePath();
|
||||
|
||||
try {
|
||||
String folder;
|
||||
try {
|
||||
if (Build.CPU_ABI.equalsIgnoreCase("armeabi-v7a")) {
|
||||
folder = "armeabi-v7a";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("armeabi")) {
|
||||
folder = "armeabi";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("x86")) {
|
||||
folder = "x86";
|
||||
} else if (Build.CPU_ABI.equalsIgnoreCase("mips")) {
|
||||
folder = "mips";
|
||||
} else {
|
||||
folder = "armeabi";
|
||||
FileLog.e("messenger", "Unsupported arch: " + Build.CPU_ABI);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
folder = "armeabi";
|
||||
}
|
||||
|
||||
String javaArch = System.getProperty("os.arch");
|
||||
if (javaArch != null && javaArch.contains("686")) {
|
||||
folder = "x86";
|
||||
}
|
||||
|
||||
|
||||
File destFile = getNativeLibraryDir(context);
|
||||
if (destFile != null) {
|
||||
destFile = new File(destFile, LIB_SO_NAME);
|
||||
if (destFile.exists()) {
|
||||
FileLog.d("messenger", "load normal lib");
|
||||
try {
|
||||
System.loadLibrary(LIB_NAME);
|
||||
//init(crashDir, BuildVars.DEBUG_VERSION);
|
||||
nativeLoaded = true;
|
||||
return;
|
||||
} catch (Error e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File destDir = new File(context.getFilesDir(), "lib");
|
||||
destDir.mkdirs();
|
||||
|
||||
File destLocalFile = new File(destDir, LOCALE_LIB_SO_NAME);
|
||||
if (destLocalFile.exists()) {
|
||||
try {
|
||||
FileLog.d("messenger", "Load local lib");
|
||||
System.load(destLocalFile.getAbsolutePath());
|
||||
//init(crashDir, BuildVars.DEBUG_VERSION);
|
||||
nativeLoaded = true;
|
||||
return;
|
||||
} catch (Error e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
destLocalFile.delete();
|
||||
}
|
||||
|
||||
FileLog.e("messenger", "Library not found, arch = " + folder);
|
||||
|
||||
if (loadFromZip(context, destDir, destLocalFile, folder)) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
System.loadLibrary(LIB_NAME);
|
||||
//init(crashDir, BuildVars.DEBUG_VERSION);
|
||||
nativeLoaded = true;
|
||||
} catch (Error e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
}
|
||||
|
||||
//private static native void init(String path, boolean enableGoglBreakpad); // not needed
|
||||
//public static native void crash();
|
||||
}
|
||||
@@ -31,7 +31,8 @@ public class NotificationCenter {
|
||||
|
||||
private static int totalEvents = 1;
|
||||
|
||||
public static final int connectionStateChanged = totalEvents++;
|
||||
public static final int configureEnded = totalEvents++;
|
||||
public static final int configureProgress = totalEvents++;
|
||||
public static final int didReceivedNewMessages = totalEvents++;
|
||||
public static final int updateInterfaces = totalEvents++;
|
||||
public static final int dialogsNeedReload = totalEvents++;
|
||||
@@ -43,9 +44,7 @@ public class NotificationCenter {
|
||||
public static final int chatDidCreated = totalEvents++;
|
||||
public static final int mediaDidLoaded = totalEvents++;
|
||||
public static final int mediaCountDidLoaded = totalEvents++;
|
||||
public static final int dialogPhotosLoaded = totalEvents++;
|
||||
public static final int notificationsSettingsUpdated = totalEvents++;
|
||||
//public static final int pushMessagesUpdated = totalEvents++;
|
||||
public static final int blockedUsersDidLoaded = totalEvents++;
|
||||
public static final int openedChatChanged = totalEvents++;
|
||||
public static final int mainUserInfoChanged = totalEvents++;
|
||||
@@ -54,23 +53,13 @@ public class NotificationCenter {
|
||||
public static final int didSetPasscode = totalEvents++;
|
||||
//public static final int screenStateChanged = totalEvents++; -- currently not used, but this may get handy
|
||||
public static final int stickersDidLoaded = totalEvents++;
|
||||
public static final int didReplacedPhotoInMemCache = totalEvents++;
|
||||
//public static final int musicDidLoaded = totalEvents++;
|
||||
public static final int reloadHints = totalEvents++;
|
||||
|
||||
public static final int httpFileDidLoaded = totalEvents++;
|
||||
public static final int httpFileDidFailedLoad = totalEvents++;
|
||||
|
||||
public static final int messageThumbGenerated = totalEvents++;
|
||||
|
||||
public static final int wallpapersDidLoaded = totalEvents++;
|
||||
public static final int closeOtherAppActivities = totalEvents++;
|
||||
public static final int didUpdatedConnectionState = totalEvents++;
|
||||
public static final int emojiDidLoaded = totalEvents++;
|
||||
|
||||
public static final int FileLoadProgressChanged = totalEvents++;
|
||||
public static final int FileDidLoaded = totalEvents++;
|
||||
public static final int FileDidFailedLoad = totalEvents++;
|
||||
public static final int FilePreparingStarted = totalEvents++;
|
||||
public static final int FileNewChunkAvailable = totalEvents++;
|
||||
public static final int FilePreparingFailed = totalEvents++;
|
||||
@@ -87,8 +76,6 @@ public class NotificationCenter {
|
||||
public static final int audioDidStarted = totalEvents++;
|
||||
public static final int audioRouteChanged = totalEvents++;
|
||||
|
||||
public static final int errSelfNotInGroup = totalEvents++;
|
||||
|
||||
private SparseArray<ArrayList<Object>> observers = new SparseArray<>();
|
||||
private SparseArray<ArrayList<Object>> removeAfterBroadcast = new SparseArray<>();
|
||||
private SparseArray<ArrayList<Object>> addAfterBroadcast = new SparseArray<>();
|
||||
@@ -163,17 +150,18 @@ public class NotificationCenter {
|
||||
}
|
||||
|
||||
public void postNotificationNameInternal(int id, boolean allowDuringAnimation, Object... args) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
/*if (BuildConfig.BUILD_TYPE.equals("debug")) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("postNotificationName allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if (!allowDuringAnimation && animationInProgress) {
|
||||
DelayedPost delayedPost = new DelayedPost(id, args);
|
||||
delayedPosts.add(delayedPost);
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
FileLog.e("messenger", "delay post notification " + id + " with args count = " + args.length);
|
||||
}
|
||||
/*if (BuildConfig.BUILD_TYPE.equals("debug")) {
|
||||
Log.i("DeltaChat", "delay post notification " + id + " with args count = " + args.length);
|
||||
}*/
|
||||
return;
|
||||
}
|
||||
broadcasting++;
|
||||
@@ -210,11 +198,11 @@ public class NotificationCenter {
|
||||
}
|
||||
|
||||
public void addObserver(Object observer, int id) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
/*if (BuildConfig.BUILD_TYPE.equals("debug")) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("addObserver allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if (broadcasting != 0) {
|
||||
ArrayList<Object> arrayList = addAfterBroadcast.get(id);
|
||||
if (arrayList == null) {
|
||||
@@ -235,11 +223,11 @@ public class NotificationCenter {
|
||||
}
|
||||
|
||||
public void removeObserver(Object observer, int id) {
|
||||
if (BuildVars.DEBUG_VERSION) {
|
||||
/*if (BuildConfig.BUILD_TYPE.equals("debug")) {
|
||||
if (Thread.currentThread() != ApplicationLoader.applicationHandler.getLooper().getThread()) {
|
||||
throw new RuntimeException("removeObserver allowed only from MAIN thread");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if (broadcasting != 0) {
|
||||
ArrayList<Object> arrayList = removeAfterBroadcast.get(id);
|
||||
if (arrayList == null) {
|
||||
|
||||
@@ -90,11 +90,13 @@ public class NotificationsController {
|
||||
protected AudioManager audioManager;
|
||||
private AlarmManager alarmManager;
|
||||
|
||||
public static final int DEF_LED_COLOR = 0xffffffff;
|
||||
|
||||
private static volatile NotificationsController Instance = null;
|
||||
public static NotificationsController getInstance() {
|
||||
NotificationsController localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
synchronized (MessagesController.class) {
|
||||
synchronized (NotificationsController.class) {
|
||||
localInstance = Instance;
|
||||
if (localInstance == null) {
|
||||
Instance = localInstance = new NotificationsController();
|
||||
@@ -113,26 +115,26 @@ public class NotificationsController {
|
||||
try {
|
||||
audioManager = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
try {
|
||||
alarmManager = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE);
|
||||
notificationDelayWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock");
|
||||
notificationDelayWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "notificationDelayWakelock" /*any name*/);
|
||||
notificationDelayWakelock.setReferenceCounted(false);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
notificationDelayRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FileLog.e("messenger", "delay reached");
|
||||
//Log.i("DeltaChat", "delay reached");
|
||||
if (!delayedPushMessages.isEmpty()) {
|
||||
showOrUpdateNotification(true);
|
||||
delayedPushMessages.clear();
|
||||
@@ -142,7 +144,7 @@ public class NotificationsController {
|
||||
notificationDelayWakelock.release();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -170,7 +172,7 @@ public class NotificationsController {
|
||||
notificationDelayWakelock.release();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
setBadge(0);
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||
@@ -769,12 +771,12 @@ public class NotificationsController {
|
||||
intent.putExtra("badge_count_class_name", launcherClassName);
|
||||
ApplicationLoader.applicationContext.sendBroadcast(intent);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -819,7 +821,7 @@ public class NotificationsController {
|
||||
alarmManager.cancel(pintent);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,7 +840,7 @@ public class NotificationsController {
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -847,7 +849,7 @@ public class NotificationsController {
|
||||
int notifyOverride = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (notifyOverride == 3) {
|
||||
int muteUntil = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
if (muteUntil >= ConnectionsManager.getInstance().getCurrentTime()) {
|
||||
if (muteUntil >= MrMailbox.getCurrentTime()) {
|
||||
notifyOverride = 2;
|
||||
}
|
||||
}
|
||||
@@ -876,7 +878,7 @@ public class NotificationsController {
|
||||
total_unread_count = 0;
|
||||
setBadge(0);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,7 +891,7 @@ public class NotificationsController {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -925,23 +927,23 @@ public class NotificationsController {
|
||||
soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*private void scheduleNotificationDelay(boolean onlineReason) {
|
||||
try {
|
||||
FileLog.e("messenger", "delay notification start, onlineReason = " + onlineReason);
|
||||
//Log.i("DeltaChat", "delay notification start, onlineReason = " + onlineReason);
|
||||
notificationDelayWakelock.acquire(10000);
|
||||
AndroidUtilities.cancelRunOnUIThread(notificationDelayRunnable);
|
||||
AndroidUtilities.runOnUIThread(notificationDelayRunnable, (onlineReason ? 3 * 1000 : 1000));
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
showOrUpdateNotification(notifyCheck);
|
||||
}
|
||||
}*/
|
||||
@@ -968,8 +970,6 @@ public class NotificationsController {
|
||||
}
|
||||
|
||||
try {
|
||||
ConnectionsManager.getInstance().resumeNetworkMaybe();
|
||||
|
||||
MessageObject lastMessageObject = pushMessages.get(0);
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
||||
int dismissDate = preferences.getInt("dismissDate", 0);
|
||||
@@ -994,7 +994,7 @@ public class NotificationsController {
|
||||
boolean notifyDisabled = false;
|
||||
int needVibrate = 0;
|
||||
String choosenSoundPath = null;
|
||||
int ledColor = 0xff00ff00;
|
||||
int ledColor = DEF_LED_COLOR;
|
||||
boolean inAppSounds;
|
||||
boolean inAppVibrate;
|
||||
//boolean inAppPreview = false;
|
||||
@@ -1048,7 +1048,7 @@ public class NotificationsController {
|
||||
}
|
||||
needVibrate = preferences.getInt("vibrate_group", 0);
|
||||
priority = preferences.getInt("priority_group", 1);
|
||||
ledColor = preferences.getInt("GroupLed", 0xff00ff00);
|
||||
ledColor = preferences.getInt("GroupLed", DEF_LED_COLOR);
|
||||
} else {
|
||||
if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) {
|
||||
choosenSoundPath = null;
|
||||
@@ -1057,10 +1057,10 @@ public class NotificationsController {
|
||||
}
|
||||
needVibrate = preferences.getInt("vibrate_messages", 0);
|
||||
priority = preferences.getInt("priority_messages", 1);
|
||||
ledColor = preferences.getInt("MessagesLed", 0xff00ff00);
|
||||
ledColor = preferences.getInt("MessagesLed", DEF_LED_COLOR);
|
||||
}
|
||||
if (preferences.contains("color_" + dialog_id)) {
|
||||
ledColor = preferences.getInt("color_" + dialog_id, 0);
|
||||
ledColor = preferences.getInt("color_" + dialog_id, 0/*0=off*/);
|
||||
}
|
||||
|
||||
if (priorityOverride != 3) {
|
||||
@@ -1090,7 +1090,7 @@ public class NotificationsController {
|
||||
needVibrate = 2;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1274,7 +1274,7 @@ public class NotificationsController {
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1321,7 +1321,7 @@ public class NotificationsController {
|
||||
TLRPC.User user = null;
|
||||
String name;
|
||||
if (dialog_id > 0) {
|
||||
user = MessagesController.getInstance().getUser((int)dialog_id);
|
||||
user = MrMailbox.getUser((int)dialog_id);
|
||||
if (user == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -1338,7 +1338,7 @@ public class NotificationsController {
|
||||
if (chat != null) {
|
||||
name = chat.title;
|
||||
} else {
|
||||
name = UserObject.getUserName(user);
|
||||
name = "ErrName"; // use MrContact.getName(), if really needed
|
||||
}
|
||||
/*if (chat != null) {
|
||||
if (chat.photo != null && chat.photo.photo_small != null && chat.photo.photo_small.volume_id != 0 && chat.photo.photo_small.local_id != 0) {
|
||||
@@ -1473,7 +1473,7 @@ public class NotificationsController {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
notificationsQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
@@ -1502,40 +1502,9 @@ public class NotificationsController {
|
||||
soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void updateServerNotificationsSettings(long dialog_id) {
|
||||
// the following command is needed to reflect the changes in the GUI
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.notificationsSettingsUpdated);
|
||||
|
||||
/*
|
||||
if ((int) dialog_id == 0) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE);
|
||||
TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings();
|
||||
req.settings = new TLRPC.TL_inputPeerNotifySettings();
|
||||
req.settings.sound = "default";
|
||||
int mute_type = preferences.getInt("notify2_" + dialog_id, 0);
|
||||
if (mute_type == 3) {
|
||||
req.settings.mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0);
|
||||
} else {
|
||||
req.settings.mute_until = mute_type != 2 ? 0 : Integer.MAX_VALUE;
|
||||
}
|
||||
req.settings.show_previews = preferences.getBoolean("preview_" + dialog_id, true);
|
||||
req.settings.silent = preferences.getBoolean("silent_" + dialog_id, false);
|
||||
req.peer = new TLRPC.TL_inputNotifyPeer();
|
||||
((TLRPC.TL_inputNotifyPeer) req.peer).peer = MessagesController.getInputPeer((int) dialog_id);
|
||||
ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
|
||||
@Override
|
||||
public void run(TLObject response, TLRPC.TL_error error) {
|
||||
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2013-2016 Nikolai Kudashov
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class NotificationsService extends Service {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
FileLog.e("messenger", "service started");
|
||||
ApplicationLoader.postInitApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
FileLog.e("messenger", "service destroyed");
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", MODE_PRIVATE);
|
||||
if (preferences.getBoolean("pushService", true)) {
|
||||
Intent intent = new Intent("com.b44t.start");
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,18 +26,17 @@ package com.b44t.messenger;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class ScreenReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
FileLog.e("messenger", "screen off");
|
||||
ConnectionsManager.getInstance().setAppPaused(true, true);
|
||||
MrMailbox.log_i("DeltaChat", "*** Screen off");
|
||||
ApplicationLoader.isScreenOn = false;
|
||||
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
|
||||
FileLog.e("messenger", "screen on");
|
||||
ConnectionsManager.getInstance().setAppPaused(false, true);
|
||||
MrMailbox.log_i("DeltaChat", "*** Screen on");
|
||||
ApplicationLoader.isScreenOn = true;
|
||||
}
|
||||
//NotificationCenter.getInstance().postNotificationName(NotificationCenter.screenStateChanged);
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.media.ThumbnailUtils;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.b44t.messenger.audioinfo.AudioInfo;
|
||||
|
||||
@@ -63,10 +62,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FilePreparingStarted);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileNewChunkAvailable);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FilePreparingFailed);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.httpFileDidFailedLoad);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.httpFileDidLoaded);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded);
|
||||
NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,112 +89,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
new File(messageObject.messageOwner.attachPath+".increation").delete();
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesSentOrRead);
|
||||
}
|
||||
else if (id == NotificationCenter.httpFileDidLoaded) {
|
||||
/*
|
||||
String path = (String) args[0];
|
||||
ArrayList<DelayedMessage> arr = delayedMessages.get(path);
|
||||
if (arr != null) {
|
||||
for (int a = 0; a < arr.size(); a++) {
|
||||
final DelayedMessage message = arr.get(a);
|
||||
if (message.type == 0) {
|
||||
String md5 = Utilities.MD5(message.httpLocation) + "." + ImageLoader.getHttpUrlExtension(message.httpLocation, "file");
|
||||
final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5);
|
||||
Utilities.globalQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final TLRPC.TL_photo photo = SendMessagesHelper.getInstance().generatePhotoSizes(cacheFile.toString(), null);
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (photo != null) {
|
||||
message.httpLocation = null;
|
||||
message.obj.messageOwner.media.photo = photo;
|
||||
message.obj.messageOwner.attachPath = cacheFile.toString();
|
||||
message.location = photo.sizes.get(photo.sizes.size() - 1).location;
|
||||
ArrayList<TLRPC.Message> messages = new ArrayList<>();
|
||||
messages.add(message.obj.messageOwner);
|
||||
//MessagesStorage.getInstance().putMessages(messages, false, true, false, 0);
|
||||
performSendDelayedMessage(message);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, message.obj);
|
||||
} else {
|
||||
FileLog.e("messenger", "can't load image " + message.httpLocation + " to file " + cacheFile.toString());
|
||||
//MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner);
|
||||
message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId());
|
||||
processSentMessage(message.obj.getId());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (message.type == 2) {
|
||||
String md5 = Utilities.MD5(message.httpLocation) + ".gif";
|
||||
final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5);
|
||||
Utilities.globalQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (message.documentLocation.thumb.location instanceof TLRPC.TL_fileLocationUnavailable) {
|
||||
try {
|
||||
Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90, true);
|
||||
if (bitmap != null) {
|
||||
message.documentLocation.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, message.sendEncryptedRequest != null);
|
||||
bitmap.recycle();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
message.documentLocation.thumb = null;
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
if (message.documentLocation.thumb == null) {
|
||||
message.documentLocation.thumb = new TLRPC.TL_photoSizeEmpty();
|
||||
message.documentLocation.thumb.type = "s";
|
||||
}
|
||||
}
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
message.httpLocation = null;
|
||||
message.obj.messageOwner.attachPath = cacheFile.toString();
|
||||
message.location = message.documentLocation.thumb.location;
|
||||
ArrayList<TLRPC.Message> messages = new ArrayList<>();
|
||||
messages.add(message.obj.messageOwner);
|
||||
//MessagesStorage.getInstance().putMessages(messages, false, true, false, 0);
|
||||
performSendDelayedMessage(message);
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, message.obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
delayedMessages.remove(path);
|
||||
}
|
||||
*/
|
||||
} else if (id == NotificationCenter.FileDidLoaded) {
|
||||
/*
|
||||
String path = (String) args[0];
|
||||
ArrayList<DelayedMessage> arr = delayedMessages.get(path);
|
||||
if (arr != null) {
|
||||
for (int a = 0; a < arr.size(); a++) {
|
||||
performSendDelayedMessage(arr.get(a));
|
||||
}
|
||||
delayedMessages.remove(path);
|
||||
}
|
||||
*/
|
||||
} else if (id == NotificationCenter.httpFileDidFailedLoad || id == NotificationCenter.FileDidFailedLoad) {
|
||||
/*
|
||||
String path = (String) args[0];
|
||||
ArrayList<DelayedMessage> arr = delayedMessages.get(path);
|
||||
if (arr != null) {
|
||||
for (DelayedMessage message : arr) {
|
||||
//MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner);
|
||||
message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR;
|
||||
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId());
|
||||
processSentMessage(message.obj.getId());
|
||||
}
|
||||
delayedMessages.remove(path);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public void sendSticker(TLRPC.Document document, long peer) {
|
||||
@@ -211,7 +100,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
|
||||
public void sendMessageContact(int contact_id, int dialog_id) {
|
||||
MrContact contact = MrMailbox.getContact(contact_id);
|
||||
String msg = contact.getName();
|
||||
String msg = contact.getAuthName();
|
||||
if(msg.isEmpty()) {
|
||||
msg = contact.getAddr();
|
||||
}
|
||||
@@ -343,10 +232,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
}
|
||||
}
|
||||
|
||||
public void checkUnsentMessages() {
|
||||
//MessagesStorage.getInstance().getUnsentMessages(1000);
|
||||
}
|
||||
|
||||
public TLRPC.TL_photo generatePhotoSizes(String path, Uri imageUri) {
|
||||
Bitmap bitmap = ImageLoader.loadBitmap(path, imageUri, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize(), true);
|
||||
if (bitmap == null && AndroidUtilities.getPhotoSize() != 800) {
|
||||
@@ -367,9 +252,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
if (sizes.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
UserConfig.saveConfig(false);
|
||||
UserConfig.saveConfig();
|
||||
TLRPC.TL_photo photo = new TLRPC.TL_photo();
|
||||
photo.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
photo.date = MrMailbox.getCurrentTime();
|
||||
photo.sizes = sizes;
|
||||
return photo;
|
||||
}
|
||||
@@ -438,16 +323,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
}
|
||||
|
||||
TLRPC.TL_document document = null;
|
||||
/*if (!isEncrypted)*/ {
|
||||
document = null;//(TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
|
||||
if (document == null && !path.equals(originalPath) /*&& !isEncrypted*/) {
|
||||
document = null;//(TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4);
|
||||
}
|
||||
}
|
||||
if (document == null) {
|
||||
{
|
||||
document = new TLRPC.TL_document();
|
||||
document.id = 0;
|
||||
document.date = ConnectionsManager.getInstance().getCurrentTime();
|
||||
document.date = MrMailbox.getCurrentTime();
|
||||
TLRPC.TL_documentAttributeFilename fileName = new TLRPC.TL_documentAttributeFilename();
|
||||
fileName.file_name = name;
|
||||
document.attributes.add(fileName);
|
||||
@@ -479,7 +358,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
bitmap.recycle();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
if (document.mime_type.equals("image/webp") && allowSticker) {
|
||||
@@ -491,7 +370,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
Utilities.loadWebpImage(null, buffer, buffer.limit(), bmOptions, true);
|
||||
file.close();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
if (bmOptions.outWidth != 0 && bmOptions.outHeight != 0 && bmOptions.outWidth <= 800 && bmOptions.outHeight <= 800) {
|
||||
TLRPC.TL_documentAttributeSticker attributeSticker = new TLRPC.TL_documentAttributeSticker();
|
||||
@@ -511,16 +390,16 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
}
|
||||
document.caption = caption;
|
||||
|
||||
final HashMap<String, String> params = new HashMap<>();
|
||||
/*final HashMap<String, String> params = new HashMap<>();
|
||||
if (originalPath != null) {
|
||||
params.put("originalPath", originalPath);
|
||||
}
|
||||
}*/
|
||||
final TLRPC.TL_document documentFinal = document;
|
||||
final String pathFinal = path;
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SendMessagesHelper.getInstance().sendMessageDocument(documentFinal, null, pathFinal, dialog_id, params);
|
||||
SendMessagesHelper.getInstance().sendMessageDocument(documentFinal, null, pathFinal, dialog_id, null);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@@ -541,44 +420,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id);
|
||||
}
|
||||
|
||||
/*public static void prepareSendingAudioDocuments(final ArrayList<MessageObject> messageObjects, final long dialog_id) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int size = messageObjects.size();
|
||||
for (int a = 0; a < size; a++) {
|
||||
final MessageObject messageObject = messageObjects.get(a);
|
||||
String originalPath = messageObject.messageOwner.attachPath;
|
||||
final File f = new File(originalPath);
|
||||
|
||||
if (originalPath != null) {
|
||||
originalPath += "audio" + f.length();
|
||||
}
|
||||
|
||||
TLRPC.TL_document document = null;
|
||||
if (!isEncrypted) {
|
||||
document = null;//(TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
|
||||
}
|
||||
if (document == null) {
|
||||
document = null;//(TLRPC.TL_document) messageObject.messageOwner.media.document;
|
||||
}
|
||||
|
||||
final HashMap<String, String> params = new HashMap<>();
|
||||
if (originalPath != null) {
|
||||
params.put("originalPath", originalPath);
|
||||
}
|
||||
final TLRPC.TL_document documentFinal = document;
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SendMessagesHelper.getInstance().sendMessageDocument(documentFinal, null, messageObject.messageOwner.attachPath, dialog_id, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}*/
|
||||
|
||||
public static void prepareSendingDocuments(final ArrayList<String> paths, final ArrayList<String> originalPaths, final ArrayList<Uri> uris, final String mime, final long dialog_id) {
|
||||
if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) {
|
||||
return;
|
||||
@@ -681,7 +522,6 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean isEncrypted = false;//(int) dialog_id == 0;
|
||||
|
||||
ArrayList<String> sendAsDocuments = null;
|
||||
ArrayList<String> sendAsDocumentsOriginal = null;
|
||||
@@ -700,7 +540,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
String originalPath = path;
|
||||
String tempPath = path;
|
||||
if (tempPath == null && uri != null) {
|
||||
tempPath = AndroidUtilities.getPath(uri);
|
||||
tempPath = AndroidUtilities.getPath(uri); // convert uri to path, this may fail
|
||||
originalPath = uri.toString();
|
||||
}
|
||||
|
||||
@@ -713,6 +553,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
}
|
||||
isDocument = true;
|
||||
} else if (tempPath == null && uri != null) {
|
||||
// if conversion from uri to path fails above, we have a look at the content of the file
|
||||
if (MediaController.isGif(uri)) {
|
||||
isDocument = true;
|
||||
originalPath = uri.toString();
|
||||
@@ -757,14 +598,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
photo.caption = captions.get(a);
|
||||
}
|
||||
final TLRPC.TL_photo photoFinal = photo;
|
||||
final HashMap<String, String> params = new HashMap<>();
|
||||
/*final HashMap<String, String> params = new HashMap<>();
|
||||
if (originalPath != null) {
|
||||
params.put("originalPath", originalPath);
|
||||
}
|
||||
}*/
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SendMessagesHelper.getInstance().sendMessagePhoto(photoFinal, null, dialog_id, params);
|
||||
SendMessagesHelper.getInstance().sendMessagePhoto(photoFinal, null, dialog_id, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -789,20 +630,20 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
|
||||
if (videoEditedInfo != null || videoPath.endsWith("mp4")) {
|
||||
String path = videoPath;
|
||||
String originalPath = videoPath;
|
||||
File temp = new File(originalPath);
|
||||
originalPath += temp.length() + "_" + temp.lastModified();
|
||||
//String originalPath = videoPath;
|
||||
File temp = new File(videoPath);
|
||||
/*originalPath += temp.length() + "_" + temp.lastModified();
|
||||
if (videoEditedInfo != null) {
|
||||
originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime;
|
||||
if (videoEditedInfo.resultWidth == videoEditedInfo.originalWidth) {
|
||||
originalPath += "_" + videoEditedInfo.resultWidth;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
TLRPC.TL_document document = null;
|
||||
{
|
||||
document = new TLRPC.TL_document();
|
||||
document.mime_type = "video/mp4";
|
||||
UserConfig.saveConfig(false);
|
||||
UserConfig.saveConfig();
|
||||
TLRPC.TL_documentAttributeVideo attributeVideo = new TLRPC.TL_documentAttributeVideo();
|
||||
document.attributes.add(attributeVideo);
|
||||
if (videoEditedInfo != null) {
|
||||
@@ -815,10 +656,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
attributeVideo.h = height;
|
||||
}
|
||||
document.size = (int) estimatedSize;
|
||||
String fileName = Integer.MIN_VALUE + "_" + UserConfig.lastLocalId + ".mp4";
|
||||
UserConfig.lastLocalId--;
|
||||
File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
|
||||
UserConfig.saveConfig(false);
|
||||
String fileName = temp.getName(); // we could also all videoEditInformation to the filename and re-use already encoded videos this way. however, for the moment, I have no time to check this out (bp)
|
||||
File cacheFile = AndroidUtilities.getFineFilename(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName);
|
||||
path = cacheFile.getAbsolutePath();
|
||||
} else {
|
||||
if (temp.exists()) {
|
||||
@@ -844,14 +683,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
}
|
||||
infoObtained = true;
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (mediaMetadataRetriever != null) {
|
||||
mediaMetadataRetriever.release();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
if (!infoObtained) {
|
||||
@@ -864,7 +703,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
mp.release();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -878,14 +717,14 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
|
||||
File tfile = new File(MrMailbox.getBlobdir(), vfile.getName()+"-preview.jpg");
|
||||
ImageLoader.scaleAndSaveImage(tfile, thumb, 90, 90, 55, false);
|
||||
|
||||
final HashMap<String, String> params = new HashMap<>();
|
||||
/*final HashMap<String, String> params = new HashMap<>();
|
||||
if (originalPath != null) {
|
||||
params.put("originalPath", originalPath);
|
||||
}
|
||||
}*/
|
||||
AndroidUtilities.runOnUIThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SendMessagesHelper.getInstance().sendMessageDocument(videoFinal, videoEditedInfo, finalPath, dialog_id, params);
|
||||
SendMessagesHelper.getInstance().sendMessageDocument(videoFinal, videoEditedInfo, finalPath, dialog_id, null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -103,23 +103,9 @@ public class TLRPC {
|
||||
public int duration;
|
||||
public String mime_type;
|
||||
public int size;
|
||||
public final int dc_id = 0;
|
||||
public int user_id;
|
||||
}
|
||||
|
||||
public static class InputFileLocation extends TLObject {
|
||||
public long id;
|
||||
public long access_hash;
|
||||
public long volume_id;
|
||||
public int local_id;
|
||||
}
|
||||
|
||||
public static class TL_inputDocumentFileLocation extends InputFileLocation {
|
||||
}
|
||||
|
||||
public static class TL_inputFileLocation extends InputFileLocation {
|
||||
}
|
||||
|
||||
public static class User extends TLObject {
|
||||
public int id;
|
||||
public String first_name;
|
||||
@@ -136,7 +122,6 @@ public class TLRPC {
|
||||
public int duration;
|
||||
public int size;
|
||||
public PhotoSize thumb;
|
||||
public int dc_id;
|
||||
public int w;
|
||||
public int h;
|
||||
public String mime_type;
|
||||
@@ -192,7 +177,6 @@ public class TLRPC {
|
||||
|
||||
public static class Chat extends TLObject {
|
||||
public int flags;
|
||||
public boolean creator;
|
||||
public int id;
|
||||
public String title;
|
||||
public final ChatPhoto photo = null;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Messenger Android Frontend
|
||||
* (C) 2017 Björn Petersen
|
||||
* Contact: r10s@b44t.com, http://b44t.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see http://www.gnu.org/licenses/ .
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
public class TimerReceiver extends BroadcastReceiver {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
// acquire for 3 seconds, this should wake up the threads,
|
||||
// _if_ there is more to do, the backend acquires an additional wakelock using MR_EVENT_WAKE_LOCK
|
||||
ApplicationLoader.wakeupWakeLock.acquire(3*1000); // do this first!
|
||||
MrMailbox.log_i("DeltaChat", "*** TimerReceiver.onReceive()");
|
||||
|
||||
// we assume, the IMAP thread is alive. I cannot imagine, the thread was killed with the App being running.
|
||||
// (if the whole App was killed, the IMAP thread is already started by MrMailbox.connect() if we're here)
|
||||
// (the thread itself will reconnect to the IMAP server as needed)
|
||||
// however, it seems as if the threads sleep longer than ususal, check this by calling heartbeat() manually
|
||||
MrMailbox.heartbeat();
|
||||
|
||||
// create the next alarm in about a minute
|
||||
scheduleNextAlarm();
|
||||
}
|
||||
|
||||
public static void scheduleNextAlarm()
|
||||
{
|
||||
try {
|
||||
Intent intent = new Intent(ApplicationLoader.applicationContext, TimerReceiver.class);
|
||||
PendingIntent alarmIntent = PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 0, intent, 0);
|
||||
|
||||
long triggerAtMillis = System.currentTimeMillis() + 60 * 1000;
|
||||
|
||||
AlarmManager alarmManager = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Activity.ALARM_SERVICE);
|
||||
if( Build.VERSION.SDK_INT >= 23 ) {
|
||||
// a simple AlarmManager.set() is no longer send in the new DOZE mode
|
||||
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, alarmIntent);
|
||||
}
|
||||
else {
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, alarmIntent);
|
||||
}
|
||||
}
|
||||
catch(Exception e) { Log.e("DeltaChat", "Cannot create alarm.", e); }
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import java.io.File;
|
||||
|
||||
public class UserConfig {
|
||||
|
||||
private static TLRPC.User currentUser;
|
||||
public static int lastLocalId = -210000;
|
||||
private final static Object sync = new Object();
|
||||
public static String passcodeHash = "";
|
||||
@@ -43,11 +42,7 @@ public class UserConfig {
|
||||
public static boolean isWaitingForPasscodeEnter;
|
||||
public static boolean useFingerprint = true;
|
||||
|
||||
public static void saveConfig(boolean withFile) {
|
||||
saveConfig(withFile, null);
|
||||
}
|
||||
|
||||
public static void saveConfig(boolean withFile, File oldFile) {
|
||||
public static void saveConfig() {
|
||||
synchronized (sync) {
|
||||
try {
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE);
|
||||
@@ -61,128 +56,15 @@ public class UserConfig {
|
||||
editor.putInt("lastPauseTime", lastPauseTime);
|
||||
editor.putBoolean("useFingerprint", useFingerprint);
|
||||
|
||||
/*
|
||||
if (currentUser != null) {
|
||||
if (withFile) {
|
||||
SerializedData data = new SerializedData();
|
||||
currentUser.serializeToStream(data);
|
||||
String userString = Base64.encodeToString(data.toByteArray(), Base64.DEFAULT);
|
||||
editor.putString("user", userString);
|
||||
data.cleanup();
|
||||
}
|
||||
} else {
|
||||
editor.remove("user");
|
||||
}
|
||||
*/
|
||||
|
||||
editor.apply();
|
||||
if (oldFile != null) {
|
||||
oldFile.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isClientActivated() {
|
||||
return true; // EDIT BY MR -- for "real" checking, call MrMailbox.MrMailboxIsConfigured()
|
||||
/* EDIT BY MR
|
||||
synchronized (sync) {
|
||||
return currentUser != null;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public static int getClientUserId() {
|
||||
return 1; // we are user #1 by definition
|
||||
/* EDIT BY MR
|
||||
synchronized (sync) {
|
||||
return currentUser != null ? currentUser.id : 0;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public static TLRPC.User getCurrentUser() {
|
||||
synchronized (sync) {
|
||||
if( currentUser==null ) {
|
||||
currentUser = MrContact.contactId2user(1);
|
||||
}
|
||||
return currentUser;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCurrentUser(TLRPC.User user) {
|
||||
synchronized (sync) {
|
||||
currentUser = MrContact.contactId2user(1); // EDIT BY MR - force the current user to be user #1, normally this function should not be called at all
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadConfig() {
|
||||
synchronized (sync) {
|
||||
/*
|
||||
final File configFile = new File(ApplicationLoader.getFilesDirFixed(), "user.dat");
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
SerializedData data = new SerializedData(configFile);
|
||||
int ver = data.readInt32(false);
|
||||
if (ver == 1) {
|
||||
int constructor = data.readInt32(false);
|
||||
currentUser = TLRPC.User.TLdeserialize(data, constructor, false);
|
||||
MessagesStorage.lastDateValue = data.readInt32(false);
|
||||
MessagesStorage.lastPtsValue = data.readInt32(false);
|
||||
MessagesStorage.lastSeqValue = data.readInt32(false);
|
||||
registeredForPush = data.readBool(false);
|
||||
pushString = data.readString(false);
|
||||
lastSendMessageId = data.readInt32(false);
|
||||
lastLocalId = data.readInt32(false);
|
||||
contactsHash = data.readString(false);
|
||||
data.readString(false);
|
||||
saveIncomingPhotos = data.readBool(false);
|
||||
//MessagesStorage.lastQtsValue = data.readInt32(false);
|
||||
//MessagesStorage.lastSecretVersion = data.readInt32(false);
|
||||
int val = data.readInt32(false);
|
||||
//if (val == 1) {
|
||||
// MessagesStorage.secretPBytes = data.readByteArray(false);
|
||||
//}
|
||||
//MessagesStorage.secretG = data.readInt32(false);
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
saveConfig(true, configFile);
|
||||
}
|
||||
});
|
||||
} else if (ver == 2) {
|
||||
int constructor = data.readInt32(false);
|
||||
currentUser = TLRPC.User.TLdeserialize(data, constructor, false);
|
||||
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE);
|
||||
registeredForPush = preferences.getBoolean("registeredForPush", false);
|
||||
pushString = preferences.getString("pushString2", "");
|
||||
lastSendMessageId = preferences.getInt("lastSendMessageId", -210000);
|
||||
lastLocalId = preferences.getInt("lastLocalId", -210000);
|
||||
contactsHash = preferences.getString("contactsHash", "");
|
||||
saveIncomingPhotos = preferences.getBoolean("saveIncomingPhotos", false);
|
||||
}
|
||||
if (lastLocalId > -210000) {
|
||||
lastLocalId = -210000;
|
||||
}
|
||||
if (lastSendMessageId > -210000) {
|
||||
lastSendMessageId = -210000;
|
||||
}
|
||||
data.cleanup();
|
||||
Utilities.stageQueue.postRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
saveConfig(true, configFile);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
} else
|
||||
*/
|
||||
{
|
||||
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE);
|
||||
lastLocalId = preferences.getInt("lastLocalId", -210000);
|
||||
passcodeHash = preferences.getString("passcodeHash1", "");
|
||||
@@ -192,26 +74,12 @@ public class UserConfig {
|
||||
lastPauseTime = preferences.getInt("lastPauseTime", 0);
|
||||
useFingerprint = preferences.getBoolean("useFingerprint", true);
|
||||
|
||||
/*
|
||||
String user = preferences.getString("user", null);
|
||||
if (user != null) {
|
||||
byte[] userBytes = Base64.decode(user, Base64.DEFAULT);
|
||||
if (userBytes != null) {
|
||||
SerializedData data = new SerializedData(userBytes);
|
||||
currentUser = TLRPC.User.TLdeserialize(data, data.readInt32(false), false);
|
||||
data.cleanup();
|
||||
}
|
||||
}
|
||||
*/
|
||||
setCurrentUser(null);
|
||||
|
||||
String passcodeSaltString = preferences.getString("passcodeSalt", "");
|
||||
if (passcodeSaltString.length() > 0) {
|
||||
passcodeSalt = Base64.decode(passcodeSaltString, Base64.DEFAULT);
|
||||
} else {
|
||||
passcodeSalt = new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,9 +96,9 @@ public class UserConfig {
|
||||
System.arraycopy(passcodeBytes, 0, bytes, 16, passcodeBytes.length);
|
||||
System.arraycopy(passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16);
|
||||
passcodeHash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length));
|
||||
saveConfig(false);
|
||||
saveConfig();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -244,7 +112,7 @@ public class UserConfig {
|
||||
String hash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length));
|
||||
return passcodeHash.equals(hash);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class Utilities {
|
||||
sUrandomIn.close();
|
||||
random.setSeed(buffer);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,15 +67,8 @@ public class Utilities {
|
||||
public native static void calcCDT(ByteBuffer hsvBuffer, int width, int height, ByteBuffer buffer);
|
||||
public native static boolean loadWebpImage(Bitmap bitmap, ByteBuffer buffer, int len, BitmapFactory.Options options, boolean unpin);
|
||||
public native static int convertVideoFrame(ByteBuffer src, ByteBuffer dest, int destFormat, int width, int height, int padding, int swap);
|
||||
//private native static void aesIgeEncryption(ByteBuffer buffer, byte[] key, byte[] iv, boolean encrypt, int offset, int length);
|
||||
public native static String readlink(String path);
|
||||
|
||||
/*
|
||||
public static void aesIgeEncryption(ByteBuffer buffer, byte[] key, byte[] iv, boolean encrypt, boolean changeIv, int offset, int length) {
|
||||
aesIgeEncryption(buffer, key, changeIv ? iv : iv.clone(), encrypt, offset, length);
|
||||
}
|
||||
*/
|
||||
|
||||
public static Integer parseInt(String value) {
|
||||
if (value == null) {
|
||||
return 0;
|
||||
@@ -88,7 +81,7 @@ public class Utilities {
|
||||
val = Integer.parseInt(num);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return val;
|
||||
}
|
||||
@@ -105,19 +98,11 @@ public class Utilities {
|
||||
val = Long.parseLong(num);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public static String parseIntToString(String value) {
|
||||
Matcher matcher = pattern.matcher(value);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return "";
|
||||
@@ -132,138 +117,17 @@ public class Utilities {
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hex) {
|
||||
if (hex == null) {
|
||||
return null;
|
||||
}
|
||||
int len = hex.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static boolean isGoodPrime(byte[] prime, int g) {
|
||||
if (!(g >= 2 && g <= 7)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prime.length != 256 || prime[0] >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BigInteger dhBI = new BigInteger(1, prime);
|
||||
|
||||
if (g == 2) { // p mod 8 = 7 for g = 2;
|
||||
BigInteger res = dhBI.mod(BigInteger.valueOf(8));
|
||||
if (res.intValue() != 7) {
|
||||
return false;
|
||||
}
|
||||
} else if (g == 3) { // p mod 3 = 2 for g = 3;
|
||||
BigInteger res = dhBI.mod(BigInteger.valueOf(3));
|
||||
if (res.intValue() != 2) {
|
||||
return false;
|
||||
}
|
||||
} else if (g == 5) { // p mod 5 = 1 or 4 for g = 5;
|
||||
BigInteger res = dhBI.mod(BigInteger.valueOf(5));
|
||||
int val = res.intValue();
|
||||
if (val != 1 && val != 4) {
|
||||
return false;
|
||||
}
|
||||
} else if (g == 6) { // p mod 24 = 19 or 23 for g = 6;
|
||||
BigInteger res = dhBI.mod(BigInteger.valueOf(24));
|
||||
int val = res.intValue();
|
||||
if (val != 19 && val != 23) {
|
||||
return false;
|
||||
}
|
||||
} else if (g == 7) { // p mod 7 = 3, 5 or 6 for g = 7.
|
||||
BigInteger res = dhBI.mod(BigInteger.valueOf(7));
|
||||
int val = res.intValue();
|
||||
if (val != 3 && val != 5 && val != 6) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String hex = bytesToHex(prime);
|
||||
if (hex.equals("C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BigInteger dhBI2 = dhBI.subtract(BigInteger.valueOf(1)).divide(BigInteger.valueOf(2));
|
||||
return !(!dhBI.isProbablePrime(30) || !dhBI2.isProbablePrime(30));
|
||||
}
|
||||
|
||||
public static boolean isGoodGaAndGb(BigInteger g_a, BigInteger p) {
|
||||
return !(g_a.compareTo(BigInteger.valueOf(1)) != 1 || g_a.compareTo(p.subtract(BigInteger.valueOf(1))) != -1);
|
||||
}
|
||||
|
||||
public static boolean arraysEquals(byte[] arr1, int offset1, byte[] arr2, int offset2) {
|
||||
if (arr1 == null || arr2 == null || offset1 < 0 || offset2 < 0 || arr1.length - offset1 != arr2.length - offset2 || arr1.length - offset1 < 0 || arr2.length - offset2 < 0) {
|
||||
return false;
|
||||
}
|
||||
boolean result = true;
|
||||
for (int a = offset1; a < arr1.length; a++) {
|
||||
if (arr1[a + offset1] != arr2[a + offset2]) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] computeSHA1(byte[] convertme, int offset, int len) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(convertme, offset, len);
|
||||
return md.digest();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
}
|
||||
return new byte[20];
|
||||
}
|
||||
|
||||
public static byte[] computeSHA1(ByteBuffer convertme, int offset, int len) {
|
||||
int oldp = convertme.position();
|
||||
int oldl = convertme.limit();
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
convertme.position(offset);
|
||||
convertme.limit(len);
|
||||
md.update(convertme);
|
||||
return md.digest();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
} finally {
|
||||
convertme.limit(oldl);
|
||||
convertme.position(oldp);
|
||||
}
|
||||
return new byte[20];
|
||||
}
|
||||
|
||||
public static byte[] computeSHA1(ByteBuffer convertme) {
|
||||
return computeSHA1(convertme, 0, convertme.limit());
|
||||
}
|
||||
|
||||
public static byte[] computeSHA1(byte[] convertme) {
|
||||
return computeSHA1(convertme, 0, convertme.length);
|
||||
}
|
||||
|
||||
public static byte[] computeSHA256(byte[] convertme, int offset, int len) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(convertme, offset, len);
|
||||
return md.digest();
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static long bytesToLong(byte[] bytes) {
|
||||
return ((long) bytes[7] << 56) + (((long) bytes[6] & 0xFF) << 48) + (((long) bytes[5] & 0xFF) << 40) + (((long) bytes[4] & 0xFF) << 32)
|
||||
+ (((long) bytes[3] & 0xFF) << 24) + (((long) bytes[2] & 0xFF) << 16) + (((long) bytes[1] & 0xFF) << 8) + ((long) bytes[0] & 0xFF);
|
||||
}
|
||||
|
||||
public static String MD5(String md5) {
|
||||
if (md5 == null) {
|
||||
return null;
|
||||
@@ -277,7 +141,7 @@ public class Utilities {
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,50 +23,15 @@
|
||||
|
||||
package com.b44t.messenger;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class VideoEditedInfo {
|
||||
public long startTime;
|
||||
public long endTime;
|
||||
public int rotationValue;
|
||||
public int originalWidth;
|
||||
public int originalHeight;
|
||||
public int originalBitrate;
|
||||
public int resultWidth;
|
||||
public int resultHeight;
|
||||
public int bitrate;
|
||||
public int resultBitrate;
|
||||
public String originalPath;
|
||||
|
||||
public String getString() {
|
||||
return String.format(Locale.US, "-1_%d_%d_%d_%d_%d_%d_%d_%d_%s", startTime, endTime, rotationValue, originalWidth, originalHeight, bitrate, resultWidth, resultHeight, originalPath);
|
||||
}
|
||||
|
||||
public boolean parseString(String string) {
|
||||
if (string.length() < 6) {
|
||||
return false;
|
||||
}
|
||||
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("messenger", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,13 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.util.Log;
|
||||
|
||||
public class WearReplyReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
ApplicationLoader.postInitApplication();
|
||||
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput == null) {
|
||||
return;
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.b44t.messenger.FileLog;
|
||||
import com.b44t.ui.LaunchActivity;
|
||||
|
||||
public class Browser {
|
||||
@@ -55,7 +54,7 @@ public class Browser {
|
||||
intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
||||
context.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
FileLog.e("messenger", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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));
|
||||
}
|
||||
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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() {}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.upstream.Allocator;
|
||||
import com.b44t.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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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();
|
||||
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,668 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.ExoPlayer.ExoPlayerComponent;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.PriorityHandlerThread;
|
||||
import com.b44t.messenger.exoplayer.util.TraceUtil;
|
||||
import com.b44t.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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 com.b44t.messenger.exoplayer.util.Assertions}
|
||||
* checks enabled.
|
||||
*/
|
||||
public static final boolean ASSERTIONS_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.b44t.messenger.exoplayer.util.TraceUtil}
|
||||
* trace enabled.
|
||||
*/
|
||||
public static final boolean TRACE_ENABLED = true;
|
||||
|
||||
private ExoPlayerLibraryInfo() {}
|
||||
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.SampleSource.SampleSourceReader;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmInitData;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmInitData.SchemeInitData;
|
||||
import com.b44t.messenger.exoplayer.extractor.ExtractorSampleSource;
|
||||
import com.b44t.messenger.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.MimeTypes;
|
||||
import com.b44t.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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);
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
/**
|
||||
* Tracks the progression of media time.
|
||||
*/
|
||||
public interface MediaClock {
|
||||
|
||||
/**
|
||||
* @return The current media position in microseconds.
|
||||
*/
|
||||
long getPositionUs();
|
||||
|
||||
}
|
||||
-486
@@ -1,486 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.b44t.messenger.exoplayer.audio.AudioCapabilities;
|
||||
import com.b44t.messenger.exoplayer.audio.AudioTrack;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmSessionManager;
|
||||
import com.b44t.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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;
|
||||
|
||||
}
|
||||
-1114
File diff suppressed because it is too large
Load Diff
@@ -1,501 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.MimeTypes;
|
||||
import com.b44t.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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-611
@@ -1,611 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmSessionManager;
|
||||
import com.b44t.messenger.exoplayer.util.MimeTypes;
|
||||
import com.b44t.messenger.exoplayer.util.TraceUtil;
|
||||
import com.b44t.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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,502 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.MimeTypes;
|
||||
import com.b44t.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];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.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;
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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 + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-267
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.b44t.messenger.exoplayer.SampleSource.SampleSourceReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base class for {@link TrackRenderer} implementations that render samples obtained from a
|
||||
* {@link SampleSource}.
|
||||
*/
|
||||
public abstract class SampleSourceTrackRenderer extends TrackRenderer {
|
||||
|
||||
private final SampleSourceReader[] sources;
|
||||
|
||||
private int[] handledSourceIndices;
|
||||
private int[] handledSourceTrackIndices;
|
||||
|
||||
private SampleSourceReader enabledSource;
|
||||
private int enabledSourceTrackIndex;
|
||||
|
||||
private long durationUs;
|
||||
|
||||
/**
|
||||
* @param sources One or more upstream sources from which the renderer can obtain samples.
|
||||
*/
|
||||
public SampleSourceTrackRenderer(SampleSource... sources) {
|
||||
this.sources = new SampleSourceReader[sources.length];
|
||||
for (int i = 0; i < sources.length; i++) {
|
||||
this.sources[i] = sources[i].register();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final boolean doPrepare(long positionUs) throws ExoPlaybackException {
|
||||
boolean allSourcesPrepared = true;
|
||||
for (int i = 0; i < sources.length; i++) {
|
||||
allSourcesPrepared &= sources[i].prepare(positionUs);
|
||||
}
|
||||
if (!allSourcesPrepared) {
|
||||
return false;
|
||||
}
|
||||
// The sources are all prepared.
|
||||
int totalSourceTrackCount = 0;
|
||||
for (int i = 0; i < sources.length; i++) {
|
||||
totalSourceTrackCount += sources[i].getTrackCount();
|
||||
}
|
||||
long durationUs = 0;
|
||||
int handledTrackCount = 0;
|
||||
int[] handledSourceIndices = new int[totalSourceTrackCount];
|
||||
int[] handledTrackIndices = new int[totalSourceTrackCount];
|
||||
int sourceCount = sources.length;
|
||||
for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) {
|
||||
SampleSourceReader source = sources[sourceIndex];
|
||||
int sourceTrackCount = source.getTrackCount();
|
||||
for (int trackIndex = 0; trackIndex < sourceTrackCount; trackIndex++) {
|
||||
MediaFormat format = source.getFormat(trackIndex);
|
||||
boolean handlesTrack;
|
||||
try {
|
||||
handlesTrack = handlesTrack(format);
|
||||
} catch (DecoderQueryException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
if (handlesTrack) {
|
||||
handledSourceIndices[handledTrackCount] = sourceIndex;
|
||||
handledTrackIndices[handledTrackCount] = trackIndex;
|
||||
handledTrackCount++;
|
||||
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 = format.durationUs;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.durationUs = durationUs;
|
||||
this.handledSourceIndices = Arrays.copyOf(handledSourceIndices, handledTrackCount);
|
||||
this.handledSourceTrackIndices = Arrays.copyOf(handledTrackIndices, handledTrackCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabled(int track, long positionUs, boolean joining)
|
||||
throws ExoPlaybackException {
|
||||
positionUs = shiftInputPosition(positionUs);
|
||||
enabledSource = sources[handledSourceIndices[track]];
|
||||
enabledSourceTrackIndex = handledSourceTrackIndices[track];
|
||||
enabledSource.enable(enabledSourceTrackIndex, positionUs);
|
||||
onDiscontinuity(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void seekTo(long positionUs) throws ExoPlaybackException {
|
||||
positionUs = shiftInputPosition(positionUs);
|
||||
enabledSource.seekToUs(positionUs);
|
||||
checkForDiscontinuity(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void doSomeWork(long positionUs, long elapsedRealtimeUs)
|
||||
throws ExoPlaybackException {
|
||||
positionUs = shiftInputPosition(positionUs);
|
||||
boolean sourceIsReady = enabledSource.continueBuffering(enabledSourceTrackIndex, positionUs);
|
||||
positionUs = checkForDiscontinuity(positionUs);
|
||||
doSomeWork(positionUs, elapsedRealtimeUs, sourceIsReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getBufferedPositionUs() {
|
||||
return enabledSource.getBufferedPositionUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getDurationUs() {
|
||||
return durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maybeThrowError() throws ExoPlaybackException {
|
||||
if (enabledSource != null) {
|
||||
maybeThrowError(enabledSource);
|
||||
} else {
|
||||
int sourceCount = sources.length;
|
||||
for (int i = 0; i < sourceCount; i++) {
|
||||
maybeThrowError(sources[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisabled() throws ExoPlaybackException {
|
||||
enabledSource.disable(enabledSourceTrackIndex);
|
||||
enabledSource = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReleased() throws ExoPlaybackException {
|
||||
int sourceCount = sources.length;
|
||||
for (int i = 0; i < sourceCount; i++) {
|
||||
sources[i].release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final int getTrackCount() {
|
||||
return handledSourceTrackIndices.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final MediaFormat getFormat(int track) {
|
||||
SampleSourceReader source = sources[handledSourceIndices[track]];
|
||||
return source.getFormat(handledSourceTrackIndices[track]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts positions passed to {@link #onEnabled(int, long, boolean)}, {@link #seekTo(long)} and
|
||||
* {@link #doSomeWork(long, long)}.
|
||||
* <p>
|
||||
* The default implementation does not modify the position. Except in very specific cases,
|
||||
* subclasses should not override this method.
|
||||
*
|
||||
* @param positionUs The position in microseconds.
|
||||
* @return The adjusted position in microseconds.
|
||||
*/
|
||||
protected long shiftInputPosition(long positionUs) {
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
// Methods to be called by subclasses.
|
||||
|
||||
/**
|
||||
* Reads from the enabled upstream source.
|
||||
*
|
||||
* @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 SampleSource#SAMPLE_READ},
|
||||
* {@link SampleSource#FORMAT_READ}, {@link SampleSource#NOTHING_READ} or
|
||||
* {@link SampleSource#END_OF_STREAM}.
|
||||
*/
|
||||
protected final int readSource(long positionUs, MediaFormatHolder formatHolder,
|
||||
SampleHolder sampleHolder) {
|
||||
return enabledSource.readData(enabledSourceTrackIndex, positionUs, formatHolder, sampleHolder);
|
||||
}
|
||||
|
||||
// Abstract methods.
|
||||
|
||||
/**
|
||||
* Returns whether this renderer is capable of handling the provided track.
|
||||
*
|
||||
* @param mediaFormat The format of the track.
|
||||
* @return True if the renderer can handle the track, false otherwise.
|
||||
* @throws DecoderQueryException Thrown if there was an error querying decoders.
|
||||
*/
|
||||
protected abstract boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException;
|
||||
|
||||
/**
|
||||
* Invoked when a discontinuity is encountered. Also invoked when the renderer is enabled, for
|
||||
* convenience.
|
||||
*
|
||||
* @param positionUs The playback position after the discontinuity, or the position at which
|
||||
* the renderer is being enabled.
|
||||
* @throws ExoPlaybackException If an error occurs handling the discontinuity.
|
||||
*/
|
||||
protected abstract void onDiscontinuity(long positionUs) throws ExoPlaybackException;
|
||||
|
||||
/**
|
||||
* Called by {@link #doSomeWork(long, long)}.
|
||||
*
|
||||
* @param positionUs The current media time in microseconds, measured at the start of the
|
||||
* current iteration of the rendering loop.
|
||||
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
||||
* measured at the start of the current iteration of the rendering loop.
|
||||
* @param sourceIsReady The result of the most recent call to
|
||||
* {@link SampleSourceReader#continueBuffering(int, long)}.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
* @throws ExoPlaybackException
|
||||
*/
|
||||
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
|
||||
throws ExoPlaybackException;
|
||||
|
||||
// Private methods.
|
||||
|
||||
private long checkForDiscontinuity(long positionUs) throws ExoPlaybackException {
|
||||
long discontinuityPositionUs = enabledSource.readDiscontinuity(enabledSourceTrackIndex);
|
||||
if (discontinuityPositionUs != SampleSource.NO_DISCONTINUITY) {
|
||||
onDiscontinuity(discontinuityPositionUs);
|
||||
return discontinuityPositionUs;
|
||||
}
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
private void maybeThrowError(SampleSourceReader source) throws ExoPlaybackException {
|
||||
try {
|
||||
source.maybeThrowError();
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.SampleSource.SampleSourceReader;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSource;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSpec;
|
||||
import com.b44t.messenger.exoplayer.upstream.Loader;
|
||||
import com.b44t.messenger.exoplayer.upstream.Loader.Loadable;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link SampleSource} that loads the data at a given {@link Uri} as a single sample.
|
||||
*/
|
||||
public final class SingleSampleSource implements SampleSource, SampleSourceReader, Loader.Callback,
|
||||
Loadable {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link SingleSampleSource} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked when an error occurs loading media data.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param e The cause of the failure.
|
||||
*/
|
||||
void onLoadError(int sourceId, IOException e);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The default minimum number of times to retry loading data prior to failing.
|
||||
*/
|
||||
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
|
||||
|
||||
/**
|
||||
* The initial size of the allocation used to hold the sample data.
|
||||
*/
|
||||
private static final int INITIAL_SAMPLE_SIZE = 1;
|
||||
|
||||
private static final int STATE_SEND_FORMAT = 0;
|
||||
private static final int STATE_SEND_SAMPLE = 1;
|
||||
private static final int STATE_END_OF_STREAM = 2;
|
||||
|
||||
private final Uri uri;
|
||||
private final DataSource dataSource;
|
||||
private final MediaFormat format;
|
||||
private final int minLoadableRetryCount;
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final int eventSourceId;
|
||||
|
||||
private int state;
|
||||
private byte[] sampleData;
|
||||
private int sampleSize;
|
||||
|
||||
private long pendingDiscontinuityPositionUs;
|
||||
private boolean loadingFinished;
|
||||
private Loader loader;
|
||||
private IOException currentLoadableException;
|
||||
private int currentLoadableExceptionCount;
|
||||
private long currentLoadableExceptionTimestamp;
|
||||
|
||||
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format) {
|
||||
this(uri, dataSource, format, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
|
||||
}
|
||||
|
||||
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format,
|
||||
int minLoadableRetryCount) {
|
||||
this(uri, dataSource, format, minLoadableRetryCount, null, null, 0);
|
||||
}
|
||||
|
||||
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format,
|
||||
int minLoadableRetryCount, Handler eventHandler, EventListener eventListener,
|
||||
int eventSourceId) {
|
||||
this.uri = uri;
|
||||
this.dataSource = dataSource;
|
||||
this.format = format;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.eventSourceId = eventSourceId;
|
||||
sampleData = new byte[INITIAL_SAMPLE_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleSourceReader register() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) {
|
||||
if (loader == null) {
|
||||
loader = new Loader("Loader:" + format.mimeType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getFormat(int track) {
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable(int track, long positionUs) {
|
||||
state = STATE_SEND_FORMAT;
|
||||
pendingDiscontinuityPositionUs = NO_DISCONTINUITY;
|
||||
clearCurrentLoadableException();
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueBuffering(int track, long positionUs) {
|
||||
maybeStartLoading();
|
||||
return loadingFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
|
||||
throw currentLoadableException;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity(int track) {
|
||||
long discontinuityPositionUs = pendingDiscontinuityPositionUs;
|
||||
pendingDiscontinuityPositionUs = NO_DISCONTINUITY;
|
||||
return discontinuityPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
|
||||
SampleHolder sampleHolder) {
|
||||
if (state == STATE_END_OF_STREAM) {
|
||||
return END_OF_STREAM;
|
||||
} else if (state == STATE_SEND_FORMAT) {
|
||||
formatHolder.format = format;
|
||||
state = STATE_SEND_SAMPLE;
|
||||
return FORMAT_READ;
|
||||
}
|
||||
|
||||
Assertions.checkState(state == STATE_SEND_SAMPLE);
|
||||
if (!loadingFinished) {
|
||||
return NOTHING_READ;
|
||||
} else {
|
||||
sampleHolder.timeUs = 0;
|
||||
sampleHolder.size = sampleSize;
|
||||
sampleHolder.flags = C.SAMPLE_FLAG_SYNC;
|
||||
sampleHolder.ensureSpaceForWrite(sampleHolder.size);
|
||||
sampleHolder.data.put(sampleData, 0, sampleSize);
|
||||
state = STATE_END_OF_STREAM;
|
||||
return SAMPLE_READ;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
if (state == STATE_END_OF_STREAM) {
|
||||
pendingDiscontinuityPositionUs = positionUs;
|
||||
state = STATE_SEND_SAMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
return loadingFinished ? TrackRenderer.END_OF_TRACK_US : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(int track) {
|
||||
state = STATE_END_OF_STREAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (loader != null) {
|
||||
loader.release();
|
||||
loader = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private void maybeStartLoading() {
|
||||
if (loadingFinished || state == STATE_END_OF_STREAM || loader.isLoading()) {
|
||||
return;
|
||||
}
|
||||
if (currentLoadableException != null) {
|
||||
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp;
|
||||
if (elapsedMillis < getRetryDelayMillis(currentLoadableExceptionCount)) {
|
||||
return;
|
||||
}
|
||||
currentLoadableException = null;
|
||||
}
|
||||
loader.startLoading(this, this);
|
||||
}
|
||||
|
||||
private void clearCurrentLoadableException() {
|
||||
currentLoadableException = null;
|
||||
currentLoadableExceptionCount = 0;
|
||||
}
|
||||
|
||||
private long getRetryDelayMillis(long errorCount) {
|
||||
return Math.min((errorCount - 1) * 1000, 5000);
|
||||
}
|
||||
|
||||
// Loader.Callback implementation.
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(Loadable loadable) {
|
||||
loadingFinished = true;
|
||||
clearCurrentLoadableException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(Loadable loadable) {
|
||||
// Never happens.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadError(Loadable loadable, IOException e) {
|
||||
currentLoadableException = e;
|
||||
currentLoadableExceptionCount++;
|
||||
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
|
||||
notifyLoadError(e);
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
||||
// Loadable implementation.
|
||||
|
||||
@Override
|
||||
public void cancelLoad() {
|
||||
// Never happens.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadCanceled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() throws IOException, InterruptedException {
|
||||
// We always load from the beginning, so reset the sampleSize to 0.
|
||||
sampleSize = 0;
|
||||
try {
|
||||
// Create and open the input.
|
||||
dataSource.open(new DataSpec(uri));
|
||||
// Load the sample data.
|
||||
int result = 0;
|
||||
while (result != C.RESULT_END_OF_INPUT) {
|
||||
sampleSize += result;
|
||||
if (sampleSize == sampleData.length) {
|
||||
sampleData = Arrays.copyOf(sampleData, sampleData.length * 2);
|
||||
}
|
||||
result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize);
|
||||
}
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLoadError(final IOException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onLoadError(eventSourceId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* A standalone {@link MediaClock}. The clock can be started, stopped and its time can be set and
|
||||
* retrieved. When started, this clock is based on {@link SystemClock#elapsedRealtime()}.
|
||||
*/
|
||||
/* package */ final class StandaloneMediaClock implements MediaClock {
|
||||
|
||||
private boolean started;
|
||||
|
||||
/**
|
||||
* The media time when the clock was last set or stopped.
|
||||
*/
|
||||
private long positionUs;
|
||||
|
||||
/**
|
||||
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #positionUs}
|
||||
* when the clock was last set or started.
|
||||
*/
|
||||
private long deltaUs;
|
||||
|
||||
/**
|
||||
* Starts the clock. Does nothing if the clock is already started.
|
||||
*/
|
||||
public void start() {
|
||||
if (!started) {
|
||||
started = true;
|
||||
deltaUs = elapsedRealtimeMinus(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the clock. Does nothing if the clock is already stopped.
|
||||
*/
|
||||
public void stop() {
|
||||
if (started) {
|
||||
positionUs = elapsedRealtimeMinus(deltaUs);
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeUs The position to set in microseconds.
|
||||
*/
|
||||
public void setPositionUs(long timeUs) {
|
||||
this.positionUs = timeUs;
|
||||
deltaUs = elapsedRealtimeMinus(timeUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPositionUs() {
|
||||
return started ? elapsedRealtimeMinus(deltaUs) : positionUs;
|
||||
}
|
||||
|
||||
private long elapsedRealtimeMinus(long toSubtractUs) {
|
||||
return SystemClock.elapsedRealtime() * 1000 - toSubtractUs;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.util.Clock;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
/**
|
||||
* A container to store a start and end time in microseconds.
|
||||
*/
|
||||
public interface TimeRange {
|
||||
|
||||
/**
|
||||
* Whether the range is static, meaning repeated calls to {@link #getCurrentBoundsMs(long[])}
|
||||
* or {@link #getCurrentBoundsUs(long[])} will return identical results.
|
||||
*
|
||||
* @return Whether the range is static.
|
||||
*/
|
||||
public boolean isStatic();
|
||||
|
||||
/**
|
||||
* Returns the start and end times (in milliseconds) of the TimeRange in the provided array,
|
||||
* or creates a new one.
|
||||
*
|
||||
* @param out An array to store the start and end times; can be null.
|
||||
* @return An array containing the start time (index 0) and end time (index 1) in milliseconds.
|
||||
*/
|
||||
public long[] getCurrentBoundsMs(long[] out);
|
||||
|
||||
/**
|
||||
* Returns the start and end times (in microseconds) of the TimeRange in the provided array,
|
||||
* or creates a new one.
|
||||
*
|
||||
* @param out An array to store the start and end times; can be null.
|
||||
* @return An array containing the start time (index 0) and end time (index 1) in microseconds.
|
||||
*/
|
||||
public long[] getCurrentBoundsUs(long[] out);
|
||||
|
||||
/**
|
||||
* A static {@link TimeRange}.
|
||||
*/
|
||||
public static final class StaticTimeRange implements TimeRange {
|
||||
|
||||
private final long startTimeUs;
|
||||
private final long endTimeUs;
|
||||
|
||||
/**
|
||||
* @param startTimeUs The beginning of the range.
|
||||
* @param endTimeUs The end of the range.
|
||||
*/
|
||||
public StaticTimeRange(long startTimeUs, long endTimeUs) {
|
||||
this.startTimeUs = startTimeUs;
|
||||
this.endTimeUs = endTimeUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsMs(long[] out) {
|
||||
out = getCurrentBoundsUs(out);
|
||||
out[0] /= 1000;
|
||||
out[1] /= 1000;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsUs(long[] out) {
|
||||
if (out == null || out.length < 2) {
|
||||
out = new long[2];
|
||||
}
|
||||
out[0] = startTimeUs;
|
||||
out[1] = endTimeUs;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) startTimeUs;
|
||||
result = 31 * result + (int) endTimeUs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
StaticTimeRange other = (StaticTimeRange) obj;
|
||||
return other.startTimeUs == startTimeUs
|
||||
&& other.endTimeUs == endTimeUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A dynamic {@link TimeRange}.
|
||||
*/
|
||||
public static final class DynamicTimeRange implements TimeRange {
|
||||
|
||||
private final long minStartTimeUs;
|
||||
private final long maxEndTimeUs;
|
||||
private final long elapsedRealtimeAtStartUs;
|
||||
private final long bufferDepthUs;
|
||||
private final Clock systemClock;
|
||||
|
||||
/**
|
||||
* @param minStartTimeUs A lower bound on the beginning of the range.
|
||||
* @param maxEndTimeUs An upper bound on the end of the range.
|
||||
* @param elapsedRealtimeAtStartUs The value of {@link SystemClock#elapsedRealtime()},
|
||||
* multiplied by 1000, corresponding to a media time of zero.
|
||||
* @param bufferDepthUs The buffer depth of the media, or -1.
|
||||
* @param systemClock A system clock.
|
||||
*/
|
||||
public DynamicTimeRange(long minStartTimeUs, long maxEndTimeUs, long elapsedRealtimeAtStartUs,
|
||||
long bufferDepthUs, Clock systemClock) {
|
||||
this.minStartTimeUs = minStartTimeUs;
|
||||
this.maxEndTimeUs = maxEndTimeUs;
|
||||
this.elapsedRealtimeAtStartUs = elapsedRealtimeAtStartUs;
|
||||
this.bufferDepthUs = bufferDepthUs;
|
||||
this.systemClock = systemClock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsMs(long[] out) {
|
||||
out = getCurrentBoundsUs(out);
|
||||
out[0] /= 1000;
|
||||
out[1] /= 1000;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] getCurrentBoundsUs(long[] out) {
|
||||
if (out == null || out.length < 2) {
|
||||
out = new long[2];
|
||||
}
|
||||
// Don't allow the end time to be greater than the total elapsed time.
|
||||
long currentEndTimeUs = Math.min(maxEndTimeUs,
|
||||
(systemClock.elapsedRealtime() * 1000) - elapsedRealtimeAtStartUs);
|
||||
long currentStartTimeUs = minStartTimeUs;
|
||||
if (bufferDepthUs != -1) {
|
||||
// Don't allow the start time to be less than the current end time minus the buffer depth.
|
||||
currentStartTimeUs = Math.max(currentStartTimeUs,
|
||||
currentEndTimeUs - bufferDepthUs);
|
||||
}
|
||||
out[0] = currentStartTimeUs;
|
||||
out[1] = currentEndTimeUs;
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + (int) minStartTimeUs;
|
||||
result = 31 * result + (int) maxEndTimeUs;
|
||||
result = 31 * result + (int) elapsedRealtimeAtStartUs;
|
||||
result = 31 * result + (int) bufferDepthUs;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DynamicTimeRange other = (DynamicTimeRange) obj;
|
||||
return other.minStartTimeUs == minStartTimeUs
|
||||
&& other.maxEndTimeUs == maxEndTimeUs
|
||||
&& other.elapsedRealtimeAtStartUs == elapsedRealtimeAtStartUs
|
||||
&& other.bufferDepthUs == bufferDepthUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import com.b44t.messenger.exoplayer.ExoPlayer.ExoPlayerComponent;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
|
||||
/**
|
||||
* Renders a single component of media.
|
||||
*
|
||||
* <p>Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The player
|
||||
* will transition its renderers through various states as the overall playback state changes. The
|
||||
* valid state transitions are shown below, annotated with the methods that are invoked during each
|
||||
* transition.
|
||||
* <p align="center"><img src="../../../../../images/trackrenderer_state.png"
|
||||
* alt="TrackRenderer state transitions"
|
||||
* border="0"/></p>
|
||||
*/
|
||||
public abstract class TrackRenderer implements ExoPlayerComponent {
|
||||
|
||||
/**
|
||||
* Represents an unknown time or duration. Equal to {@link C#UNKNOWN_TIME_US}.
|
||||
*/
|
||||
public static final long UNKNOWN_TIME_US = C.UNKNOWN_TIME_US; // -1
|
||||
/**
|
||||
* Represents a time or duration that should match the duration of the longest track whose
|
||||
* duration is known. Equal to {@link C#MATCH_LONGEST_US}.
|
||||
*/
|
||||
public static final long MATCH_LONGEST_US = C.MATCH_LONGEST_US; // -2
|
||||
/**
|
||||
* Represents the time of the end of the track.
|
||||
*/
|
||||
public static final long END_OF_TRACK_US = -3;
|
||||
|
||||
/**
|
||||
* The renderer has been released and should not be used.
|
||||
*/
|
||||
protected static final int STATE_RELEASED = -1;
|
||||
/**
|
||||
* The renderer has not yet been prepared.
|
||||
*/
|
||||
protected static final int STATE_UNPREPARED = 0;
|
||||
/**
|
||||
* The renderer has completed necessary preparation. Preparation may include, for example,
|
||||
* reading the header of a media file to determine the track format and duration.
|
||||
* <p>
|
||||
* The renderer should not hold scarce or expensive system resources (e.g. media decoders) and
|
||||
* should not be actively buffering media data when in this state.
|
||||
*/
|
||||
protected static final int STATE_PREPARED = 1;
|
||||
/**
|
||||
* The renderer is enabled. It should either be ready to be started, or be actively working
|
||||
* towards this state (e.g. a renderer in this state will typically hold any resources that it
|
||||
* requires, such as media decoders, and will have buffered or be buffering any media data that
|
||||
* is required to start playback).
|
||||
*/
|
||||
protected static final int STATE_ENABLED = 2;
|
||||
/**
|
||||
* The renderer is started. Calls to {@link #doSomeWork(long, long)} should cause the media to be
|
||||
* rendered.
|
||||
*/
|
||||
protected static final int STATE_STARTED = 3;
|
||||
|
||||
private int state;
|
||||
|
||||
/**
|
||||
* If the renderer advances its own playback position then this method returns a corresponding
|
||||
* {@link MediaClock}. If provided, the player will use the returned {@link MediaClock} as its
|
||||
* source of time during playback. A player may have at most one renderer that returns a
|
||||
* {@link MediaClock} from this method.
|
||||
*
|
||||
* @return The {@link MediaClock} tracking the playback position of the renderer, or null.
|
||||
*/
|
||||
protected MediaClock getMediaClock() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the renderer.
|
||||
*
|
||||
* @return The current state (one of the STATE_* constants).
|
||||
*/
|
||||
protected final int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it
|
||||
* more than once in order to transition the renderer into the prepared state.
|
||||
*
|
||||
* @param positionUs The player's current playback position.
|
||||
* @return The current state (one of the STATE_* constants), for convenience.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final int prepare(long positionUs) throws ExoPlaybackException {
|
||||
Assertions.checkState(state == STATE_UNPREPARED);
|
||||
state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked to make progress when the renderer is in the {@link #STATE_UNPREPARED} state. This
|
||||
* method will be called repeatedly until {@code true} is returned.
|
||||
* <p>
|
||||
* This method should return quickly, and should not block if the renderer is currently unable to
|
||||
* make any useful progress.
|
||||
*
|
||||
* @param positionUs The player's current playback position.
|
||||
* @return True if the renderer is now prepared. False otherwise.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;
|
||||
|
||||
/**
|
||||
* Returns the number of tracks exposed by the renderer.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @return The number of tracks.
|
||||
*/
|
||||
protected abstract int getTrackCount();
|
||||
|
||||
/**
|
||||
* Returns the format of the specified track.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @param track The track index.
|
||||
* @return The format of the specified track.
|
||||
*/
|
||||
protected abstract MediaFormat getFormat(int track);
|
||||
|
||||
/**
|
||||
* Enable the renderer for a specified track.
|
||||
*
|
||||
* @param track The track for which the renderer is being enabled.
|
||||
* @param positionUs The player's current position.
|
||||
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void enable(int track, long positionUs, boolean joining)
|
||||
throws ExoPlaybackException {
|
||||
Assertions.checkState(state == STATE_PREPARED);
|
||||
state = STATE_ENABLED;
|
||||
onEnabled(track, positionUs, joining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the renderer is enabled.
|
||||
* <p>
|
||||
* The default implementation is a no-op.
|
||||
*
|
||||
* @param track The track for which the renderer is being enabled.
|
||||
* @param positionUs The player's current position.
|
||||
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected void onEnabled(int track, long positionUs, boolean joining)
|
||||
throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the
|
||||
* track to be rendered.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void start() throws ExoPlaybackException {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
state = STATE_STARTED;
|
||||
onStarted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the renderer is started.
|
||||
* <p>
|
||||
* The default implementation is a no-op.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected void onStarted() throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the renderer.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void stop() throws ExoPlaybackException {
|
||||
Assertions.checkState(state == STATE_STARTED);
|
||||
state = STATE_ENABLED;
|
||||
onStopped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the renderer is stopped.
|
||||
* <p>
|
||||
* The default implementation is a no-op.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected void onStopped() throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the renderer.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void disable() throws ExoPlaybackException {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
state = STATE_PREPARED;
|
||||
onDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the renderer is disabled.
|
||||
* <p>
|
||||
* The default implementation is a no-op.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected void onDisabled() throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the renderer.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
/* package */ final void release() throws ExoPlaybackException {
|
||||
Assertions.checkState(state != STATE_ENABLED
|
||||
&& state != STATE_STARTED
|
||||
&& state != STATE_RELEASED);
|
||||
state = STATE_RELEASED;
|
||||
onReleased();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the renderer is released.
|
||||
* <p>
|
||||
* The default implementation is a no-op.
|
||||
*
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected void onReleased() throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the renderer is ready for the {@link ExoPlayer} instance to transition to
|
||||
* {@link ExoPlayer#STATE_ENDED}. The player will make this transition as soon as {@code true} is
|
||||
* returned by all of its {@link TrackRenderer}s.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @return Whether the renderer is ready for the player to transition to the ended state.
|
||||
*/
|
||||
protected abstract boolean isEnded();
|
||||
|
||||
/**
|
||||
* Whether the renderer is able to immediately render media from the current position.
|
||||
* <p>
|
||||
* If the renderer is in the {@link #STATE_STARTED} state then returning true indicates that the
|
||||
* renderer has everything that it needs to continue playback. Returning false indicates that
|
||||
* the player should pause until the renderer is ready.
|
||||
* <p>
|
||||
* If the renderer is in the {@link #STATE_ENABLED} state then returning true indicates that the
|
||||
* renderer is ready for playback to be started. Returning false indicates that it is not.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @return True if the renderer is ready to render media. False otherwise.
|
||||
*/
|
||||
protected abstract boolean isReady();
|
||||
|
||||
/**
|
||||
* Invoked to make progress when the renderer is in the {@link #STATE_ENABLED} or
|
||||
* {@link #STATE_STARTED} states.
|
||||
* <p>
|
||||
* If the renderer's state is {@link #STATE_STARTED}, then repeated calls to this method should
|
||||
* cause the media track to be rendered. If the state is {@link #STATE_ENABLED}, then repeated
|
||||
* calls should make progress towards getting the renderer into a position where it is ready to
|
||||
* render the track.
|
||||
* <p>
|
||||
* This method should return quickly, and should not block if the renderer is currently unable to
|
||||
* make any useful progress.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @param positionUs The current media time in microseconds, measured at the start of the
|
||||
* current iteration of the rendering loop.
|
||||
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
|
||||
* measured at the start of the current iteration of the rendering loop.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs)
|
||||
throws ExoPlaybackException;
|
||||
|
||||
/**
|
||||
* Throws an error that's preventing the renderer from making progress or buffering more data at
|
||||
* this point in time.
|
||||
*
|
||||
* @throws ExoPlaybackException An error that's preventing the renderer from making progress or
|
||||
* buffering more data.
|
||||
*/
|
||||
protected abstract void maybeThrowError() throws ExoPlaybackException;
|
||||
|
||||
/**
|
||||
* Returns the duration of the media being rendered.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @return The duration of the track in microseconds, or {@link #MATCH_LONGEST_US} if
|
||||
* the track's duration should match that of the longest track whose duration is known, or
|
||||
* or {@link #UNKNOWN_TIME_US} if the duration is not known.
|
||||
*/
|
||||
protected abstract long getDurationUs();
|
||||
|
||||
/**
|
||||
* Returns an estimate of the absolute position in microseconds up to which data is buffered.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}
|
||||
*
|
||||
* @return An estimate of the absolute position in microseconds up to which data is buffered,
|
||||
* or {@link #END_OF_TRACK_US} if the track is fully buffered, or {@link #UNKNOWN_TIME_US} if
|
||||
* no estimate is available.
|
||||
*/
|
||||
protected abstract long getBufferedPositionUs();
|
||||
|
||||
/**
|
||||
* Seeks to a specified time in the track.
|
||||
* <p>
|
||||
* This method may be called when the renderer is in the following states:
|
||||
* {@link #STATE_ENABLED}
|
||||
*
|
||||
* @param positionUs The desired playback position in microseconds.
|
||||
* @throws ExoPlaybackException If an error occurs.
|
||||
*/
|
||||
protected abstract void seekTo(long positionUs) throws ExoPlaybackException;
|
||||
|
||||
@Override
|
||||
public void handleMessage(int what, Object object) throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
}
|
||||
-305
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Choreographer.FrameCallback;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* Makes a best effort to adjust frame release timestamps for a smoother visual result.
|
||||
*/
|
||||
@TargetApi(16)
|
||||
public final class VideoFrameReleaseTimeHelper {
|
||||
|
||||
private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
|
||||
private static final long MAX_ALLOWED_DRIFT_NS = 20000000;
|
||||
|
||||
private static final long VSYNC_OFFSET_PERCENTAGE = 80;
|
||||
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
|
||||
|
||||
private final VSyncSampler vsyncSampler;
|
||||
private final boolean useDefaultDisplayVsync;
|
||||
private final long vsyncDurationNs;
|
||||
private final long vsyncOffsetNs;
|
||||
|
||||
private long lastFramePresentationTimeUs;
|
||||
private long adjustedLastFrameTimeNs;
|
||||
private long pendingAdjustedFrameTimeNs;
|
||||
|
||||
private boolean haveSync;
|
||||
private long syncUnadjustedReleaseTimeNs;
|
||||
private long syncFramePresentationTimeNs;
|
||||
private long frameCount;
|
||||
|
||||
/**
|
||||
* Constructs an instance that smoothes frame release but does not snap release to the default
|
||||
* display's vsync signal.
|
||||
*/
|
||||
public VideoFrameReleaseTimeHelper() {
|
||||
this(-1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance that smoothes frame release and snaps release to the default display's
|
||||
* vsync signal.
|
||||
*
|
||||
* @param context A context from which information about the default display can be retrieved.
|
||||
*/
|
||||
public VideoFrameReleaseTimeHelper(Context context) {
|
||||
this(getDefaultDisplayRefreshRate(context), true);
|
||||
}
|
||||
|
||||
private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate,
|
||||
boolean useDefaultDisplayVsync) {
|
||||
this.useDefaultDisplayVsync = useDefaultDisplayVsync;
|
||||
if (useDefaultDisplayVsync) {
|
||||
vsyncSampler = VSyncSampler.getInstance();
|
||||
vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate);
|
||||
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
|
||||
} else {
|
||||
vsyncSampler = null;
|
||||
vsyncDurationNs = -1;
|
||||
vsyncOffsetNs = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the helper.
|
||||
*/
|
||||
public void enable() {
|
||||
haveSync = false;
|
||||
if (useDefaultDisplayVsync) {
|
||||
vsyncSampler.addObserver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the helper.
|
||||
*/
|
||||
public void disable() {
|
||||
if (useDefaultDisplayVsync) {
|
||||
vsyncSampler.removeObserver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to make a fine-grained adjustment to a frame release time.
|
||||
*
|
||||
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
|
||||
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
|
||||
* the same time base as {@link System#nanoTime()}.
|
||||
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
|
||||
* {@link System#nanoTime()}.
|
||||
*/
|
||||
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) {
|
||||
long framePresentationTimeNs = framePresentationTimeUs * 1000;
|
||||
|
||||
// Until we know better, the adjustment will be a no-op.
|
||||
long adjustedFrameTimeNs = framePresentationTimeNs;
|
||||
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
|
||||
|
||||
if (haveSync) {
|
||||
// See if we've advanced to the next frame.
|
||||
if (framePresentationTimeUs != lastFramePresentationTimeUs) {
|
||||
frameCount++;
|
||||
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
|
||||
}
|
||||
if (frameCount >= MIN_FRAMES_FOR_ADJUSTMENT) {
|
||||
// We're synced and have waited the required number of frames to apply an adjustment.
|
||||
// Calculate the average frame time across all the frames we've seen since the last sync.
|
||||
// This will typically give us a frame rate at a finer granularity than the frame times
|
||||
// themselves (which often only have millisecond granularity).
|
||||
long averageFrameDurationNs = (framePresentationTimeNs - syncFramePresentationTimeNs)
|
||||
/ frameCount;
|
||||
// Project the adjusted frame time forward using the average.
|
||||
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs;
|
||||
|
||||
if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
|
||||
haveSync = false;
|
||||
} else {
|
||||
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
|
||||
adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs
|
||||
- syncFramePresentationTimeNs;
|
||||
}
|
||||
} else {
|
||||
// We're synced but haven't waited the required number of frames to apply an adjustment.
|
||||
// Check drift anyway.
|
||||
if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
|
||||
haveSync = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we need to sync, do so now.
|
||||
if (!haveSync) {
|
||||
syncFramePresentationTimeNs = framePresentationTimeNs;
|
||||
syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
|
||||
frameCount = 0;
|
||||
haveSync = true;
|
||||
onSynced();
|
||||
}
|
||||
|
||||
lastFramePresentationTimeUs = framePresentationTimeUs;
|
||||
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
|
||||
|
||||
if (vsyncSampler == null || vsyncSampler.sampledVsyncTimeNs == 0) {
|
||||
return adjustedReleaseTimeNs;
|
||||
}
|
||||
|
||||
// Find the timestamp of the closest vsync. This is the vsync that we're targeting.
|
||||
long snappedTimeNs = closestVsync(adjustedReleaseTimeNs,
|
||||
vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs);
|
||||
// Apply an offset so that we release before the target vsync, but after the previous one.
|
||||
return snappedTimeNs - vsyncOffsetNs;
|
||||
}
|
||||
|
||||
protected void onSynced() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
|
||||
long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
|
||||
long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
|
||||
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS;
|
||||
}
|
||||
|
||||
private static long closestVsync(long releaseTime, long sampledVsyncTime, long vsyncDuration) {
|
||||
long vsyncCount = (releaseTime - sampledVsyncTime) / vsyncDuration;
|
||||
long snappedTimeNs = sampledVsyncTime + (vsyncDuration * vsyncCount);
|
||||
long snappedBeforeNs;
|
||||
long snappedAfterNs;
|
||||
if (releaseTime <= snappedTimeNs) {
|
||||
snappedBeforeNs = snappedTimeNs - vsyncDuration;
|
||||
snappedAfterNs = snappedTimeNs;
|
||||
} else {
|
||||
snappedBeforeNs = snappedTimeNs;
|
||||
snappedAfterNs = snappedTimeNs + vsyncDuration;
|
||||
}
|
||||
long snappedAfterDiff = snappedAfterNs - releaseTime;
|
||||
long snappedBeforeDiff = releaseTime - snappedBeforeNs;
|
||||
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
|
||||
}
|
||||
|
||||
private static float getDefaultDisplayRefreshRate(Context context) {
|
||||
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
return manager.getDefaultDisplay().getRefreshRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of a single {@link Choreographer} to be shared among all
|
||||
* {@link VideoFrameReleaseTimeHelper} instances. This is done to avoid a bug fixed in platform
|
||||
* API version 23 that causes resource leakage. See [Internal: b/12455729].
|
||||
*/
|
||||
private static final class VSyncSampler implements FrameCallback, Handler.Callback {
|
||||
|
||||
public volatile long sampledVsyncTimeNs;
|
||||
|
||||
private static final int CREATE_CHOREOGRAPHER = 0;
|
||||
private static final int MSG_ADD_OBSERVER = 1;
|
||||
private static final int MSG_REMOVE_OBSERVER = 2;
|
||||
|
||||
private static final VSyncSampler INSTANCE = new VSyncSampler();
|
||||
|
||||
private final Handler handler;
|
||||
private final HandlerThread choreographerOwnerThread;
|
||||
private Choreographer choreographer;
|
||||
private int observerCount;
|
||||
|
||||
public static VSyncSampler getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private VSyncSampler() {
|
||||
choreographerOwnerThread = new HandlerThread("ChoreographerOwner:Handler");
|
||||
choreographerOwnerThread.start();
|
||||
handler = new Handler(choreographerOwnerThread.getLooper(), this);
|
||||
handler.sendEmptyMessage(CREATE_CHOREOGRAPHER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the {@link VSyncSampler} that there is a new {@link VideoFrameReleaseTimeHelper}
|
||||
* instance observing the currentSampledVsyncTimeNs value. As a consequence, if necessary, it
|
||||
* will register itself as a {@code doFrame} callback listener.
|
||||
*/
|
||||
public void addObserver() {
|
||||
handler.sendEmptyMessage(MSG_ADD_OBSERVER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart of {@code addNewObservingHelper}. This method should be called once the observer
|
||||
* no longer needs to read {@link #sampledVsyncTimeNs}
|
||||
*/
|
||||
public void removeObserver() {
|
||||
handler.sendEmptyMessage(MSG_REMOVE_OBSERVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long vsyncTimeNs) {
|
||||
sampledVsyncTimeNs = vsyncTimeNs;
|
||||
choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
case CREATE_CHOREOGRAPHER: {
|
||||
createChoreographerInstanceInternal();
|
||||
return true;
|
||||
}
|
||||
case MSG_ADD_OBSERVER: {
|
||||
addObserverInternal();
|
||||
return true;
|
||||
}
|
||||
case MSG_REMOVE_OBSERVER: {
|
||||
removeObserverInternal();
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void createChoreographerInstanceInternal() {
|
||||
choreographer = Choreographer.getInstance();
|
||||
}
|
||||
|
||||
private void addObserverInternal() {
|
||||
observerCount++;
|
||||
if (observerCount == 1) {
|
||||
choreographer.postFrameCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeObserverInternal() {
|
||||
observerCount--;
|
||||
if (observerCount == 0) {
|
||||
choreographer.removeFrameCallback(this);
|
||||
sampledVsyncTimeNs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.audio;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents the set of audio formats a device is capable of playing back.
|
||||
*/
|
||||
@TargetApi(21)
|
||||
public final class AudioCapabilities {
|
||||
|
||||
/**
|
||||
* The minimum audio capabilities supported by all devices.
|
||||
*/
|
||||
public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES =
|
||||
new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, 2);
|
||||
|
||||
/**
|
||||
* Gets the current audio capabilities. Note that to be notified when audio capabilities change,
|
||||
* you can create an instance of {@link AudioCapabilitiesReceiver} and register a listener.
|
||||
*
|
||||
* @param context Context for receiving the initial broadcast.
|
||||
* @return Current audio capabilities for the device.
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public static AudioCapabilities getCapabilities(Context context) {
|
||||
return getCapabilities(
|
||||
context.registerReceiver(null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)));
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
/* package */ static AudioCapabilities getCapabilities(Intent intent) {
|
||||
if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) {
|
||||
return DEFAULT_AUDIO_CAPABILITIES;
|
||||
}
|
||||
return new AudioCapabilities(intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS),
|
||||
intent.getIntExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, 0));
|
||||
}
|
||||
|
||||
private final int[] supportedEncodings;
|
||||
private final int maxChannelCount;
|
||||
|
||||
/**
|
||||
* Constructs new audio capabilities based on a set of supported encodings and a maximum channel
|
||||
* count.
|
||||
*
|
||||
* @param supportedEncodings Supported audio encodings from {@link android.media.AudioFormat}'s
|
||||
* {@code ENCODING_*} constants.
|
||||
* @param maxChannelCount The maximum number of audio channels that can be played simultaneously.
|
||||
*/
|
||||
/* package */ AudioCapabilities(int[] supportedEncodings, int maxChannelCount) {
|
||||
if (supportedEncodings != null) {
|
||||
this.supportedEncodings = Arrays.copyOf(supportedEncodings, supportedEncodings.length);
|
||||
Arrays.sort(this.supportedEncodings);
|
||||
} else {
|
||||
this.supportedEncodings = new int[0];
|
||||
}
|
||||
this.maxChannelCount = maxChannelCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this device supports playback of the specified audio {@code encoding}.
|
||||
*
|
||||
* @param encoding One of {@link android.media.AudioFormat}'s {@code ENCODING_*} constants.
|
||||
* @return Whether this device supports playback the specified audio {@code encoding}.
|
||||
*/
|
||||
public boolean supportsEncoding(int encoding) {
|
||||
return Arrays.binarySearch(supportedEncodings, encoding) >= 0;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of channels the device can play at the same time. */
|
||||
public int getMaxChannelCount() {
|
||||
return maxChannelCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AudioCapabilities)) {
|
||||
return false;
|
||||
}
|
||||
AudioCapabilities audioCapabilities = (AudioCapabilities) other;
|
||||
return Arrays.equals(supportedEncodings, audioCapabilities.supportedEncodings)
|
||||
&& maxChannelCount == audioCapabilities.maxChannelCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return maxChannelCount + 31 * Arrays.hashCode(supportedEncodings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AudioCapabilities[maxChannelCount=" + maxChannelCount
|
||||
+ ", supportedEncodings=" + Arrays.toString(supportedEncodings) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.audio;
|
||||
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.Util;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
|
||||
/**
|
||||
* Notifies a listener when the audio playback capabilities change. Call {@link #register} to start
|
||||
* (or resume) receiving notifications, and {@link #unregister} to stop.
|
||||
*/
|
||||
public final class AudioCapabilitiesReceiver {
|
||||
|
||||
/**
|
||||
* Listener notified when audio capabilities change.
|
||||
*/
|
||||
public interface Listener {
|
||||
|
||||
/**
|
||||
* Called when the audio capabilities change.
|
||||
*
|
||||
* @param audioCapabilities Current audio capabilities for the device.
|
||||
*/
|
||||
void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities);
|
||||
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final Listener listener;
|
||||
private final BroadcastReceiver receiver;
|
||||
|
||||
/* package */ AudioCapabilities audioCapabilities;
|
||||
|
||||
/**
|
||||
* Constructs a new audio capabilities receiver.
|
||||
*
|
||||
* @param context Context for registering to receive broadcasts.
|
||||
* @param listener Listener to notify when audio capabilities change.
|
||||
*/
|
||||
public AudioCapabilitiesReceiver(Context context, Listener listener) {
|
||||
this.context = Assertions.checkNotNull(context);
|
||||
this.listener = Assertions.checkNotNull(listener);
|
||||
this.receiver = Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers to notify the listener when audio capabilities change. The current capabilities will
|
||||
* be returned. It is important to call {@link #unregister} so that the listener can be garbage
|
||||
* collected.
|
||||
*
|
||||
* @return Current audio capabilities for the device.
|
||||
*/
|
||||
@SuppressWarnings("InlinedApi")
|
||||
public AudioCapabilities register() {
|
||||
Intent stickyIntent = receiver == null ? null
|
||||
: context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
|
||||
audioCapabilities = AudioCapabilities.getCapabilities(stickyIntent);
|
||||
return audioCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters to stop notifying the listener when audio capabilities change.
|
||||
*/
|
||||
public void unregister() {
|
||||
if (receiver != null) {
|
||||
context.unregisterReceiver(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!isInitialStickyBroadcast()) {
|
||||
AudioCapabilities newAudioCapabilities = AudioCapabilities.getCapabilities(intent);
|
||||
if (!newAudioCapabilities.equals(audioCapabilities)) {
|
||||
audioCapabilities = newAudioCapabilities;
|
||||
listener.onAudioCapabilitiesChanged(newAudioCapabilities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-104
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.C;
|
||||
import com.b44t.messenger.exoplayer.SampleSource;
|
||||
import com.b44t.messenger.exoplayer.SampleSource.SampleSourceReader;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Interface for callbacks to be notified of chunk based {@link SampleSource} events.
|
||||
*/
|
||||
public interface BaseChunkSampleSourceEventListener {
|
||||
|
||||
/**
|
||||
* Invoked when an upstream load is started.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param length The length of the data being loaded in bytes, or {@link C#LENGTH_UNBOUNDED} if
|
||||
* the length of the data is not known in advance.
|
||||
* @param type The type of the data being loaded.
|
||||
* @param trigger The reason for the data being loaded.
|
||||
* @param format The particular format to which this data corresponds, or null if the data being
|
||||
* loaded does not correspond to a format.
|
||||
* @param mediaStartTimeMs The media time of the start of the data being loaded, or -1 if this
|
||||
* load is for initialization data.
|
||||
* @param mediaEndTimeMs The media time of the end of the data being loaded, or -1 if this
|
||||
* load is for initialization data.
|
||||
*/
|
||||
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs);
|
||||
|
||||
/**
|
||||
* Invoked when the current load operation completes.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param bytesLoaded The number of bytes that were loaded.
|
||||
* @param type The type of the loaded data.
|
||||
* @param trigger The reason for the data being loaded.
|
||||
* @param format The particular format to which this data corresponds, or null if the loaded data
|
||||
* does not correspond to a format.
|
||||
* @param mediaStartTimeMs The media time of the start of the loaded data, or -1 if this load was
|
||||
* for initialization data.
|
||||
* @param mediaEndTimeMs The media time of the end of the loaded data, or -1 if this load was for
|
||||
* initialization data.
|
||||
* @param elapsedRealtimeMs {@code elapsedRealtime} timestamp of when the load finished.
|
||||
* @param loadDurationMs Amount of time taken to load the data.
|
||||
*/
|
||||
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
||||
|
||||
/**
|
||||
* Invoked when the current upstream load operation is canceled.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param bytesLoaded The number of bytes that were loaded prior to the cancellation.
|
||||
*/
|
||||
void onLoadCanceled(int sourceId, long bytesLoaded);
|
||||
|
||||
/**
|
||||
* Invoked when an error occurs loading media data.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param e The cause of the failure.
|
||||
*/
|
||||
void onLoadError(int sourceId, IOException e);
|
||||
|
||||
/**
|
||||
* Invoked when data is removed from the back of the buffer, typically so that it can be
|
||||
* re-buffered using a different representation.
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param mediaStartTimeMs The media time of the start of the discarded data.
|
||||
* @param mediaEndTimeMs The media time of the end of the discarded data.
|
||||
*/
|
||||
void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs);
|
||||
|
||||
/**
|
||||
* Invoked when the downstream format changes (i.e. when the format being supplied to the
|
||||
* caller of {@link SampleSourceReader#readData} changes).
|
||||
*
|
||||
* @param sourceId The id of the reporting {@link SampleSource}.
|
||||
* @param format The format.
|
||||
* @param trigger The trigger specified in the corresponding upstream load, as specified by the
|
||||
* {@link ChunkSource}.
|
||||
* @param mediaTimeMs The media time at which the change occurred.
|
||||
*/
|
||||
void onDownstreamFormatChanged(int sourceId, Format format, int trigger, long mediaTimeMs);
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaFormat;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmInitData;
|
||||
import com.b44t.messenger.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSource;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSpec;
|
||||
|
||||
/**
|
||||
* A base implementation of {@link MediaChunk}, for chunks that contain a single track.
|
||||
* <p>
|
||||
* Loaded samples are output to a {@link DefaultTrackOutput}.
|
||||
*/
|
||||
public abstract class BaseMediaChunk extends MediaChunk {
|
||||
|
||||
/**
|
||||
* Whether {@link #getMediaFormat()} and {@link #getDrmInitData()} can be called at any time to
|
||||
* obtain the chunk's media format and drm initialization data. If false, these methods are only
|
||||
* guaranteed to return correct data after the first sample data has been output from the chunk.
|
||||
*/
|
||||
public final boolean isMediaFormatFinal;
|
||||
|
||||
private DefaultTrackOutput output;
|
||||
private int firstSampleIndex;
|
||||
|
||||
/**
|
||||
* @param dataSource A {@link DataSource} for loading the data.
|
||||
* @param dataSpec Defines the data to be loaded.
|
||||
* @param trigger The reason for this chunk being selected.
|
||||
* @param format The format of the stream to which this chunk belongs.
|
||||
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
|
||||
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can
|
||||
* be called at any time to obtain the media format and drm initialization data. False if
|
||||
* these methods are only guaranteed to return correct data after the first sample data has
|
||||
* been output from the chunk.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, boolean isMediaFormatFinal, int parentId) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
|
||||
this.isMediaFormatFinal = isMediaFormatFinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive
|
||||
* samples as they are loaded.
|
||||
*
|
||||
* @param output The output that will receive the loaded samples.
|
||||
*/
|
||||
public void init(DefaultTrackOutput output) {
|
||||
this.output = output;
|
||||
this.firstSampleIndex = output.getWriteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first sample in the output that was passed to
|
||||
* {@link #init(DefaultTrackOutput)} that will originate from this chunk.
|
||||
*/
|
||||
public final int getFirstSampleIndex() {
|
||||
return firstSampleIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link MediaFormat} corresponding to the chunk.
|
||||
* <p>
|
||||
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
|
||||
* correct data.
|
||||
*
|
||||
* @return The {@link MediaFormat} corresponding to this chunk.
|
||||
*/
|
||||
public abstract MediaFormat getMediaFormat();
|
||||
|
||||
/**
|
||||
* Gets the {@link DrmInitData} corresponding to the chunk.
|
||||
* <p>
|
||||
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
|
||||
* correct data.
|
||||
*
|
||||
* @return The {@link DrmInitData} corresponding to this chunk.
|
||||
*/
|
||||
public abstract DrmInitData getDrmInitData();
|
||||
|
||||
/**
|
||||
* Returns the output most recently passed to {@link #init(DefaultTrackOutput)}.
|
||||
*/
|
||||
protected final DefaultTrackOutput getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSource;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSpec;
|
||||
import com.b44t.messenger.exoplayer.upstream.Loader.Loadable;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
|
||||
/**
|
||||
* An abstract base class for {@link Loadable} implementations that load chunks of data required
|
||||
* for the playback of streams.
|
||||
*/
|
||||
public abstract class Chunk implements Loadable {
|
||||
|
||||
/**
|
||||
* Value of {@link #type} for chunks containing unspecified data.
|
||||
*/
|
||||
public static final int TYPE_UNSPECIFIED = 0;
|
||||
/**
|
||||
* Value of {@link #type} for chunks containing media data.
|
||||
*/
|
||||
public static final int TYPE_MEDIA = 1;
|
||||
/**
|
||||
* Value of {@link #type} for chunks containing media initialization data.
|
||||
*/
|
||||
public static final int TYPE_MEDIA_INITIALIZATION = 2;
|
||||
/**
|
||||
* Value of {@link #type} for chunks containing drm related data.
|
||||
*/
|
||||
public static final int TYPE_DRM = 3;
|
||||
/**
|
||||
* Value of {@link #type} for chunks containing manifest or playlist data.
|
||||
*/
|
||||
public static final int TYPE_MANIFEST = 4;
|
||||
/**
|
||||
* Implementations may define custom {@link #type} codes greater than or equal to this value.
|
||||
*/
|
||||
public static final int TYPE_CUSTOM_BASE = 10000;
|
||||
|
||||
/**
|
||||
* Value of {@link #trigger} for a load whose reason is unspecified.
|
||||
*/
|
||||
public static final int TRIGGER_UNSPECIFIED = 0;
|
||||
/**
|
||||
* Value of {@link #trigger} for a load triggered by an initial format selection.
|
||||
*/
|
||||
public static final int TRIGGER_INITIAL = 1;
|
||||
/**
|
||||
* Value of {@link #trigger} for a load triggered by a user initiated format selection.
|
||||
*/
|
||||
public static final int TRIGGER_MANUAL = 2;
|
||||
/**
|
||||
* Value of {@link #trigger} for a load triggered by an adaptive format selection.
|
||||
*/
|
||||
public static final int TRIGGER_ADAPTIVE = 3;
|
||||
/**
|
||||
* Value of {@link #trigger} for a load triggered whilst in a trick play mode.
|
||||
*/
|
||||
public static final int TRIGGER_TRICK_PLAY = 4;
|
||||
/**
|
||||
* Implementations may define custom {@link #trigger} codes greater than or equal to this value.
|
||||
*/
|
||||
public static final int TRIGGER_CUSTOM_BASE = 10000;
|
||||
/**
|
||||
* Value of {@link #parentId} if no parent id need be specified.
|
||||
*/
|
||||
public static final int NO_PARENT_ID = -1;
|
||||
|
||||
/**
|
||||
* The type of the chunk. For reporting only.
|
||||
*/
|
||||
public final int type;
|
||||
/**
|
||||
* The reason why the chunk was generated. For reporting only.
|
||||
*/
|
||||
public final int trigger;
|
||||
/**
|
||||
* The format associated with the data being loaded, or null if the data being loaded is not
|
||||
* associated with a specific format.
|
||||
*/
|
||||
public final Format format;
|
||||
/**
|
||||
* The {@link DataSpec} that defines the data to be loaded.
|
||||
*/
|
||||
public final DataSpec dataSpec;
|
||||
/**
|
||||
* Optional identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public final int parentId;
|
||||
|
||||
protected final DataSource dataSource;
|
||||
|
||||
/**
|
||||
* @param dataSource The source from which the data should be loaded.
|
||||
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
|
||||
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
|
||||
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
|
||||
* {@link Integer#MAX_VALUE}.
|
||||
* @param type See {@link #type}.
|
||||
* @param trigger See {@link #trigger}.
|
||||
* @param format See {@link #format}.
|
||||
* @param parentId See {@link #parentId}.
|
||||
*/
|
||||
public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format,
|
||||
int parentId) {
|
||||
this.dataSource = Assertions.checkNotNull(dataSource);
|
||||
this.dataSpec = Assertions.checkNotNull(dataSpec);
|
||||
this.type = type;
|
||||
this.trigger = trigger;
|
||||
this.format = format;
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes that have been loaded.
|
||||
*
|
||||
* @return The number of bytes that have been loaded.
|
||||
*/
|
||||
public abstract long bytesLoaded();
|
||||
|
||||
}
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaFormat;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmInitData;
|
||||
import com.b44t.messenger.exoplayer.extractor.Extractor;
|
||||
import com.b44t.messenger.exoplayer.extractor.ExtractorInput;
|
||||
import com.b44t.messenger.exoplayer.extractor.ExtractorOutput;
|
||||
import com.b44t.messenger.exoplayer.extractor.SeekMap;
|
||||
import com.b44t.messenger.exoplayer.extractor.TrackOutput;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
import com.b44t.messenger.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} wrapper for loading chunks containing a single track.
|
||||
* <p>
|
||||
* The wrapper allows switching of the {@link SingleTrackOutput} that receives parsed data.
|
||||
*/
|
||||
public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput {
|
||||
|
||||
/**
|
||||
* Receives stream level data extracted by the wrapped {@link Extractor}.
|
||||
*/
|
||||
public interface SingleTrackOutput extends TrackOutput {
|
||||
|
||||
/**
|
||||
* @see ExtractorOutput#seekMap(SeekMap)
|
||||
*/
|
||||
void seekMap(SeekMap seekMap);
|
||||
|
||||
/**
|
||||
* @see ExtractorOutput#drmInitData(DrmInitData)
|
||||
*/
|
||||
void drmInitData(DrmInitData drmInitData);
|
||||
|
||||
}
|
||||
|
||||
private final Extractor extractor;
|
||||
private boolean extractorInitialized;
|
||||
private SingleTrackOutput output;
|
||||
|
||||
// Accessed only on the loader thread.
|
||||
private boolean seenTrack;
|
||||
|
||||
/**
|
||||
* @param extractor The extractor to wrap.
|
||||
*/
|
||||
public ChunkExtractorWrapper(Extractor extractor) {
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the extractor to output to the provided {@link SingleTrackOutput}, and configures
|
||||
* it to receive data from a new chunk.
|
||||
*
|
||||
* @param output The {@link SingleTrackOutput} that will receive the parsed data.
|
||||
*/
|
||||
public void init(SingleTrackOutput output) {
|
||||
this.output = output;
|
||||
if (!extractorInitialized) {
|
||||
extractor.init(this);
|
||||
extractorInitialized = true;
|
||||
} else {
|
||||
extractor.seek();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the provided {@link ExtractorInput}.
|
||||
*
|
||||
* @param input The {@link ExtractorInput} from which to read.
|
||||
* @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}.
|
||||
* @throws IOException If an error occurred reading from the source.
|
||||
* @throws InterruptedException If the thread was interrupted.
|
||||
*/
|
||||
public int read(ExtractorInput input) throws IOException, InterruptedException {
|
||||
int result = extractor.read(input, null);
|
||||
Assertions.checkState(result != Extractor.RESULT_SEEK);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ExtractorOutput implementation.
|
||||
|
||||
@Override
|
||||
public TrackOutput track(int id) {
|
||||
Assertions.checkState(!seenTrack);
|
||||
seenTrack = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTracks() {
|
||||
Assertions.checkState(seenTrack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekMap(SeekMap seekMap) {
|
||||
output.seekMap(seekMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drmInitData(DrmInitData drmInitData) {
|
||||
output.drmInitData(drmInitData);
|
||||
}
|
||||
|
||||
// TrackOutput implementation.
|
||||
|
||||
@Override
|
||||
public void format(MediaFormat format) {
|
||||
output.format(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
|
||||
throws IOException, InterruptedException {
|
||||
return output.sampleData(input, length, allowEndOfInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleData(ParsableByteArray data, int length) {
|
||||
output.sampleData(data, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
output.sampleMetadata(timeUs, flags, size, offset, encryptionKey);
|
||||
}
|
||||
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
/**
|
||||
* Holds a chunk operation, which consists of a either:
|
||||
* <ul>
|
||||
* <li>The number of {@link MediaChunk}s that should be retained on the queue ({@link #queueSize})
|
||||
* together with the next {@link Chunk} to load ({@link #chunk}). {@link #chunk} may be null if the
|
||||
* next chunk cannot be provided yet.</li>
|
||||
* <li>A flag indicating that the end of the stream has been reached ({@link #endOfStream}).</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ChunkOperationHolder {
|
||||
|
||||
/**
|
||||
* The number of {@link MediaChunk}s to retain in a queue.
|
||||
*/
|
||||
public int queueSize;
|
||||
|
||||
/**
|
||||
* The chunk.
|
||||
*/
|
||||
public Chunk chunk;
|
||||
|
||||
/**
|
||||
* Indicates that the end of the stream has been reached.
|
||||
*/
|
||||
public boolean endOfStream;
|
||||
|
||||
/**
|
||||
* Clears the holder.
|
||||
*/
|
||||
public void clear() {
|
||||
queueSize = 0;
|
||||
chunk = null;
|
||||
endOfStream = false;
|
||||
}
|
||||
|
||||
}
|
||||
-688
@@ -1,688 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.C;
|
||||
import com.b44t.messenger.exoplayer.LoadControl;
|
||||
import com.b44t.messenger.exoplayer.MediaFormat;
|
||||
import com.b44t.messenger.exoplayer.MediaFormatHolder;
|
||||
import com.b44t.messenger.exoplayer.SampleHolder;
|
||||
import com.b44t.messenger.exoplayer.SampleSource;
|
||||
import com.b44t.messenger.exoplayer.SampleSource.SampleSourceReader;
|
||||
import com.b44t.messenger.exoplayer.TrackRenderer;
|
||||
import com.b44t.messenger.exoplayer.extractor.DefaultTrackOutput;
|
||||
import com.b44t.messenger.exoplayer.upstream.Loader;
|
||||
import com.b44t.messenger.exoplayer.upstream.Loader.Loadable;
|
||||
import com.b44t.messenger.exoplayer.util.Assertions;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link SampleSource} that loads media in {@link Chunk}s, which are themselves obtained from a
|
||||
* {@link ChunkSource}.
|
||||
*/
|
||||
public class ChunkSampleSource implements SampleSource, SampleSourceReader, Loader.Callback {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link ChunkSampleSource} events.
|
||||
*/
|
||||
public interface EventListener extends BaseChunkSampleSourceEventListener {}
|
||||
|
||||
/**
|
||||
* The default minimum number of times to retry loading data prior to failing.
|
||||
*/
|
||||
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
|
||||
|
||||
protected final DefaultTrackOutput sampleQueue;
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_INITIALIZED = 1;
|
||||
private static final int STATE_PREPARED = 2;
|
||||
private static final int STATE_ENABLED = 3;
|
||||
|
||||
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
|
||||
|
||||
private final int eventSourceId;
|
||||
private final LoadControl loadControl;
|
||||
private final ChunkSource chunkSource;
|
||||
private final ChunkOperationHolder currentLoadableHolder;
|
||||
private final LinkedList<BaseMediaChunk> mediaChunks;
|
||||
private final List<BaseMediaChunk> readOnlyMediaChunks;
|
||||
private final int bufferSizeContribution;
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final int minLoadableRetryCount;
|
||||
|
||||
private int state;
|
||||
private long downstreamPositionUs;
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingResetPositionUs;
|
||||
private long lastPerformedBufferOperation;
|
||||
private boolean pendingDiscontinuity;
|
||||
|
||||
private Loader loader;
|
||||
private boolean loadingFinished;
|
||||
private IOException currentLoadableException;
|
||||
private int enabledTrackCount;
|
||||
private int currentLoadableExceptionCount;
|
||||
private long currentLoadableExceptionTimestamp;
|
||||
private long currentLoadStartTimeMs;
|
||||
|
||||
private MediaFormat downstreamMediaFormat;
|
||||
private Format downstreamFormat;
|
||||
|
||||
/**
|
||||
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
|
||||
* @param loadControl Controls when the source is permitted to load data.
|
||||
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
|
||||
*/
|
||||
public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl,
|
||||
int bufferSizeContribution) {
|
||||
this(chunkSource, loadControl, bufferSizeContribution, null, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
|
||||
* @param loadControl Controls when the source is permitted to load data.
|
||||
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
|
||||
* @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 eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
||||
*/
|
||||
public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl,
|
||||
int bufferSizeContribution, Handler eventHandler, EventListener eventListener,
|
||||
int eventSourceId) {
|
||||
this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener,
|
||||
eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
|
||||
* @param loadControl Controls when the source is permitted to load data.
|
||||
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
|
||||
* @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 eventSourceId An identifier that gets passed to {@code eventListener} methods.
|
||||
* @param minLoadableRetryCount The minimum number of times that the source should retry a load
|
||||
* before propagating an error.
|
||||
*/
|
||||
public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl,
|
||||
int bufferSizeContribution, Handler eventHandler, EventListener eventListener,
|
||||
int eventSourceId, int minLoadableRetryCount) {
|
||||
this.chunkSource = chunkSource;
|
||||
this.loadControl = loadControl;
|
||||
this.bufferSizeContribution = bufferSizeContribution;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.eventSourceId = eventSourceId;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
currentLoadableHolder = new ChunkOperationHolder();
|
||||
mediaChunks = new LinkedList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||
state = STATE_IDLE;
|
||||
pendingResetPositionUs = NO_RESET_PENDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleSourceReader register() {
|
||||
Assertions.checkState(state == STATE_IDLE);
|
||||
state = STATE_INITIALIZED;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(long positionUs) {
|
||||
Assertions.checkState(state == STATE_INITIALIZED || state == STATE_PREPARED);
|
||||
if (state == STATE_PREPARED) {
|
||||
return true;
|
||||
} else if (!chunkSource.prepare()) {
|
||||
return false;
|
||||
}
|
||||
if (chunkSource.getTrackCount() > 0) {
|
||||
loader = new Loader("Loader:" + chunkSource.getFormat(0).mimeType);
|
||||
}
|
||||
state = STATE_PREPARED;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackCount() {
|
||||
Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED);
|
||||
return chunkSource.getTrackCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getFormat(int track) {
|
||||
Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED);
|
||||
return chunkSource.getFormat(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable(int track, long positionUs) {
|
||||
Assertions.checkState(state == STATE_PREPARED);
|
||||
Assertions.checkState(enabledTrackCount++ == 0);
|
||||
state = STATE_ENABLED;
|
||||
chunkSource.enable(track);
|
||||
loadControl.register(this, bufferSizeContribution);
|
||||
downstreamFormat = null;
|
||||
downstreamMediaFormat = null;
|
||||
downstreamPositionUs = positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingDiscontinuity = false;
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(int track) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
Assertions.checkState(--enabledTrackCount == 0);
|
||||
state = STATE_PREPARED;
|
||||
try {
|
||||
chunkSource.disable(mediaChunks);
|
||||
} finally {
|
||||
loadControl.unregister(this);
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
sampleQueue.clear();
|
||||
mediaChunks.clear();
|
||||
clearCurrentLoadable();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean continueBuffering(int track, long positionUs) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
downstreamPositionUs = positionUs;
|
||||
chunkSource.continueBuffering(positionUs);
|
||||
updateLoadControl();
|
||||
return loadingFinished || !sampleQueue.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readDiscontinuity(int track) {
|
||||
if (pendingDiscontinuity) {
|
||||
pendingDiscontinuity = false;
|
||||
return lastSeekPositionUs;
|
||||
}
|
||||
return NO_DISCONTINUITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
|
||||
SampleHolder sampleHolder) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
downstreamPositionUs = positionUs;
|
||||
|
||||
if (pendingDiscontinuity || isPendingReset()) {
|
||||
return NOTHING_READ;
|
||||
}
|
||||
|
||||
boolean haveSamples = !sampleQueue.isEmpty();
|
||||
BaseMediaChunk currentChunk = mediaChunks.getFirst();
|
||||
while (haveSamples && mediaChunks.size() > 1
|
||||
&& mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) {
|
||||
mediaChunks.removeFirst();
|
||||
currentChunk = mediaChunks.getFirst();
|
||||
}
|
||||
|
||||
Format format = currentChunk.format;
|
||||
if (!format.equals(downstreamFormat)) {
|
||||
notifyDownstreamFormatChanged(format, currentChunk.trigger, currentChunk.startTimeUs);
|
||||
}
|
||||
downstreamFormat = format;
|
||||
|
||||
if (haveSamples || currentChunk.isMediaFormatFinal) {
|
||||
MediaFormat mediaFormat = currentChunk.getMediaFormat();
|
||||
if (!mediaFormat.equals(downstreamMediaFormat)) {
|
||||
formatHolder.format = mediaFormat;
|
||||
formatHolder.drmInitData = currentChunk.getDrmInitData();
|
||||
downstreamMediaFormat = mediaFormat;
|
||||
return FORMAT_READ;
|
||||
}
|
||||
// If mediaFormat and downstreamMediaFormat are equal but different objects then the equality
|
||||
// check above will have been expensive, comparing the fields in each format. We update
|
||||
// downstreamMediaFormat here so that referential equality can be cheaply established during
|
||||
// subsequent calls.
|
||||
downstreamMediaFormat = mediaFormat;
|
||||
}
|
||||
|
||||
if (!haveSamples) {
|
||||
if (loadingFinished) {
|
||||
return END_OF_STREAM;
|
||||
}
|
||||
return NOTHING_READ;
|
||||
}
|
||||
|
||||
if (sampleQueue.getSample(sampleHolder)) {
|
||||
boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs;
|
||||
sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0;
|
||||
onSampleRead(currentChunk, sampleHolder);
|
||||
return SAMPLE_READ;
|
||||
}
|
||||
|
||||
return NOTHING_READ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
|
||||
long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
lastSeekPositionUs = positionUs;
|
||||
if (currentPositionUs == positionUs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not pending a reset, see if we can seek within the sample queue.
|
||||
boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs);
|
||||
if (seekInsideBuffer) {
|
||||
// We succeeded. All we need to do is discard any chunks that we've moved past.
|
||||
boolean haveSamples = !sampleQueue.isEmpty();
|
||||
while (haveSamples && mediaChunks.size() > 1
|
||||
&& mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) {
|
||||
mediaChunks.removeFirst();
|
||||
}
|
||||
} else {
|
||||
// We failed, and need to restart.
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
// Either way, we need to send a discontinuity to the downstream components.
|
||||
pendingDiscontinuity = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
|
||||
throw currentLoadableException;
|
||||
} else if (currentLoadableHolder.chunk == null) {
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPositionUs() {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
if (isPendingReset()) {
|
||||
return pendingResetPositionUs;
|
||||
} else if (loadingFinished) {
|
||||
return TrackRenderer.END_OF_TRACK_US;
|
||||
} else {
|
||||
long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs();
|
||||
return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
|
||||
: largestParsedTimestampUs;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
Assertions.checkState(state != STATE_ENABLED);
|
||||
if (loader != null) {
|
||||
loader.release();
|
||||
loader = null;
|
||||
}
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(Loadable loadable) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
long loadDurationMs = now - currentLoadStartTimeMs;
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
chunkSource.onChunkLoadCompleted(currentLoadable);
|
||||
if (isMediaChunk(currentLoadable)) {
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
|
||||
notifyLoadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger,
|
||||
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, loadDurationMs);
|
||||
} else {
|
||||
notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type,
|
||||
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
|
||||
}
|
||||
clearCurrentLoadable();
|
||||
updateLoadControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(Loadable loadable) {
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
notifyLoadCanceled(currentLoadable.bytesLoaded());
|
||||
clearCurrentLoadable();
|
||||
if (state == STATE_ENABLED) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
} else {
|
||||
sampleQueue.clear();
|
||||
mediaChunks.clear();
|
||||
clearCurrentLoadable();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadError(Loadable loadable, IOException e) {
|
||||
currentLoadableException = e;
|
||||
currentLoadableExceptionCount++;
|
||||
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
|
||||
notifyLoadError(e);
|
||||
chunkSource.onChunkLoadError(currentLoadableHolder.chunk, e);
|
||||
updateLoadControl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a sample has been read. Can be used to perform any modifications necessary before
|
||||
* the sample is returned.
|
||||
*
|
||||
* @param mediaChunk The chunk from which the sample was obtained.
|
||||
* @param sampleHolder Holds the read sample.
|
||||
*/
|
||||
protected void onSampleRead(MediaChunk mediaChunk, SampleHolder sampleHolder) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private void restartFrom(long positionUs) {
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
sampleQueue.clear();
|
||||
mediaChunks.clear();
|
||||
clearCurrentLoadable();
|
||||
updateLoadControl();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCurrentLoadable() {
|
||||
currentLoadableHolder.chunk = null;
|
||||
clearCurrentLoadableException();
|
||||
}
|
||||
|
||||
private void clearCurrentLoadableException() {
|
||||
currentLoadableException = null;
|
||||
currentLoadableExceptionCount = 0;
|
||||
}
|
||||
|
||||
private void updateLoadControl() {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
long nextLoadPositionUs = getNextLoadPositionUs();
|
||||
boolean isBackedOff = currentLoadableException != null;
|
||||
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
|
||||
|
||||
// If we're not loading or backed off, evaluate the operation if (a) we don't have the next
|
||||
// chunk yet and we're not finished, or (b) if the last evaluation was over 2000ms ago.
|
||||
if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|
||||
|| (now - lastPerformedBufferOperation > 2000))) {
|
||||
// Perform the evaluation.
|
||||
lastPerformedBufferOperation = now;
|
||||
doChunkOperation();
|
||||
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
|
||||
// Update the next load position as appropriate.
|
||||
if (currentLoadableHolder.chunk == null) {
|
||||
// Set loadPosition to -1 to indicate that we don't have anything to load.
|
||||
nextLoadPositionUs = -1;
|
||||
} else if (chunksDiscarded) {
|
||||
// Chunks were discarded, so we need to re-evaluate the load position.
|
||||
nextLoadPositionUs = getNextLoadPositionUs();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the control with our current state, and determine whether we're the next loader.
|
||||
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
|
||||
loadingOrBackedOff);
|
||||
|
||||
if (isBackedOff) {
|
||||
long elapsedMillis = now - currentLoadableExceptionTimestamp;
|
||||
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
|
||||
resumeFromBackOff();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loader.isLoading() && nextLoader) {
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next load time, assuming that the next load starts where the previous chunk ended (or
|
||||
* from the pending reset time, if there is one).
|
||||
*/
|
||||
private long getNextLoadPositionUs() {
|
||||
if (isPendingReset()) {
|
||||
return pendingResetPositionUs;
|
||||
} else {
|
||||
return loadingFinished ? -1 : mediaChunks.getLast().endTimeUs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes loading.
|
||||
* <p>
|
||||
* If the {@link ChunkSource} returns a chunk equivalent to the backed off chunk B, then the
|
||||
* loading of B will be resumed. In all other cases B will be discarded and the new chunk will
|
||||
* be loaded.
|
||||
*/
|
||||
private void resumeFromBackOff() {
|
||||
currentLoadableException = null;
|
||||
|
||||
Chunk backedOffChunk = currentLoadableHolder.chunk;
|
||||
if (!isMediaChunk(backedOffChunk)) {
|
||||
doChunkOperation();
|
||||
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
|
||||
if (currentLoadableHolder.chunk == backedOffChunk) {
|
||||
// Chunk was unchanged. Resume loading.
|
||||
loader.startLoading(backedOffChunk, this);
|
||||
} else {
|
||||
// Chunk was changed. Notify that the existing load was canceled.
|
||||
notifyLoadCanceled(backedOffChunk.bytesLoaded());
|
||||
// Start loading the replacement.
|
||||
maybeStartLoading();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (backedOffChunk == mediaChunks.getFirst()) {
|
||||
// We're not able to clear the first media chunk, so we have no choice but to continue
|
||||
// loading it.
|
||||
loader.startLoading(backedOffChunk, this);
|
||||
return;
|
||||
}
|
||||
|
||||
// The current loadable is the last media chunk. Remove it before we invoke the chunk source,
|
||||
// and add it back again afterwards.
|
||||
BaseMediaChunk removedChunk = mediaChunks.removeLast();
|
||||
Assertions.checkState(backedOffChunk == removedChunk);
|
||||
doChunkOperation();
|
||||
mediaChunks.add(removedChunk);
|
||||
|
||||
if (currentLoadableHolder.chunk == backedOffChunk) {
|
||||
// Chunk was unchanged. Resume loading.
|
||||
loader.startLoading(backedOffChunk, this);
|
||||
} else {
|
||||
// Chunk was changed. Notify that the existing load was canceled.
|
||||
notifyLoadCanceled(backedOffChunk.bytesLoaded());
|
||||
// This call will remove and release at least one chunk from the end of mediaChunks. Since
|
||||
// the current loadable is the last media chunk, it is guaranteed to be removed.
|
||||
discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
|
||||
clearCurrentLoadableException();
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeStartLoading() {
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
if (currentLoadable == null) {
|
||||
// Nothing to load.
|
||||
return;
|
||||
}
|
||||
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
|
||||
if (isMediaChunk(currentLoadable)) {
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
|
||||
mediaChunk.init(sampleQueue);
|
||||
mediaChunks.add(mediaChunk);
|
||||
if (isPendingReset()) {
|
||||
pendingResetPositionUs = NO_RESET_PENDING;
|
||||
}
|
||||
notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
|
||||
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
|
||||
} else {
|
||||
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
|
||||
currentLoadable.trigger, currentLoadable.format, -1, -1);
|
||||
}
|
||||
loader.startLoading(currentLoadable, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the {@link #currentLoadableHolder}, passes it to the chunk source to cause it to be
|
||||
* updated with the next operation, and updates {@link #loadingFinished} if the end of the stream
|
||||
* is reached.
|
||||
*/
|
||||
private void doChunkOperation() {
|
||||
currentLoadableHolder.endOfStream = false;
|
||||
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
|
||||
chunkSource.getChunkOperation(readOnlyMediaChunks,
|
||||
pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs,
|
||||
currentLoadableHolder);
|
||||
loadingFinished = currentLoadableHolder.endOfStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard upstream media chunks until the queue length is equal to the length specified.
|
||||
*
|
||||
* @param queueLength The desired length of the queue.
|
||||
* @return True if chunks were discarded. False otherwise.
|
||||
*/
|
||||
private boolean discardUpstreamMediaChunks(int queueLength) {
|
||||
if (mediaChunks.size() <= queueLength) {
|
||||
return false;
|
||||
}
|
||||
long startTimeUs = 0;
|
||||
long endTimeUs = mediaChunks.getLast().endTimeUs;
|
||||
|
||||
BaseMediaChunk removed = null;
|
||||
while (mediaChunks.size() > queueLength) {
|
||||
removed = mediaChunks.removeLast();
|
||||
startTimeUs = removed.startTimeUs;
|
||||
loadingFinished = false;
|
||||
}
|
||||
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
|
||||
|
||||
notifyUpstreamDiscarded(startTimeUs, endTimeUs);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMediaChunk(Chunk chunk) {
|
||||
return chunk instanceof BaseMediaChunk;
|
||||
}
|
||||
|
||||
private boolean isPendingReset() {
|
||||
return pendingResetPositionUs != NO_RESET_PENDING;
|
||||
}
|
||||
|
||||
private long getRetryDelayMillis(long errorCount) {
|
||||
return Math.min((errorCount - 1) * 1000, 5000);
|
||||
}
|
||||
|
||||
protected final long usToMs(long timeUs) {
|
||||
return timeUs / 1000;
|
||||
}
|
||||
|
||||
private void notifyLoadStarted(final long length, final int type, final int trigger,
|
||||
final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onLoadStarted(eventSourceId, length, type, trigger, format,
|
||||
usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLoadCompleted(final long bytesLoaded, final int type, final int trigger,
|
||||
final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs,
|
||||
final long elapsedRealtimeMs, final long loadDurationMs) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onLoadCompleted(eventSourceId, bytesLoaded, type, trigger, format,
|
||||
usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLoadCanceled(final long bytesLoaded) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onLoadCanceled(eventSourceId, bytesLoaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyLoadError(final IOException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onLoadError(eventSourceId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyUpstreamDiscarded(final long mediaStartTimeUs, final long mediaEndTimeUs) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onUpstreamDiscarded(eventSourceId, usToMs(mediaStartTimeUs),
|
||||
usToMs(mediaEndTimeUs));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDownstreamFormatChanged(final Format format, final int trigger,
|
||||
final long positionUs) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger,
|
||||
usToMs(positionUs));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaFormat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A provider of {@link Chunk}s for a {@link ChunkSampleSource} to load.
|
||||
*/
|
||||
/*
|
||||
* TODO: Share more state between this interface and {@link ChunkSampleSource}. In particular
|
||||
* implementations of this class needs to know about errors, and should be more tightly integrated
|
||||
* into the process of resuming loading of a chunk after an error occurs.
|
||||
*/
|
||||
public interface ChunkSource {
|
||||
|
||||
/**
|
||||
* If the source is currently having difficulty preparing or providing chunks, then this method
|
||||
* throws the underlying error. Otherwise does nothing.
|
||||
*
|
||||
* @throws IOException The underlying error.
|
||||
*/
|
||||
void maybeThrowError() throws IOException;
|
||||
|
||||
/**
|
||||
* Prepares the source.
|
||||
* <p>
|
||||
* The method can be called repeatedly until the return value indicates success.
|
||||
*
|
||||
* @return True if the source was prepared, false otherwise.
|
||||
*/
|
||||
boolean prepare();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int getTrackCount();
|
||||
|
||||
/**
|
||||
* Gets the format of the specified track.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared.
|
||||
*
|
||||
* @param track The track index.
|
||||
* @return The format of the track.
|
||||
*/
|
||||
MediaFormat getFormat(int track);
|
||||
|
||||
/**
|
||||
* Enable the source for the specified track.
|
||||
* <p>
|
||||
* This method should only be called after the source has been prepared, and when the source is
|
||||
* disabled.
|
||||
*
|
||||
* @param track The track index.
|
||||
*/
|
||||
void enable(int track);
|
||||
|
||||
/**
|
||||
* Indicates to the source that it should still be checking for updates to the stream.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param playbackPositionUs The current playback position.
|
||||
*/
|
||||
void continueBuffering(long playbackPositionUs);
|
||||
|
||||
/**
|
||||
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
|
||||
* be performed by the calling {@link ChunkSampleSource}.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param queue A representation of the currently buffered {@link MediaChunk}s.
|
||||
* @param playbackPositionUs The current playback position. If the queue is empty then this
|
||||
* parameter is the position from which playback is expected to start (or restart) and hence
|
||||
* should be interpreted as a seek position.
|
||||
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#endOfStream} is
|
||||
* initially set to false, whose {@link ChunkOperationHolder#queueSize} is initially equal to
|
||||
* the length of the queue, and whose {@link ChunkOperationHolder#chunk} is initially equal to
|
||||
* null or a {@link Chunk} previously supplied by the {@link ChunkSource} that the caller has
|
||||
* not yet finished loading. In the latter case the chunk can either be replaced or left
|
||||
* unchanged. Note that leaving the chunk unchanged is both preferred and more efficient than
|
||||
* replacing it with a new but identical chunk.
|
||||
*/
|
||||
void getChunkOperation(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
ChunkOperationHolder out);
|
||||
|
||||
/**
|
||||
* Invoked when the {@link ChunkSampleSource} has finished loading a chunk obtained from this
|
||||
* source.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param chunk The chunk whose load has been completed.
|
||||
*/
|
||||
void onChunkLoadCompleted(Chunk chunk);
|
||||
|
||||
/**
|
||||
* Invoked when the {@link ChunkSampleSource} encounters an error loading a chunk obtained from
|
||||
* this source.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param chunk The chunk whose load encountered the error.
|
||||
* @param e The error.
|
||||
*/
|
||||
void onChunkLoadError(Chunk chunk, Exception e);
|
||||
|
||||
/**
|
||||
* Disables the source.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
*
|
||||
* @param queue A representation of the currently buffered {@link MediaChunk}s.
|
||||
*/
|
||||
void disable(List<? extends MediaChunk> queue);
|
||||
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* 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 com.b44t.messenger.exoplayer.chunk;
|
||||
|
||||
import com.b44t.messenger.exoplayer.MediaFormat;
|
||||
import com.b44t.messenger.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput;
|
||||
import com.b44t.messenger.exoplayer.drm.DrmInitData;
|
||||
import com.b44t.messenger.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.b44t.messenger.exoplayer.extractor.Extractor;
|
||||
import com.b44t.messenger.exoplayer.extractor.ExtractorInput;
|
||||
import com.b44t.messenger.exoplayer.extractor.SeekMap;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSource;
|
||||
import com.b44t.messenger.exoplayer.upstream.DataSpec;
|
||||
import com.b44t.messenger.exoplayer.util.ParsableByteArray;
|
||||
import com.b44t.messenger.exoplayer.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@link BaseMediaChunk} that uses an {@link Extractor} to parse sample data.
|
||||
*/
|
||||
public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOutput {
|
||||
|
||||
private final ChunkExtractorWrapper extractorWrapper;
|
||||
private final long sampleOffsetUs;
|
||||
private final int adaptiveMaxWidth;
|
||||
private final int adaptiveMaxHeight;
|
||||
|
||||
private MediaFormat mediaFormat;
|
||||
private DrmInitData drmInitData;
|
||||
|
||||
private volatile int bytesLoaded;
|
||||
private volatile boolean loadCanceled;
|
||||
|
||||
/**
|
||||
* @param dataSource A {@link DataSource} for loading the data.
|
||||
* @param dataSpec Defines the data to be loaded.
|
||||
* @param trigger The reason for this chunk being selected.
|
||||
* @param format The format of the stream to which this chunk belongs.
|
||||
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
|
||||
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
|
||||
* @param extractorWrapper A wrapped extractor to use for parsing the data.
|
||||
* @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is
|
||||
* known to define its own format.
|
||||
* @param adaptiveMaxWidth If this chunk contains video and is part of an adaptive playback, this
|
||||
* is the maximum width of the video in pixels that will be encountered during the playback.
|
||||
* {@link MediaFormat#NO_VALUE} otherwise.
|
||||
* @param adaptiveMaxHeight If this chunk contains video and is part of an adaptive playback, this
|
||||
* is the maximum height of the video in pixels that will be encountered during the playback.
|
||||
* {@link MediaFormat#NO_VALUE} otherwise.
|
||||
* @param drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm
|
||||
* protected. May also be null if the data is known to define its own initialization data.
|
||||
* @param isMediaFormatFinal True if {@code mediaFormat} and {@code drmInitData} are known to be
|
||||
* correct and final. False if the data may define its own format or initialization data.
|
||||
* @param parentId Identifier for a parent from which this chunk originates.
|
||||
*/
|
||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
||||
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth,
|
||||
int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) {
|
||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
||||
isMediaFormatFinal, parentId);
|
||||
this.extractorWrapper = extractorWrapper;
|
||||
this.sampleOffsetUs = sampleOffsetUs;
|
||||
this.adaptiveMaxWidth = adaptiveMaxWidth;
|
||||
this.adaptiveMaxHeight = adaptiveMaxHeight;
|
||||
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
|
||||
adaptiveMaxHeight);
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long bytesLoaded() {
|
||||
return bytesLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final MediaFormat getMediaFormat() {
|
||||
return mediaFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DrmInitData getDrmInitData() {
|
||||
return drmInitData;
|
||||
}
|
||||
|
||||
// SingleTrackOutput implementation.
|
||||
|
||||
@Override
|
||||
public final void seekMap(SeekMap seekMap) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void drmInitData(DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void format(MediaFormat mediaFormat) {
|
||||
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
|
||||
adaptiveMaxHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
|
||||
throws IOException, InterruptedException {
|
||||
return getOutput().sampleData(input, length, allowEndOfInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void sampleData(ParsableByteArray data, int length) {
|
||||
getOutput().sampleData(data, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void sampleMetadata(long timeUs, int flags, int size, int offset,
|
||||
byte[] encryptionKey) {
|
||||
getOutput().sampleMetadata(timeUs + sampleOffsetUs, flags, size, offset, encryptionKey);
|
||||
}
|
||||
|
||||
// Loadable implementation.
|
||||
|
||||
@Override
|
||||
public final void cancelLoad() {
|
||||
loadCanceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isLoadCanceled() {
|
||||
return loadCanceled;
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonAtomicVolatileUpdate")
|
||||
@Override
|
||||
public final void load() throws IOException, InterruptedException {
|
||||
DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
|
||||
try {
|
||||
// Create and open the input.
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource,
|
||||
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
|
||||
if (bytesLoaded == 0) {
|
||||
// Set the target to ourselves.
|
||||
extractorWrapper.init(this);
|
||||
}
|
||||
// Load and parse the sample data.
|
||||
try {
|
||||
int result = Extractor.RESULT_CONTINUE;
|
||||
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
||||
result = extractorWrapper.read(input);
|
||||
}
|
||||
} finally {
|
||||
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
|
||||
}
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs,
|
||||
int adaptiveMaxWidth, int adaptiveMaxHeight) {
|
||||
if (format == null) {
|
||||
return null;
|
||||
}
|
||||
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) {
|
||||
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
|
||||
}
|
||||
if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) {
|
||||
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user